OXYGENHUB
◆ main spectra / crates / spectra-core / src / ids.rs
2.1 KiB · Rust · last changed Spectra stands alone: its own identities, signatures and CBOR; projects of any kind of work (#26, #27) 1 day ago source blame history raw
36537112 Andy McCall ↶ 1
//! Identifiers (spec §9.4): ULIDs, 26 characters of Crockford base32, time
2
//! ordered and globally unique, so an id made on one machine merges into a
3
//! hub without renaming.
4
5
use rand::RngCore;
6
7
const CROCKFORD: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
8
9
/// A new ULID for the current time.
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
/// A ULID from a millisecond time and 80 bits taken from `random`.
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
/// Milliseconds since the Unix epoch.
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
037c45d8 Andy McCall ↶ 27
/// A time as people and models read it: `2026-11-02 21:43Z`, UTC.
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);
98a0fb9f Andy McCall ↶ 32
    // Civil from days (Howard Hinnant).
037c45d8 Andy McCall ↶ 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
36537112 Andy McCall ↶ 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());
037c45d8 Andy McCall ↶ 57
        assert_eq!(format_time(1_793_655_780_000), "2026-11-02 21:43Z");
36537112 Andy McCall ↶ 58
    }
59
}