veecle_osal_api/time/
duration.rs

1//! This module implements a [`Duration`] with microsecond precision.
2
3use core::fmt;
4use core::num::TryFromIntError;
5use core::ops::{Add, Div, Mul, Sub};
6
7/// Duration represents a span of time.
8///
9/// Negative durations are not supported. [`Duration`] is not meant to be used
10/// for math operations.
11#[derive(
12    Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
13)]
14pub struct Duration {
15    micros: u64,
16}
17
18impl Duration {
19    /// The largest value that can be represented by the `Duration` type.
20    ///
21    /// # Examples
22    ///
23    /// ```
24    /// use veecle_osal_api::time::Duration;
25    ///
26    /// assert_eq!(Duration::MAX, Duration::from_micros(u64::MAX));
27    /// ```
28    pub const MAX: Duration = Duration { micros: u64::MAX };
29
30    /// A duration of zero time.
31    ///
32    /// # Examples
33    ///
34    /// ```
35    /// use veecle_osal_api::time::Duration;
36    ///
37    /// assert_eq!(Duration::ZERO, Duration::from_micros(0));
38    /// ```
39    pub const ZERO: Duration = Duration { micros: 0 };
40
41    /// Factor of microseconds per second.
42    const MICROS_PER_SECOND: u64 = 1_000_000;
43    /// Factor of milliseconds per second.
44    const MILLIS_PER_SECOND: u64 = 1_000;
45
46    /// Creates a duration from the specified number of seconds.
47    ///
48    /// # Examples
49    ///
50    /// ```
51    /// use veecle_osal_api::time::Duration;
52    ///
53    /// assert_eq!(Duration::from_secs(1), Duration::from_millis(1000));
54    /// ```
55    pub const fn from_secs(secs: u64) -> Duration {
56        Duration {
57            micros: secs * Self::MICROS_PER_SECOND,
58        }
59    }
60
61    /// Creates a duration from the specified number of milliseconds.
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// use veecle_osal_api::time::Duration;
67    ///
68    /// assert_eq!(Duration::from_secs(1), Duration::from_millis(1000));
69    /// ```
70    pub const fn from_millis(millis: u64) -> Duration {
71        Duration {
72            micros: millis * Self::MILLIS_PER_SECOND,
73        }
74    }
75
76    /// Creates a duration from the specified number of microseconds.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use veecle_osal_api::time::Duration;
82    ///
83    /// assert_eq!(Duration::from_secs(1), Duration::from_micros(1000000));
84    /// ```
85    pub const fn from_micros(micros: u64) -> Duration {
86        Duration { micros }
87    }
88
89    /// Returns the total amount of seconds, rounded down.
90    ///
91    /// # Examples
92    ///
93    /// ```
94    /// use veecle_osal_api::time::Duration;
95    ///
96    /// assert_eq!(Duration::from_millis(1980).as_secs(), 1);
97    /// ```
98    pub const fn as_secs(&self) -> u64 {
99        self.micros / Self::MICROS_PER_SECOND
100    }
101
102    /// Returns the total amount of milliseconds, rounded down.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use veecle_osal_api::time::Duration;
108    ///
109    /// assert_eq!(Duration::from_millis(1980).as_millis(), 1980);
110    /// ```
111    pub const fn as_millis(&self) -> u64 {
112        self.micros / Self::MILLIS_PER_SECOND
113    }
114
115    /// Returns the total amount of microseconds.
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use veecle_osal_api::time::Duration;
121    ///
122    /// assert_eq!(Duration::from_millis(1980).as_micros(), 1980000);
123    /// ```
124    pub const fn as_micros(&self) -> u64 {
125        self.micros
126    }
127
128    /// Adds one Duration to another, returning a new Duration or None in the event of an overflow.
129    ///
130    /// # Examples
131    ///
132    /// ```
133    /// use veecle_osal_api::time::Duration;
134    ///
135    /// assert_eq!(
136    ///     Duration::from_secs(1).checked_add(Duration::from_secs(1)),
137    ///     Some(Duration::from_secs(2))
138    /// );
139    /// assert_eq!(Duration::MAX.checked_add(Duration::from_secs(1)), None);
140    /// ```
141    pub fn checked_add(self, rhs: Duration) -> Option<Duration> {
142        self.micros
143            .checked_add(rhs.micros)
144            .map(|micros| Duration { micros })
145    }
146
147    /// Subtracts one Duration from another, returning a new Duration or None in the event of an underflow.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use veecle_osal_api::time::Duration;
153    ///
154    /// assert_eq!(
155    ///     Duration::from_secs(2).checked_sub(Duration::from_secs(1)),
156    ///     Some(Duration::from_secs(1))
157    /// );
158    /// assert_eq!(Duration::from_secs(1).checked_sub(Duration::from_secs(2)), None);
159    /// ```
160    pub fn checked_sub(self, rhs: Duration) -> Option<Duration> {
161        self.micros
162            .checked_sub(rhs.micros)
163            .map(|micros| Duration { micros })
164    }
165
166    /// Multiplies one Duration by a scalar `u32`, returning a new Duration or None in the event of an overflow.
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use veecle_osal_api::time::Duration;
172    ///
173    /// assert_eq!(Duration::from_secs(1).checked_mul(2), Some(Duration::from_secs(2)));
174    /// assert_eq!(Duration::MAX.checked_mul(2), None);
175    /// ```
176    pub fn checked_mul(self, rhs: u32) -> Option<Duration> {
177        self.micros
178            .checked_mul(rhs as _)
179            .map(|micros| Duration { micros })
180    }
181
182    /// Divides one Duration by a scalar `u32`, returning a new Duration or None if `rhs == 0`.
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use veecle_osal_api::time::Duration;
188    ///
189    /// assert_eq!(Duration::from_secs(1).checked_div(2), Some(Duration::from_millis(500)));
190    /// assert_eq!(Duration::from_secs(1).checked_div(0), None);
191    /// ```
192    pub fn checked_div(self, rhs: u32) -> Option<Duration> {
193        self.micros
194            .checked_div(rhs as _)
195            .map(|micros| Duration { micros })
196    }
197
198    /// Returns the absolute difference between self and rhs.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use veecle_osal_api::time::Duration;
204    ///
205    /// assert_eq!(Duration::from_secs(1).abs_diff(Duration::from_secs(2)), Duration::from_secs(1));
206    /// assert_eq!(Duration::from_secs(2).abs_diff(Duration::from_secs(1)), Duration::from_secs(1));
207    /// ```
208    pub fn abs_diff(self, rhs: Self) -> Duration {
209        Self {
210            micros: self.micros.abs_diff(rhs.micros),
211        }
212    }
213
214    /// Returns a duration of approximately 50 years.
215    ///
216    /// No leap-seconds/days have been taken into account.
217    ///
218    /// Can be used in place of [MAX][Self::MAX] to avoid overflows.
219    pub(crate) fn max_no_overflow_alias() -> Self {
220        // seconds * minutes * hours * days * weeks * years
221        Self::from_secs(60 * 60 * 24 * 7 * 52 * 50)
222    }
223}
224
225impl Add for Duration {
226    type Output = Self;
227
228    /// # Panics
229    ///
230    /// This function may panic if the resulting duration overflows. See [`Duration::checked_add`] for a version
231    /// without panic.
232    ///
233    /// # Examples
234    ///
235    /// ```
236    /// use veecle_osal_api::time::Duration;
237    ///
238    /// assert_eq!(Duration::from_secs(1) + Duration::from_secs(1), Duration::from_secs(2));
239    /// ```
240    ///
241    /// ```should_panic
242    /// use veecle_osal_api::time::Duration;
243    ///
244    /// let _ = Duration::MAX + Duration::from_secs(1);
245    /// ```
246    fn add(self, rhs: Self) -> Self::Output {
247        let Some(result) = self.checked_add(rhs) else {
248            panic!("overflow when adding two durations");
249        };
250
251        result
252    }
253}
254
255impl Sub for Duration {
256    type Output = Self;
257
258    /// # Panics
259    ///
260    /// This function may panic if the resulting duration underflows. See [`Duration::checked_sub`] for a
261    /// version without panic.
262    ///
263    /// # Examples
264    ///
265    /// ```
266    /// use veecle_osal_api::time::Duration;
267    ///
268    /// assert_eq!(Duration::from_secs(2) - Duration::from_secs(1), Duration::from_secs(1));
269    /// ```
270    ///
271    /// ```should_panic
272    /// use veecle_osal_api::time::Duration;
273    ///
274    /// let _ = Duration::from_secs(1) - Duration::from_secs(2);
275    /// ```
276    fn sub(self, rhs: Self) -> Self::Output {
277        let Some(result) = self.checked_sub(rhs) else {
278            panic!("underflow when subtracting two durations");
279        };
280
281        result
282    }
283}
284
285impl Mul<u32> for Duration {
286    type Output = Self;
287
288    /// # Panics
289    ///
290    /// This function may panic if the resulting duration overflows. See [`Duration::checked_mul`] for a version
291    /// without panic.
292    ///
293    /// # Examples
294    ///
295    /// ```
296    /// use veecle_osal_api::time::Duration;
297    ///
298    /// assert_eq!(Duration::from_secs(1) * 2, Duration::from_secs(2));
299    /// ```
300    ///
301    /// ```should_panic
302    /// use veecle_osal_api::time::Duration;
303    ///
304    /// let _ = Duration::MAX * 2;
305    /// ```
306    fn mul(self, rhs: u32) -> Self::Output {
307        let Some(result) = self.checked_mul(rhs) else {
308            panic!("overflow when multiplying a duration by a scalar");
309        };
310
311        result
312    }
313}
314
315impl Mul<Duration> for u32 {
316    type Output = Duration;
317
318    /// # Panics
319    ///
320    /// This function may panic if the resulting duration overflows. See [`Duration::checked_mul`] for a version
321    /// without panic.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// use veecle_osal_api::time::Duration;
327    ///
328    /// assert_eq!(2 * Duration::from_secs(1), Duration::from_secs(2));
329    /// ```
330    ///
331    /// ```should_panic
332    /// use veecle_osal_api::time::Duration;
333    ///
334    /// let _ = 2 * Duration::MAX;
335    /// ```
336    fn mul(self, rhs: Duration) -> Self::Output {
337        rhs * self
338    }
339}
340
341impl Div<u32> for Duration {
342    type Output = Self;
343
344    /// # Panics
345    ///
346    /// This function may panic if the duration is divided by zero. See [`Duration::checked_div`] for a version
347    /// without panic.
348    ///
349    /// # Examples
350    ///
351    /// ```
352    /// use veecle_osal_api::time::Duration;
353    ///
354    /// assert_eq!(Duration::from_secs(1) / 2, Duration::from_millis(500));
355    /// ```
356    ///
357    /// ```should_panic
358    /// use veecle_osal_api::time::Duration;
359    ///
360    /// let _ = Duration::from_secs(1) / 0;
361    /// ```
362    fn div(self, rhs: u32) -> Self::Output {
363        let Some(result) = self.checked_div(rhs) else {
364            panic!("divided a duration by zero");
365        };
366
367        result
368    }
369}
370
371impl fmt::Debug for Duration {
372    /// # Examples
373    ///
374    /// ```
375    /// use veecle_osal_api::time::Duration;
376    ///
377    /// let duration = Duration::from_millis(1980);
378    /// assert_eq!(format!("{duration:?}"), "1s.980000us");
379    /// ```
380    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381        write!(
382            f,
383            "{}s.{}us",
384            self.as_secs(),
385            self.as_micros() % Self::MICROS_PER_SECOND
386        )
387    }
388}
389
390impl TryFrom<core::time::Duration> for Duration {
391    type Error = TryFromIntError;
392
393    /// # Examples
394    ///
395    /// ```
396    /// use veecle_osal_api::time::Duration;
397    ///
398    /// assert_eq!(Duration::try_from(core::time::Duration::from_secs(1)), Ok(Duration::from_secs(1)));
399    /// ```
400    fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
401        value.as_micros().try_into().map(Self::from_micros)
402    }
403}
404
405impl From<Duration> for core::time::Duration {
406    /// # Examples
407    ///
408    /// ```
409    /// use veecle_osal_api::time::Duration;
410    ///
411    /// assert_eq!(
412    ///     core::time::Duration::from(Duration::from_secs(1)),
413    ///     core::time::Duration::from_secs(1)
414    /// );
415    /// ```
416    fn from(value: Duration) -> Self {
417        Self::from_micros(value.as_micros())
418    }
419}