1use std::time::{Duration, SystemTime, UNIX_EPOCH};
2
3use crate::{Gregorian, UUID};
4
5const FILETIME_EPOCH_OFFSET: u64 = 116_444_736_000_000_000;
7
8const NCS_EPOCH: Duration = Duration::from_secs(315_532_800);
10
11impl UUID {
12 #[must_use]
16 pub fn get_timestamp(&self) -> Option<SystemTime> {
17 match (self.get_version(), self.get_variant()) {
18 (Some(1 | 2), crate::Variant::OSF) => {
20 let time_low = u32::from_be_bytes([
21 self.bytes[0],
22 self.bytes[1],
23 self.bytes[2],
24 self.bytes[3],
25 ]);
26 let time_mid = u16::from_be_bytes([self.bytes[4], self.bytes[5]]);
27 let time_hi = u16::from_be_bytes([self.bytes[6], self.bytes[7]]) & 0x0FFF;
28 let timestamp: u64 =
29 (u64::from(time_hi) << 48) | (u64::from(time_mid) << 32) | u64::from(time_low);
30
31 #[allow(clippy::cast_possible_truncation)]
32 Some(
33 Gregorian::epoch()
34 + Duration::new(
35 timestamp / 10_000_000,
36 ((timestamp % 10_000_000) * 100) as u32,
37 ),
38 )
39 }
40 (Some(6), crate::Variant::OSF) => {
42 let time_high = u32::from_be_bytes([
43 self.bytes[0],
44 self.bytes[1],
45 self.bytes[2],
46 self.bytes[3],
47 ]);
48 let time_mid = u16::from_be_bytes([self.bytes[4], self.bytes[5]]);
49 let time_low = u16::from_be_bytes([self.bytes[6], self.bytes[7]]) & 0x0FFF;
50 let timestamp: u64 = (u64::from(time_high) << 28)
51 | (u64::from(time_mid) << 12)
52 | u64::from(time_low);
53 Some(Gregorian::epoch() + Duration::from_nanos(timestamp * 100))
54 }
55 (Some(7), crate::Variant::OSF) => {
57 let mut ms_bytes = [0u8; 8];
58 ms_bytes[2..8].copy_from_slice(&self.bytes[0..6]);
59 let ms = u64::from_be_bytes(ms_bytes);
60 Some(UNIX_EPOCH + Duration::from_millis(ms))
61 }
62 (_, crate::Variant::DCOM) => {
64 let time_low = u32::from_le_bytes([
66 self.bytes[0],
67 self.bytes[1],
68 self.bytes[2],
69 self.bytes[3],
70 ]);
71 let time_mid = u16::from_le_bytes([self.bytes[4], self.bytes[5]]);
72 let time_hi = u16::from_le_bytes([self.bytes[6], self.bytes[7]]);
73 let filetime: u64 =
74 (u64::from(time_hi) << 48) | (u64::from(time_mid) << 32) | u64::from(time_low);
75
76 #[allow(clippy::cast_possible_truncation)]
77 if filetime < FILETIME_EPOCH_OFFSET {
78 let unix_100ns = FILETIME_EPOCH_OFFSET - filetime;
79
80 Some(
81 UNIX_EPOCH
82 - Duration::new(
83 unix_100ns / 10_000_000,
84 (unix_100ns % 10_000_000) as u32 * 100,
85 ),
86 )
87 } else {
88 let unix_100ns = filetime - FILETIME_EPOCH_OFFSET;
89
90 Some(
91 UNIX_EPOCH
92 + Duration::new(
93 unix_100ns / 10_000_000,
94 (unix_100ns % 10_000_000) as u32 * 100,
95 ),
96 )
97 }
98 }
99 (_, crate::Variant::NCS) => {
101 let mut ts_bytes = [0u8; 8];
103 ts_bytes[2..8].copy_from_slice(&self.bytes[0..6]);
104 let ts = u64::from_be_bytes(ts_bytes);
105
106 let ncs_epoch = UNIX_EPOCH + NCS_EPOCH;
107 Some(ncs_epoch + Duration::from_micros(ts * 4))
108 }
109 _ => None,
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 #![allow(clippy::cast_possible_truncation, clippy::expect_used)]
117 use crate::Variant;
118
119 use super::*;
120 use std::time::{Duration, UNIX_EPOCH};
121
122 #[test]
123 fn v1_and_v2_timestamp_roundtrip() {
124 let t = Gregorian::epoch() + Duration::from_secs(1_000_000_000);
125 let uuid = UUID::from_parts_v1(0x6fc1_0000, 0x86f2, 0x23, 0x1234, [1, 2, 3, 4, 5, 6]);
126 let ts = uuid
127 .get_timestamp()
128 .expect("timestamp should be present for time-based UUID");
129 assert_eq!(ts, t, "v1 timestamp roundtrip failed");
130 }
131
132 #[test]
133 fn v6_timestamp_roundtrip() {
134 let t = Gregorian::epoch() + Duration::from_secs(1_000_000_000);
135 let uuid = UUID::from_parts_v6(0x0238_6f26, 0xfc10, 0x6000, 0x1234, [1, 2, 3, 4, 5, 6]);
136 let ts = uuid
137 .get_timestamp()
138 .expect("timestamp should be present for time-based UUID");
139 assert_eq!(ts, t, "v6 timestamp roundtrip failed");
140 }
141
142 #[test]
143 fn v7_timestamp_roundtrip() {
144 let ms = 1_700_000_000_000u64;
145 let uuid = UUID::from_parts_v7(ms, 0, 0);
146 let ts = uuid
147 .get_timestamp()
148 .expect("timestamp should be present for time-based UUID");
149 let expected = UNIX_EPOCH + Duration::from_millis(ms);
150 assert_eq!(ts, expected, "v7 timestamp roundtrip failed");
151 }
152
153 #[test]
154 fn dcom_timestamp_roundtrip() {
155 let t = UNIX_EPOCH + Duration::from_secs(1_000_000_000);
157 let node = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
158 let uuid = UUID::new_dcom(t, node).expect("new_dcom should succeed for valid test inputs");
159 let ts = uuid
160 .get_timestamp()
161 .expect("timestamp should be present for time-based UUID");
162 assert_eq!(ts, t, "DCOM timestamp roundtrip failed");
163 }
164
165 #[test]
166 fn ncs_timestamp_roundtrip() {
167 let ncs_epoch = UNIX_EPOCH + Duration::from_secs(315_532_800);
169 let t = ncs_epoch + Duration::from_secs(1_000_000);
170 let address = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
171 let uuid =
172 UUID::new_ncs(t, 1, &address).expect("new_ncs should succeed for valid test inputs");
173 let ts = uuid
174 .get_timestamp()
175 .expect("timestamp should be present for time-based UUID");
176 assert_eq!(ts, t, "NCS timestamp roundtrip failed");
177 }
178
179 #[test]
180 fn non_time_based_versions_return_none() {
181 for v in [3, 4, 5, 8] {
182 let mut bytes = [0u8; 16];
183 bytes[6] = v << 4;
184 let uuid = UUID::from_parts_v8(bytes);
185 assert!(
186 uuid.get_timestamp().is_none(),
187 "Non-time-based v{v} should return None"
188 );
189 }
190 }
191
192 #[test]
193 fn v1_from_bytes_and_timestamp() {
194 let mut bytes = [0u8; 16];
195 bytes[6] = 0x10; bytes[8] = 0x80; let uuid = UUID::from_bytes(bytes);
198 assert_eq!(uuid.as_bytes(), &bytes);
199 assert_eq!(uuid.get_version(), Some(1));
200 assert!(uuid.get_timestamp().is_some());
201 }
202
203 #[test]
204 fn v2_from_bytes_and_timestamp() {
205 let mut bytes = [0u8; 16];
206 bytes[6] = 0x20; bytes[8] = 0x80; let uuid = UUID::from_bytes(bytes);
209 assert_eq!(uuid.as_bytes(), &bytes);
210 assert_eq!(uuid.get_version(), Some(2));
211 assert!(uuid.get_timestamp().is_some());
212 }
213
214 #[test]
215 fn v3_from_bytes_and_timestamp() {
216 let mut bytes = [0u8; 16];
217 bytes[6] = 0x30; bytes[8] = 0x80; let uuid = UUID::from_bytes(bytes);
220 assert_eq!(uuid.as_bytes(), &bytes);
221 assert_eq!(uuid.get_version(), Some(3));
222 assert_eq!(uuid.get_timestamp(), None);
223 }
224
225 #[test]
226 fn v4_from_bytes_and_timestamp() {
227 let mut bytes = [0u8; 16];
228 bytes[6] = 0x40; bytes[8] = 0x80; let uuid = UUID::from_bytes(bytes);
231 assert_eq!(uuid.as_bytes(), &bytes);
232 assert_eq!(uuid.get_version(), Some(4));
233 assert_eq!(uuid.get_timestamp(), None);
234 }
235
236 #[test]
237 fn v5_from_bytes_and_timestamp() {
238 let mut bytes = [0u8; 16];
239 bytes[6] = 0x50; bytes[8] = 0x80; let uuid: UUID = UUID::from_bytes(bytes);
242 assert_eq!(uuid.as_bytes(), &bytes);
243 assert_eq!(uuid.get_version(), Some(5));
244 assert_eq!(uuid.get_timestamp(), None);
245 }
246
247 #[test]
250 fn variant_ncs() {
251 let mut bytes = [0u8; 16];
252 bytes[6] = 0x40; bytes[8] = 0x00; let uuid = UUID::from_bytes(bytes);
255 assert_eq!(uuid.as_bytes(), &bytes);
256 assert_eq!(uuid.get_variant(), Variant::NCS);
257 }
258
259 #[test]
260 fn variant_rfc4122() {
261 let mut bytes = [0u8; 16];
262 bytes[6] = 0x40; bytes[8] = 0x80; let uuid = UUID::from_bytes(bytes);
265 assert_eq!(uuid.as_bytes(), &bytes);
266 assert_eq!(uuid.get_variant(), Variant::OSF);
267 }
268
269 #[test]
270 fn variant_microsoft() {
271 let mut bytes = [0u8; 16];
272 bytes[6] = 0x40; bytes[8] = 0xC0; let uuid = UUID::from_bytes(bytes);
275 assert_eq!(uuid.as_bytes(), &bytes);
276 assert_eq!(uuid.get_variant(), Variant::DCOM);
277 }
278
279 #[test]
280 fn variant_future() {
281 let mut bytes = [0u8; 16];
282 bytes[6] = 0x40; bytes[8] = 0xE0; let uuid = UUID::from_bytes(bytes);
285 assert_eq!(uuid.as_bytes(), &bytes);
286 assert_eq!(uuid.get_variant(), Variant::Reserved);
287 }
288
289 const HUNDRED_NS_PER_SEC: u64 = 10_000_000;
291
292 const UUID_UNIX_TICKS: u64 = 0x01B2_1DD2_1381_4000; const SECS_1970_TO_1980: u64 = 315_532_800;
297
298 #[test]
301 fn v1_timestamp_exact_unix_epoch() {
302 let ticks = UUID_UNIX_TICKS;
303 let time_low = (ticks & 0xFFFF_FFFF) as u32;
304 let time_mid = ((ticks >> 32) & 0xFFFF) as u16;
305 let time_hi_ver = (((ticks >> 48) & 0x0FFF) as u16) | 0x1000; let mut b = [0u8; 16];
308 b[0] = (time_low >> 24) as u8;
309 b[1] = (time_low >> 16) as u8;
310 b[2] = (time_low >> 8) as u8;
311 b[3] = time_low as u8;
312 b[4] = (time_mid >> 8) as u8;
313 b[5] = time_mid as u8;
314 b[6] = (time_hi_ver >> 8) as u8;
315 b[7] = time_hi_ver as u8;
316 b[8] = 0x80; let uuid = UUID::from_bytes(b);
318 assert_eq!(uuid.get_timestamp(), Some(SystemTime::UNIX_EPOCH));
319 }
320
321 #[test]
322 fn v2_timestamp_exact_unix_epoch() {
323 let ticks = UUID_UNIX_TICKS;
324 let time_low = (ticks & 0xFFFF_FFFF) as u32;
325 let time_mid = ((ticks >> 32) & 0xFFFF) as u16;
326 let time_hi_ver = (((ticks >> 48) & 0x0FFF) as u16) | 0x2000; let mut b = [0u8; 16];
329 b[0] = (time_low >> 24) as u8;
330 b[1] = (time_low >> 16) as u8;
331 b[2] = (time_low >> 8) as u8;
332 b[3] = time_low as u8;
333 b[4] = (time_mid >> 8) as u8;
334 b[5] = time_mid as u8;
335 b[6] = (time_hi_ver >> 8) as u8;
336 b[7] = time_hi_ver as u8;
337 b[8] = 0x80;
338 let uuid = UUID::from_bytes(b);
339 assert_eq!(uuid.get_timestamp(), Some(SystemTime::UNIX_EPOCH));
340 }
341
342 #[test]
343 fn v6_timestamp_exact_unix_epoch() {
344 let ticks = UUID_UNIX_TICKS;
345 let hi = (ticks >> 28) as u32;
346 let mid = ((ticks >> 12) & 0xFFFF) as u16;
347 let lo_ver = ((ticks & 0x0FFF) as u16) | 0x6000; let mut b = [0u8; 16];
350 b[0] = (hi >> 24) as u8;
351 b[1] = (hi >> 16) as u8;
352 b[2] = (hi >> 8) as u8;
353 b[3] = hi as u8;
354 b[4] = (mid >> 8) as u8;
355 b[5] = mid as u8;
356 b[6] = (lo_ver >> 8) as u8;
357 b[7] = lo_ver as u8;
358 b[8] = 0x80;
359 let uuid = UUID::from_bytes(b);
360 assert_eq!(uuid.get_timestamp(), Some(SystemTime::UNIX_EPOCH));
361 }
362
363 #[test]
366 fn v7_timestamp_zero() {
367 let mut b = [0u8; 16];
369 b[6] = 0x70; b[8] = 0x80; let uuid = UUID::from_bytes(b);
372 assert_eq!(uuid.get_timestamp(), Some(SystemTime::UNIX_EPOCH));
373 }
374
375 #[test]
378 fn ncs_timestamp_epoch() {
379 let uuid = UUID::from_bytes([0u8; 16]); let expected = SystemTime::UNIX_EPOCH + Duration::from_secs(SECS_1970_TO_1980);
382 assert_eq!(uuid.get_timestamp(), Some(expected));
383 }
384
385 #[test]
388 fn dcom_timestamp_exact_unix_epoch() {
389 let ticks = 11_644_473_600u64 * HUNDRED_NS_PER_SEC; let lo = (ticks & 0xFFFF_FFFF) as u32;
392 let mid = ((ticks >> 32) & 0xFFFF) as u16;
393 let hi = ((ticks >> 48) & 0xFFFF) as u16;
394
395 let mut b = [0u8; 16];
396 b[0] = lo as u8;
398 b[1] = (lo >> 8) as u8;
399 b[2] = (lo >> 16) as u8;
400 b[3] = (lo >> 24) as u8;
401 b[4] = mid as u8;
402 b[5] = (mid >> 8) as u8;
403 b[6] = hi as u8;
404 b[7] = (hi >> 8) as u8;
405 b[8] = 0xC0; let uuid = UUID::from_bytes(b);
407 assert_eq!(uuid.get_timestamp(), Some(SystemTime::UNIX_EPOCH));
408 }
409}