use std::time::{SystemTime, UNIX_EPOCH};
fn epoch_parts(time: SystemTime) -> (i64, u32) {
match time.duration_since(UNIX_EPOCH) {
Ok(d) => (d.as_secs() as i64, d.subsec_nanos()),
Err(e) => {
let d = e.duration();
let (secs, nanos) = (d.as_secs() as i64, d.subsec_nanos());
if nanos == 0 {
(-secs, 0)
} else {
(-secs - 1, 1_000_000_000 - nanos)
}
}
}
}
fn civil_from_days(days: i64) -> (i64, u32, u32) {
let z = days + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 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 civil_from_secs(secs: i64) -> (i64, u32, u32, u32, u32, u32) {
let days = secs.div_euclid(86_400);
let sod = secs.rem_euclid(86_400) as u32;
let (y, mo, d) = civil_from_days(days);
(y, mo, d, sod / 3600, (sod % 3600) / 60, sod % 60)
}
fn push_year(out: &mut String, year: i64) {
use std::fmt::Write as _;
if (0..=9999).contains(&year) {
let _ = write!(out, "{:04}", year);
} else if year > 9999 {
let _ = write!(out, "+{:04}", year);
} else {
let _ = write!(out, "-{:04}", -year);
}
}
fn push_datetime_secs(out: &mut String, secs: i64) {
use std::fmt::Write as _;
let (y, mo, d, h, mi, s) = civil_from_secs(secs);
push_year(out, y);
let _ = write!(out, "-{:02}-{:02}T{:02}:{:02}:{:02}", mo, d, h, mi, s);
}
fn push_fraction(out: &mut String, nanos: u32) {
use std::fmt::Write as _;
if nanos == 0 {
} else if nanos % 1_000_000 == 0 {
let _ = write!(out, ".{:03}", nanos / 1_000_000);
} else if nanos % 1_000 == 0 {
let _ = write!(out, ".{:06}", nanos / 1_000);
} else {
let _ = write!(out, ".{:09}", nanos);
}
}
#[cfg_attr(not(feature = "admin-api"), allow(dead_code))]
pub(crate) fn format_rfc3339(time: SystemTime) -> String {
let (secs, nanos) = epoch_parts(time);
let mut out = String::with_capacity(35);
push_datetime_secs(&mut out, secs);
push_fraction(&mut out, nanos);
out.push_str("+00:00");
out
}
const CHRONO_MIN_TIMESTAMP: i64 = -8_334_601_228_800; const CHRONO_MAX_TIMESTAMP: i64 = 8_210_266_876_799;
#[cfg_attr(not(feature = "admin-api"), allow(dead_code))]
pub(crate) fn format_rfc3339_from_secs(secs: u64) -> String {
let secs = secs as i64;
let secs = if (CHRONO_MIN_TIMESTAMP..=CHRONO_MAX_TIMESTAMP).contains(&secs) {
secs
} else {
0
};
let mut out = String::with_capacity(25);
push_datetime_secs(&mut out, secs);
out.push_str("+00:00");
out
}
#[cfg(feature = "serde")]
pub(crate) fn format_iso8601_millis(time: SystemTime) -> String {
let (secs, _) = epoch_parts(time);
let millis = time
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.subsec_millis();
let mut out = String::with_capacity(28);
push_datetime_secs(&mut out, secs);
out.push_str(&format!(".{:03}Z", millis));
out
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
fn at(secs: i64, nanos: u32) -> SystemTime {
if secs >= 0 {
UNIX_EPOCH + Duration::new(secs as u64, nanos)
} else {
UNIX_EPOCH - Duration::new((-secs) as u64, 0) + Duration::new(0, nanos)
}
}
#[test]
fn civil_from_days_known_values() {
assert_eq!(civil_from_days(0), (1970, 1, 1));
assert_eq!(civil_from_days(-1), (1969, 12, 31));
assert_eq!(civil_from_days(1), (1970, 1, 2));
assert_eq!(civil_from_days(11016), (2000, 2, 29));
assert_eq!(civil_from_days(19782), (2024, 2, 29));
assert_eq!(civil_from_days(-25509), (1900, 2, 28));
assert_eq!(civil_from_days(-25508), (1900, 3, 1));
assert_eq!(civil_from_days(10956), (1999, 12, 31));
assert_eq!(civil_from_days(10957), (2000, 1, 1));
}
#[test]
fn rfc3339_epoch_and_boundaries() {
assert_eq!(format_rfc3339(at(0, 0)), "1970-01-01T00:00:00+00:00");
assert_eq!(format_rfc3339(at(1, 0)), "1970-01-01T00:00:01+00:00");
assert_eq!(format_rfc3339(at(-1, 0)), "1969-12-31T23:59:59+00:00");
assert_eq!(format_rfc3339(at(86_399, 0)), "1970-01-01T23:59:59+00:00");
assert_eq!(format_rfc3339(at(86_400, 0)), "1970-01-02T00:00:00+00:00");
assert_eq!(
format_rfc3339(at(2_147_483_647, 0)),
"2038-01-19T03:14:07+00:00"
);
}
#[test]
fn rfc3339_leap_day_and_year_boundary() {
assert_eq!(
format_rfc3339(at(1_709_208_000, 0)),
"2024-02-29T12:00:00+00:00"
);
assert_eq!(
format_rfc3339(at(951_782_400, 0)),
"2000-02-29T00:00:00+00:00"
);
assert_eq!(
format_rfc3339(at(-2_203_891_200, 0)),
"1900-03-01T00:00:00+00:00"
);
assert_eq!(
format_rfc3339(at(946_684_799, 0)),
"1999-12-31T23:59:59+00:00"
);
assert_eq!(
format_rfc3339(at(946_684_800, 0)),
"2000-01-01T00:00:00+00:00"
);
}
#[test]
fn rfc3339_fraction_precision_matches_chrono_auto_si() {
assert_eq!(format_rfc3339(at(0, 0)), "1970-01-01T00:00:00+00:00");
assert_eq!(
format_rfc3339(at(0, 123_000_000)),
"1970-01-01T00:00:00.123+00:00"
);
assert_eq!(
format_rfc3339(at(0, 123_456_000)),
"1970-01-01T00:00:00.123456+00:00"
);
assert_eq!(
format_rfc3339(at(0, 123_456_789)),
"1970-01-01T00:00:00.123456789+00:00"
);
assert_eq!(
format_rfc3339(at(0, 1)),
"1970-01-01T00:00:00.000000001+00:00"
);
}
#[test]
fn rfc3339_from_secs_drops_the_fraction() {
assert_eq!(format_rfc3339_from_secs(0), "1970-01-01T00:00:00+00:00");
assert_eq!(
format_rfc3339_from_secs(1_709_208_000),
"2024-02-29T12:00:00+00:00"
);
assert_eq!(
format_rfc3339_from_secs(u64::MAX),
"1969-12-31T23:59:59+00:00"
);
assert_eq!(
format_rfc3339_from_secs(CHRONO_MAX_TIMESTAMP as u64 + 1),
"1970-01-01T00:00:00+00:00"
);
assert_eq!(
format_rfc3339_from_secs(CHRONO_MAX_TIMESTAMP as u64),
"+262142-12-31T23:59:59+00:00"
);
}
#[cfg(feature = "serde")]
#[test]
fn iso8601_millis_shape() {
assert_eq!(format_iso8601_millis(at(0, 0)), "1970-01-01T00:00:00.000Z");
assert_eq!(
format_iso8601_millis(at(1_709_208_000, 123_456_789)),
"2024-02-29T12:00:00.123Z"
);
assert_eq!(
format_iso8601_millis(at(946_684_799, 999_999_999)),
"1999-12-31T23:59:59.999Z"
);
assert_eq!(
format_iso8601_millis(at(946_684_800, 0)),
"2000-01-01T00:00:00.000Z"
);
}
#[test]
fn extended_years_match_chrono_padding() {
assert_eq!(
format_rfc3339_from_secs(CHRONO_MIN_TIMESTAMP as u64),
"-262143-01-01T00:00:00+00:00"
);
assert_eq!(
format_rfc3339_from_secs(CHRONO_MAX_TIMESTAMP as u64),
"+262142-12-31T23:59:59+00:00"
);
assert_eq!(
format_rfc3339_from_secs((-62_167_219_200i64) as u64),
"0000-01-01T00:00:00+00:00"
);
assert_eq!(
format_rfc3339_from_secs((-62_135_596_800i64) as u64),
"0001-01-01T00:00:00+00:00"
);
assert_eq!(
format_rfc3339_from_secs((-62_198_755_200i64) as u64),
"-0001-01-01T00:00:00+00:00"
);
assert_eq!(
format_rfc3339_from_secs(253_402_300_800),
"+10000-01-01T00:00:00+00:00"
);
assert_eq!(
format_rfc3339_from_secs(253_402_300_799),
"9999-12-31T23:59:59+00:00"
);
}
#[test]
fn round_trip_across_a_full_leap_year() {
let mut prev = String::new();
for day in 0..366 {
let s = format_rfc3339(at(1_704_067_200 + day * 86_400, 0));
assert!(s > prev, "not monotonic: {} <= {}", s, prev);
prev = s;
}
assert_eq!(
format_rfc3339(at(1_704_067_200 + 59 * 86_400, 0)),
"2024-02-29T00:00:00+00:00"
);
assert_eq!(
format_rfc3339(at(1_704_067_200 + 366 * 86_400, 0)),
"2025-01-01T00:00:00+00:00"
);
}
}