#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct RawDate {
pub year: i32,
pub month: u8,
pub day: u8,
pub ordinal0: u16,
pub weekday: u8,
}
impl RawDate {
pub(crate) fn from_ymd(year: i32, month: u8, day: u8) -> Option<Self> {
if !(crate::MIN_YEAR..=crate::MAX_YEAR).contains(&year) {
return None;
}
if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
return None;
}
let max_day = days_in_month(year, month);
if day > max_day {
return None;
}
let ordinal0 = day_of_year(year, month, day) - 1;
let weekday = weekday_from_ymd(year, month, day);
Some(Self {
year,
month,
day,
ordinal0,
weekday,
})
}
#[inline]
pub(crate) fn is_supported(self) -> bool {
(crate::MIN_YEAR..=crate::MAX_YEAR).contains(&self.year)
}
#[inline]
pub(crate) fn is_supported_range_end(self) -> bool {
self.is_supported()
|| (self.year == crate::MAX_YEAR + 1 && self.month == 1 && self.day == 1)
}
pub(crate) const fn from_ymd_unchecked(year: i32, month: u8, day: u8) -> Self {
let ordinal0 = day_of_year(year, month, day) - 1;
let weekday = weekday_from_ymd(year, month, day);
Self {
year,
month,
day,
ordinal0,
weekday,
}
}
#[cfg(any(feature = "time", feature = "chrono"))]
#[inline]
pub(crate) fn from_calendar_date<D: crate::date::CalendarDate>(date: D) -> Self {
Self {
year: date.year(),
month: date.month(),
day: date.day(),
ordinal0: date.ordinal0(),
weekday: date.weekday_number_from_monday(),
}
}
pub(crate) fn next_day(self) -> Self {
let max_day = days_in_month(self.year, self.month);
let (year, month, day) = if self.day < max_day {
(self.year, self.month, self.day + 1)
} else if self.month < 12 {
(self.year, self.month + 1, 1)
} else {
(self.year + 1, 1, 1)
};
Self::from_ymd_unchecked(year, month, day)
}
}
#[inline]
pub(crate) const fn days_in_month(year: i32, month: u8) -> u8 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
if is_leap_year(year) {
29
} else {
28
}
}
_ => 30,
}
}
#[inline]
pub(crate) const fn is_leap_year(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}
const fn day_of_year(year: i32, month: u8, day: u8) -> u16 {
let mut doy = day as u16;
let mut m: u8 = 1;
while m < month {
doy += days_in_month(year, m) as u16;
m += 1;
}
doy
}
pub(crate) const fn weekday_from_ymd(year: i32, month: u8, day: u8) -> u8 {
let m = if month < 3 {
month as i32 + 12
} else {
month as i32
};
let y = if month < 3 { year - 1 } else { year };
let c = y / 100;
let ya = y % 100;
let h = (day as i32 + (13 * (m + 1)) / 5 + ya + ya / 4 + c / 4 - 2 * c) % 7;
((h + 5) % 7) as u8
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_days_in_month() {
assert_eq!(days_in_month(2024, 1), 31); assert_eq!(days_in_month(2024, 2), 29); assert_eq!(days_in_month(2025, 2), 28); assert_eq!(days_in_month(2024, 4), 30); }
#[test]
fn test_is_leap_year() {
assert!(is_leap_year(2024)); assert!(!is_leap_year(2025));
assert!(!is_leap_year(1900)); assert!(is_leap_year(2000)); }
#[test]
fn test_weekday_known_dates() {
assert_eq!(weekday_from_ymd(2026, 1, 1), 3);
assert_eq!(weekday_from_ymd(2026, 1, 5), 0);
assert_eq!(weekday_from_ymd(2026, 1, 11), 6);
assert_eq!(weekday_from_ymd(2024, 1, 1), 0);
assert_eq!(weekday_from_ymd(2025, 1, 1), 2);
}
#[test]
fn test_from_ymd_valid() {
let d = RawDate::from_ymd(2026, 1, 15).unwrap();
assert_eq!(d.year, 2026);
assert_eq!(d.month, 1);
assert_eq!(d.day, 15);
}
#[test]
fn test_from_ymd_invalid() {
assert!(RawDate::from_ymd(2026, 2, 31).is_none());
assert!(RawDate::from_ymd(2026, 13, 1).is_none());
assert!(RawDate::from_ymd(2026, 0, 1).is_none());
assert!(RawDate::from_ymd(2026, 1, 0).is_none());
assert!(RawDate::from_ymd(2025, 2, 29).is_none()); }
#[test]
fn test_next_day() {
let d = RawDate::from_ymd(2026, 1, 31).unwrap();
let next = d.next_day();
assert_eq!(next.year, 2026);
assert_eq!(next.month, 2);
assert_eq!(next.day, 1);
let d = RawDate::from_ymd(2026, 12, 31).unwrap();
let next = d.next_day();
assert_eq!(next.year, 2027);
assert_eq!(next.month, 1);
assert_eq!(next.day, 1);
}
#[test]
fn test_ordinal0() {
let d = RawDate::from_ymd(2026, 1, 1).unwrap();
assert_eq!(d.ordinal0, 0);
let d = RawDate::from_ymd(2026, 1, 10).unwrap();
assert_eq!(d.ordinal0, 9);
let d = RawDate::from_ymd(2026, 12, 31).unwrap();
assert_eq!(d.ordinal0, 364);
}
}