1use crate::{
80 beidou_to_glonass, beidou_to_utc, galileo_to_glonass, galileo_to_utc, glonass_to_beidou,
81 glonass_to_galileo, glonass_to_gps, glonass_to_utc, gps_to_glonass, gps_to_utc, utc_to_beidou,
82 utc_to_galileo, utc_to_glonass, utc_to_gps, Beidou, Galileo, Glonass, GnssTimeError, Gps,
83 LeapSecondsProvider, Tai, Time, TimeScale, Utc,
84};
85
86#[must_use = "conversion result must be used; ignoring it discards the converted time"]
91pub trait IntoScale<Target: TimeScale>: Sized {
92 fn into_scale(self) -> Result<Time<Target>, GnssTimeError>;
99}
100
101#[must_use = "conversion result must be used; ignoring it discards the converted time"]
108pub trait IntoScaleWith<Target: TimeScale>: Sized {
109 fn into_scale_with<P: LeapSecondsProvider>(
111 self,
112 ls: P,
113 ) -> Result<Time<Target>, GnssTimeError>;
114
115 fn into_scale_with_checked<P: LeapSecondsProvider>(
118 self,
119 ls: P,
120 ) -> Result<ConvertResult<Time<Target>>, GnssTimeError>;
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
125#[must_use = "ConvertResult contains ambiguity information; call .into_inner() or match explicitly"]
126pub enum ConvertResult<T> {
127 Exact(T),
129
130 AmbiguousLeapSecond(T),
134}
135
136impl<T> ConvertResult<T> {
137 #[inline]
139 #[must_use]
140 pub fn into_inner(self) -> T {
141 match self {
142 Self::Exact(t) | Self::AmbiguousLeapSecond(t) => t,
143 }
144 }
145
146 #[inline]
148 #[must_use]
149 pub fn is_exact(&self) -> bool {
150 matches!(self, Self::Exact(_))
151 }
152
153 #[inline]
155 #[must_use]
156 pub fn is_ambiguous(&self) -> bool {
157 matches!(self, Self::AmbiguousLeapSecond(_))
158 }
159}
160
161impl IntoScale<Glonass> for Time<Utc> {
166 #[inline]
173 fn into_scale(self) -> Result<Time<Glonass>, GnssTimeError> {
174 utc_to_glonass(self)
175 }
176}
177
178impl IntoScaleWith<Glonass> for Time<Gps> {
179 fn into_scale_with<P: LeapSecondsProvider>(
180 self,
181 ls: P,
182 ) -> Result<Time<Glonass>, GnssTimeError> {
183 gps_to_glonass(self, &ls)
184 }
185
186 fn into_scale_with_checked<P: LeapSecondsProvider>(
187 self,
188 ls: P,
189 ) -> Result<ConvertResult<Time<Glonass>>, GnssTimeError> {
190 Ok(ConvertResult::Exact(gps_to_glonass(self, &ls)?))
191 }
192}
193
194impl IntoScaleWith<Glonass> for Time<Galileo> {
195 fn into_scale_with<P: LeapSecondsProvider>(
196 self,
197 ls: P,
198 ) -> Result<Time<Glonass>, GnssTimeError> {
199 galileo_to_glonass(self, &ls)
200 }
201
202 fn into_scale_with_checked<P: LeapSecondsProvider>(
203 self,
204 ls: P,
205 ) -> Result<ConvertResult<Time<Glonass>>, GnssTimeError> {
206 Ok(ConvertResult::Exact(galileo_to_glonass(self, &ls)?))
207 }
208}
209
210impl IntoScaleWith<Glonass> for Time<Beidou> {
211 fn into_scale_with<P: LeapSecondsProvider>(
212 self,
213 ls: P,
214 ) -> Result<Time<Glonass>, GnssTimeError> {
215 beidou_to_glonass(self, &ls)
216 }
217
218 fn into_scale_with_checked<P: LeapSecondsProvider>(
219 self,
220 ls: P,
221 ) -> Result<ConvertResult<Time<Glonass>>, GnssTimeError> {
222 Ok(ConvertResult::Exact(beidou_to_glonass(self, &ls)?))
223 }
224}
225
226impl IntoScale<Gps> for Time<Galileo> {
231 #[inline]
232 fn into_scale(self) -> Result<Time<Gps>, GnssTimeError> {
233 self.try_convert::<Gps>()
234 }
235}
236
237impl IntoScale<Gps> for Time<Beidou> {
238 #[inline]
249 fn into_scale(self) -> Result<Time<Gps>, GnssTimeError> {
250 self.try_convert::<Gps>()
251 }
252}
253
254impl IntoScale<Gps> for Time<Tai> {
255 #[inline]
266 fn into_scale(self) -> Result<Time<Gps>, GnssTimeError> {
267 Time::<Gps>::from_tai(self)
268 }
269}
270
271impl IntoScaleWith<Gps> for Time<Glonass> {
272 fn into_scale_with<P: LeapSecondsProvider>(
274 self,
275 ls: P,
276 ) -> Result<Time<Gps>, GnssTimeError> {
277 glonass_to_gps(self, &ls)
278 }
279
280 fn into_scale_with_checked<P: LeapSecondsProvider>(
281 self,
282 ls: P,
283 ) -> Result<ConvertResult<Time<Gps>>, GnssTimeError> {
284 Ok(ConvertResult::Exact(glonass_to_gps(self, &ls)?))
285 }
286}
287
288impl IntoScaleWith<Gps> for Time<Utc> {
289 fn into_scale_with<P: LeapSecondsProvider>(
309 self,
310 ls: P,
311 ) -> Result<Time<Gps>, GnssTimeError> {
312 utc_to_gps(self, &ls)
313 }
314
315 fn into_scale_with_checked<P: LeapSecondsProvider>(
316 self,
317 ls: P,
318 ) -> Result<ConvertResult<Time<Gps>>, GnssTimeError> {
319 Ok(ConvertResult::Exact(utc_to_gps(self, &ls)?))
322 }
323}
324
325impl IntoScale<Galileo> for Time<Gps> {
330 #[inline]
344 fn into_scale(self) -> Result<Time<Galileo>, GnssTimeError> {
345 self.try_convert::<Galileo>()
348 }
349}
350
351impl IntoScale<Galileo> for Time<Beidou> {
352 #[inline]
354 fn into_scale(self) -> Result<Time<Galileo>, GnssTimeError> {
355 self.try_convert::<Galileo>()
356 }
357}
358
359impl IntoScaleWith<Galileo> for Time<Glonass> {
360 fn into_scale_with<P: LeapSecondsProvider>(
362 self,
363 ls: P,
364 ) -> Result<Time<Galileo>, GnssTimeError> {
365 glonass_to_galileo(self, &ls)
366 }
367
368 fn into_scale_with_checked<P: LeapSecondsProvider>(
369 self,
370 ls: P,
371 ) -> Result<ConvertResult<Time<Galileo>>, GnssTimeError> {
372 Ok(ConvertResult::Exact(glonass_to_galileo(self, &ls)?))
373 }
374}
375
376impl IntoScaleWith<Galileo> for Time<Utc> {
377 fn into_scale_with<P: LeapSecondsProvider>(
379 self,
380 ls: P,
381 ) -> Result<Time<Galileo>, GnssTimeError> {
382 utc_to_galileo(self, &ls)
383 }
384
385 fn into_scale_with_checked<P: LeapSecondsProvider>(
386 self,
387 ls: P,
388 ) -> Result<ConvertResult<Time<Galileo>>, GnssTimeError> {
389 Ok(ConvertResult::Exact(utc_to_galileo(self, &ls)?))
390 }
391}
392
393impl IntoScale<Beidou> for Time<Gps> {
398 #[inline]
409 fn into_scale(self) -> Result<Time<Beidou>, GnssTimeError> {
410 self.try_convert::<Beidou>()
411 }
412}
413
414impl IntoScale<Beidou> for Time<Galileo> {
415 #[inline]
417 fn into_scale(self) -> Result<Time<Beidou>, GnssTimeError> {
418 self.try_convert::<Beidou>()
419 }
420}
421
422impl IntoScaleWith<Beidou> for Time<Utc> {
423 fn into_scale_with<P: LeapSecondsProvider>(
425 self,
426 ls: P,
427 ) -> Result<Time<Beidou>, GnssTimeError> {
428 utc_to_beidou(self, &ls)
429 }
430 fn into_scale_with_checked<P: LeapSecondsProvider>(
431 self,
432 ls: P,
433 ) -> Result<ConvertResult<Time<Beidou>>, GnssTimeError> {
434 Ok(ConvertResult::Exact(utc_to_beidou(self, &ls)?))
435 }
436}
437
438impl IntoScaleWith<Beidou> for Time<Glonass> {
439 fn into_scale_with<P: LeapSecondsProvider>(
441 self,
442 ls: P,
443 ) -> Result<Time<Beidou>, GnssTimeError> {
444 glonass_to_beidou(self, &ls)
445 }
446
447 fn into_scale_with_checked<P: LeapSecondsProvider>(
448 self,
449 ls: P,
450 ) -> Result<ConvertResult<Time<Beidou>>, GnssTimeError> {
451 Ok(ConvertResult::Exact(glonass_to_beidou(self, &ls)?))
452 }
453}
454
455impl IntoScale<Utc> for Time<Glonass> {
460 #[inline]
483 fn into_scale(self) -> Result<Time<Utc>, GnssTimeError> {
484 glonass_to_utc(self)
485 }
486}
487
488impl IntoScaleWith<Utc> for Time<Gps> {
489 #[inline]
507 fn into_scale_with<P: LeapSecondsProvider>(
508 self,
509 ls: P,
510 ) -> Result<Time<Utc>, GnssTimeError> {
511 gps_to_utc(self, &ls)
512 }
513
514 fn into_scale_with_checked<P: LeapSecondsProvider>(
515 self,
516 ls: P,
517 ) -> Result<ConvertResult<Time<Utc>>, GnssTimeError> {
518 let utc = gps_to_utc(self, &ls)?;
519
520 let tai = self.to_tai()?;
524 let n_at = ls.tai_minus_utc_at(tai);
525
526 let tai_prev = if tai.as_nanos() >= 1_000_000_000 {
528 Time::<Tai>::from_nanos(tai.as_nanos() - 1_000_000_000)
529 } else {
530 tai
531 };
532 let n_before = ls.tai_minus_utc_at(tai_prev);
533
534 if n_at != n_before {
535 Ok(ConvertResult::AmbiguousLeapSecond(utc))
538 } else {
539 Ok(ConvertResult::Exact(utc))
540 }
541 }
542}
543
544impl IntoScaleWith<Utc> for Time<Galileo> {
545 fn into_scale_with<P: LeapSecondsProvider>(
547 self,
548 ls: P,
549 ) -> Result<Time<Utc>, GnssTimeError> {
550 galileo_to_utc(self, &ls)
551 }
552
553 fn into_scale_with_checked<P: LeapSecondsProvider>(
554 self,
555 ls: P,
556 ) -> Result<ConvertResult<Time<Utc>>, GnssTimeError> {
557 Ok(ConvertResult::Exact(galileo_to_utc(self, &ls)?))
558 }
559}
560
561impl IntoScaleWith<Utc> for Time<Beidou> {
562 fn into_scale_with<P: LeapSecondsProvider>(
564 self,
565 ls: P,
566 ) -> Result<Time<Utc>, GnssTimeError> {
567 beidou_to_utc(self, &ls)
568 }
569
570 fn into_scale_with_checked<P: LeapSecondsProvider>(
571 self,
572 ls: P,
573 ) -> Result<ConvertResult<Time<Utc>>, GnssTimeError> {
574 Ok(ConvertResult::Exact(beidou_to_utc(self, &ls)?))
575 }
576}
577
578impl IntoScale<Tai> for Time<Gps> {
583 #[inline]
594 fn into_scale(self) -> Result<Time<Tai>, GnssTimeError> {
595 self.to_tai()
596 }
597}
598
599#[cfg(test)]
604mod tests {
605 use super::*;
606 use crate::{DurationParts, LeapSeconds};
607
608 #[test]
609 fn test_gps_to_tai_adds_19_seconds() {
610 let gps = Time::<Gps>::from_seconds(100);
611 let tai: Time<Tai> = gps.into_scale().unwrap();
612
613 assert_eq!(tai.as_seconds(), 119);
615 }
616
617 #[test]
618 fn test_tai_to_gps_subtracts_19_seconds() {
619 let tai = Time::<Tai>::from_seconds(119);
620 let gps: Time<Gps> = tai.into_scale().unwrap();
621
622 assert_eq!(gps.as_seconds(), 100);
624 }
625
626 #[test]
627 fn test_gps_tai_gps_roundtrip() {
628 let gps = Time::<Gps>::from_week_tow(
629 2345,
630 DurationParts {
631 seconds: 432_000,
632 nanos: 0,
633 },
634 )
635 .unwrap();
636 let tai: Time<Tai> = gps.into_scale().unwrap();
637 let back: Time<Gps> = tai.into_scale().unwrap();
638
639 assert_eq!(gps, back);
640 }
641
642 #[test]
643 fn test_tai_to_gps_underflow_at_tai_zero() {
644 let tai = Time::<Tai>::EPOCH;
646 let result: Result<Time<Gps>, _> = tai.into_scale();
647
648 assert!(matches!(result, Err(GnssTimeError::Overflow)));
649 }
650
651 #[test]
652 fn test_gps_to_galileo_preserves_nanos() {
653 let gps = Time::<Gps>::from_seconds(12_345_678);
654 let gal: Time<Galileo> = gps.into_scale().unwrap();
655
656 assert_eq!(gps.as_nanos(), gal.as_nanos());
657 }
658
659 #[test]
660 fn test_galileo_to_gps_preserves_nanos() {
661 let gal = Time::<Galileo>::from_seconds(99_999_999);
662 let gps: Time<Gps> = gal.into_scale().unwrap();
663
664 assert_eq!(gal.as_nanos(), gps.as_nanos());
665 }
666
667 #[test]
668 fn test_gps_galileo_gps_roundtrip() {
669 let gps = Time::<Gps>::from_week_tow(
670 2000,
671 DurationParts {
672 seconds: 123_456,
673 nanos: 789_000_000,
674 },
675 )
676 .unwrap();
677 let gal: Time<Galileo> = gps.into_scale().unwrap();
678 let back: Time<Gps> = gal.into_scale().unwrap();
679
680 assert_eq!(gps, back);
681 }
682
683 #[test]
684 fn test_gps_to_beidou_subtracts_14_seconds() {
685 let gps = Time::<Gps>::from_seconds(100);
687 let bdt: Time<Beidou> = gps.into_scale().unwrap();
688
689 assert_eq!(bdt.as_seconds(), 86); }
691
692 #[test]
693 fn test_beidou_to_gps_adds_14_seconds() {
694 let bdt = Time::<Beidou>::from_seconds(86);
695 let gps: Time<Gps> = bdt.into_scale().unwrap();
696
697 assert_eq!(gps.as_seconds(), 100);
698 }
699
700 #[test]
701 fn test_gps_beidou_gps_roundtrip() {
702 let gps = Time::<Gps>::from_week_tow(
703 2100,
704 DurationParts {
705 seconds: 86_400,
706 nanos: 0,
707 },
708 )
709 .unwrap();
710 let bdt: Time<Beidou> = gps.into_scale().unwrap();
711 let back: Time<Gps> = bdt.into_scale().unwrap();
712
713 assert_eq!(gps, back);
714 }
715
716 #[test]
717 fn test_galileo_beidou_roundtrip() {
718 let gal = Time::<Galileo>::from_seconds(1_000_000_000);
719 let bdt: Time<Beidou> = gal.into_scale().unwrap();
720 let back: Time<Galileo> = bdt.into_scale().unwrap();
721
722 assert_eq!(gal, back);
723 }
724
725 #[test]
726 fn test_glonass_epoch_to_utc_nanos() {
727 let glo = Time::<Glonass>::EPOCH;
728 let utc: Time<Utc> = glo.into_scale().unwrap();
729
730 assert_eq!(utc.as_nanos(), 757_371_600_000_000_000);
732 }
733
734 #[test]
735 fn test_utc_at_glonass_epoch_gives_zero() {
736 let utc = Time::<Utc>::from_nanos(757_371_600_000_000_000);
737 let glo: Time<Glonass> = utc.into_scale().unwrap();
738
739 assert_eq!(glo, Time::<Glonass>::EPOCH);
740 }
741
742 #[test]
743 fn test_glonass_utc_glonass_roundtrip() {
744 let glo = Time::<Glonass>::from_day_tod(
745 10_000,
746 DurationParts {
747 seconds: 36_000,
748 nanos: 0,
749 },
750 )
751 .unwrap();
752 let utc: Time<Utc> = glo.into_scale().unwrap();
753 let back: Time<Glonass> = utc.into_scale().unwrap();
754
755 assert_eq!(glo, back);
756 }
757
758 #[test]
759 fn test_utc_before_glonass_epoch_is_error() {
760 let utc = Time::<Utc>::EPOCH;
761 let result: Result<Time<Glonass>, _> = utc.into_scale();
762
763 assert!(matches!(result, Err(GnssTimeError::Overflow)));
764 }
765
766 #[test]
767 fn test_gps_utc_gps_roundtrip_at_gps_epoch() {
768 let ls = LeapSeconds::builtin();
769 let gps = Time::<Gps>::EPOCH;
770 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
771 let back: Time<Gps> = utc.into_scale_with(ls).unwrap();
772
773 assert_eq!(gps, back);
774 }
775
776 #[test]
777 fn test_gps_utc_gps_roundtrip_at_2020() {
778 let ls = LeapSeconds::builtin();
779 let gps = Time::<Gps>::from_week_tow(
780 2086,
781 DurationParts {
782 seconds: 0,
783 nanos: 0,
784 },
785 )
786 .unwrap();
787 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
788 let back: Time<Gps> = utc.into_scale_with(ls).unwrap();
789
790 assert_eq!(gps, back);
791 }
792
793 #[test]
794 fn test_gps_utc_roundtrip_exact_at_nanosecond_level() {
795 let ls = LeapSeconds::builtin();
796 let gps = Time::<Gps>::from_nanos(1_167_264_100_123_456_789);
798 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
799 let back: Time<Gps> = utc.into_scale_with(ls).unwrap();
800
801 assert_eq!(gps, back); }
803
804 #[test]
805 fn test_gps_leads_utc_by_18s_at_2017_01_01() {
806 let ls = LeapSeconds::builtin();
807 let expected_utc_s: u64 = 16_437 * 86_400;
809 let gps_s: u64 = 1_167_264_000 + 18; let gps = Time::<Gps>::from_seconds(gps_s);
814 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
815
816 assert_eq!(utc.as_seconds(), expected_utc_s);
817 }
818
819 #[test]
820 fn test_gps_leads_utc_by_13s_at_1999_01_01() {
821 let ls = LeapSeconds::builtin();
822 let gps = Time::<Gps>::from_seconds(599_184_013);
823 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
824 let expected_utc_s: u64 = 9_862 * 86_400; assert_eq!(utc.as_seconds(), expected_utc_s);
827 }
828
829 #[test]
830 fn test_gps_glonass_gps_roundtrip() {
831 let ls = LeapSeconds::builtin();
832 let gps = Time::<Gps>::from_week_tow(
833 2100,
834 DurationParts {
835 seconds: 86_400,
836 nanos: 0,
837 },
838 )
839 .unwrap();
840 let glo: Time<Glonass> = gps.into_scale_with(ls).unwrap();
841 let back: Time<Gps> = glo.into_scale_with(ls).unwrap();
842
843 assert_eq!(gps, back);
844 }
845
846 #[test]
847 fn test_normal_gps_gives_exact_convert_result() {
848 let ls = LeapSeconds::builtin();
849 let gps = Time::<Gps>::from_week_tow(
850 2086,
851 DurationParts {
852 seconds: 0,
853 nanos: 0,
854 },
855 )
856 .unwrap();
857 let result: ConvertResult<Time<Utc>> = gps.into_scale_with_checked(ls).unwrap();
858
859 assert!(result.is_exact());
860 }
861
862 #[test]
863 fn test_utc_to_gps_always_exact() {
864 let ls = LeapSeconds::builtin();
865 let utc = Time::<Utc>::from_nanos(757_371_600_000_000_000 + 1_000_000_000);
866 let result: ConvertResult<Time<Gps>> = utc.into_scale_with_checked(ls).unwrap();
867
868 assert!(result.is_exact());
869 }
870
871 #[test]
872 fn test_into_inner_returns_value() {
873 let t = Time::<Gps>::from_seconds(100);
874 let r = ConvertResult::Exact(t);
875
876 assert_eq!(r.into_inner(), t);
877
878 let t2 = Time::<Gps>::from_seconds(200);
879 let r2 = ConvertResult::AmbiguousLeapSecond(t2);
880
881 assert_eq!(r2.into_inner(), t2);
882 }
883
884 #[test]
885 fn test_gps_to_tai_overflow_at_max() {
886 let gps = Time::<Gps>::MAX;
887 let result: Result<Time<Tai>, _> = gps.into_scale();
888
889 assert!(matches!(result, Err(GnssTimeError::Overflow)));
890 }
891
892 #[test]
893 fn test_into_scale_gps_tai_matches_to_tai() {
894 let gps = Time::<Gps>::from_seconds(999_999);
895 let via_trait: Time<Tai> = gps.into_scale().unwrap();
896 let via_method = gps.to_tai().unwrap();
897
898 assert_eq!(via_trait, via_method);
899 }
900
901 #[test]
902 fn test_into_scale_with_gps_utc_matches_gps_to_utc() {
903 use crate::leap::{gps_to_utc, LeapSeconds};
904 let ls = LeapSeconds::builtin();
905 let gps = Time::<Gps>::from_seconds(599_184_013);
906 let via_trait: Time<Utc> = gps.into_scale_with(ls).unwrap();
907 let via_fn = gps_to_utc(gps, ls).unwrap();
908
909 assert_eq!(via_trait, via_fn);
910 }
911
912 #[test]
913 fn test_gps_to_utc_detects_leap_second_ambiguity() {
914 let ls = LeapSeconds::builtin();
915 let gps = Time::<Gps>::from_seconds(1_167_264_018);
917 let result: ConvertResult<Time<Utc>> = gps.into_scale_with_checked(ls).unwrap();
918
919 assert!(matches!(result, ConvertResult::AmbiguousLeapSecond(_)));
920 }
921
922 #[test]
923 fn test_all_roundtrip_invariants() {
924 let ls = LeapSeconds::builtin();
925
926 let gps_values = [
927 Time::<Gps>::from_week_tow(
928 2086,
929 DurationParts {
930 seconds: 0,
931 nanos: 0,
932 },
933 )
934 .unwrap(),
935 Time::<Gps>::from_week_tow(
936 2100,
937 DurationParts {
938 seconds: 86_400,
939 nanos: 0,
940 },
941 )
942 .unwrap(),
943 Time::<Gps>::from_nanos(1_167_264_100_123_456_789),
944 ];
945
946 for gps in gps_values {
947 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
948 let back: Time<Gps> = utc.into_scale_with(ls).unwrap();
949 assert_eq!(gps, back);
950
951 let gal: Time<Galileo> = gps.into_scale().unwrap();
952 let back: Time<Gps> = gal.into_scale().unwrap();
953 assert_eq!(gps, back);
954
955 let bdt: Time<Beidou> = gps.into_scale().unwrap();
956 let back: Time<Gps> = bdt.into_scale().unwrap();
957 assert_eq!(gps, back);
958 }
959 }
960
961 #[test]
962 fn test_gps_epoch_to_utc_is_exact() {
963 let ls = LeapSeconds::builtin();
964
965 let gps = Time::<Gps>::EPOCH;
966 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
967
968 assert_eq!(utc.as_seconds(), 252_892_800);
969 }
970
971 #[test]
972 fn test_gps_epoch_utc_roundtrip() {
973 let ls = LeapSeconds::builtin();
974
975 let gps = Time::<Gps>::EPOCH;
976 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
977 let back: Time<Gps> = utc.into_scale_with(ls).unwrap();
978
979 assert_eq!(gps, back);
980 }
981
982 #[test]
983 fn test_glonass_roundtrip_invariants_supported_range() {
984 let ls = LeapSeconds::builtin();
985
986 let gps_values = [
987 Time::<Gps>::from_week_tow(
988 2086,
989 DurationParts {
990 seconds: 0,
991 nanos: 0,
992 },
993 )
994 .unwrap(),
995 Time::<Gps>::from_week_tow(
996 2100,
997 DurationParts {
998 seconds: 86_400,
999 nanos: 0,
1000 },
1001 )
1002 .unwrap(),
1003 Time::<Gps>::from_nanos(1_167_264_100_123_456_789),
1004 ];
1005
1006 for gps in gps_values {
1007 let glo: Time<Glonass> = gps.into_scale_with(ls).unwrap();
1008 let back: Time<Gps> = glo.into_scale_with(ls).unwrap();
1009
1010 assert_eq!(gps, back);
1011 }
1012 }
1013
1014 #[test]
1015 fn test_checked_variants_contract() {
1016 let ls = LeapSeconds::builtin();
1017 let gps = Time::<Gps>::from_week_tow(
1018 2000,
1019 DurationParts {
1020 seconds: 0,
1021 nanos: 0,
1022 },
1023 )
1024 .unwrap();
1025 let res: ConvertResult<Time<Utc>> = gps.into_scale_with_checked(ls).unwrap();
1026
1027 match res {
1028 ConvertResult::Exact(_) => {}
1029 ConvertResult::AmbiguousLeapSecond(_) => panic!("unexpected ambiguity"),
1030 }
1031 }
1032
1033 #[test]
1034 fn test_convert_result_consistency() {
1035 let t = Time::<Gps>::from_seconds(42);
1036 let exact = ConvertResult::Exact(t);
1037
1038 assert!(exact.is_exact());
1039 assert!(!exact.is_ambiguous());
1040
1041 let amb = ConvertResult::AmbiguousLeapSecond(t);
1042
1043 assert!(!amb.is_exact());
1044 assert!(amb.is_ambiguous());
1045 }
1046
1047 #[test]
1048 fn test_gps_to_tai_overflow_near_max() {
1049 let gps = Time::<Gps>::from_nanos(Time::<Gps>::MAX.as_nanos() - 1);
1050 let result: Result<Time<Tai>, _> = gps.into_scale();
1051
1052 assert!(matches!(result, Err(GnssTimeError::Overflow)));
1053 }
1054
1055 #[test]
1056 fn test_gps_to_tai_near_overflow_succeeds() {
1057 let gps = Time::<Gps>::from_nanos(Time::<Gps>::MAX.as_nanos() - 20_000_000_000);
1058 let tai: Time<Tai> = gps.into_scale().unwrap();
1059
1060 assert!(tai.as_nanos() > gps.as_nanos());
1061 }
1062
1063 #[test]
1064 fn test_glonass_utc_symmetry_random() {
1065 let utc = Time::<Utc>::from_nanos(800_000_000_000_000_000);
1066 let glo: Time<Glonass> = utc.into_scale().unwrap();
1067 let back: Time<Utc> = glo.into_scale().unwrap();
1068
1069 assert_eq!(utc, back);
1070 }
1071}