s2n_quic_core/time/
timestamp.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Defines time related datatypes and functions
5
6use crate::recovery::K_GRANULARITY;
7use core::{fmt, num::NonZeroU64, time::Duration};
8
9/// An absolute point in time.
10///
11/// The absolute value of `Timestamp`s should be treated as opaque. It is not
12/// necessarily related to any calendar time.
13/// `Timestamp`s should only be compared if they are are sourced from the same
14/// clock.
15///
16/// `Timestamp`s are similar to the `Instant` data-type in the Rust standard
17/// library, but can be created even without an available standard library.
18///
19/// The size of `Timestamp` is guaranteed to be consistent across platforms.
20#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
21pub struct Timestamp(NonZeroU64);
22
23impl fmt::Debug for Timestamp {
24    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25        write!(f, "Timestamp({self})")
26    }
27}
28
29impl fmt::Display for Timestamp {
30    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31        let duration = self.as_duration_impl();
32        let micros = duration.subsec_micros();
33        let secs = duration.as_secs() % 60;
34        let mins = duration.as_secs() / 60 % 60;
35        let hours = duration.as_secs() / 60 / 60;
36        if micros != 0 {
37            write!(f, "{hours}:{mins:02}:{secs:02}.{micros:06}")
38        } else {
39            write!(f, "{hours}:{mins:02}:{secs:02}")
40        }
41    }
42}
43
44#[test]
45fn fmt_test() {
46    macro_rules! debug {
47        ($secs:expr, $micros:expr) => {
48            format!(
49                "{:#?}",
50                Timestamp::from_duration_impl(
51                    Duration::from_secs($secs) + Duration::from_micros($micros)
52                )
53            )
54        };
55    }
56
57    assert_eq!(debug!(1, 0), "Timestamp(0:00:01)");
58    assert_eq!(debug!(1, 1), "Timestamp(0:00:01.000001)");
59    assert_eq!(debug!(123456789, 123456), "Timestamp(34293:33:09.123456)");
60}
61
62/// A prechecked 1us value
63const ONE_MICROSECOND: NonZeroU64 = NonZeroU64::new(1).unwrap();
64
65impl Timestamp {
66    /// Tries to calculate a `Timestamp` based on the current `Timestamp` and
67    /// adding the provided `Duration`. If this `Timestamp` is representable
68    /// within the range of `Timestamp` it is returned as `Some(timestamp)`.
69    /// Otherwise `None` is returned.
70    #[inline]
71    pub fn checked_add(self, duration: Duration) -> Option<Self> {
72        self.as_duration_impl()
73            .checked_add(duration)
74            .map(Self::from_duration_impl)
75    }
76
77    /// Tries to calculate a `Timestamp` based on the current `Timestamp` and
78    /// subtracting the provided `Duration`. If this `Timestamp` is representable
79    /// within the range of `Timestamp` it is returned as `Some(timestamp)`.
80    /// Otherwise `None` is returned.
81    #[inline]
82    pub fn checked_sub(self, duration: Duration) -> Option<Self> {
83        self.as_duration_impl()
84            .checked_sub(duration)
85            .map(Self::from_duration_impl)
86    }
87
88    /// Returns the `Duration` which elapsed since an earlier `Timestamp`.
89    /// If `earlier` is more recent, the method returns a `Duration` of 0.
90    #[inline]
91    pub fn saturating_duration_since(self, earlier: Self) -> Duration {
92        self.checked_sub(earlier.as_duration_impl())
93            .map(Self::as_duration_impl)
94            .unwrap_or_default()
95    }
96
97    /// Creates a `Timestamp` from a `Duration` since the time source's epoch.
98    /// This will treat the duration as an absolute point in time.
99    ///
100    /// # Safety
101    /// This should only be used by time sources
102    #[inline]
103    pub unsafe fn from_duration(duration: Duration) -> Self {
104        Self::from_duration_impl(duration)
105    }
106
107    /// Creates a `Timestamp` from a `Duration` since the time source's epoch.
108    #[inline]
109    fn from_duration_impl(duration: Duration) -> Self {
110        // 2^64 microseconds is ~580,000 years so casting from a u128 should be ok
111        debug_assert!(duration.as_micros() <= u64::MAX.into());
112        let micros = duration.as_micros() as u64;
113        // if the value is 0 then round up to 1us after the epoch
114        let micros = NonZeroU64::new(micros).unwrap_or(ONE_MICROSECOND);
115        Self(micros)
116    }
117
118    /// Converts the `Timestamp` into the `Duration` since the time source's epoch.
119    ///
120    /// # Safety
121    /// This should only be used by time sources
122    #[inline]
123    pub unsafe fn as_duration(self) -> Duration {
124        Self::as_duration_impl(self)
125    }
126
127    /// Returns the timestamp as a [`Duration`] since the clock epoch.
128    #[inline]
129    const fn as_duration_impl(self) -> Duration {
130        Duration::from_micros(self.0.get())
131    }
132
133    /// Compares the timestamp to the current time and returns true if it is in the past
134    ///
135    /// Note that this compares milliseconds, as any finer resolution would result in
136    /// excessive timer churn.
137    #[inline]
138    pub const fn has_elapsed(self, now: Self) -> bool {
139        let mut now = now.0.get();
140
141        // even if the timestamp is less than the timer granularity in the future, consider it elapsed
142        now += K_GRANULARITY.as_micros() as u64;
143
144        self.0.get() < now
145    }
146}
147
148impl core::ops::Add<Duration> for Timestamp {
149    type Output = Timestamp;
150
151    #[inline]
152    fn add(self, rhs: Duration) -> Self::Output {
153        Timestamp::from_duration_impl(self.as_duration_impl() + rhs)
154    }
155}
156
157impl core::ops::AddAssign<Duration> for Timestamp {
158    #[inline]
159    fn add_assign(&mut self, other: Duration) {
160        *self = *self + other;
161    }
162}
163
164impl core::ops::Sub for Timestamp {
165    type Output = Duration;
166
167    #[inline]
168    fn sub(self, rhs: Timestamp) -> Self::Output {
169        self.as_duration_impl() - rhs.as_duration_impl()
170    }
171}
172
173impl core::ops::Sub<Duration> for Timestamp {
174    type Output = Timestamp;
175
176    #[inline]
177    fn sub(self, rhs: Duration) -> Self::Output {
178        Timestamp::from_duration_impl(self.as_duration_impl() - rhs)
179    }
180}
181
182impl core::ops::SubAssign<Duration> for Timestamp {
183    #[inline]
184    fn sub_assign(&mut self, other: Duration) {
185        *self = *self - other;
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn timestamp_from_and_to_duration() {
195        let ts1 = Timestamp::from_duration_impl(Duration::from_millis(100));
196        let ts2 = Timestamp::from_duration_impl(Duration::from_millis(220));
197
198        // Subtract timestamps to gain a duration
199        assert_eq!(Duration::from_millis(120), ts2 - ts1);
200
201        // Add duration to timestamp
202        let ts3 = ts2 + Duration::from_millis(11);
203        assert_eq!(Duration::from_millis(231), unsafe {
204            Timestamp::as_duration(ts3)
205        });
206
207        // Subtract a duration from a timestamp
208        let ts4 = ts3 - Duration::from_millis(41);
209        assert_eq!(Duration::from_millis(190), unsafe {
210            Timestamp::as_duration(ts4)
211        });
212    }
213
214    fn timestamp_math(initial: Timestamp) {
215        // Add Duration
216        let mut ts1 = initial + Duration::from_millis(500);
217        assert_eq!(Duration::from_millis(500), ts1 - initial);
218        // AddAssign Duration
219        ts1 += Duration::from_millis(100);
220        assert_eq!(Duration::from_millis(600), ts1 - initial);
221        // SubAssign Duration
222        ts1 -= Duration::from_millis(50);
223        assert_eq!(Duration::from_millis(550), ts1 - initial);
224        // Sub Duration
225        let ts2 = ts1 - Duration::from_millis(110);
226        assert_eq!(Duration::from_millis(440), ts2 - initial);
227        // Checked Sub
228        assert!(ts2.checked_sub(Duration::from_secs(u64::MAX)).is_none());
229        assert_eq!(Some(initial), ts2.checked_sub(Duration::from_millis(440)));
230        // Checked Add
231        let max_duration =
232            Duration::from_secs(u64::MAX) + (Duration::from_secs(1) - Duration::from_nanos(1));
233        assert_eq!(None, ts2.checked_add(max_duration));
234        assert!(ts2.checked_add(Duration::from_secs(24 * 60 * 60)).is_some());
235
236        // Saturating Timestamp sub
237        let higher = initial + Duration::from_millis(200);
238        assert_eq!(
239            Duration::from_millis(200),
240            higher.saturating_duration_since(initial)
241        );
242        assert_eq!(
243            Duration::from_millis(0),
244            initial.saturating_duration_since(higher)
245        );
246    }
247
248    #[test]
249    fn timestamp_math_test() {
250        // Start at a high initial timestamp to let the overflow check work
251        let initial = Timestamp::from_duration_impl(Duration::from_micros(1));
252        timestamp_math(initial);
253
254        // Start at a high initial timestamp to let the overflow check work
255        let initial = Timestamp::from_duration_impl(Duration::from_micros(1u64 << 63));
256        timestamp_math(initial);
257    }
258}