pub const DATE_EPOCH_DAYS_BEFORE_UNIX: i64 = 4017;
pub const SA_DAY_MIN_PLAUSIBLE: u32 = 1;
pub const SA_DAY_MAX_PLAUSIBLE: u32 = 80_000;
pub fn sa_day_to_unix_day(sa_day: u32) -> Option<i64> {
if !(SA_DAY_MIN_PLAUSIBLE..=SA_DAY_MAX_PLAUSIBLE).contains(&sa_day) {
return None;
}
Some(sa_day as i64 - DATE_EPOCH_DAYS_BEFORE_UNIX)
}
pub fn unix_day_to_sa_day(unix_day: i64) -> Option<u32> {
let v = unix_day + DATE_EPOCH_DAYS_BEFORE_UNIX;
if v < 0 || v > SA_DAY_MAX_PLAUSIBLE as i64 {
return None;
}
Some(v as u32)
}
pub fn sa_day_to_unix_seconds(sa_day: u32) -> Option<i64> {
sa_day_to_unix_day(sa_day).map(|d| d * 86_400)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sa_day_round_trips_through_1990() {
let sa_day = unix_day_to_sa_day(7305).unwrap();
assert_eq!(sa_day, 7305 + 4017);
assert_eq!(sa_day_to_unix_day(sa_day), Some(7305));
}
#[test]
fn day_zero_and_max_are_rejected_in_sa() {
assert_eq!(sa_day_to_unix_day(0), None);
assert_eq!(sa_day_to_unix_day(u32::MAX), None);
}
#[test]
fn upper_plausible_boundary_is_inclusive() {
assert!(sa_day_to_unix_day(SA_DAY_MAX_PLAUSIBLE).is_some());
assert_eq!(sa_day_to_unix_day(SA_DAY_MAX_PLAUSIBLE + 1), None);
}
#[test]
fn rock_castle_min_max_decode_to_expected_unix_days() {
assert_eq!(sa_day_to_unix_day(13_000), Some(8_983));
assert_eq!(sa_day_to_unix_day(19_790), Some(15_773));
}
#[test]
fn unix_seconds_aligns_with_unix_day() {
let secs = sa_day_to_unix_seconds(13_000).unwrap();
assert_eq!(secs % 86_400, 0);
assert_eq!(secs / 86_400, 8_983);
}
#[test]
fn negative_unix_day_rejected_for_sa_conversion() {
assert_eq!(unix_day_to_sa_day(-5000), None);
}
}