emergent_client/types/
timestamp.rs1use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
12#[serde(transparent)]
13pub struct Timestamp(u64);
14
15impl Timestamp {
16 #[must_use]
18 pub const fn from_millis(millis: u64) -> Self {
19 Self(millis)
20 }
21
22 #[must_use]
26 pub fn now() -> Self {
27 SystemTime::now()
28 .duration_since(UNIX_EPOCH)
29 .map_or(Self(0), |d| {
30 Self(u64::try_from(d.as_millis()).unwrap_or(u64::MAX))
31 })
32 }
33
34 #[must_use]
36 pub const fn as_millis(&self) -> u64 {
37 self.0
38 }
39
40 #[must_use]
42 pub const fn as_secs(&self) -> u64 {
43 self.0 / 1000
44 }
45
46 #[must_use]
50 pub const fn duration_since(&self, other: Self) -> Option<u64> {
51 if self.0 >= other.0 {
52 Some(self.0 - other.0)
53 } else {
54 None
55 }
56 }
57
58 #[must_use]
60 pub const fn add_millis(&self, millis: u64) -> Self {
61 Self(self.0.saturating_add(millis))
62 }
63
64 #[must_use]
66 pub const fn sub_millis(&self, millis: u64) -> Self {
67 Self(self.0.saturating_sub(millis))
68 }
69}
70
71impl Default for Timestamp {
72 fn default() -> Self {
73 Self::now()
74 }
75}
76
77impl fmt::Display for Timestamp {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "{}", self.0)
80 }
81}
82
83impl From<u64> for Timestamp {
84 fn from(millis: u64) -> Self {
85 Self::from_millis(millis)
86 }
87}
88
89impl From<Timestamp> for u64 {
90 fn from(ts: Timestamp) -> Self {
91 ts.0
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn from_millis_creates_timestamp() {
101 let ts = Timestamp::from_millis(1_704_067_200_000);
102 assert_eq!(ts.as_millis(), 1_704_067_200_000);
103 }
104
105 #[test]
106 fn now_creates_current_timestamp() {
107 let ts = Timestamp::now();
108 assert!(ts.as_millis() > 0);
109 }
110
111 #[test]
112 fn as_secs_truncates() {
113 let ts = Timestamp::from_millis(5500);
114 assert_eq!(ts.as_secs(), 5);
115 }
116
117 #[test]
118 fn timestamps_are_ordered() {
119 let ts1 = Timestamp::from_millis(1000);
120 let ts2 = Timestamp::from_millis(2000);
121 assert!(ts1 < ts2);
122 }
123
124 #[test]
125 fn conversion_to_u64() {
126 let ts = Timestamp::from_millis(12345);
127 let millis: u64 = ts.into();
128 assert_eq!(millis, 12345);
129 }
130
131 #[test]
132 fn conversion_from_u64() {
133 let ts: Timestamp = 12345_u64.into();
134 assert_eq!(ts.as_millis(), 12345);
135 }
136
137 #[test]
138 fn duration_since() {
139 let ts1 = Timestamp::from_millis(1000);
140 let ts2 = Timestamp::from_millis(2000);
141 assert_eq!(ts2.duration_since(ts1), Some(1000));
142 assert_eq!(ts1.duration_since(ts2), None);
143 }
144
145 #[test]
146 fn add_millis() {
147 let ts = Timestamp::from_millis(1000);
148 assert_eq!(ts.add_millis(500).as_millis(), 1500);
149 }
150
151 #[test]
152 fn sub_millis() {
153 let ts = Timestamp::from_millis(1000);
154 assert_eq!(ts.sub_millis(500).as_millis(), 500);
155 }
156
157 #[test]
158 fn sub_millis_saturates() {
159 let ts = Timestamp::from_millis(100);
160 assert_eq!(ts.sub_millis(500).as_millis(), 0);
161 }
162
163 #[test]
164 fn serde_roundtrip() -> Result<(), serde_json::Error> {
165 let ts = Timestamp::from_millis(1_704_067_200_000);
166 let json = serde_json::to_string(&ts)?;
167 let restored: Timestamp = serde_json::from_str(&json)?;
168 assert_eq!(ts, restored);
169 Ok(())
170 }
171
172 #[test]
173 fn serde_is_transparent() -> Result<(), serde_json::Error> {
174 let ts = Timestamp::from_millis(12345);
175 let json = serde_json::to_string(&ts)?;
176 assert_eq!(json, "12345");
177 Ok(())
178 }
179}