use std::time::{SystemTime, UNIX_EPOCH};
pub(crate) const fn secs_to_ymdhms(secs: u64) -> (u32, u32, u32, u32, u32, u32) {
const SECS_PER_MIN: u64 = 60;
const DAYS_PER_400Y: u64 = 146_097;
let s = (secs % SECS_PER_MIN) as u32;
let total_mins = secs / SECS_PER_MIN;
let mi = (total_mins % 60) as u32;
let total_hours = total_mins / 60;
let h = (total_hours % 24) as u32;
let mut days = total_hours / 24;
days += 719_468;
let era = days / DAYS_PER_400Y;
let doe = days % DAYS_PER_400Y;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
#[allow(clippy::cast_possible_truncation)]
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
#[allow(clippy::cast_possible_truncation)]
let mo = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
#[allow(clippy::cast_possible_truncation)]
let y = if mo <= 2 { y + 1 } else { y } as u32;
(y, mo, d, h, mi, s)
}
fn unix_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_secs())
}
#[must_use]
pub fn utc_now_datetime() -> (u32, u32, u32, u32, u32, u32) {
secs_to_ymdhms(unix_secs())
}
#[must_use]
pub fn utc_now_rfc3339() -> String {
let (y, mo, d, h, mi, s) = utc_now_datetime();
format!("{y:04}-{mo:02}-{d:02}T{h:02}:{mi:02}:{s:02}Z")
}
#[must_use]
pub fn utc_now_compact() -> String {
let (y, mo, d, h, mi, s) = utc_now_datetime();
format!("{y:04}{mo:02}{d:02}_{h:02}{mi:02}{s:02}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rfc3339_format_is_correct() {
let ts = utc_now_rfc3339();
assert_eq!(ts.len(), 20);
assert!(ts.ends_with('Z'));
assert_eq!(&ts[10..11], "T");
assert!(ts[..4].chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn compact_format_is_correct() {
let ts = utc_now_compact();
assert_eq!(ts.len(), 15);
assert_eq!(&ts[8..9], "_");
}
#[test]
fn secs_to_ymdhms_known_epoch() {
let (y, mo, d, h, mi, s) = secs_to_ymdhms(0);
assert_eq!((y, mo, d, h, mi, s), (1970, 1, 1, 0, 0, 0));
}
#[test]
fn secs_to_ymdhms_known_date() {
let (y, mo, d, h, mi, s) = secs_to_ymdhms(1_709_251_200);
assert_eq!((y, mo, d, h, mi, s), (2024, 3, 1, 0, 0, 0));
}
#[test]
fn secs_to_ymdhms_y2k_boundary() {
assert_eq!(secs_to_ymdhms(946_684_800), (2000, 1, 1, 0, 0, 0));
}
#[test]
fn secs_to_ymdhms_feb29_century_leap_year() {
assert_eq!(secs_to_ymdhms(951_782_400), (2000, 2, 29, 0, 0, 0));
}
#[test]
fn secs_to_ymdhms_feb29_regular_leap_year() {
assert_eq!(secs_to_ymdhms(1_709_164_800), (2024, 2, 29, 0, 0, 0));
}
#[test]
fn secs_to_ymdhms_year_end_boundary() {
assert_eq!(secs_to_ymdhms(1_704_067_199), (2023, 12, 31, 23, 59, 59));
}
#[test]
fn secs_to_ymdhms_nontrivial_hhmmss() {
assert_eq!(secs_to_ymdhms(1_718_458_245), (2024, 6, 15, 13, 30, 45));
}
#[test]
fn datetime_fields_are_in_range() {
let (y, mo, d, h, mi, s) = utc_now_datetime();
assert!(y >= 2024);
assert!((1..=12).contains(&mo));
assert!((1..=31).contains(&d));
assert!(h < 24 && mi < 60 && s < 60);
}
}