crates/dwarves-slsa/src/time.rs
//! RFC 3339 UTC timestamps without a time crate.
use std::time::{SystemTime, UNIX_EPOCH};
/// (year, month, day) of a day count since 1970-01-01 (Howard Hinnant's civil_from_days).
fn civil(days: i64) -> (i64, u32, u32) {
let z = days + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
(if m <= 2 { y + 1 } else { y }, m, d)
}
fn parts(secs: u64) -> (i64, u32, u32, u64, u64, u64) {
let (y, m, d) = civil((secs / 86_400) as i64);
let s = secs % 86_400;
(y, m, d, s / 3600, s / 60 % 60, s % 60)
}
/// `YYYY-MM-DDTHH:MM:SSZ` for a time after 1970.
pub fn rfc3339(t: SystemTime) -> String {
rfc3339_secs(
t.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0),
)
}
/// Seconds since the epoch, as [`rfc3339`] writes them.
pub fn rfc3339_secs(secs: u64) -> String {
let (y, m, d, hh, mm, ss) = parts(secs);
format!("{y:04}-{m:02}-{d:02}T{hh:02}:{mm:02}:{ss:02}Z")
}
/// `YYYYmmddTHHMMSSZ`, for directory and invocation names.
pub fn compact(secs: u64) -> String {
let (y, m, d, hh, mm, ss) = parts(secs);
format!("{y:04}{m:02}{d:02}T{hh:02}{mm:02}{ss:02}Z")
}
/// Now, in seconds since the epoch.
pub fn now_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
/// Is this `YYYY-MM-DDTHH:MM:SSZ`?
pub fn is_rfc3339_utc(s: &str) -> bool {
let b = s.as_bytes();
b.len() == 20
&& b.iter().enumerate().all(|(i, c)| match i {
4 | 7 => *c == b'-',
10 => *c == b'T',
13 | 16 => *c == b':',
19 => *c == b'Z',
_ => c.is_ascii_digit(),
})
}