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