const MS_PER_DAY: i64 = 86_400_000;
const MS_PER_HOUR: i64 = 3_600_000;
const MS_PER_MINUTE: i64 = 60_000;
const MS_PER_SECOND: i64 = 1_000;
fn civil_from_days(days: i64) -> (i64, i64, i64) {
let shifted = days + 719_468;
let era = shifted.div_euclid(146_097);
let day_of_era = shifted - era * 146_097;
let year_of_era =
(day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
let year = year_of_era + era * 400;
let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
let shifted_month = (5 * day_of_year + 2) / 153;
let day = day_of_year - (153 * shifted_month + 2) / 5 + 1;
let month = if shifted_month < 10 {
shifted_month + 3
} else {
shifted_month - 9
};
(if month <= 2 { year + 1 } else { year }, month, day)
}
pub(crate) fn iso8601_utc(milliseconds: i64) -> String {
let days = milliseconds.div_euclid(MS_PER_DAY);
let within_day = milliseconds.rem_euclid(MS_PER_DAY);
let (year, month, day) = civil_from_days(days);
let hour = within_day / MS_PER_HOUR;
let minute = (within_day % MS_PER_HOUR) / MS_PER_MINUTE;
let second = (within_day % MS_PER_MINUTE) / MS_PER_SECOND;
let milli = within_day % MS_PER_SECOND;
format!(
"{}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{milli:03}Z",
year_field(year)
)
}
fn year_field(year: i64) -> String {
if (0..=9999).contains(&year) {
return format!("{year:04}");
}
let sign = if year < 0 { '-' } else { '+' };
format!("{sign}{:06}", year.unsigned_abs())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_unix_epoch_is_the_epoch() {
assert_eq!(iso8601_utc(0), "1970-01-01T00:00:00.000Z");
}
#[test]
fn milliseconds_are_always_written() {
assert_eq!(iso8601_utc(1), "1970-01-01T00:00:00.001Z");
assert_eq!(iso8601_utc(1_000), "1970-01-01T00:00:01.000Z");
}
#[test]
fn known_instants_round_trip() {
for (milliseconds, expected) in [
(631_152_000_000, "1990-01-01T00:00:00.000Z"),
(946_684_800_000, "2000-01-01T00:00:00.000Z"),
(951_782_400_000, "2000-02-29T00:00:00.000Z"),
(1_288_834_974_657, "2010-11-04T01:42:54.657Z"),
(1_420_070_400_000, "2015-01-01T00:00:00.000Z"),
(1_786_492_800_000, "2026-08-12T00:00:00.000Z"),
] {
assert_eq!(iso8601_utc(milliseconds), expected, "{milliseconds}");
}
}
#[test]
fn an_instant_before_the_epoch_is_not_off_by_a_day() {
assert_eq!(iso8601_utc(-1), "1969-12-31T23:59:59.999Z");
assert_eq!(iso8601_utc(-MS_PER_DAY), "1969-12-31T00:00:00.000Z");
assert_eq!(
iso8601_utc(-12_219_292_800_000),
"1582-10-15T00:00:00.000Z",
"the Gregorian reform, which is where the v1 clock starts"
);
}
#[test]
fn a_year_outside_four_digits_is_written_in_expanded_form() {
const YEAR_10000: i64 = 253_402_300_800_000;
assert_eq!(iso8601_utc(YEAR_10000 - 1), "9999-12-31T23:59:59.999Z");
assert_eq!(iso8601_utc(YEAR_10000), "+010000-01-01T00:00:00.000Z");
assert_eq!(
iso8601_utc((1 << 48) - 1),
"+010889-08-02T05:31:50.655Z",
"the largest instant any kind here can decode"
);
}
#[test]
fn a_year_before_the_common_era_carries_its_sign() {
const YEAR_ZERO: i64 = -62_167_219_200_000;
assert_eq!(iso8601_utc(YEAR_ZERO), "0000-01-01T00:00:00.000Z");
assert_eq!(iso8601_utc(YEAR_ZERO - 1), "-000001-12-31T23:59:59.999Z");
}
#[test]
fn every_month_boundary_lands_on_the_first() {
let mut days = (2024 - 1970) * 365 + 13; for (year, month, length) in [
(2024, 1, 31),
(2024, 2, 29),
(2024, 3, 31),
(2024, 4, 30),
(2024, 5, 31),
(2024, 6, 30),
(2024, 7, 31),
(2024, 8, 31),
(2024, 9, 30),
(2024, 10, 31),
(2024, 11, 30),
(2024, 12, 31),
(2025, 1, 31),
(2025, 2, 28),
] {
assert_eq!(
iso8601_utc(days * MS_PER_DAY),
format!("{year:04}-{month:02}-01T00:00:00.000Z")
);
days += length;
}
}
}