1use core::cmp;
7
8use crate::{rng, timestamp::Timestamp, Builder, Uuid};
9
10impl Uuid {
11 #[cfg(feature = "std")]
17 pub fn now_v7() -> Self {
18 Self::new_v7(Timestamp::now(
19 crate::timestamp::context::shared_context_v7(),
20 ))
21 }
22
23 pub fn new_v7(ts: Timestamp) -> Self {
73 let (secs, nanos) = ts.to_unix();
74 let millis = secs
75 .saturating_mul(1000)
76 .saturating_add(nanos as u64 / 1_000_000);
77
78 let (mut counter, counter_bits) = ts.counter();
79
80 const RAND_A_BITS: u32 = 12;
89 const PAYLOAD_BITS: u32 = RAND_A_BITS + 62;
90
91 let mut counter_bits = cmp::min(counter_bits as u32, 128);
93 if counter_bits > PAYLOAD_BITS {
94 counter >>= counter_bits - PAYLOAD_BITS;
95 counter_bits = PAYLOAD_BITS;
96 }
97
98 if counter_bits > RAND_A_BITS {
100 let mask = u128::MAX << (counter_bits - RAND_A_BITS);
101 counter = (counter & !mask) | ((counter & mask) << 2);
102 counter_bits += 2;
103 }
104
105 let counter_and_random = if counter_bits == 0 {
106 rng::u128()
107 } else {
108 let shift = 124 - counter_bits;
109
110 (rng::u128() & (u128::MAX >> (128 - shift))) | (counter << shift)
111 };
112
113 Builder::from_unix_timestamp_millis(
114 millis,
115 &counter_and_random.to_be_bytes()[..10].try_into().unwrap(),
116 )
117 .into_uuid()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 use crate::{std::string::ToString, ClockSequence, NoContext, Variant, Version};
126
127 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
128 use wasm_bindgen_test::*;
129
130 #[test]
131 #[cfg_attr(
132 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
133 wasm_bindgen_test
134 )]
135 fn test_new() {
136 let ts: u64 = 1645557742000;
137
138 let seconds = ts / 1000;
139 let nanos = ((ts % 1000) * 1_000_000) as u32;
140
141 let uuid = Uuid::new_v7(Timestamp::from_unix(NoContext, seconds, nanos));
142 let uustr = uuid.hyphenated().to_string();
143
144 assert_eq!(uuid.get_version(), Some(Version::SortRand));
145 assert_eq!(uuid.get_variant(), Variant::RFC4122);
146 assert!(uuid.hyphenated().to_string().starts_with("017f22e2-79b0-7"));
147
148 let parsed = Uuid::parse_str(uustr.as_str()).unwrap();
150
151 assert_eq!(uuid, parsed);
152 }
153
154 #[test]
155 #[cfg_attr(
156 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
157 wasm_bindgen_test
158 )]
159 #[cfg(feature = "std")]
160 fn test_now() {
161 let uuid = Uuid::now_v7();
162
163 assert_eq!(uuid.get_version(), Some(Version::SortRand));
164 assert_eq!(uuid.get_variant(), Variant::RFC4122);
165 }
166
167 #[test]
168 #[cfg_attr(
169 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
170 wasm_bindgen_test
171 )]
172 fn test_sorting() {
173 let time1: u64 = 1_496_854_535;
174 let time_fraction1: u32 = 812_000_000;
175
176 let time2 = time1 + 4000;
177 let time_fraction2 = time_fraction1;
178
179 let uuid1 = Uuid::new_v7(Timestamp::from_unix(NoContext, time1, time_fraction1));
180 let uuid2 = Uuid::new_v7(Timestamp::from_unix(NoContext, time2, time_fraction2));
181
182 assert!(uuid1.as_bytes() < uuid2.as_bytes());
183 assert!(uuid1.to_string() < uuid2.to_string());
184 }
185
186 #[test]
187 #[cfg_attr(
188 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
189 wasm_bindgen_test
190 )]
191 fn test_new_timestamp_roundtrip() {
192 let time: u64 = 1_496_854_535;
193 let time_fraction: u32 = 812_000_000;
194
195 let ts = Timestamp::from_unix(NoContext, time, time_fraction);
196
197 let uuid = Uuid::new_v7(ts);
198
199 let decoded_ts = uuid.get_timestamp().unwrap();
200
201 assert_eq!(ts.to_unix(), decoded_ts.to_unix());
202 }
203
204 #[test]
205 #[cfg_attr(
206 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
207 wasm_bindgen_test
208 )]
209 fn test_new_max_context() {
210 struct MaxContext;
211
212 impl ClockSequence for MaxContext {
213 type Output = u128;
214
215 fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output {
216 u128::MAX
217 }
218
219 fn usable_bits(&self) -> usize {
220 128
221 }
222 }
223
224 let time: u64 = 1_496_854_535;
225 let time_fraction: u32 = 812_000_000;
226
227 let ts = Timestamp::from_unix(MaxContext, time, time_fraction);
229
230 let uuid = Uuid::new_v7(ts);
231
232 assert_eq!(uuid.get_version(), Some(Version::SortRand));
233 assert_eq!(uuid.get_variant(), Variant::RFC4122);
234
235 let decoded_ts = uuid.get_timestamp().unwrap();
236
237 assert_eq!(ts.to_unix(), decoded_ts.to_unix());
238 }
239
240 #[test]
241 #[cfg_attr(
242 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
243 wasm_bindgen_test
244 )]
245 fn test_new_counter_range() {
246 for (width, eq) in [
247 (0, false),
248 (3, false),
249 (43, false),
250 (74, true),
251 (u8::MAX, true),
252 ] {
253 for counter in [0u128, u128::MAX] {
254 let ts = Timestamp::from_unix_time(1_700_000_000, 0, counter, width);
255
256 let a = Uuid::new_v7(ts);
257 let b = Uuid::new_v7(ts);
258
259 assert_eq!((1_700_000_000, 0), a.get_timestamp().unwrap().to_unix());
260 assert_eq!((1_700_000_000, 0), b.get_timestamp().unwrap().to_unix());
261
262 assert_eq!(
263 eq,
264 a == b,
265 "{:>032x} = {:>032x} with counter {counter:x} should be {eq:?}",
266 a.as_u128(),
267 b.as_u128()
268 );
269 }
270 }
271 }
272
273 #[test]
274 #[cfg_attr(
275 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
276 wasm_bindgen_test
277 )]
278 fn test_42bit_counter_is_fully_preserved() {
279 fn rand_a(uuid: &Uuid) -> u16 {
280 let b = uuid.as_bytes();
281
282 (((b[6] & 0x0f) as u16) << 8) | b[7] as u16
283 }
284
285 fn counter_bits(uuid: &Uuid) -> (u16, [u8; 4]) {
287 let b = uuid.as_bytes();
288
289 (rand_a(uuid), [b[8] & 0x3f, b[9], b[10], b[11]])
290 }
291
292 for bit in 0..42 {
293 let counter = 1u128 << bit;
294
295 let with = Uuid::new_v7(Timestamp::from_unix_time(0, 0, counter, 42));
296 let without = Uuid::new_v7(Timestamp::from_unix_time(0, 0, 0, 42));
297
298 assert_ne!(
299 counter_bits(&with),
300 counter_bits(&without),
301 "counter bit {bit} did not reach the UUID"
302 );
303 }
304
305 let all_ones = Uuid::new_v7(Timestamp::from_unix_time(0, 0, (1 << 42) - 1, 42));
306 assert_eq!(0x0fff, rand_a(&all_ones));
307 assert_eq!(Variant::RFC4122, all_ones.get_variant());
308
309 let before = Uuid::new_v7(Timestamp::from_unix_time(1, 0, 0x3f_ffff_ffff, 42));
310 let after = Uuid::new_v7(Timestamp::from_unix_time(1, 0, 0x40_0000_0000, 42));
311 assert!(before < after, "{before} should sort before {after}");
312 }
313
314 #[test]
315 #[cfg_attr(
316 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
317 wasm_bindgen_test
318 )]
319 fn test_additional_precision_preserves_sorting() {
320 for start in (0..1_000_000).step_by(50_000) {
321 for delta in [50_000u32, 100_000, 200_000] {
322 if start + delta >= 1_000_000 {
323 continue;
324 }
325
326 let context = crate::ContextV7::new().with_additional_precision();
327
328 let earlier = Uuid::new_v7(Timestamp::from_unix(&context, 1_700_000_000, start));
329 let later =
330 Uuid::new_v7(Timestamp::from_unix(&context, 1_700_000_000, start + delta));
331
332 assert!(
333 earlier < later,
334 "{start}ns gave {earlier} and {}ns gave {later}",
335 start + delta
336 );
337 }
338 }
339 }
340
341 #[test]
342 #[cfg_attr(
343 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
344 wasm_bindgen_test
345 )]
346 fn test_new_max() {
347 let ts = Timestamp::from_unix_time(u64::MAX, 0, 0, 0);
348 let uuid = Uuid::new_v7(ts);
349
350 let decoded_ts = uuid.get_timestamp().unwrap();
351
352 assert_eq!((281474976710, 655000000), decoded_ts.to_unix());
353 }
354}