#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Unit {
Seconds,
Millis,
Micros,
Nanos,
}
const DAY_SECONDS: i128 = 86_400;
const EPOCH_SHIFT: i64 = 719_468;
impl Unit {
const fn per_second(self) -> i128 {
match self {
Self::Seconds => 1,
Self::Millis => 1_000,
Self::Micros => 1_000_000,
Self::Nanos => 1_000_000_000,
}
}
}
pub(crate) fn date_text(days: i32) -> String {
match days {
i32::MAX => "infinity".to_owned(),
d if d == -i32::MAX => "-infinity".to_owned(),
d => civil_text(i64::from(d)),
}
}
pub(crate) fn time_text(unit: Unit, value: i64) -> String {
let (seconds, nanos) = split(unit, value);
clock_text(seconds, nanos)
}
pub(crate) fn timestamp_text(unit: Unit, value: i64) -> String {
match value {
i64::MAX => "infinity".to_owned(),
v if v == -i64::MAX => "-infinity".to_owned(),
v => {
let (seconds, nanos) = split(unit, v);
let days = seconds.div_euclid(DAY_SECONDS);
let clock = clock_text(seconds.rem_euclid(DAY_SECONDS), nanos);
let days = i64::try_from(days).unwrap_or_default();
format!("{}T{clock}", civil_text(days))
}
}
}
fn split(unit: Unit, value: i64) -> (i128, u32) {
let per = unit.per_second();
let value = i128::from(value);
let nanos = value.rem_euclid(per) * (1_000_000_000 / per);
(
value.div_euclid(per),
u32::try_from(nanos).unwrap_or_default(),
)
}
fn clock_text(seconds: i128, nanos: u32) -> String {
let (h, m, s) = (seconds / 3600, seconds / 60 % 60, seconds % 60);
let mut text = format!("{h:02}:{m:02}:{s:02}");
if nanos > 0 {
let fraction = format!("{nanos:09}");
text.push('.');
text.push_str(fraction.trim_end_matches('0'));
}
text
}
fn civil_text(days: i64) -> String {
let z = days + EPOCH_SHIFT;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = doy - (153 * mp + 2) / 5 + 1;
let month = if mp < 10 { mp + 3 } else { mp - 9 };
let year = yoe + era * 400 + i64::from(month <= 2);
let year = match year {
0..=9999 => format!("{year:04}"),
y if y > 9999 => format!("+{y}"),
y => format!("-{:04}", y.unsigned_abs()),
};
format!("{year}-{month:02}-{day:02}")
}
#[cfg(test)]
mod tests;