use core::fmt::{self, Debug, Formatter};
use crate::calendar::Iso;
use crate::{AsDate, Calendar, Month, PlainDateTime, PlainTime, Year};
const UNIX_TO_AKO: i32 = 719_468;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Date<C: Calendar = Iso> {
calendar: C,
days: i32,
}
macro_rules! date {
($year:expr, $month:expr, $day:expr) => {
const {
match $crate::Date::iso($year, $month, $day) {
Ok(date) => date,
Err(error) => error.panic(),
}
}
};
}
impl Date<Iso> {
pub const fn iso(year: i32, month: u8, day: u8) -> crate::Result<Self> {
Iso.date(year, month, day)
}
}
impl<C: Calendar> Date<C> {
pub fn of(calendar: C, year: i32, month: u8, day: u8) -> crate::Result<Self> {
calendar.date(year, month, day)
}
#[allow(unsafe_code)]
#[must_use]
pub(crate) const unsafe fn unchecked_of(calendar: C, days: i32) -> Self {
Self { calendar, days }
}
}
impl<C: Calendar> Date<C> {
pub fn from_ordinal_date(calendar: C, year: i32, day: u16) -> crate::Result<Self> {
calendar.date_from_ordinal(year, day)
}
#[must_use]
pub const fn from_days_since_unix_epoch(calendar: C, days: i32) -> Self {
Self {
calendar,
days: days + UNIX_TO_AKO,
}
}
#[must_use]
pub const fn from_unix_timestamp(calendar: C, seconds: i64) -> Self {
Self::from_days_since_unix_epoch(calendar, seconds.div_euclid(86_400) as i32)
}
}
impl<C: Calendar> Date<C> {
#[must_use]
pub fn components(self) -> (Year<C>, Month<C>, u8) {
self.calendar.date_components(self.days)
}
#[must_use]
pub const fn calendar(self) -> C {
self.calendar
}
#[must_use]
pub fn year(self) -> Year<C> {
self.components().0
}
#[must_use]
pub fn month(self) -> Month<C> {
self.components().1
}
#[must_use]
pub fn day(self) -> u8 {
self.components().2
}
#[must_use]
pub fn day_of_year(self) -> u16 {
self.calendar.date_to_ordinal(self.days).1
}
}
impl<C: Calendar> Date<C> {
#[must_use]
pub const fn on<C2: Calendar>(self, calendar: C2) -> Date<C2> {
Date::<C2> {
calendar,
days: self.days,
}
}
#[must_use]
pub fn with_time<T: Into<PlainTime>>(self, time: T) -> PlainDateTime<C> {
PlainDateTime {
date: self,
time: time.into(),
}
}
}
impl<C: Calendar> Date<C> {
#[must_use]
pub fn to_ordinal_date(self) -> (Year<C>, u16) {
self.calendar.date_to_ordinal(self.days)
}
#[must_use]
pub const fn as_days_since_unix_epoch(self) -> i32 {
self.days - UNIX_TO_AKO
}
#[must_use]
pub(crate) const fn as_days_since_ako_epoch(self) -> i32 {
self.days
}
}
impl<C: Calendar> Debug for Date<C> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad(&self.format_rfc3339())
}
}
impl<C: Calendar> AsDate<C> for Date<C> {
fn as_date(&self) -> Self {
*self
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::string::ToString;
use crate::{Date, YearMonth};
#[test]
fn expect_calendar_valid() -> crate::Result<()> {
for year in -9999..=9999 {
for month in 1..=12 {
let year_month = YearMonth::iso(year, month)?;
for day in 1..=year_month.days() {
let date = year_month.with_day(day)?;
assert_eq!(date.year().number(), year);
assert_eq!(date.month().number(), month);
assert_eq!(date.day(), day);
let year = if year >= 0 {
format!("{year:04}")
} else {
year.to_string()
};
let expected = format!("{}-{:02}-{:02}", year, month, day);
assert_eq!(date.format_rfc3339(), expected);
assert_eq!(Date::parse_rfc3339(&expected)?, date);
}
}
}
Ok(())
}
}