Skip to main content

mimir_core/
clock.rs

1//! `ClockTime` — UTC milliseconds-since-Unix-epoch newtype used for every
2//! Mimir clock. Implements the contract in `docs/concepts/temporal-model.md`
3//! § 9.1.
4
5use std::fmt;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8use thiserror::Error;
9
10/// Sentinel value reserved for "no invalidation" in the canonical form
11/// (see `docs/concepts/ir-canonical-form.md` § 3.1). This value is an
12/// encoding concern — the public API exposes absence via `Option<ClockTime>`.
13pub(crate) const NONE_SENTINEL: u64 = u64::MAX;
14
15/// A point in time, in milliseconds since the Unix epoch, UTC.
16///
17/// Honors `docs/concepts/temporal-model.md` § 9.1:
18///
19/// - Millisecond precision only in v1.
20/// - UTC exclusively — agent-provided times must be in UTC or the grammar
21///   rejects.
22/// - `u64` capacity reaches well past year 584,000,000.
23///
24/// # Examples
25///
26/// ```
27/// # #![allow(clippy::unwrap_used)]
28/// use mimir_core::ClockTime;
29///
30/// let t = ClockTime::try_from_millis(1_713_350_400_000).unwrap();
31/// assert_eq!(t.as_millis(), 1_713_350_400_000);
32/// ```
33#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub struct ClockTime(u64);
35
36/// Errors returned when constructing or manipulating a [`ClockTime`].
37#[derive(Debug, Error, PartialEq, Eq)]
38pub enum ClockTimeError {
39    /// The wall clock appears to predate the Unix epoch. Mimir assumes
40    /// positive epoch times; this error is returned only when the host
41    /// clock is badly misconfigured.
42    #[error("system clock before Unix epoch")]
43    BeforeEpoch,
44
45    /// The requested value collides with the canonical-form `None`
46    /// sentinel (`u64::MAX`). The Mimir API reserves that value for
47    /// "no invalidation" encoding.
48    #[error("reserved sentinel value {0} (u64::MAX)")]
49    ReservedSentinel(u64),
50}
51
52impl ClockTime {
53    /// Construct a [`ClockTime`] from raw milliseconds since the epoch.
54    ///
55    /// # Errors
56    ///
57    /// Returns [`ClockTimeError::ReservedSentinel`] if `millis == u64::MAX`,
58    /// because that value is reserved by the canonical form to encode
59    /// `Option::<ClockTime>::None` (per `ir-canonical-form.md` § 3.1).
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use mimir_core::{ClockTime, ClockTimeError};
65    ///
66    /// assert!(ClockTime::try_from_millis(0).is_ok());
67    /// assert_eq!(
68    ///     ClockTime::try_from_millis(u64::MAX),
69    ///     Err(ClockTimeError::ReservedSentinel(u64::MAX)),
70    /// );
71    /// ```
72    pub const fn try_from_millis(millis: u64) -> Result<Self, ClockTimeError> {
73        if millis == NONE_SENTINEL {
74            Err(ClockTimeError::ReservedSentinel(millis))
75        } else {
76            Ok(Self(millis))
77        }
78    }
79
80    /// Current wall-clock time as milliseconds since the Unix epoch.
81    ///
82    /// # Errors
83    ///
84    /// Returns [`ClockTimeError::BeforeEpoch`] if the host clock reports
85    /// a time before the Unix epoch (should not happen on sane hosts).
86    pub fn now() -> Result<Self, ClockTimeError> {
87        let millis = SystemTime::now()
88            .duration_since(UNIX_EPOCH)
89            .map_err(|_| ClockTimeError::BeforeEpoch)?
90            .as_millis();
91        // `as_millis` returns u128; values up to 2^64-1 ms cover year
92        // 584,000,000, so truncation cannot occur for any real clock.
93        let truncated = u64::try_from(millis).unwrap_or(NONE_SENTINEL - 1);
94        Self::try_from_millis(truncated)
95    }
96
97    /// The underlying millisecond count.
98    #[must_use]
99    pub const fn as_millis(self) -> u64 {
100        self.0
101    }
102}
103
104impl fmt::Display for ClockTime {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        write!(f, "{}ms", self.0)
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn sentinel_is_rejected() {
116        assert!(matches!(
117            ClockTime::try_from_millis(u64::MAX),
118            Err(ClockTimeError::ReservedSentinel(_))
119        ));
120    }
121
122    #[test]
123    fn ordering_is_numeric() {
124        let a = ClockTime::try_from_millis(100).expect("non-sentinel");
125        let b = ClockTime::try_from_millis(200).expect("non-sentinel");
126        assert!(a < b);
127    }
128
129    #[test]
130    fn now_is_close_to_epoch_plus_wallclock() {
131        let t = ClockTime::now().expect("wall clock sane");
132        // Any reasonable wall clock is after 2020-01-01 (1_577_836_800_000 ms).
133        assert!(t.as_millis() > 1_577_836_800_000);
134    }
135}