use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::{Gregorian, UUID};
const FILETIME_EPOCH_OFFSET: u64 = 116_444_736_000_000_000;
const NCS_EPOCH: Duration = Duration::from_secs(315_532_800);
impl UUID {
#[must_use]
pub fn get_timestamp(&self) -> Option<SystemTime> {
match (self.get_version(), self.get_variant()) {
(Some(1 | 2), crate::Variant::OSF) => {
let time_low = u32::from_be_bytes([
self.bytes[0],
self.bytes[1],
self.bytes[2],
self.bytes[3],
]);
let time_mid = u16::from_be_bytes([self.bytes[4], self.bytes[5]]);
let time_hi = u16::from_be_bytes([self.bytes[6], self.bytes[7]]) & 0x0FFF;
let timestamp: u64 =
(u64::from(time_hi) << 48) | (u64::from(time_mid) << 32) | u64::from(time_low);
#[allow(clippy::cast_possible_truncation)]
Some(
Gregorian::epoch()
+ Duration::new(
timestamp / 10_000_000,
((timestamp % 10_000_000) * 100) as u32,
),
)
}
(Some(6), crate::Variant::OSF) => {
let time_high = u32::from_be_bytes([
self.bytes[0],
self.bytes[1],
self.bytes[2],
self.bytes[3],
]);
let time_mid = u16::from_be_bytes([self.bytes[4], self.bytes[5]]);
let time_low = u16::from_be_bytes([self.bytes[6], self.bytes[7]]) & 0x0FFF;
let timestamp: u64 = (u64::from(time_high) << 28)
| (u64::from(time_mid) << 12)
| u64::from(time_low);
Some(Gregorian::epoch() + Duration::from_nanos(timestamp * 100))
}
(Some(7), crate::Variant::OSF) => {
let mut ms_bytes = [0u8; 8];
ms_bytes[2..8].copy_from_slice(&self.bytes[0..6]);
let ms = u64::from_be_bytes(ms_bytes);
Some(UNIX_EPOCH + Duration::from_millis(ms))
}
(_, crate::Variant::DCOM) => {
let time_low = u32::from_le_bytes([
self.bytes[0],
self.bytes[1],
self.bytes[2],
self.bytes[3],
]);
let time_mid = u16::from_le_bytes([self.bytes[4], self.bytes[5]]);
let time_hi = u16::from_le_bytes([self.bytes[6], self.bytes[7]]);
let filetime: u64 =
(u64::from(time_hi) << 48) | (u64::from(time_mid) << 32) | u64::from(time_low);
#[allow(clippy::cast_possible_truncation)]
if filetime < FILETIME_EPOCH_OFFSET {
let unix_100ns = FILETIME_EPOCH_OFFSET - filetime;
Some(
UNIX_EPOCH
- Duration::new(
unix_100ns / 10_000_000,
(unix_100ns % 10_000_000) as u32 * 100,
),
)
} else {
let unix_100ns = filetime - FILETIME_EPOCH_OFFSET;
Some(
UNIX_EPOCH
+ Duration::new(
unix_100ns / 10_000_000,
(unix_100ns % 10_000_000) as u32 * 100,
),
)
}
}
(_, crate::Variant::NCS) => {
let mut ts_bytes = [0u8; 8];
ts_bytes[2..8].copy_from_slice(&self.bytes[0..6]);
let ts = u64::from_be_bytes(ts_bytes);
let ncs_epoch = UNIX_EPOCH + NCS_EPOCH;
Some(ncs_epoch + Duration::from_micros(ts * 4))
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::cast_possible_truncation, clippy::expect_used)]
use crate::Variant;
use super::*;
use std::time::{Duration, UNIX_EPOCH};
#[test]
fn v1_and_v2_timestamp_roundtrip() {
let t = Gregorian::epoch() + Duration::from_secs(1_000_000_000);
let uuid = UUID::from_parts_v1(0x6fc1_0000, 0x86f2, 0x23, 0x1234, [1, 2, 3, 4, 5, 6]);
let ts = uuid
.get_timestamp()
.expect("timestamp should be present for time-based UUID");
assert_eq!(ts, t, "v1 timestamp roundtrip failed");
}
#[test]
fn v6_timestamp_roundtrip() {
let t = Gregorian::epoch() + Duration::from_secs(1_000_000_000);
let uuid = UUID::from_parts_v6(0x0238_6f26, 0xfc10, 0x6000, 0x1234, [1, 2, 3, 4, 5, 6]);
let ts = uuid
.get_timestamp()
.expect("timestamp should be present for time-based UUID");
assert_eq!(ts, t, "v6 timestamp roundtrip failed");
}
#[test]
fn v7_timestamp_roundtrip() {
let ms = 1_700_000_000_000u64;
let uuid = UUID::from_parts_v7(ms, 0, 0);
let ts = uuid
.get_timestamp()
.expect("timestamp should be present for time-based UUID");
let expected = UNIX_EPOCH + Duration::from_millis(ms);
assert_eq!(ts, expected, "v7 timestamp roundtrip failed");
}
#[test]
fn dcom_timestamp_roundtrip() {
let t = UNIX_EPOCH + Duration::from_secs(1_000_000_000);
let node = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
let uuid = UUID::new_dcom(t, node).expect("new_dcom should succeed for valid test inputs");
let ts = uuid
.get_timestamp()
.expect("timestamp should be present for time-based UUID");
assert_eq!(ts, t, "DCOM timestamp roundtrip failed");
}
#[test]
fn ncs_timestamp_roundtrip() {
let ncs_epoch = UNIX_EPOCH + Duration::from_secs(315_532_800);
let t = ncs_epoch + Duration::from_secs(1_000_000);
let address = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
let uuid =
UUID::new_ncs(t, 1, &address).expect("new_ncs should succeed for valid test inputs");
let ts = uuid
.get_timestamp()
.expect("timestamp should be present for time-based UUID");
assert_eq!(ts, t, "NCS timestamp roundtrip failed");
}
#[test]
fn non_time_based_versions_return_none() {
for v in [3, 4, 5, 8] {
let mut bytes = [0u8; 16];
bytes[6] = v << 4;
let uuid = UUID::from_parts_v8(bytes);
assert!(
uuid.get_timestamp().is_none(),
"Non-time-based v{v} should return None"
);
}
}
#[test]
fn v1_from_bytes_and_timestamp() {
let mut bytes = [0u8; 16];
bytes[6] = 0x10; bytes[8] = 0x80; let uuid = UUID::from_bytes(bytes);
assert_eq!(uuid.as_bytes(), &bytes);
assert_eq!(uuid.get_version(), Some(1));
assert!(uuid.get_timestamp().is_some());
}
#[test]
fn v2_from_bytes_and_timestamp() {
let mut bytes = [0u8; 16];
bytes[6] = 0x20; bytes[8] = 0x80; let uuid = UUID::from_bytes(bytes);
assert_eq!(uuid.as_bytes(), &bytes);
assert_eq!(uuid.get_version(), Some(2));
assert!(uuid.get_timestamp().is_some());
}
#[test]
fn v3_from_bytes_and_timestamp() {
let mut bytes = [0u8; 16];
bytes[6] = 0x30; bytes[8] = 0x80; let uuid = UUID::from_bytes(bytes);
assert_eq!(uuid.as_bytes(), &bytes);
assert_eq!(uuid.get_version(), Some(3));
assert_eq!(uuid.get_timestamp(), None);
}
#[test]
fn v4_from_bytes_and_timestamp() {
let mut bytes = [0u8; 16];
bytes[6] = 0x40; bytes[8] = 0x80; let uuid = UUID::from_bytes(bytes);
assert_eq!(uuid.as_bytes(), &bytes);
assert_eq!(uuid.get_version(), Some(4));
assert_eq!(uuid.get_timestamp(), None);
}
#[test]
fn v5_from_bytes_and_timestamp() {
let mut bytes = [0u8; 16];
bytes[6] = 0x50; bytes[8] = 0x80; let uuid: UUID = UUID::from_bytes(bytes);
assert_eq!(uuid.as_bytes(), &bytes);
assert_eq!(uuid.get_version(), Some(5));
assert_eq!(uuid.get_timestamp(), None);
}
#[test]
fn variant_ncs() {
let mut bytes = [0u8; 16];
bytes[6] = 0x40; bytes[8] = 0x00; let uuid = UUID::from_bytes(bytes);
assert_eq!(uuid.as_bytes(), &bytes);
assert_eq!(uuid.get_variant(), Variant::NCS);
}
#[test]
fn variant_rfc4122() {
let mut bytes = [0u8; 16];
bytes[6] = 0x40; bytes[8] = 0x80; let uuid = UUID::from_bytes(bytes);
assert_eq!(uuid.as_bytes(), &bytes);
assert_eq!(uuid.get_variant(), Variant::OSF);
}
#[test]
fn variant_microsoft() {
let mut bytes = [0u8; 16];
bytes[6] = 0x40; bytes[8] = 0xC0; let uuid = UUID::from_bytes(bytes);
assert_eq!(uuid.as_bytes(), &bytes);
assert_eq!(uuid.get_variant(), Variant::DCOM);
}
#[test]
fn variant_future() {
let mut bytes = [0u8; 16];
bytes[6] = 0x40; bytes[8] = 0xE0; let uuid = UUID::from_bytes(bytes);
assert_eq!(uuid.as_bytes(), &bytes);
assert_eq!(uuid.get_variant(), Variant::Reserved);
}
const HUNDRED_NS_PER_SEC: u64 = 10_000_000;
const UUID_UNIX_TICKS: u64 = 0x01B2_1DD2_1381_4000;
const SECS_1970_TO_1980: u64 = 315_532_800;
#[test]
fn v1_timestamp_exact_unix_epoch() {
let ticks = UUID_UNIX_TICKS;
let time_low = (ticks & 0xFFFF_FFFF) as u32;
let time_mid = ((ticks >> 32) & 0xFFFF) as u16;
let time_hi_ver = (((ticks >> 48) & 0x0FFF) as u16) | 0x1000;
let mut b = [0u8; 16];
b[0] = (time_low >> 24) as u8;
b[1] = (time_low >> 16) as u8;
b[2] = (time_low >> 8) as u8;
b[3] = time_low as u8;
b[4] = (time_mid >> 8) as u8;
b[5] = time_mid as u8;
b[6] = (time_hi_ver >> 8) as u8;
b[7] = time_hi_ver as u8;
b[8] = 0x80; let uuid = UUID::from_bytes(b);
assert_eq!(uuid.get_timestamp(), Some(SystemTime::UNIX_EPOCH));
}
#[test]
fn v2_timestamp_exact_unix_epoch() {
let ticks = UUID_UNIX_TICKS;
let time_low = (ticks & 0xFFFF_FFFF) as u32;
let time_mid = ((ticks >> 32) & 0xFFFF) as u16;
let time_hi_ver = (((ticks >> 48) & 0x0FFF) as u16) | 0x2000;
let mut b = [0u8; 16];
b[0] = (time_low >> 24) as u8;
b[1] = (time_low >> 16) as u8;
b[2] = (time_low >> 8) as u8;
b[3] = time_low as u8;
b[4] = (time_mid >> 8) as u8;
b[5] = time_mid as u8;
b[6] = (time_hi_ver >> 8) as u8;
b[7] = time_hi_ver as u8;
b[8] = 0x80;
let uuid = UUID::from_bytes(b);
assert_eq!(uuid.get_timestamp(), Some(SystemTime::UNIX_EPOCH));
}
#[test]
fn v6_timestamp_exact_unix_epoch() {
let ticks = UUID_UNIX_TICKS;
let hi = (ticks >> 28) as u32;
let mid = ((ticks >> 12) & 0xFFFF) as u16;
let lo_ver = ((ticks & 0x0FFF) as u16) | 0x6000;
let mut b = [0u8; 16];
b[0] = (hi >> 24) as u8;
b[1] = (hi >> 16) as u8;
b[2] = (hi >> 8) as u8;
b[3] = hi as u8;
b[4] = (mid >> 8) as u8;
b[5] = mid as u8;
b[6] = (lo_ver >> 8) as u8;
b[7] = lo_ver as u8;
b[8] = 0x80;
let uuid = UUID::from_bytes(b);
assert_eq!(uuid.get_timestamp(), Some(SystemTime::UNIX_EPOCH));
}
#[test]
fn v7_timestamp_zero() {
let mut b = [0u8; 16];
b[6] = 0x70; b[8] = 0x80; let uuid = UUID::from_bytes(b);
assert_eq!(uuid.get_timestamp(), Some(SystemTime::UNIX_EPOCH));
}
#[test]
fn ncs_timestamp_epoch() {
let uuid = UUID::from_bytes([0u8; 16]); let expected = SystemTime::UNIX_EPOCH + Duration::from_secs(SECS_1970_TO_1980);
assert_eq!(uuid.get_timestamp(), Some(expected));
}
#[test]
fn dcom_timestamp_exact_unix_epoch() {
let ticks = 11_644_473_600u64 * HUNDRED_NS_PER_SEC; let lo = (ticks & 0xFFFF_FFFF) as u32;
let mid = ((ticks >> 32) & 0xFFFF) as u16;
let hi = ((ticks >> 48) & 0xFFFF) as u16;
let mut b = [0u8; 16];
b[0] = lo as u8;
b[1] = (lo >> 8) as u8;
b[2] = (lo >> 16) as u8;
b[3] = (lo >> 24) as u8;
b[4] = mid as u8;
b[5] = (mid >> 8) as u8;
b[6] = hi as u8;
b[7] = (hi >> 8) as u8;
b[8] = 0xC0; let uuid = UUID::from_bytes(b);
assert_eq!(uuid.get_timestamp(), Some(SystemTime::UNIX_EPOCH));
}
}