1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! An instance-local, reprogrammable, real time clock.
//!
//! This crate's primary export is the [`LocalClock`] trait, which defines an
//! interface for interacting with instances of reprogrammable real time clocks,
//! decoupled from any particular platform's global real time clock.
//!
//! Also included in `local_time` is [`SystemTimeClock`], which is a basic
//! in-tree implementation of [`LocalClock`] backed by
//! [`std::time::SystemTime`]. Unless you're worried about handling instances
//! where `SystemTime` jumps backwards, this is perfectly reasonable
//! implementation to use.
//!
//! This crate deliberately takes a minimalist approach to dependencies, and
//! avoids pulling in any "heavyweight" date/time crates by default (e.g:
//! `time`, `chrono`, etc...). That said, native integration with some of these
//! crates is provided via optional features.
//!
//! # Features
//!
//! - `inspect` - Derives the [`Inspect`] trait on various types.
//! - `time_exts` - Integration with the [`time`] crate
//!     - Provides `From`/`Into` implementations to interop with duration and
//!       date/time types from `time`.
//!     - Changes the `Debug` implementation of [`LocalClockTime`] to print a
//!       human readable date (instead of a raw duration since the Unix Epoch).
//!
//! [`Inspect`]: inspect::Inspect

#![warn(missing_docs)]

mod clock_impls;

pub use clock_impls::MockLocalClock;
pub use clock_impls::MockLocalClockAccessor;
pub use clock_impls::SystemTimeClock;
#[cfg(feature = "inspect")]
pub use inspect_ext::InspectableLocalClock;

use std::fmt::Debug;

/// A local real-time clock, hanging-off the platform's global real-time clock.
///
/// One way to think about [`LocalClock`] is that it matches the semantics of
/// the POSIX methods `clock_gettime` and `clock_settime` when backed by
/// `CLOCK_REALTIME`, except setting the time on a [`LocalClock`] will only
/// affect that particular instance (as opposed to changing the global system
/// time).
///
/// NOTE: These methods may be invoked fairly often, and as such, implementors
/// should ensure these methods do not block!
pub trait LocalClock: Send {
    /// Return the current clock time.
    ///
    /// # First call
    ///
    /// If `set_time` has yet to be called, the `LocalClock` trait makes _no
    /// guarantees_ as to what time `get_time` will return!
    ///
    /// Conceptually, this would be akin to pulling the real-time-block battery
    /// from a physical machine, thereby resetting the clock to its "default"
    /// state - whatever that might be.
    ///
    /// A simple implementation would be to just return a hard-coded, fixed
    /// value, corresponding to some arbitrary date.
    ///
    /// ...that being said, a far more useful implementation would be to simply
    /// report the platform's current real time. That way, even if `set_time`
    /// never gets invoked, (e.g: as a result of communicating with a NTP
    /// server, a synthetic real-time assist virtual device, manual user input,
    /// etc...), the reported real time will still be reasonably close to the
    /// current date.
    ///
    /// # Subsequent calls
    ///
    /// On subsequent calls to this function, this method MUST return the sum of
    /// the previously set time set via `set_time`, _plus_ the wall-clock time
    /// that has elapsed since.
    ///
    /// NOTE: implementations SHOULD ensure that real time continues to tick
    /// even when the device / platform itself has shutdown / been paused.
    fn get_time(&mut self) -> LocalClockTime;

    /// Set the current clock time.
    fn set_time(&mut self, new_time: LocalClockTime);
}

/// A delta between two [`LocalClockTime`] instances.
///
/// Unlike [`std::time::Duration`], a `LocalClockDelta` may be negative, as
/// unlike [`std::time::Instant`] or [`std::time::SystemTime`], it's perfectly
/// reasonable (and expected!) that [`LocalClock`] returns a [`LocalClockTime`]
/// that is _earlier_ than a previously returned `LocalClockTime` (as would be
/// the case if a `LocalClock` is re-programmed to an earlier time).
///
/// This type doesn't expose a particularly "rich" API for working the the
/// contained time delta. Rather, consumers of this type are expected to us it
/// alongside an external time/date library (such as `time` or `chrono`) in
/// order to more easily manipulate the time delta.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
#[cfg_attr(feature = "inspect", inspect(debug))]
pub struct LocalClockDelta {
    // DEVNOTE: see DEVNOTE in `LocalClockTime` for rationale around storing
    // duration in units of milliseconds.
    millis: i64,
}

