Skip to main content

embassy_time/
duration.rs

1use core::fmt;
2use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
3
4use super::{GCD_1K, GCD_1M, TICK_HZ};
5use crate::GCD_1G;
6
7#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
8#[cfg_attr(feature = "defmt", derive(defmt::Format))]
9/// Represents the difference between two [Instant](struct.Instant.html)s
10pub struct Duration {
11    pub(crate) ticks: u64,
12}
13
14impl Duration {
15    /// The smallest value that can be represented by the `Duration` type.
16    pub const MIN: Duration = Duration { ticks: u64::MIN };
17    /// The largest value that can be represented by the `Duration` type.
18    pub const MAX: Duration = Duration { ticks: u64::MAX };
19
20    /// Tick count of the `Duration`.
21    pub const fn as_ticks(&self) -> u64 {
22        self.ticks
23    }
24
25    /// Convert the `Duration` to seconds, rounding down.
26    pub const fn as_secs(&self) -> u64 {
27        self.ticks / TICK_HZ
28    }
29
30    /// Convert the `Duration` to milliseconds, rounding down.
31    pub const fn as_millis(&self) -> u64 {
32        self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K)
33    }
34
35    /// Convert the `Duration` to microseconds, rounding down.
36    pub const fn as_micros(&self) -> u64 {
37        self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M)
38    }
39
40    /// Convert the `Duration` to nanoseconds, rounding down.
41    pub const fn as_nanos(&self) -> u64 {
42        self.ticks * (1_000_000_000 / GCD_1G) / (TICK_HZ / GCD_1G)
43    }
44
45    /// Creates a duration from the specified number of clock ticks
46    pub const fn from_ticks(ticks: u64) -> Duration {
47        Duration { ticks }
48    }
49
50    /// Creates a duration from the specified number of seconds, rounding up.
51    pub const fn from_secs(secs: u64) -> Duration {
52        Duration { ticks: secs * TICK_HZ }
53    }
54
55    /// Creates a duration from the specified number of milliseconds, rounding up.
56    pub const fn from_millis(millis: u64) -> Duration {
57        Duration {
58            ticks: div_ceil(millis * (TICK_HZ / GCD_1K), 1000 / GCD_1K),
59        }
60    }
61
62    /// Creates a duration from the specified number of microseconds, rounding up.
63    /// NOTE: Delays this small may be inaccurate.
64    pub const fn from_micros(micros: u64) -> Duration {
65        Duration {
66            ticks: div_ceil(micros * (TICK_HZ / GCD_1M), 1_000_000 / GCD_1M),
67        }
68    }
69
70    /// Creates a duration from the specified number of nanoseconds, rounding up.
71    /// NOTE: Delays this small may be inaccurate.
72    pub const fn from_nanos(nanoseconds: u64) -> Duration {
73        Duration {
74            ticks: div_ceil(nanoseconds * (TICK_HZ / GCD_1G), 1_000_000_000 / GCD_1G),
75        }
76    }
77
78    /// Creates a duration from the specified number of seconds, rounding down.
79    pub const fn from_secs_floor(secs: u64) -> Duration {
80        Duration { ticks: secs * TICK_HZ }
81    }
82
83    /// Creates a duration from the specified number of milliseconds, rounding down.
84    pub const fn from_millis_floor(millis: u64) -> Duration {
85        Duration {
86            ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K),
87        }
88    }
89
90    /// Creates a duration from the specified number of microseconds, rounding down.
91    /// NOTE: Delays this small may be inaccurate.
92    pub const fn from_micros_floor(micros: u64) -> Duration {
93        Duration {
94            ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M),
95        }
96    }
97
98    /// Try to create a duration from the specified number of seconds, rounding up.
99    /// Fails if the number of seconds is too large.
100    pub const fn try_from_secs(secs: u64) -> Option<Duration> {
101        let Some(ticks) = secs.checked_mul(TICK_HZ) else {
102            return None;
103        };
104        Some(Duration { ticks })
105    }
106
107    /// Try to create a duration from the specified number of milliseconds, rounding up.
108    /// Fails if the number of milliseconds is too large.
109    pub const fn try_from_millis(millis: u64) -> Option<Duration> {
110        let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else {
111            return None;
112        };
113        Some(Duration {
114            ticks: div_ceil(value, 1000 / GCD_1K),
115        })
116    }
117
118    /// Try to create a duration from the specified number of microseconds, rounding up.
119    /// Fails if the number of microseconds is too large.
120    /// NOTE: Delays this small may be inaccurate.
121    pub const fn try_from_micros(micros: u64) -> Option<Duration> {
122        let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else {
123            return None;
124        };
125        Some(Duration {
126            ticks: div_ceil(value, 1_000_000 / GCD_1M),
127        })
128    }
129
130    /// Try to create a duration from the specified number of nanoseconds, rounding up.
131    /// Fails if the number of nanoseconds is too large.
132    /// NOTE: Delays this small may be inaccurate.
133    pub const fn try_from_nanos(nanoseconds: u64) -> Option<Duration> {
134        let Some(value) = nanoseconds.checked_mul(TICK_HZ / GCD_1G) else {
135            return None;
136        };
137        Some(Duration {
138            ticks: div_ceil(value, 1_000_000_000 / GCD_1G),
139        })
140    }
141
142    /// Try to create a duration from the specified number of seconds, rounding down.
143    /// Fails if the number of seconds is too large.
144    pub const fn try_from_secs_floor(secs: u64) -> Option<Duration> {
145        let Some(ticks) = secs.checked_mul(TICK_HZ) else {
146            return None;
147        };
148        Some(Duration { ticks })
149    }
150
151    /// Try to create a duration from the specified number of milliseconds, rounding down.
152    /// Fails if the number of milliseconds is too large.
153    pub const fn try_from_millis_floor(millis: u64) -> Option<Duration> {
154        let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else {
155            return None;
156        };
157        Some(Duration {
158            ticks: value / (1000 / GCD_1K),
159        })
160    }
161
162    /// Try to create a duration from the specified number of microseconds, rounding down.
163    /// Fails if the number of microseconds is too large.
164    /// NOTE: Delays this small may be inaccurate.
165    pub const fn try_from_micros_floor(micros: u64) -> Option<Duration> {
166        let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else {
167            return None;
168        };
169        Some(Duration {
170            ticks: value / (1_000_000 / GCD_1M),
171        })
172    }
173
174    /// Creates a duration corresponding to the specified Hz.
175    /// NOTE: Giving this function a hz >= the TICK_HZ of your platform will clamp the Duration to 1
176    /// tick. Doing so will not deadlock, but will certainly not produce the desired output.
177    pub const fn from_hz(hz: u64) -> Duration {
178        let ticks = { if hz >= TICK_HZ { 1 } else { (TICK_HZ + hz / 2) / hz } };
179        Duration { ticks }
180    }
181
182    /// Adds one `Duration` to another, returning a new `Duration` or `None` in the event of an overflow.
183    pub fn checked_add(self, rhs: Duration) -> Option<Duration> {
184        self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks })
185    }
186
187    /// Subtracts one `Duration` from another, returning a new `Duration` or `None` in the event of an overflow.
188    pub fn checked_sub(self, rhs: Duration) -> Option<Duration> {
189        self.ticks.checked_sub(rhs.ticks).map(|ticks| Duration { ticks })
190    }
191
192    /// Multiplies one `Duration` by a scalar `u32`, returning a new `Duration` or `None` in the event of an overflow.
193    pub fn checked_mul(self, rhs: u32) -> Option<Duration> {
194        self.ticks.checked_mul(rhs as _).map(|ticks| Duration { ticks })
195    }
196
197    /// Divides one `Duration` by a scalar `u32`, returning a new `Duration` or `None` in the event of an overflow.
198    pub fn checked_div(self, rhs: u32) -> Option<Duration> {
199        self.ticks.checked_div(rhs as _).map(|ticks| Duration { ticks })
200    }
201}
202
203impl Add for Duration {
204    type Output = Duration;
205
206    fn add(self, rhs: Duration) -> Duration {
207        self.checked_add(rhs).expect("overflow when adding durations")
208    }
209}
210
211impl AddAssign for Duration {
212    fn add_assign(&mut self, rhs: Duration) {
213        *self = *self + rhs;
214    }
215}
216
217impl Sub for Duration {
218    type Output = Duration;
219
220    fn sub(self, rhs: Duration) -> Duration {
221        self.checked_sub(rhs).expect("overflow when subtracting durations")
222    }
223}
224
225impl SubAssign for Duration {
226    fn sub_assign(&mut self, rhs: Duration) {
227        *self = *self - rhs;
228    }
229}
230
231impl Mul<u32> for Duration {
232    type Output = Duration;
233
234    fn mul(self, rhs: u32) -> Duration {
235        self.checked_mul(rhs)
236            .expect("overflow when multiplying duration by scalar")
237    }
238}
239
240impl Mul<Duration> for u32 {
241    type Output = Duration;
242
243    fn mul(self, rhs: Duration) -> Duration {
244        rhs * self
245    }
246}
247
248impl MulAssign<u32> for Duration {
249    fn mul_assign(&mut self, rhs: u32) {
250        *self = *self * rhs;
251    }
252}
253
254impl Div<u32> for Duration {
255    type Output = Duration;
256
257    fn div(self, rhs: u32) -> Duration {
258        self.checked_div(rhs)
259            .expect("divide by zero error when dividing duration by scalar")
260    }
261}
262
263impl DivAssign<u32> for Duration {
264    fn div_assign(&mut self, rhs: u32) {
265        *self = *self / rhs;
266    }
267}
268
269impl<'a> fmt::Display for Duration {
270    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271        write!(f, "{} ticks", self.ticks)
272    }
273}
274
275#[inline]
276const fn div_ceil(num: u64, den: u64) -> u64 {
277    (num + den - 1) / den
278}
279
280impl TryFrom<core::time::Duration> for Duration {
281    type Error = <u64 as TryFrom<u128>>::Error;
282
283    /// Converts using [`Duration::from_micros`]. Fails if value can not be represented as u64.
284    fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
285        Ok(Self::from_micros(value.as_micros().try_into()?))
286    }
287}
288
289impl From<Duration> for core::time::Duration {
290    /// Converts using [`Duration::as_micros`].
291    fn from(value: Duration) -> Self {
292        core::time::Duration::from_micros(value.as_micros())
293    }
294}
295
296impl core::iter::Sum for Duration {
297    fn sum<I>(iter: I) -> Self
298    where
299        I: Iterator<Item = Duration>,
300    {
301        Duration::from_ticks(iter.map(|d| d.as_ticks()).sum())
302    }
303}