uuid/
v7.rs

1//! The implementation for Version 7 UUIDs.
2//!
3//! Note that you need to enable the `v7` Cargo feature
4//! in order to use this module.
5
6use crate::{rng, std::convert::TryInto, timestamp::Timestamp, Builder, Uuid};
7
8impl Uuid {
9    /// Create a new version 7 UUID using the current time value.
10    ///
11    /// This method is a convenient alternative to [`Uuid::new_v7`] that uses the current system time
12    /// as the source timestamp. All UUIDs generated through this method by the same process are
13    /// guaranteed to be ordered by their creation.
14    #[cfg(feature = "std")]
15    pub fn now_v7() -> Self {
16        Self::new_v7(Timestamp::now(
17            crate::timestamp::context::shared_context_v7(),
18        ))
19    }
20
21    /// Create a new version 7 UUID using a time value and random bytes.
22    ///
23    /// When the `std` feature is enabled, you can also use [`Uuid::now_v7`].
24    ///
25    /// Note that usage of this method requires the `v7` feature of this crate
26    /// to be enabled.
27    ///
28    /// Also see [`Uuid::now_v7`] for a convenient way to generate version 7
29    /// UUIDs using the current system time.
30    ///
31    /// # Examples
32    ///
33    /// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit
34    /// random number. When supplied as such, the data will be combined
35    /// to ensure uniqueness and sortability at millisecond granularity.
36    ///
37    /// ```rust
38    /// # use uuid::{Uuid, Timestamp, NoContext};
39    /// let ts = Timestamp::from_unix(NoContext, 1497624119, 1234);
40    ///
41    /// let uuid = Uuid::new_v7(ts);
42    ///
43    /// assert!(
44    ///     uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7")
45    /// );
46    /// ```
47    ///
48    /// A v7 UUID can also be created with a counter to ensure batches of
49    /// UUIDs created together remain sortable:
50    ///
51    /// ```rust
52    /// # use uuid::{Uuid, Timestamp, ContextV7};
53    /// let context = ContextV7::new();
54    /// let uuid1 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234));
55    /// let uuid2 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234));
56    ///
57    /// assert!(uuid1 < uuid2);
58    /// ```
59    ///
60    /// # References
61    ///
62    /// * [UUID Version 7 in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-5.7)
63    pub fn new_v7(ts: Timestamp) -> Self {
64        let (secs, nanos) = ts.to_unix();
65        let millis = (secs * 1000).saturating_add(nanos as u64 / 1_000_000);
66
67        let mut counter_and_random = rng::u128();
68
69        let (mut counter, counter_bits) = ts.counter();
70
71        debug_assert!(counter_bits <= 128);
72
73        let mut counter_bits = counter_bits as u32;
74
75        // If the counter intersects the variant field then shift around it.
76        // This ensures that any bits set in the counter that would intersect
77        // the variant are still preserved
78        if counter_bits > 12 {
79            let mask = u128::MAX << (counter_bits - 12);
80
81            counter = (counter & !mask) | ((counter & mask) << 2);
82
83            counter_bits += 2;
84        }
85
86        counter_and_random &= u128::MAX.overflowing_shr(counter_bits).0;
87        counter_and_random |= counter
88            .overflowing_shl(128u32.saturating_sub(counter_bits))
89            .0;
90
91        Builder::from_unix_timestamp_millis(
92            millis,
93            &counter_and_random.to_be_bytes()[..10].try_into().unwrap(),
94        )
95        .into_uuid()
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    use crate::{std::string::ToString, ClockSequence, NoContext, Variant, Version};
104
105    #[cfg(all(
106        target_arch = "wasm32",
107        target_vendor = "unknown",
108        target_os = "unknown"
109    ))]
110    use wasm_bindgen_test::*;
111
112    #[test]
113    #[cfg_attr(
114        all(
115            target_arch = "wasm32",
116            target_vendor = "unknown",
117            target_os = "unknown"
118        ),
119        wasm_bindgen_test
120    )]
121    fn test_new() {
122        let ts: u64 = 1645557742000;
123
124        let seconds = ts / 1000;
125        let nanos = ((ts % 1000) * 1_000_000) as u32;
126
127        let uuid = Uuid::new_v7(Timestamp::from_unix(NoContext, seconds, nanos));
128        let uustr = uuid.hyphenated().to_string();
129
130        assert_eq!(uuid.get_version(), Some(Version::SortRand));
131        assert_eq!(uuid.get_variant(), Variant::RFC4122);
132        assert!(uuid.hyphenated().to_string().starts_with("017f22e2-79b0-7"));
133
134        // Ensure parsing the same UUID produces the same timestamp
135        let parsed = Uuid::parse_str(uustr.as_str()).unwrap();
136
137        assert_eq!(uuid, parsed);
138    }
139
140    #[test]
141    #[cfg_attr(
142        all(
143            target_arch = "wasm32",
144            target_vendor = "unknown",
145            target_os = "unknown"
146        ),
147        wasm_bindgen_test
148    )]
149    #[cfg(feature = "std")]
150    fn test_now() {
151        let uuid = Uuid::now_v7();
152
153        assert_eq!(uuid.get_version(), Some(Version::SortRand));
154        assert_eq!(uuid.get_variant(), Variant::RFC4122);
155    }
156
157    #[test]
158    #[cfg_attr(
159        all(
160            target_arch = "wasm32",
161            target_vendor = "unknown",
162            target_os = "unknown"
163        ),
164        wasm_bindgen_test
165    )]
166    fn test_sorting() {
167        let time1: u64 = 1_496_854_535;
168        let time_fraction1: u32 = 812_000_000;
169
170        let time2 = time1 + 4000;
171        let time_fraction2 = time_fraction1;
172
173        let uuid1 = Uuid::new_v7(Timestamp::from_unix(NoContext, time1, time_fraction1));
174        let uuid2 = Uuid::new_v7(Timestamp::from_unix(NoContext, time2, time_fraction2));
175
176        assert!(uuid1.as_bytes() < uuid2.as_bytes());
177        assert!(uuid1.to_string() < uuid2.to_string());
178    }
179
180    #[test]
181    #[cfg_attr(
182        all(
183            target_arch = "wasm32",
184            target_vendor = "unknown",
185            target_os = "unknown"
186        ),
187        wasm_bindgen_test
188    )]
189    fn test_new_timestamp_roundtrip() {
190        let time: u64 = 1_496_854_535;
191        let time_fraction: u32 = 812_000_000;
192
193        let ts = Timestamp::from_unix(NoContext, time, time_fraction);
194
195        let uuid = Uuid::new_v7(ts);
196
197        let decoded_ts = uuid.get_timestamp().unwrap();
198
199        assert_eq!(ts.to_unix(), decoded_ts.to_unix());
200    }
201
202    #[test]
203    #[cfg_attr(
204        all(
205            target_arch = "wasm32",
206            target_vendor = "unknown",
207            target_os = "unknown"
208        ),
209        wasm_bindgen_test
210    )]
211    fn test_new_max_context() {
212        struct MaxContext;
213
214        #[cfg(test)]
215        impl ClockSequence for MaxContext {
216            type Output = u128;
217
218            fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output {
219                u128::MAX
220            }
221
222            fn usable_bits(&self) -> usize {
223                128
224            }
225        }
226
227        let time: u64 = 1_496_854_535;
228        let time_fraction: u32 = 812_000_000;
229
230        // Ensure we don't overflow here
231        let ts = Timestamp::from_unix(MaxContext, time, time_fraction);
232
233        let uuid = Uuid::new_v7(ts);
234
235        assert_eq!(uuid.get_version(), Some(Version::SortRand));
236        assert_eq!(uuid.get_variant(), Variant::RFC4122);
237
238        let decoded_ts = uuid.get_timestamp().unwrap();
239
240        assert_eq!(ts.to_unix(), decoded_ts.to_unix());
241    }
242}