OXYGENHUB

Coverage / crates/spectra-core/src/ids.rs 100.0%

38 of 38 lines run, 13 of 13 functions · run #19 on main · run missed

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.
1012345×
pub fn ulid() -> String {
1112345×
    let mut r = [0u8; 16];
1212345×
    rand::rngs::OsRng.fill_bytes(&mut r);
1312345×
    ulid_from(now_ms(), u128::from_be_bytes(r))
1412345×
}
15
16
/// A ULID from a millisecond time and 80 bits taken from `random`.
1712348×
pub fn ulid_from(ms: u64, random: u128) -> String {
1812348×
    let v: u128 = ((ms as u128 & ((1 << 48) - 1)) << 80) | (random & ((1u128 << 80) - 1));
19321048×
    (0..26).map(|i| CROCKFORD[((v >> (125 - 5 * i)) & 31) as usize] as char).collect()
2012348×
}
21
22
/// Milliseconds since the Unix epoch.
2327528×
pub fn now_ms() -> u64 {
2427528×
    std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis() as u64).unwrap_or(0)
2527528×
}
26
27
/// A time as people and models read it: `2026-11-02 21:43Z`, UTC.
2856×
pub fn format_time(ms: u64) -> String {
2956×
    let secs = (ms / 1000) as i64;
3056×
    let days = secs.div_euclid(86_400);
3156×
    let rem = secs.rem_euclid(86_400);
32
    // Civil from days (Howard Hinnant), as oxy's `format::when`.
3356×
    let z = days + 719_468;
3456×
    let era = z.div_euclid(146_097);
3556×
    let doe = z - era * 146_097;
3656×
    let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
3756×
    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
3856×
    let mp = (5 * doy + 2) / 153;
3956×
    let d = doy - (153 * mp + 2) / 5 + 1;
4056×
    let m = if mp < 10 { mp + 3 } else { mp - 9 };
4156×
    let y = yoe + era * 400 + if m <= 2 { 1 } else { 0 };
4256×
    format!("{y:04}-{m:02}-{d:02} {:02}:{:02}Z", rem / 3600, (rem % 3600) / 60)
4356×
}
44
45
#[cfg(test)]
46
mod tests {
47
    use super::*;
48
49
    #[test]
501×
    fn ulids_are_26_chars_and_sort_by_time() {
511×
        let a = ulid_from(1_000, 5);
521×
        let b = ulid_from(2_000, 0);
531×
        assert_eq!(a.len(), 26);
541×
        assert!(a < b);
551×
        assert_eq!(ulid_from(0, 0), "00000000000000000000000000");
561×
        assert_ne!(ulid(), ulid());
571×
        assert_eq!(format_time(1_793_655_780_000), "2026-11-02 21:43Z");
581×
    }
59
}