impl Debug for LocalClockDelta {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}{:?}",
            if self.millis.is_negative() { "-" } else { "" },
            std::time::Duration::from_millis(self.millis.unsigned_abs())
        )
    }
}

impl LocalClockDelta {
    /// Return the duration in milliseconds.
    pub fn as_millis(self) -> i64 {
        self.millis
    }

    /// Create a duration from milliseconds.
    pub fn from_millis(millis: i64) -> Self {
        Self { millis }
    }
}

/// An opaque type, representing an instant in time.
///
/// This type doesn't expose a particularly "rich" API for working the the
/// contained time. Rather, consumers of this type are expected to us it
/// alongside an external time/date library (such as `time` or `chrono`) in
/// order to more easily manipulate time/date values.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "inspect", derive(inspect::Inspect))]
#[cfg_attr(feature = "inspect", inspect(debug))]
pub struct LocalClockTime {
    // A couple DEVNOTES:
    //
    // - The decision to store time using the Unix Epoch as an "anchor" was
    //   primarily chosen out of convenience, and could just as well be swapped
    //   out for something else if that turns out to be more expedient.
    //
    // - The decision to store time in units of milliseconds was a purely
    //   pragmatic one: RTC devices typically don't have resolutions greater
    //   than 1ms. That said, if this assumption turns out to be untrue, the
    //   fact that `LocalClockTime` is an opaque type means that we could
    //   non-intrusively bump the contained resolution without affecting
    //   existing consumers.
    //
    // - i64::MAX / i64::MIN correspond to ~300000000 years on either end of the
    //   unix epoch, in case you're worried about not being able to represent a
    //   particular date.
    millis_since_epoch: i64,
}

impl Debug for LocalClockTime {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        #[cfg(not(feature = "time_exts"))]
        {
            write!(
                f,
                "{}{:?}",
                if self.millis_since_epoch.is_negative() {
                    "-"
                } else {
                    ""
                },
                std::time::Duration::from_millis(self.millis_since_epoch.unsigned_abs())
            )
        }

        #[cfg(feature = "time_exts")]
        {
            let date_time: Result<time::OffsetDateTime, _> = (*self).try_into();
            match date_time {
                Ok(date_time) => write!(f, "{}", date_time),
                Err(e) => write!(f, "{:?}", e),
            }
        }
    }
}

impl LocalClockTime {
    /// Return the number of millis since the Unix Epoch (in UTC).
    ///
    /// Also see: [`std::time::UNIX_EPOCH`]
    pub fn as_millis_since_unix_epoch(&self) -> i64 {
        self.millis_since_epoch
    }

    /// Create a new [`LocalClockTime`] containing the time some number of
    /// millis offset from the Unix Epoch (in UTC).
    ///
    /// Also see: [`std::time::UNIX_EPOCH`]
    pub fn from_millis_since_unix_epoch(millis: i64) -> Self {
        Self {
            millis_since_epoch: millis,
        }
    }
}

impl std::ops::Sub for LocalClockDelta {
    type Output = LocalClockDelta;

    fn sub(self, rhs: Self) -> Self::Output {
        LocalClockDelta {
            millis: self.millis - rhs.millis,
        }
    }
}

impl std::ops::Add for LocalClockDelta {
    type Output = LocalClockDelta;

    fn add(self, rhs: Self) -> Self::Output {
        LocalClockDelta {
            millis: self.millis + rhs.millis,
        }
    }
}

impl std::ops::Sub for LocalClockTime {
    type Output = LocalClockDelta;

    fn sub(self, rhs: Self) -> Self::Output {
        LocalClockDelta::from_millis(self.millis_since_epoch - rhs.millis_since_epoch)
    }
}

impl std::ops::Sub<LocalClockDelta> for LocalClockTime {
    type Output = LocalClockTime;

