Skip to main content

ps_uuid/methods/
duration_to_ticks.rs

1use std::time::Duration;
2
3use crate::{DurationToTicksError, UUID};
4
5impl UUID {
6    /// Generates an RFC 4122 timestamp from a `Duration`.
7    ///
8    /// The timestamp represents the number of 100-nanosecond intervals since
9    /// the Gregorian epoch (1582-10-15 00:00:00 UTC).
10    ///
11    /// # Returns
12    ///
13    /// The number of full 100-ns segments is returned as a `u64`.
14    ///
15    /// # Errors
16    ///
17    /// - [`DurationToTicksError::TimestampOverflow`] is returned if the
18    ///   duration corresponds to a value of \( 2^{60} \) or greater, which
19    ///   would overflow the 60 bits available for the timestamp. This occurs
20    ///   for dates beyond 5236-03-31.
21    pub const fn duration_to_ticks(duration: Duration) -> Result<u64, DurationToTicksError> {
22        // `duration.as_nanos()` returns a `u128`, preventing overflow for
23        // large durations during this intermediate calculation.
24        let ticks = duration.as_nanos() / 100;
25
26        // The UUID timestamp is 60 bits. We must check if the calculated
27        // ticks can fit. `1u128 << 60` is the first value that is too large.
28        if ticks >= (1u128 << 60) {
29            return Err(DurationToTicksError::TimestampOverflow);
30        }
31
32        // The value is confirmed to be < 2^60, so it can be safely cast to u64.
33        #[allow(clippy::cast_possible_truncation)]
34        Ok(ticks as u64)
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    use std::time::Duration;
42
43    #[test]
44    fn zero_duration_yields_zero_ticks() {
45        let duration = Duration::from_nanos(0);
46        assert_eq!(UUID::duration_to_ticks(duration), Ok(0));
47    }
48
49    #[test]
50    fn duration_less_than_one_tick_truncates_to_zero() {
51        // A duration of 99 ns should produce 0 ticks, as it's less than one
52        // full 100-ns interval.
53        let duration = Duration::from_nanos(99);
54        assert_eq!(UUID::duration_to_ticks(duration), Ok(0));
55    }
56
57    #[test]
58    fn duration_of_exactly_one_tick() {
59        let duration = Duration::from_nanos(100);
60        assert_eq!(UUID::duration_to_ticks(duration), Ok(1));
61    }
62
63    #[test]
64    fn duration_truncates_to_full_ticks() {
65        // A duration of 199 ns should produce 1 tick, verifying that
66        // fractional ticks are truncated.
67        let duration = Duration::from_nanos(199);
68        assert_eq!(UUID::duration_to_ticks(duration), Ok(1));
69    }
70
71    #[test]
72    fn large_but_valid_duration() {
73        // Test with a large duration that is still well within the valid range.
74        let ticks = 1u64 << 50;
75        let nanos = 100 * ticks;
76        let duration = Duration::from_nanos(nanos);
77        assert_eq!(UUID::duration_to_ticks(duration), Ok(ticks));
78    }
79
80    #[test]
81    fn maximum_allowed_duration() {
82        // The maximum number of ticks is 2^60 - 1.
83        // We must use u128 for the nanosecond calculation to avoid overflow
84        // during the test setup itself.
85        let max_ticks = (1 << 60) - 1;
86        let secs = max_ticks / 10_000_000;
87        let nanos = (max_ticks % 10_000_000) * 100;
88        let duration = Duration::from_secs(secs) + Duration::from_nanos(nanos);
89        assert_eq!(UUID::duration_to_ticks(duration), Ok(max_ticks));
90    }
91
92    #[test]
93    fn duration_that_overflows() {
94        // A duration corresponding to 2^60 ticks should cause an overflow.
95        // We must use u128 for the nanosecond calculation.
96        let overflow_ticks = 1 << 60;
97        let micros = overflow_ticks / 10 + 1;
98        let duration = Duration::from_micros(micros);
99        assert_eq!(
100            UUID::duration_to_ticks(duration),
101            Err(DurationToTicksError::TimestampOverflow)
102        );
103    }
104}