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_signed(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_add_signed(-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        Self::Output {
140            // Use a wrapping_sub then conversion to i64 to avoid losing
141            // resolution for large values of ticks.
142            ticks: self.ticks.wrapping_sub(rhs.ticks).cast_signed(),
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 a signed 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: i64,
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: i64::MAX,
186        _phantom: PhantomData,
187    };
188
189    /// The minimum possible value for a `Duration`.
190    pub const MIN: Self = Self {
191        ticks: i64::MIN,
192        _phantom: PhantomData,
193    };
194
195    /// Returns the raw tick count of this `Duration`.
196    #[must_use]
197    pub const fn ticks(self) -> i64 {
198        self.ticks
199    }
200
201    /// Creates a `Duration` from a number of seconds.
202    #[must_use]
203    pub const fn from_secs(secs: i64) -> Self {
204        Self {
205            ticks: secs * (Clock::TICKS_PER_SEC.cast_signed()),
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: i64) -> Self {
213        Self {
214            ticks: millis * (Clock::TICKS_PER_SEC.cast_signed()) / 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: i64) -> Self {
222        Self {
223            ticks: micros * (Clock::TICKS_PER_SEC.cast_signed()) / 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: i64) -> Self {
231        Self {
232            ticks: nanos * (Clock::TICKS_PER_SEC.cast_signed()) / 1_000_000_000,
233            _phantom: PhantomData,
234        }
235    }
236
237    /// Adds another `Duration`, returning `None` if overflow occurred.
238    #[must_use]
239    pub const fn checked_add(self, rhs: Duration<Clock>) -> Option<Self> {
240        if let Some(ticks) = self.ticks.checked_add(rhs.ticks) {
241            Some(Self {
242                ticks,
243                _phantom: PhantomData,
244            })
245        } else {
246            None
247        }
248    }
249
250    /// Subtracts another `Duration`, returning `None` if underflow occurred.
251    #[must_use]
252    pub const fn checked_sub(self, rhs: Duration<Clock>) -> Option<Self> {
253        if let Some(ticks) = self.ticks.checked_sub(rhs.ticks) {
254            Some(Self {
255                ticks,
256                _phantom: PhantomData,
257            })
258        } else {
259            None
260        }
261    }
262}
263
264// Manually implement Copy so that we don't require `Duration` to be Copy
265impl<Clock: crate::Clock> Copy for Duration<Clock> {}
266
267// Manually implement Clone so that we don't require `Duration` to be Clone
268impl<Clock: crate::Clock> Clone for Duration<Clock> {
269    fn clone(&self) -> Self {
270        *self
271    }
272}
273
274// Manually implement Ord so that we don't require `Clock` to be Ord
275impl<Clock: crate::Clock> Ord for Duration<Clock> {
276    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
277        self.ticks.cmp(&other.ticks)
278    }
279}
280
281// Manually implement PartialOrd so that we don't require `Clock` to be PartialOrd
282impl<Clock: crate::Clock> PartialOrd for Duration<Clock> {
283    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
284        Some(self.cmp(other))
285    }
286}
287
288// Manually implement Eq so that we don't require `Clock` to be Eq
289impl<Clock: crate::Clock> Eq for Duration<Clock> {}
290
291// Manually implement PartialEq so that we don't require `Clock` to be PartialEq
292impl<Clock: crate::Clock> PartialEq for Duration<Clock> {
293    fn eq(&self, other: &Self) -> bool {
294        self.ticks == other.ticks
295    }
296}
297
298impl<Clock: crate::Clock> Sub<Duration<Clock>> for Duration<Clock> {
299    type Output = Duration<Clock>;
300
301    fn sub(self, rhs: Duration<Clock>) -> Self::Output {
302        let time = self.checked_sub(rhs);
303        if time.is_none() {
304            pw_assert::panic!("Duration subtraction overflow")
305        }
306        time.unwrap()
307    }
308}
309
310impl<Clock: crate::Clock> Add<Duration<Clock>> for Duration<Clock> {
311    type Output = Duration<Clock>;
312
313    fn add(self, rhs: Duration<Clock>) -> Self::Output {
314        let time = self.checked_add(rhs);
315        if time.is_none() {
316            pw_assert::panic!("Duration addition overflow")
317        }
318        time.unwrap()
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    #[derive(Debug)]
327    struct TestClock;
328
329    impl Clock for TestClock {
330        const TICKS_PER_SEC: u64 = 1_000;
331        fn now() -> Instant<Self> {
332            Instant::from_ticks(0)
333        }
334    }
335
336    #[derive(Debug)]
337    struct HighResTestClock;
338
339    impl Clock for HighResTestClock {
340        const TICKS_PER_SEC: u64 = 1_000_000_000;
341        fn now() -> Instant<Self> {
342            Instant::from_ticks(0)
343        }
344    }
345
346    #[test]
347    fn duration_constructors_return_correct_values() {
348        assert_eq!(Duration::<TestClock>::from_secs(1234).ticks(), 1_234_000);
349        assert_eq!(Duration::<TestClock>::from_millis(1234).ticks(), 1_234);
350        assert_eq!(Duration::<TestClock>::from_micros(1234).ticks(), 1);
351        assert_eq!(Duration::<TestClock>::from_nanos(1234).ticks(), 0);
352
353        assert_eq!(Duration::<HighResTestClock>::from_nanos(1234).ticks(), 1234);
354    }
355
356    #[test]
357    fn duration_checked_addition_returns_correct_values() {
358        let ten_ms = Duration::<TestClock>::from_millis(10);
359        let one_ms = Duration::<TestClock>::from_millis(1);
360
361        assert_eq!(
362            ten_ms.checked_add(one_ms),
363            Some(Duration::<TestClock>::from_millis(11))
364        );
365
366        assert_eq!(
367            one_ms.checked_add(ten_ms),
368            Some(Duration::<TestClock>::from_millis(11))
369        );
370
371        assert_eq!(Duration::<TestClock>::MAX.checked_add(one_ms), None);
372
373        assert_eq!(
374            Duration::<TestClock>::MIN.checked_add(Duration::from_millis(-1)),
375            None
376        );
377    }
378
379    #[test]
380    fn duration_checked_subtraction_returns_correct_values() {
381        let ten_ms = Duration::<TestClock>::from_millis(10);
382        let one_ms = Duration::<TestClock>::from_millis(1);
383
384        assert_eq!(
385            ten_ms.checked_sub(one_ms),
386            Some(Duration::<TestClock>::from_millis(9))
387        );
388
389        assert_eq!(
390            one_ms.checked_sub(ten_ms),
391            Some(Duration::<TestClock>::from_millis(-9))
392        );
393
394        assert_eq!(
395            Duration::<TestClock>::MAX.checked_sub(Duration::from_millis(-1)),
396            None
397        );
398
399        assert_eq!(
400            Duration::<TestClock>::MIN.checked_sub(Duration::from_millis(1)),
401            None
402        );
403    }
404
405    #[test]
406    fn instant_subtraction_returns_correct_values() {
407        let ten_ms = Instant::from_ticks(10 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
408        let one_ms = Instant::from_ticks(<TestClock as Clock>::TICKS_PER_SEC / 1000);
409
410        assert_eq!(ten_ms - one_ms, Duration::<TestClock>::from_millis(9));
411        assert_eq!(one_ms - ten_ms, Duration::<TestClock>::from_millis(-9));
412    }
413
414    #[test]
415    fn instant_checked_duration_addition_returns_correct_values() {
416        let instant_eleven_ms =
417            Instant::<TestClock>::from_ticks(11 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
418        let instant_ten_ms =
419            Instant::<TestClock>::from_ticks(10 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
420        let instant_nine_ms =
421            Instant::<TestClock>::from_ticks(9 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
422
423        let duration_one_ms = Duration::<TestClock>::from_millis(1);
424        let duration_minus_one_ms = Duration::<TestClock>::from_millis(-1);
425
426        assert_eq!(
427            instant_ten_ms.checked_add_duration(duration_one_ms),
428            Some(instant_eleven_ms)
429        );
430
431        assert_eq!(
432            instant_ten_ms.checked_add_duration(duration_minus_one_ms),
433            Some(instant_nine_ms)
434        );
435
436        assert_eq!(
437            Instant::<TestClock>::MAX.checked_add_duration(duration_one_ms),
438            None
439        );
440
441        assert_eq!(
442            Instant::<TestClock>::MIN.checked_add_duration(duration_minus_one_ms),
443            None
444        );
445    }
446
447    #[test]
448    fn instant_checked_duration_subtraction_returns_correct_values() {
449        let instant_eleven_ms =
450            Instant::<TestClock>::from_ticks(11 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
451        let instant_ten_ms =
452            Instant::<TestClock>::from_ticks(10 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
453        let instant_nine_ms =
454            Instant::<TestClock>::from_ticks(9 * <TestClock as Clock>::TICKS_PER_SEC / 1000);
455
456        let duration_one_ms = Duration::<TestClock>::from_millis(1);
457        let duration_minus_one_ms = Duration::<TestClock>::from_millis(-1);
458
459        assert_eq!(
460            instant_ten_ms.checked_sub_duration(duration_one_ms),
461            Some(instant_nine_ms)
462        );
463
464        assert_eq!(
465            instant_ten_ms.checked_sub_duration(duration_minus_one_ms),
466            Some(instant_eleven_ms)
467        );
468
469        assert_eq!(
470            Instant::<TestClock>::MAX.checked_sub_duration(duration_minus_one_ms),
471            None
472        );
473
474        assert_eq!(
475            Instant::<TestClock>::MIN.checked_sub_duration(duration_one_ms),
476            None
477        );
478    }
479}