Skip to main content

dis_rs/common/
timestamp.rs

1use crate::Serialize;
2use bytes::BufMut;
3use core::{fmt::Display, time::Duration};
4
5/// Number of [`TimeUnits`] in one hour.
6pub const TIME_UNITS_PER_HOUR: u32 = 1 << 31;
7
8/// Number of nanoseconds in one hour.
9const NANOS_PER_HOUR: u64 = 3_600_000_000_000;
10
11/// Number of nanoseconds in one [`TimeUnits`].
12#[allow(clippy::cast_precision_loss)]
13const NANOS_PER_TIME_UNIT: f64 = NANOS_PER_HOUR as f64 / TIME_UNITS_PER_HOUR as f64;
14
15/// Maximum number of nanoseconds.
16const MAX_NANOS: u64 = 3_599_999_998_324; // TODO: NANOS_PER_HOUR - (NANOS_PER_TIME_UNIT.round() as u64);
17
18/// Reference time at which the data contained in the *PDU* was generated.
19///
20/// Time is represented as [`TimeUnits`] elapsed since the beginning of the current hour in the selected time reference.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum Timestamp {
23    /// Time is *relative* to the simulation application issuing the *PDU*.
24    ///
25    /// A *relative* timestamp should be used when host clocks are not synchronized.
26    Relative(TimeUnits),
27    /// Time is *absolute* to the simulation time.
28    ///
29    /// An *absolute* timestamp should be used when host clocks are synchronized.
30    Absolute(TimeUnits),
31}
32
33impl Timestamp {
34    /// Bit indicating a *relative* timestamp.
35    pub const RELATIVE_BIT: u32 = 0b0;
36
37    /// Bit indicating an *absolute* timestamp.
38    pub const ABSOLUTE_BIT: u32 = 0b1;
39
40    /// Constructs a new `Timestamp` from `value`.
41    ///
42    /// # Examples
43    ///
44    /// ```
45    /// # use dis_rs::model::{Timestamp, TimeUnits};
46    /// let timestamp = Timestamp::new(20);
47    /// assert_eq!(timestamp, Timestamp::Relative(TimeUnits::new(10).unwrap()));
48    ///
49    /// let timestamp = Timestamp::new(21);
50    /// assert_eq!(timestamp, Timestamp::Absolute(TimeUnits::new(10).unwrap()));
51    /// ```
52    #[inline]
53    #[must_use]
54    pub const fn new(value: u32) -> Self {
55        // SAFETY: right-shifting a u32 by one bit guarantees the value is always less than 21474836478
56        #[allow(unsafe_code)]
57        let time_units = unsafe { TimeUnits::new_unchecked(value >> 1) };
58        let is_relative = (value & 1) == Self::RELATIVE_BIT;
59
60        if is_relative {
61            Self::Relative(time_units)
62        } else {
63            Self::Absolute(time_units)
64        }
65    }
66
67    /// Returns the [`TimeUnits`] contained by this `Timestamp`.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// # use dis_rs::model::{Timestamp, TimeUnits};
73    /// let timestamp = Timestamp::new(20);
74    /// assert_eq!(timestamp.time_units(), TimeUnits::new(10).unwrap());
75    ///
76    /// let timestamp = Timestamp::new(21);
77    /// assert_eq!(timestamp.time_units(), TimeUnits::new(10).unwrap());
78    /// ```
79    #[inline]
80    #[must_use]
81    pub const fn time_units(self) -> TimeUnits {
82        match self {
83            Self::Absolute(time_units) | Self::Relative(time_units) => time_units,
84        }
85    }
86
87    /// Returns `true` if this `Timestamp` is *relative*.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// # use dis_rs::model::{Timestamp, TimeUnits};
93    /// let timestamp = Timestamp::Relative(TimeUnits::new(10).unwrap());
94    /// assert!(timestamp.is_relative());
95    /// ```
96    #[inline]
97    #[must_use]
98    pub const fn is_relative(self) -> bool {
99        matches!(self, Self::Relative(_))
100    }
101
102    /// Returns `true` if this `Timestamp` is *absolute*.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// # use dis_rs::model::{Timestamp, TimeUnits};
108    /// let timestamp = Timestamp::Absolute(TimeUnits::new(10).unwrap());
109    /// assert!(timestamp.is_absolute());
110    /// ```
111    #[inline]
112    #[must_use]
113    pub const fn is_absolute(self) -> bool {
114        matches!(self, Self::Absolute(_))
115    }
116
117    /// Converts this `Timestamp` into a [`u32`] value.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// # use dis_rs::model::{Timestamp, TimeUnits};
123    /// let timestamp = Timestamp::Relative(TimeUnits::new(10).unwrap());
124    /// assert_eq!(timestamp.to_u32(), 20);
125    ///
126    /// let timestamp = Timestamp::Absolute(TimeUnits::new(10).unwrap());
127    /// assert_eq!(timestamp.to_u32(), 21);
128    /// ```
129    #[inline]
130    #[must_use]
131    pub const fn to_u32(self) -> u32 {
132        let time_units = self.time_units().inner();
133        let bit = match self {
134            Self::Relative(_) => Self::RELATIVE_BIT,
135            Self::Absolute(_) => Self::ABSOLUTE_BIT,
136        };
137
138        (time_units << 1) | bit
139    }
140
141    /// Converts this `Timestamp` into a [`Duration`].
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// # use core::time::Duration;
147    /// # use dis_rs::model::{Timestamp, TimeUnits};
148    /// let timestamp = Timestamp::Relative(TimeUnits::new(1_073_741_824).unwrap());
149    /// assert_eq!(timestamp.to_duration(), Duration::from_mins(30));
150    ///
151    /// let timestamp = Timestamp::Absolute(TimeUnits::new(1_073_741_824).unwrap());
152    /// assert_eq!(timestamp.to_duration(), Duration::from_mins(30));
153    /// ```
154    #[inline]
155    #[must_use]
156    pub fn to_duration(self) -> Duration {
157        // TODO: const
158
159        self.time_units().to_duration()
160    }
161}
162
163impl Serialize for Timestamp {
164    #[inline]
165    fn serialize(&self, buf: &mut bytes::BytesMut) -> u16 {
166        buf.put_u32(self.to_u32());
167        4
168    }
169}
170
171impl Default for Timestamp {
172    #[inline]
173    fn default() -> Self {
174        Self::Relative(TimeUnits::default())
175    }
176}
177
178impl From<u32> for Timestamp {
179    #[inline]
180    fn from(value: u32) -> Self {
181        Self::new(value)
182    }
183}
184
185impl From<Timestamp> for u32 {
186    #[inline]
187    fn from(value: Timestamp) -> Self {
188        value.to_u32()
189    }
190}
191
192impl From<Timestamp> for Duration {
193    #[inline]
194    fn from(value: Timestamp) -> Self {
195        value.to_duration()
196    }
197}
198
199impl PartialEq<u32> for Timestamp {
200    #[inline]
201    fn eq(&self, other: &u32) -> bool {
202        self.to_u32().eq(other)
203    }
204}
205
206impl PartialEq<Timestamp> for u32 {
207    #[inline]
208    fn eq(&self, other: &Timestamp) -> bool {
209        self.eq(&other.to_u32())
210    }
211}
212
213impl PartialOrd<u32> for Timestamp {
214    #[inline]
215    fn partial_cmp(&self, other: &u32) -> Option<core::cmp::Ordering> {
216        self.to_u32().partial_cmp(other)
217    }
218}
219
220impl PartialOrd<Timestamp> for u32 {
221    #[inline]
222    fn partial_cmp(&self, other: &Timestamp) -> Option<core::cmp::Ordering> {
223        self.partial_cmp(&other.to_u32())
224    }
225}
226
227#[cfg(feature = "serde")]
228mod serde {
229    use super::Timestamp;
230    use serde::{Deserialize, Deserializer, Serialize, Serializer};
231
232    impl Serialize for Timestamp {
233        #[inline]
234        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
235            self.to_u32().serialize(serializer)
236        }
237    }
238
239    impl<'de> Deserialize<'de> for Timestamp {
240        #[inline]
241        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
242            u32::deserialize(deserializer).map(Self::new)
243        }
244    }
245}
246
247/// Units of time elapsed since the beginning of the current hour.
248///
249/// `TimeUnits` is guaranteed to be less than `2147483648`.
250///
251/// A time unit is approximately `1676.38063431` nanoseconds.
252#[repr(transparent)]
253#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
254pub struct TimeUnits(u32);
255
256impl TimeUnits {
257    /// A zero `TimeUnits` representing the start of the hour.
258    pub const ZERO: Self = Self(0);
259
260    /// Maximum `TimeUnits` representing one time unit before the start of the next hour.
261    pub const MAX: Self = Self(TIME_UNITS_PER_HOUR - 1);
262
263    /// Constructs a new `TimeUnits` from `value` if less than `2147483648`.
264    ///
265    /// # Examples
266    ///
267    /// ```
268    /// # use dis_rs::model::TimeUnits;
269    /// let time_units = TimeUnits::new(1_073_741_824);
270    /// assert!(matches!(time_units, Some(_)));
271    /// assert_eq!(time_units.unwrap().inner(), 1_073_741_824);
272    ///
273    /// let time_units = TimeUnits::new(2_147_483_647);
274    /// assert!(matches!(time_units, Some(_)));
275    /// assert_eq!(time_units.unwrap().inner(), 2_147_483_647);
276    ///
277    /// let time_units = TimeUnits::new(2_147_483_648);
278    /// assert!(matches!(time_units, None));
279    /// ```
280    #[inline]
281    #[must_use]
282    pub const fn new(value: u32) -> Option<Self> {
283        if value < TIME_UNITS_PER_HOUR {
284            Some(Self(value))
285        } else {
286            None
287        }
288    }
289
290    /// Constructs a new `TimeUnits` from `value` without checking if less than `2147483648`.
291    ///
292    /// # Safety
293    ///
294    /// `value` must be less than `2147483648`.
295    ///
296    /// # Examples
297    ///
298    /// ```
299    /// # use dis_rs::model::TimeUnits;
300    /// let time_units = unsafe { TimeUnits::new_unchecked(1_073_741_824) };
301    /// assert_eq!(time_units.inner(), 1_073_741_824);
302    ///
303    /// let time_units = unsafe { TimeUnits::new_unchecked(2_147_483_647) };
304    /// assert_eq!(time_units.inner(), 2_147_483_647);
305    /// ```
306    #[inline]
307    #[must_use]
308    #[allow(unsafe_code)]
309    pub const unsafe fn new_unchecked(value: u32) -> Self {
310        Self(value)
311    }
312
313    /// Constructs a new `TimeUnits` from `duration` if the total number of nanoseconds
314    /// is less than or equal to `3599999998324`.
315    ///
316    /// # Examples
317    ///
318    /// ```
319    /// # use core::time::Duration;
320    /// # use dis_rs::model::TimeUnits;
321    /// let time_units = TimeUnits::from_duration(Duration::from_mins(30));
322    /// assert!(matches!(time_units, Some(_)));
323    /// assert_eq!(time_units.unwrap().to_duration(), Duration::from_mins(30));
324    ///
325    /// let time_units = TimeUnits::from_duration(Duration::from_nanos(3_599_999_998_324));
326    /// assert!(matches!(time_units, Some(_)));
327    /// assert_eq!(time_units.unwrap().to_duration(), Duration::from_nanos(3_599_999_998_324));
328    ///
329    /// let time_units = TimeUnits::from_duration(Duration::from_nanos(3_599_999_998_325));
330    /// assert!(matches!(time_units, None));
331    /// ```
332    #[inline]
333    #[must_use]
334    pub fn from_duration(duration: Duration) -> Option<Self> {
335        // TODO: const
336
337        let nanos = duration.as_nanos();
338
339        if nanos <= u128::from(MAX_NANOS) {
340            let time_units = crate::math::round((nanos as f64) / NANOS_PER_TIME_UNIT) as u32;
341            Some(Self(time_units))
342        } else {
343            None
344        }
345    }
346
347    /// Returns the inner [`u32`] value.
348    ///
349    /// # Examples
350    ///
351    /// ```
352    /// # use core::time::Duration;
353    /// # use dis_rs::model::TimeUnits;
354    /// let time_units = TimeUnits::new(1_073_741_824).unwrap();
355    /// assert_eq!(time_units.inner(), 1_073_741_824);
356    ///
357    /// let time_units = TimeUnits::from_duration(Duration::from_mins(30)).unwrap();
358    /// assert_eq!(time_units.inner(), 1_073_741_824);
359    /// ```
360    #[inline]
361    #[must_use]
362    pub const fn inner(self) -> u32 {
363        self.0
364    }
365
366    /// Returns `true` if this `TimeUnits` is [`Self::ZERO`].
367    ///
368    /// # Examples
369    ///
370    /// ```
371    /// # use dis_rs::model::TimeUnits;
372    /// let time_units = TimeUnits::ZERO;
373    /// assert!(time_units.is_zero());
374    /// assert_eq!(time_units, TimeUnits::new(0).unwrap());
375    /// ```
376    #[inline]
377    #[must_use]
378    pub const fn is_zero(self) -> bool {
379        self.0 == Self::ZERO.0
380    }
381
382    /// Returns `true` if this `TimeUnits` is [`Self::MAX`].
383    ///
384    /// # Examples
385    ///
386    /// ```
387    /// # use dis_rs::model::TimeUnits;
388    /// let time_units = TimeUnits::MAX;
389    /// assert!(time_units.is_max());
390    /// assert_eq!(time_units, TimeUnits::new(2_147_483_647).unwrap());
391    /// ```
392    #[inline]
393    #[must_use]
394    pub const fn is_max(self) -> bool {
395        self.0 == Self::MAX.0
396    }
397
398    /// Converts this `TimeUnits` into a [`Duration`].
399    ///
400    /// # Examples
401    ///
402    /// ```
403    /// # use core::time::Duration;
404    /// # use dis_rs::model::TimeUnits;
405    /// let time_units = TimeUnits::new(1_073_741_824).unwrap();
406    /// assert_eq!(time_units.to_duration(), Duration::from_mins(30));
407    ///
408    /// let time_units = TimeUnits::from_duration(Duration::from_mins(30)).unwrap();
409    /// assert_eq!(time_units.to_duration(), Duration::from_mins(30));
410    /// ```
411    #[inline]
412    #[must_use]
413    pub fn to_duration(self) -> Duration {
414        // TODO: const
415
416        let nanos = crate::math::round(f64::from(self.0) * NANOS_PER_TIME_UNIT) as u64;
417        Duration::from_nanos(nanos)
418    }
419}
420
421// TODO: uncomment when `to_duration()` is const
422//
423// const _: () = {
424//     assert!(TimeUnits::MAX.to_duration().as_nanos() == MAX_NANOS as u128);
425// };
426
427impl Default for TimeUnits {
428    #[inline]
429    fn default() -> Self {
430        Self::ZERO
431    }
432}
433
434impl Display for TimeUnits {
435    #[inline]
436    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
437        self.0.fmt(f)
438    }
439}
440
441impl From<TimeUnits> for u32 {
442    #[inline]
443    fn from(value: TimeUnits) -> Self {
444        value.inner()
445    }
446}
447
448impl From<TimeUnits> for Duration {
449    #[inline]
450    fn from(value: TimeUnits) -> Self {
451        value.to_duration()
452    }
453}
454
455impl PartialEq<u32> for TimeUnits {
456    #[inline]
457    fn eq(&self, other: &u32) -> bool {
458        self.0.eq(other)
459    }
460}
461
462impl PartialEq<TimeUnits> for u32 {
463    #[inline]
464    fn eq(&self, other: &TimeUnits) -> bool {
465        self.eq(&other.0)
466    }
467}
468
469impl PartialOrd<u32> for TimeUnits {
470    #[inline]
471    fn partial_cmp(&self, other: &u32) -> Option<core::cmp::Ordering> {
472        self.0.partial_cmp(other)
473    }
474}
475
476impl PartialOrd<TimeUnits> for u32 {
477    #[inline]
478    fn partial_cmp(&self, other: &TimeUnits) -> Option<core::cmp::Ordering> {
479        self.partial_cmp(&other.0)
480    }
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486    use bytes::BytesMut;
487    use core::cmp::Ordering;
488    use rstest::rstest;
489
490    #[rstest]
491    #[case(Timestamp::Relative(TimeUnits::ZERO), [0x00, 0x00, 0x00, 0x00])]
492    #[case(Timestamp::Relative(TimeUnits::new(152_709_948).unwrap()), [0x12, 0x34, 0x56, 0x78])]
493    #[case(Timestamp::Relative(TimeUnits::MAX), [0xff, 0xff, 0xff, 0xfe])]
494    #[case(Timestamp::Absolute(TimeUnits::ZERO), [0x00, 0x00, 0x00, 0x01])]
495    #[case(Timestamp::Absolute(TimeUnits::new(152_709_948).unwrap()), [0x12, 0x34, 0x56, 0x79])]
496    #[case(Timestamp::Absolute(TimeUnits::MAX), [0xff, 0xff, 0xff, 0xff])]
497    fn timestamp_parse_and_serialize_roundtrip(
498        #[case] timestamp: Timestamp,
499        #[case] expected: [u8; core::mem::size_of::<u32>()],
500    ) {
501        let (slice, timestamp_parse) =
502            nom::number::complete::be_u32::<&[u8], nom::error::Error<&[u8]>>(expected.as_slice())
503                .map(|(slice, timestamp)| (slice, Timestamp::new(timestamp)))
504                .unwrap();
505        assert!(slice.is_empty());
506        assert_eq!(timestamp_parse, timestamp);
507
508        let mut buf = BytesMut::with_capacity(expected.len());
509        timestamp.serialize(&mut buf);
510        assert_eq!(buf.len(), expected.len());
511        assert_eq!(buf, expected.as_slice());
512    }
513
514    #[rstest]
515    #[case(Timestamp::Relative(TimeUnits::ZERO), 0)]
516    #[case(Timestamp::Relative(TimeUnits::new(123_456_789).unwrap()), 246_913_578)]
517    #[case(Timestamp::Relative(TimeUnits::MAX), 4_294_967_294)]
518    #[case(Timestamp::Absolute(TimeUnits::ZERO), 1)]
519    #[case(Timestamp::Absolute(TimeUnits::new(123_456_789).unwrap()), 246_913_579)]
520    #[case(Timestamp::Absolute(TimeUnits::MAX), 4_294_967_295)]
521    fn timestamp_u32_roundtrip(#[case] timestamp: Timestamp, #[case] expected: u32) {
522        assert_eq!(Timestamp::new(expected), timestamp);
523        assert_eq!(Timestamp::from(expected), timestamp);
524        assert_eq!(timestamp.to_u32(), expected);
525        assert_eq!(u32::from(timestamp), expected);
526    }
527
528    #[rstest]
529    #[case(Timestamp::Relative(TimeUnits::ZERO), Duration::ZERO)]
530    #[case(Timestamp::Relative(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), Duration::from_mins(30))]
531    #[case(
532        Timestamp::Relative(TimeUnits::MAX),
533        Duration::from_nanos(3_599_999_998_324)
534    )]
535    #[case(Timestamp::Absolute(TimeUnits::ZERO), Duration::ZERO)]
536    #[case(Timestamp::Absolute(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), Duration::from_mins(30))]
537    #[case(
538        Timestamp::Absolute(TimeUnits::MAX),
539        Duration::from_nanos(3_599_999_998_324)
540    )]
541    fn timestamp_duration_consistency(#[case] timestamp: Timestamp, #[case] duration: Duration) {
542        assert_eq!(timestamp.to_duration(), duration);
543        assert_eq!(Duration::from(timestamp), duration);
544    }
545
546    #[rstest]
547    #[case(Timestamp::Relative(TimeUnits::ZERO))]
548    #[case(Timestamp::Relative(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()))]
549    #[case(Timestamp::Relative(TimeUnits::MAX))]
550    #[case(Timestamp::Absolute(TimeUnits::ZERO))]
551    #[case(Timestamp::Absolute(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()))]
552    #[case(Timestamp::Absolute(TimeUnits::MAX))]
553    fn timestamp_time_units_consistency(#[case] timestamp: Timestamp) {
554        assert_eq!(
555            timestamp.to_duration(),
556            timestamp.time_units().to_duration()
557        );
558    }
559
560    #[rstest]
561    #[case(Timestamp::Relative(TimeUnits::ZERO))]
562    #[case(Timestamp::Relative(TimeUnits::MAX))]
563    fn timestamp_is_relative(#[case] timestamp: Timestamp) {
564        assert!(timestamp.is_relative());
565        assert!(!timestamp.is_absolute());
566    }
567
568    #[rstest]
569    #[case(Timestamp::Absolute(TimeUnits::ZERO))]
570    #[case(Timestamp::Absolute(TimeUnits::MAX))]
571    fn timestamp_is_absolute(#[case] timestamp: Timestamp) {
572        assert!(!timestamp.is_relative());
573        assert!(timestamp.is_absolute());
574    }
575
576    #[test]
577    fn timestamp_default() {
578        assert_eq!(Timestamp::default(), Timestamp::Relative(TimeUnits::ZERO));
579    }
580
581    #[rstest]
582    #[case(Timestamp::Relative(TimeUnits::ZERO), 0, true)]
583    #[case(Timestamp::Relative(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), 2_147_483_648, true)]
584    #[case(Timestamp::Relative(TimeUnits::MAX), 4_294_967_294, true)]
585    #[case(Timestamp::Relative(TimeUnits::ZERO), 1, false)]
586    #[case(Timestamp::Relative(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), 2_147_483_649, false)]
587    #[case(Timestamp::Relative(TimeUnits::MAX), 4_294_967_295, false)]
588    #[case(Timestamp::Absolute(TimeUnits::ZERO), 1, true)]
589    #[case(Timestamp::Absolute(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), 2_147_483_649, true)]
590    #[case(Timestamp::Absolute(TimeUnits::MAX), 4_294_967_295, true)]
591    #[case(Timestamp::Absolute(TimeUnits::ZERO), 0, false)]
592    #[case(Timestamp::Absolute(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), 2_147_483_648, false)]
593    #[case(Timestamp::Absolute(TimeUnits::MAX), 4_294_967_294, false)]
594    fn timestamp_partial_eq(
595        #[case] timestamp: Timestamp,
596        #[case] value: u32,
597        #[case] expected: bool,
598    ) {
599        assert_eq!(timestamp == value, expected);
600        assert_eq!(value == timestamp, expected);
601    }
602
603    #[rstest]
604    #[case(Timestamp::Relative(TimeUnits::ZERO), 0, Ordering::Equal)]
605    #[case(Timestamp::Relative(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), 2_147_483_648, Ordering::Equal)]
606    #[case(Timestamp::Relative(TimeUnits::MAX), 4_294_967_294, Ordering::Equal)]
607    #[case(Timestamp::Relative(TimeUnits::ZERO), 4_294_967_294, Ordering::Less)]
608    #[case(Timestamp::Relative(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), 2_147_483_647, Ordering::Greater)]
609    #[case(Timestamp::Relative(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), 2_147_483_649, Ordering::Less)]
610    #[case(Timestamp::Relative(TimeUnits::MAX), 0, Ordering::Greater)]
611    #[case(Timestamp::Absolute(TimeUnits::ZERO), 1, Ordering::Equal)]
612    #[case(Timestamp::Absolute(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), 2_147_483_649, Ordering::Equal)]
613    #[case(Timestamp::Absolute(TimeUnits::MAX), 4_294_967_295, Ordering::Equal)]
614    #[case(Timestamp::Absolute(TimeUnits::ZERO), 4_294_967_295, Ordering::Less)]
615    #[case(Timestamp::Absolute(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), 2_147_483_648, Ordering::Greater)]
616    #[case(Timestamp::Absolute(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap()), 2_147_483_650, Ordering::Less)]
617    #[case(Timestamp::Absolute(TimeUnits::MAX), 0, Ordering::Greater)]
618    fn timestamp_partial_ord(
619        #[case] timestamp: Timestamp,
620        #[case] value: u32,
621        #[case] expected: Ordering,
622    ) {
623        let expected = Some(expected);
624
625        assert_eq!(timestamp.partial_cmp(&value), expected);
626        assert_eq!(
627            value.partial_cmp(&timestamp),
628            expected.map(Ordering::reverse)
629        );
630    }
631
632    #[rstest]
633    #[case(Timestamp::Relative(TimeUnits::ZERO), 1, Timestamp::Relative(TimeUnits::new(1).unwrap()))]
634    #[case(
635        Timestamp::Relative(TimeUnits::ZERO),
636        2_147_483_648,
637        Timestamp::Relative(TimeUnits::MAX)
638    )]
639    #[case(Timestamp::Relative(TimeUnits::new(2_147_483_646).unwrap()), 4_294_967_293, Timestamp::Relative(TimeUnits::MAX))]
640    #[case(Timestamp::Absolute(TimeUnits::ZERO), 2, Timestamp::Absolute(TimeUnits::new(1).unwrap()))]
641    #[case(
642        Timestamp::Absolute(TimeUnits::ZERO),
643        2_147_483_648,
644        Timestamp::Absolute(TimeUnits::MAX)
645    )]
646    #[case(Timestamp::Absolute(TimeUnits::new(2_147_483_646).unwrap()), 4_294_967_294, Timestamp::Absolute(TimeUnits::MAX))]
647    fn timestamp_partial_ord_transitive(
648        #[case] a: Timestamp,
649        #[case] b: u32,
650        #[case] c: Timestamp,
651    ) {
652        assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
653        assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
654        assert_eq!(b.partial_cmp(&c), Some(Ordering::Less));
655        assert_eq!(c.partial_cmp(&b), Some(Ordering::Greater));
656        assert_eq!(a.partial_cmp(&c.to_u32()), Some(Ordering::Less));
657        assert_eq!(c.partial_cmp(&a.to_u32()), Some(Ordering::Greater));
658    }
659
660    #[cfg(feature = "serde")]
661    #[rstest]
662    #[case(Timestamp::Relative(TimeUnits::ZERO), 0)]
663    #[case(Timestamp::Relative(TimeUnits::new(123_456_789).unwrap()), 246_913_578)]
664    #[case(Timestamp::Relative(TimeUnits::MAX), 4_294_967_294)]
665    #[case(Timestamp::Absolute(TimeUnits::ZERO), 1)]
666    #[case(Timestamp::Absolute(TimeUnits::new(123_456_789).unwrap()), 246_913_579)]
667    #[case(Timestamp::Absolute(TimeUnits::MAX), 4_294_967_295)]
668    fn timestamp_serde_roundtrip(#[case] timestamp: Timestamp, #[case] expected: u32) {
669        let expected = serde_json::Value::Number(serde_json::Number::from(expected));
670
671        let timestamp_de = serde_json::from_value::<Timestamp>(expected.clone()).unwrap();
672        assert_eq!(timestamp_de, timestamp);
673
674        let timestamp_ser = serde_json::to_value(timestamp).unwrap();
675        assert_eq!(timestamp_ser, expected);
676    }
677
678    #[rstest]
679    #[case(TimeUnits::ZERO, Duration::ZERO)]
680    #[case(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap(), Duration::from_mins(30))]
681    #[case(TimeUnits::MAX, Duration::from_nanos(3_599_999_998_324))]
682    fn time_units_duration_consistency(#[case] time_units: TimeUnits, #[case] duration: Duration) {
683        assert_eq!(TimeUnits::from_duration(duration).unwrap(), time_units);
684        assert_eq!(Duration::from(time_units), duration);
685        assert_eq!(time_units.to_duration(), duration);
686    }
687
688    #[rstest]
689    #[case(TimeUnits::ZERO, 0)]
690    #[case(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap(), TIME_UNITS_PER_HOUR / 2)]
691    #[case(TimeUnits::MAX, 2_147_483_647)]
692    fn time_units_inner_value(#[case] time_units: TimeUnits, #[case] value: u32) {
693        assert_eq!(time_units.inner(), value);
694        assert_eq!(u32::from(time_units), value);
695    }
696
697    #[rstest]
698    #[case(TimeUnits::ZERO.inner())]
699    #[case(TIME_UNITS_PER_HOUR / 2)]
700    #[case(TimeUnits::MAX.inner())]
701    fn time_units_ok(#[case] value: u32) {
702        let time_units = TimeUnits::new(value);
703        assert!(time_units.is_some());
704        assert_eq!(time_units.unwrap().inner(), value);
705    }
706
707    #[rstest]
708    #[case(TimeUnits::MAX.inner() + 1)]
709    #[case(TIME_UNITS_PER_HOUR)]
710    #[case(u32::MAX)]
711    fn time_units_err(#[case] value: u32) {
712        assert!(TimeUnits::new(value).is_none());
713    }
714
715    #[rstest]
716    fn time_units_is_zero() {
717        assert!(TimeUnits::ZERO.is_zero());
718        assert!(!TimeUnits::MAX.is_zero());
719        assert!(!TimeUnits::new(1).unwrap().is_zero());
720    }
721
722    #[rstest]
723    fn time_units_is_max() {
724        assert!(!TimeUnits::ZERO.is_max());
725        assert!(TimeUnits::MAX.is_max());
726        assert!(!TimeUnits::new(2_147_483_646).unwrap().is_max());
727    }
728
729    #[test]
730    fn time_units_default() {
731        assert_eq!(TimeUnits::default(), TimeUnits::ZERO);
732    }
733
734    #[rstest]
735    #[case(TimeUnits::ZERO, "0")]
736    #[case(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap(), "1073741824")]
737    #[case(TimeUnits::MAX, "2147483647")]
738    fn time_units_display(#[case] time_units: TimeUnits, #[case] expected: &str) {
739        use alloc::string::ToString;
740        assert_eq!(time_units.to_string(), expected);
741    }
742
743    #[rstest]
744    #[case(TimeUnits::ZERO, 0, true)]
745    #[case(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap(), 1_073_741_824, true)]
746    #[case(TimeUnits::MAX, 2_147_483_647, true)]
747    #[case(TimeUnits::ZERO, 1, false)]
748    #[case(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap(), 1_073_741_825, false)]
749    #[case(TimeUnits::MAX, 2_147_483_646, false)]
750    fn time_units_partial_eq(
751        #[case] time_units: TimeUnits,
752        #[case] value: u32,
753        #[case] expected: bool,
754    ) {
755        assert_eq!(time_units == value, expected);
756        assert_eq!(value == time_units, expected);
757    }
758
759    #[rstest]
760    #[case(TimeUnits::ZERO, 0, Ordering::Equal)]
761    #[case(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap(), 1_073_741_824, Ordering::Equal)]
762    #[case(TimeUnits::MAX, 2_147_483_647, Ordering::Equal)]
763    #[case(TimeUnits::ZERO, 2_147_483_647, Ordering::Less)]
764    #[case(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap(), 1_073_741_823, Ordering::Greater)]
765    #[case(TimeUnits::new(TIME_UNITS_PER_HOUR / 2).unwrap(), 1_073_741_825, Ordering::Less)]
766    #[case(TimeUnits::MAX, 0, Ordering::Greater)]
767    fn time_units_partial_ord(
768        #[case] time_units: TimeUnits,
769        #[case] value: u32,
770        #[case] expected: Ordering,
771    ) {
772        let expected = Some(expected);
773
774        assert_eq!(time_units.partial_cmp(&value), expected);
775        assert_eq!(
776            value.partial_cmp(&time_units),
777            expected.map(Ordering::reverse)
778        );
779    }
780
781    #[rstest]
782    #[case(TimeUnits::ZERO, 1, TimeUnits::new(2).unwrap())]
783    #[case(TimeUnits::ZERO, 1_073_741_824, TimeUnits::MAX)]
784    #[case(TimeUnits::new(2_147_483_645).unwrap(), 2_147_483_646, TimeUnits::MAX)]
785    fn time_units_partial_ord_transitive(
786        #[case] a: TimeUnits,
787        #[case] b: u32,
788        #[case] c: TimeUnits,
789    ) {
790        assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
791        assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
792        assert_eq!(b.partial_cmp(&c), Some(Ordering::Less));
793        assert_eq!(c.partial_cmp(&b), Some(Ordering::Greater));
794        assert_eq!(a.partial_cmp(&c), Some(Ordering::Less));
795        assert_eq!(c.partial_cmp(&a), Some(Ordering::Greater));
796    }
797}