| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
use rand::RngCore; |
| 6 |
|
| 7 |
const CROCKFORD: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ"; |
| 8 |
|
| 9 |
|
| 10 |
pub fn ulid() -> String { |
| 11 |
let mut r = [0u8; 16]; |
| 12 |
rand::rngs::OsRng.fill_bytes(&mut r); |
| 13 |
ulid_from(now_ms(), u128::from_be_bytes(r)) |
| 14 |
} |
| 15 |
|
| 16 |
|
| 17 |
pub fn ulid_from(ms: u64, random: u128) -> String { |
| 18 |
let v: u128 = ((ms as u128 & ((1 << 48) - 1)) << 80) | (random & ((1u128 << 80) - 1)); |
| 19 |
(0..26).map(|i| CROCKFORD[((v >> (125 - 5 * i)) & 31) as usize] as char).collect() |
| 20 |
} |
| 21 |
|
| 22 |
|
| 23 |
pub fn now_ms() -> u64 { |
| 24 |
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis() as u64).unwrap_or(0) |
| 25 |
} |
| 26 |
|
| 27 |
|
| 28 |
pub fn format_time(ms: u64) -> String { |
| 29 |
let secs = (ms / 1000) as i64; |
| 30 |
let days = secs.div_euclid(86_400); |
| 31 |
let rem = secs.rem_euclid(86_400); |
| 32 |
|
| 33 |
let z = days + 719_468; |
| 34 |
let era = z.div_euclid(146_097); |
| 35 |
let doe = z - era * 146_097; |
| 36 |
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; |
| 37 |
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); |
| 38 |
let mp = (5 * doy + 2) / 153; |
| 39 |
let d = doy - (153 * mp + 2) / 5 + 1; |
| 40 |
let m = if mp < 10 { mp + 3 } else { mp - 9 }; |
| 41 |
let y = yoe + era * 400 + if m <= 2 { 1 } else { 0 }; |
| 42 |
format!("{y:04}-{m:02}-{d:02} {:02}:{:02}Z", rem / 3600, (rem % 3600) / 60) |
| 43 |
} |
| 44 |
|
| 45 |
#[cfg(test)] |
| 46 |
mod tests { |
| 47 |
use super::*; |
| 48 |
|
| 49 |
#[test] |
| 50 |
fn ulids_are_26_chars_and_sort_by_time() { |
| 51 |
let a = ulid_from(1_000, 5); |
| 52 |
let b = ulid_from(2_000, 0); |
| 53 |
assert_eq!(a.len(), 26); |
| 54 |
assert!(a < b); |
| 55 |
assert_eq!(ulid_from(0, 0), "00000000000000000000000000"); |
| 56 |
assert_ne!(ulid(), ulid()); |
| 57 |
assert_eq!(format_time(1_793_655_780_000), "2026-11-02 21:43Z"); |
| 58 |
} |
| 59 |
} |