local_clock/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! An instance-local, reprogrammable, real time clock.
5//!
6//! This crate's primary export is the [`LocalClock`] trait, which defines an
7//! interface for interacting with instances of reprogrammable real time clocks,
8//! decoupled from any particular platform's global real time clock.
9//!
10//! Also included in `local_time` is [`SystemTimeClock`], which is a basic
11//! in-tree implementation of [`LocalClock`] backed by
12//! [`std::time::SystemTime`]. Unless you're worried about handling instances
13//! where `SystemTime` jumps backwards, this is perfectly reasonable
14//! implementation to use.
15//!
16//! This crate deliberately takes a minimalist approach to dependencies, and
17//! avoids pulling in any "heavyweight" date/time crates by default (e.g:
18//! `time`, `chrono`, etc...). That said, native integration with some of these
19//! crates is provided via optional features.
20//!
21//! # Features
22//!
23//! - `inspect` - Derives the [`Inspect`] trait on various types.
24//! - `time_exts` - Integration with the [`time`] crate
25//!     - Provides `From`/`Into` implementations to interop with duration and
26//!       date/time types from `time`.
27//!     - Changes the `Debug` implementation of [`LocalClockTime`] to print a
28//!       human readable date (instead of a raw duration since the Unix Epoch).
29//!
30//! [`Inspect`]: inspect::Inspect
31
32#![forbid(unsafe_code)]
33
34mod clock_impls;
35
36pub use clock_impls::MockLocalClock;
37pub use clock_impls::MockLocalClockAccessor;
38pub use clock_impls::SystemTimeClock;
39#[cfg(feature = "inspect")]
40pub use inspect_ext::InspectableLocalClock;
41
42use std::fmt::Debug;
43
44/// A local real-time clock, hanging-off the platform's global real-time clock.
45///
46/// One way to think about [`LocalClock`] is that it matches the semantics of
47/// the POSIX methods `clock_gettime` and `clock_settime` when backed by
48/// `CLOCK_REALTIME`, except setting the time on a [`LocalClock`] will only
49/// affect that particular instance (as opposed to changing the global system
50/// time).
51///
52/// NOTE: These methods may be invoked fairly often, and as such, implementors
53/// should ensure these methods do not block!
54pub trait LocalClock: Send {
55    /// Return the current clock time.
56    ///
57    /// # First call
58    ///
59    /// If `set_time` has yet to be called, the `LocalClock` trait makes _no
60    /// guarantees_ as to what time `get_time` will return!
61    ///
62    /// Conceptually, this would be akin to pulling the real-time-block battery
63    /// from a physical machine, thereby resetting the clock to its "default"
64    /// state - whatever that might be.
65    ///
66    /// A simple implementation would be to just return a hard-coded, fixed
67    /// value, corresponding to some arbitrary date.
68    ///
69    /// ...that being said, a far more useful implementation would be to simply
70    /// report the platform's current real time. That way, even if `set_time`
71    /// never gets invoked, (e.g: as a result of communicating with a NTP
72    /// server, a synthetic real-time assist virtual device, manual user input,
73    /// etc...), the reported real time will still be reasonably close to the
74    /// current date.
75    ///
76    /// # Subsequent calls
77    ///
78    /// On subsequent calls to this function, this method MUST return the sum of
79    /// the previously set time set via `set_time`, _plus_ the wall-clock time
80    /// that has elapsed since.
81    ///
82    /// NOTE: implementations SHOULD ensure that real time continues to tick
83    /// even when the device / platform itself has shutdown / been paused.
84    fn get_time(&mut self) -> LocalClockTime;
85
86    /// Set the current clock time.
87    fn set_time(&mut self, new_time: LocalClockTime);
88}
89
90/// A delta between two [`LocalClockTime`] instances.
91///
92/// Unlike [`std::time::Duration`], a `LocalClockDelta` may be negative, as
93/// unlike [`std::time::Instant`] or [`std::time::SystemTime`], it's perfectly
94/// reasonable (and expected!) that [`LocalClock`] returns a [`LocalClockTime`]
95/// that is _earlier_ than a previously returned `LocalClockTime` (as would be
96/// the case if a `LocalClock` is re-programmed to an earlier time).
97///
98/// This type doesn't expose a particularly "rich" API for working the the
99/// contained time delta. Rather, consumers of this type are expected to us it
100/// alongside an external time/date library (such as `time` or `chrono`) in
101/// order to more easily manipulate the time delta.
102#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
103#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
104#[cfg_attr(feature = "inspect", inspect(debug))]
105pub struct LocalClockDelta {
106    // DEVNOTE: see DEVNOTE in `LocalClockTime` for rationale around storing
107    // duration in units of milliseconds.
108    millis: i64,
109}
110
111impl Debug for LocalClockDelta {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        write!(
114            f,
115            "{}{:?}",
116            if self.millis.is_negative() { "-" } else { "" },
117            std::time::Duration::from_millis(self.millis.unsigned_abs())
118        )
119    }
120}
121
122impl LocalClockDelta {
123    /// A delta of zero milliseconds.
124    pub const ZERO: Self = Self { millis: 0 };
125
126    /// Return the duration in milliseconds.
127    pub const fn as_millis(self) -> i64 {
128        self.millis
129    }
130
131    /// Create a duration from milliseconds.
132    pub const fn from_millis(millis: i64) -> Self {
133        Self { millis }
134    }
135}
136
137/// An opaque type, representing an instant in time.
138///
139/// This type doesn't expose a particularly "rich" API for working the the
140/// contained time. Rather, consumers of this type are expected to us it
141/// alongside an external time/date library (such as `time` or `chrono`) in
142/// order to more easily manipulate time/date values.
143#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
144#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
145#[cfg_attr(feature = "inspect", inspect(debug))]
146pub struct LocalClockTime {
147    // A couple DEVNOTES:
148    //
149    // - The decision to store time using the Unix Epoch as an "anchor" was
150    //   primarily chosen out of convenience, and could just as well be swapped
151    //   out for something else if that turns out to be more expedient.
152    //
153    // - The decision to store time in units of milliseconds was a purely
154    //   pragmatic one: RTC devices typically don't have resolutions greater
155    //   than 1ms. That said, if this assumption turns out to be untrue, the
156    //   fact that `LocalClockTime` is an opaque type means that we could
157    //   non-intrusively bump the contained resolution without affecting
158    //   existing consumers.
159    //
160    // - i64::MAX / i64::MIN correspond to ~300000000 years on either end of the
161    //   unix epoch, in case you're worried about not being able to represent a
162    //   particular date.
163    millis_since_epoch: i64,
164}
165
166impl Debug for LocalClockTime {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        #[cfg(not(feature = "time_exts"))]
169        {
170            write!(
171                f,
172                "{}{:?}",
173                if self.millis_since_epoch.is_negative() {
174                    "-"
175                } else {
176                    ""
177                },
178                std::time::Duration::from_millis(self.millis_since_epoch.unsigned_abs())
179            )
180        }
181
182        #[cfg(feature = "time_exts")]
183        {
184            let date_time: Result<time::OffsetDateTime, _> = (*self).try_into();
185            match date_time {
186                Ok(date_time) => write!(f, "{}", date_time),
187                Err(e) => write!(f, "{:?}", e),
188            }
189        }
190    }
191}
192
193impl LocalClockTime {
194    /// Return the number of millis since the Unix Epoch (in UTC).
195    ///
196    /// Also see: [`std::time::UNIX_EPOCH`]
197    pub fn as_millis_since_unix_epoch(&self) -> i64 {
198        self.millis_since_epoch
199    }
200
201    /// Create a new [`LocalClockTime`] containing the time some number of
202    /// millis offset from the Unix Epoch (in UTC).
203    ///
204    /// Also see: [`std::time::UNIX_EPOCH`]
205    pub fn from_millis_since_unix_epoch(millis: i64) -> Self {
206        Self {
207            millis_since_epoch: millis,
208        }
209    }
210}
211
212impl std::ops::Sub for LocalClockDelta {
213    type Output = LocalClockDelta;
214
215    fn sub(self, rhs: Self) -> Self::Output {
216        LocalClockDelta {
217            millis: self.millis - rhs.millis,
218        }
219    }
220}
221
222impl std::ops::Add for LocalClockDelta {
223    type Output = LocalClockDelta;
224
225    fn add(self, rhs: Self) -> Self::Output {
226        LocalClockDelta {
227            millis: self.millis + rhs.millis,
228        }
229    }
230}
231
232impl std::ops::Sub for LocalClockTime {
233    type Output = LocalClockDelta;
234
235    fn sub(self, rhs: Self) -> Self::Output {
236        LocalClockDelta::from_millis(self.millis_since_epoch - rhs.millis_since_epoch)
237    }
238}
239
240impl std::ops::Sub<LocalClockDelta> for LocalClockTime {
241    type Output = LocalClockTime;
242
243    fn sub(self, rhs: LocalClockDelta) -> Self::Output {
244        LocalClockTime {
245            millis_since_epoch: self.millis_since_epoch - rhs.as_millis(),
246        }
247    }
248}
249
250impl std::ops::Add<LocalClockDelta> for LocalClockTime {
251    type Output = LocalClockTime;
252
253    fn add(self, rhs: LocalClockDelta) -> Self::Output {
254        LocalClockTime {
255            millis_since_epoch: self.millis_since_epoch + rhs.as_millis(),
256        }
257    }
258}
259
260// only allow one-way conversion, as std::time::Duration doesn't support
261// negative time
262impl From<std::time::Duration> for LocalClockDelta {
263    fn from(d: std::time::Duration) -> Self {
264        LocalClockDelta::from_millis(d.as_millis().try_into().unwrap())
265    }
266}
267
268impl From<std::time::SystemTime> for LocalClockTime {
269    fn from(sys_time: std::time::SystemTime) -> Self {
270        let millis_since_epoch = if std::time::UNIX_EPOCH < sys_time {
271            let since_epoch = sys_time.duration_since(std::time::UNIX_EPOCH).unwrap();
272            let millis: i64 = since_epoch.as_millis().try_into().unwrap();
273            millis
274        } else {
275            let since_epoch = std::time::UNIX_EPOCH.duration_since(sys_time).unwrap();
276            let millis: i64 = since_epoch.as_millis().try_into().unwrap();
277            -millis
278        };
279
280        LocalClockTime::from_millis_since_unix_epoch(millis_since_epoch)
281    }
282}
283
284impl From<LocalClockTime> for std::time::SystemTime {
285    fn from(clock_time: LocalClockTime) -> Self {
286        let millis_since_epoch = clock_time.as_millis_since_unix_epoch();
287        if millis_since_epoch.is_negative() {
288            let before_epoch = std::time::Duration::from_millis((-millis_since_epoch) as u64);
289            std::time::UNIX_EPOCH.checked_sub(before_epoch).unwrap()
290        } else {
291            let after_epoch = std::time::Duration::from_millis(millis_since_epoch as u64);
292            std::time::UNIX_EPOCH.checked_add(after_epoch).unwrap()
293        }
294    }
295}
296
297/// Indicates that an overflow error occurred during conversion.
298#[derive(Debug)]
299pub struct OverflowError;
300
301#[cfg(feature = "time_exts")]
302mod time_ext {
303    use super::LocalClockDelta;
304    use super::LocalClockTime;
305
306    impl From<time::OffsetDateTime> for LocalClockTime {
307        fn from(date_time: time::OffsetDateTime) -> LocalClockTime {
308            let since_epoch = date_time - time::OffsetDateTime::UNIX_EPOCH;
309            LocalClockTime::from_millis_since_unix_epoch(
310                since_epoch.whole_milliseconds().try_into().unwrap(),
311            )
312        }
313    }
314
315    impl From<time::Duration> for LocalClockDelta {
316        fn from(time_duration: time::Duration) -> LocalClockDelta {
317            LocalClockDelta::from_millis(time_duration.whole_milliseconds().try_into().unwrap())
318        }
319    }
320
321    impl From<LocalClockDelta> for time::Duration {
322        fn from(clock_duration: LocalClockDelta) -> time::Duration {
323            time::Duration::milliseconds(clock_duration.as_millis())
324        }
325    }
326
327    impl TryFrom<LocalClockTime> for time::OffsetDateTime {
328        type Error = super::OverflowError;
329
330        fn try_from(clock_time: LocalClockTime) -> Result<time::OffsetDateTime, Self::Error> {
331            let duration = time::Duration::milliseconds(clock_time.as_millis_since_unix_epoch());
332            time::OffsetDateTime::UNIX_EPOCH
333                .checked_add(duration)
334                .ok_or(super::OverflowError)
335        }
336    }
337}
338
339/// Defines a trait that combines LocalClock and Inspect
340#[cfg(feature = "inspect")]
341mod inspect_ext {
342    use super::*;
343    use inspect::Inspect;
344
345    /// Extends [`LocalClock`] with a bound on [`Inspect`]
346    pub trait InspectableLocalClock: LocalClock + Inspect {}
347    impl<T: LocalClock + Inspect> InspectableLocalClock for T {}
348}