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