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