grafeo_common/types/
timestamp.rs1use serde::{Deserialize, Serialize};
6use std::fmt;
7use std::time::{Duration, SystemTime, UNIX_EPOCH};
8
9#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
15#[repr(transparent)]
16pub struct Timestamp(i64);
17
18impl Timestamp {
19 pub const EPOCH: Self = Self(0);
21
22 pub const MIN: Self = Self(i64::MIN);
24
25 pub const MAX: Self = Self(i64::MAX);
27
28 #[inline]
30 #[must_use]
31 pub const fn from_micros(micros: i64) -> Self {
32 Self(micros)
33 }
34
35 #[inline]
37 #[must_use]
38 pub const fn from_millis(millis: i64) -> Self {
39 Self(millis * 1000)
40 }
41
42 #[inline]
44 #[must_use]
45 pub const fn from_secs(secs: i64) -> Self {
46 Self(secs * 1_000_000)
47 }
48
49 #[must_use]
51 pub fn now() -> Self {
52 let duration = SystemTime::now()
53 .duration_since(UNIX_EPOCH)
54 .unwrap_or(Duration::ZERO);
55 Self::from_micros(duration.as_micros() as i64)
56 }
57
58 #[inline]
60 #[must_use]
61 pub const fn as_micros(&self) -> i64 {
62 self.0
63 }
64
65 #[inline]
67 #[must_use]
68 pub const fn as_millis(&self) -> i64 {
69 self.0 / 1000
70 }
71
72 #[inline]
74 #[must_use]
75 pub const fn as_secs(&self) -> i64 {
76 self.0 / 1_000_000
77 }
78
79 #[must_use]
81 pub fn as_system_time(&self) -> Option<SystemTime> {
82 if self.0 >= 0 {
83 Some(UNIX_EPOCH + Duration::from_micros(self.0 as u64))
84 } else {
85 UNIX_EPOCH.checked_sub(Duration::from_micros((-self.0) as u64))
86 }
87 }
88
89 #[must_use]
91 pub const fn add_micros(self, micros: i64) -> Self {
92 Self(self.0.saturating_add(micros))
93 }
94
95 #[must_use]
97 pub const fn sub_micros(self, micros: i64) -> Self {
98 Self(self.0.saturating_sub(micros))
99 }
100
101 #[must_use]
105 pub const fn duration_since(self, other: Self) -> i64 {
106 self.0 - other.0
107 }
108}
109
110impl fmt::Debug for Timestamp {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 write!(f, "Timestamp({}μs)", self.0)
113 }
114}
115
116impl fmt::Display for Timestamp {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 let secs = self.0 / 1_000_000;
120 let micros = (self.0 % 1_000_000).unsigned_abs();
121
122 const SECS_PER_DAY: i64 = 86400;
124 const DAYS_PER_YEAR: i64 = 365;
125
126 let days = secs / SECS_PER_DAY;
127 let time_secs = (secs % SECS_PER_DAY + SECS_PER_DAY) % SECS_PER_DAY;
128
129 let hours = time_secs / 3600;
130 let minutes = (time_secs % 3600) / 60;
131 let seconds = time_secs % 60;
132
133 let year = 1970 + days / DAYS_PER_YEAR;
135 let day_of_year = days % DAYS_PER_YEAR;
136
137 write!(
138 f,
139 "{:04}-{:03}T{:02}:{:02}:{:02}.{:06}Z",
140 year, day_of_year, hours, minutes, seconds, micros
141 )
142 }
143}
144
145impl From<i64> for Timestamp {
146 fn from(micros: i64) -> Self {
147 Self::from_micros(micros)
148 }
149}
150
151impl From<Timestamp> for i64 {
152 fn from(ts: Timestamp) -> Self {
153 ts.0
154 }
155}
156
157impl TryFrom<SystemTime> for Timestamp {
158 type Error = ();
159
160 fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
161 match time.duration_since(UNIX_EPOCH) {
162 Ok(duration) => Ok(Self::from_micros(duration.as_micros() as i64)),
163 Err(e) => Ok(Self::from_micros(-(e.duration().as_micros() as i64))),
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_timestamp_creation() {
174 let ts = Timestamp::from_secs(1000);
175 assert_eq!(ts.as_secs(), 1000);
176 assert_eq!(ts.as_millis(), 1_000_000);
177 assert_eq!(ts.as_micros(), 1_000_000_000);
178
179 let ts = Timestamp::from_millis(1234);
180 assert_eq!(ts.as_millis(), 1234);
181
182 let ts = Timestamp::from_micros(1_234_567);
183 assert_eq!(ts.as_micros(), 1_234_567);
184 }
185
186 #[test]
187 fn test_timestamp_now() {
188 let ts = Timestamp::now();
189 assert!(ts.as_secs() > 1_577_836_800);
191 }
192
193 #[test]
194 fn test_timestamp_arithmetic() {
195 let ts = Timestamp::from_secs(1000);
196
197 let ts2 = ts.add_micros(1_000_000);
198 assert_eq!(ts2.as_secs(), 1001);
199
200 let ts3 = ts.sub_micros(1_000_000);
201 assert_eq!(ts3.as_secs(), 999);
202
203 assert_eq!(ts2.duration_since(ts), 1_000_000);
204 assert_eq!(ts.duration_since(ts2), -1_000_000);
205 }
206
207 #[test]
208 fn test_timestamp_ordering() {
209 let ts1 = Timestamp::from_secs(100);
210 let ts2 = Timestamp::from_secs(200);
211
212 assert!(ts1 < ts2);
213 assert!(ts2 > ts1);
214 assert_eq!(ts1, Timestamp::from_secs(100));
215 }
216
217 #[test]
218 fn test_timestamp_system_time_conversion() {
219 let now = SystemTime::now();
220 let ts: Timestamp = now.try_into().unwrap();
221 let back = ts.as_system_time().unwrap();
222
223 let diff = back
225 .duration_since(now)
226 .or_else(|e| Ok::<_, ()>(e.duration()))
227 .unwrap();
228 assert!(diff.as_micros() < 2);
229 }
230
231 #[test]
232 fn test_timestamp_epoch() {
233 assert_eq!(Timestamp::EPOCH.as_micros(), 0);
234 assert_eq!(Timestamp::EPOCH.as_secs(), 0);
235 }
236}