const UNIX_EPOCH_DAYS: i64 = 719468;
#[inline]
pub fn is_leap_year(year: i32) -> bool {
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
}
#[inline]
pub fn days_in_month(year: i32, month: u32) -> u32 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
if is_leap_year(year) {
29
} else {
28
}
}
_ => 0, }
}
#[inline]
pub fn ymd_to_days(year: i32, month: u32, day: u32) -> i64 {
let y = if month <= 2 {
year as i64 - 1
} else {
year as i64
};
let m = if month <= 2 { month + 12 } else { month } as i64;
let d = day as i64;
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = y - era * 400;
let doy = (153 * (m - 3) + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days_since_era_0 = era * 146097 + doe;
days_since_era_0 - UNIX_EPOCH_DAYS
}
#[inline]
pub fn days_to_ymd(days: i64) -> (i32, u32, u32) {
let z = days + UNIX_EPOCH_DAYS;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if m <= 2 { y + 1 } else { y };
(year as i32, m as u32, d as u32)
}
#[inline]
pub fn day_of_week(days: i64) -> u32 {
((days + 3).rem_euclid(7)) as u32
}
#[inline]
pub fn day_of_year(year: i32, month: u32, day: u32) -> u32 {
let days_before_month = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
let mut doy = days_before_month[(month - 1) as usize] + day;
if month > 2 && is_leap_year(year) {
doy += 1;
}
doy
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_leap_year() {
assert!(is_leap_year(2000)); assert!(is_leap_year(2004)); assert!(is_leap_year(2020));
assert!(is_leap_year(1600));
assert!(!is_leap_year(1900)); assert!(!is_leap_year(2100));
assert!(!is_leap_year(2001)); assert!(!is_leap_year(2019));
}
#[test]
fn test_days_in_month() {
assert_eq!(days_in_month(2020, 1), 31); assert_eq!(days_in_month(2020, 3), 31); assert_eq!(days_in_month(2020, 5), 31); assert_eq!(days_in_month(2020, 7), 31); assert_eq!(days_in_month(2020, 8), 31); assert_eq!(days_in_month(2020, 10), 31); assert_eq!(days_in_month(2020, 12), 31);
assert_eq!(days_in_month(2020, 4), 30); assert_eq!(days_in_month(2020, 6), 30); assert_eq!(days_in_month(2020, 9), 30); assert_eq!(days_in_month(2020, 11), 30);
assert_eq!(days_in_month(2020, 2), 29); assert_eq!(days_in_month(2019, 2), 28); assert_eq!(days_in_month(2000, 2), 29); assert_eq!(days_in_month(1900, 2), 28); }
#[test]
fn test_ymd_to_days_epoch() {
assert_eq!(ymd_to_days(1970, 1, 1), 0);
}
#[test]
fn test_ymd_to_days_roundtrip() {
let test_dates = [
(1970, 1, 1),
(2000, 1, 1),
(2020, 2, 29), (1900, 3, 1),
(2100, 12, 31),
(1600, 1, 1),
(1969, 12, 31), (1950, 6, 15),
];
for &(y, m, d) in &test_dates {
let days = ymd_to_days(y, m, d);
let (y2, m2, d2) = days_to_ymd(days);
assert_eq!(
(y, m, d),
(y2, m2, d2),
"Round-trip failed for {}-{:02}-{:02}",
y,
m,
d
);
}
}
#[test]
fn test_days_to_ymd_epoch() {
assert_eq!(days_to_ymd(0), (1970, 1, 1));
}
#[test]
fn test_day_of_week() {
assert_eq!(day_of_week(0), 3);
assert_eq!(day_of_week(4), 0);
assert_eq!(day_of_week(4), 0); assert_eq!(day_of_week(5), 1); assert_eq!(day_of_week(6), 2); assert_eq!(day_of_week(0), 3); assert_eq!(day_of_week(1), 4); assert_eq!(day_of_week(2), 5); assert_eq!(day_of_week(3), 6); }
#[test]
fn test_day_of_year() {
assert_eq!(day_of_year(2020, 1, 1), 1);
assert_eq!(day_of_year(2019, 12, 31), 365);
assert_eq!(day_of_year(2020, 12, 31), 366);
assert_eq!(day_of_year(2020, 2, 29), 60);
assert_eq!(day_of_year(2020, 3, 1), 61);
assert_eq!(day_of_year(2019, 3, 1), 60);
}
#[test]
fn test_negative_years() {
let days = ymd_to_days(-1, 12, 31);
let (y, m, d) = days_to_ymd(days);
assert_eq!((y, m, d), (-1, 12, 31));
}
#[test]
fn test_leap_year_boundaries() {
let feb28_leap = ymd_to_days(2020, 2, 28);
let feb29_leap = ymd_to_days(2020, 2, 29);
let mar1_leap = ymd_to_days(2020, 3, 1);
assert_eq!(feb29_leap - feb28_leap, 1);
assert_eq!(mar1_leap - feb29_leap, 1);
let feb28_normal = ymd_to_days(2019, 2, 28);
let mar1_normal = ymd_to_days(2019, 3, 1);
assert_eq!(mar1_normal - feb28_normal, 1);
}
}