tai_time/
tai_clock.rs

1//! Clock for the generation of `TaiTime` timestamps.
2
3use std::time::{Instant, SystemTime};
4
5use crate::{BdtTime, GpsTime, GstTime, MonotonicTime, Tai1958Time, Tai1972Time, TaiTime};
6
7/// A [`TaiClock`] alias generating [`MonotonicTime`] timestamps.
8pub type MonotonicClock = TaiClock<{ MonotonicTime::EPOCH_REF }>;
9
10/// A [`TaiClock`] alias generating [`GpsTime`] timestamps.
11pub type GpsClock = TaiClock<{ GpsTime::EPOCH_REF }>;
12
13/// A [`TaiClock`] alias generating [`GstTime`] timestamps.
14pub type GstClock = TaiClock<{ GstTime::EPOCH_REF }>;
15
16/// A [`TaiClock`] alias generating [`BdtTime`] timestamps.
17pub type BdtClock = TaiClock<{ BdtTime::EPOCH_REF }>;
18
19/// A [`TaiClock`] alias generating [`Tai1958Time`] timestamps.
20pub type Tai1958Clock = TaiClock<{ Tai1958Time::EPOCH_REF }>;
21
22/// A [`TaiClock`] alias generating [`Tai1972Time`] timestamps.
23pub type Tai1972Clock = TaiClock<{ Tai1972Time::EPOCH_REF }>;
24
25/// A monotonic clock that generates [`TaiTime`] timestamps.
26///
27/// This clock internally relies on [`Instant::now`] and can therefore be used
28/// on systems that do not support [`TaiTime::now`], or when the time reference
29/// needs to differ from the wall clock time (clock with offset).
30///
31/// A `TaiClock` instance can be simultaneously accessed from several threads.
32///
33/// See also: [`MonotonicClock`], [`GpsClock`], [`GstClock`], [`BdtClock`],
34/// [`Tai1958Clock`] and [`Tai1972Clock`].
35///
36/// # Examples
37///
38/// ```
39/// use std::thread;
40/// use std::sync::Arc;
41/// use tai_time::TaiClock;
42///
43/// type MyCustomClock = TaiClock<123>;
44///
45/// // Initialize the TAI clock assuming that the current difference
46/// // between TAI and UTC time is 37s.
47/// let clock = Arc::new(MyCustomClock::init_from_utc(37));
48///
49/// // Time the execution of 2 different threads.
50/// let th1 = thread::spawn({
51///     let clock = clock.clone();
52///     move || clock.now()
53/// });
54/// let th2 = thread::spawn(
55///     move || clock.now()
56/// );
57/// let t1 = th1.join().unwrap();
58/// let t2 = th2.join().unwrap();
59///
60/// println!("thread 1 has completed at {} TAI", t1);
61/// println!("thread 2 has completed at {} TAI", t2);
62/// ```
63#[derive(Copy, Clone, Debug, Hash)]
64pub struct TaiClock<const EPOCH_REF: i64> {
65    timestamp_ref: TaiTime<EPOCH_REF>,
66    wall_clock_ref: Instant,
67}
68
69impl<const EPOCH_REF: i64> TaiClock<EPOCH_REF> {
70    /// Initializes the clock by associating a TAI timestamp to the current wall
71    /// clock time.
72    ///
73    /// Future calls to [`now`](Self::now) will return timestamps that are
74    /// relative to the provided timestamp, with a constant offset with respect
75    /// to the monotonic wall clock time.
76    pub fn init_at(now: TaiTime<EPOCH_REF>) -> Self {
77        Self::init_from_instant(now, Instant::now())
78    }
79
80    /// Initializes the clock from the UTC system clock.
81    ///
82    /// The argument is the difference between TAI and UTC time in seconds
83    /// (a.k.a. leap seconds) applicable at the date represented by the
84    /// timestamp. For reference, this offset has been +37s since 2017-01-01, a
85    /// value which is to remain valid until at least 2024-12-28. See the
86    /// [official IERS bulletin
87    /// C](http://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) for leap second
88    /// announcements or the [IERS
89    /// table](https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat) for
90    /// current and historical values.
91    ///
92    /// Beware that the behavior of the system clock near a leap second
93    /// shouldn't be relied upon, where *near* might actually stand for the
94    /// whole 24h period preceding a leap second due to the possible use of the
95    /// so-called *leap second smearing* strategy.
96    ///
97    /// Note that `TaiClock` is based on the monotonic system clock while UTC
98    /// time can only be obtained from the non-monotonic system clock. This
99    /// constructor attempts to find a well-correlated pair of monotonic and UTC
100    /// system clock timestamps by collecting several candidate samples from
101    /// interleaved calls to `SystemTime::now` and `Instant::now`.
102    pub fn init_from_utc(leap_secs: i64) -> Self {
103        let (system_time_ref, instant_ref) = get_correlated_time_refs();
104
105        Self::init_from_instant(
106            TaiTime::from_system_time(&system_time_ref, leap_secs),
107            instant_ref,
108        )
109    }
110
111    /// Initializes the clock by associating the provided TAI timestamp to the
112    /// provided `Instant`.
113    ///
114    /// The `wall_clock_ref` argument may lie in the past or in the future of
115    /// the current wall clock time.
116    ///
117    /// Future calls to [`now`](Self::now) will return timestamps with a
118    /// constant offset with respect to the monotonic wall clock time. The
119    /// offset is defined by the requirement that [`now`](Self::now) should
120    /// return `timestamp_ref` when the wall clock time matches
121    /// `wall_clock_ref`.
122    pub fn init_from_instant(timestamp_ref: TaiTime<EPOCH_REF>, wall_clock_ref: Instant) -> Self {
123        Self {
124            timestamp_ref,
125            wall_clock_ref,
126        }
127    }
128
129    /// Initializes the clock by associating a TAI timestamp to a `SystemTime`.
130    ///
131    /// The `wall_clock_ref` argument may lie in the past or in the future of
132    /// the current wall clock time.
133    ///
134    /// Future calls to [`now`](Self::now) will return timestamps with a
135    /// constant offset with respect to the monotonic wall clock time. The
136    /// offset is defined by the requirement that [`now`](Self::now) should
137    /// return `timestamp_ref` when the wall clock time matches
138    /// `wall_clock_ref`.
139    ///
140    /// Note that `TaiClock` is based on the monotonic system clock while UTC
141    /// time can only be obtained from the non-monotonic system clock. This
142    /// constructor attempts to find a well-correlated pair of monotonic and UTC
143    /// system clock timestamps by collecting several candidate samples from
144    /// interleaved calls to `SystemTime::now` and `Instant::now`.
145    pub fn init_from_system_time(
146        timestamp_ref: TaiTime<EPOCH_REF>,
147        wall_clock_ref: SystemTime,
148    ) -> Self {
149        let (system_time_ref, instant_ref) = get_correlated_time_refs();
150
151        let timestamp_ref = if wall_clock_ref > system_time_ref {
152            timestamp_ref - wall_clock_ref.duration_since(system_time_ref).unwrap()
153        } else {
154            timestamp_ref + system_time_ref.duration_since(wall_clock_ref).unwrap()
155        };
156
157        Self::init_from_instant(timestamp_ref, instant_ref)
158    }
159
160    /// Returns a TAI timestamp corresponding to the current wall clock time.
161    ///
162    /// The returned timestamp will never be lower than a timestamp returned by
163    /// a previous call to `now`.
164    pub fn now(&self) -> TaiTime<EPOCH_REF> {
165        let now = Instant::now();
166
167        if now >= self.wall_clock_ref {
168            self.timestamp_ref + now.duration_since(self.wall_clock_ref)
169        } else {
170            self.timestamp_ref - self.wall_clock_ref.duration_since(now)
171        }
172    }
173}
174
175/// Returns a pair of well-correlated `SystemTime` and `Instant`.
176fn get_correlated_time_refs() -> (SystemTime, Instant) {
177    const EXTRA_SAMPLES: usize = 2;
178
179    let mut instant = Instant::now();
180    let system_time = SystemTime::now();
181    let mut instant_after = Instant::now();
182
183    let delta = instant_after.saturating_duration_since(instant); // uncertainty on measurement.
184    let mut measurement = (instant, delta, system_time);
185
186    for _ in 0..EXTRA_SAMPLES {
187        instant = instant_after;
188        let system_time = SystemTime::now();
189        instant_after = Instant::now();
190        let delta = instant_after.saturating_duration_since(instant);
191
192        // If the uncertainty on this measurement is lower then prefer this
193        // measurement. Measurements with a null uncertainty are discarded
194        // as they are most likely indicative of a platform bug.
195        if measurement.1.is_zero() || (delta < measurement.1 && !delta.is_zero()) {
196            measurement = (instant, delta, system_time);
197        }
198    }
199
200    // Take the best measurement and associate its `SystemTime` to the average
201    // value of the `Instant`s measured just before and just after it.
202    (measurement.2, measurement.0 + measurement.1.mul_f32(0.5))
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn clock_init_at_smoke() {
211        use std::time::Duration;
212
213        let time_ref: MonotonicTime = MonotonicTime::new(-12345678, 987654321).unwrap(); // just an arbitrary value
214        const TOLERANCE: Duration = Duration::from_millis(20);
215
216        let clock = MonotonicClock::init_at(time_ref);
217
218        assert!(clock.now().duration_since(time_ref) <= TOLERANCE);
219    }
220
221    #[test]
222    fn clock_init_from_utc_smoke() {
223        use std::time::Duration;
224
225        const LEAP_SECS: i64 = 123; // just an arbitrary value
226        const TOLERANCE: Duration = Duration::from_millis(20);
227
228        let clock = MonotonicClock::init_from_utc(LEAP_SECS);
229
230        let utc_now = SystemTime::UNIX_EPOCH.elapsed().unwrap();
231        let tai_now_from_utc = MonotonicTime::new(LEAP_SECS, 0).unwrap() + utc_now;
232        let tai_now_from_clock = clock.now();
233
234        if tai_now_from_clock >= tai_now_from_utc {
235            assert!(tai_now_from_clock.duration_since(tai_now_from_utc) <= TOLERANCE);
236        } else {
237            assert!(tai_now_from_utc.duration_since(tai_now_from_clock) <= TOLERANCE);
238        }
239    }
240
241    #[test]
242    fn clock_init_from_past_instant_smoke() {
243        use std::time::Duration;
244
245        use crate::MonotonicTime;
246
247        const OFFSET: Duration = Duration::from_secs(1000);
248        const TOLERANCE: Duration = Duration::from_millis(20);
249
250        let t0 = MonotonicTime::new(123, 456).unwrap();
251        let clock = MonotonicClock::init_from_instant(t0, Instant::now() - OFFSET);
252
253        let delta = clock.now().duration_since(t0 + OFFSET);
254        assert!(delta <= TOLERANCE);
255    }
256
257    #[test]
258    fn clock_init_from_future_instant_smoke() {
259        use std::time::Duration;
260
261        use crate::MonotonicTime;
262
263        const OFFSET: Duration = Duration::from_secs(1000);
264        const TOLERANCE: Duration = Duration::from_millis(20);
265
266        let t0 = MonotonicTime::new(123, 456).unwrap();
267        let clock = MonotonicClock::init_from_instant(t0, Instant::now() + OFFSET);
268
269        let delta = clock.now().duration_since(t0 - OFFSET);
270        assert!(delta <= TOLERANCE);
271    }
272
273    #[test]
274    fn clock_init_from_past_system_time_smoke() {
275        use std::time::Duration;
276
277        use crate::MonotonicTime;
278
279        const OFFSET: Duration = Duration::from_secs(1000);
280        const TOLERANCE: Duration = Duration::from_millis(20);
281
282        let t0 = MonotonicTime::new(123, 456).unwrap();
283        let clock = MonotonicClock::init_from_system_time(t0, SystemTime::now() - OFFSET);
284
285        let delta = clock.now().duration_since(t0 + OFFSET);
286        assert!(delta <= TOLERANCE);
287    }
288
289    #[test]
290    fn clock_init_from_future_system_time_smoke() {
291        use std::time::Duration;
292
293        use crate::MonotonicTime;
294
295        const OFFSET: Duration = Duration::from_secs(1000);
296        const TOLERANCE: Duration = Duration::from_millis(20);
297
298        let t0 = MonotonicTime::new(123, 456).unwrap();
299        let clock = MonotonicClock::init_from_system_time(t0, SystemTime::now() + OFFSET);
300
301        let delta = clock.now().duration_since(t0 - OFFSET);
302        assert!(delta <= TOLERANCE);
303    }
304}