1use crate::context::TimeContext;
11use crate::error::ConversionError;
12use crate::scale::conversion::{ContextScaleConvert, InfallibleScaleConvert};
13use crate::scale::{Scale, TAI, TCB, TCG, TDB, TT, UT1, UTC};
14use crate::sealed::Sealed;
15use crate::time::Time;
16
17#[allow(private_bounds)]
19pub trait ConversionTarget<S: Scale>: Sealed {
20 type Output;
21
22 fn try_convert(src: Time<S>) -> Result<Self::Output, ConversionError>;
23}
24
25#[allow(private_bounds)]
27pub trait InfallibleConversionTarget<S: Scale>: ConversionTarget<S> + Sealed {
28 fn convert(src: Time<S>) -> Self::Output;
29}
30
31#[allow(private_bounds)]
33pub trait ContextConversionTarget<S: Scale>: Sealed {
34 type Output;
35
36 fn convert_with(src: Time<S>, ctx: &TimeContext) -> Result<Self::Output, ConversionError>;
37}
38
39impl<S1, S2> ConversionTarget<S1> for S2
40where
41 S1: Scale + InfallibleScaleConvert<S2>,
42 S2: Scale,
43{
44 type Output = Time<S2>;
45
46 #[inline]
47 fn try_convert(src: Time<S1>) -> Result<Self::Output, ConversionError> {
48 Ok(Self::convert(src))
49 }
50}
51
52impl<S1, S2> InfallibleConversionTarget<S1> for S2
53where
54 S1: Scale + InfallibleScaleConvert<S2>,
55 S2: Scale,
56{
57 #[inline]
58 fn convert(src: Time<S1>) -> Self::Output {
59 src.to_scale()
60 }
61}
62
63impl<S1, S2> ContextConversionTarget<S1> for S2
64where
65 S1: Scale + ContextScaleConvert<S2>,
66 S2: Scale,
67{
68 type Output = Time<S2>;
69
70 #[inline]
71 fn convert_with(src: Time<S1>, ctx: &TimeContext) -> Result<Self::Output, ConversionError> {
72 src.to_scale_with(ctx)
73 }
74}
75
76macro_rules! default_context_scale_target {
81 ($src:ty => $dst:ty) => {
82 impl ConversionTarget<$src> for $dst {
83 type Output = Time<$dst>;
84
85 #[inline]
86 fn try_convert(src: Time<$src>) -> Result<Self::Output, ConversionError> {
87 src.to_scale_with::<$dst>(&TimeContext::new())
88 }
89 }
90 };
91}
92
93default_context_scale_target!(TT => UT1);
94default_context_scale_target!(TAI => UT1);
95default_context_scale_target!(TDB => UT1);
96default_context_scale_target!(TCG => UT1);
97default_context_scale_target!(TCB => UT1);
98default_context_scale_target!(UTC => UT1);
99default_context_scale_target!(UT1 => TT);
100default_context_scale_target!(UT1 => TAI);
101default_context_scale_target!(UT1 => TDB);
102default_context_scale_target!(UT1 => TCG);
103default_context_scale_target!(UT1 => TCB);
104default_context_scale_target!(UT1 => UTC);
105
106#[cfg(test)]
107mod tests {
108 use crate::representation::{J2000s, Unix, GPS, JD, MJD};
109 use crate::scale::{TAI, TT, UT1, UTC};
110 use qtty::Second;
111
112 #[test]
113 fn scalar_targets_match_coordinate_helpers() {
114 let tt = crate::time::Time::<TT>::from_raw_j2000_seconds(Second::new(12_345.678)).unwrap();
115
116 assert_eq!(tt.to::<J2000s>().raw(), tt.raw_j2000_seconds());
117 assert_eq!(
118 tt.to::<JD>().raw(),
119 crate::encoding::j2000_seconds_to_jd(tt.raw_j2000_seconds())
120 );
121 assert_eq!(
122 tt.to::<MJD>().raw(),
123 crate::encoding::j2000_seconds_to_mjd(tt.raw_j2000_seconds())
124 );
125 }
126
127 #[test]
128 fn unix_and_gps_targets_use_expected_axes() {
129 let ctx = crate::context::TimeContext::new();
130 let utc =
131 crate::time::Time::<UTC>::from_raw_unix_seconds_with(Second::new(946_728_000.0), &ctx)
132 .unwrap();
133 let unix = utc.try_to::<Unix>().unwrap();
134 assert!((unix.raw() - utc.raw_unix_seconds_with(&ctx).unwrap()).abs() < Second::new(1e-12));
135
136 let tai = utc.to::<TAI>();
137 let gps = tai.to::<GPS>();
138 assert!((gps.raw() - tai.raw_gps_seconds()).abs() < Second::new(1e-12));
139
140 let gps_from_tt = crate::time::Time::<TT>::from_raw_j2000_seconds(Second::new(0.0))
141 .unwrap()
142 .to::<GPS>();
143 assert!(gps_from_tt.raw().is_finite());
144 }
145
146 #[test]
147 fn default_context_ut1_routes_are_reachable() {
148 let tt = crate::time::Time::<TT>::from_raw_j2000_seconds(Second::new(0.0)).unwrap();
149 let ut1 = tt.try_to::<UT1>().unwrap();
150 let tt_back = ut1.try_to::<TT>().unwrap();
151 let utc_back = ut1.try_to::<UTC>().unwrap();
152
153 assert!(tt_back.raw_j2000_seconds().is_finite());
154 assert!(utc_back.raw_j2000_seconds().is_finite());
155 }
156
157 #[test]
158 fn context_targets_support_ut1_sources() {
159 let tt = crate::time::Time::<TT>::from_raw_j2000_seconds(Second::new(0.0)).unwrap();
160 let ctx = crate::context::TimeContext::new();
161 let ut1 = tt.to_with::<UT1>(&ctx).unwrap();
162
163 let unix = ut1.to_with::<Unix>(&ctx).unwrap();
164 let gps = ut1.to_with::<GPS>(&ctx).unwrap();
165
166 assert!(unix.raw().is_finite());
167 assert!(gps.raw().is_finite());
168 }
169}