pub fn year_to_days(year: i32) -> i32 {
let y = year - 1970;
if y >= 0 {
y * 365 + (y + 1) / 4 - (y + 69) / 100 + (y + 369) / 400
} else {
y * 365 + y / 4 - (y - 31) / 100 + (y - 31) / 400
}
}
pub fn days_to_year(days: i32) -> i32 {
let mut year = 1970 + days / 365;
loop {
let year_start = year_to_days(year);
if year_start > days {
year -= 1;
} else {
let next_year_start = year_to_days(year + 1);
if next_year_start <= days {
year += 1;
} else {
break;
}
}
}
year
}
pub fn days_to_year_month(days: i32) -> (i32, i32) {
let year = days_to_year(days);
let year_start = year_to_days(year);
let day_of_year = days - year_start;
let is_leap = is_leap_year(year);
let days_in_months: [i32; 12] = if is_leap {
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
} else {
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
};
let mut remaining = day_of_year;
for (i, &days_in_month) in days_in_months.iter().enumerate() {
if remaining < days_in_month {
return (year, i as i32 + 1);
}
remaining -= days_in_month;
}
(year, 12)
}
pub fn is_leap_year(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
pub fn parse_date_to_days(s: &str) -> Option<i32> {
let parts: Vec<&str> = s.split('-').collect();
if parts.len() != 3 {
return None;
}
let year: i32 = parts[0].parse().ok()?;
let month: i32 = parts[1].parse().ok()?;
let day: i32 = parts[2].parse().ok()?;
if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
return None;
}
let year_days = year_to_days(year);
let is_leap = is_leap_year(year);
let days_before_month: [i32; 12] = if is_leap {
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
} else {
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
};
Some(year_days + days_before_month[(month - 1) as usize] + day - 1)
}
pub fn parse_date_year(s: &str) -> Option<i32> {
let parts: Vec<&str> = s.split('-').collect();
if !parts.is_empty() {
parts[0].parse().ok()
} else {
None
}
}
pub fn parse_date_year_month(s: &str) -> Option<(i32, i32)> {
let parts: Vec<&str> = s.split('-').collect();
if parts.len() >= 2 {
let year: i32 = parts[0].parse().ok()?;
let month: i32 = parts[1].parse().ok()?;
Some((year, month))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_year_to_days() {
assert_eq!(year_to_days(1970), 0);
assert_eq!(year_to_days(1971), 365);
assert_eq!(year_to_days(2000), 10957);
}
#[test]
fn test_days_to_year() {
assert_eq!(days_to_year(0), 1970);
assert_eq!(days_to_year(365), 1971);
assert_eq!(days_to_year(10957), 2000);
}
#[test]
fn test_is_leap_year() {
assert!(!is_leap_year(1970));
assert!(is_leap_year(2000));
assert!(!is_leap_year(1900));
assert!(is_leap_year(2024));
}
#[test]
fn test_parse_date_to_days() {
let days = parse_date_to_days("2024-01-01").unwrap();
assert_eq!(days_to_year(days), 2024);
assert!(parse_date_to_days("invalid").is_none());
assert!(parse_date_to_days("2024-13-01").is_none());
}
#[test]
fn test_days_to_year_month() {
let days = parse_date_to_days("2024-01-15").unwrap();
assert_eq!(days_to_year_month(days), (2024, 1));
let days = parse_date_to_days("2024-06-15").unwrap();
assert_eq!(days_to_year_month(days), (2024, 6));
}
}