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