Skip to main content

saturating_time/
internal.rs

1//! Internal parts used for sealing.
2//!
3//! This module primarily consists of the internal [`SaturatingTime`] trait, an
4//! unstable abstraction used internally to implement the main logic behind
5//! this.
6//!
7//! Normal users should not be using this.
8
9use std::{
10    cmp,
11    sync::LazyLock,
12    time::{Duration, Instant, SystemTime},
13};
14
15/// The maximum value of [`SystemTime`] for this platform.
16static MAX_SYSTEM_TIME: LazyLock<SystemTime> = LazyLock::new(find_max);
17
18/// The minimum value of [`SystemTime`] for this platform.
19static MIN_SYSTEM_TIME: LazyLock<SystemTime> = LazyLock::new(find_min);
20
21/// The maximum value of [`Instant`] for this platform.
22static MAX_INSTANT: LazyLock<Instant> = LazyLock::new(find_max);
23
24/// The minimum value of [`Instant`] for this platform.
25static MIN_INSTANT: LazyLock<Instant> = LazyLock::new(find_min);
26
27/// An internal trait implementing the actual magic behind this.
28pub trait SaturatingTime: Sized + Copy {
29    /// Anchor method to obtain an instance of this type.
30    fn anchor() -> Self;
31
32    /// Returns the maximum value of this type.
33    fn max_value() -> Self;
34
35    /// Returns the minimum value of this type.
36    fn min_value() -> Self;
37
38    /// Performs a checked addition on this type.
39    fn checked_add(&self, duration: Duration) -> Option<Self>;
40
41    /// Performs a checked subtraction on this type.
42    fn checked_sub(&self, duration: Duration) -> Option<Self>;
43
44    /// Performs a checked time delta on this type.
45    fn checked_duration_since(&self, earlier: Self) -> Option<Duration>;
46}
47
48impl SaturatingTime for SystemTime {
49    fn anchor() -> Self {
50        Self::UNIX_EPOCH
51    }
52
53    fn max_value() -> Self {
54        *MAX_SYSTEM_TIME
55    }
56
57    fn min_value() -> Self {
58        *MIN_SYSTEM_TIME
59    }
60
61    fn checked_add(&self, duration: Duration) -> Option<Self> {
62        Self::checked_add(self, duration)
63    }
64
65    fn checked_sub(&self, duration: Duration) -> Option<Self> {
66        Self::checked_sub(self, duration)
67    }
68
69    fn checked_duration_since(&self, earlier: Self) -> Option<Duration> {
70        Self::duration_since(self, earlier).ok()
71    }
72}
73
74impl SaturatingTime for Instant {
75    fn anchor() -> Self {
76        use web_time_compat::InstantExt;
77        Self::get()
78    }
79
80    fn max_value() -> Self {
81        *MAX_INSTANT
82    }
83
84    fn min_value() -> Self {
85        *MIN_INSTANT
86    }
87
88    fn checked_add(&self, duration: Duration) -> Option<Self> {
89        Self::checked_add(self, duration)
90    }
91
92    fn checked_sub(&self, duration: Duration) -> Option<Self> {
93        Self::checked_sub(self, duration)
94    }
95
96    /// DO NOT USE!
97    ///
98    /// Instead, override the top-level provided implementation with the already
99    /// existing [`Instant::saturating_duration_since()`].
100    fn checked_duration_since(&self, _earlier: Self) -> Option<Duration> {
101        unreachable!()
102    }
103}
104
105/// Finds the value for [`SaturatingTime::max_value()`].
106fn find_max<T: SaturatingTime>() -> T {
107    find_limit(T::checked_add)
108}
109
110/// Finds the value for [`SaturatingTime::min_value()`].
111fn find_min<T: SaturatingTime>() -> T {
112    find_limit(T::checked_sub)
113}
114
115/// Internal algorithm of [`find_max()`] and [`find_min()`].
116///
117/// It works by performing `f` with a very large [`Duration`] onto
118/// [`SaturatingTime::anchor()`] until this call returns [`None`], in which case
119/// this [`Duration`] gets halved.  This process is repeated until `f` returns
120/// [`None`] and the [`Duration`] has reached 1ns.
121///
122/// # Algorithm
123///
124/// 1. Set `step` to `INITIAL_STEP` and `res` to [`SaturatingTime::anchor()`].
125/// 2. Call `f(&res, step)`.
126///     1. If [`Some`], set `res` to the returned value and continue.
127///     2. If [`None`] and `step == 1ns`, return `res`.
128///     3. Else, set `step` to `MAX{1ns, step / 2}` and continue.
129fn find_limit<T, F>(f: F) -> T
130where
131    T: SaturatingTime,
132    F: Fn(&T, Duration) -> Option<T>,
133{
134    const INITIAL_STEP: Duration = Duration::new(1_000_000_000_000_000_000, 0);
135    const ONE_NS: Duration = Duration::new(0, 1);
136
137    // (1) Set step to INITIAL_STEP and res to T::anchor().
138    let mut step = INITIAL_STEP;
139    let mut res = T::anchor();
140
141    loop {
142        // (2) Call f().
143        let next = f(&res, step);
144        match next {
145            Some(st) => {
146                // (2.1) If Some, set res to the returned value and continue.
147                res = st;
148            }
149            None => {
150                if step == ONE_NS {
151                    // (2.2) If None and step == 1ns, return res.
152                    return res;
153                } else {
154                    // (2.3) Else, set step to MAX{1ns, step / 2}.
155                    step = cmp::max(ONE_NS, step / 2);
156                }
157            }
158        }
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    // @@ begin test lint list maintained by maint/add_warning @@
165    #![allow(clippy::bool_assert_comparison)]
166    #![allow(clippy::clone_on_copy)]
167    #![allow(clippy::dbg_macro)]
168    #![allow(clippy::mixed_attributes_style)]
169    #![allow(clippy::print_stderr)]
170    #![allow(clippy::print_stdout)]
171    #![allow(clippy::single_char_pattern)]
172    #![allow(clippy::unwrap_used)]
173    #![allow(clippy::unchecked_time_subtraction)]
174    #![allow(clippy::useless_vec)]
175    #![allow(clippy::needless_pass_by_value)]
176    #![allow(clippy::string_slice)] // See arti#2571
177    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
178
179    use std::{
180        fmt::Debug,
181        ops::{Add, Sub},
182    };
183
184    use super::*;
185
186    /// Checks whether the minimum and maximum values are correct.
187    fn min_max<T>()
188    where
189        T: SaturatingTime
190            + PartialEq
191            + Debug
192            + Add<Duration, Output = T>
193            + Sub<Duration, Output = T>,
194    {
195        assert_eq!(
196            T::max_value().checked_add(Duration::ZERO),
197            Some(T::max_value())
198        );
199        assert_eq!(T::max_value().checked_add(Duration::new(0, 1)), None);
200        assert_eq!(
201            T::max_value().checked_sub(Duration::ZERO),
202            Some(T::max_value())
203        );
204        assert_eq!(
205            T::max_value().checked_sub(Duration::new(0, 1)),
206            Some(T::max_value() - Duration::new(0, 1))
207        );
208
209        assert_eq!(
210            T::min_value().checked_sub(Duration::ZERO),
211            Some(T::min_value())
212        );
213        assert_eq!(T::min_value().checked_sub(Duration::new(0, 1)), None);
214        assert_eq!(
215            T::min_value().checked_add(Duration::ZERO),
216            Some(T::min_value())
217        );
218        assert_eq!(
219            T::min_value().checked_add(Duration::new(0, 1)),
220            Some(T::min_value() + Duration::new(0, 1))
221        );
222    }
223
224    /// Verifies [`SystemTime::min_value()`] and [`SystemTime::max_value()`] are
225    /// correct.
226    #[test]
227    fn system_time_min_max() {
228        min_max::<SystemTime>();
229    }
230
231    /// Verifies [`Instant::min_value()`] and [`Instant::max_value()`] are
232    /// correct.
233    #[test]
234    fn instant_min_max() {
235        min_max::<Instant>();
236    }
237
238    /// Verifies [`SystemTime::min_value()`] and [`SystemTime::max_value()`] are
239    /// correct on Unix systems.
240    #[cfg(target_family = "unix")]
241    #[test]
242    fn system_time_min_max_unix() {
243        assert_eq!(
244            SystemTime::max_value(),
245            SystemTime::UNIX_EPOCH + Duration::new(i64::MAX as u64, 999_999_999)
246        );
247        assert_eq!(
248            SystemTime::min_value(),
249            SystemTime::UNIX_EPOCH - Duration::new(i64::MAX as u64 + 1, 0)
250        );
251    }
252
253    /// Verifies that [`Instant::min_value()`] and [`Instant::max_value()`] are
254    /// correct on Unix systems.
255    #[test]
256    #[cfg(target_family = "unix")]
257    fn instant_min_max_unix() {
258        // Using format is not nice but I cannot see a better way for now.
259        assert_eq!(
260            format!("{:?}", Instant::max_value()),
261            format!(
262                "Instant {{ tv_sec: {}, tv_nsec: {} }}",
263                i64::MAX,
264                999_999_999
265            )
266        );
267
268        assert_eq!(
269            format!("{:?}", Instant::min_value()),
270            format!("Instant {{ tv_sec: {}, tv_nsec: {} }}", i64::MIN, 0)
271        );
272    }
273}