1use core::fmt;
7use core::marker::PhantomData;
8
9use crate::context::TimeContext;
10use crate::encoding::{
11 j2000_seconds_to_jd, j2000_seconds_to_mjd, jd_to_j2000_seconds, mjd_to_j2000_seconds,
12};
13use crate::error::ConversionError;
14use crate::scale::conversion::InfallibleScaleConvert;
15use crate::scale::{CoordinateScale, Scale, TAI, TDB, TT, UTC};
16use crate::sealed::Sealed;
17use crate::target::{ContextConversionTarget, ConversionTarget, InfallibleConversionTarget};
18use crate::time::Time;
19use qtty::{Day, Quantity, Second, Unit};
20
21#[allow(private_bounds)]
23pub trait TimeRepresentation: Sealed + Copy + Clone + fmt::Debug + 'static {
24 type Unit: Unit;
26
27 const NAME: &'static str;
29}
30
31#[allow(private_bounds)]
33pub trait RepresentationForScale<S: Scale>: TimeRepresentation + Sealed {
34 fn try_from_time(
35 time: Time<S>,
36 ctx: &TimeContext,
37 ) -> Result<Quantity<Self::Unit>, ConversionError>;
38 fn try_into_time(
39 raw: Quantity<Self::Unit>,
40 ctx: &TimeContext,
41 ) -> Result<Time<S>, ConversionError>;
42}
43
44#[allow(private_bounds)]
46pub trait InfallibleRepresentationForScale<S: Scale>: RepresentationForScale<S> + Sealed {
47 fn from_time(time: Time<S>) -> Quantity<Self::Unit>;
48 fn into_time(raw: Quantity<Self::Unit>) -> Time<S>;
49}
50
51#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
53pub struct J2000s;
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub struct JD;
58
59#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub struct MJD;
62
63#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
65pub struct Unix;
66
67#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub struct GPS;
70
71impl Sealed for J2000s {}
72impl Sealed for JD {}
73impl Sealed for MJD {}
74impl Sealed for Unix {}
75impl Sealed for GPS {}
76
77impl TimeRepresentation for J2000s {
78 type Unit = qtty::unit::Second;
79 const NAME: &'static str = "J2000s";
80}
81
82impl TimeRepresentation for JD {
83 type Unit = qtty::unit::Day;
84 const NAME: &'static str = "Julian Day";
85}
86
87impl TimeRepresentation for MJD {
88 type Unit = qtty::unit::Day;
89 const NAME: &'static str = "Modified Julian Day";
90}
91
92impl TimeRepresentation for Unix {
93 type Unit = qtty::unit::Second;
94 const NAME: &'static str = "Unix";
95}
96
97impl TimeRepresentation for GPS {
98 type Unit = qtty::unit::Second;
99 const NAME: &'static str = "GPS";
100}
101
102pub struct EncodedTime<S: Scale, R: TimeRepresentation> {
104 raw: Quantity<R::Unit>,
105 _marker: PhantomData<fn() -> S>,
106}
107
108impl<S: Scale, R: TimeRepresentation> Copy for EncodedTime<S, R> {}
109
110impl<S: Scale, R: TimeRepresentation> Clone for EncodedTime<S, R> {
111 #[inline]
112 fn clone(&self) -> Self {
113 *self
114 }
115}
116
117impl<S: Scale, R: TimeRepresentation> fmt::Debug for EncodedTime<S, R> {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 f.debug_struct("EncodedTime")
120 .field("scale", &S::NAME)
121 .field("representation", &R::NAME)
122 .field("raw", &self.raw)
123 .finish()
124 }
125}
126
127impl<S: Scale, R: TimeRepresentation> fmt::Display for EncodedTime<S, R>
128where
129 qtty::Quantity<R::Unit>: fmt::Display,
130{
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 write!(f, "{} ", R::NAME)?;
133 fmt::Display::fmt(&self.raw, f)
134 }
135}
136
137impl<S: Scale, R: TimeRepresentation> fmt::LowerExp for EncodedTime<S, R>
138where
139 qtty::Quantity<R::Unit>: fmt::LowerExp,
140{
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 fmt::LowerExp::fmt(&self.raw, f)
143 }
144}
145
146impl<S: Scale, R: TimeRepresentation> fmt::UpperExp for EncodedTime<S, R>
147where
148 qtty::Quantity<R::Unit>: fmt::UpperExp,
149{
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 fmt::UpperExp::fmt(&self.raw, f)
152 }
153}
154
155impl<S: Scale, R: TimeRepresentation> PartialEq for EncodedTime<S, R> {
156 #[inline]
157 fn eq(&self, other: &Self) -> bool {
158 self.raw == other.raw
159 }
160}
161
162impl<S: Scale, R: TimeRepresentation> PartialOrd for EncodedTime<S, R> {
163 #[inline]
164 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
165 self.raw.partial_cmp(&other.raw)
166 }
167}
168
169impl<S: Scale, R: TimeRepresentation> EncodedTime<S, R> {
170 #[inline]
176 pub const fn new_unchecked(raw: Quantity<R::Unit>) -> Self {
177 Self {
178 raw,
179 _marker: PhantomData,
180 }
181 }
182
183 #[inline]
185 pub const fn raw(self) -> Quantity<R::Unit> {
186 self.raw
187 }
188
189 #[inline]
191 pub const fn quantity(self) -> Quantity<R::Unit> {
192 self.raw
193 }
194}
195
196impl<S: Scale, R> EncodedTime<S, R>
197where
198 R: RepresentationForScale<S>,
199{
200 #[inline]
202 pub fn try_new(raw: Quantity<R::Unit>) -> Result<Self, ConversionError> {
203 if raw.is_finite() {
204 Ok(Self::new_unchecked(raw))
205 } else {
206 Err(ConversionError::NonFinite)
207 }
208 }
209
210 #[inline]
216 pub fn try_to_time(self) -> Result<Time<S>, ConversionError> {
217 R::try_into_time(self.raw, &TimeContext::new())
218 }
219
220 #[inline]
222 pub fn to_time_with(self, ctx: &TimeContext) -> Result<Time<S>, ConversionError> {
223 R::try_into_time(self.raw, ctx)
224 }
225}
226
227impl<S: Scale, R> EncodedTime<S, R>
228where
229 R: InfallibleRepresentationForScale<S>,
230{
231 #[inline]
232 pub(crate) fn from_time_infallible(time: Time<S>) -> Self {
233 Self::new_unchecked(R::from_time(time))
234 }
235
236 #[inline]
238 pub fn to_time(self) -> Time<S> {
239 R::into_time(self.raw)
240 }
241
242 #[allow(private_bounds)]
244 #[inline]
245 pub fn to<T>(self) -> T::Output
246 where
247 T: InfallibleConversionTarget<S>,
248 {
249 T::convert(self.to_time())
250 }
251
252 #[allow(private_bounds)]
254 #[inline]
255 pub fn try_to<T>(self) -> Result<T::Output, ConversionError>
256 where
257 T: ConversionTarget<S>,
258 {
259 T::try_convert(self.to_time())
260 }
261}
262
263impl<S: Scale, R> EncodedTime<S, R>
264where
265 R: RepresentationForScale<S>,
266{
267 #[allow(private_bounds)]
269 #[inline]
270 pub fn to_with<T>(self, ctx: &TimeContext) -> Result<T::Output, ConversionError>
271 where
272 T: ContextConversionTarget<S>,
273 {
274 T::convert_with(self.to_time_with(ctx)?, ctx)
275 }
276}
277
278pub type JulianDate<S> = EncodedTime<S, JD>;
280
281pub type ModifiedJulianDate<S> = EncodedTime<S, MJD>;
283
284pub type J2000Seconds<S> = EncodedTime<S, J2000s>;
286
287pub type UnixTime = EncodedTime<UTC, Unix>;
289
290pub type GpsTime = EncodedTime<TAI, GPS>;
292
293pub const J2000_TT: JulianDate<TT> = EncodedTime::<TT, JD>::new_unchecked(Day::new(2_451_545.0));
295
296impl<S: Scale, R> EncodedTime<S, R>
299where
300 R: TimeRepresentation<Unit = qtty::unit::Day>,
301{
302 #[inline]
306 pub fn min(self, other: Self) -> Self {
307 if self.raw.value() <= other.raw.value() {
308 self
309 } else {
310 other
311 }
312 }
313
314 #[inline]
316 pub fn max(self, other: Self) -> Self {
317 if self.raw.value() >= other.raw.value() {
318 self
319 } else {
320 other
321 }
322 }
323
324 #[inline]
326 pub fn mean(self, other: Self) -> Self {
327 Self::new_unchecked(Day::new((self.raw.value() + other.raw.value()) * 0.5))
328 }
329}
330
331pub const JULIAN_YEAR_DAYS: Day = Day::new(365.25);
335
336impl<S: CoordinateScale> JulianDate<S> {
337 #[inline]
341 pub fn new(jd: f64) -> Self {
342 Self::new_unchecked(Day::new(jd))
343 }
344
345 #[inline]
347 pub fn jd_value(self) -> f64 {
348 self.raw().value()
349 }
350
351 #[inline]
353 pub fn julian_centuries(self) -> f64 {
354 (self.raw().value() - crate::constats::J2000_JD_TT.value())
355 / crate::constats::DAYS_PER_JC.value()
356 }
357
358 #[inline]
360 pub fn julian_millennias(self) -> f64 {
361 (self.raw().value() - crate::constats::J2000_JD_TT.value())
362 / (crate::constats::DAYS_PER_JC.value() * 10.0)
363 }
364
365 pub const JULIAN_YEAR: Day = Day::new(365.25);
367
368 pub const JULIAN_CENTURY: Day = Day::new(36_525.0);
370}
371
372impl JulianDate<TT> {
373 pub const J2000: Self = J2000_TT;
375
376 #[inline]
382 pub fn tt_to_tdb(self) -> JulianDate<TDB> {
383 JulianDate::<TDB>::from_time_infallible(self.to_time().to_scale::<TDB>())
384 }
385
386 #[inline]
392 pub fn from_chrono(dt: chrono::DateTime<chrono::Utc>) -> Self {
393 let utc = Time::<UTC>::from_chrono(dt);
394 Self::from_time_infallible(utc.to_scale::<TT>())
395 }
396
397 #[inline]
401 pub fn to_chrono(self) -> Option<chrono::DateTime<chrono::Utc>> {
402 self.to_time().to_scale::<UTC>().to_chrono()
403 }
404}
405
406impl<S: CoordinateScale> From<ModifiedJulianDate<S>> for JulianDate<S> {
409 #[inline]
411 fn from(mjd: ModifiedJulianDate<S>) -> Self {
412 mjd.to::<JD>()
413 }
414}
415
416impl<S: CoordinateScale> From<JulianDate<S>> for ModifiedJulianDate<S> {
417 #[inline]
419 fn from(jd: JulianDate<S>) -> Self {
420 jd.to::<MJD>()
421 }
422}
423
424impl<S: CoordinateScale> ModifiedJulianDate<S> {
427 #[inline]
431 pub fn new(mjd: f64) -> Self {
432 Self::new_unchecked(Day::new(mjd))
433 }
434
435 #[inline]
437 pub fn mjd_value(self) -> f64 {
438 self.raw().value()
439 }
440}
441
442impl ModifiedJulianDate<TT> {
443 #[inline]
448 pub fn from_chrono(dt: chrono::DateTime<chrono::Utc>) -> Self {
449 let utc = Time::<UTC>::from_chrono(dt);
450 Self::from_time_infallible(utc.to_scale::<TT>())
451 }
452
453 #[inline]
457 pub fn to_chrono(self) -> Option<chrono::DateTime<chrono::Utc>> {
458 self.to_time().to_scale::<UTC>().to_chrono()
459 }
460}
461
462macro_rules! coordinate_representation {
463 ($repr:ty, $quantity:ty, $from_time:expr, $to_time:expr) => {
464 impl<S: CoordinateScale> RepresentationForScale<S> for $repr {
465 #[inline]
466 fn try_from_time(
467 time: Time<S>,
468 _ctx: &TimeContext,
469 ) -> Result<$quantity, ConversionError> {
470 Ok(<Self as InfallibleRepresentationForScale<S>>::from_time(
471 time,
472 ))
473 }
474
475 #[inline]
476 fn try_into_time(
477 raw: $quantity,
478 _ctx: &TimeContext,
479 ) -> Result<Time<S>, ConversionError> {
480 Ok(<Self as InfallibleRepresentationForScale<S>>::into_time(
481 raw,
482 ))
483 }
484 }
485
486 impl<S: CoordinateScale> InfallibleRepresentationForScale<S> for $repr {
487 #[inline]
488 fn from_time(time: Time<S>) -> $quantity {
489 $from_time(time)
490 }
491
492 #[inline]
493 fn into_time(raw: $quantity) -> Time<S> {
494 $to_time(raw)
495 }
496 }
497 };
498}
499
500coordinate_representation!(
501 J2000s,
502 Second,
503 |time: Time<_>| time.raw_j2000_seconds(),
504 |raw: Second| Time::from_raw_j2000_seconds(raw).expect("finite J2000 seconds must decode")
505);
506coordinate_representation!(
507 JD,
508 Day,
509 |time: Time<_>| j2000_seconds_to_jd(time.raw_j2000_seconds()),
510 |raw: Day| Time::from_raw_j2000_seconds(jd_to_j2000_seconds(raw))
511 .expect("finite Julian date must decode")
512);
513coordinate_representation!(
514 MJD,
515 Day,
516 |time: Time<_>| j2000_seconds_to_mjd(time.raw_j2000_seconds()),
517 |raw: Day| Time::from_raw_j2000_seconds(mjd_to_j2000_seconds(raw))
518 .expect("finite Modified Julian date must decode")
519);
520
521impl RepresentationForScale<UTC> for Unix {
522 #[inline]
523 fn try_from_time(time: Time<UTC>, ctx: &TimeContext) -> Result<Second, ConversionError> {
524 time.raw_unix_seconds_with(ctx)
525 }
526
527 #[inline]
528 fn try_into_time(raw: Second, ctx: &TimeContext) -> Result<Time<UTC>, ConversionError> {
529 Time::from_raw_unix_seconds_with(raw, ctx)
530 }
531}
532
533impl RepresentationForScale<TAI> for GPS {
534 #[inline]
535 fn try_from_time(time: Time<TAI>, _ctx: &TimeContext) -> Result<Second, ConversionError> {
536 Ok(<Self as InfallibleRepresentationForScale<TAI>>::from_time(
537 time,
538 ))
539 }
540
541 #[inline]
542 fn try_into_time(raw: Second, _ctx: &TimeContext) -> Result<Time<TAI>, ConversionError> {
543 Ok(<Self as InfallibleRepresentationForScale<TAI>>::into_time(
544 raw,
545 ))
546 }
547}
548
549impl InfallibleRepresentationForScale<TAI> for GPS {
550 #[inline]
551 fn from_time(time: Time<TAI>) -> Second {
552 time.raw_gps_seconds()
553 }
554
555 #[inline]
556 fn into_time(raw: Second) -> Time<TAI> {
557 Time::from_raw_gps_seconds(raw).expect("finite GPS seconds must decode")
558 }
559}
560
561impl<S: Scale, R> core::ops::Add<Day> for EncodedTime<S, R>
568where
569 R: TimeRepresentation<Unit = qtty::unit::Day>,
570{
571 type Output = Self;
572
573 #[inline]
574 fn add(self, rhs: Day) -> Self {
575 Self::new_unchecked(Day::new(self.raw.value() + rhs.value()))
576 }
577}
578
579impl<S: Scale, R> core::ops::Sub<Day> for EncodedTime<S, R>
580where
581 R: TimeRepresentation<Unit = qtty::unit::Day>,
582{
583 type Output = Self;
584
585 #[inline]
586 fn sub(self, rhs: Day) -> Self {
587 Self::new_unchecked(Day::new(self.raw.value() - rhs.value()))
588 }
589}
590
591impl<S: Scale, R> core::ops::AddAssign<Day> for EncodedTime<S, R>
592where
593 R: TimeRepresentation<Unit = qtty::unit::Day>,
594{
595 #[inline]
596 fn add_assign(&mut self, rhs: Day) {
597 *self = *self + rhs;
598 }
599}
600
601impl<S: Scale, R> core::ops::SubAssign<Day> for EncodedTime<S, R>
602where
603 R: TimeRepresentation<Unit = qtty::unit::Day>,
604{
605 #[inline]
606 fn sub_assign(&mut self, rhs: Day) {
607 *self = *self - rhs;
608 }
609}
610
611impl<S: Scale, R> core::ops::Sub for EncodedTime<S, R>
613where
614 R: TimeRepresentation<Unit = qtty::unit::Day>,
615{
616 type Output = Day;
617
618 #[inline]
619 fn sub(self, rhs: Self) -> Day {
620 Day::new(self.raw.value() - rhs.raw.value())
621 }
622}
623
624impl<S: Scale, R> From<EncodedTime<S, R>> for Time<S>
625where
626 R: InfallibleRepresentationForScale<S>,
627{
628 #[inline]
629 fn from(value: EncodedTime<S, R>) -> Self {
630 value.to_time()
631 }
632}
633
634impl<S: Scale, R> From<Time<S>> for EncodedTime<S, R>
635where
636 R: InfallibleRepresentationForScale<S>,
637{
638 #[inline]
639 fn from(value: Time<S>) -> Self {
640 Self::from_time_infallible(value)
641 }
642}
643
644impl<S: CoordinateScale> ConversionTarget<S> for J2000s {
647 type Output = EncodedTime<S, J2000s>;
648
649 #[inline]
650 fn try_convert(src: Time<S>) -> Result<Self::Output, ConversionError> {
651 Ok(EncodedTime::from_time_infallible(src))
652 }
653}
654
655impl<S: CoordinateScale> InfallibleConversionTarget<S> for J2000s {
656 #[inline]
657 fn convert(src: Time<S>) -> Self::Output {
658 EncodedTime::from_time_infallible(src)
659 }
660}
661
662impl<S: CoordinateScale> ConversionTarget<S> for JD {
663 type Output = EncodedTime<S, JD>;
664
665 #[inline]
666 fn try_convert(src: Time<S>) -> Result<Self::Output, ConversionError> {
667 Ok(EncodedTime::from_time_infallible(src))
668 }
669}
670
671impl<S: CoordinateScale> InfallibleConversionTarget<S> for JD {
672 #[inline]
673 fn convert(src: Time<S>) -> Self::Output {
674 EncodedTime::from_time_infallible(src)
675 }
676}
677
678impl<S: CoordinateScale> ConversionTarget<S> for MJD {
679 type Output = EncodedTime<S, MJD>;
680
681 #[inline]
682 fn try_convert(src: Time<S>) -> Result<Self::Output, ConversionError> {
683 Ok(EncodedTime::from_time_infallible(src))
684 }
685}
686
687impl<S: CoordinateScale> InfallibleConversionTarget<S> for MJD {
688 #[inline]
689 fn convert(src: Time<S>) -> Self::Output {
690 EncodedTime::from_time_infallible(src)
691 }
692}
693
694impl<S> ConversionTarget<S> for Unix
695where
696 S: crate::scale::Scale + InfallibleScaleConvert<UTC>,
697{
698 type Output = EncodedTime<UTC, Unix>;
699
700 #[inline]
704 fn try_convert(src: Time<S>) -> Result<Self::Output, ConversionError> {
705 let utc = src.to_scale::<UTC>();
706 let raw = Unix::try_from_time(utc, &TimeContext::new())?;
707 Ok(EncodedTime::new_unchecked(raw))
708 }
709}
710
711impl ContextConversionTarget<UTC> for Unix {
712 type Output = EncodedTime<UTC, Unix>;
713
714 #[inline]
715 fn convert_with(src: Time<UTC>, ctx: &TimeContext) -> Result<Self::Output, ConversionError> {
716 let raw = Unix::try_from_time(src, ctx)?;
717 Ok(EncodedTime::new_unchecked(raw))
718 }
719}
720
721impl<S> ContextConversionTarget<S> for Unix
722where
723 S: crate::scale::Scale + crate::scale::conversion::ContextScaleConvert<UTC>,
724{
725 type Output = EncodedTime<UTC, Unix>;
726
727 #[inline]
728 fn convert_with(src: Time<S>, ctx: &TimeContext) -> Result<Self::Output, ConversionError> {
729 let utc = src.to_scale_with::<UTC>(ctx)?;
730 let raw = Unix::try_from_time(utc, ctx)?;
731 Ok(EncodedTime::new_unchecked(raw))
732 }
733}
734
735impl<S> ConversionTarget<S> for GPS
736where
737 S: crate::scale::Scale + InfallibleScaleConvert<TAI>,
738{
739 type Output = EncodedTime<TAI, GPS>;
740
741 #[inline]
742 fn try_convert(src: Time<S>) -> Result<Self::Output, ConversionError> {
743 Ok(Self::convert(src))
744 }
745}
746
747impl<S> InfallibleConversionTarget<S> for GPS
748where
749 S: crate::scale::Scale + InfallibleScaleConvert<TAI>,
750{
751 #[inline]
752 fn convert(src: Time<S>) -> Self::Output {
753 EncodedTime::from_time_infallible(src.to_scale::<TAI>())
754 }
755}
756
757impl<S> ContextConversionTarget<S> for GPS
758where
759 S: crate::scale::Scale + crate::scale::conversion::ContextScaleConvert<TAI>,
760{
761 type Output = EncodedTime<TAI, GPS>;
762
763 #[inline]
764 fn convert_with(src: Time<S>, ctx: &TimeContext) -> Result<Self::Output, ConversionError> {
765 let tai = src.to_scale_with::<TAI>(ctx)?;
766 Ok(EncodedTime::from_time_infallible(tai))
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773 use crate::data::active::{active_time_data, with_test_time_data};
774 use crate::scale::{TAI, TT, UT1, UTC};
775
776 #[test]
777 fn encoded_time_display_delegates_to_quantity() {
778 let jd = JulianDate::<TT>::try_new(Day::new(2_451_545.123_456_789)).unwrap();
779
780 assert_eq!(format!("{jd:.9}"), "Julian Day 2451545.123456789 d");
781 }
782
783 #[test]
784 fn encoded_time_lower_exp_delegates_to_quantity() {
785 let seconds = J2000Seconds::<TT>::try_new(Second::new(1_234.5)).unwrap();
786 let formatted = format!("{seconds:.2e}");
787
788 assert!(formatted.contains("e"));
789 assert!(formatted.ends_with(" s"));
790 }
791
792 #[test]
793 #[allow(clippy::clone_on_copy)]
794 fn encoded_time_core_helpers_and_day_arithmetic() {
795 let base = JulianDate::<TT>::try_new(Day::new(2_451_545.0)).unwrap();
796 let later = JulianDate::<TT>::try_new(Day::new(2_451_547.0)).unwrap();
797
798 assert_eq!(base.clone(), base);
799 assert!(format!("{base:?}").contains("Julian Day"));
800 assert!(format!("{base:.2E}").ends_with(" d"));
801 assert_eq!(base.raw(), Day::new(2_451_545.0));
802 assert_eq!(base.quantity(), base.raw());
803 assert_eq!(base.jd_value(), 2_451_545.0);
804 assert_eq!(base.julian_centuries(), 0.0);
805 assert_eq!(base.julian_millennias(), 0.0);
806
807 assert_eq!(base.min(later), base);
808 assert_eq!(later.min(base), base);
809 assert_eq!(base.max(later), later);
810 assert_eq!(later.max(base), later);
811 assert_eq!(base.mean(later).raw(), Day::new(2_451_546.0));
812
813 assert_eq!((base + Day::new(2.0)).raw(), later.raw());
814 assert_eq!((later - Day::new(2.0)).raw(), base.raw());
815 assert_eq!(later - base, Day::new(2.0));
816
817 let mut shifted = base;
818 shifted += Day::new(3.0);
819 shifted -= Day::new(1.0);
820 assert_eq!(shifted, later);
821
822 let mjd = ModifiedJulianDate::<TT>::new(51_544.5);
823 assert_eq!(mjd.mjd_value(), 51_544.5);
824 assert_eq!(JulianDate::<TT>::from(mjd).raw(), base.raw());
825 assert_eq!(
826 ModifiedJulianDate::<TT>::from(base).raw(),
827 Day::new(51_544.5)
828 );
829 }
830
831 #[test]
832 fn encoded_time_conversion_helpers_cover_targets() {
833 let ctx = TimeContext::new();
834 let seconds = J2000Seconds::<TT>::try_new(Second::new(86_400.0)).unwrap();
835
836 let time = seconds.try_to_time().unwrap();
837 assert_eq!(seconds.to_time_with(&ctx).unwrap(), time);
838 assert_eq!(seconds.to_time(), time);
839
840 let jd = seconds.to::<JD>();
841 let mjd = seconds.try_to::<MJD>().unwrap();
842 let ut1 = seconds.to_with::<UT1>(&ctx).unwrap();
843 assert!(ut1.raw_seconds_pair().0.is_finite());
844
845 assert_eq!(jd.raw(), Day::new(2_451_546.0));
846 assert_eq!(mjd.raw(), Day::new(51_545.5));
847
848 let time_from_encoded: Time<TT> = seconds.into();
849 let encoded_from_time: J2000Seconds<TT> = time_from_encoded.into();
850 assert_eq!(
851 encoded_from_time,
852 J2000Seconds::<TT>::try_new(Second::new(86_400.0)).unwrap()
853 );
854
855 assert!(J2000Seconds::<TT>::try_new(Second::new(f64::NAN)).is_err());
856 }
857
858 #[test]
859 fn gps_and_unix_encoded_representations_roundtrip() {
860 let ctx = TimeContext::new();
861 let utc =
862 Time::<UTC>::from_raw_unix_seconds_with(Second::new(946_728_000.0), &ctx).unwrap();
863
864 let unix = utc.to_with::<Unix>(&ctx).unwrap();
865 let utc_from_unix = unix.to_time_with(&ctx).unwrap();
866 assert!((utc_from_unix - utc).abs() < Second::new(1e-4));
867
868 let ut1_from_unix = unix.to_with::<UT1>(&ctx).unwrap();
869 assert!(ut1_from_unix.raw_seconds_pair().0.is_finite());
870
871 let tai = utc.to::<TAI>();
872 let gps = tai.try_to::<GPS>().unwrap();
873 assert_eq!(gps.try_to_time().unwrap(), tai);
874 assert_eq!(gps.to_time_with(&ctx).unwrap(), tai);
875
876 let gps_as_jd = gps.try_to::<JD>().unwrap();
877 assert!(gps_as_jd.raw().is_finite());
878 }
879
880 #[test]
881 fn tt_jd_and_mjd_chrono_helpers_roundtrip() {
882 let bundle = active_time_data().as_ref().clone();
883 with_test_time_data(bundle, || {
884 let dt = chrono::DateTime::from_timestamp(946_728_000, 250_000_000).unwrap();
885
886 let jd = JulianDate::<TT>::from_chrono(dt);
887 let jd_back = jd.to_chrono().unwrap();
888 let jd_delta_ns =
889 jd_back.timestamp_nanos_opt().unwrap() - dt.timestamp_nanos_opt().unwrap();
890 assert!(jd_delta_ns.abs() < 50_000);
891
892 let mjd = ModifiedJulianDate::<TT>::from_chrono(dt);
893 let mjd_back = mjd.to_chrono().unwrap();
894 let mjd_delta_ns =
895 mjd_back.timestamp_nanos_opt().unwrap() - dt.timestamp_nanos_opt().unwrap();
896 assert!(mjd_delta_ns.abs() < 50_000);
897
898 assert!(JulianDate::<TT>::J2000.tt_to_tdb().raw().is_finite());
899 });
900 }
901}