Skip to main content Skip to main content
Pigweed's logo Pigweed
code_xml
  1. Home
  2. Reference
  3. Rust
  4. src
  5. pw_time_core/pw_time_core.rs

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