1use core::{
16 fmt,
17 ops::{Add, AddAssign, Neg, Sub, SubAssign},
18};
19
20use crate::GnssTimeError;
21
22const NANOS_PER_SECOND: i64 = 1_000_000_000;
23const NANOS_PER_MILLI: i64 = 1_000_000;
24const NANOS_PER_MICRO: i64 = 1_000;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
50#[must_use = "Duration is a value type; ignoring it has no effect"]
51#[repr(transparent)]
52pub struct Duration(i64); impl Duration {
55 pub const ZERO: Duration = Duration(0);
57
58 pub const MAX: Duration = Duration(i64::MAX);
60
61 pub const MIN: Duration = Duration(i64::MIN);
63
64 pub const ONE_NANOSECOND: Duration = Duration(1);
66
67 pub const ONE_SECOND: Duration = Duration(NANOS_PER_SECOND);
69
70 #[inline(always)]
72 pub const fn from_nanos(nanos: i64) -> Self {
73 Duration(nanos)
74 }
75
76 #[inline]
78 pub const fn from_micros(micros: i64) -> Self {
79 Duration(micros * NANOS_PER_MICRO)
80 }
81
82 #[inline]
84 pub const fn from_millis(millis: i64) -> Self {
85 Duration(millis * NANOS_PER_MILLI)
86 }
87
88 #[inline]
90 pub const fn from_seconds(secs: i64) -> Self {
91 Duration(secs * NANOS_PER_SECOND)
92 }
93
94 #[inline]
96 pub const fn from_minutes(mins: i64) -> Self {
97 Duration(mins * 60 * NANOS_PER_SECOND)
98 }
99
100 #[inline]
102 pub const fn from_hours(hours: i64) -> Self {
103 Duration(hours * 3_600 * NANOS_PER_SECOND)
104 }
105
106 #[inline]
108 pub const fn from_days(days: i64) -> Self {
109 Duration(days * 86_400 * NANOS_PER_SECOND)
110 }
111
112 #[inline]
114 #[must_use = "returns None on overflow; check the result"]
115 pub const fn checked_from_micros(micros: i64) -> Option<Self> {
116 match micros.checked_mul(NANOS_PER_MICRO) {
117 Some(n) => Some(Duration(n)),
118 None => None,
119 }
120 }
121
122 #[inline]
124 #[must_use = "returns None on overflow; check the result"]
125 pub const fn checked_from_millis(millis: i64) -> Option<Self> {
126 match millis.checked_mul(NANOS_PER_MILLI) {
127 Some(n) => Some(Duration(n)),
128 None => None,
129 }
130 }
131
132 #[inline]
134 #[must_use = "returns None on overflow; check the result"]
135 pub const fn checked_from_seconds(secs: i64) -> Option<Self> {
136 match secs.checked_mul(NANOS_PER_SECOND) {
137 Some(n) => Some(Duration(n)),
138 None => None,
139 }
140 }
141}
142
143impl Duration {
144 #[inline(always)]
146 #[must_use]
147 pub const fn as_nanos(self) -> i64 {
148 self.0
149 }
150
151 #[inline]
153 #[must_use]
154 pub const fn as_micros(self) -> i64 {
155 self.0 / NANOS_PER_MICRO
156 }
157
158 #[inline]
160 #[must_use]
161 pub const fn as_millis(self) -> i64 {
162 self.0 / NANOS_PER_MILLI
163 }
164
165 #[inline]
167 #[must_use]
168 pub const fn as_seconds(self) -> i64 {
169 self.0 / NANOS_PER_SECOND
170 }
171
172 #[inline]
176 #[must_use]
177 pub fn as_seconds_f64(self) -> f64 {
178 self.0 as f64 / NANOS_PER_SECOND as f64
179 }
180
181 #[inline]
183 #[must_use]
184 pub const fn is_positive(self) -> bool {
185 self.0 > 0
186 }
187
188 #[inline]
190 #[must_use]
191 pub const fn is_negative(self) -> bool {
192 self.0 < 0
193 }
194
195 #[inline]
197 #[must_use]
198 pub const fn is_zero(self) -> bool {
199 self.0 == 0
200 }
201
202 #[inline]
205 #[must_use = "returns None for Duration::MIN; check the result"]
206 pub const fn abs(self) -> Option<Self> {
207 match self.0.checked_abs() {
208 Some(n) => Some(Duration(n)),
209 None => None,
210 }
211 }
212}
213
214impl Duration {
215 #[inline]
217 #[must_use = "returns None on overflow; check the result"]
218 pub const fn checked_add(
219 self,
220 rhs: Duration,
221 ) -> Option<Duration> {
222 match self.0.checked_add(rhs.0) {
223 Some(n) => Some(Duration(n)),
224 None => None,
225 }
226 }
227
228 #[inline]
230 #[must_use = "returns None on overflow; check the result"]
231 pub const fn checked_sub(
232 self,
233 rhs: Duration,
234 ) -> Option<Duration> {
235 match self.0.checked_sub(rhs.0) {
236 Some(n) => Some(Duration(n)),
237 None => None,
238 }
239 }
240
241 #[inline]
243 #[must_use = "saturating_add returns a new Duration; the original is unchanged"]
244 pub const fn saturating_add(
245 self,
246 rhs: Duration,
247 ) -> Duration {
248 Duration(self.0.saturating_add(rhs.0))
249 }
250
251 #[inline]
253 #[must_use = "saturating_sub returns a new Duration; the original is unchanged"]
254 pub const fn saturating_sub(
255 self,
256 rhs: Duration,
257 ) -> Duration {
258 Duration(self.0.saturating_sub(rhs.0))
259 }
260
261 #[inline]
263 pub fn try_add(
264 self,
265 rhs: Duration,
266 ) -> Result<Duration, GnssTimeError> {
267 self.checked_add(rhs).ok_or(GnssTimeError::Overflow)
268 }
269
270 #[inline]
272 pub fn try_sub(
273 self,
274 rhs: Duration,
275 ) -> Result<Duration, GnssTimeError> {
276 self.checked_sub(rhs).ok_or(GnssTimeError::Overflow)
277 }
278}
279
280impl Add for Duration {
281 type Output = Duration;
282
283 #[inline]
288 fn add(
289 self,
290 rhs: Self,
291 ) -> Self::Output {
292 Duration(self.0 + rhs.0)
293 }
294}
295
296impl AddAssign for Duration {
297 #[inline]
298 fn add_assign(
299 &mut self,
300 rhs: Self,
301 ) {
302 self.0 += rhs.0
303 }
304}
305
306impl Sub for Duration {
307 type Output = Duration;
308
309 #[inline]
310 fn sub(
311 self,
312 rhs: Self,
313 ) -> Self::Output {
314 Duration(self.0 - rhs.0)
315 }
316}
317
318impl SubAssign for Duration {
319 #[inline]
320 fn sub_assign(
321 &mut self,
322 rhs: Self,
323 ) {
324 self.0 -= rhs.0
325 }
326}
327
328impl Neg for Duration {
329 type Output = Duration;
330
331 #[inline]
332 fn neg(self) -> Self::Output {
333 Duration(-self.0)
334 }
335}
336
337impl fmt::Display for Duration {
338 fn fmt(
342 &self,
343 f: &mut fmt::Formatter<'_>,
344 ) -> fmt::Result {
345 let total = self.0;
346 let sign = if total < 0 { "-" } else { "" };
347 let abs = total.unsigned_abs(); let secs = abs / 1_000_000_000u64;
349 let nanos = abs % 1_000_000_000u64;
350
351 write!(f, "{sign}{secs}s {nanos}ns")
352 }
353}
354
355#[cfg(test)]
360mod tests {
361 #[allow(unused_imports)]
362 use std::string::ToString;
363
364 use super::*;
365
366 #[test]
367 fn test_from_seconds_roundtrip() {
368 let d = Duration::from_seconds(42);
369
370 assert_eq!(d.as_seconds(), 42);
371 assert_eq!(d.as_nanos(), 42_000_000_000);
372 }
373
374 #[test]
375 fn test_from_millis_roundtrip() {
376 let d = Duration::from_millis(1500);
377
378 assert_eq!(d.as_millis(), 1500);
379 assert_eq!(d.as_seconds(), 1);
380 }
381
382 #[test]
383 fn test_from_micros_roundtrip() {
384 let d = Duration::from_micros(1_000_000);
385
386 assert_eq!(d.as_micros(), 1_000_000);
387 assert_eq!(d.as_millis(), 1_000);
388 }
389
390 #[test]
391 fn test_zero_constants() {
392 assert!(Duration::ZERO.is_zero());
393 assert_eq!(Duration::ZERO.as_nanos(), 0);
394 }
395
396 #[test]
397 fn test_sign_helpers() {
398 assert!(Duration::from_seconds(1).is_positive());
399 assert!(Duration::from_seconds(-1).is_negative());
400 assert!(!Duration::ZERO.is_positive());
401 assert!(!Duration::ZERO.is_negative());
402 }
403
404 #[test]
405 fn test_add_sub_identify() {
406 let a = Duration::from_seconds(10);
407 let b = Duration::from_seconds(3);
408
409 assert_eq!(a - b + b, a);
410 }
411
412 #[test]
413 fn test_negative() {
414 let d = Duration::from_seconds(5);
415
416 assert_eq!((-d).as_nanos(), -5_000_000_000);
417 assert_eq!(-(-d), d);
418 }
419
420 #[test]
421 fn test_checked_add_overflow() {
422 assert!(Duration::MAX
423 .checked_add(Duration::ONE_NANOSECOND)
424 .is_none());
425 }
426
427 #[test]
428 fn test_checked_add_underflow() {
429 assert!(Duration::MIN
430 .checked_sub(Duration::ONE_NANOSECOND)
431 .is_none());
432 }
433
434 #[test]
435 fn test_saturating_add_clamps() {
436 let result = Duration::MAX.saturating_add(Duration::ONE_NANOSECOND);
437
438 assert_eq!(result, Duration::MAX);
439 }
440
441 #[test]
442 fn test_saturating_sub_clamps() {
443 let result = Duration::MIN.saturating_sub(Duration::ONE_NANOSECOND);
444
445 assert_eq!(result, Duration::MIN);
446 }
447
448 #[test]
449 fn test_abs_positive() {
450 let d = Duration::from_seconds(-7);
451
452 assert_eq!(d.abs().unwrap().as_seconds(), 7);
453 }
454
455 #[test]
456 fn test_abs_min_is_none() {
457 assert!(Duration::MIN.abs().is_none());
458 }
459
460 #[test]
461 fn test_as_seconds_f64_precision() {
462 let d = Duration::from_nanos(1_500_000_001); let f = d.as_seconds_f64();
464
465 assert!((f - 1.500_000_001_f64).abs() < 1e-9);
467 }
468
469 #[test]
470 fn test_display_positive() {
471 assert_eq!(Duration::from_seconds(1).to_string(), "1s 0ns");
472 }
473
474 #[test]
475 fn test_display_negative() {
476 let d = Duration::from_nanos(-3_141_592_654);
477 assert_eq!(d.to_string(), "-3s 141592654ns");
478 }
479
480 #[test]
481 fn test_display_zero() {
482 assert_eq!(Duration::ZERO.to_string(), "0s 0ns");
483 }
484
485 #[test]
486 fn test_size_of_duration_is_8_bytes() {
487 assert_eq!(core::mem::size_of::<Duration>(), 8);
488 }
489
490 #[test]
491 fn test_identity_zero_addition() {
492 let d = Duration::from_seconds(123);
493
494 assert_eq!(d + Duration::ZERO, d);
495 assert_eq!(Duration::ZERO + d, d);
496 }
497
498 #[test]
499 fn test_identity_zero_subtraction() {
500 let d = Duration::from_seconds(123);
501
502 assert_eq!(d - Duration::ZERO, d);
503 }
504
505 #[test]
506 fn test_double_negation() {
507 let d = Duration::from_seconds(999);
508
509 assert_eq!(-(-d), d);
510 }
511
512 #[test]
513 fn test_add_sub_inverse() {
514 let a = Duration::from_seconds(1000);
515 let b = Duration::from_seconds(250);
516
517 assert_eq!((a + b) - b, a);
518 }
519
520 #[test]
521 fn test_sub_add_inverse() {
522 let a = Duration::from_seconds(1000);
523 let b = Duration::from_seconds(250);
524
525 assert_eq!((a - b) + b, a);
526 }
527
528 #[test]
529 fn test_add_commutativity() {
530 let a = Duration::from_seconds(10);
531 let b = Duration::from_seconds(3);
532
533 assert_eq!(a + b, b + a);
534 }
535
536 #[test]
537 fn test_add_associativity() {
538 let a = Duration::from_seconds(1);
539 let b = Duration::from_seconds(2);
540 let c = Duration::from_seconds(3);
541
542 assert_eq!((a + b) + c, a + (b + c));
543 }
544
545 #[test]
546 fn test_checked_add_matches_operator_when_safe() {
547 let a = Duration::from_seconds(10);
548 let b = Duration::from_seconds(5);
549
550 assert_eq!(a.checked_add(b), Some(a + b));
551 }
552
553 #[test]
554 fn test_checked_sub_matches_operator_when_safe() {
555 let a = Duration::from_seconds(10);
556 let b = Duration::from_seconds(5);
557
558 assert_eq!(a.checked_sub(b), Some(a - b));
559 }
560
561 #[test]
562 fn test_sign_symmetry() {
563 let d = Duration::from_seconds(42);
564
565 assert_eq!(d.is_positive(), (-d).is_negative());
566 assert_eq!(d.is_negative(), (-d).is_positive());
567 }
568
569 #[test]
570 fn test_conversion_consistency() {
571 let d = Duration::from_seconds(1);
572
573 assert_eq!(Duration::from_millis(1000), d);
574 assert_eq!(Duration::from_micros(1_000_000), d);
575 }
576
577 #[test]
578 fn test_nanos_identity() {
579 let d = Duration::from_nanos(123456789);
580
581 assert_eq!(d.as_nanos(), 123456789);
582 }
583
584 #[test]
585 fn test_checked_from_seconds_overflow() {
586 assert!(Duration::checked_from_seconds(i64::MAX / NANOS_PER_SECOND + 1).is_none());
587 }
588
589 #[test]
590 fn test_checked_from_millis_overflow() {
591 assert!(Duration::checked_from_millis(i64::MAX / NANOS_PER_MILLI + 1).is_none());
592 }
593
594 #[test]
595 fn test_checked_from_micros_overflow() {
596 assert!(Duration::checked_from_micros(i64::MAX / NANOS_PER_MICRO + 1).is_none());
597 }
598
599 #[test]
600 fn test_as_seconds_truncation_positive() {
601 let d = Duration::from_nanos(1_500_000_000);
602 assert_eq!(d.as_seconds(), 1);
603 }
604
605 #[test]
606 fn test_as_seconds_truncation_negative() {
607 let d = Duration::from_nanos(-1_500_000_000);
608
609 assert_eq!(d.as_seconds(), -1);
611 }
612
613 #[test]
614 fn test_as_millis_truncation_negative() {
615 let d = Duration::from_nanos(-1_500_000);
616 assert_eq!(d.as_millis(), -1);
617 }
618
619 #[test]
620 fn test_add_assign() {
621 let mut d = Duration::from_seconds(10);
622 d += Duration::from_seconds(5);
623
624 assert_eq!(d, Duration::from_seconds(15));
625 }
626
627 #[test]
628 fn test_sub_assign() {
629 let mut d = Duration::from_seconds(10);
630 d -= Duration::from_seconds(5);
631
632 assert_eq!(d, Duration::from_seconds(5));
633 }
634
635 #[test]
636 fn test_add_assign_zero_identity() {
637 let mut d = Duration::from_seconds(42);
638 d += Duration::ZERO;
639
640 assert_eq!(d, Duration::from_seconds(42));
641 }
642
643 #[test]
644 fn test_sub_assign_zero_identity() {
645 let mut d = Duration::from_seconds(42);
646 d -= Duration::ZERO;
647
648 assert_eq!(d, Duration::from_seconds(42));
649 }
650
651 #[test]
652 fn test_min_plus_zero() {
653 assert_eq!(Duration::MIN + Duration::ZERO, Duration::MIN);
654 }
655
656 #[test]
657 fn test_max_plus_zero() {
658 assert_eq!(Duration::MAX + Duration::ZERO, Duration::MAX);
659 }
660
661 #[test]
662 fn test_min_minus_zero() {
663 assert_eq!(Duration::MIN - Duration::ZERO, Duration::MIN);
664 }
665
666 #[test]
667 fn test_max_minus_zero() {
668 assert_eq!(Duration::MAX - Duration::ZERO, Duration::MAX);
669 }
670
671 #[test]
672 fn test_abs_positive_identity() {
673 let d = Duration::from_seconds(10);
674 assert_eq!(d.abs().unwrap(), d);
675 }
676
677 #[test]
678 fn test_abs_zero() {
679 assert_eq!(Duration::ZERO.abs().unwrap(), Duration::ZERO);
680 }
681
682 #[test]
683 fn test_seconds_millis_consistency() {
684 assert_eq!(Duration::from_seconds(1), Duration::from_millis(1000));
685 }
686
687 #[test]
688 fn test_seconds_micros_consistency() {
689 assert_eq!(Duration::from_seconds(1), Duration::from_micros(1_000_000));
690 }
691
692 #[test]
693 fn test_seconds_nanos_consistency() {
694 assert_eq!(
695 Duration::from_seconds(1),
696 Duration::from_nanos(1_000_000_000)
697 );
698 }
699
700 #[test]
701 fn test_checked_add_matches_manual() {
702 let a = Duration::from_seconds(123);
703 let b = Duration::from_seconds(456);
704
705 assert_eq!(a.checked_add(b), Some(Duration::from_seconds(579)));
706 }
707
708 #[test]
709 fn test_checked_sub_matches_manual() {
710 let a = Duration::from_seconds(500);
711 let b = Duration::from_seconds(200);
712
713 assert_eq!(a.checked_sub(b), Some(Duration::from_seconds(300)));
714 }
715
716 #[test]
717 fn test_ordering_basic() {
718 let a = Duration::from_seconds(1);
719 let b = Duration::from_seconds(2);
720
721 assert!(a < b);
722 assert!(b > a);
723 }
724
725 #[test]
726 fn test_ordering_zero() {
727 let a = Duration::ZERO;
728 let b = Duration::from_seconds(1);
729
730 assert!(a < b);
731 }
732
733 #[test]
734 fn test_neg_zero() {
735 assert_eq!(-Duration::ZERO, Duration::ZERO);
736 }
737
738 #[test]
739 fn test_neg_sign_flip() {
740 let d = Duration::from_seconds(100);
741
742 assert_eq!(-d, Duration::from_seconds(-100));
743 }
744
745 #[test]
746 fn test_checked_add_overflow_returns_none() {
747 assert_eq!(Duration::MAX.checked_add(Duration::ONE_NANOSECOND), None);
748 }
749
750 #[test]
751 fn test_checked_sub_underflow_returns_none() {
752 assert_eq!(Duration::MIN.checked_sub(Duration::ONE_NANOSECOND), None);
753 }
754
755 #[test]
756 fn test_try_add_overflow_returns_err() {
757 assert_eq!(
758 Duration::MAX.try_add(Duration::ONE_NANOSECOND),
759 Err(GnssTimeError::Overflow)
760 );
761 }
762
763 #[test]
764 fn test_try_sub_underflow_returns_err() {
765 assert_eq!(
766 Duration::MIN.try_sub(Duration::ONE_NANOSECOND),
767 Err(GnssTimeError::Overflow)
768 );
769 }
770}