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 {
72 let (secs, nanos) = ts.to_unix();
73 let millis = secs
74 .saturating_mul(1000)
75 .saturating_add(nanos as u64 / 1_000_000);
76
77 let (counter, counter_bits) = ts.counter();
78
79 let shift_counter_over_variant = |mut counter: u128, mut counter_bits: u32| {
83 let mask = u128::MAX << (cmp::min(128, counter_bits) - 12);
84 counter = (counter & !mask) | ((counter & mask) << 2);
85 counter_bits += 2;
86
87 (counter, counter_bits)
88 };
89
90 let mask_counter_into_random = |mut dst: u128, counter: u128, counter_bits: u32| {
92 dst &= u128::MAX >> counter_bits;
93 dst |= counter << (128 - counter_bits);
94
95 dst
96 };
97
98 let counter_and_random = match counter_bits {
99 0 => rng::u128(),
101 ..12 => {
104 let counter_bits = counter_bits as u32;
105
106 mask_counter_into_random(rng::u128(), counter, counter_bits)
107 }
108 ..74 => {
111 let (counter, counter_bits) =
112 shift_counter_over_variant(counter, counter_bits as u32);
113
114 mask_counter_into_random(rng::u128(), counter, counter_bits)
115 }
116 74.. => {
118 let (counter, _) = shift_counter_over_variant(counter, counter_bits as u32);
119
120 counter
121 }
122 };
123
124 Builder::from_unix_timestamp_millis(
125 millis,
126 &counter_and_random.to_be_bytes()[..10].try_into().unwrap(),
127 )
128 .into_uuid()
129 }
130}
131
132#[cfg(test)]
133mod 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 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 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}