Skip to main content

transforms/time/timestamp/
mod.rs

1use core::{
2    cmp::Ordering,
3    ops::{Add, Sub},
4    time::Duration,
5};
6
7use crate::time::{TimeError, TimePoint};
8
9#[cfg(feature = "std")]
10use std::time::{SystemTime, UNIX_EPOCH};
11
12pub mod error;
13#[allow(deprecated)]
14pub use error::TimestampError;
15
16/// Default concrete time type used by this crate.
17///
18/// `Timestamp` stores a time value in `u128` nanoseconds.
19///
20/// For custom clocks, implement `crate::time::TimePoint` on your own type and
21/// use it with `Registry<T>`.
22#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
23pub struct Timestamp {
24    pub t: u128,
25}
26
27impl Timestamp {
28    #[cfg(feature = "std")]
29    #[must_use = "this returns the result of the operation"]
30    /// Returns a `Timestamp` initialized to the current time.
31    /// This functionality is useful for dynamic transforms.
32    ///
33    /// # Examples
34    ///
35    /// ```
36    /// use transforms::time::Timestamp;
37    ///
38    /// let now = Timestamp::now();
39    /// assert!(now.t > 0);
40    /// ```
41    ///
42    /// # Panics
43    ///
44    /// Panics if the system time is earlier than `UNIX_EPOCH` (January 1, 1970).
45    pub fn now() -> Self {
46        let duration_since_epoch = SystemTime::now()
47            .duration_since(UNIX_EPOCH)
48            .expect("Time went backwards");
49
50        Timestamp {
51            t: duration_since_epoch.as_nanos(),
52        }
53    }
54
55    #[must_use = "this returns the result of the operation"]
56    /// Returns a `Timestamp` initialized at zero.
57    /// This functionality is especially useful for static transforms.
58    ///
59    /// # Examples
60    ///
61    /// ```
62    /// use transforms::time::Timestamp;
63    ///
64    /// let zero = Timestamp::zero();
65    /// assert_eq!(zero.t, 0);
66    /// ```
67    pub fn zero() -> Self {
68        Timestamp { t: 0 }
69    }
70
71    /// Converts the `Timestamp` to seconds as a floating-point number.
72    ///
73    /// Returns an error if the conversion results in accuracy loss.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use transforms::time::Timestamp;
79    ///
80    /// let timestamp = Timestamp { t: 1_000_000_000 };
81    /// let result = timestamp.as_seconds();
82    /// assert!(result.is_ok());
83    /// assert_eq!(result.unwrap(), 1.0);
84    ///
85    /// let timestamp = Timestamp {
86    ///     t: 1_000_000_000_000_000_001,
87    /// };
88    /// let result = timestamp.as_seconds();
89    /// assert!(result.is_err());
90    /// ```
91    ///
92    /// # Errors
93    ///
94    /// Returns `TimeError::AccuracyLoss` if the conversion is not exact.
95    pub fn as_seconds(&self) -> Result<f64, TimeError> {
96        const NANOSECONDS_PER_SECOND: f64 = 1_000_000_000.0;
97        #[allow(clippy::cast_precision_loss)]
98        let seconds = self.t as f64 / NANOSECONDS_PER_SECOND;
99
100        #[allow(clippy::cast_possible_truncation)]
101        #[allow(clippy::cast_sign_loss)]
102        if (seconds * NANOSECONDS_PER_SECOND) as u128 == self.t {
103            Ok(seconds)
104        } else {
105            Err(TimeError::AccuracyLoss)
106        }
107    }
108
109    #[must_use = "this returns the result of the operation"]
110    /// Converts the `Timestamp` to seconds as a floating-point number without checking for accuracy.
111    ///
112    /// # Examples
113    ///
114    /// ```
115    /// use transforms::time::Timestamp;
116    ///
117    /// let timestamp = Timestamp {
118    ///     t: 1_000_000_000_000_000_001,
119    /// };
120    /// let seconds = timestamp.as_seconds_unchecked();
121    /// assert_eq!(seconds, 1_000_000_000.0);
122    /// ```
123    #[allow(clippy::cast_precision_loss)]
124    pub fn as_seconds_unchecked(&self) -> f64 {
125        const NANOSECONDS_PER_SECOND: f64 = 1_000_000_000.0;
126        self.t as f64 / NANOSECONDS_PER_SECOND
127    }
128}
129
130impl Sub<Timestamp> for Timestamp {
131    type Output = Result<Duration, TimeError>;
132
133    fn sub(
134        self,
135        other: Timestamp,
136    ) -> Self::Output {
137        match self.t.cmp(&other.t) {
138            Ordering::Less => Err(TimeError::DurationUnderflow),
139            Ordering::Equal => Ok(Duration::from_secs(0)),
140            Ordering::Greater => {
141                let diff = self.t - other.t;
142                let seconds = diff / 1_000_000_000;
143                let nanos = (diff % 1_000_000_000) as u32;
144
145                if seconds > u128::from(u64::MAX) {
146                    return Err(TimeError::DurationOverflow);
147                }
148
149                #[allow(clippy::cast_possible_truncation)]
150                Ok(Duration::new(seconds as u64, nanos))
151            }
152        }
153    }
154}
155
156impl Add<Duration> for Timestamp {
157    type Output = Result<Timestamp, TimeError>;
158
159    fn add(
160        self,
161        rhs: Duration,
162    ) -> Self::Output {
163        (u128::from(rhs.as_secs()))
164            .checked_mul(1_000_000_000)
165            .and_then(|seconds| seconds.checked_add(u128::from(rhs.subsec_nanos())))
166            .and_then(|total_duration_nanos| self.t.checked_add(total_duration_nanos))
167            .map(|final_nanos| Timestamp { t: final_nanos })
168            .ok_or(TimeError::DurationOverflow)
169    }
170}
171
172impl Sub<Duration> for Timestamp {
173    type Output = Result<Timestamp, TimeError>;
174
175    fn sub(
176        self,
177        rhs: Duration,
178    ) -> Self::Output {
179        u128::from(rhs.as_secs())
180            .checked_mul(1_000_000_000)
181            .and_then(|seconds| seconds.checked_add(u128::from(rhs.subsec_nanos())))
182            .and_then(|total_duration_nanos| self.t.checked_sub(total_duration_nanos))
183            .map(|final_nanos| Timestamp { t: final_nanos })
184            .ok_or(TimeError::DurationUnderflow)
185    }
186}
187
188impl TimePoint for Timestamp {
189    fn static_timestamp() -> Self {
190        Timestamp::zero()
191    }
192
193    fn duration_since(
194        self,
195        earlier: Self,
196    ) -> Result<Duration, TimeError> {
197        self - earlier
198    }
199
200    fn checked_add(
201        self,
202        rhs: Duration,
203    ) -> Result<Self, TimeError> {
204        self + rhs
205    }
206
207    fn checked_sub(
208        self,
209        rhs: Duration,
210    ) -> Result<Self, TimeError> {
211        self - rhs
212    }
213
214    fn as_seconds(self) -> Result<f64, TimeError> {
215        Timestamp::as_seconds(&self)
216    }
217}
218
219#[cfg(test)]
220mod tests;