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