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