use std::fmt::Write as _;
const NANOS_PER_SECOND: u64 = 1_000_000_000;
const SECONDS_PER_DAY: u64 = 86_400;
#[must_use]
pub fn format_unix_nanos_rfc3339(unix_nanos: u64) -> String {
let whole_seconds = unix_nanos / NANOS_PER_SECOND;
let subsecond_nanos = unix_nanos % NANOS_PER_SECOND;
let unix_days = whole_seconds / SECONDS_PER_DAY;
let seconds_of_day = whole_seconds % SECONDS_PER_DAY;
let (year, month, day) = civil_date_from_unix_days(unix_days);
let hour = seconds_of_day / 3_600;
let minute = seconds_of_day % 3_600 / 60;
let second = seconds_of_day % 60;
let mut rendered = String::with_capacity(30);
write!(
rendered,
"{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}"
)
.expect("writing to a String cannot fail");
if subsecond_nanos != 0 {
rendered.push('.');
write!(rendered, "{subsecond_nanos:09}").expect("writing to a String cannot fail");
while rendered.ends_with('0') {
rendered.pop();
}
}
rendered.push('Z');
rendered
}
fn civil_date_from_unix_days(unix_days: u64) -> (i64, i64, i64) {
let shifted_days =
i64::try_from(unix_days).expect("the u64 nanosecond domain fits in i64 days") + 719_468;
let era = shifted_days / 146_097;
let day_of_era = shifted_days - 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 mut 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 month_prime = (5 * day_of_year + 2) / 153;
let day = day_of_year - (153 * month_prime + 2) / 5 + 1;
let month = month_prime + if month_prime < 10 { 3 } else { -9 };
year += i64::from(month <= 2);
(year, month, day)
}
#[cfg(test)]
mod tests {
use super::format_unix_nanos_rfc3339;
#[test]
fn formats_fixed_nonnegative_unix_nanosecond_vectors() {
let vectors = [
(0, "1970-01-01T00:00:00Z"),
(1, "1970-01-01T00:00:00.000000001Z"),
(100_000_000, "1970-01-01T00:00:00.1Z"),
(120_000_000, "1970-01-01T00:00:00.12Z"),
(951_782_400_000_000_000, "2000-02-29T00:00:00Z"),
(1_582_979_696_123_456_789, "2020-02-29T12:34:56.123456789Z"),
(4_107_542_400_000_000_000, "2100-03-01T00:00:00Z"),
(u64::MAX, "2554-07-21T23:34:33.709551615Z"),
];
for (unix_nanos, expected) in vectors {
assert_eq!(format_unix_nanos_rfc3339(unix_nanos), expected);
}
}
#[test]
fn crosses_day_and_gregorian_leap_boundaries_exactly() {
let vectors = [
(951_782_399_999_999_999, "2000-02-28T23:59:59.999999999Z"),
(951_782_400_000_000_000, "2000-02-29T00:00:00Z"),
(4_107_542_399_999_999_999, "2100-02-28T23:59:59.999999999Z"),
(4_107_542_400_000_000_000, "2100-03-01T00:00:00Z"),
];
for (unix_nanos, expected) in vectors {
assert_eq!(format_unix_nanos_rfc3339(unix_nanos), expected);
}
}
}