use std::thread;
use std::time::Duration;
use uuid::Uuid;
#[derive(Debug, Default)]
pub struct Uuidv7Generator {
last_unix_ts_ms: Option<u64>,
}
impl Uuidv7Generator {
pub fn new() -> Self {
Self {
last_unix_ts_ms: None,
}
}
pub fn next_id(&mut self) -> Uuid {
loop {
let id = Uuid::now_v7();
let ts = unix_time_ms(&id);
match self.last_unix_ts_ms {
Some(last) if ts <= last => {
let wait = last.saturating_sub(ts).saturating_add(1);
thread::sleep(Duration::from_millis(wait));
continue;
}
_ => {
self.last_unix_ts_ms = Some(ts);
return id;
}
}
}
}
}
pub fn generate_ids(count: usize) -> Vec<Uuid> {
let mut id_gen = Uuidv7Generator::new();
let mut ids = Vec::with_capacity(count);
for _ in 0..count {
ids.push(id_gen.next_id());
}
ids
}
pub fn unix_time_ms(id: &Uuid) -> u64 {
let bytes = id.as_bytes();
((bytes[0] as u64) << 40)
| ((bytes[1] as u64) << 32)
| ((bytes[2] as u64) << 24)
| ((bytes[3] as u64) << 16)
| ((bytes[4] as u64) << 8)
| (bytes[5] as u64)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ids_are_uuidv7() {
let ids = generate_ids(3);
for id in &ids {
assert_eq!(id.get_version_num(), 7, "expected UUIDv7, got {}", id);
}
}
#[test]
fn timestamps_strictly_increase() {
let ids = generate_ids(5);
let mut last = 0u64;
for (i, id) in ids.iter().enumerate() {
let ts = unix_time_ms(id);
if i > 0 {
assert!(
ts > last,
"expected strictly increasing unix_time_ts, got {} after {}",
ts,
last
);
}
last = ts;
}
}
#[test]
fn tight_loop_strictly_monotonic_unix_time_ts() {
let mut generator = Uuidv7Generator::new();
let mut last_ts = 0u64;
for i in 0..20 {
let id = generator.next_id();
let ts = unix_time_ms(&id);
if i > 0 {
assert!(
ts > last_ts,
"expected strictly increasing unix_time_ts at iter {}, got {} after {}",
i,
ts,
last_ts
);
}
last_ts = ts;
}
}
}