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, NonNilUuid, 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 timestamp = Timestamp::from_unix(NoContext, seconds, nanos);
142
143 let uuid = Uuid::new_v7(timestamp);
144 let uustr = uuid.hyphenated().to_string();
145
146 assert_eq!(
147 NonNilUuid::new_v7(timestamp).get().get_timestamp(),
148 uuid.get_timestamp()
149 );
150 assert_eq!(uuid.get_version(), Some(Version::SortRand));
151 assert_eq!(uuid.get_variant(), Variant::RFC4122);
152 assert!(uuid.hyphenated().to_string().starts_with("017f22e2-79b0-7"));
153
154 let parsed = Uuid::parse_str(uustr.as_str()).unwrap();
156
157 assert_eq!(uuid, parsed);
158 }
159
160 #[test]
161 #[cfg_attr(
162 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
163 wasm_bindgen_test
164 )]
165 #[cfg(feature = "std")]
166 fn test_now() {
167 let uuid = Uuid::now_v7();
168
169 assert_eq!(uuid.get_version(), Some(Version::SortRand));
170 assert_eq!(uuid.get_variant(), Variant::RFC4122);
171 assert!(!NonNilUuid::now_v7().get().is_nil());
172 }
173
174 #[test]
175 #[cfg_attr(
176 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
177 wasm_bindgen_test
178 )]
179 fn test_sorting() {
180 let time1: u64 = 1_496_854_535;
181 let time_fraction1: u32 = 812_000_000;
182
183 let time2 = time1 + 4000;
184 let time_fraction2 = time_fraction1;
185
186 let uuid1 = Uuid::new_v7(Timestamp::from_unix(NoContext, time1, time_fraction1));
187 let uuid2 = Uuid::new_v7(Timestamp::from_unix(NoContext, time2, time_fraction2));
188
189 assert!(uuid1.as_bytes() < uuid2.as_bytes());
190 assert!(uuid1.to_string() < uuid2.to_string());
191 }
192
193 #[test]
194 #[cfg_attr(
195 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
196 wasm_bindgen_test
197 )]
198 fn test_new_timestamp_roundtrip() {
199 let time: u64 = 1_496_854_535;
200 let time_fraction: u32 = 812_000_000;
201
202 let ts = Timestamp::from_unix(NoContext, time, time_fraction);
203
204 let uuid = Uuid::new_v7(ts);
205
206 let decoded_ts = uuid.get_timestamp().unwrap();
207
208 assert_eq!(ts.to_unix(), decoded_ts.to_unix());
209 }
210
211 #[test]
212 #[cfg_attr(
213 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
214 wasm_bindgen_test
215 )]
216 fn test_new_max_context() {
217 struct MaxContext;
218
219 impl ClockSequence for MaxContext {
220 type Output = u128;
221
222 fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output {
223 u128::MAX
224 }
225
226 fn usable_bits(&self) -> usize {
227 128
228 }
229 }
230
231 let time: u64 = 1_496_854_535;
232 let time_fraction: u32 = 812_000_000;
233
234 let ts = Timestamp::from_unix(MaxContext, time, time_fraction);
236
237 let uuid = Uuid::new_v7(ts);
238
239 assert_eq!(uuid.get_version(), Some(Version::SortRand));
240 assert_eq!(uuid.get_variant(), Variant::RFC4122);
241
242 let decoded_ts = uuid.get_timestamp().unwrap();
243
244 assert_eq!(ts.to_unix(), decoded_ts.to_unix());
245 }
246
247 #[test]
248 #[cfg_attr(
249 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
250 wasm_bindgen_test
251 )]
252 fn test_new_counter_range() {
253 for (width, eq) in [
254 (0, false),
255 (3, false),
256 (43, false),
257 (74, true),
258 (u8::MAX, true),
259 ] {
260 for counter in [0u128, u128::MAX] {
261 let ts = Timestamp::from_unix_time(1_700_000_000, 0, counter, width);
262
263 let a = Uuid::new_v7(ts);
264 let b = Uuid::new_v7(ts);
265
266 assert_eq!((1_700_000_000, 0), a.get_timestamp().unwrap().to_unix());
267 assert_eq!((1_700_000_000, 0), b.get_timestamp().unwrap().to_unix());
268
269 assert_eq!(
270 eq,
271 a == b,
272 "{:>032x} = {:>032x} with counter {counter:x} should be {eq:?}",
273 a.as_u128(),
274 b.as_u128()
275 );
276 }
277 }
278 }
279
280 #[test]
281 #[cfg_attr(
282 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
283 wasm_bindgen_test
284 )]
285 fn test_42bit_counter_is_fully_preserved() {
286 fn rand_a(uuid: &Uuid) -> u16 {
287 let b = uuid.as_bytes();
288
289 (((b[6] & 0x0f) as u16) << 8) | b[7] as u16
290 }
291
292 fn counter_bits(uuid: &Uuid) -> (u16, [u8; 4]) {
294 let b = uuid.as_bytes();
295
296 (rand_a(uuid), [b[8] & 0x3f, b[9], b[10], b[11]])
297 }
298
299 for bit in 0..42 {
300 let counter = 1u128 << bit;
301
302 let with = Uuid::new_v7(Timestamp::from_unix_time(0, 0, counter, 42));
303 let without = Uuid::new_v7(Timestamp::from_unix_time(0, 0, 0, 42));
304
305 assert_ne!(
306 counter_bits(&with),
307 counter_bits(&without),
308 "counter bit {bit} did not reach the UUID"
309 );
310 }
311
312 let all_ones = Uuid::new_v7(Timestamp::from_unix_time(0, 0, (1 << 42) - 1, 42));
313 assert_eq!(0x0fff, rand_a(&all_ones));
314 assert_eq!(Variant::RFC4122, all_ones.get_variant());
315
316 let before = Uuid::new_v7(Timestamp::from_unix_time(1, 0, 0x3f_ffff_ffff, 42));
317 let after = Uuid::new_v7(Timestamp::from_unix_time(1, 0, 0x40_0000_0000, 42));
318 assert!(before < after, "{before} should sort before {after}");
319 }
320
321 #[test]
322 #[cfg_attr(
323 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
324 wasm_bindgen_test
325 )]
326 fn test_additional_precision_preserves_sorting() {
327 for start in (0..1_000_000).step_by(50_000) {
328 for delta in [50_000u32, 100_000, 200_000] {
329 if start + delta >= 1_000_000 {
330 continue;
331 }
332
333 let context = crate::ContextV7::new().with_additional_precision();
334
335 let earlier = Uuid::new_v7(Timestamp::from_unix(&context, 1_700_000_000, start));
336 let later =
337 Uuid::new_v7(Timestamp::from_unix(&context, 1_700_000_000, start + delta));
338
339 assert!(
340 earlier < later,
341 "{start}ns gave {earlier} and {}ns gave {later}",
342 start + delta
343 );
344 }
345 }
346 }
347
348 #[test]
349 #[cfg_attr(
350 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
351 wasm_bindgen_test
352 )]
353 fn test_new_max() {
354 let ts = Timestamp::from_unix_time(u64::MAX, 0, 0, 0);
355 let uuid = Uuid::new_v7(ts);
356
357 let decoded_ts = uuid.get_timestamp().unwrap();
358
359 assert_eq!((281474976710, 655000000), decoded_ts.to_unix());
360 }
361}