Skip to main content

saturating_time/
lib.rs

1#![doc = include_str!("../README.md")]
2// @@ begin lint list maintained by maint/add_warning @@
3#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
4#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
5#![warn(missing_docs)]
6#![warn(noop_method_call)]
7#![warn(unreachable_pub)]
8#![warn(clippy::all)]
9#![deny(clippy::await_holding_lock)]
10#![deny(clippy::cargo_common_metadata)]
11#![deny(clippy::cast_lossless)]
12#![deny(clippy::checked_conversions)]
13#![allow(clippy::cognitive_complexity)] // See arti#2556
14#![deny(clippy::debug_assert_with_mut_call)]
15#![deny(clippy::exhaustive_enums)]
16#![deny(clippy::exhaustive_structs)]
17#![deny(clippy::expl_impl_clone_on_copy)]
18#![deny(clippy::fallible_impl_from)]
19#![deny(clippy::implicit_clone)]
20#![deny(clippy::large_stack_arrays)]
21#![warn(clippy::manual_ok_or)]
22#![deny(clippy::missing_docs_in_private_items)]
23#![warn(clippy::needless_borrow)]
24#![warn(clippy::needless_pass_by_value)]
25#![warn(clippy::option_option)]
26#![deny(clippy::print_stderr)]
27#![deny(clippy::print_stdout)]
28#![warn(clippy::rc_buffer)]
29#![deny(clippy::ref_option_ref)]
30#![warn(clippy::semicolon_if_nothing_returned)]
31#![warn(clippy::trait_duplication_in_bounds)]
32#![deny(clippy::unchecked_time_subtraction)]
33#![deny(clippy::unnecessary_wraps)]
34#![warn(clippy::unseparated_literal_suffix)]
35#![deny(clippy::unwrap_used)]
36#![deny(clippy::mod_module_files)]
37#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
38#![allow(clippy::uninlined_format_args)]
39#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
40#![allow(clippy::result_large_err)] // temporary workaround for arti#587
41#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
42#![allow(clippy::needless_lifetimes)] // See arti#1765
43#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
44#![allow(clippy::collapsible_if)] // See arti#2342
45#![deny(clippy::unused_async)]
46#![deny(clippy::string_slice)] // See arti#2571
47//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
48#![allow(unexpected_cfgs)]
49#![forbid(unsafe_code)]
50#![cfg_attr(
51    saturating_time_nightly,
52    feature(time_systemtime_limits, time_saturating_systemtime)
53)]
54
55use std::time::{Duration, Instant, SystemTime};
56
57mod internal;
58
59/// The core trait of this crait, [`SaturatingTime`].
60///
61/// This trait provides methods for performing saturating arithmetic on those
62/// types in [`std::time`] that not already come with such a functionality,
63/// such as [`SystemTime`] or [`Instant`].
64///
65/// The trait itself is not implementable from the outside, because it is sealed
66/// by an internal trait.
67///
68/// See the methods or the top-level documentation for concrete code examples.
69pub trait SaturatingTime: internal::SaturatingTime {
70    /// Returns the maximum value for this type on the current platform.
71    ///
72    /// This limit is highly platform specific.  It differs heavily between
73    /// Unix, Windows, and other operating systems.
74    ///
75    /// The limit itself is calculated dynamically during runtime with a correct
76    /// algorithm.  Afterwards, it gets stored in a lazy static value, meaning
77    /// that only the first call to it will be slightly more expensive, whereas
78    /// all latter calls will result in an immediate return of the value.
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// use std::time::{Duration, SystemTime};
84    /// use saturating_time::SaturatingTime;
85    ///
86    /// let max = SystemTime::max_value();
87    ///
88    /// // Adding zero to the maximum value will change nothing.
89    /// assert!(max.checked_add(Duration::ZERO).is_some());
90    ///
91    /// // Adding 1ns to the maximum value will fail.
92    /// assert!(max.checked_add(Duration::new(0, 1)).is_none());
93    ///
94    /// // Subtracting 1ns from the maximum value will work of course.
95    /// assert!(max.checked_sub(Duration::new(0, 1)).is_some());
96    /// ```
97    fn max_value() -> Self {
98        internal::SaturatingTime::max_value()
99    }
100
101    /// Returns the minimum value for this type on the current platform.
102    ///
103    /// This limit is highly platform specific.  It differs heavily between
104    /// Unix, Windows, and other operating systems.
105    ///
106    /// The limit itself is calculated dynamically during runtime with a correct
107    /// algorithm.  Afterwards, it gets stored in a lazy static value, meaning
108    /// that only the first call to it will be slightly more expensive, whereas
109    /// all latter calls will result in an immediate return of the value.
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use std::time::{Duration, SystemTime};
115    /// use saturating_time::SaturatingTime;
116    ///
117    /// let min = SystemTime::min_value();
118    ///
119    /// // Subtracting a zero from the minimum value will change nothing.
120    /// assert!(min.checked_sub(Duration::ZERO).is_some());
121    ///
122    /// // Subtracting 1ns from the minimum value will fail.
123    /// assert!(min.checked_sub(Duration::new(0, 1)).is_none());
124    ///
125    /// // Adding 1ns to the minimum value will work of course.
126    /// assert!(min.checked_add(Duration::new(0, 1)).is_some());
127    /// ```
128    fn min_value() -> Self {
129        internal::SaturatingTime::min_value()
130    }
131
132    /// Performs a saturating addition of a [`Duration`].
133    ///
134    /// The resulting value will saturate to [`SaturatingTime::max_value()`] in
135    /// the case the addition would have caused an overflow of value.
136    ///
137    /// # Examples
138    ///
139    /// ```
140    /// use std::time::{Duration, SystemTime};
141    /// use saturating_time::SaturatingTime;
142    ///
143    /// let max = SystemTime::max_value();
144    ///
145    /// // Adding zero will change nothing.
146    /// assert_eq!(max.saturating_add(Duration::ZERO), max);
147    ///
148    /// // Adding 1ns would overflow so we saturate to the maximum.
149    /// assert_eq!(max.saturating_add(Duration::new(0, 1)), max);
150    /// ```
151    fn saturating_add(self, duration: Duration) -> Self {
152        self.checked_add(duration)
153            .unwrap_or(SaturatingTime::max_value())
154    }
155
156    /// Performs a saturating subtraction of a [`Duration`].
157    ///
158    /// The resulting value will saturate to [`SaturatingTime::min_value()`] in
159    /// the case the subtraction would have caused an overflow of value.
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use std::time::{Duration, SystemTime};
165    /// use saturating_time::SaturatingTime;
166    ///
167    /// let min = SystemTime::min_value();
168    ///
169    /// // Subtracting zero will change nothing.
170    /// assert_eq!(min.saturating_sub(Duration::ZERO), min);
171    ///
172    /// // Subtracting 1ns would overflow so we saturate to the minimum.
173    /// assert_eq!(min.saturating_sub(Duration::new(0, 1)), min);
174    /// ```
175    fn saturating_sub(self, duration: Duration) -> Self {
176        self.checked_sub(duration)
177            .unwrap_or(SaturatingTime::min_value())
178    }
179
180    /// Performs a saturating time difference calculation between two points.
181    ///
182    /// The resulting value will saturate to [`Duration::ZERO`] in the case that
183    /// the `earlier` point in time is actually not earlier, thereby resulting
184    /// in a negative difference.
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// use std::time::{Duration, SystemTime};
190    /// use saturating_time::SaturatingTime;
191    ///
192    /// let epoch = SystemTime::UNIX_EPOCH;
193    /// let now = SystemTime::now();
194    /// let min = SystemTime::min_value();
195    ///
196    /// assert!(now.saturating_duration_since(epoch).as_secs() > 0);
197    /// assert!(epoch.saturating_duration_since(epoch) == Duration::ZERO);
198    /// assert!(min.saturating_duration_since(epoch) == Duration::ZERO);
199    /// ```
200    fn saturating_duration_since(&self, earlier: Self) -> Duration {
201        self.checked_duration_since(earlier)
202            .unwrap_or(Duration::ZERO)
203    }
204}
205
206// Use nightly implementation if compiled with the nightly feature.
207#[cfg(saturating_time_nightly)]
208impl SaturatingTime for SystemTime {
209    fn max_value() -> Self {
210        Self::MAX
211    }
212
213    fn min_value() -> Self {
214        Self::MIN
215    }
216
217    fn saturating_add(self, duration: Duration) -> Self {
218        Self::saturating_add(&self, duration)
219    }
220
221    fn saturating_sub(self, duration: Duration) -> Self {
222        Self::saturating_sub(&self, duration)
223    }
224
225    fn saturating_duration_since(&self, earlier: Self) -> Duration {
226        Self::saturating_duration_since(&self, earlier)
227    }
228}
229
230// Otherwise, use the default one.
231#[cfg(not(saturating_time_nightly))]
232impl SaturatingTime for SystemTime {}
233
234impl SaturatingTime for Instant {
235    // Override to use the provided implementation from the standard library.
236    fn saturating_duration_since(&self, earlier: Self) -> Duration {
237        Self::saturating_duration_since(self, earlier)
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    // @@ begin test lint list maintained by maint/add_warning @@
244    #![allow(clippy::bool_assert_comparison)]
245    #![allow(clippy::clone_on_copy)]
246    #![allow(clippy::dbg_macro)]
247    #![allow(clippy::mixed_attributes_style)]
248    #![allow(clippy::print_stderr)]
249    #![allow(clippy::print_stdout)]
250    #![allow(clippy::single_char_pattern)]
251    #![allow(clippy::unwrap_used)]
252    #![allow(clippy::unchecked_time_subtraction)]
253    #![allow(clippy::useless_vec)]
254    #![allow(clippy::needless_pass_by_value)]
255    #![allow(clippy::string_slice)] // See arti#2571
256    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
257
258    use super::*;
259    use crate::internal;
260    use std::{
261        fmt::Debug,
262        ops::{Add, Sub},
263        time::{Instant, SystemTime},
264    };
265
266    /// Verifies the maximum and minimum values of [`SaturatingTime`] equal
267    /// their pedant in [`internal::SaturatingTime`].
268    fn min_max<T: SaturatingTime + PartialEq + Debug>() {
269        assert_eq!(
270            <T as SaturatingTime>::max_value(),
271            <T as internal::SaturatingTime>::max_value()
272        );
273        assert_eq!(
274            <T as SaturatingTime>::min_value(),
275            <T as internal::SaturatingTime>::min_value()
276        );
277    }
278
279    /// Verifies the saturating arithmetic for [`SaturatingTime`].
280    fn saturating_add_sub<
281        T: SaturatingTime + PartialEq + Debug + Add<Duration, Output = T> + Sub<Duration, Output = T>,
282    >() {
283        let max = <T as SaturatingTime>::max_value();
284        assert_eq!(max.saturating_add(Duration::ZERO), max);
285        assert_eq!(max.saturating_add(Duration::new(0, 1)), max);
286        assert_eq!(max.saturating_sub(Duration::ZERO), max);
287        assert_eq!(
288            max.saturating_sub(Duration::new(0, 1)),
289            max - Duration::new(0, 1)
290        );
291
292        let min = <T as SaturatingTime>::min_value();
293        assert_eq!(min.saturating_sub(Duration::ZERO), min);
294        assert_eq!(min.saturating_sub(Duration::new(0, 1)), min);
295        assert_eq!(min.saturating_add(Duration::ZERO), min);
296        assert_eq!(
297            min.saturating_add(Duration::new(0, 1)),
298            min + Duration::new(0, 1)
299        );
300    }
301
302    /// Verifies whether the saturating logic behind [`Duration`] types work.
303    fn saturating_duration<T: SaturatingTime + PartialEq + Debug>() {
304        // The duration from the same anchor should always be zero.
305        let anchor = T::anchor();
306        assert_eq!(anchor.saturating_duration_since(anchor), Duration::ZERO);
307
308        // Try with a later anchor.
309        let later_anchor = anchor.checked_add(Duration::from_secs(1)).unwrap();
310        assert!(later_anchor.saturating_duration_since(anchor) == Duration::from_secs(1));
311        assert_eq!(
312            anchor.saturating_duration_since(later_anchor),
313            Duration::ZERO
314        );
315
316        // Try with min and max.
317        let max = <T as SaturatingTime>::max_value();
318        let min = <T as SaturatingTime>::min_value();
319
320        // This first assertion might not be so portable, maybe remove it if
321        // this becomes a problem.
322        assert_eq!(max.saturating_duration_since(min), Duration::MAX);
323        assert_eq!(min.saturating_duration_since(max), Duration::ZERO);
324    }
325
326    /// Calls [`min_max()`] using [`SystemTime`].
327    #[test]
328    fn system_time_min_max() {
329        min_max::<SystemTime>();
330    }
331
332    /// Calls [`min_max()`] using [`Instant`].
333    #[test]
334    fn instant_min_max() {
335        min_max::<Instant>();
336    }
337
338    /// Calls [`saturating_add_sub()`] and [`saturating_duration()`] using [`SystemTime`].
339    #[test]
340    fn system_time_saturating() {
341        saturating_add_sub::<SystemTime>();
342        saturating_duration::<SystemTime>();
343    }
344
345    /// Calls [`saturating_add_sub()`] and [`saturating_duration()`] using [`Instant`].
346    #[test]
347    fn instant_saturating() {
348        saturating_add_sub::<Instant>();
349        saturating_duration::<Instant>();
350    }
351}