Coverage Report

Created: 2026-09-19 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/uuid-1.23.1/src/v7.rs
Line
Count
Source
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
6
use core::cmp;
7
8
use crate::{rng, timestamp::Timestamp, Builder, Uuid};
9
10
impl Uuid {
11
    /// Create a new version 7 UUID using the current time value.
12
    ///
13
    /// This method is a convenient alternative to [`Uuid::new_v7`] that uses the current system time
14
    /// as the source timestamp. All UUIDs generated through this method by the same process are
15
    /// guaranteed to be ordered by their creation.
16
    #[cfg(feature = "std")]
17
35.4k
    pub fn now_v7() -> Self {
18
35.4k
        Self::new_v7(Timestamp::now(
19
35.4k
            crate::timestamp::context::shared_context_v7(),
20
        ))
21
35.4k
    }
22
23
    /// Create a new version 7 UUID using a time value and random bytes.
24
    ///
25
    /// When the `std` feature is enabled, you can also use [`Uuid::now_v7`].
26
    ///
27
    /// Note that usage of this method requires the `v7` feature of this crate
28
    /// to be enabled.
29
    ///
30
    /// Also see [`Uuid::now_v7`] for a convenient way to generate version 7
31
    /// UUIDs using the current system time.
32
    ///
33
    /// # Counter treatment
34
    ///
35
    /// This method accepts a [`Timestamp`] which may include a counter value.
36
    /// Only up to 74 bits of the counter value will be used when constructing
37
    /// the UUID. Any unused counter bits will be filled with random data.
38
    ///
39
    /// # Examples
40
    ///
41
    /// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit
42
    /// random number. When supplied as such, the data will be combined
43
    /// to ensure uniqueness and sortability at millisecond granularity.
44
    ///
45
    /// ```rust
46
    /// # use uuid::{Uuid, Timestamp, NoContext};
47
    /// let ts = Timestamp::from_unix(NoContext, 1497624119, 1234);
48
    ///
49
    /// let uuid = Uuid::new_v7(ts);
50
    ///
51
    /// assert!(
52
    ///     uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7")
53
    /// );
54
    /// ```
55
    ///
56
    /// A v7 UUID can also be created with a counter to ensure batches of
57
    /// UUIDs created together remain sortable:
58
    ///
59
    /// ```rust
60
    /// # use uuid::{Uuid, Timestamp, ContextV7};
61
    /// let context = ContextV7::new();
62
    /// let uuid1 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234));
63
    /// let uuid2 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234));
64
    ///
65
    /// assert!(uuid1 < uuid2);
66
    /// ```
67
    ///
68
    /// # References
69
    ///
70
    /// * [UUID Version 7 in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-5.7)
71
35.4k
    pub fn new_v7(ts: Timestamp) -> Self {
72
35.4k
        let (secs, nanos) = ts.to_unix();
73
35.4k
        let millis = secs
74
35.4k
            .saturating_mul(1000)
75
35.4k
            .saturating_add(nanos as u64 / 1_000_000);
76
77
35.4k
        let (counter, counter_bits) = ts.counter();
78
79
        // If the counter intersects the variant field then shift around it.
80
        // This ensures that any bits set in the counter that would intersect
81
        // the variant are still preserved
82
35.4k
        let shift_counter_over_variant = |mut counter: u128, mut counter_bits: u32| {
83
35.4k
            let mask = u128::MAX << (cmp::min(128, counter_bits) - 12);
84
35.4k
            counter = (counter & !mask) | ((counter & mask) << 2);
85
35.4k
            counter_bits += 2;
86
87
35.4k
            (counter, counter_bits)
88
35.4k
        };
89
90
        // Mask `counter_bits` of the `counter` into `dst`
91
35.4k
        let mask_counter_into_random = |mut dst: u128, counter: u128, counter_bits: u32| {
92
35.4k
            dst &= u128::MAX >> counter_bits;
93
35.4k
            dst |= counter << (128 - counter_bits);
94
95
35.4k
            dst
96
35.4k
        };
97
98
35.4k
        let counter_and_random = match counter_bits {
99
            // The counter doesn't contribute any bits
100
0
            0 => rng::u128(),
101
            // The counter doesn't intersect the variant field
102
            // It needs to be merged with random data
103
35.4k
            ..12 => {
104
0
                let counter_bits = counter_bits as u32;
105
106
0
                mask_counter_into_random(rng::u128(), counter, counter_bits)
107
            }
108
            // `rand_a` (12 bits) + `rand_b` (62 bits) + `var` (2 bits)
109
            // The counter needs to be shifted around the variant and merged with random data
110
35.4k
            ..74 => {
111
35.4k
                let (counter, counter_bits) =
112
35.4k
                    shift_counter_over_variant(counter, counter_bits as u32);
113
114
35.4k
                mask_counter_into_random(rng::u128(), counter, counter_bits)
115
            }
116
            // The counter overrides all bits
117
0
            74.. => {
118
0
                let (counter, _) = shift_counter_over_variant(counter, counter_bits as u32);
119
120
0
                counter
121
            }
122
        };
123
124
35.4k
        Builder::from_unix_timestamp_millis(
125
35.4k
            millis,
126
35.4k
            &counter_and_random.to_be_bytes()[..10].try_into().unwrap(),
127
        )
128
35.4k
        .into_uuid()
129
35.4k
    }
130
}
131
132
#[cfg(test)]
133
mod tests {
134
    use super::*;
135
136
    use crate::{std::string::ToString, ClockSequence, NoContext, Variant, Version};
137
138
    #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
139
    use wasm_bindgen_test::*;
140
141
    #[test]
142
    #[cfg_attr(
143
        all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
144
        wasm_bindgen_test
145
    )]
