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}