    fn sub(self, rhs: LocalClockDelta) -> Self::Output {
        LocalClockTime {
            millis_since_epoch: self.millis_since_epoch - rhs.as_millis(),
        }
    }
}

impl std::ops::Add<LocalClockDelta> for LocalClockTime {
    type Output = LocalClockTime;

    fn add(self, rhs: LocalClockDelta) -> Self::Output {
        LocalClockTime {
            millis_since_epoch: self.millis_since_epoch + rhs.as_millis(),
        }
    }
}

// only allow one-way conversion, as std::time::Duration doesn't support
// negative time
impl From<std::time::Duration> for LocalClockDelta {
    fn from(d: std::time::Duration) -> Self {
        LocalClockDelta::from_millis(d.as_millis().try_into().unwrap())
    }
}

impl From<std::time::SystemTime> for LocalClockTime {
    fn from(sys_time: std::time::SystemTime) -> Self {
        let millis_since_epoch = if std::time::UNIX_EPOCH < sys_time {
            let since_epoch = sys_time.duration_since(std::time::UNIX_EPOCH).unwrap();
            let millis: i64 = since_epoch.as_millis().try_into().unwrap();
            millis
        } else {
            let since_epoch = std::time::UNIX_EPOCH.duration_since(sys_time).unwrap();
            let millis: i64 = since_epoch.as_millis().try_into().unwrap();
            -millis
        };

        LocalClockTime::from_millis_since_unix_epoch(millis_since_epoch)
    }
}

impl From<LocalClockTime> for std::time::SystemTime {
    fn from(clock_time: LocalClockTime) -> Self {
        let millis_since_epoch = clock_time.as_millis_since_unix_epoch();
        if millis_since_epoch.is_negative() {
            let before_epoch = std::time::Duration::from_millis((-millis_since_epoch) as u64);
            std::time::UNIX_EPOCH.checked_sub(before_epoch).unwrap()
        } else {
            let after_epoch = std::time::Duration::from_millis(millis_since_epoch as u64);
            std::time::UNIX_EPOCH.checked_add(after_epoch).unwrap()
        }
    }
}

/// Indicates that an overflow error occurred during conversion.
#[derive(Debug)]
pub struct OverflowError;

#[cfg(feature = "time_exts")]
mod time_ext {
    use super::LocalClockDelta;
    use super::LocalClockTime;

    impl From<time::OffsetDateTime> for LocalClockTime {
        fn from(date_time: time::OffsetDateTime) -> LocalClockTime {
            let since_epoch = date_time - time::OffsetDateTime::UNIX_EPOCH;
            LocalClockTime::from_millis_since_unix_epoch(
                since_epoch.whole_milliseconds().try_into().unwrap(),
            )
        }
    }

    impl From<time::Duration> for LocalClockDelta {
        fn from(time_duration: time::Duration) -> LocalClockDelta {
            LocalClockDelta::from_millis(time_duration.whole_milliseconds().try_into().unwrap())
        }
    }

    impl From<LocalClockDelta> for time::Duration {
        fn from(clock_duration: LocalClockDelta) -> time::Duration {
            time::Duration::milliseconds(clock_duration.as_millis())
        }
    }

    impl TryFrom<LocalClockTime> for time::OffsetDateTime {
        type Error = super::OverflowError;

        fn try_from(clock_time: LocalClockTime) -> Result<time::OffsetDateTime, Self::Error> {
            let duration = time::Duration::milliseconds(clock_time.as_millis_since_unix_epoch());
            time::OffsetDateTime::UNIX_EPOCH
                .checked_add(duration)
                .ok_or(super::OverflowError)
        }
    }
}

/// Defines a trait that combines LocalClock and Inspect
#[cfg(feature = "inspect")]
mod inspect_ext {
    use super::*;
    use inspect::Inspect;

    /// Extends [`LocalClock`] with a bound on [`Inspect`]
    pub trait InspectableLocalClock: LocalClock + Inspect {}
    impl<T: LocalClock + Inspect> InspectableLocalClock for T {}
}