146
    fn test_new() {
147
        let ts: u64 = 1645557742000;
148
149
        let seconds = ts / 1000;
150
        let nanos = ((ts % 1000) * 1_000_000) as u32;
151
152
        let uuid = Uuid::new_v7(Timestamp::from_unix(NoContext, seconds, nanos));
153
        let uustr = uuid.hyphenated().to_string();
154
155
        assert_eq!(uuid.get_version(), Some(Version::SortRand));
156
        assert_eq!(uuid.get_variant(), Variant::RFC4122);
157
        assert!(uuid.hyphenated().to_string().starts_with("017f22e2-79b0-7"));
158
159
        // Ensure parsing the same UUID produces the same timestamp
160
        let parsed = Uuid::parse_str(uustr.as_str()).unwrap();
161
162
        assert_eq!(uuid, parsed);
163
    }
164
165
    #[test]
166
    #[cfg_attr(
167
        all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
168
        wasm_bindgen_test
169
    )]
170
    #[cfg(feature = "std")]
171
    fn test_now() {
172
        let uuid = Uuid::now_v7();
173
174
        assert_eq!(uuid.get_version(), Some(Version::SortRand));
175
        assert_eq!(uuid.get_variant(), Variant::RFC4122);
176
    }
177
178
    #[test]
179
    #[cfg_attr(
180
        all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
181
        wasm_bindgen_test
182
    )]
183
    fn test_sorting() {
184
        let time1: u64 = 1_496_854_535;
185
        let time_fraction1: u32 = 812_000_000;
186
187
        let time2 = time1 + 4000;
188
        let time_fraction2 = time_fraction1;
189
190
        let uuid1 = Uuid::new_v7(Timestamp::from_unix(NoContext, time1, time_fraction1));
191
        let uuid2 = Uuid::new_v7(Timestamp::from_unix(NoContext, time2, time_fraction2));
192
193
        assert!(uuid1.as_bytes() < uuid2.as_bytes());
194
        assert!(uuid1.to_string() < uuid2.to_string());
195
    }
196
197
    #[test]
198
    #[cfg_attr(
199
        all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
200
        wasm_bindgen_test
201
    )]
202
    fn test_new_timestamp_roundtrip() {
203
        let time: u64 = 1_496_854_535;
204
        let time_fraction: u32 = 812_000_000;
205
206
        let ts = Timestamp::from_unix(NoContext, time, time_fraction);
207
208
        let uuid = Uuid::new_v7(ts);
209
210
        let decoded_ts = uuid.get_timestamp().unwrap();
211
212
        assert_eq!(ts.to_unix(), decoded_ts.to_unix());
213
    }
214
215
    #[test]
216
    #[cfg_attr(
217
        all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
218
        wasm_bindgen_test
219
    )]
220
    fn test_new_max_context() {
221
        struct MaxContext;
222
223
        impl ClockSequence for MaxContext {
224
            type Output = u128;
225
226
            fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output {
227
                u128::MAX
228
            }
229
230
            fn usable_bits(&self) -> usize {
231
                128
232
            }
233
        }
234
235
        let time: u64 = 1_496_854_535;
236
        let time_fraction: u32 = 812_000_000;
237
238
        // Ensure we don't overflow here
239
        let ts = Timestamp::from_unix(MaxContext, time, time_fraction);
240
241
        let uuid = Uuid::new_v7(ts);
242
243
        assert_eq!(uuid.get_version(), Some(Version::SortRand));
244
        assert_eq!(uuid.get_variant(), Variant::RFC4122);
245
246
        let decoded_ts = uuid.get_timestamp().unwrap();
247
248
        assert_eq!(ts.to_unix(), decoded_ts.to_unix());
249
    }
250
251
    #[test]
252
    #[cfg_attr(
253
        all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
254
        wasm_bindgen_test
255
    )]
256
    fn test_new_counter_range() {
257
        for (width, eq) in [
258
            (0, false),
259
            (3, false),
260
            (43, false),
261
            (74, true),
262
            (u8::MAX, true),
263
        ] {
264
            for counter in [0u128, u128::MAX] {
265
                let ts = Timestamp::from_unix_time(1_700_000_000, 0, counter, width);
266
267
                let a = Uuid::new_v7(ts);
268
                let b = Uuid::new_v7(ts);
269
270
                assert_eq!((1_700_000_000, 0), a.get_timestamp().unwrap().to_unix());
271
                assert_eq!((1_700_000_000, 0), b.get_timestamp().unwrap().to_unix());
272
273
                assert_eq!(
274
                    eq,
275
                    a == b,
276
                    "{:>032x} = {:>032x} with counter {counter:x} should be {eq:?}",
277
                    a.as_u128(),
278
                    b.as_u128()
279
                );
280
            }
281
        }
282
    }
283
284
    #[test]
285
    #[cfg_attr(
286
        all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
287
        wasm_bindgen_test
288
    )]
289
    fn test_new_max() {
290
        let ts = Timestamp::from_unix_time(u64::MAX, 0, 0, 0);
291
        let uuid = Uuid::new_v7(ts);
292
293
        let decoded_ts = uuid.get_timestamp().unwrap();
294
295
        assert_eq!((281474976710, 655000000), decoded_ts.to_unix());
296
    }
297
}