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>(
116 self,
117 ls: P,
118 ) -> Result<Time<Target>, GnssTimeError>;
119
120 fn into_scale_with_checked<P: LeapSecondsProvider>(
128 self,
129 ls: P,
130 ) -> Result<ConvertResult<Time<Target>>, GnssTimeError>;
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
135#[must_use = "ConvertResult contains ambiguity information; call .into_inner() or match explicitly"]
136pub enum ConvertResult<T> {
137 Exact(T),
139
140 AmbiguousLeapSecond(T),
144}
145
146impl<T> ConvertResult<T> {
147 #[inline]
149 #[must_use]
150 pub fn into_inner(self) -> T {
151 match self {
152 Self::Exact(t) | Self::AmbiguousLeapSecond(t) => t,
153 }
154 }
155
156 #[inline]
158 #[must_use]
159 pub const fn is_exact(&self) -> bool {
160 matches!(self, Self::Exact(_))
161 }
162
163 #[inline]
165 #[must_use]
166 pub const fn is_ambiguous(&self) -> bool {
167 matches!(self, Self::AmbiguousLeapSecond(_))
168 }
169}
170
171impl IntoScale<Glonass> for Time<Utc> {
176 #[inline]
183 fn into_scale(self) -> Result<Time<Glonass>, GnssTimeError> {
184 utc_to_glonass(self)
185 }
186}
187
188impl IntoScaleWith<Glonass> for Time<Gps> {
189 fn into_scale_with<P: LeapSecondsProvider>(
190 self,
191 ls: P,
192 ) -> Result<Time<Glonass>, GnssTimeError> {
193 gps_to_glonass(self, &ls)
194 }
195
196 fn into_scale_with_checked<P: LeapSecondsProvider>(
197 self,
198 ls: P,
199 ) -> Result<ConvertResult<Time<Glonass>>, GnssTimeError> {
200 Ok(ConvertResult::Exact(gps_to_glonass(self, &ls)?))
201 }
202}
203
204impl IntoScaleWith<Glonass> for Time<Galileo> {
205 fn into_scale_with<P: LeapSecondsProvider>(
206 self,
207 ls: P,
208 ) -> Result<Time<Glonass>, GnssTimeError> {
209 galileo_to_glonass(self, &ls)
210 }
211
212 fn into_scale_with_checked<P: LeapSecondsProvider>(
213 self,
214 ls: P,
215 ) -> Result<ConvertResult<Time<Glonass>>, GnssTimeError> {
216 Ok(ConvertResult::Exact(galileo_to_glonass(self, &ls)?))
217 }
218}
219
220impl IntoScaleWith<Glonass> for Time<Beidou> {
221 fn into_scale_with<P: LeapSecondsProvider>(
222 self,
223 ls: P,
224 ) -> Result<Time<Glonass>, GnssTimeError> {
225 beidou_to_glonass(self, &ls)
226 }
227
228 fn into_scale_with_checked<P: LeapSecondsProvider>(
229 self,
230 ls: P,
231 ) -> Result<ConvertResult<Time<Glonass>>, GnssTimeError> {
232 Ok(ConvertResult::Exact(beidou_to_glonass(self, &ls)?))
233 }
234}
235
236impl IntoScale<Gps> for Time<Galileo> {
241 #[inline]
242 fn into_scale(self) -> Result<Time<Gps>, GnssTimeError> {
243 self.try_convert::<Gps>()
244 }
245}
246
247impl IntoScale<Gps> for Time<Beidou> {
248 #[inline]
259 fn into_scale(self) -> Result<Time<Gps>, GnssTimeError> {
260 self.try_convert::<Gps>()
261 }
262}
263
264impl IntoScale<Gps> for Time<Tai> {
265 #[inline]
276 fn into_scale(self) -> Result<Time<Gps>, GnssTimeError> {
277 Time::<Gps>::from_tai(self)
278 }
279}
280
281impl IntoScaleWith<Gps> for Time<Glonass> {
282 fn into_scale_with<P: LeapSecondsProvider>(
284 self,
285 ls: P,
286 ) -> Result<Time<Gps>, GnssTimeError> {
287 glonass_to_gps(self, &ls)
288 }
289
290 fn into_scale_with_checked<P: LeapSecondsProvider>(
291 self,
292 ls: P,
293 ) -> Result<ConvertResult<Time<Gps>>, GnssTimeError> {
294 Ok(ConvertResult::Exact(glonass_to_gps(self, &ls)?))
295 }
296}
297
298impl IntoScaleWith<Gps> for Time<Utc> {
299 fn into_scale_with<P: LeapSecondsProvider>(
319 self,
320 ls: P,
321 ) -> Result<Time<Gps>, GnssTimeError> {
322 utc_to_gps(self, &ls)
323 }
324
325 fn into_scale_with_checked<P: LeapSecondsProvider>(
326 self,
327 ls: P,
328 ) -> Result<ConvertResult<Time<Gps>>, GnssTimeError> {
329 Ok(ConvertResult::Exact(utc_to_gps(self, &ls)?))
332 }
333}
334
335impl IntoScale<Galileo> for Time<Gps> {
340 #[inline]
354 fn into_scale(self) -> Result<Time<Galileo>, GnssTimeError> {
355 self.try_convert::<Galileo>()
358 }
359}
360
361impl IntoScale<Galileo> for Time<Beidou> {
362 #[inline]
364 fn into_scale(self) -> Result<Time<Galileo>, GnssTimeError> {
365 self.try_convert::<Galileo>()
366 }
367}
368
369impl IntoScaleWith<Galileo> for Time<Glonass> {
370 fn into_scale_with<P: LeapSecondsProvider>(
372 self,
373 ls: P,
374 ) -> Result<Time<Galileo>, GnssTimeError> {
375 glonass_to_galileo(self, &ls)
376 }
377
378 fn into_scale_with_checked<P: LeapSecondsProvider>(
379 self,
380 ls: P,
381 ) -> Result<ConvertResult<Time<Galileo>>, GnssTimeError> {
382 Ok(ConvertResult::Exact(glonass_to_galileo(self, &ls)?))
383 }
384}
385
386impl IntoScaleWith<Galileo> for Time<Utc> {
387 fn into_scale_with<P: LeapSecondsProvider>(
389 self,
390 ls: P,
391 ) -> Result<Time<Galileo>, GnssTimeError> {
392 utc_to_galileo(self, &ls)
393 }
394
395 fn into_scale_with_checked<P: LeapSecondsProvider>(
396 self,
397 ls: P,
398 ) -> Result<ConvertResult<Time<Galileo>>, GnssTimeError> {
399 Ok(ConvertResult::Exact(utc_to_galileo(self, &ls)?))
400 }
401}
402
403impl IntoScale<Beidou> for Time<Gps> {
408 #[inline]
419 fn into_scale(self) -> Result<Time<Beidou>, GnssTimeError> {
420 self.try_convert::<Beidou>()
421 }
422}
423
424impl IntoScale<Beidou> for Time<Galileo> {
425 #[inline]
427 fn into_scale(self) -> Result<Time<Beidou>, GnssTimeError> {
428 self.try_convert::<Beidou>()
429 }
430}
431
432impl IntoScaleWith<Beidou> for Time<Utc> {
433 fn into_scale_with<P: LeapSecondsProvider>(
435 self,
436 ls: P,
437 ) -> Result<Time<Beidou>, GnssTimeError> {
438 utc_to_beidou(self, &ls)
439 }
440 fn into_scale_with_checked<P: LeapSecondsProvider>(
441 self,
442 ls: P,
443 ) -> Result<ConvertResult<Time<Beidou>>, GnssTimeError> {
444 Ok(ConvertResult::Exact(utc_to_beidou(self, &ls)?))
445 }
446}
447
448impl IntoScaleWith<Beidou> for Time<Glonass> {
449 fn into_scale_with<P: LeapSecondsProvider>(
451 self,
452 ls: P,
453 ) -> Result<Time<Beidou>, GnssTimeError> {
454 glonass_to_beidou(self, &ls)
455 }
456
457 fn into_scale_with_checked<P: LeapSecondsProvider>(
458 self,
459 ls: P,
460 ) -> Result<ConvertResult<Time<Beidou>>, GnssTimeError> {
461 Ok(ConvertResult::Exact(glonass_to_beidou(self, &ls)?))
462 }
463}
464
465impl IntoScale<Utc> for Time<Glonass> {
470 #[inline]
493 fn into_scale(self) -> Result<Time<Utc>, GnssTimeError> {
494 glonass_to_utc(self)
495 }
496}
497
498impl IntoScaleWith<Utc> for Time<Gps> {
499 #[inline]
517 fn into_scale_with<P: LeapSecondsProvider>(
518 self,
519 ls: P,
520 ) -> Result<Time<Utc>, GnssTimeError> {
521 gps_to_utc(self, &ls)
522 }
523
524 fn into_scale_with_checked<P: LeapSecondsProvider>(
525 self,
526 ls: P,
527 ) -> Result<ConvertResult<Time<Utc>>, GnssTimeError> {
528 let utc = gps_to_utc(self, &ls)?;
529
530 let tai = self.to_tai()?;
534 let n_at = ls.tai_minus_utc_at(tai);
535
536 let tai_prev = if tai.as_nanos() >= 1_000_000_000 {
538 Time::<Tai>::from_nanos(tai.as_nanos() - 1_000_000_000)
539 } else {
540 tai
541 };
542 let n_before = ls.tai_minus_utc_at(tai_prev);
543
544 if n_at == n_before {
545 Ok(ConvertResult::Exact(utc))
546 } else {
547 Ok(ConvertResult::AmbiguousLeapSecond(utc))
550 }
551 }
552}
553
554impl IntoScaleWith<Utc> for Time<Galileo> {
555 fn into_scale_with<P: LeapSecondsProvider>(
557 self,
558 ls: P,
559 ) -> Result<Time<Utc>, GnssTimeError> {
560 galileo_to_utc(self, &ls)
561 }
562
563 fn into_scale_with_checked<P: LeapSecondsProvider>(
564 self,
565 ls: P,
566 ) -> Result<ConvertResult<Time<Utc>>, GnssTimeError> {
567 Ok(ConvertResult::Exact(galileo_to_utc(self, &ls)?))
568 }
569}
570
571impl IntoScaleWith<Utc> for Time<Beidou> {
572 fn into_scale_with<P: LeapSecondsProvider>(
574 self,
575 ls: P,
576 ) -> Result<Time<Utc>, GnssTimeError> {
577 beidou_to_utc(self, &ls)
578 }
579
580 fn into_scale_with_checked<P: LeapSecondsProvider>(
581 self,
582 ls: P,
583 ) -> Result<ConvertResult<Time<Utc>>, GnssTimeError> {
584 Ok(ConvertResult::Exact(beidou_to_utc(self, &ls)?))
585 }
586}
587
588impl IntoScale<Tai> for Time<Gps> {
593 #[inline]
604 fn into_scale(self) -> Result<Time<Tai>, GnssTimeError> {
605 self.to_tai()
606 }
607}
608
609#[cfg(test)]
614mod tests {
615 use super::*;
616 use crate::{DurationParts, LeapSeconds};
617
618 #[test]
619 fn test_gps_to_tai_adds_19_seconds() {
620 let gps = Time::<Gps>::from_seconds(100);
621 let tai: Time<Tai> = gps.into_scale().unwrap();
622
623 assert_eq!(tai.as_seconds(), 119);
625 }
626
627 #[test]
628 fn test_tai_to_gps_subtracts_19_seconds() {
629 let tai = Time::<Tai>::from_seconds(119);
630 let gps: Time<Gps> = tai.into_scale().unwrap();
631
632 assert_eq!(gps.as_seconds(), 100);
634 }
635
636 #[test]
637 fn test_gps_tai_gps_roundtrip() {
638 let gps = Time::<Gps>::from_week_tow(
639 2345,
640 DurationParts {
641 seconds: 432_000,
642 nanos: 0,
643 },
644 )
645 .unwrap();
646 let tai: Time<Tai> = gps.into_scale().unwrap();
647 let back: Time<Gps> = tai.into_scale().unwrap();
648
649 assert_eq!(gps, back);
650 }
651
652 #[test]
653 fn test_tai_to_gps_underflow_at_tai_zero() {
654 let tai = Time::<Tai>::EPOCH;
656 let result: Result<Time<Gps>, _> = tai.into_scale();
657
658 assert!(matches!(result, Err(GnssTimeError::Overflow)));
659 }
660
661 #[test]
662 fn test_gps_to_galileo_preserves_nanos() {
663 let gps = Time::<Gps>::from_seconds(12_345_678);
664 let gal: Time<Galileo> = gps.into_scale().unwrap();
665
666 assert_eq!(gps.as_nanos(), gal.as_nanos());
667 }
668
669 #[test]
670 fn test_galileo_to_gps_preserves_nanos() {
671 let gal = Time::<Galileo>::from_seconds(99_999_999);
672 let gps: Time<Gps> = gal.into_scale().unwrap();
673
674 assert_eq!(gal.as_nanos(), gps.as_nanos());
675 }
676
677 #[test]
678 fn test_gps_galileo_gps_roundtrip() {
679 let gps = Time::<Gps>::from_week_tow(
680 2000,
681 DurationParts {
682 seconds: 123_456,
683 nanos: 789_000_000,
684 },
685 )
686 .unwrap();
687 let gal: Time<Galileo> = gps.into_scale().unwrap();
688 let back: Time<Gps> = gal.into_scale().unwrap();
689
690 assert_eq!(gps, back);
691 }
692
693 #[test]
694 fn test_gps_to_beidou_subtracts_14_seconds() {
695 let gps = Time::<Gps>::from_seconds(100);
697 let bdt: Time<Beidou> = gps.into_scale().unwrap();
698
699 assert_eq!(bdt.as_seconds(), 86); }
701
702 #[test]
703 fn test_beidou_to_gps_adds_14_seconds() {
704 let bdt = Time::<Beidou>::from_seconds(86);
705 let gps: Time<Gps> = bdt.into_scale().unwrap();
706
707 assert_eq!(gps.as_seconds(), 100);
708 }
709
710 #[test]
711 fn test_gps_beidou_gps_roundtrip() {
712 let gps = Time::<Gps>::from_week_tow(
713 2100,
714 DurationParts {
715 seconds: 86_400,
716 nanos: 0,
717 },
718 )
719 .unwrap();
720 let bdt: Time<Beidou> = gps.into_scale().unwrap();
721 let back: Time<Gps> = bdt.into_scale().unwrap();
722
723 assert_eq!(gps, back);
724 }
725
726 #[test]
727 fn test_galileo_beidou_roundtrip() {
728 let gal = Time::<Galileo>::from_seconds(1_000_000_000);
729 let bdt: Time<Beidou> = gal.into_scale().unwrap();
730 let back: Time<Galileo> = bdt.into_scale().unwrap();
731
732 assert_eq!(gal, back);
733 }
734
735 #[test]
736 fn test_glonass_epoch_to_utc_nanos() {
737 let glo = Time::<Glonass>::EPOCH;
738 let utc: Time<Utc> = glo.into_scale().unwrap();
739
740 assert_eq!(utc.as_nanos(), 757_371_600_000_000_000);
742 }
743
744 #[test]
745 fn test_utc_at_glonass_epoch_gives_zero() {
746 let utc = Time::<Utc>::from_nanos(757_371_600_000_000_000);
747 let glo: Time<Glonass> = utc.into_scale().unwrap();
748
749 assert_eq!(glo, Time::<Glonass>::EPOCH);
750 }
751
752 #[test]
753 fn test_glonass_utc_glonass_roundtrip() {
754 let glo = Time::<Glonass>::from_day_tod(
755 10_000,
756 DurationParts {
757 seconds: 36_000,
758 nanos: 0,
759 },
760 )
761 .unwrap();
762 let utc: Time<Utc> = glo.into_scale().unwrap();
763 let back: Time<Glonass> = utc.into_scale().unwrap();
764
765 assert_eq!(glo, back);
766 }
767
768 #[test]
769 fn test_utc_before_glonass_epoch_is_error() {
770 let utc = Time::<Utc>::EPOCH;
771 let result: Result<Time<Glonass>, _> = utc.into_scale();
772
773 assert!(matches!(result, Err(GnssTimeError::Overflow)));
774 }
775
776 #[test]
777 fn test_gps_utc_gps_roundtrip_at_gps_epoch() {
778 let ls = LeapSeconds::builtin();
779 let gps = Time::<Gps>::EPOCH;
780 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
781 let back: Time<Gps> = utc.into_scale_with(ls).unwrap();
782
783 assert_eq!(gps, back);
784 }
785
786 #[test]
787 fn test_gps_utc_gps_roundtrip_at_2020() {
788 let ls = LeapSeconds::builtin();
789 let gps = Time::<Gps>::from_week_tow(
790 2086,
791 DurationParts {
792 seconds: 0,
793 nanos: 0,
794 },
795 )
796 .unwrap();
797 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
798 let back: Time<Gps> = utc.into_scale_with(ls).unwrap();
799
800 assert_eq!(gps, back);
801 }
802
803 #[test]
804 fn test_gps_utc_roundtrip_exact_at_nanosecond_level() {
805 let ls = LeapSeconds::builtin();
806 let gps = Time::<Gps>::from_nanos(1_167_264_100_123_456_789);
808 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
809 let back: Time<Gps> = utc.into_scale_with(ls).unwrap();
810
811 assert_eq!(gps, back); }
813
814 #[test]
815 fn test_gps_leads_utc_by_18s_at_2017_01_01() {
816 let ls = LeapSeconds::builtin();
817 let expected_utc_s: u64 = 16_437 * 86_400;
819 let gps_s: u64 = 1_167_264_000 + 18; let gps = Time::<Gps>::from_seconds(gps_s);
824 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
825
826 assert_eq!(utc.as_seconds(), expected_utc_s);
827 }
828
829 #[test]
830 fn test_gps_leads_utc_by_13s_at_1999_01_01() {
831 let ls = LeapSeconds::builtin();
832 let gps = Time::<Gps>::from_seconds(599_184_013);
833 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
834 let expected_utc_s: u64 = 9_862 * 86_400; assert_eq!(utc.as_seconds(), expected_utc_s);
837 }
838
839 #[test]
840 fn test_gps_glonass_gps_roundtrip() {
841 let ls = LeapSeconds::builtin();
842 let gps = Time::<Gps>::from_week_tow(
843 2100,
844 DurationParts {
845 seconds: 86_400,
846 nanos: 0,
847 },
848 )
849 .unwrap();
850 let glo: Time<Glonass> = gps.into_scale_with(ls).unwrap();
851 let back: Time<Gps> = glo.into_scale_with(ls).unwrap();
852
853 assert_eq!(gps, back);
854 }
855
856 #[test]
857 fn test_normal_gps_gives_exact_convert_result() {
858 let ls = LeapSeconds::builtin();
859 let gps = Time::<Gps>::from_week_tow(
860 2086,
861 DurationParts {
862 seconds: 0,
863 nanos: 0,
864 },
865 )
866 .unwrap();
867 let result: ConvertResult<Time<Utc>> = gps.into_scale_with_checked(ls).unwrap();
868
869 assert!(result.is_exact());
870 }
871
872 #[test]
873 fn test_utc_to_gps_always_exact() {
874 let ls = LeapSeconds::builtin();
875 let utc = Time::<Utc>::from_nanos(757_371_600_000_000_000 + 1_000_000_000);
876 let result: ConvertResult<Time<Gps>> = utc.into_scale_with_checked(ls).unwrap();
877
878 assert!(result.is_exact());
879 }
880
881 #[test]
882 fn test_into_inner_returns_value() {
883 let t = Time::<Gps>::from_seconds(100);
884 let r = ConvertResult::Exact(t);
885
886 assert_eq!(r.into_inner(), t);
887
888 let t2 = Time::<Gps>::from_seconds(200);
889 let r2 = ConvertResult::AmbiguousLeapSecond(t2);
890
891 assert_eq!(r2.into_inner(), t2);
892 }
893
894 #[test]
895 fn test_gps_to_tai_overflow_at_max() {
896 let gps = Time::<Gps>::MAX;
897 let result: Result<Time<Tai>, _> = gps.into_scale();
898
899 assert!(matches!(result, Err(GnssTimeError::Overflow)));
900 }
901
902 #[test]
903 fn test_into_scale_gps_tai_matches_to_tai() {
904 let gps = Time::<Gps>::from_seconds(999_999);
905 let via_trait: Time<Tai> = gps.into_scale().unwrap();
906 let via_method = gps.to_tai().unwrap();
907
908 assert_eq!(via_trait, via_method);
909 }
910
911 #[test]
912 fn test_into_scale_with_gps_utc_matches_gps_to_utc() {
913 use crate::leap::{gps_to_utc, LeapSeconds};
914 let ls = LeapSeconds::builtin();
915 let gps = Time::<Gps>::from_seconds(599_184_013);
916 let via_trait: Time<Utc> = gps.into_scale_with(ls).unwrap();
917 let via_fn = gps_to_utc(gps, ls).unwrap();
918
919 assert_eq!(via_trait, via_fn);
920 }
921
922 #[test]
923 fn test_gps_to_utc_detects_leap_second_ambiguity() {
924 let ls = LeapSeconds::builtin();
925 let gps = Time::<Gps>::from_seconds(1_167_264_018);
927 let result: ConvertResult<Time<Utc>> = gps.into_scale_with_checked(ls).unwrap();
928
929 assert!(matches!(result, ConvertResult::AmbiguousLeapSecond(_)));
930 }
931
932 #[test]
933 fn test_all_roundtrip_invariants() {
934 let ls = LeapSeconds::builtin();
935
936 let gps_values = [
937 Time::<Gps>::from_week_tow(
938 2086,
939 DurationParts {
940 seconds: 0,
941 nanos: 0,
942 },
943 )
944 .unwrap(),
945 Time::<Gps>::from_week_tow(
946 2100,
947 DurationParts {
948 seconds: 86_400,
949 nanos: 0,
950 },
951 )
952 .unwrap(),
953 Time::<Gps>::from_nanos(1_167_264_100_123_456_789),
954 ];
955
956 for gps in gps_values {
957 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
958 let back: Time<Gps> = utc.into_scale_with(ls).unwrap();
959 assert_eq!(gps, back);
960
961 let gal: Time<Galileo> = gps.into_scale().unwrap();
962 let back: Time<Gps> = gal.into_scale().unwrap();
963 assert_eq!(gps, back);
964
965 let bdt: Time<Beidou> = gps.into_scale().unwrap();
966 let back: Time<Gps> = bdt.into_scale().unwrap();
967 assert_eq!(gps, back);
968 }
969 }
970
971 #[test]
972 fn test_gps_epoch_to_utc_is_exact() {
973 let ls = LeapSeconds::builtin();
974
975 let gps = Time::<Gps>::EPOCH;
976 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
977
978 assert_eq!(utc.as_seconds(), 252_892_800);
979 }
980
981 #[test]
982 fn test_gps_epoch_utc_roundtrip() {
983 let ls = LeapSeconds::builtin();
984
985 let gps = Time::<Gps>::EPOCH;
986 let utc: Time<Utc> = gps.into_scale_with(ls).unwrap();
987 let back: Time<Gps> = utc.into_scale_with(ls).unwrap();
988
989 assert_eq!(gps, back);
990 }
991
992 #[test]
993 fn test_glonass_roundtrip_invariants_supported_range() {
994 let ls = LeapSeconds::builtin();
995
996 let gps_values = [
997 Time::<Gps>::from_week_tow(
998 2086,
999 DurationParts {
1000 seconds: 0,
1001 nanos: 0,
1002 },
1003 )
1004 .unwrap(),
1005 Time::<Gps>::from_week_tow(
1006 2100,
1007 DurationParts {
1008 seconds: 86_400,
1009 nanos: 0,
1010 },
1011 )
1012 .unwrap(),
1013 Time::<Gps>::from_nanos(1_167_264_100_123_456_789),
1014 ];
1015
1016 for gps in gps_values {
1017 let glo: Time<Glonass> = gps.into_scale_with(ls).unwrap();
1018 let back: Time<Gps> = glo.into_scale_with(ls).unwrap();
1019
1020 assert_eq!(gps, back);
1021 }
1022 }
1023
1024 #[test]
1025 fn test_checked_variants_contract() {
1026 let ls = LeapSeconds::builtin();
1027 let gps = Time::<Gps>::from_week_tow(
1028 2000,
1029 DurationParts {
1030 seconds: 0,
1031 nanos: 0,
1032 },
1033 )
1034 .unwrap();
1035 let res: ConvertResult<Time<Utc>> = gps.into_scale_with_checked(ls).unwrap();
1036
1037 match res {
1038 ConvertResult::Exact(_) => {}
1039 ConvertResult::AmbiguousLeapSecond(_) => panic!("unexpected ambiguity"),
1040 }
1041 }
1042
1043 #[test]
1044 fn test_convert_result_consistency() {
1045 let t = Time::<Gps>::from_seconds(42);
1046 let exact = ConvertResult::Exact(t);
1047
1048 assert!(exact.is_exact());
1049 assert!(!exact.is_ambiguous());
1050
1051 let amb = ConvertResult::AmbiguousLeapSecond(t);
1052
1053 assert!(!amb.is_exact());
1054 assert!(amb.is_ambiguous());
1055 }
1056
1057 #[test]
1058 fn test_gps_to_tai_overflow_near_max() {
1059 let gps = Time::<Gps>::from_nanos(Time::<Gps>::MAX.as_nanos() - 1);
1060 let result: Result<Time<Tai>, _> = gps.into_scale();
1061
1062 assert!(matches!(result, Err(GnssTimeError::Overflow)));
1063 }
1064
1065 #[test]
1066 fn test_gps_to_tai_near_overflow_succeeds() {
1067 let gps = Time::<Gps>::from_nanos(Time::<Gps>::MAX.as_nanos() - 20_000_000_000);
1068 let tai: Time<Tai> = gps.into_scale().unwrap();
1069
1070 assert!(tai.as_nanos() > gps.as_nanos());
1071 }
1072
1073 #[test]
1074 fn test_glonass_utc_symmetry_random() {
1075 let utc = Time::<Utc>::from_nanos(800_000_000_000_000_000);
1076 let glo: Time<Glonass> = utc.into_scale().unwrap();
1077 let back: Time<Utc> = glo.into_scale().unwrap();
1078
1079 assert_eq!(utc, back);
1080 }
1081}