use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Ulid([u8; 16]);
impl Ulid {
pub fn new() -> Self {
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
let mut bytes = [0u8; 16];
let ms_bytes = ms.to_be_bytes();
bytes[0..6].copy_from_slice(&ms_bytes[2..8]);
bytes[6..14].copy_from_slice(&counter.to_be_bytes());
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
}
impl Default for Ulid {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for Ulid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const ALPHABET: &[u8] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
let mut n: u128 = 0;
for &b in &self.0 {
n = (n << 8) | (b as u128);
}
let mut out = [0u8; 26];
for i in (0..26).rev() {
out[i] = ALPHABET[(n & 31) as usize];
n >>= 5;
}
f.write_str(core::str::from_utf8(&out).unwrap_or(""))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_length_26() {
let id = Ulid::new();
assert_eq!(id.to_string().len(), 26);
}
#[test]
fn unique() {
let a = Ulid::new();
let b = Ulid::new();
assert_ne!(a, b);
}
#[test]
fn time_ordered() {
let a = Ulid::new();
let b = Ulid::new();
assert!(b.as_bytes() >= a.as_bytes());
}
}