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]
143 pub fn truncate(self, unit: &str) -> Option<Self> {
144 match unit {
145 "year" => {
146 let date = self.to_date();
147 let jan1 = super::Date::from_ymd(date.year(), 1, 1)?;
148 Some(jan1.to_timestamp())
149 }
150 "month" => {
151 let date = self.to_date();
152 let first = super::Date::from_ymd(date.year(), date.month(), 1)?;
153 Some(first.to_timestamp())
154 }
155 "day" => {
156 let days = self.0.div_euclid(86_400_000_000);
157 Some(Self(days * 86_400_000_000))
158 }
159 "hour" => {
160 let days = self.0.div_euclid(86_400_000_000);
161 let day_micros = self.0.rem_euclid(86_400_000_000);
162 let hours = day_micros / 3_600_000_000;
163 Some(Self(days * 86_400_000_000 + hours * 3_600_000_000))
164 }
165 "minute" => {
166 let days = self.0.div_euclid(86_400_000_000);
167 let day_micros = self.0.rem_euclid(86_400_000_000);
168 let minutes = day_micros / 60_000_000;
169 Some(Self(days * 86_400_000_000 + minutes * 60_000_000))
170 }
171 "second" => {
172 let days = self.0.div_euclid(86_400_000_000);
173 let day_micros = self.0.rem_euclid(86_400_000_000);
174 let seconds = day_micros / 1_000_000;
175 Some(Self(days * 86_400_000_000 + seconds * 1_000_000))
176 }
177 _ => None,
178 }
179 }
180
181 #[must_use]
183 pub fn add_duration(self, dur: &super::Duration) -> Self {
184 let date = self
186 .to_date()
187 .add_duration(&super::Duration::from_months(dur.months()));
188 let time = self.to_time();
189 let base = Self::from_date_time(date, time);
190 let day_micros = dur.days() * 86_400_000_000;
192 let nano_micros = dur.nanos() / 1000;
193 Self(base.0 + day_micros + nano_micros)
194 }
195}
196
197impl fmt::Debug for Timestamp {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(f, "Timestamp({}μs)", self.0)
200 }
201}
202
203impl fmt::Display for Timestamp {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 let micros = self.0;
206 let micro_frac = micros.rem_euclid(1_000_000) as u64;
207
208 let total_days = micros.div_euclid(86_400_000_000) as i32;
209 let day_micros = micros.rem_euclid(86_400_000_000);
210 let day_secs = day_micros / 1_000_000;
211
212 let hours = day_secs / 3600;
213 let minutes = (day_secs % 3600) / 60;
214 let seconds = day_secs % 60;
215
216 let (year, month, day) = civil_from_days(total_days);
217
218 write!(
219 f,
220 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
221 year, month, day, hours, minutes, seconds, micro_frac
222 )
223 }
224}
225
226impl From<i64> for Timestamp {
227 fn from(micros: i64) -> Self {
228 Self::from_micros(micros)
229 }
230}
231
232impl From<Timestamp> for i64 {
233 fn from(ts: Timestamp) -> Self {
234 ts.0
235 }
236}
237
238impl TryFrom<SystemTime> for Timestamp {
239 type Error = ();
240
241 fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
242 match time.duration_since(UNIX_EPOCH) {
243 Ok(duration) => Ok(Self::from_micros(duration.as_micros() as i64)),
244 Err(e) => Ok(Self::from_micros(-(e.duration().as_micros() as i64))),
245 }
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn test_timestamp_creation() {
255 let ts = Timestamp::from_secs(1000);
256 assert_eq!(ts.as_secs(), 1000);
257 assert_eq!(ts.as_millis(), 1_000_000);
258 assert_eq!(ts.as_micros(), 1_000_000_000);
259
260 let ts = Timestamp::from_millis(1234);
261 assert_eq!(ts.as_millis(), 1234);
262
263 let ts = Timestamp::from_micros(1_234_567);
264 assert_eq!(ts.as_micros(), 1_234_567);
265 }
266
267 #[test]
268 #[cfg(not(miri))] fn test_timestamp_now() {
270 let ts = Timestamp::now();
271 assert!(ts.as_secs() > 1_577_836_800);
273 }
274
275 #[test]
276 fn test_timestamp_arithmetic() {
277 let ts = Timestamp::from_secs(1000);
278
279 let ts2 = ts.add_micros(1_000_000);
280 assert_eq!(ts2.as_secs(), 1001);
281
282 let ts3 = ts.sub_micros(1_000_000);
283 assert_eq!(ts3.as_secs(), 999);
284
285 assert_eq!(ts2.duration_since(ts), 1_000_000);
286 assert_eq!(ts.duration_since(ts2), -1_000_000);
287 }
288
289 #[test]
290 fn test_timestamp_ordering() {
291 let ts1 = Timestamp::from_secs(100);
292 let ts2 = Timestamp::from_secs(200);
293
294 assert!(ts1 < ts2);
295 assert!(ts2 > ts1);
296 assert_eq!(ts1, Timestamp::from_secs(100));
297 }
298
299 #[test]
300 #[cfg(not(miri))] fn test_timestamp_system_time_conversion() {
302 let now = SystemTime::now();
303 let ts: Timestamp = now.try_into().unwrap();
304 let back = ts.as_system_time().unwrap();
305
306 let diff = back
308 .duration_since(now)
309 .or_else(|e| Ok::<_, ()>(e.duration()))
310 .unwrap();
311 assert!(diff.as_micros() < 2);
312 }
313
314 #[test]
315 fn test_truncate() {
316 let date = crate::types::Date::from_ymd(2024, 6, 15).unwrap();
318 let time = crate::types::Time::from_hms_nano(14, 30, 45, 123_456_000).unwrap();
319 let ts = Timestamp::from_date_time(date, time);
320
321 let year = ts.truncate("year").unwrap();
322 assert_eq!(year.to_date().to_string(), "2024-01-01");
323 assert_eq!(year.to_time().hour(), 0);
324
325 let month = ts.truncate("month").unwrap();
326 assert_eq!(month.to_date().to_string(), "2024-06-01");
327 assert_eq!(month.to_time().hour(), 0);
328
329 let day = ts.truncate("day").unwrap();
330 assert_eq!(day.to_date().to_string(), "2024-06-15");
331 assert_eq!(day.to_time().hour(), 0);
332
333 let hour = ts.truncate("hour").unwrap();
334 assert_eq!(hour.to_time().hour(), 14);
335 assert_eq!(hour.to_time().minute(), 0);
336
337 let minute = ts.truncate("minute").unwrap();
338 assert_eq!(minute.to_time().hour(), 14);
339 assert_eq!(minute.to_time().minute(), 30);
340 assert_eq!(minute.to_time().second(), 0);
341
342 let second = ts.truncate("second").unwrap();
343 assert_eq!(second.to_time().second(), 45);
344 assert_eq!(second.to_time().nanosecond(), 0);
345
346 assert!(ts.truncate("invalid").is_none());
347 }
348
349 #[test]
350 fn test_timestamp_epoch() {
351 assert_eq!(Timestamp::EPOCH.as_micros(), 0);
352 assert_eq!(Timestamp::EPOCH.as_secs(), 0);
353 }
354
355 #[test]
356 fn test_add_duration_days_and_nanos() {
357 use crate::types::Duration;
358 let ts = Timestamp::from_secs(1_700_000_000); let dur = Duration::from_days(1);
360 let result = ts.add_duration(&dur);
361 assert_eq!(result.as_micros() - ts.as_micros(), 86_400_000_000);
363 }
364
365 #[test]
366 fn test_add_duration_months() {
367 use crate::types::Duration;
368 let ts = Timestamp::from_secs(1_700_000_000); let dur = Duration::from_months(2);
370 let result = ts.add_duration(&dur);
371 let result_date = result.to_date();
372 assert_eq!(result_date.month(), 1);
374 assert_eq!(result_date.year(), 2024);
375 }
376}