pub mod raw_date;
use core::fmt::Display;
use crate::{ParsedDate, SacOrGreg, month::Month, scalars::Year, traits::CalendarDate};
use raw_date::{YearOrdinal, date_to_yo, yo_to_date};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Date {
year: Year,
month: Month,
day: u8,
}
impl Date {
pub fn parse_str(input: &str) -> Option<ParsedDate> {
SacOrGreg::parse_str(input)?.to_sac13()
}
#[must_use]
pub const fn from_ymd(year: Year, month: Month, day: u8) -> Option<Self> {
if day == 0 || day > Self::month_len(year, month) {
None
} else {
Some(Self { year, month, day })
}
}
#[must_use]
pub const fn from_ymd_untyped(year: u16, month: u8, day: u8) -> Option<Self> {
let Some(year) = Year::new(year) else {
return None;
};
let Some(month) = Month::new(month) else {
return None;
};
Self::from_ymd(year, month, day)
}
#[must_use]
pub const fn year(&self) -> Year {
self.year
}
#[must_use]
pub const fn month(&self) -> Month {
self.month
}
#[must_use]
pub const fn day(&self) -> u8 {
self.day
}
#[must_use]
pub const fn weekday_ordinal(&self) -> u8 {
match self.day {
29 => 8,
x => (x - 1) % 7 + 1,
}
}
#[must_use]
pub const fn month_len(year: Year, month: Month) -> u8 {
if matches!(month, Month::Addenduary) || (matches!(month, Month::August) && year.is_leap())
{
29
} else {
28
}
}
}
impl Display for Date {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}-{:02}-{:02}", self.year, self.month.ord(), self.day)
}
}
impl CalendarDate for Date {
const MIN: Self = date!(A000 - 01 - 01);
const MAX: Self = date!(Z999 - 13 - 29);
fn as_julian(&self) -> i32 {
date_to_yo(*self).as_julian()
}
fn from_julian(value: i32) -> Option<Self> {
Some(yo_to_date(YearOrdinal::from_julian(value)?))
}
fn tomorrow(mut self) -> Option<Self> {
if self.day < 28 {
self.day += 1;
return Some(self);
}
let days = Self::month_len(self.year, self.month);
if self.day < days {
self.day += 1;
return Some(self);
}
self.day = 1;
self.month = self.month.next();
if matches!(self.month, Month::March) {
self.year = ok!(self.year.next());
}
Some(self)
}
fn yesterday(mut self) -> Option<Self> {
if self.day > 1 {
self.day -= 1;
return Some(self);
}
self.month = self.month().previous();
if matches!(self.month, Month::Addenduary) {
self.year = ok!(self.year.previous());
}
self.day = Self::month_len(self.year, self.month);
Some(self)
}
}
#[cfg(test)]
mod tests {
use raw_date::YearOrdinal;
use crate::{
scalars::{CycleEpochDay, UnixDay},
traits::CalendarDate,
};
use super::*;
#[test]
fn test_date_order_and_equality() {
assert!(date!(M020 - 05 - 16) == date!(M020 - 05 - 16));
assert!(date!(M020 - 05 - 15) < date!(M020 - 05 - 16));
assert!(date!(M020 - 05 - 16) > date!(M020 - 05 - 15));
assert!(date!(M020 - 04 - 17) < date!(M020 - 05 - 16));
assert!(date!(M019 - 06 - 17) < date!(M020 - 05 - 16));
}
#[test]
pub fn reference_date_unix_epoch_works() {
let date: Date = UnixDay::new(11036).unwrap().convert();
assert_eq!(date.year(), year!(M000));
assert_eq!(date.month(), Month::March);
assert_eq!(date.day(), 1);
}
#[test]
pub fn reference_date_julian_day_works() {
let date: Date = Date::from_julian(2451624).unwrap();
assert_eq!(date.year(), year!(M000));
assert_eq!(date.month(), Month::March);
assert_eq!(date.day(), 1);
}
#[test]
pub fn reference_date_gregorian_converted_to_sac13_works() {
let date: Date = date_greg!(2000 - 03 - 20).convert();
assert_eq!(date.year(), year!(M000));
assert_eq!(date.month(), Month::March);
assert_eq!(date.day(), 1);
}
#[test]
pub fn reference_timestamp_year_zero_works() {
let result: YearOrdinal = CycleEpochDay::new(72683).unwrap().convert();
assert_eq!(result.year(), year!(A000));
assert_eq!(result.day(), 0);
}
#[test]
pub fn leap_year_rule_works_as_expected() {
assert!(year!(L814).is_common());
assert!(year!(L815).is_leap());
}
}