gnss_qc_traits/processing/time/
database.rs

1use crate::{
2    merge::{Error as MergeError, Merge},
3    processing::TimeCorrection,
4};
5
6use hifitime::{Epoch, TimeScale, Unit};
7
8#[cfg(doc)]
9use super::Timeshift;
10
11/// [TimeCorrectionsDB] is a [TimeCorrection]s database used by [TimeScale]
12/// monitoring applications and applications that need exact [TimeScale] states at all times.
13/// Our [Timeshift] trait uses it in the precise conversion method.
14/// [TimeCorrectionsDB] has no means to "check" the internal content with respect
15/// to your application, other than the possible verification of the corrections validity (in time).
16/// You are responsible of the database content with respect to the current time and keeping
17/// it up to date. To avoid memory growth in long term applications, we propose:
18/// - [TimeCorrectionsDB::outdate_past] to declare past [TimePolynomial]s as outdated
19/// - and [TimeCorrectionsDB::outdate_weekly] to discard [TimePolynomial]s published before that week
20#[derive(Default, Clone)]
21pub struct TimeCorrectionsDB {
22    /// When strict validity is asserted, we will respect
23    /// the corrections validity strictly. Otherwise, the last
24    /// correction available may be used (propagated) in the future.
25    strict_validity: bool,
26
27    /// [TimeCorrection] database
28    corrections: Vec<TimeCorrection>,
29}
30
31impl TimeCorrectionsDB {
32    /// The database will respect the corrections validity period strictly,
33    /// and will not propose corrections past the last available in time.
34    pub fn strict_validity(&self) -> Self {
35        let mut s = self.clone();
36        s.strict_validity = true;
37        s
38    }
39
40    /// Add a new [TimeCorrection] to the database.
41    /// This does not discard possible [TimeCorrection]s that may apply
42    /// to these timescales.
43    pub fn add(&mut self, correction: TimeCorrection) {
44        self.corrections.push(correction);
45    }
46
47    /// Discard corrections past this [Epoch].
48    /// Corrections must still exist or be provided quickly, for the database
49    /// to remain valid.
50    pub fn outdate_past(&mut self, instant: Epoch) {
51        self.corrections.retain(|poly| poly.ref_epoch > instant);
52    }
53
54    /// Discard corrections published the week before this [Epoch].
55    /// Corrections must still exist or be provided quickly, for the database
56    /// to remain valid.
57    pub fn outdate_weekly(&mut self, instant: Epoch) {
58        let limit = instant - 7.0 * Unit::Week;
59        self.corrections.retain(|poly| poly.ref_epoch > limit);
60    }
61
62    /// [Epoch] interpolation & correction attempt, into desired [TimeScale].
63    pub fn precise_epoch_correction(&self, t: Epoch, target: TimeScale) -> Option<Epoch> {
64        if t.time_scale == target {
65            // nothing to be done!
66            return Some(t);
67        }
68
69        if let Some(poly) = self
70            .corrections
71            .iter()
72            .filter_map(|poly| {
73                if poly.lhs_timescale == t.time_scale && poly.rhs_timescale == target {
74                    Some(poly)
75                } else {
76                    None
77                }
78            })
79            .min_by_key(|poly| (t - poly.ref_epoch).abs())
80        {
81            let mut applies = poly.applies(t);
82            if !self.strict_validity {
83                applies |= true;
84            }
85
86            if applies {
87                Some(
88                    t.precise_timescale_conversion(true, poly.ref_epoch, poly.polynomial, target)
89                        .unwrap(),
90                )
91            } else {
92                None
93            }
94        } else if let Some(poly) = self
95            .corrections
96            .iter()
97            .filter_map(|poly| {
98                if poly.lhs_timescale == target && poly.rhs_timescale == t.time_scale {
99                    Some(poly)
100                } else {
101                    None
102                }
103            })
104            .min_by_key(|poly| (t - poly.ref_epoch).abs())
105        {
106            let mut applies = poly.applies(t);
107            if !self.strict_validity {
108                applies |= true;
109            }
110
111            if applies {
112                Some(
113                    t.precise_timescale_conversion(false, poly.ref_epoch, poly.polynomial, target)
114                        .unwrap(),
115                )
116            } else {
117                None
118            }
119        } else {
120            // mixed combinations not supported yet
121            None
122        }
123    }
124
125    // else if let Some(poly) = self
126    //     .corrections
127    //     .iter()
128    //     .filter(|poly| {
129    //         if poly.lhs_timescale == t.time_scale {
130    //             Some(poly)
131    //         } else {
132    //             None
133    //         }
134    //     })
135    //     .min_by_key(|poly| {
136    //         let transposed = t.to_time_scale(poly.lhs_timescale);
137    //         transposed - poly.ref_epoch
138    //     })
139    // {
140    //     // got a forward (1) proposal
141    //     if let Some(poly) = self
142    //         .corrections
143    //         .iter()
144    //         .filter(|poly| {
145    //             if poly.rhs_timescale == target {
146    //                 Some(poly)
147    //             } else {
148    //                 None
149    //             }
150    //         })
151    //         .min_by_key(|poly| {
152    //             let transposed = t.to_time_scale(poly.lhs_timescale);
153    //             transposed - poly.ref_epoch
154    //         })
155    //     {
156    //         // got a forward (2) proposal
157    //     } else {
158    //         // got a backward (2) proposal
159    //         None
160    //     }
161    // } else {
162    //     None
163    // }
164    //     Some(
165    //         t.precise_timescale_conversion(true, poly.ref_epoch, poly.polynomial, target)
166    //             .unwrap(),
167    //     )
168
169    //     for lhs_poly in self.corrections.iter() {
170    //         for rhs_poly in self.corrections.iter() {
171    //             if lhs_poly.lhs_timescale == t.time_scale && rhs_poly.rhs_timescale == target {
172    //                 // indirect forward transforms
173
174    //                 // |BDT-GST|=a0_bdt & |GST-GPST|=a1 dt_gst
175    //                 // GST=BDT-a0_bdt
176    //                 // BDT-a0 dt_bdt - GPST = a1 dt_gpst
177    //                 // BDT-GPST (foward indirect) = a1 dt_gpst + a0 dt_bdt
178
179    //                 let dt_lhs_s = (t.to_time_scale(lhs_poly.lhs_timescale)
180    //                     - lhs_poly.ref_epoch)
181    //                     .to_seconds();
182
183    //                 let dt_rhs_s = (t.to_time_scale(rhs_poly.lhs_timescale)
184    //                     - rhs_poly.ref_epoch)
185    //                     .to_seconds();
186
187    //                 let mut correction = lhs_poly.polynomial.constant.to_seconds()
188    //                     + lhs_poly.polynomial.rate.to_seconds() * dt_lhs_s
189    //                     + lhs_poly.polynomial.accel.to_seconds() * dt_lhs_s.powi(2);
190
191    //                 // println!("correction = {}", correction);
192
193    //                 correction += rhs_poly.polynomial.constant.to_seconds()
194    //                     + rhs_poly.polynomial.rate.to_seconds() * dt_rhs_s
195    //                     + rhs_poly.polynomial.accel.to_seconds() * dt_rhs_s.powi(2);
196
197    //                 // println!("total correction = {}", correction);
198
199    //                 return Some(t.to_time_scale(target) - Duration::from_seconds(correction));
200    //             } else if lhs_poly.rhs_timescale == t.time_scale
201    //                 && rhs_poly.rhs_timescale == target
202    //             {
203    //                 // indirect backward + forward transforms
204    //             } else if lhs_poly.lhs_timescale == t.time_scale
205    //                 && rhs_poly.lhs_timescale == target
206    //             {
207    //                 // indirect forward + backward transforms
208    //             } else if lhs_poly.rhs_timescale == t.time_scale
209    //                 && rhs_poly.lhs_timescale == target
210    //             {
211    //                 // indirect backward transforms
212
213    //                 // |BDT-GST|=a0_bdt & |GST-GPST|=a1 dt_gst
214    //                 // BDT  = a0_bdt + GST
215    //                 // GPST = GST -a1 dt_gpst
216    //                 // GPST-BDT (backward indirect) = -a1 -a0
217
218    //                 let dt_lhs_s = (t.to_time_scale(lhs_poly.lhs_timescale)
219    //                     - lhs_poly.ref_epoch)
220    //                     .to_seconds();
221
222    //                 let dt_rhs_s = (t.to_time_scale(rhs_poly.lhs_timescale)
223    //                     - rhs_poly.ref_epoch)
224    //                     .to_seconds();
225
226    //                 let correction_a = lhs_poly.polynomial.constant.to_seconds()
227    //                     + lhs_poly.polynomial.rate.to_seconds() * dt_lhs_s
228    //                     + lhs_poly.polynomial.accel.to_seconds() * dt_lhs_s.powi(2);
229
230    //                 // println!("correction = {}", correction_a);
231
232    //                 let correction_b = rhs_poly.polynomial.constant.to_seconds()
233    //                     + rhs_poly.polynomial.rate.to_seconds() * dt_rhs_s
234    //                     + rhs_poly.polynomial.accel.to_seconds() * dt_rhs_s.powi(2);
235
236    //                 // println!("correction = {}", correction_b);
237
238    //                 return Some(
239    //                     t.to_time_scale(target)
240    //                         + Duration::from_seconds(correction_a)
241    //                         + Duration::from_seconds(correction_b),
242    //                 );
243    //             }
244    //         }
245    //     }
246
247    //     None
248}
249
250impl Merge for TimeCorrectionsDB {
251    fn merge(&self, rhs: &Self) -> Result<Self, MergeError>
252    where
253        Self: Sized,
254    {
255        let mut s = self.clone();
256        s.merge_mut(rhs)?;
257
258        Ok(s)
259    }
260
261    fn merge_mut(&mut self, rhs: &Self) -> Result<(), MergeError> {
262        // latch new corrections
263        for polynomial in rhs.corrections.iter() {
264            self.corrections.push(*polynomial);
265        }
266        Ok(())
267    }
268}
269
270#[cfg(test)]
271mod test {
272    use crate::{TimeCorrection, TimeCorrectionsDB};
273    use hifitime::{Duration, Epoch, Polynomial, TimeScale};
274    use std::str::FromStr;
275
276    #[test]
277    fn time_corrections_db_without_strict_validity() {
278        let t_ref_gpst = Epoch::from_str("2020-01-01T00:00:00 GPST").unwrap();
279
280        let (a0, _, _) = (1.0E-9, 0.0, 0.0);
281
282        let polynomial = Polynomial {
283            constant: Duration::from_seconds(a0),
284            rate: Duration::ZERO,
285            accel: Duration::ZERO,
286        };
287
288        let mut database = TimeCorrectionsDB::default();
289
290        database.add(TimeCorrection {
291            lhs_timescale: TimeScale::GST,
292            rhs_timescale: TimeScale::GPST,
293            ref_epoch: t_ref_gpst,
294            polynomial,
295            validity_period: Duration::from_hours(1.0),
296        });
297
298        // Random date in GST
299        let t_gst = Epoch::from_str("2020-01-01T00:00:00 GST").unwrap();
300
301        let t_gst_gpst = database
302            .precise_epoch_correction(t_gst, TimeScale::GPST)
303            .unwrap();
304
305        assert_eq!(t_gst_gpst.time_scale, TimeScale::GPST);
306
307        // Random date in GPST
308        let t_gpst = Epoch::from_str("2020-01-01T00:00:10 GPST").unwrap();
309
310        let t_gpst_gst = database
311            .precise_epoch_correction(t_gpst, TimeScale::GST)
312            .unwrap();
313
314        assert_eq!(t_gpst_gst.time_scale, TimeScale::GST);
315
316        // Random date in UTC
317        let t_utc = Epoch::from_str("2020-01-01T00:00:10 UTC").unwrap();
318
319        assert!(database
320            .precise_epoch_correction(t_utc, TimeScale::GST)
321            .is_none());
322
323        assert!(database
324            .precise_epoch_correction(t_utc, TimeScale::GPST)
325            .is_none());
326    }
327
328    #[test]
329    #[ignore]
330    fn test_indirect_forward_transform_not_utc() {
331        let t_ref_bdt = Epoch::from_str("2020-01-01T00:00:00 BDT").unwrap();
332        let t_ref_gst = Epoch::from_str("2020-01-01T00:00:00 GST").unwrap();
333        //let t_ref_gpst = Epoch::from_str("2020-01-01T00:00:00 GPST").unwrap();
334
335        let (a0_bdt_gst, _, _) = (1.0E-9, 0.0, 0.0);
336        let (a0_gst_gpst, _, _) = (2.0E-9, 0.0, 0.0);
337
338        let mut solver = TimeCorrectionsDB::default();
339
340        solver.add(TimeCorrection {
341            lhs_timescale: TimeScale::BDT,
342            rhs_timescale: TimeScale::GST,
343            validity_period: Duration::from_hours(1.0),
344            ref_epoch: t_ref_bdt,
345            polynomial: Polynomial {
346                constant: Duration::from_seconds(a0_bdt_gst),
347                rate: Duration::ZERO,
348                accel: Duration::ZERO,
349            },
350        });
351
352        solver.add(TimeCorrection {
353            lhs_timescale: TimeScale::GST,
354            rhs_timescale: TimeScale::GPST,
355            validity_period: Duration::from_hours(1.0),
356            ref_epoch: t_ref_gst,
357            polynomial: Polynomial {
358                constant: Duration::from_seconds(a0_gst_gpst),
359                rate: Duration::ZERO,
360                accel: Duration::ZERO,
361            },
362        });
363
364        // verify direct transforms still work
365        let t_gst = Epoch::from_str("2020-01-01T00:00:00 GST").unwrap();
366        let t_bdt = Epoch::from_str("2020-01-01T00:00:00 BDT").unwrap();
367        let t_gpst = Epoch::from_str("2020-01-01T00:00:00 GPST").unwrap();
368
369        let t_gst_gpst = solver
370            .precise_epoch_correction(t_gst, TimeScale::GPST)
371            .unwrap();
372
373        assert_eq!(t_gst_gpst.time_scale, TimeScale::GPST);
374
375        let t_gpst_gst = solver
376            .precise_epoch_correction(t_gpst, TimeScale::GST)
377            .unwrap();
378
379        assert_eq!(t_gpst_gst.time_scale, TimeScale::GST);
380
381        let t_gst_bdt = solver
382            .precise_epoch_correction(t_gst, TimeScale::BDT)
383            .unwrap();
384
385        assert_eq!(t_gst_bdt.time_scale, TimeScale::BDT);
386
387        let t_bdt_gst = solver
388            .precise_epoch_correction(t_bdt, TimeScale::GST)
389            .unwrap();
390
391        assert_eq!(t_bdt_gst.time_scale, TimeScale::GST);
392
393        // indirect forward transform
394        let t_bdt_gpst = solver
395            .precise_epoch_correction(t_bdt, TimeScale::GPST)
396            .unwrap();
397
398        assert_eq!(t_bdt_gpst.time_scale, TimeScale::GPST);
399
400        let coarsed = t_bdt.to_time_scale(TimeScale::GPST);
401        let dt = coarsed - t_bdt_gpst;
402
403        assert_eq!(
404            dt,
405            Duration::from_seconds(a0_bdt_gst) + Duration::from_seconds(a0_gst_gpst)
406        );
407
408        // linearity
409        let reciprocal = solver
410            .precise_epoch_correction(t_bdt_gpst, TimeScale::BDT)
411            .unwrap();
412
413        assert_eq!(reciprocal.time_scale, TimeScale::BDT);
414        assert_eq!(reciprocal, t_bdt);
415
416        // indirect backward transform
417        let t_gpst_bdt = solver
418            .precise_epoch_correction(t_gpst, TimeScale::BDT)
419            .unwrap();
420
421        assert_eq!(t_gpst_bdt.time_scale, TimeScale::BDT);
422
423        let coarsed = t_gpst.to_time_scale(TimeScale::BDT);
424        let dt = coarsed - t_gpst_bdt;
425
426        assert_eq!(
427            dt,
428            Duration::from_seconds(a0_bdt_gst) + Duration::from_seconds(a0_gst_gpst)
429        );
430
431        // linearity
432        let reciprocal = solver
433            .precise_epoch_correction(t_gpst_bdt, TimeScale::GPST)
434            .unwrap();
435
436        assert_eq!(reciprocal.time_scale, TimeScale::GPST);
437        assert_eq!(reciprocal, t_gpst);
438    }
439
440    #[test]
441    #[ignore]
442    fn test_indirect_forward_transform_utc() {
443        let t_ref_bdt = Epoch::from_str("2020-01-01T00:00:00 BDT").unwrap();
444        //let t_ref_gst = Epoch::from_str("2020-01-01T00:00:00 GST").unwrap();
445        let t_ref_gpst = Epoch::from_str("2020-01-01T00:00:00 GPST").unwrap();
446
447        let (a0_bdt_gst, _, _) = (1.0E-9, 0.0, 0.0);
448        let (a0_gpst_utc, _, _) = (2.0E-9, 0.0, 0.0);
449
450        let mut database = TimeCorrectionsDB::default();
451
452        database.add(TimeCorrection {
453            lhs_timescale: TimeScale::BDT,
454            rhs_timescale: TimeScale::GST,
455            ref_epoch: t_ref_bdt,
456            polynomial: Polynomial {
457                constant: Duration::from_seconds(a0_bdt_gst),
458                rate: Duration::ZERO,
459                accel: Duration::ZERO,
460            },
461            validity_period: Duration::from_hours(1.0),
462        });
463
464        database.add(TimeCorrection {
465            lhs_timescale: TimeScale::GPST,
466            rhs_timescale: TimeScale::UTC,
467            ref_epoch: t_ref_gpst,
468            polynomial: Polynomial {
469                constant: Duration::from_seconds(a0_gpst_utc),
470                rate: Duration::ZERO,
471                accel: Duration::ZERO,
472            },
473            validity_period: Duration::from_hours(1.0),
474        });
475
476        // verify direct transforms still work
477        let t_gst = Epoch::from_str("2020-01-01T00:00:00 GST").unwrap();
478        let t_bdt = Epoch::from_str("2020-01-01T00:00:00 BDT").unwrap();
479        let t_gpst = Epoch::from_str("2020-01-01T00:00:00 GPST").unwrap();
480
481        let t_gpst_utc = database
482            .precise_epoch_correction(t_gpst, TimeScale::UTC)
483            .unwrap();
484
485        assert_eq!(t_gpst_utc.time_scale, TimeScale::UTC);
486
487        // linearity
488        let reciprocal = database
489            .precise_epoch_correction(t_gpst_utc, TimeScale::GPST)
490            .unwrap();
491
492        assert_eq!(reciprocal.time_scale, TimeScale::GPST);
493        assert_eq!(reciprocal, t_gpst);
494
495        let t_gst_bdt = database
496            .precise_epoch_correction(t_gst, TimeScale::BDT)
497            .unwrap();
498
499        assert_eq!(t_gst_bdt.time_scale, TimeScale::BDT);
500
501        let t_bdt_gst = database
502            .precise_epoch_correction(t_bdt, TimeScale::GST)
503            .unwrap();
504
505        assert_eq!(t_bdt_gst.time_scale, TimeScale::GST);
506
507        // indirect forward transform
508        let t_bdt_utc = database
509            .precise_epoch_correction(t_bdt, TimeScale::UTC)
510            .unwrap();
511
512        assert_eq!(t_bdt_utc.time_scale, TimeScale::UTC);
513
514        let coarsed = t_bdt.to_time_scale(TimeScale::UTC);
515        let dt = coarsed - t_bdt_utc;
516
517        assert_eq!(
518            dt,
519            Duration::from_seconds(a0_bdt_gst) + Duration::from_seconds(a0_gpst_utc)
520        );
521
522        // linearity
523        let reciprocal = database
524            .precise_epoch_correction(t_bdt_utc, TimeScale::BDT)
525            .unwrap();
526
527        assert_eq!(reciprocal.time_scale, TimeScale::BDT);
528        assert_eq!(reciprocal, t_bdt);
529    }
530}