1#![allow(
32 clippy::cast_possible_truncation,
33 clippy::cast_sign_loss,
34 clippy::cast_precision_loss,
35 clippy::cast_possible_wrap
36)]
37use std::{
54 cmp::Ordering,
55 fmt::Display,
56 ops::{Add, AddAssign, Deref, Sub, SubAssign},
57 str::FromStr,
58};
59
60use chrono::{DateTime, NaiveDate, Utc};
61use serde::{
62 Deserialize, Deserializer, Serialize,
63 de::{self, Visitor},
64};
65
66pub type DurationNanos = u64;
68
69#[repr(C)]
71#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
72pub struct UnixNanos(u64);
73
74impl UnixNanos {
75 #[must_use]
77 pub const fn new(value: u64) -> Self {
78 Self(value)
79 }
80
81 #[must_use]
83 pub const fn max() -> Self {
84 Self(u64::MAX)
85 }
86
87 #[must_use]
89 pub const fn is_zero(&self) -> bool {
90 self.0 == 0
91 }
92
93 #[must_use]
95 pub const fn as_u64(&self) -> u64 {
96 self.0
97 }
98
99 #[must_use]
105 pub const fn as_i64(&self) -> i64 {
106 assert!(
107 (self.0 <= i64::MAX as u64),
108 "UnixNanos value exceeds i64::MAX"
109 );
110 self.0 as i64
111 }
112
113 #[must_use]
115 pub const fn as_f64(&self) -> f64 {
116 self.0 as f64
117 }
118
119 #[must_use]
121 pub const fn to_datetime_utc(&self) -> DateTime<Utc> {
122 DateTime::from_timestamp_nanos(self.0 as i64)
123 }
124
125 #[must_use]
127 pub fn to_rfc3339(&self) -> String {
128 self.to_datetime_utc().to_rfc3339()
129 }
130
131 #[must_use]
136 pub const fn duration_since(&self, other: &Self) -> Option<DurationNanos> {
137 self.0.checked_sub(other.0)
138 }
139
140 fn parse_string(s: &str) -> Result<Self, String> {
141 if let Ok(int_value) = s.parse::<u64>() {
143 return Ok(Self(int_value));
144 }
145
146 if s.chars().all(|c| c.is_ascii_digit()) {
152 return Err("Unix timestamp is out of range".into());
153 }
154
155 if let Ok(float_value) = s.parse::<f64>() {
157 if !float_value.is_finite() {
158 return Err("Unix timestamp must be finite".into());
159 }
160
161 if float_value < 0.0 {
162 return Err("Unix timestamp cannot be negative".into());
163 }
164
165 const MAX_NS_F64: f64 = u64::MAX as f64;
169 let nanos_f64 = float_value * 1_000_000_000.0;
170
171 if nanos_f64 > MAX_NS_F64 {
172 return Err("Unix timestamp is out of range".into());
173 }
174
175 let nanos = nanos_f64.round() as u64;
176 return Ok(Self(nanos));
177 }
178
179 if let Ok(datetime) = DateTime::parse_from_rfc3339(s) {
181 let nanos = datetime
182 .timestamp_nanos_opt()
183 .ok_or_else(|| "Timestamp out of range".to_string())?;
184
185 if nanos < 0 {
186 return Err("Unix timestamp cannot be negative".into());
187 }
188
189 return Ok(Self(nanos as u64));
191 }
192
193 if let Ok(datetime) = NaiveDate::parse_from_str(s, "%Y-%m-%d")
195 .map(|date| date.and_hms_opt(0, 0, 0).unwrap())
196 .map(|naive_dt| DateTime::<Utc>::from_naive_utc_and_offset(naive_dt, Utc))
197 {
198 let nanos = datetime
199 .timestamp_nanos_opt()
200 .ok_or_else(|| "Timestamp out of range".to_string())?;
201 return Ok(Self(nanos as u64));
203 }
204
205 Err(format!("Invalid format: {s}"))
206 }
207
208 #[must_use]
210 pub fn checked_add<T: Into<u64>>(self, rhs: T) -> Option<Self> {
211 self.0.checked_add(rhs.into()).map(Self)
212 }
213
214 #[must_use]
216 pub fn checked_sub<T: Into<u64>>(self, rhs: T) -> Option<Self> {
217 self.0.checked_sub(rhs.into()).map(Self)
218 }
219
220 #[must_use]
222 pub fn saturating_add_ns<T: Into<u64>>(self, rhs: T) -> Self {
223 Self(self.0.saturating_add(rhs.into()))
224 }
225
226 #[must_use]
228 pub fn saturating_sub_ns<T: Into<u64>>(self, rhs: T) -> Self {
229 Self(self.0.saturating_sub(rhs.into()))
230 }
231}
232
233impl Deref for UnixNanos {
234 type Target = u64;
235
236 fn deref(&self) -> &Self::Target {
237 &self.0
238 }
239}
240
241impl PartialEq<u64> for UnixNanos {
242 fn eq(&self, other: &u64) -> bool {
243 self.0 == *other
244 }
245}
246
247impl PartialOrd<u64> for UnixNanos {
248 fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
249 self.0.partial_cmp(other)
250 }
251}
252
253impl PartialEq<Option<u64>> for UnixNanos {
254 fn eq(&self, other: &Option<u64>) -> bool {
255 match other {
256 Some(value) => self.0 == *value,
257 None => false,
258 }
259 }
260}
261
262impl PartialOrd<Option<u64>> for UnixNanos {
263 fn partial_cmp(&self, other: &Option<u64>) -> Option<Ordering> {
264 match other {
265 Some(value) => self.0.partial_cmp(value),
266 None => Some(Ordering::Greater),
267 }
268 }
269}
270
271impl PartialEq<UnixNanos> for u64 {
272 fn eq(&self, other: &UnixNanos) -> bool {
273 *self == other.0
274 }
275}
276
277impl PartialOrd<UnixNanos> for u64 {
278 fn partial_cmp(&self, other: &UnixNanos) -> Option<Ordering> {
279 self.partial_cmp(&other.0)
280 }
281}
282
283impl From<u64> for UnixNanos {
284 fn from(value: u64) -> Self {
285 Self(value)
286 }
287}
288
289impl From<UnixNanos> for u64 {
290 fn from(value: UnixNanos) -> Self {
291 value.0
292 }
293}
294
295impl From<&str> for UnixNanos {
296 fn from(value: &str) -> Self {
297 value
298 .parse()
299 .unwrap_or_else(|e| panic!("Failed to parse string into UnixNanos: {e}"))
300 }
301}
302
303impl From<String> for UnixNanos {
304 fn from(value: String) -> Self {
305 value
306 .parse()
307 .unwrap_or_else(|e| panic!("Failed to parse string into UnixNanos: {e}"))
308 }
309}
310
311impl From<DateTime<Utc>> for UnixNanos {
312 fn from(value: DateTime<Utc>) -> Self {
313 Self::from(value.timestamp_nanos_opt().expect("Invalid timestamp") as u64)
314 }
315}
316
317impl FromStr for UnixNanos {
318 type Err = Box<dyn std::error::Error>;
319
320 fn from_str(s: &str) -> Result<Self, Self::Err> {
321 Self::parse_string(s).map_err(std::convert::Into::into)
322 }
323}
324
325impl Add for UnixNanos {
326 type Output = Self;
327
328 fn add(self, rhs: Self) -> Self::Output {
329 Self(
330 self.0
331 .checked_add(rhs.0)
332 .expect("Error adding with overflow"),
333 )
334 }
335}
336
337impl Sub for UnixNanos {
338 type Output = Self;
339
340 fn sub(self, rhs: Self) -> Self::Output {
341 Self(
342 self.0
343 .checked_sub(rhs.0)
344 .expect("Error subtracting with underflow"),
345 )
346 }
347}
348
349impl Add<u64> for UnixNanos {
350 type Output = Self;
351
352 fn add(self, rhs: u64) -> Self::Output {
353 Self(self.0.checked_add(rhs).expect("Error adding with overflow"))
354 }
355}
356
357impl Sub<u64> for UnixNanos {
358 type Output = Self;
359
360 fn sub(self, rhs: u64) -> Self::Output {
361 Self(
362 self.0
363 .checked_sub(rhs)
364 .expect("Error subtracting with underflow"),
365 )
366 }
367}
368
369impl<T: Into<u64>> AddAssign<T> for UnixNanos {
370 fn add_assign(&mut self, other: T) {
371 let other_u64 = other.into();
372 self.0 = self
373 .0
374 .checked_add(other_u64)
375 .expect("Error adding with overflow");
376 }
377}
378
379impl<T: Into<u64>> SubAssign<T> for UnixNanos {
380 fn sub_assign(&mut self, other: T) {
381 let other_u64 = other.into();
382 self.0 = self
383 .0
384 .checked_sub(other_u64)
385 .expect("Error subtracting with underflow");
386 }
387}
388
389impl Display for UnixNanos {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 write!(f, "{}", self.0)
392 }
393}
394
395impl From<UnixNanos> for DateTime<Utc> {
396 fn from(value: UnixNanos) -> Self {
397 value.to_datetime_utc()
398 }
399}
400
401impl<'de> Deserialize<'de> for UnixNanos {
402 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
403 where
404 D: Deserializer<'de>,
405 {
406 struct UnixNanosVisitor;
407
408 impl Visitor<'_> for UnixNanosVisitor {
409 type Value = UnixNanos;
410
411 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
412 formatter.write_str("an integer, a string integer, or an RFC 3339 timestamp")
413 }
414
415 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
416 where
417 E: de::Error,
418 {
419 Ok(UnixNanos(value))
420 }
421
422 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
423 where
424 E: de::Error,
425 {
426 if value < 0 {
427 return Err(E::custom("Unix timestamp cannot be negative"));
428 }
429 Ok(UnixNanos(value as u64))
430 }
431
432 fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
433 where
434 E: de::Error,
435 {
436 if value < 0.0 {
437 return Err(E::custom("Unix timestamp cannot be negative"));
438 }
439 let nanos = (value * 1_000_000_000.0).round() as u64;
441 Ok(UnixNanos(nanos))
442 }
443
444 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
445 where
446 E: de::Error,
447 {
448 UnixNanos::parse_string(value).map_err(E::custom)
449 }
450 }
451
452 deserializer.deserialize_any(UnixNanosVisitor)
453 }
454}
455
456#[cfg(test)]
460mod tests {
461 use chrono::{Duration, TimeZone};
462 use rstest::rstest;
463
464 use super::*;
465
466 #[rstest]
467 fn test_new() {
468 let nanos = UnixNanos::new(123);
469 assert_eq!(nanos.as_u64(), 123);
470 assert_eq!(nanos.as_i64(), 123);
471 }
472
473 #[rstest]
474 fn test_max() {
475 let nanos = UnixNanos::max();
476 assert_eq!(nanos.as_u64(), u64::MAX);
477 }
478
479 #[rstest]
480 fn test_is_zero() {
481 assert!(UnixNanos::default().is_zero());
482 assert!(!UnixNanos::max().is_zero());
483 }
484
485 #[rstest]
486 fn test_from_u64() {
487 let nanos = UnixNanos::from(123);
488 assert_eq!(nanos.as_u64(), 123);
489 assert_eq!(nanos.as_i64(), 123);
490 }
491
492 #[rstest]
493 fn test_default() {
494 let nanos = UnixNanos::default();
495 assert_eq!(nanos.as_u64(), 0);
496 assert_eq!(nanos.as_i64(), 0);
497 }
498
499 #[rstest]
500 fn test_into_from() {
501 let nanos: UnixNanos = 456.into();
502 let value: u64 = nanos.into();
503 assert_eq!(value, 456);
504 }
505
506 #[rstest]
507 #[case(0, "1970-01-01T00:00:00+00:00")]
508 #[case(1_000_000_000, "1970-01-01T00:00:01+00:00")]
509 #[case(1_000_000_000_000_000_000, "2001-09-09T01:46:40+00:00")]
510 #[case(1_500_000_000_000_000_000, "2017-07-14T02:40:00+00:00")]
511 #[case(1_707_577_123_456_789_000, "2024-02-10T14:58:43.456789+00:00")]
512 fn test_to_datetime_utc(#[case] nanos: u64, #[case] expected: &str) {
513 let nanos = UnixNanos::from(nanos);
514 let datetime = nanos.to_datetime_utc();
515 assert_eq!(datetime.to_rfc3339(), expected);
516 }
517
518 #[rstest]
519 #[case(0, "1970-01-01T00:00:00+00:00")]
520 #[case(1_000_000_000, "1970-01-01T00:00:01+00:00")]
521 #[case(1_000_000_000_000_000_000, "2001-09-09T01:46:40+00:00")]
522 #[case(1_500_000_000_000_000_000, "2017-07-14T02:40:00+00:00")]
523 #[case(1_707_577_123_456_789_000, "2024-02-10T14:58:43.456789+00:00")]
524 fn test_to_rfc3339(#[case] nanos: u64, #[case] expected: &str) {
525 let nanos = UnixNanos::from(nanos);
526 assert_eq!(nanos.to_rfc3339(), expected);
527 }
528
529 #[rstest]
530 fn test_from_str() {
531 let nanos: UnixNanos = "123".parse().unwrap();
532 assert_eq!(nanos.as_u64(), 123);
533 }
534
535 #[rstest]
536 fn test_from_str_invalid() {
537 let result = "abc".parse::<UnixNanos>();
538 assert!(result.is_err());
539 }
540
541 #[rstest]
542 fn test_try_from_datetime_valid() {
543 use chrono::TimeZone;
544 let datetime = Utc.timestamp_opt(1_000_000_000, 0).unwrap(); let nanos = UnixNanos::from(datetime);
546 assert_eq!(nanos.as_u64(), 1_000_000_000_000_000_000);
547 }
548
549 #[rstest]
550 fn test_eq() {
551 let nanos = UnixNanos::from(100);
552 assert_eq!(nanos, 100);
553 assert_eq!(nanos, Some(100));
554 assert_ne!(nanos, 200);
555 assert_ne!(nanos, Some(200));
556 assert_ne!(nanos, None);
557 }
558
559 #[rstest]
560 fn test_partial_cmp() {
561 let nanos = UnixNanos::from(100);
562 assert_eq!(nanos.partial_cmp(&100), Some(Ordering::Equal));
563 assert_eq!(nanos.partial_cmp(&200), Some(Ordering::Less));
564 assert_eq!(nanos.partial_cmp(&50), Some(Ordering::Greater));
565 assert_eq!(nanos.partial_cmp(&None), Some(Ordering::Greater));
566 }
567
568 #[rstest]
569 fn test_edge_case_max_value() {
570 let nanos = UnixNanos::from(u64::MAX);
571 assert_eq!(format!("{nanos}"), format!("{}", u64::MAX));
572 }
573
574 #[rstest]
575 fn test_display() {
576 let nanos = UnixNanos::from(123);
577 assert_eq!(format!("{nanos}"), "123");
578 }
579
580 #[rstest]
581 fn test_addition() {
582 let nanos1 = UnixNanos::from(100);
583 let nanos2 = UnixNanos::from(200);
584 let result = nanos1 + nanos2;
585 assert_eq!(result.as_u64(), 300);
586 }
587
588 #[rstest]
589 fn test_add_assign() {
590 let mut nanos = UnixNanos::from(100);
591 nanos += 50_u64;
592 assert_eq!(nanos.as_u64(), 150);
593 }
594
595 #[rstest]
596 fn test_subtraction() {
597 let nanos1 = UnixNanos::from(200);
598 let nanos2 = UnixNanos::from(100);
599 let result = nanos1 - nanos2;
600 assert_eq!(result.as_u64(), 100);
601 }
602
603 #[rstest]
604 fn test_sub_assign() {
605 let mut nanos = UnixNanos::from(200);
606 nanos -= 50_u64;
607 assert_eq!(nanos.as_u64(), 150);
608 }
609
610 #[rstest]
611 #[should_panic(expected = "Error adding with overflow")]
612 fn test_overflow_add() {
613 let nanos = UnixNanos::from(u64::MAX);
614 let _ = nanos + UnixNanos::from(1); }
616
617 #[rstest]
618 #[should_panic(expected = "Error adding with overflow")]
619 fn test_overflow_add_u64() {
620 let nanos = UnixNanos::from(u64::MAX);
621 let _ = nanos + 1_u64; }
623
624 #[rstest]
625 #[should_panic(expected = "Error subtracting with underflow")]
626 fn test_overflow_sub() {
627 let _ = UnixNanos::default() - UnixNanos::from(1); }
629
630 #[rstest]
631 #[should_panic(expected = "Error subtracting with underflow")]
632 fn test_overflow_sub_u64() {
633 let _ = UnixNanos::default() - 1_u64; }
635
636 #[rstest]
637 #[case(100, 50, Some(50))]
638 #[case(1_000_000_000, 500_000_000, Some(500_000_000))]
639 #[case(u64::MAX, u64::MAX - 1, Some(1))]
640 #[case(50, 50, Some(0))]
641 #[case(50, 100, None)]
642 #[case(0, 1, None)]
643 fn test_duration_since(
644 #[case] time1: u64,
645 #[case] time2: u64,
646 #[case] expected: Option<DurationNanos>,
647 ) {
648 let nanos1 = UnixNanos::from(time1);
649 let nanos2 = UnixNanos::from(time2);
650 assert_eq!(nanos1.duration_since(&nanos2), expected);
651 }
652
653 #[rstest]
654 fn test_duration_since_same_moment() {
655 let moment = UnixNanos::from(1_707_577_123_456_789_000);
656 assert_eq!(moment.duration_since(&moment), Some(0));
657 }
658
659 #[rstest]
660 fn test_duration_since_chronological() {
661 let earlier = Utc.with_ymd_and_hms(2024, 2, 10, 12, 0, 0).unwrap();
663
664 let later = earlier
666 + Duration::hours(1)
667 + Duration::minutes(30)
668 + Duration::seconds(45)
669 + Duration::nanoseconds(500_000_000);
670
671 let earlier_nanos = UnixNanos::from(earlier);
672 let later_nanos = UnixNanos::from(later);
673
674 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!(
681 later_nanos.duration_since(&earlier_nanos),
682 Some(expected_duration)
683 );
684 assert_eq!(earlier_nanos.duration_since(&later_nanos), None);
685 }
686
687 #[rstest]
688 fn test_duration_since_with_edge_cases() {
689 let max = UnixNanos::from(u64::MAX);
691 let smaller = UnixNanos::from(u64::MAX - 1000);
692
693 assert_eq!(max.duration_since(&smaller), Some(1000));
694 assert_eq!(smaller.duration_since(&max), None);
695
696 let min = UnixNanos::default(); let larger = UnixNanos::from(1000);
699
700 assert_eq!(min.duration_since(&min), Some(0));
701 assert_eq!(larger.duration_since(&min), Some(1000));
702 assert_eq!(min.duration_since(&larger), None);
703 }
704
705 #[rstest]
706 fn test_serde_json() {
707 let nanos = UnixNanos::from(123);
708 let json = serde_json::to_string(&nanos).unwrap();
709 let deserialized: UnixNanos = serde_json::from_str(&json).unwrap();
710 assert_eq!(deserialized, nanos);
711 }
712
713 #[rstest]
714 fn test_serde_edge_cases() {
715 let nanos = UnixNanos::from(u64::MAX);
716 let json = serde_json::to_string(&nanos).unwrap();
717 let deserialized: UnixNanos = serde_json::from_str(&json).unwrap();
718 assert_eq!(deserialized, nanos);
719 }
720
721 #[rstest]
722 #[case("123", 123)] #[case("1234.567", 1_234_567_000_000)] #[case("2024-02-10", 1_707_523_200_000_000_000)] #[case("2024-02-10T14:58:43Z", 1_707_577_123_000_000_000)] #[case("2024-02-10T14:58:43.456789Z", 1_707_577_123_456_789_000)] fn test_from_str_formats(#[case] input: &str, #[case] expected: u64) {
728 let parsed: UnixNanos = input.parse().unwrap();
729 assert_eq!(parsed.as_u64(), expected);
730 }
731
732 #[rstest]
733 #[case("abc")] #[case("not a timestamp")] #[case("2024-02-10 14:58:43")] fn test_from_str_invalid_formats(#[case] input: &str) {
737 let result = input.parse::<UnixNanos>();
738 assert!(result.is_err());
739 }
740
741 #[rstest]
742 fn test_from_str_integer_overflow() {
743 let input = "184467440737095516160";
745 let result = input.parse::<UnixNanos>();
746 assert!(result.is_err());
747 }
748
749 #[rstest]
752 fn test_checked_add_overflow_returns_none() {
753 let max = UnixNanos::from(u64::MAX);
754 assert_eq!(max.checked_add(1_u64), None);
755 }
756
757 #[rstest]
758 fn test_checked_sub_underflow_returns_none() {
759 let zero = UnixNanos::default();
760 assert_eq!(zero.checked_sub(1_u64), None);
761 }
762
763 #[rstest]
764 fn test_saturating_add_overflow() {
765 let max = UnixNanos::from(u64::MAX);
766 let result = max.saturating_add_ns(1_u64);
767 assert_eq!(result, UnixNanos::from(u64::MAX));
768 }
769
770 #[rstest]
771 fn test_saturating_sub_underflow() {
772 let zero = UnixNanos::default();
773 let result = zero.saturating_sub_ns(1_u64);
774 assert_eq!(result, UnixNanos::default());
775 }
776
777 #[rstest]
778 fn test_from_str_float_overflow() {
779 let input = "2e10"; let result = input.parse::<UnixNanos>();
782 assert!(result.is_err());
783 }
784
785 #[rstest]
786 fn test_deserialize_u64() {
787 let json = "123456789";
788 let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
789 assert_eq!(deserialized.as_u64(), 123_456_789);
790 }
791
792 #[rstest]
793 fn test_deserialize_string_with_int() {
794 let json = "\"123456789\"";
795 let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
796 assert_eq!(deserialized.as_u64(), 123_456_789);
797 }
798
799 #[rstest]
800 fn test_deserialize_float() {
801 let json = "1234.567";
802 let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
803 assert_eq!(deserialized.as_u64(), 1_234_567_000_000);
804 }
805
806 #[rstest]
807 fn test_deserialize_string_with_float() {
808 let json = "\"1234.567\"";
809 let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
810 assert_eq!(deserialized.as_u64(), 1_234_567_000_000);
811 }
812
813 #[rstest]
814 #[case("\"2024-02-10T14:58:43.456789Z\"", 1_707_577_123_456_789_000)]
815 #[case("\"2024-02-10T14:58:43Z\"", 1_707_577_123_000_000_000)]
816 fn test_deserialize_timestamp_strings(#[case] input: &str, #[case] expected: u64) {
817 let deserialized: UnixNanos = serde_json::from_str(input).unwrap();
818 assert_eq!(deserialized.as_u64(), expected);
819 }
820
821 #[rstest]
822 fn test_deserialize_negative_int_fails() {
823 let json = "-123456789";
824 let result: Result<UnixNanos, _> = serde_json::from_str(json);
825 assert!(result.is_err());
826 }
827
828 #[rstest]
829 fn test_deserialize_negative_float_fails() {
830 let json = "-1234.567";
831 let result: Result<UnixNanos, _> = serde_json::from_str(json);
832 assert!(result.is_err());
833 }
834
835 #[rstest]
836 fn test_deserialize_invalid_string_fails() {
837 let json = "\"not a timestamp\"";
838 let result: Result<UnixNanos, _> = serde_json::from_str(json);
839 assert!(result.is_err());
840 }
841
842 #[rstest]
843 fn test_deserialize_edge_cases() {
844 let json = "0";
846 let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
847 assert_eq!(deserialized.as_u64(), 0);
848
849 let json = "18446744073709551615"; let deserialized: UnixNanos = serde_json::from_str(json).unwrap();
852 assert_eq!(deserialized.as_u64(), u64::MAX);
853 }
854
855 #[rstest]
856 #[should_panic(expected = "UnixNanos value exceeds i64::MAX")]
857 fn test_as_i64_overflow_panics() {
858 let nanos = UnixNanos::from(u64::MAX);
859 let _ = nanos.as_i64(); }
861}