grafeo_common/types/
timestamp.rs1use super::date::civil_from_days;
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::time::{Duration as StdDuration, SystemTime, UNIX_EPOCH};
9
10#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
16#[repr(transparent)]
17pub struct Timestamp(i64);
18
19impl Timestamp {
20 pub const EPOCH: Self = Self(0);
22
23 pub const MIN: Self = Self(i64::MIN);
25
26 pub const MAX: Self = Self(i64::MAX);
28
29 #[inline]
31 #[must_use]
32 pub const fn from_micros(micros: i64) -> Self {
33 Self(micros)
34 }
35
36 #[inline]
38 #[must_use]
39 pub const fn from_millis(millis: i64) -> Self {
40 Self(millis * 1000)
41 }
42
43 #[inline]
45 #[must_use]
46 pub const fn from_secs(secs: i64) -> Self {
47 Self(secs * 1_000_000)
48 }
49
50 #[must_use]
52 pub fn now() -> Self {
53 let duration = SystemTime::now()
54 .duration_since(UNIX_EPOCH)
55 .unwrap_or(StdDuration::ZERO);
56 Self::from_micros(duration.as_micros() as i64)
57 }
58
59 #[inline]
61 #[must_use]
62 pub const fn as_micros(&self) -> i64 {
63 self.0
64 }
65
66 #[inline]
68 #[must_use]
69 pub const fn as_millis(&self) -> i64 {
70 self.0 / 1000
71 }
72
73 #[inline]
75 #[must_use]
76 pub const fn as_secs(&self) -> i64 {
77 self.0 / 1_000_000
78 }
79
80 #[must_use]
82 pub fn as_system_time(&self) -> Option<SystemTime> {
83 if self.0 >= 0 {
84 Some(UNIX_EPOCH + StdDuration::from_micros(self.0 as u64))
85 } else {
86 UNIX_EPOCH.checked_sub(StdDuration::from_micros((-self.0) as u64))
87 }
88 }
89
90 #[must_use]
92 pub const fn add_micros(self, micros: i64) -> Self {
93 Self(self.0.saturating_add(micros))
94 }
95
96 #[must_use]
98 pub const fn sub_micros(self, micros: i64) -> Self {
99 Self(self.0.saturating_sub(micros))
100 }
101
102 #[must_use]
106 pub const fn duration_since(self, other: Self) -> i64 {
107 self.0 - other.0
108 }
109
110 #[must_use]
112 pub fn from_date_time(date: super::Date, time: super::Time) -> Self {
113 let day_micros = date.as_days() as i64 * 86_400_000_000;
114 let time_micros = (time.as_nanos() / 1000) as i64;
115 let offset_micros = time.offset_seconds().unwrap_or(0) as i64 * 1_000_000;
117 Self(day_micros + time_micros - offset_micros)
118 }
119
120 #[must_use]
122 pub fn to_date(self) -> super::Date {
123 let days = self.0.div_euclid(86_400_000_000) as i32;
124 super::Date::from_days(days)
125 }
126
127 #[must_use]
129 pub fn to_time(self) -> super::Time {
130 let day_nanos = self.0.rem_euclid(86_400_000_000) as u64 * 1000;
131 super::Time::from_nanos(day_nanos).unwrap_or_default()
132 }
133
134 #[must_use]
136 pub fn add_duration(self, dur: &super::Duration) -> Self {
137 let date = self
139 .to_date()
140 .add_duration(&super::Duration::from_months(dur.months()));
141 let time = self.to_time();
142 let base = Self::from_date_time(date, time);
143 let day_micros = dur.days() * 86_400_000_000;
145 let nano_micros = dur.nanos() / 1000;
146 Self(base.0 + day_micros + nano_micros)
147 }
148}
149
150impl fmt::Debug for Timestamp {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 write!(f, "Timestamp({}μs)", self.0)
153 }
154}
155
156impl fmt::Display for Timestamp {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 let micros = self.0;
159 let micro_frac = micros.rem_euclid(1_000_000) as u64;
160
161 let total_days = micros.div_euclid(86_400_000_000) as i32;
162 let day_micros = micros.rem_euclid(86_400_000_000);
163 let day_secs = day_micros / 1_000_000;
164
165 let hours = day_secs / 3600;
166 let minutes = (day_secs % 3600) / 60;
167 let seconds = day_secs % 60;
168
169 let (year, month, day) = civil_from_days(total_days);
170
171 write!(
172 f,
173 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
174 year, month, day, hours, minutes, seconds, micro_frac
175 )
176 }
177}
178
179impl From<i64> for Timestamp {
180 fn from(micros: i64) -> Self {
181 Self::from_micros(micros)
182 }
183}
184
185impl From<Timestamp> for i64 {
186 fn from(ts: Timestamp) -> Self {
187 ts.0
188 }
189}
190
191impl TryFrom<SystemTime> for Timestamp {
192 type Error = ();
193
194 fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
195 match time.duration_since(UNIX_EPOCH) {
196 Ok(duration) => Ok(Self::from_micros(duration.as_micros() as i64)),
197 Err(e) => Ok(Self::from_micros(-(e.duration().as_micros() as i64))),
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn test_timestamp_creation() {
208 let ts = Timestamp::from_secs(1000);
209 assert_eq!(ts.as_secs(), 1000);
210 assert_eq!(ts.as_millis(), 1_000_000);
211 assert_eq!(ts.as_micros(), 1_000_000_000);
212
213 let ts = Timestamp::from_millis(1234);
214 assert_eq!(ts.as_millis(), 1234);
215
216 let ts = Timestamp::from_micros(1_234_567);
217 assert_eq!(ts.as_micros(), 1_234_567);
218 }
219
220 #[test]
221 #[cfg(not(miri))] fn test_timestamp_now() {
223 let ts = Timestamp::now();
224 assert!(ts.as_secs() > 1_577_836_800);
226 }
227
228 #[test]
229 fn test_timestamp_arithmetic() {
230 let ts = Timestamp::from_secs(1000);
231
232 let ts2 = ts.add_micros(1_000_000);
233 assert_eq!(ts2.as_secs(), 1001);
234
235 let ts3 = ts.sub_micros(1_000_000);
236 assert_eq!(ts3.as_secs(), 999);
237
238 assert_eq!(ts2.duration_since(ts), 1_000_000);
239 assert_eq!(ts.duration_since(ts2), -1_000_000);
240 }
241
242 #[test]
243 fn test_timestamp_ordering() {
244 let ts1 = Timestamp::from_secs(100);
245 let ts2 = Timestamp::from_secs(200);
246
247 assert!(ts1 < ts2);
248 assert!(ts2 > ts1);
249 assert_eq!(ts1, Timestamp::from_secs(100));
250 }
251
252 #[test]
253 #[cfg(not(miri))] fn test_timestamp_system_time_conversion() {
255 let now = SystemTime::now();
256 let ts: Timestamp = now.try_into().unwrap();
257 let back = ts.as_system_time().unwrap();
258
259 let diff = back
261 .duration_since(now)
262 .or_else(|e| Ok::<_, ()>(e.duration()))
263 .unwrap();
264 assert!(diff.as_micros() < 2);
265 }
266
267 #[test]
268 fn test_timestamp_epoch() {
269 assert_eq!(Timestamp::EPOCH.as_micros(), 0);
270 assert_eq!(Timestamp::EPOCH.as_secs(), 0);
271 }
272}