Skip to main content

cyclonedds/
time.rs

1use crate::Duration;
2
3/// An absolute point in time represented as nanoseconds since the UNIX epoch.
4///
5/// Used in DDS for sample source timestamps and other time-stamped metadata.
6#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
7pub struct Time {
8    pub(crate) inner: cyclonedds_sys::dds_time_t,
9}
10
11impl Time {
12    /// A sentinel value representing a time that will never occur.
13    ///
14    /// Used in DDS APIs to indicate an invalid or unset timestamp.
15    pub const NEVER: Self = Time {
16        inner: cyclonedds_sys::TIME_NEVER,
17    };
18
19    /// Creates a [`Time`] from a nanosecond timestamp.
20    ///
21    /// # Examples
22    ///
23    /// ```
24    /// use cyclonedds::Time;
25    ///
26    /// let t = Time::from_nanos(1_000_000_000);
27    /// ```
28    #[must_use]
29    pub const fn from_nanos(nanos: i64) -> Self {
30        Self { inner: nanos }
31    }
32
33    /// Creates a [`Time`] from a millisecond timestamp.
34    ///
35    /// # Examples
36    ///
37    /// ```
38    /// use cyclonedds::Time;
39    ///
40    /// let t = Time::from_millis(1_000);
41    /// ```
42    #[must_use]
43    pub const fn from_millis(millis: i64) -> Self {
44        Self {
45            inner: millis * 1_000_000,
46        }
47    }
48
49    /// Creates a [`Time`] from a second timestamp.
50    ///
51    /// # Examples
52    ///
53    /// ```
54    /// use cyclonedds::Time;
55    ///
56    /// let t = Time::from_secs(1);
57    /// ```
58    #[must_use]
59    pub const fn from_secs(secs: i64) -> Self {
60        Self {
61            inner: secs * 1_000_000_000,
62        }
63    }
64
65    /// Returns the timestamp as nanoseconds since the UNIX epoch.
66    ///
67    /// # Examples
68    ///
69    /// ```
70    /// use cyclonedds::Time;
71    ///
72    /// let t = Time::from_secs(1);
73    /// assert_eq!(t.as_nanos(), 1_000_000_000);
74    /// ```
75    #[must_use]
76    pub const fn as_nanos(&self) -> i64 {
77        self.inner
78    }
79
80    /// Returns the time elapsed since this timestamp as a [`Duration`].
81    ///
82    /// # Errors
83    ///
84    /// Returns [`Error::BadParameter`](crate::Error::BadParameter) if the
85    /// system time cannot be converted or if this timestamp is in the future.
86    pub fn elapsed(&self) -> crate::Result<Duration> {
87        Time::try_from(std::time::SystemTime::now()).and_then(|now| {
88            if self.inner <= now.inner {
89                let nanos = now.inner - self.inner;
90                Ok(Duration::from_nanos(nanos))
91            } else {
92                Err(crate::Error::BadParameter)
93            }
94        })
95    }
96
97    /// Adds a [`Duration`] to this time, returning `None` on overflow.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use cyclonedds::{Duration, Time};
103    ///
104    /// let t = Time::from_secs(1).checked_add(Duration::from_secs(1));
105    /// assert_eq!(t, Some(Time::from_secs(2)));
106    /// ```
107    #[must_use]
108    pub fn checked_add(&self, duration: Duration) -> Option<Self> {
109        self.inner
110            .checked_add(duration.inner)
111            .map(|inner| Self { inner })
112    }
113
114    /// Subtracts a [`Duration`] from this time, returning `None` on underflow.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// use cyclonedds::{Duration, Time};
120    ///
121    /// let t = Time::from_secs(2).checked_sub(Duration::from_secs(1));
122    /// assert_eq!(t, Some(Time::from_secs(1)));
123    /// ```
124    #[must_use]
125    pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
126        self.inner
127            .checked_sub(duration.inner)
128            .map(|inner| Self { inner })
129    }
130}
131
132impl TryFrom<std::time::SystemTime> for Time {
133    type Error = crate::Error;
134
135    fn try_from(value: std::time::SystemTime) -> Result<Self, Self::Error> {
136        let value = value
137            .duration_since(std::time::SystemTime::UNIX_EPOCH)
138            .map_err(|_err| crate::Error::BadParameter)?;
139
140        let inner = cyclonedds_sys::dds_time_t::try_from(value.as_nanos())
141            .map_err(|_err| crate::Error::BadParameter)?;
142
143        if inner == Self::NEVER.inner {
144            Err(crate::Error::BadParameter)
145        } else {
146            Ok(Self { inner })
147        }
148    }
149}
150
151impl From<Time> for std::time::SystemTime {
152    fn from(time: Time) -> Self {
153        std::time::SystemTime::UNIX_EPOCH
154            + std::time::Duration::from_nanos(time.inner.cast_unsigned())
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::Duration;
162
163    #[test]
164    fn test_time_create() {
165        let nanos = 1_000_000_000;
166        let time = Time::from_nanos(nanos);
167        assert_eq!(time.inner, nanos);
168    }
169
170    #[test]
171    fn test_time_from_std_system_time() {
172        let nanos = 1_000_000_000;
173        let standard =
174            std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_nanos(nanos as u64);
175        let time = Time::try_from(standard).unwrap();
176        assert_eq!(time.inner, nanos);
177    }
178
179    #[test]
180    // Windows SystemTime uses 100ns intervals in FILETIME, so Time::NEVER is
181    // lost in the round-trip (it rounds down by 7ns).
182    #[cfg(not(target_os = "windows"))]
183    fn test_time_from_never_std_system_time() {
184        let nanos = Time::NEVER.inner as u64;
185        let standard = std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_nanos(nanos);
186        let result = Time::try_from(standard).unwrap_err();
187        assert_eq!(result, crate::Error::BadParameter);
188    }
189
190    #[test]
191    fn test_time_from_out_of_range_std_system_time() {
192        let standard = std::time::SystemTime::UNIX_EPOCH - std::time::Duration::from_nanos(100);
193        let result = Time::try_from(standard).unwrap_err();
194        assert_eq!(result, crate::Error::BadParameter);
195
196        let nanos = u64::MAX;
197        let standard = std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_nanos(nanos);
198        let result = Time::try_from(standard).unwrap_err();
199        assert_eq!(result, crate::Error::BadParameter);
200    }
201
202    #[test]
203    fn test_time_from_millis() {
204        let time = Time::from_millis(1_000);
205        assert_eq!(time.inner, 1_000_000_000);
206    }
207
208    #[test]
209    fn test_time_from_secs() {
210        let time = Time::from_secs(1);
211        assert_eq!(time.inner, 1_000_000_000);
212    }
213
214    #[test]
215    fn test_time_as_nanos() {
216        let time = Time::from_nanos(1_000_000_000);
217        assert_eq!(time.as_nanos(), 1_000_000_000);
218    }
219
220    #[test]
221    fn test_time_checked_add() {
222        let result = Time::from_secs(1).checked_add(Duration::from_secs(2));
223        assert_eq!(result, Some(Time::from_secs(3)));
224    }
225
226    #[test]
227    fn test_time_checked_add_overflow() {
228        let result = Time::from_nanos(i64::MAX).checked_add(Duration::from_nanos(1));
229        assert_eq!(result, None);
230    }
231
232    #[test]
233    fn test_time_checked_sub() {
234        let result = Time::from_secs(3).checked_sub(Duration::from_secs(1));
235        assert_eq!(result, Some(Time::from_secs(2)));
236    }
237
238    #[test]
239    fn test_time_checked_sub_underflow() {
240        let result = Time::from_nanos(i64::MIN).checked_sub(Duration::from_nanos(1));
241        assert_eq!(result, None);
242    }
243
244    #[test]
245    fn test_time_elapsed_increases() {
246        let before = Time::try_from(std::time::SystemTime::now()).unwrap();
247        std::thread::sleep(std::time::Duration::from_millis(10));
248        let elapsed = before.elapsed().unwrap();
249        assert!(elapsed.as_nanos() >= 10_000_000);
250    }
251
252    #[test]
253    fn test_time_elapsed_future_returns_error() {
254        let future = Time::from_nanos(i64::MAX);
255        assert_eq!(future.elapsed().unwrap_err(), crate::Error::BadParameter);
256    }
257
258    #[test]
259    fn test_to_std_system_time() {
260        let nanos = 11_222_33;
261        let time = Time::from_nanos(nanos);
262        let expected =
263            std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_nanos(nanos as u64);
264        let actual: std::time::SystemTime = time.into();
265        assert_eq!(actual, expected);
266    }
267}