Skip to main content

lox_time/offsets/
impls.rs

1// SPDX-FileCopyrightText: 2025 Helge Eichhorn <git@helgeeichhorn.de>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5use core::convert::Infallible;
6
7use lox_core::time::deltas::TimeDelta;
8
9use crate::{
10    offsets::{Offset, OffsetProvider, TryOffset},
11    time_scales::{Gps, Tai, Tcb, Tcg, Tdb, TimeScale, Tt, Ut1},
12};
13
14// No-ops
15
16macro_rules! impl_noop {
17    ($($scale:ident),*) => {
18        $(
19            impl<T> TryOffset<$scale, $scale> for T
20            where
21                T: OffsetProvider,
22            {
23                type Error = Infallible;
24
25                fn try_offset(
26                    &self,
27                    _origin: $scale,
28                    _target: $scale,
29                    _delta: TimeDelta
30                ) -> Result<TimeDelta, Self::Error> {
31                    Ok(TimeDelta::default())
32                }
33            }
34        )*
35    };
36}
37
38impl_noop!(Gps, Tai, Tcb, Tcg, Tdb, Tt, Ut1);
39
40// TAI <-> TT
41
42impl<T> TryOffset<Tai, Tt> for T
43where
44    T: OffsetProvider,
45{
46    type Error = Infallible;
47
48    fn try_offset(
49        &self,
50        _origin: Tai,
51        _target: Tt,
52        _delta: TimeDelta,
53    ) -> Result<TimeDelta, Self::Error> {
54        Ok(self.tai_to_tt())
55    }
56}
57
58impl<T> TryOffset<Tt, Tai> for T
59where
60    T: OffsetProvider,
61{
62    type Error = Infallible;
63
64    fn try_offset(
65        &self,
66        _origin: Tt,
67        _target: Tai,
68        _delta: TimeDelta,
69    ) -> Result<TimeDelta, Self::Error> {
70        Ok(self.tt_to_tai())
71    }
72}
73
74// TAI <-> GPS
75
76impl<T> TryOffset<Tai, Gps> for T
77where
78    T: OffsetProvider,
79{
80    type Error = Infallible;
81
82    fn try_offset(
83        &self,
84        _origin: Tai,
85        _target: Gps,
86        _delta: TimeDelta,
87    ) -> Result<TimeDelta, Self::Error> {
88        Ok(self.tai_to_gps())
89    }
90}
91
92impl<T> TryOffset<Gps, Tai> for T
93where
94    T: OffsetProvider,
95{
96    type Error = Infallible;
97
98    fn try_offset(
99        &self,
100        _origin: Gps,
101        _target: Tai,
102        _delta: TimeDelta,
103    ) -> Result<TimeDelta, Self::Error> {
104        Ok(self.gps_to_tai())
105    }
106}
107
108// TT <-> TCG
109
110impl<T> TryOffset<Tt, Tcg> for T
111where
112    T: OffsetProvider,
113{
114    type Error = Infallible;
115
116    fn try_offset(
117        &self,
118        _origin: Tt,
119        _target: Tcg,
120        delta: TimeDelta,
121    ) -> Result<TimeDelta, Self::Error> {
122        Ok(self.tt_to_tcg(delta))
123    }
124}
125
126impl<T> TryOffset<Tcg, Tt> for T
127where
128    T: OffsetProvider,
129{
130    type Error = Infallible;
131
132    fn try_offset(
133        &self,
134        _origin: Tcg,
135        _target: Tt,
136        delta: TimeDelta,
137    ) -> Result<TimeDelta, Self::Error> {
138        Ok(self.tcg_to_tt(delta))
139    }
140}
141
142// TDB <-> TCB
143
144impl<T> TryOffset<Tdb, Tcb> for T
145where
146    T: OffsetProvider,
147{
148    type Error = Infallible;
149
150    fn try_offset(
151        &self,
152        _origin: Tdb,
153        _target: Tcb,
154        delta: TimeDelta,
155    ) -> Result<TimeDelta, Self::Error> {
156        Ok(self.tdb_to_tcb(delta))
157    }
158}
159
160impl<T> TryOffset<Tcb, Tdb> for T
161where
162    T: OffsetProvider,
163{
164    type Error = Infallible;
165
166    fn try_offset(
167        &self,
168        _origin: Tcb,
169        _target: Tdb,
170        delta: TimeDelta,
171    ) -> Result<TimeDelta, Self::Error> {
172        Ok(self.tcb_to_tdb(delta))
173    }
174}
175
176// TT <-> TDB
177
178impl<T> TryOffset<Tt, Tdb> for T
179where
180    T: OffsetProvider,
181{
182    type Error = Infallible;
183
184    fn try_offset(
185        &self,
186        _origin: Tt,
187        _target: Tdb,
188        delta: TimeDelta,
189    ) -> Result<TimeDelta, Self::Error> {
190        Ok(self.tt_to_tdb(delta))
191    }
192}
193
194impl<T> TryOffset<Tdb, Tt> for T
195where
196    T: OffsetProvider,
197{
198    type Error = Infallible;
199
200    fn try_offset(
201        &self,
202        _origin: Tdb,
203        _target: Tt,
204        delta: TimeDelta,
205    ) -> Result<TimeDelta, Self::Error> {
206        Ok(self.tdb_to_tt(delta))
207    }
208}
209
210// TAI <-> UT1
211
212impl<T> TryOffset<Tai, Ut1> for T
213where
214    T: OffsetProvider,
215{
216    type Error = <Self as OffsetProvider>::Error;
217
218    fn try_offset(
219        &self,
220        _origin: Tai,
221        _target: Ut1,
222        delta: TimeDelta,
223    ) -> Result<TimeDelta, Self::Error> {
224        self.tai_to_ut1(delta)
225    }
226}
227
228impl<T> TryOffset<Ut1, Tai> for T
229where
230    T: OffsetProvider,
231{
232    type Error = <Self as OffsetProvider>::Error;
233
234    fn try_offset(
235        &self,
236        _origin: Ut1,
237        _target: Tai,
238        delta: TimeDelta,
239    ) -> Result<TimeDelta, Self::Error> {
240        self.ut1_to_tai(delta)
241    }
242}
243
244// Two-step
245
246macro_rules! impl_two_step {
247    ($(($origin:ident, $via:ident, $target:ident)),*) => {
248        $(
249            impl<T> TryOffset<$origin, $target> for T
250            where
251                T: OffsetProvider,
252            {
253                type Error = Infallible;
254
255                fn try_offset(
256                    &self,
257                    origin: $origin,
258                    target: $target,
259                    delta: TimeDelta,
260                ) -> Result<TimeDelta, Self::Error> {
261                    Ok(super::two_step_offset(self, origin, $via, target, delta))
262                }
263            }
264
265            impl<T> TryOffset<$target, $origin> for T
266            where
267                T: OffsetProvider,
268            {
269                type Error = Infallible;
270
271                fn try_offset(
272                    &self,
273                    origin: $target,
274                    target: $origin,
275                    delta: TimeDelta,
276                ) -> Result<TimeDelta, Self::Error> {
277                    Ok(super::two_step_offset(self, origin, $via, target, delta))
278                }
279            }
280        )*
281    }
282}
283
284impl_two_step!(
285    (Tai, Tt, Tdb),
286    (Tdb, Tt, Tcg),
287    (Tai, Tt, Tcg),
288    (Tai, Tdb, Tcb),
289    (Tt, Tdb, Tcb),
290    (Tcb, Tdb, Tcg),
291    (Gps, Tai, Tt),
292    (Gps, Tai, Tcg),
293    (Gps, Tai, Tdb),
294    (Gps, Tai, Tcb)
295);
296
297macro_rules! impl_two_step_ut1 {
298    ($($scale:ident),*) => {
299        $(
300            impl<T> TryOffset<$scale, Ut1> for T
301            where
302                T: OffsetProvider,
303            {
304                type Error = <Self as OffsetProvider>::Error;
305
306                fn try_offset(
307                    &self,
308                    _origin: $scale,
309                    _target: Ut1,
310                    delta: TimeDelta,
311                ) -> Result<TimeDelta, Self::Error> {
312                    let mut offset = self.offset($scale, Tai, delta);
313                    offset += self.try_offset(Tai, Ut1, delta + offset)?;
314                    Ok(offset)
315                }
316            }
317
318            impl<T> TryOffset<Ut1, $scale> for T
319            where
320                T: OffsetProvider,
321            {
322                type Error = <Self as OffsetProvider>::Error;
323
324                fn try_offset(
325                    &self,
326                    _origin: Ut1,
327                    _target: $scale,
328                    delta: TimeDelta,
329                ) -> Result<TimeDelta, Self::Error> {
330                    let mut offset = self.try_offset(Ut1, Tai, delta)?;
331                    offset += self.offset(Tai, $scale, delta + offset);
332                    Ok(offset)
333                }
334            }
335        )*
336    };
337}
338
339impl_two_step_ut1!(Gps, Tcb, Tcg, Tdb, Tt);
340
341// Dynamic
342
343impl<T> TryOffset<TimeScale, TimeScale> for T
344where
345    T: OffsetProvider,
346{
347    type Error = <Self as OffsetProvider>::Error;
348
349    fn try_offset(
350        &self,
351        origin: TimeScale,
352        target: TimeScale,
353        delta: TimeDelta,
354    ) -> Result<TimeDelta, Self::Error> {
355        if origin == target {
356            return Ok(TimeDelta::default());
357        }
358        match (origin, target) {
359            (TimeScale::Gps, TimeScale::Tai) => Ok(self.offset(Gps, Tai, delta)),
360            (TimeScale::Gps, TimeScale::Tcb) => Ok(self.offset(Gps, Tcb, delta)),
361            (TimeScale::Gps, TimeScale::Tcg) => Ok(self.offset(Gps, Tcg, delta)),
362            (TimeScale::Gps, TimeScale::Tdb) => Ok(self.offset(Gps, Tdb, delta)),
363            (TimeScale::Gps, TimeScale::Tt) => Ok(self.offset(Gps, Tt, delta)),
364            (TimeScale::Gps, TimeScale::Ut1) => self.try_offset(Gps, Ut1, delta),
365            (TimeScale::Tai, TimeScale::Gps) => Ok(self.offset(Tai, Gps, delta)),
366            (TimeScale::Tai, TimeScale::Tcb) => Ok(self.offset(Tai, Tcb, delta)),
367            (TimeScale::Tai, TimeScale::Tcg) => Ok(self.offset(Tai, Tcg, delta)),
368            (TimeScale::Tai, TimeScale::Tdb) => Ok(self.offset(Tai, Tdb, delta)),
369            (TimeScale::Tai, TimeScale::Tt) => Ok(self.offset(Tai, Tt, delta)),
370            (TimeScale::Tai, TimeScale::Ut1) => self.try_offset(Tai, Ut1, delta),
371            (TimeScale::Tcb, TimeScale::Gps) => Ok(self.offset(Tcb, Gps, delta)),
372            (TimeScale::Tcb, TimeScale::Tai) => Ok(self.offset(Tcb, Tai, delta)),
373            (TimeScale::Tcb, TimeScale::Tcg) => Ok(self.offset(Tcb, Tcg, delta)),
374            (TimeScale::Tcb, TimeScale::Tdb) => Ok(self.offset(Tcb, Tdb, delta)),
375            (TimeScale::Tcb, TimeScale::Tt) => Ok(self.offset(Tcb, Tt, delta)),
376            (TimeScale::Tcb, TimeScale::Ut1) => self.try_offset(Tcb, Ut1, delta),
377            (TimeScale::Tcg, TimeScale::Gps) => Ok(self.offset(Tcg, Gps, delta)),
378            (TimeScale::Tcg, TimeScale::Tai) => Ok(self.offset(Tcg, Tai, delta)),
379            (TimeScale::Tcg, TimeScale::Tcb) => Ok(self.offset(Tcg, Tcb, delta)),
380            (TimeScale::Tcg, TimeScale::Tdb) => Ok(self.offset(Tcg, Tdb, delta)),
381            (TimeScale::Tcg, TimeScale::Tt) => Ok(self.offset(Tcg, Tt, delta)),
382            (TimeScale::Tcg, TimeScale::Ut1) => self.try_offset(Tcg, Ut1, delta),
383            (TimeScale::Tdb, TimeScale::Gps) => Ok(self.offset(Tdb, Gps, delta)),
384            (TimeScale::Tdb, TimeScale::Tai) => Ok(self.offset(Tdb, Tai, delta)),
385            (TimeScale::Tdb, TimeScale::Tcb) => Ok(self.offset(Tdb, Tcb, delta)),
386            (TimeScale::Tdb, TimeScale::Tcg) => Ok(self.offset(Tdb, Tcg, delta)),
387            (TimeScale::Tdb, TimeScale::Tt) => Ok(self.offset(Tdb, Tt, delta)),
388            (TimeScale::Tdb, TimeScale::Ut1) => self.try_offset(Tdb, Ut1, delta),
389            (TimeScale::Tt, TimeScale::Gps) => Ok(self.offset(Tt, Gps, delta)),
390            (TimeScale::Tt, TimeScale::Tai) => Ok(self.offset(Tt, Tai, delta)),
391            (TimeScale::Tt, TimeScale::Tcb) => Ok(self.offset(Tt, Tcb, delta)),
392            (TimeScale::Tt, TimeScale::Tcg) => Ok(self.offset(Tt, Tcg, delta)),
393            (TimeScale::Tt, TimeScale::Tdb) => Ok(self.offset(Tt, Tdb, delta)),
394            (TimeScale::Tt, TimeScale::Ut1) => self.try_offset(Tt, Ut1, delta),
395            (TimeScale::Ut1, TimeScale::Gps) => self.try_offset(Ut1, Gps, delta),
396            (TimeScale::Ut1, TimeScale::Tai) => self.try_offset(Ut1, Tai, delta),
397            (TimeScale::Ut1, TimeScale::Tcb) => self.try_offset(Ut1, Tcb, delta),
398            (TimeScale::Ut1, TimeScale::Tcg) => self.try_offset(Ut1, Tcg, delta),
399            (TimeScale::Ut1, TimeScale::Tdb) => self.try_offset(Ut1, Tdb, delta),
400            (TimeScale::Ut1, TimeScale::Tt) => self.try_offset(Ut1, Tt, delta),
401            (TimeScale::Gps, TimeScale::Gps)
402            | (TimeScale::Tai, TimeScale::Tai)
403            | (TimeScale::Tcb, TimeScale::Tcb)
404            | (TimeScale::Tcg, TimeScale::Tcg)
405            | (TimeScale::Tdb, TimeScale::Tdb)
406            | (TimeScale::Tt, TimeScale::Tt)
407            | (TimeScale::Ut1, TimeScale::Ut1) => Ok(TimeDelta::default()),
408        }
409    }
410}
411
412macro_rules! impl_dynamic {
413    ($($scale:ident),*) => {
414        $(
415            impl<T> TryOffset<$scale, TimeScale> for T
416            where
417                T: OffsetProvider,
418            {
419                type Error = <Self as OffsetProvider>::Error;
420
421                fn try_offset(
422                    &self,
423                    origin: $scale,
424                    target: TimeScale,
425                    delta: TimeDelta,
426                ) -> Result<TimeDelta, Self::Error> {
427                    let origin: TimeScale = origin.into();
428                    self.try_offset(origin, target, delta)
429                }
430            }
431
432            impl<T> TryOffset<TimeScale, $scale> for T
433            where
434                T: OffsetProvider,
435            {
436                type Error = <Self as OffsetProvider>::Error;
437
438                fn try_offset(
439                    &self,
440                    origin: TimeScale,
441                    target: $scale,
442                    delta: TimeDelta,
443                ) -> Result<TimeDelta, Self::Error> {
444                    let target: TimeScale = target.into();
445                    self.try_offset(origin, target, delta)
446                }
447            }
448        )*
449    };
450}
451
452impl_dynamic!(Gps, Tai, Tcb, Tcg, Tdb, Tt, Ut1);
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457    use crate::offsets::DefaultOffsetProvider;
458
459    #[test]
460    fn tai_to_gps_is_negative_19_seconds() {
461        let p = DefaultOffsetProvider;
462        let delta = p.offset(Tai, Gps, TimeDelta::default());
463        assert_eq!(delta, TimeDelta::builder().seconds(-19).build());
464    }
465
466    #[test]
467    fn gps_to_tai_is_positive_19_seconds() {
468        let p = DefaultOffsetProvider;
469        let delta = p.offset(Gps, Tai, TimeDelta::default());
470        assert_eq!(delta, TimeDelta::builder().seconds(19).build());
471    }
472
473    #[test]
474    fn tai_gps_round_trip_is_zero() {
475        let p = DefaultOffsetProvider;
476        let to_gps = p.offset(Tai, Gps, TimeDelta::default());
477        let back_to_tai = p.offset(Gps, Tai, to_gps);
478        assert_eq!(to_gps + back_to_tai, TimeDelta::default());
479    }
480}