1use std::{
19 cmp::Ordering,
20 fmt::Display,
21 ops::{Add, AddAssign, Deref, Sub, SubAssign},
22 str::FromStr,
23};
24
25use chrono::{DateTime, Utc};
26use serde::{
27 Deserialize, Deserializer, Serialize,
28 de::{self, Visitor},
29};
30
31pub type DurationNanos = u64;
33
34#[repr(C)]
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
37pub struct UnixNanos(u64);
38
39impl UnixNanos {
40 pub const fn new(value: u64) -> Self {
42 Self(value)
43 }
44
45 #[must_use]
47 pub const fn as_u64(&self) -> u64 {
48 self.0
49 }
50
51 #[must_use]
53 pub const fn as_i64(&self) -> i64 {
54 self.0 as i64
55 }
56
57 #[must_use]
59 pub const fn as_f64(&self) -> f64 {
60 self.0 as f64
61 }
62
63 #[must_use]
65 pub const fn to_datetime_utc(&self) -> DateTime<Utc> {
66 DateTime::from_timestamp_nanos(self.0 as i64)
67 }
68
69 #[must_use]
74 pub const fn duration_since(&self, other: &Self) -> Option<DurationNanos> {
75 self.0.checked_sub(other.0)
76 }
77}
78
79impl Deref for UnixNanos {
80 type Target = u64;
81
82 fn deref(&self) -> &Self::Target {
83 &self.0
84 }
85}
86
87impl PartialEq<u64> for UnixNanos {
88 fn eq(&self, other: &u64) -> bool {
89 self.0 == *other
90 }
91}
92
93impl PartialOrd<u64> for UnixNanos {
94 fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
95 self.0.partial_cmp(other)
96 }
97}
98
99impl PartialEq<Option<u64>> for UnixNanos {
100 fn eq(&self, other: &Option<u64>) -> bool {
101 match other {
102 Some(value) => self.0 == *value,
103 None => false,
104 }
105 }
106}
107
108impl PartialOrd<Option<u64>> for UnixNanos {
109 fn partial_cmp(&self, other: &Option<u64>) -> Option<Ordering> {
110 match other {
111 Some(value) => self.0.partial_cmp(value),
112 None => Some(Ordering::Greater),
113 }
114 }
115}
116
117impl From<u64> for UnixNanos {
118 fn from(value: u64) -> Self {
119 Self(value)
120 }
121}
122
123impl From<UnixNanos> for u64 {
124 fn from(value: UnixNanos) -> Self {
125 value.0
126 }
127}
128
129impl From<&str> for UnixNanos {
130 fn from(value: &str) -> Self {
131 Self(
132 value
133 .parse()
134 .expect("`value` should be a valid integer string"),
135 )
136 }
137}
138
139impl From<String> for UnixNanos {
140 fn from(value: String) -> Self {
141 Self::from(value.as_str())
142 }
143}
144
145impl From<DateTime<Utc>> for UnixNanos {
146 fn from(value: DateTime<Utc>) -> Self {
147 Self::from(value.timestamp_nanos_opt().expect("Invalid timestamp") as u64)
148 }
149}
150
151impl FromStr for UnixNanos {
152 type Err = std::num::ParseIntError;
153
154 fn from_str(s: &str) -> Result<Self, Self::Err> {
155 s.parse().map(UnixNanos)
156 }
157}
158
159impl Add for UnixNanos {
160 type Output = Self;
161 fn add(self, rhs: Self) -> Self::Output {
162 Self(
163 self.0
164 .checked_add(rhs.0)
165 .expect("Error adding with overflow"),
166 )
167 }
168}
169
170impl Sub for UnixNanos {
171 type Output = Self;
172 fn sub(self, rhs: Self) -> Self::Output {
173 Self(
174 self.0
175 .checked_sub(rhs.0)
176 .expect("Error subtracting with underflow"),
177 )
178 }
179}
180
181impl Add<u64> for UnixNanos {
182 type Output = Self;
183
184 fn add(self, rhs: u64) -> Self::Output {
185 Self(self.0.checked_add(rhs).expect("Error adding with overflow"))
186 }
187}
188
189impl Sub<u64> for UnixNanos {
190 type Output = Self;
191
192 fn sub(self, rhs: u64) -> Self::Output {
193 Self(
194 self.0
195 .checked_sub(rhs)
196 .expect("Error subtracting with underflow"),
197 )
198 }
199}
200
201impl<T: Into<u64>> AddAssign<T> for UnixNanos {
202 fn add_assign(&mut self, other: T) {
203 let other_u64 = other.into();
204 self.0 = self
205 .0
206 .checked_add(other_u64)
207 .expect("Error adding with overflow");
208 }
209}
210
211impl<T: Into<u64>> SubAssign<T> for UnixNanos {
212 fn sub_assign(&mut self, other: T) {
213 let other_u64 = other.into();
214 self.0 = self
215 .0
216 .checked_sub(other_u64)
217 .expect("Error subtracting with underflow");
218 }
219}
220
221impl Display for UnixNanos {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 write!(f, "{}", self.0)
224 }
225}
226
227impl From<UnixNanos> for DateTime<Utc> {
228 fn from(value: UnixNanos) -> Self {
229 value.to_datetime_utc()
230 }
231}
232
233impl<'de> Deserialize<'de> for UnixNanos {
234 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
244 where
245 D: Deserializer<'de>,
246 {
247 struct UnixNanosVisitor;
248
249 impl Visitor<'_> for UnixNanosVisitor {
250 type Value = UnixNanos;
251
252 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
253 formatter.write_str("an integer, a string integer, or an RFC 3339 timestamp")
254 }
255
256 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
257 where
258 E: de::Error,
259 {
260 Ok(UnixNanos(value))
261 }
262
263 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
264 where
265 E: de::Error,
266 {
267 if value < 0 {
268 return Err(E::custom("Unix timestamp cannot be negative"));
269 }
270 Ok(UnixNanos(value as u64))
271 }
272
273 fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
274 where
275 E: de::Error,
276 {
277 if value < 0.0 {
278 return Err(E::custom("Unix timestamp cannot be negative"));
279 }
280 let nanos = (value * 1_000_000_000.0).round() as u64;
282 Ok(UnixNanos(nanos))
283 }
284
285 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
286 where
287 E: de::Error,
288 {
289 if let Ok(int_value) = value.parse::<u64>() {
291 return Ok(UnixNanos(int_value));
292 }
293
294 if let Ok(float_value) = value.parse::<f64>() {
296 if float_value < 0.0 {
297 return Err(E::custom("Unix timestamp cannot be negative"));
298 }
299 let nanos = (float_value * 1_000_000_000.0).round() as u64;
300 return Ok(UnixNanos(nanos));
301 }
302
303 if let Ok(datetime) = DateTime::parse_from_rfc3339(value) {
305 let nanos = datetime
306 .timestamp_nanos_opt()
307 .ok_or_else(|| E::custom("Timestamp out of range"))?;
308 if nanos < 0 {
309 return Err(E::custom("Unix timestamp cannot be negative"));
310 }
311 return Ok(UnixNanos(nanos as u64));
312 }
313
314 Err(E::custom(format!("Invalid format: {value}")))
316 }
317 }
318
319 deserializer.deserialize_any(UnixNanosVisitor)
320 }
321}
322
323#[cfg(test)]
327mod tests {
328 use chrono::{Duration, TimeZone};
329 use rstest::rstest;
330
331 use super::*;
332
333 #[rstest]
334 fn test_new() {
335 let nanos = UnixNanos::new(123);
336 assert_eq!(nanos.as_u64(), 123);
337 assert_eq!(nanos.as_i64(), 123);
338 }
339
340 #[rstest]
341 fn test_from_u64() {
342 let nanos = UnixNanos::from(123);
343 assert_eq!(nanos.as_u64(), 123);
344 assert_eq!(nanos.as_i64(), 123);
345 }
346
347 #[rstest]
348 fn test_default() {
349 let nanos = UnixNanos::default();
350 assert_eq!(nanos.as_u64(), 0);
351 assert_eq!(nanos.as_i64(), 0);
352 }
353
354 #[rstest]
355 fn test_into_from() {
356 let nanos: UnixNanos = 456.into();
357 let value: u64 = nanos.into();
358 assert_eq!(value, 456);
359 }
360
361 #[rstest]
362 #[case(0, "1970-01-01T00:00:00+00:00")]
363 #[case(1_000_000_000, "1970-01-01T00:00:01+00:00")]
364 #[case(1_000_000_000_000_000_000, "2001-09-09T01:46:40+00:00")]
365 #[case(1_500_000_000_000_000_000, "2017-07-14T02:40:00+00:00")]
366 #[case(1_707_577_123_456_789_000, "2024-02-10T14:58:43.456789+00:00")]
367 fn test_as_datetime_utc(#[case] nanos: u64, #[case] expected: &str) {
368 let nanos = UnixNanos::from(nanos);
369 let datetime = nanos.to_datetime_utc();
370 assert_eq!(datetime.to_rfc3339(), expected);
371 }
372
373 #[rstest]
374 fn test_from_str() {
375 let nanos: UnixNanos = "123".parse().unwrap();
376 assert_eq!(nanos.as_u64(), 123);
377 }
378
379 #[rstest]
380 fn test_from_str_invalid() {
381 let result = "abc".parse::<UnixNanos>();
382 assert!(result.is_err());
383 }
384
385 #[rstest]
386 fn test_try_from_datetime_valid() {
387 use chrono::TimeZone;
388 let datetime = Utc.timestamp_opt(1_000_000_000, 0).unwrap(); let nanos = UnixNanos::from(datetime);
390 assert_eq!(nanos.as_u64(), 1_000_000_000_000_000_000);
391 }
392
393 #[rstest]
394 fn test_eq() {
395 let nanos = UnixNanos::from(100);
396 assert_eq!(nanos, 100);
397 assert_eq!(nanos, Some(100));
398 assert_ne!(nanos, 200);
399 assert_ne!(nanos, Some(200));
400 assert_ne!(nanos, None);
401 }
402
403 #[rstest]
404 fn test_partial_cmp() {
405 let nanos = UnixNanos::from(100);
406 assert_eq!(nanos.partial_cmp(&100), Some(Ordering::Equal));
407 assert_eq!(nanos.partial_cmp(&200), Some(Ordering::Less));
408 assert_eq!(nanos.partial_cmp(&50), Some(Ordering::Greater));
409 assert_eq!(nanos.partial_cmp(&None), Some(Ordering::Greater));
410 }
411
412 #[rstest]
413 fn test_edge_case_max_value() {
414 let nanos = UnixNanos::from(u64::MAX);
415 assert_eq!(format!("{nanos}"), format!("{}", u64::MAX));
416 }
417
418 #[rstest]
419 fn test_display() {
420 let nanos = UnixNanos::from(123);
421 assert_eq!(format!("{nanos}"), "123");
422 }
423
424 #[rstest]
425 fn test_addition() {
426 let nanos1 = UnixNanos::from(100);
427 let nanos2 = UnixNanos::from(200);
428 let result = nanos1 + nanos2;
429 assert_eq!(result.as_u64(), 300);
430 }
431
432 #[rstest]
433 fn test_add_assign() {
434 let mut nanos = UnixNanos::from(100);
435 nanos += 50_u64;
436 assert_eq!(nanos.as_u64(), 150);
437 }
438
439 #[rstest]
440 fn test_subtraction() {
441 let nanos1 = UnixNanos::from(200);
442 let nanos2 = UnixNanos::from(100);
443 let result = nanos1 - nanos2;
444 assert_eq!(result.as_u64(), 100);
445 }
446
447 #[rstest]
448 fn test_sub_assign() {
449 let mut nanos = UnixNanos::from(200);
450 nanos -= 50_u64;
451 assert_eq!(nanos.as_u64(), 150);
452 }
453
454 #[rstest]
455 #[should_panic(expected = "Error adding with overflow")]
456 fn test_overflow_add() {
457 let nanos = UnixNanos::from(u64::MAX);
458 let _ = nanos + UnixNanos::from(1); }
460
461 #[rstest]
462 #[should_panic(expected = "Error adding with overflow")]
463 fn test_overflow_add_u64() {
464 let nanos = UnixNanos::from(u64::MAX);
465 let _ = nanos + 1_u64; }
467
468 #[rstest]
469 #[should_panic(expected = "Error subtracting with underflow")]
470 fn test_overflow_sub() {
471 let _ = UnixNanos::default() - UnixNanos::from(1); }
473
474 #[rstest]
475 #[should_panic(expected = "Error subtracting with underflow")]
476 fn test_overflow_sub_u64() {
477 let _ = UnixNanos::default() - 1_u64; }
479
480 #[rstest]
481 #[case(100, 50, Some(50))]
482 #[case(1_000_000_000, 500_000_000, Some(500_000_000))]
483 #[case(u64::MAX, u64::MAX - 1, Some(1))]
484 #[case(50, 50, Some(0))]
485 #[case(50, 100, None)]
486 #[case(0, 1, None)]
487 fn test_duration_since(
488 #[case] time1: u64,
489 #[case] time2: u64,
490 #[case] expected: Option<DurationNanos>,
491 ) {
492 let nanos1 = UnixNanos::from(time1);
493 let nanos2 = UnixNanos::from(time2);
494 assert_eq!(nanos1.duration_since(&nanos2), expected);
495 }
496
497 #[rstest]
498 fn test_duration_since_same_moment() {
499 let moment = UnixNanos::from(1_707_577_123_456_789_000);
500 assert_eq!(moment.duration_since(&moment), Some(0));
501 }
502
503 #[rstest]
504 fn test_duration_since_chronological() {
505 let earlier = Utc.with_ymd_and_hms(2024, 2, 10, 12, 0, 0).unwrap();
507
508 let later = earlier
510 + Duration::hours(1)
511 + Duration::minutes(30)
512 + Duration::seconds(45)
513 + Duration::nanoseconds(500_000_000);
514
515 let earlier_nanos = UnixNanos::from(earlier);
516 let later_nanos = UnixNanos::from(later);
517
518 let expected_duration = 1 * 60 * 60 * 1_000_000_000 + 30 * 60 * 1_000_000_000 + 45 * 1_000_000_000 + 500_000_000; assert_eq!(
525 later_nanos.duration_since(&earlier_nanos),
526 Some(expected_duration)
527 );
528 assert_eq!(earlier_nanos.duration_since(&later_nanos), None);
529 }
530
531 #[rstest]
532 fn test_duration_since_with_edge_cases() {
533 let max = UnixNanos::from(u64::MAX);
535 let smaller = UnixNanos::from(u64::MAX - 1000);
536
537 assert_eq!(max.duration_since(&smaller), Some(1000));
538 assert_eq!(smaller.duration_since(&max), None);
539
540 let min = UnixNanos::default(); let larger = UnixNanos::from(1000);
543
544 assert_eq!(min.duration_since(&min), Some(0));
545 assert_eq!(larger.duration_since(&min), Some(1000));
546 assert_eq!(min.duration_since(&larger), None);
547 }
548
549 #[rstest]
550 fn test_serde_json() {
551 let nanos = UnixNanos::from(123);
552 let json = serde_json::to_string(&nanos).unwrap();
553 let deserialized: UnixNanos = serde_json::from_str(&json).unwrap();
554 assert_eq!(deserialized, nanos);
555 }
556
557 #[rstest]
558 fn test_serde_edge_cases() {
559 let nanos = UnixNanos::from(u64::MAX);
560 let json = serde_json::to_string(&nanos).unwrap();
561 let deserialized: UnixNanos = serde_json::from_str(&json).unwrap();
562 assert_eq!(deserialized, nanos);
563 }
564
565 #[rstest]
566 fn test_deserialize_u64() {
567 let json = "123456789";
568 let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
569 assert_eq!(deserialized.as_u64(), 123456789);
570 }
571
572 #[rstest]
573 fn test_deserialize_string_with_int() {
574 let json = "\"123456789\"";
575 let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
576 assert_eq!(deserialized.as_u64(), 123456789);
577 }
578
579 #[rstest]
580 fn test_deserialize_float() {
581 let json = "1234.567";
582 let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
583 assert_eq!(deserialized.as_u64(), 1_234_567_000_000);
584 }
585
586 #[rstest]
587 fn test_deserialize_string_with_float() {
588 let json = "\"1234.567\"";
589 let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
590 assert_eq!(deserialized.as_u64(), 1_234_567_000_000);
591 }
592
593 #[rstest]
594 #[case("\"2024-02-10T14:58:43.456789Z\"", 1707577123456789000)]
595 #[case("\"2024-02-10T14:58:43Z\"", 1707577123000000000)]
596 fn test_deserialize_timestamp_strings(#[case] input: &str, #[case] expected: u64) {
597 let deserialized: UnixNanos = serde_json::from_str(input).unwrap();
598 assert_eq!(deserialized.as_u64(), expected);
599 }
600
601 #[rstest]
602 fn test_deserialize_negative_int_fails() {
603 let json = "-123456789";
604 let result: Result<UnixNanos, _> = serde_json::from_str(json);
605 assert!(result.is_err());
606 }
607
608 #[rstest]
609 fn test_deserialize_negative_float_fails() {
610 let json = "-1234.567";
611 let result: Result<UnixNanos, _> = serde_json::from_str(json);
612 assert!(result.is_err());
613 }
614
615 #[rstest]
616 fn test_deserialize_invalid_string_fails() {
617 let json = "\"not a timestamp\"";
618 let result: Result<UnixNanos, _> = serde_json::from_str(json);
619 assert!(result.is_err());
620 }
621
622 #[rstest]
623 fn test_non_rfc3339_formats_fail() {
624 let json = "\"2024-02-10 14:58:43.456789\"";
626 let result: Result<UnixNanos, _> = serde_json::from_str(json);
627 assert!(result.is_err());
628
629 let json = "\"2024-02-10\"";
631 let result: Result<UnixNanos, _> = serde_json::from_str(json);
632 assert!(result.is_err());
633 }
634
635 #[rstest]
636 fn test_deserialize_edge_cases() {
637 let json = "0";
639 let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
640 assert_eq!(deserialized.as_u64(), 0);
641
642 let json = "18446744073709551615"; let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
645 assert_eq!(deserialized.as_u64(), u64::MAX);
646 }
647}