1use crate::earth::context::TimeContext;
11use crate::format::markers::{J2000s, Unix, GPS, JD, MJD};
12use crate::format::TimeFormat;
13use crate::foundation::error::ConversionError;
14use crate::foundation::sealed::Sealed;
15use crate::model::scale::conversion::{ContextScaleConvert, InfallibleScaleConvert};
16use crate::model::scale::{CoordinateScale, Scale, TAI, TCB, TCG, TDB, TT, UT1, UTC};
17use crate::model::time::Time;
18
19#[allow(private_bounds)]
21pub trait ConversionTarget<S: Scale, SrcF: TimeFormat = J2000s>: Sealed {
22 type Output;
23
24 fn try_convert(src: Time<S, SrcF>) -> Result<Self::Output, ConversionError>;
25}
26
27#[allow(private_bounds)]
29pub trait InfallibleConversionTarget<S: Scale, SrcF: TimeFormat = J2000s>:
30 ConversionTarget<S, SrcF> + Sealed
31{
32 fn convert(src: Time<S, SrcF>) -> Self::Output;
33}
34
35#[allow(private_bounds)]
37pub trait ContextConversionTarget<S: Scale, SrcF: TimeFormat = J2000s>: Sealed {
38 type Output;
39
40 fn convert_with(src: Time<S, SrcF>, ctx: &TimeContext)
41 -> Result<Self::Output, ConversionError>;
42}
43
44impl<S: CoordinateScale, SrcF: TimeFormat> ConversionTarget<S, SrcF> for J2000s {
45 type Output = Time<S, J2000s>;
46
47 #[inline]
48 fn try_convert(src: Time<S, SrcF>) -> Result<Self::Output, ConversionError> {
49 Ok(src.reinterpret())
50 }
51}
52
53impl<S: CoordinateScale, SrcF: TimeFormat> InfallibleConversionTarget<S, SrcF> for J2000s {
54 #[inline]
55 fn convert(src: Time<S, SrcF>) -> Self::Output {
56 src.reinterpret()
57 }
58}
59
60impl<S: CoordinateScale, SrcF: TimeFormat> ConversionTarget<S, SrcF> for JD {
61 type Output = Time<S, JD>;
62
63 #[inline]
64 fn try_convert(src: Time<S, SrcF>) -> Result<Self::Output, ConversionError> {
65 Ok(<JD as InfallibleConversionTarget<S, SrcF>>::convert(src))
66 }
67}
68
69impl<S: CoordinateScale, SrcF: TimeFormat> InfallibleConversionTarget<S, SrcF> for JD {
70 #[inline]
71 fn convert(src: Time<S, SrcF>) -> Self::Output {
72 src.reinterpret()
73 }
74}
75
76impl<S: CoordinateScale, SrcF: TimeFormat> ConversionTarget<S, SrcF> for MJD {
77 type Output = Time<S, MJD>;
78
79 #[inline]
80 fn try_convert(src: Time<S, SrcF>) -> Result<Self::Output, ConversionError> {
81 Ok(<MJD as InfallibleConversionTarget<S, SrcF>>::convert(src))
82 }
83}
84
85impl<S: CoordinateScale, SrcF: TimeFormat> InfallibleConversionTarget<S, SrcF> for MJD {
86 #[inline]
87 fn convert(src: Time<S, SrcF>) -> Self::Output {
88 src.reinterpret()
89 }
90}
91
92impl<S1: Scale + InfallibleScaleConvert<S2>, S2: Scale, SrcF: TimeFormat> ConversionTarget<S1, SrcF>
93 for S2
94{
95 type Output = Time<S2, SrcF>;
96
97 #[inline]
98 fn try_convert(src: Time<S1, SrcF>) -> Result<Self::Output, ConversionError> {
99 Ok(<S2 as InfallibleConversionTarget<S1, SrcF>>::convert(src))
100 }
101}
102
103impl<S1: Scale + InfallibleScaleConvert<S2>, S2: Scale, SrcF: TimeFormat>
104 InfallibleConversionTarget<S1, SrcF> for S2
105{
106 #[inline]
107 fn convert(src: Time<S1, SrcF>) -> Self::Output {
108 src.to_scale()
109 }
110}
111
112impl<S1: Scale + ContextScaleConvert<S2>, S2: Scale, SrcF: TimeFormat>
113 ContextConversionTarget<S1, SrcF> for S2
114{
115 type Output = Time<S2, SrcF>;
116
117 #[inline]
118 fn convert_with(
119 src: Time<S1, SrcF>,
120 ctx: &TimeContext,
121 ) -> Result<Self::Output, ConversionError> {
122 src.to_scale_with(ctx)
123 }
124}
125
126macro_rules! default_context_scale_target {
129 ($src:ty => $dst:ty) => {
130 impl<SrcF: TimeFormat> ConversionTarget<$src, SrcF> for $dst {
131 type Output = Time<$dst, SrcF>;
132
133 #[inline]
134 fn try_convert(src: Time<$src, SrcF>) -> Result<Self::Output, ConversionError> {
135 src.to_scale_with::<$dst>(&TimeContext::new())
136 }
137 }
138 };
139}
140
141default_context_scale_target!(TT => UT1);
142default_context_scale_target!(TAI => UT1);
143default_context_scale_target!(TDB => UT1);
144default_context_scale_target!(TCG => UT1);
145default_context_scale_target!(TCB => UT1);
146default_context_scale_target!(UTC => UT1);
147default_context_scale_target!(UT1 => TT);
148default_context_scale_target!(UT1 => TAI);
149default_context_scale_target!(UT1 => TDB);
150default_context_scale_target!(UT1 => TCG);
151default_context_scale_target!(UT1 => TCB);
152default_context_scale_target!(UT1 => UTC);
153
154impl<S: Scale + InfallibleScaleConvert<UTC>, SrcF: TimeFormat> ConversionTarget<S, SrcF> for Unix {
155 type Output = Time<UTC, Unix>;
156
157 #[inline]
158 fn try_convert(src: Time<S, SrcF>) -> Result<Self::Output, ConversionError> {
159 let ctx = TimeContext::new();
160 let utc = src.to_scale::<UTC>();
161 utc.to_j2000s().raw_unix_seconds_with(&ctx)?;
162 Ok(utc.reinterpret())
163 }
164}
165
166impl<S: Scale + ContextScaleConvert<UTC>, SrcF: TimeFormat> ContextConversionTarget<S, SrcF>
167 for Unix
168{
169 type Output = Time<UTC, Unix>;
170
171 #[inline]
172 fn convert_with(
173 src: Time<S, SrcF>,
174 ctx: &TimeContext,
175 ) -> Result<Self::Output, ConversionError> {
176 let utc = src.to_scale_with::<UTC>(ctx)?;
177 utc.to_j2000s().raw_unix_seconds_with(ctx)?;
178 Ok(utc.reinterpret())
179 }
180}
181
182impl<S: Scale + InfallibleScaleConvert<TAI>, SrcF: TimeFormat> ConversionTarget<S, SrcF> for GPS {
183 type Output = Time<TAI, GPS>;
184
185 #[inline]
186 fn try_convert(src: Time<S, SrcF>) -> Result<Self::Output, ConversionError> {
187 Ok(<GPS as InfallibleConversionTarget<S, SrcF>>::convert(src))
188 }
189}
190
191impl<S: Scale + InfallibleScaleConvert<TAI>, SrcF: TimeFormat> InfallibleConversionTarget<S, SrcF>
192 for GPS
193{
194 #[inline]
195 fn convert(src: Time<S, SrcF>) -> Self::Output {
196 src.to_scale::<TAI>().reinterpret()
197 }
198}
199
200impl<S: Scale + ContextScaleConvert<TAI>, SrcF: TimeFormat> ContextConversionTarget<S, SrcF>
201 for GPS
202{
203 type Output = Time<TAI, GPS>;
204
205 #[inline]
206 fn convert_with(
207 src: Time<S, SrcF>,
208 ctx: &TimeContext,
209 ) -> Result<Self::Output, ConversionError> {
210 Ok(src.to_scale_with::<TAI>(ctx)?.reinterpret())
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use crate::format::{J2000s, Unix, GPS, JD, MJD};
217 use crate::model::scale::{TAI, TT, UT1, UTC};
218 use qtty::Second;
219
220 #[test]
221 fn scalar_targets_match_coordinate_helpers() {
222 let tt = crate::model::time::Time::<TT>::from_raw_j2000_seconds(Second::new(12_345.678))
223 .unwrap();
224
225 assert_eq!(tt.to::<J2000s>().raw(), tt.raw_j2000_seconds());
226 assert_eq!(
227 tt.to::<JD>().raw(),
228 crate::encoding::j2000_seconds_to_day::<JD>(tt.raw_j2000_seconds())
229 );
230 assert_eq!(
231 tt.to::<MJD>().raw(),
232 crate::encoding::j2000_seconds_to_day::<MJD>(tt.raw_j2000_seconds())
233 );
234 }
235
236 #[test]
237 fn unix_and_gps_targets_use_expected_axes() {
238 let ctx = crate::earth::context::TimeContext::new();
239 let utc = crate::model::time::Time::<UTC>::from_raw_unix_seconds_with(
240 Second::new(946_728_000.0),
241 &ctx,
242 )
243 .unwrap();
244 let unix = utc.try_to::<Unix>().unwrap();
245 let unix_sec = unix.try_raw_with(&ctx).unwrap();
246 assert!(
247 (unix_sec - utc.to_j2000s().raw_unix_seconds_with(&ctx).unwrap()).abs()
248 < Second::new(1e-12)
249 );
250
251 let tai = utc.to::<TAI>();
252 let gps = tai.to::<GPS>();
253 assert!((gps.raw() - tai.to_j2000s().raw_gps_seconds()).abs() < Second::new(1e-12));
254
255 let gps_from_tt = crate::model::time::Time::<TT>::from_raw_j2000_seconds(Second::new(0.0))
256 .unwrap()
257 .to::<GPS>();
258 assert!(gps_from_tt.raw().is_finite());
259 }
260
261 #[test]
262 fn default_context_ut1_routes_are_reachable() {
263 let tt = crate::model::time::Time::<TT>::from_raw_j2000_seconds(Second::new(0.0)).unwrap();
264 let ut1 = tt.try_to::<UT1>().unwrap();
265 let tt_back = ut1.try_to::<TT>().unwrap();
266 let utc_back = ut1.try_to::<UTC>().unwrap();
267
268 assert!(tt_back.raw_j2000_seconds().is_finite());
269 assert!(utc_back.raw_j2000_seconds().is_finite());
270 }
271
272 #[test]
273 fn context_targets_support_ut1_sources() {
274 let tt = crate::model::time::Time::<TT>::from_raw_j2000_seconds(Second::new(0.0)).unwrap();
275 let ctx = crate::earth::context::TimeContext::new();
276 let ut1 = tt.to_with::<UT1>(&ctx).unwrap();
277
278 let unix_sec = ut1
279 .to_with::<Unix>(&ctx)
280 .unwrap()
281 .try_raw_with(&ctx)
282 .unwrap();
283 let gps = ut1.to_with::<GPS>(&ctx).unwrap();
284
285 assert!(unix_sec.is_finite());
286 assert!(gps.raw().is_finite());
287 }
288}