use std::time::{SystemTime, UNIX_EPOCH};
const CROCKFORD: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
pub fn ulid() -> String {
let ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock before Unix epoch")
.as_millis() as u64;
let mut rand_bytes = [0u8; 10];
rand::fill(&mut rand_bytes);
let mut buf = [b'0'; 26];
let mut rand_val = u128::from_be_bytes({
let mut padded = [0u8; 16];
padded[6..].copy_from_slice(&rand_bytes);
padded
});
for i in (10..26).rev() {
buf[i] = CROCKFORD[(rand_val % 32) as usize];
rand_val >>= 5;
}
let mut ts = ms;
for i in (0..10).rev() {
buf[i] = CROCKFORD[(ts % 32) as usize];
ts >>= 5;
}
String::from_utf8(buf.to_vec()).expect("Crockford chars are valid UTF-8")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ulid_is_26_chars() {
assert_eq!(ulid().len(), 26);
}
#[test]
fn ulid_valid_crockford_base32() {
let id = ulid();
let valid = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
assert!(
id.chars().all(|c| valid.contains(c)),
"invalid char in {id}"
);
}
#[test]
fn ulid_is_uppercase() {
let id = ulid();
assert_eq!(id, id.to_uppercase());
}
#[test]
fn ulid_unique() {
let a = ulid();
let b = ulid();
assert_ne!(a, b);
}
#[test]
fn ulid_time_sortable() {
let a = ulid();
std::thread::sleep(std::time::Duration::from_millis(2));
let b = ulid();
assert!(a < b, "ULIDs should be time-sortable: {a} < {b}");
}
#[test]
fn ulid_first_char_max_7() {
for _ in 0..100 {
let id = ulid();
let first = id.chars().next().unwrap();
let idx = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".find(first).unwrap();
assert!(idx <= 7, "first char '{first}' (index {idx}) exceeds 7");
}
}
}