mz_ore/
time.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Timing related extensions.
17
18use std::time::Duration;
19
20use num::Zero;
21
22/// Generic Error type returned from methods on [`DurationExt`].
23#[derive(Copy, Clone, Debug)]
24pub struct DurationError(#[allow(dead_code)] &'static str);
25
26/// Extensions for [`std::time::Duration`].
27pub trait DurationExt {
28    /// Creates a [`Duration`] from the specified number of seconds represented as an `i64`.
29    ///
30    /// Returns an error if the provided number of seconds is negative.
31    ///
32    /// ```
33    /// use std::time::Duration;
34    /// use mz_ore::time::DurationExt;
35    ///
36    /// // Negative numbers will fail.
37    /// assert!(Duration::try_from_secs_i64(-100).is_err());
38    ///
39    /// // i64 max works.
40    /// assert!(Duration::try_from_secs_i64(i64::MAX).is_ok());
41    /// ```
42    fn try_from_secs_i64(secs: i64) -> Result<Duration, DurationError>;
43
44    /// Saturating `Duration` multiplication. Computes `self * rhs`, saturating at the numeric
45    /// bounds instead of overflowing.
46    ///
47    /// ```
48    /// use std::time::Duration;
49    /// use mz_ore::time::DurationExt;
50    ///
51    /// let one = Duration::from_secs(1);
52    /// assert_eq!(one.saturating_mul_f64(f64::INFINITY), Duration::from_secs(u64::MAX));
53    ///
54    /// assert_eq!(one.saturating_mul_f64(f64::NEG_INFINITY), Duration::from_secs(0));
55    /// assert_eq!(one.saturating_mul_f64(f64::NAN), Duration::from_secs(0));
56    /// assert_eq!(one.saturating_mul_f64(-0.0), Duration::from_secs(0));
57    /// assert_eq!(one.saturating_mul_f64(0.0), Duration::from_secs(0));
58    ///
59    /// assert_eq!(Duration::from_secs(20).saturating_mul_f64(0.1), Duration::from_secs(2));
60    /// ```
61    fn saturating_mul_f64(&self, rhs: f64) -> Duration;
62}
63
64impl DurationExt for Duration {
65    fn try_from_secs_i64(secs: i64) -> Result<Duration, DurationError> {
66        let secs: u64 = secs
67            .try_into()
68            .map_err(|_| DurationError("negative number of seconds"))?;
69        Ok(Duration::from_secs(secs))
70    }
71
72    fn saturating_mul_f64(&self, rhs: f64) -> Duration {
73        let x = self.as_secs_f64() * rhs;
74        let bound = if x.is_sign_negative() || x.is_nan() || x.is_zero() {
75            u64::MIN
76        } else {
77            u64::MAX
78        };
79        Duration::try_from_secs_f64(x).unwrap_or(Duration::from_secs(bound))
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::DurationExt;
86    use proptest::prelude::*;
87    use std::time::Duration;
88
89    const ONE: Duration = Duration::from_secs(1);
90
91    proptest! {
92        #[crate::test]
93        fn proptest_saturating_mul_f64(rhs: f64) {
94            // Saturating multiplication should never panic.
95            let _ = ONE.saturating_mul_f64(rhs);
96        }
97    }
98}