use icu_calendar::{Date as IcuDate, Gregorian};
use crate::field::context::{Date, Time};
const SECONDS_PER_DAY: i64 = 86_400;
const SECONDS_PER_HOUR: i64 = 3_600;
const SECONDS_PER_MINUTE: i64 = 60;
pub fn from_unix_seconds(unix_seconds: i64) -> (Date, Time) {
let days = unix_seconds.div_euclid(SECONDS_PER_DAY);
let seconds_of_day = unix_seconds.rem_euclid(SECONDS_PER_DAY);
let epoch = IcuDate::try_new_gregorian(1970, 1, 1)
.expect("1970-01-01 is a valid Gregorian date")
.to_rata_die();
let date = IcuDate::from_rata_die(epoch + days, Gregorian);
(
Date {
year: date.year().extended_year(),
month: u32::from(date.month().ordinal),
day: u32::from(date.day_of_month().0),
},
Time {
hour: (seconds_of_day / SECONDS_PER_HOUR) as u32,
minute: (seconds_of_day % SECONDS_PER_HOUR / SECONDS_PER_MINUTE) as u32,
second: (seconds_of_day % SECONDS_PER_MINUTE) as u32,
},
)
}
pub fn now() -> (Date, Time) {
let unix_seconds = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |elapsed| elapsed.as_secs() as i64);
from_unix_seconds(unix_seconds)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decodes_a_known_instant() {
let (date, time) = from_unix_seconds(1_786_278_896);
assert_eq!((date.year, date.month, date.day), (2026, 8, 9));
assert_eq!((time.hour, time.minute, time.second), (12, 34, 56));
}
#[test]
fn decodes_the_epoch_itself() {
let (date, time) = from_unix_seconds(0);
assert_eq!((date.year, date.month, date.day), (1970, 1, 1));
assert_eq!((time.hour, time.minute, time.second), (0, 0, 0));
}
#[test]
fn a_day_boundary_falls_on_the_right_side() {
let (before, t_before) = from_unix_seconds(1_786_233_599); assert_eq!((before.year, before.month, before.day), (2026, 8, 8));
assert_eq!(
(t_before.hour, t_before.minute, t_before.second),
(23, 59, 59)
);
let (after, t_after) = from_unix_seconds(1_786_233_600); assert_eq!((after.year, after.month, after.day), (2026, 8, 9));
assert_eq!((t_after.hour, t_after.minute, t_after.second), (0, 0, 0));
}
#[test]
fn a_pre_epoch_timestamp_floors_instead_of_truncating() {
let (date, time) = from_unix_seconds(-1); assert_eq!((date.year, date.month, date.day), (1969, 12, 31));
assert_eq!((time.hour, time.minute, time.second), (23, 59, 59));
}
#[test]
fn leap_days_land_correctly() {
let (date, _) = from_unix_seconds(951_782_400); assert_eq!((date.year, date.month, date.day), (2000, 2, 29));
let (date, _) = from_unix_seconds(1_709_164_800); assert_eq!((date.year, date.month, date.day), (2024, 2, 29));
}
#[test]
fn now_returns_a_plausible_present_date() {
let (date, time) = now();
assert!(date.year >= 2026, "year {} looks wrong", date.year);
assert!((1..=12).contains(&date.month));
assert!((1..=31).contains(&date.day));
assert!(time.hour < 24 && time.minute < 60 && time.second < 60);
}
}