use super::calendar::civil_from_days;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
const NANOS_PER_SECOND: u32 = 1_000_000_000;
const SECONDS_PER_DAY: i64 = 86_400;
fn divide(numerator: u32, denominator: u32) -> u32 {
numerator.checked_div(denominator).unwrap_or(0)
}
fn push_digit(out: &mut String, value: u32) {
out.push(char::from_digit(value % 10, 10).unwrap_or('0'));
}
fn digit_byte(value: u32) -> u8 {
b'0'.saturating_add(u8::try_from(value % 10).unwrap_or(0))
}
#[must_use]
pub fn unix_parts(at: SystemTime) -> (i64, u32) {
match at.duration_since(UNIX_EPOCH) {
Ok(duration_since_epoch) => (
i64::try_from(duration_since_epoch.as_secs()).unwrap_or(i64::MAX),
duration_since_epoch.subsec_nanos(),
),
Err(before) => split_pre_epoch(before.duration()),
}
}
fn split_pre_epoch(duration: Duration) -> (i64, u32) {
let whole_seconds = i64::try_from(duration.as_secs()).unwrap_or(i64::MAX);
let nanos = duration.subsec_nanos();
if nanos == 0 {
(whole_seconds.saturating_neg(), 0)
} else {
(
whole_seconds.saturating_neg().saturating_sub(1),
NANOS_PER_SECOND.saturating_sub(nanos),
)
}
}
#[must_use]
pub fn from_unix_parts(secs: i64, nanos: u32) -> SystemTime {
try_from_unix_parts(secs, nanos).unwrap_or(UNIX_EPOCH)
}
#[must_use]
pub fn try_from_unix_parts(secs: i64, nanos: u32) -> Option<SystemTime> {
let carry = nanos.checked_div(NANOS_PER_SECOND).unwrap_or(0);
let fraction = nanos.checked_rem(NANOS_PER_SECOND).unwrap_or(0);
let secs = secs.checked_add(i64::from(carry))?;
if secs >= 0 {
let whole = u64::try_from(secs).unwrap_or(0);
UNIX_EPOCH.checked_add(Duration::new(whole, fraction))
} else if fraction == 0 {
UNIX_EPOCH.checked_sub(Duration::new(secs.unsigned_abs(), 0))
} else {
UNIX_EPOCH.checked_sub(Duration::new(
secs.unsigned_abs().saturating_sub(1),
NANOS_PER_SECOND.saturating_sub(fraction),
))
}
}
fn write_two_digits(out: &mut String, value: u32) {
push_digit(out, divide(value, 10));
push_digit(out, value % 10);
}
fn write_four_digits(out: &mut String, value: u32) {
write_two_digits(out, divide(value, 100));
write_two_digits(out, value % 100);
}
fn extract_fraction_digits(nanos: u32, buf: &mut [u8; 9]) -> usize {
let mut remainder = nanos;
for index in (0..9).rev() {
buf[index] = digit_byte(remainder % 10);
remainder = divide(remainder, 10);
}
let mut length: usize = 9;
while length > 0 && buf[length.saturating_sub(1)] == b'0' {
length = length.saturating_sub(1);
}
length
}
fn write_fraction(out: &mut String, nanos: u32) {
let mut buf = [0u8; 9];
let length = extract_fraction_digits(nanos, &mut buf);
for &byte in &buf[..length] {
out.push(char::from(byte));
}
}
fn write_civil(out: &mut String, year: i64, month: u32, day: u32) {
if (0..=9999).contains(&year) {
write_four_digits(out, u32::try_from(year).unwrap_or(0));
} else {
use std::fmt::Write;
let _written = out.write_fmt(format_args!("{year:04}"));
}
out.push('-');
write_two_digits(out, month);
out.push('-');
write_two_digits(out, day);
}
fn write_time_parts(out: &mut String, hour: u32, min: u32, sec: u32) {
out.push('T');
write_two_digits(out, hour);
out.push(':');
write_two_digits(out, min);
out.push(':');
write_two_digits(out, sec);
}
fn write_optional_fraction(out: &mut String, nanos: u32) {
if nanos > 0 {
out.push('.');
write_fraction(out, nanos);
}
out.push('Z');
}
#[must_use]
pub fn to_rfc3339(at: SystemTime) -> String {
let (secs, nanos) = unix_parts(at);
let (year, month, day) = civil_from_days(secs.div_euclid(SECONDS_PER_DAY));
let rem = u32::try_from(secs.rem_euclid(SECONDS_PER_DAY)).unwrap_or(0);
let hour = divide(rem, 3_600);
let min = divide(rem % 3_600, 60);
let sec = rem % 60;
let mut out = String::with_capacity(35);
write_civil(&mut out, year, month, day);
write_time_parts(&mut out, hour, min, sec);
write_optional_fraction(&mut out, nanos);
out
}