Skip to main content

cyclonedds/
duration.rs

1/// A relative span of time represented as nanoseconds.
2///
3/// Used in DDS for timeouts, lease durations, deadlines, and other
4/// interval-based [`QoS`](crate::QoS) policies.
5#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
6pub struct Duration {
7    pub(crate) inner: cyclonedds_sys::dds_duration_t,
8}
9
10impl Duration {
11    /// A sentinel value representing an infinite timeout.
12    ///
13    /// Pass this to any API that accepts a [`Duration`] to block indefinitely.
14    pub const INFINITE: Self = Duration {
15        inner: cyclonedds_sys::DURATION_INFINITE,
16    };
17
18    /// Creates a [`Duration`] from a nanosecond value.
19    ///
20    /// Unlike [`std::time::Duration::from_nanos`], this accepts an [`i64`]
21    /// rather than a [`u64`].
22    ///
23    /// # Examples
24    ///
25    /// ```
26    /// use cyclonedds::Duration;
27    ///
28    /// let d = Duration::from_nanos(1_000_000);
29    /// ```
30    #[must_use]
31    pub const fn from_nanos(nanos: i64) -> Self {
32        Self { inner: nanos }
33    }
34
35    /// Creates a [`Duration`] from a millisecond value.
36    ///
37    /// # Examples
38    ///
39    /// ```
40    /// use cyclonedds::Duration;
41    ///
42    /// let d = Duration::from_millis(100);
43    /// ```
44    #[must_use]
45    pub const fn from_millis(millis: i64) -> Self {
46        Self {
47            inner: millis * 1_000_000,
48        }
49    }
50
51    /// Creates a [`Duration`] from a second value.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use cyclonedds::Duration;
57    ///
58    /// let d = Duration::from_secs(5);
59    /// ```
60    #[must_use]
61    pub const fn from_secs(secs: i64) -> Self {
62        Self {
63            inner: secs * 1_000_000_000,
64        }
65    }
66
67    /// Returns the duration in nanoseconds.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use cyclonedds::Duration;
73    ///
74    /// assert_eq!(Duration::from_secs(1).as_nanos(), 1_000_000_000);
75    /// ```
76    #[must_use]
77    pub const fn as_nanos(&self) -> i64 {
78        self.inner
79    }
80
81    /// Returns the duration in whole milliseconds.
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// use cyclonedds::Duration;
87    ///
88    /// assert_eq!(Duration::from_secs(1).as_millis(), 1_000);
89    /// ```
90    #[must_use]
91    pub const fn as_millis(&self) -> i64 {
92        self.inner / 1_000_000
93    }
94
95    /// Returns the duration in whole seconds.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use cyclonedds::Duration;
101    ///
102    /// assert_eq!(Duration::from_millis(1_500).as_secs(), 1);
103    /// ```
104    #[must_use]
105    pub const fn as_secs(&self) -> i64 {
106        self.inner / 1_000_000_000
107    }
108
109    /// Returns `true` if this duration equals [`Duration::INFINITE`].
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use cyclonedds::Duration;
115    ///
116    /// assert!(Duration::INFINITE.is_infinite());
117    /// assert!(!Duration::from_secs(5).is_infinite());
118    /// ```
119    #[must_use]
120    pub const fn is_infinite(&self) -> bool {
121        self.inner == Self::INFINITE.inner
122    }
123
124    /// Adds two durations, returning `None` on overflow.
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use cyclonedds::Duration;
130    ///
131    /// let d = Duration::from_secs(1).checked_add(Duration::from_secs(2));
132    /// assert_eq!(d, Some(Duration::from_secs(3)));
133    /// ```
134    #[must_use]
135    pub fn checked_add(self, rhs: Self) -> Option<Self> {
136        self.inner
137            .checked_add(rhs.inner)
138            .map(|inner| Self { inner })
139    }
140
141    /// Subtracts a duration, returning `None` on underflow.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use cyclonedds::Duration;
147    ///
148    /// let d = Duration::from_secs(3).checked_sub(Duration::from_secs(1));
149    /// assert_eq!(d, Some(Duration::from_secs(2)));
150    /// ```
151    #[must_use]
152    pub fn checked_sub(self, rhs: Self) -> Option<Self> {
153        self.inner
154            .checked_sub(rhs.inner)
155            .map(|inner| Self { inner })
156    }
157
158    /// Multiplies a duration by a scalar, returning `None` on overflow.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use cyclonedds::Duration;
164    ///
165    /// let d = Duration::from_secs(2).checked_mul(3);
166    /// assert_eq!(d, Some(Duration::from_secs(6)));
167    /// ```
168    #[must_use]
169    pub fn checked_mul(self, rhs: i64) -> Option<Self> {
170        self.inner.checked_mul(rhs).map(|inner| Self { inner })
171    }
172}
173
174impl std::ops::Add for Duration {
175    type Output = Self;
176
177    /// Adds two durations.
178    ///
179    /// # Panics
180    ///
181    /// Panics on overflow. Use [`checked_add`](Duration::checked_add) for a
182    /// non-panicking alternative.
183    fn add(self, rhs: Self) -> Self::Output {
184        self.checked_add(rhs)
185            .expect("overflow when adding durations")
186    }
187}
188
189impl std::ops::Sub for Duration {
190    type Output = Self;
191
192    /// Subtracts a duration.
193    ///
194    /// # Panics
195    ///
196    /// Panics on underflow. Use [`checked_sub`](Duration::checked_sub) for a
197    /// non-panicking alternative.
198    fn sub(self, rhs: Self) -> Self::Output {
199        self.checked_sub(rhs)
200            .expect("underflow when subtracting durations")
201    }
202}
203
204impl std::ops::Mul<i64> for Duration {
205    type Output = Self;
206
207    /// Multiplies a duration by a scalar.
208    ///
209    /// # Panics
210    ///
211    /// Panics on overflow. Use [`checked_mul`](Duration::checked_mul) for a
212    /// non-panicking alternative.
213    fn mul(self, rhs: i64) -> Self::Output {
214        self.checked_mul(rhs)
215            .expect("overflow when multiplying duration")
216    }
217}
218
219impl TryFrom<std::time::Duration> for Duration {
220    type Error = crate::Error;
221
222    fn try_from(value: std::time::Duration) -> Result<Self, Self::Error> {
223        let inner = cyclonedds_sys::dds_duration_t::try_from(value.as_nanos())
224            .map_err(|_err| crate::Error::BadParameter)?;
225
226        if inner == Self::INFINITE.inner {
227            Err(crate::Error::BadParameter)
228        } else {
229            Ok(Self { inner })
230        }
231    }
232}
233
234impl From<Duration> for std::time::Duration {
235    fn from(duration: Duration) -> Self {
236        std::time::Duration::from_nanos(duration.inner.cast_unsigned())
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_duration_from_nanos() {
246        let nanos = 1_000_000_000;
247        let duration = Duration::from_nanos(nanos);
248        assert_eq!(duration.inner, nanos);
249    }
250    #[test]
251    fn test_duration_from_millis() {
252        let duration = Duration::from_millis(100);
253        assert_eq!(duration.inner, 100_000_000);
254    }
255
256    #[test]
257    fn test_duration_from_secs() {
258        let duration = Duration::from_secs(5);
259        assert_eq!(duration.inner, 5_000_000_000);
260    }
261
262    #[test]
263    fn test_duration_as_nanos() {
264        let duration = Duration::from_nanos(1_000_000_000);
265        assert_eq!(duration.as_nanos(), 1_000_000_000);
266    }
267
268    #[test]
269    fn test_duration_as_millis() {
270        let duration = Duration::from_secs(1);
271        assert_eq!(duration.as_millis(), 1_000);
272    }
273
274    #[test]
275    fn test_duration_from_std_duration() {
276        let nanos = 1_000_000_000;
277        let standard = std::time::Duration::from_nanos(nanos as u64);
278        let duration = Duration::try_from(standard).unwrap();
279        assert_eq!(duration.inner, nanos);
280    }
281
282    #[test]
283    fn test_duration_from_infinite_std_duration() {
284        let nanos = Duration::INFINITE.inner;
285        let standard = std::time::Duration::from_nanos(nanos as u64);
286        let result = Duration::try_from(standard).unwrap_err();
287        assert_eq!(result, crate::Error::BadParameter);
288    }
289
290    #[test]
291    fn test_duration_from_out_of_range_std_duration() {
292        let nanos = u64::MAX;
293        let standard = std::time::Duration::from_nanos(nanos);
294        let result = Duration::try_from(standard).unwrap_err();
295        assert_eq!(result, crate::Error::BadParameter);
296    }
297
298    #[test]
299    fn test_duration_as_millis_truncates() {
300        let duration = Duration::from_millis(1) + Duration::from_nanos(999_999);
301        assert_eq!(duration.as_millis(), 1);
302    }
303
304    #[test]
305    fn test_duration_as_secs() {
306        let duration = Duration::from_millis(1_500);
307        assert_eq!(duration.as_secs(), 1);
308    }
309
310    #[test]
311    fn test_duration_as_secs_truncates() {
312        let duration = Duration::from_millis(999);
313        assert_eq!(duration.as_secs(), 0);
314    }
315
316    #[test]
317    fn test_duration_is_infinite() {
318        assert!(Duration::INFINITE.is_infinite());
319    }
320
321    #[test]
322    fn test_duration_is_not_infinite() {
323        assert!(!Duration::from_secs(5).is_infinite());
324    }
325
326    #[test]
327    fn test_duration_checked_add() {
328        let result = Duration::from_secs(1).checked_add(Duration::from_secs(2));
329        assert_eq!(result, Some(Duration::from_secs(3)));
330    }
331
332    #[test]
333    fn test_duration_checked_add_overflow() {
334        let result = Duration::from_nanos(i64::MAX).checked_add(Duration::from_nanos(1));
335        assert_eq!(result, None);
336    }
337
338    #[test]
339    fn test_duration_add() {
340        let result = Duration::from_secs(1) + Duration::from_secs(2);
341        assert_eq!(result, Duration::from_secs(3));
342    }
343
344    #[test]
345    #[should_panic(expected = "overflow when adding durations")]
346    fn test_duration_add_overflow() {
347        let _ = Duration::from_nanos(i64::MAX) + Duration::from_nanos(1);
348    }
349    #[test]
350    fn test_duration_checked_sub() {
351        let result = Duration::from_secs(3).checked_sub(Duration::from_secs(1));
352        assert_eq!(result, Some(Duration::from_secs(2)));
353    }
354
355    #[test]
356    fn test_duration_checked_sub_underflow() {
357        let result = Duration::from_nanos(i64::MIN).checked_sub(Duration::from_nanos(1));
358        assert_eq!(result, None);
359    }
360
361    #[test]
362    fn test_duration_sub() {
363        let result = Duration::from_secs(3) - Duration::from_secs(1);
364        assert_eq!(result, Duration::from_secs(2));
365    }
366
367    #[test]
368    #[should_panic(expected = "underflow when subtracting durations")]
369    fn test_duration_sub_underflow() {
370        let _ = Duration::from_nanos(i64::MIN) - Duration::from_nanos(1);
371    }
372
373    #[test]
374    fn test_duration_checked_mul() {
375        let result = Duration::from_secs(2).checked_mul(3);
376        assert_eq!(result, Some(Duration::from_secs(6)));
377    }
378
379    #[test]
380    fn test_duration_checked_mul_overflow() {
381        let result = Duration::from_nanos(i64::MAX).checked_mul(2);
382        assert_eq!(result, None);
383    }
384
385    #[test]
386    fn test_duration_mul() {
387        let result = Duration::from_secs(2) * 3;
388        assert_eq!(result, Duration::from_secs(6));
389    }
390
391    #[test]
392    fn test_to_std_duration() {
393        let nanos = 11_222_33;
394        let duration = Duration::from_nanos(nanos);
395        let expected = std::time::Duration::from_nanos(nanos as u64);
396        let actual = std::time::Duration::from(duration);
397        assert_eq!(actual, expected);
398    }
399
400    #[test]
401    #[should_panic(expected = "overflow when multiplying duration")]
402    fn test_duration_mul_overflow() {
403        let _ = Duration::from_nanos(i64::MAX) * 2;
404    }
405}