Skip to main content

pw_time_core/
pw_time_core.rs

1// Copyright 2025 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14#![no_std]
15
16//! `pw_time_core` provides clock aware time and duration types.
17//!
18//! `pw_time_core` contains the basic types for
19//!  <a href="../pw_time/index.html"><code>pw_time</code></a> without providing
20//! a default system clock.  This allows it to be used by:
21//! * [`Clock`] implementations.
22//! * systems which do not support a system clock.
23//!
24//! For more information see <a href="../pw_time/index.html"><code>pw_time</code></a>.
25use core::marker::PhantomData;
26use core::ops::{Add, Sub};
27
28#[cfg(test)]
29#[unsafe(no_mangle)]
30unsafe extern "C-unwind" fn pw_assert_HandleFailure() -> ! {
31    panic!("pw_assert failed");
32}
33
34/// A trait for retrieving the current system or hardware time.
35pub trait Clock: Sized {
36    /// The number of clock ticks per second.
37    const TICKS_PER_SEC: u64;
38
39    /// Returns the current time [`Instant`] according to this clock.
40    fn now() -> Instant<Self>;
41}
42
43/// A measurement of a monotonically non-decreasing clock.
44///
45/// An `Instant` is generic over a [`Clock`], preventing arithmetic operations
46/// between instants of different clocks at compile-time.
47#[derive(Debug)]
48pub struct Instant<Clock: crate::Clock> {
49    ticks: u64,
50    _phantom: PhantomData<Clock>,
51}
52
53impl<Clock: crate::Clock> Instant<Clock> {
54    /// The maximum possible value for an `Instant`.
55    pub const MAX: Self = Self::from_ticks(u64::MAX);
56    /// The minimum possible value for an `Instant`.
57    pub const MIN: Self = Self::from_ticks(u64::MIN);
58
59    /// Creates a new `Instant` from a raw tick count.
60    #[must_use]
61    pub const fn from_ticks(ticks: u64) -> Self {
62        Self {
63            ticks,
64            _phantom: PhantomData,
65        }
66    }
67
68    /// Returns the raw tick count of this `Instant`.
69    #[must_use]
70    pub const fn ticks(&self) -> u64 {
71        self.ticks
72    }
73
74    /// Returns the `Instant` resulting from adding `Duration`, or `None` if overflow occurred.
75    #[must_use]
76    pub const fn checked_add_duration(self, duration: Duration<Clock>) -> Option<Self> {
77        if let Some(ticks) = self.ticks.checked_add(duration.ticks) {
78            Some(Self {
79                ticks,
80                _phantom: PhantomData,
81            })
82        } else {
83            None
84        }
85    }
86
87    /// Returns the `Instant` resulting from subtracting `Duration`, or `None` if underflow occurred.
88    #[must_use]
89    pub const fn checked_sub_duration(self, duration: Duration<Clock>) -> Option<Self> {
90        if let Some(ticks) = self.ticks.checked_sub(duration.ticks) {
91            Some(Self {
92                ticks,
93                _phantom: PhantomData,
94            })
95        } else {
96            None
97        }
98    }
99}
100
101// Manually implement Copy so that we don't require `Clock` to be Copy
102impl<Clock: crate::Clock> Copy for Instant<Clock> {}
103
104// Manually implement Clone so that we don't require `Clock` to be Clone
105impl<Clock: crate::Clock> Clone for Instant<Clock> {
106    fn clone(&self) -> Self {
107        *self
108    }
109}
110
111// Manually implement Ord so that we don't require `Clock` to be Ord
112impl<Clock: crate::Clock> Ord for Instant<Clock> {
113    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
114        self.ticks.cmp(&other.ticks)
115    }
116}
117
118// Manually implement PartialOrd so that we don't require `Clock` to be PartialOrd
119impl<Clock: crate::Clock> PartialOrd for Instant<Clock> {
120    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
121        Some(self.cmp(other))
122    }
123}
124
125// Manually implement Eq so that we don't require `Clock` to be Eq
126impl<Clock: crate::Clock> Eq for Instant<Clock> {}
127
128// Manually implement PartialEq so that we don't require `Clock` to be PartialEq
129impl<Clock: crate::Clock> PartialEq for Instant<Clock> {
130    fn eq(&self, other: &Self) -> bool {
131        self.ticks == other.ticks
132    }
133}
134
135impl<Clock: crate::Clock> Sub<Instant<Clock>> for Instant<Clock> {
136    type Output = Duration<Clock>;
137
138    fn sub(self, rhs: Instant<Clock>) -> Self::Output {
139        // Saturate to Duration::MIN (0 ticks) on underflow, matching std::time::Instant.
140        let ticks = self.ticks.saturating_sub(rhs.ticks);
141        Self::Output {
142            ticks,
143            _phantom: PhantomData,
144        }
145    }
146}
147
148impl<Clock: crate::Clock> Add<Duration<Clock>> for Instant<Clock> {
149    type Output = Instant<Clock>;
150
151    fn add(self, rhs: Duration<Clock>) -> Self::Output {
152        let time = self.checked_add_duration(rhs);
153        if time.is_none() {
154            pw_assert::panic!("Instant - Duration overflow");
155        }
156        time.unwrap()
157    }
158}
159
160impl<Clock: crate::Clock> Sub<Duration<Clock>> for Instant<Clock> {
161    type Output = Instant<Clock>;
162
163    fn sub(self, rhs: Duration<Clock>) -> Self::Output {
164        let time = self.checked_sub_duration(rhs);
165        if time.is_none() {
166            pw_assert::panic!("Instant - Duration overflow")
167        }
168        time.unwrap()
169    }
170}
171
172/// A span of time represented by an unsigned tick count.
173///
174/// A `Duration` is generic over a [`Clock`], preventing arithmetic operations
175/// between durations of different clocks at compile-time.
176#[derive(Debug)]
177pub struct Duration<Clock: crate::Clock> {
178    ticks: u64,
179    _phantom: PhantomData<Clock>,
180}
181
182impl<Clock: crate::Clock> Duration<Clock> {
183    /// The maximum possible value for a `Duration`.
184    pub const MAX: Self = Self {
185        ticks: u64::MAX,
186        _phantom: PhantomData,
187    };
188
189    /// The minimum possible value for a `Duration`.
190    pub const MIN: Self = Self {
191        ticks: u64::MIN,
192        _phantom: PhantomData,
193    };
194
195    /// Returns the raw tick count of this `Duration`.
196    #[must_use]
197    pub const fn ticks(self) -> u64 {
198        self.ticks
199    }
200
201    /// Creates a `Duration` from a number of seconds.
202    #[must_use]
203    pub const fn from_secs(secs: u64) -> Self {
204        Self {
205            ticks: secs * Clock::TICKS_PER_SEC,
206            _phantom: PhantomData,
207        }
208    }
209
210    /// Creates a `Duration` from a number of milliseconds.
211    #[must_use]
212    pub const fn from_millis(millis: u64) -> Self {
213        Self {
214            ticks: millis * Clock::TICKS_PER_SEC / 1000,
215            _phantom: PhantomData,
216        }
217    }
218
219    /// Creates a `Duration` from a number of microseconds.
220    #[must_use]
221    pub const fn from_micros(micros: u64) -> Self {
222        Self {
223            ticks: micros * Clock::TICKS_PER_SEC / 1_000_000,
224            _phantom: PhantomData,
225        }
226    }
227
228    /// Creates a `Duration` from a number of nanoseconds.
229    #[must_use]
230    pub const fn from_nanos(nanos: u64) -> Self {
231        Self {
232            ticks: nanos * Clock::TICKS_PER_SEC / 1_000_000_000,
233            _phantom: PhantomData,
234        }
235    }
236
237    /// Returns the total number of whole seconds contained by this `Duration`.
238    #[must_use]
239    pub fn as_secs(self) -> u64 {
240        self.ticks / Clock::TICKS_PER_SEC
241    }
242
243    /// Returns the total number of milliseconds contained by this `Duration`.
244    #[must_use]
245    pub fn as_millis(self) -> u128 {
246        u128::from(self.ticks) * 1000 / u128::from(Clock::TICKS_PER_SEC)
247    }
248
249    /// Returns the total number of microseconds contained by this `Duration`.
250    #[must_use]
251    pub fn as_micros(self) -> u128 {
252        u128::from(self.ticks) * 1_000_000 / u128::from(Clock::TICKS_PER_SEC)
253    }
254
255    /// Returns the total number of nanoseconds contained by this `Duration`.
256    #[must_use]
257    pub fn as_nanos(self) -> u128 {
258        u128::from(self.ticks) * 1_000_000_000 / u128::from(Clock::TICKS_PER_SEC)
259    }
260
261    /// Adds another `Duration`, returning `None` if overflow occurred.
262    #[must_use]
263    pub const fn checked_add(self, rhs: Duration<Clock>) -> Option<Self> {
264        if let Some(ticks) = self.ticks.checked_add(rhs.ticks) {
265            Some(Self {
266                ticks,
267                _phantom: PhantomData,
268            })
269        } else {
270            None
271        }
272    }
273
274    /// Subtracts another `Duration`, returning `None` if underflow occurred.
275    #[must_use]
276    pub const fn checked_sub(self, rhs: Duration<Clock>) -> Option<Self> {
277        if let Some(ticks) = self.ticks.checked_sub(rhs.ticks) {
278            Some(Self {
279                ticks,
280                _phantom: PhantomData,
281            })
282        } else {
283            None
284        }
285    }
286}
287
288// Manually implement Copy so that we don't require `Duration` to be Copy
289impl<Clock: crate::Clock> Copy for Duration<Clock> {}
290
291// Manually implement Clone so that we don't require `Duration` to be Clone
292impl<Clock: crate::Clock> Clone for Duration<Clock> {
293    fn clone(&self) -> Self {
294        *self
295    }
296}
297
298// Manually implement Ord so that we don't require `Clock` to be Ord
299impl<Clock: crate::Clock> Ord for Duration<Clock> {
300    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
301        self.ticks.cmp(&other.ticks)
302    }
303}
304
305// Manually implement PartialOrd so that we don't require `Clock` to be PartialOrd
306impl<Clock: crate::Clock> PartialOrd for Duration<Clock> {
307    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
308        Some(self.cmp(other))
309    }
310}
311
312// Manually implement Eq so that we don't require `Clock` to be Eq
313impl<Clock: crate::Clock> Eq for Duration<Clock> {}
314
315// Manually implement PartialEq so that we don't require `Clock` to be PartialEq
316impl<Clock: crate::Clock> PartialEq for Duration<Clock> {
317    fn eq(&self, other: &Self) -> bool {
318        self.ticks == other.ticks
319    }
320}
321
322impl<Clock: crate::Clock> Sub<Duration<Clock>> for Duration<Clock> {
323    type Output = Duration<Clock>;
324
325    fn sub(self, rhs: Duration<Clock>) -> Self::Output {
326        let time = self.checked_sub(rhs);
327        if time.is_none() {
328            pw_assert::panic!("Duration subtraction overflow")
329        }
330        time.unwrap()
331    }
332}
333
334impl<Clock: crate::Clock> Add<Duration<Clock>> for Duration<Clock> {
335    type Output = Duration<Clock>;
336
337    fn add(self, rhs: Duration<Clock>) -> Self::Output {
338        let time = self.checked_add(rhs);
339        if time.is_none() {
340            pw_assert::panic!("Duration addition overflow")
341        }
342        time.unwrap()
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[derive(Debug)]
351    struct TestClock;
352
353    impl Clock for TestClock {
354        const TICKS_PER_SEC: u64 = 1_000;
355        fn now() -> Instant<Self> {
356            Instant::from_ticks(0)
357        }
358    }
359
360    #[derive(Debug)]
361    struct HighResTestClock;
362
363    impl Clock for HighResTestClock {
364        const TICKS_PER_SEC: u64 = 1_000_000_000;
365        fn now() -> Instant<Self> {
366            Instant::from_ticks(0)
367        }
368    }
369
370    #[test]
371    fn duration_constructors_return_correct_values() {
372        assert_eq!(Duration::<TestClock>::from_secs(1234).ticks(), 1_234_000);
373        assert_eq!(Duration::<TestClock>::from_millis(1234).ticks(), 1_234);
374        assert_eq!(Duration::<TestClock>::from_micros(1234).ticks(), 1);
375        assert_eq!(Duration::<TestClock>::from_nanos(1234).ticks(), 0);
376
377        assert_eq!(Duration::<HighResTestClock>::from_nanos(1234).ticks(), 1234);
378    }
379
380    #[test]
381    fn duration_accessors_return_correct_values() {
382        let dur = Duration::<TestClock>::from_secs(123);
383        assert_eq!(dur.as_secs(), 123u64);
384        assert_eq!(dur.as_millis(), 123_000u128);
385        assert_eq!(dur.as_micros(), 123_000_000u128);
386        assert_eq!(dur.as_nanos(), 123_000_000_000u128);
387    }
388
389    #[test]
390    fn duration_checked_addition_returns_correct_values() {
391        let ten_ms = Duration::<TestClock>::from_millis(10);
392        let one_ms = Duration::<TestClock>::from_millis(1);
393
394        assert_eq!(
395            ten_ms.checked_add(one_ms),
396            Some(Duration::<TestClock>::from_millis(11))
397        );
398
399        assert_eq!(
400            one_ms.checked_add(ten_ms),
401            Some(Duration::<TestClock>::from_millis(11))
402        );
403
404        assert_eq!(Duration::<TestClock>::MAX.checked_add(one_ms), None);
405    }
406
407    #[test]
408    fn duration_checked_subtraction_returns_correct_values() {
409        let ten_ms = Duration::<TestClock>::from_millis(10);
410        let one_ms = Duration::<TestClock>::from_millis(1);
411
412        assert_eq!(
413            ten_ms.checked_sub(one_ms),
414            Some(Duration::<TestClock>::from_millis(9))
415        );
416
417        assert_eq!(one_ms.checked_sub(ten_ms), None);
418
419        assert_eq!(Duration::<TestClock>::MIN.checked_sub(one_ms), None);
420    }
421
422    #[test]
423    fn instant_subtraction_returns_correct_values() {
424        let ten_ms = Instant::from_ticks(10 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
425        let one_ms = Instant::from_ticks(<TestClock as Clock>::TICKS_PER_SEC / 1000);
426
427        assert_eq!(ten_ms - one_ms, Duration::<TestClock>::from_millis(9));
428        assert_eq!(one_ms - ten_ms, Duration::<TestClock>::from_millis(0));
429    }
430
431    #[test]
432    fn instant_checked_duration_addition_returns_correct_values() {
433        let instant_eleven_ms =
434            Instant::<TestClock>::from_ticks(11 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
435        let instant_ten_ms =
436            Instant::<TestClock>::from_ticks(10 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
437
438        let duration_one_ms = Duration::<TestClock>::from_millis(1);
439
440        assert_eq!(
441            instant_ten_ms.checked_add_duration(duration_one_ms),
442            Some(instant_eleven_ms)
443        );
444
445        assert_eq!(
446            Instant::<TestClock>::MAX.checked_add_duration(duration_one_ms),
447            None
448        );
449    }
450
451    #[test]
452    fn instant_checked_duration_subtraction_returns_correct_values() {
453        let instant_ten_ms =
454            Instant::<TestClock>::from_ticks(10 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
455        let instant_nine_ms =
456            Instant::<TestClock>::from_ticks(9 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
457
458        let duration_one_ms = Duration::<TestClock>::from_millis(1);
459
460        assert_eq!(
461            instant_ten_ms.checked_sub_duration(duration_one_ms),
462            Some(instant_nine_ms)
463        );
464
465        assert_eq!(
466            Instant::<TestClock>::MIN.checked_sub_duration(duration_one_ms),
467            None
468        );
469    }
470}