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}