use crate::calendar_arithmetic::VALID_RD_RANGE;
use crate::error::{DateAddError, DateError, DateFromFieldsError, DateNewError};
use crate::options::DateFromFieldsOptions;
use crate::options::{DateAddOptions, DateDifferenceOptions};
use crate::types::{CyclicYear, EraYear, IsoWeekOfYear};
use crate::week::{RelativeUnit, WeekCalculator, WeekOf};
use crate::{types, Calendar, Iso};
#[cfg(feature = "alloc")]
use alloc::rc::Rc;
#[cfg(feature = "alloc")]
use alloc::sync::Arc;
use calendrical_calculations::rata_die::RataDie;
use core::fmt;
use core::ops::Deref;
pub trait AsCalendar {
type Calendar: Calendar;
fn as_calendar(&self) -> &Self::Calendar;
}
impl<C: Calendar> AsCalendar for C {
type Calendar = C;
#[inline]
fn as_calendar(&self) -> &Self {
self
}
}
#[cfg(feature = "alloc")]
impl<C: AsCalendar> AsCalendar for Rc<C> {
type Calendar = C::Calendar;
#[inline]
fn as_calendar(&self) -> &Self::Calendar {
self.as_ref().as_calendar()
}
}
#[cfg(feature = "alloc")]
impl<C: AsCalendar> AsCalendar for Arc<C> {
type Calendar = C::Calendar;
#[inline]
fn as_calendar(&self) -> &Self::Calendar {
self.as_ref().as_calendar()
}
}
#[allow(clippy::exhaustive_structs)] #[derive(PartialEq, Eq, Debug)]
pub struct Ref<'a, C>(pub &'a C);
impl<C> Copy for Ref<'_, C> {}
impl<C> Clone for Ref<'_, C> {
fn clone(&self) -> Self {
*self
}
}
impl<C: AsCalendar> AsCalendar for Ref<'_, C> {
type Calendar = C::Calendar;
#[inline]
fn as_calendar(&self) -> &Self::Calendar {
self.0.as_calendar()
}
}
impl<C> Deref for Ref<'_, C> {
type Target = C;
fn deref(&self) -> &C {
self.0
}
}
pub struct Date<A: AsCalendar> {
inner: <A::Calendar as Calendar>::DateInner,
calendar: A,
}
impl<A: AsCalendar> Date<A> {
#[inline]
#[deprecated(since = "2.2.0", note = "use `Date::try_new`")]
pub fn try_new_from_codes(
era: Option<&str>,
year: i32,
month_code: types::MonthCode,
day: u8,
calendar: A,
) -> Result<Self, DateError> {
#[allow(deprecated, reason = "internal usage")]
let inner = calendar
.as_calendar()
.from_codes(era, year, month_code, day)?;
Ok(Date::from_raw(inner, calendar))
}
#[inline]
pub fn try_new(
year: types::YearInput,
month: types::Month,
day: u8,
calendar: A,
) -> Result<Self, DateNewError> {
let inner = calendar.as_calendar().new_date(year, month, day)?;
Ok(Date::from_raw(inner, calendar))
}
#[inline]
pub fn try_from_fields(
fields: types::DateFields,
options: DateFromFieldsOptions,
calendar: A,
) -> Result<Self, DateFromFieldsError> {
let inner = calendar.as_calendar().from_fields(fields, options)?;
Ok(Date::from_raw(inner, calendar))
}
#[inline]
pub fn from_rata_die(rd: RataDie, calendar: A) -> Self {
let rd = rd.clamp(*VALID_RD_RANGE.start(), *VALID_RD_RANGE.end());
Date::from_raw(calendar.as_calendar().from_rata_die(rd), calendar)
}
#[inline]
pub fn to_rata_die(&self) -> RataDie {
self.calendar.as_calendar().to_rata_die(self.inner())
}
#[inline]
#[deprecated(since = "2.2.0", note = "use `iso.to_calendar(calendar)`")]
pub fn new_from_iso(iso: Date<Iso>, calendar: A) -> Self {
iso.to_calendar(calendar)
}
#[inline]
#[deprecated(since = "2.2.0", note = "use `date.to_calendar(Iso)`")]
pub fn to_iso(&self) -> Date<Iso> {
self.to_calendar(Iso)
}
#[inline]
pub fn to_calendar<A2: AsCalendar>(&self, calendar: A2) -> Date<A2> {
let c1 = self.calendar.as_calendar();
let c2 = calendar.as_calendar();
let inner = if c1.has_cheap_iso_conversion() && c2.has_cheap_iso_conversion() {
c2.from_iso(c1.to_iso(self.inner()))
} else {
c2.from_rata_die(c1.to_rata_die(self.inner()))
};
Date::from_raw(inner, calendar)
}
#[inline]
pub fn day_of_month(&self) -> types::DayOfMonth {
self.calendar.as_calendar().day_of_month(&self.inner)
}
#[inline]
pub fn day_of_year(&self) -> types::DayOfYear {
self.calendar.as_calendar().day_of_year(&self.inner)
}
#[inline]
pub fn weekday(&self) -> types::Weekday {
self.to_rata_die().into()
}
#[deprecated(since = "2.2.0", note = "use `Date::weekday`")]
pub fn day_of_week(&self) -> types::Weekday {
self.to_rata_die().into()
}
#[inline]
pub fn month(&self) -> types::MonthInfo {
self.calendar.as_calendar().month(&self.inner)
}
#[inline]
pub fn year(&self) -> types::YearInfo {
self.calendar.as_calendar().year_info(&self.inner).into()
}
#[inline]
#[deprecated(since = "2.2.0", note = "use `date.year().extended_year()`")]
pub fn extended_year(&self) -> i32 {
self.year().extended_year()
}
#[inline]
pub fn is_in_leap_year(&self) -> bool {
self.calendar.as_calendar().is_in_leap_year(&self.inner)
}
#[inline]
pub fn days_in_month(&self) -> u8 {
self.calendar.as_calendar().days_in_month(self.inner())
}
#[inline]
pub fn days_in_year(&self) -> u16 {
self.calendar.as_calendar().days_in_year(self.inner())
}
#[inline]
pub fn months_in_year(&self) -> u8 {
self.calendar.as_calendar().months_in_year(self.inner())
}
#[inline]
pub fn try_add_with_options(
&mut self,
duration: types::DateDuration,
options: DateAddOptions,
) -> Result<(), DateAddError> {
let inner = self
.calendar
.as_calendar()
.add(&self.inner, duration, options)?;
self.inner = inner;
Ok(())
}
#[inline]
pub fn try_added_with_options(
mut self,
duration: types::DateDuration,
options: DateAddOptions,
) -> Result<Self, DateAddError> {
self.try_add_with_options(duration, options)?;
Ok(self)
}
#[inline]
pub fn try_until_with_options<B: AsCalendar<Calendar = A::Calendar>>(
&self,
other: &Date<B>,
options: DateDifferenceOptions,
) -> Result<types::DateDuration, <A::Calendar as Calendar>::DateCompatibilityError> {
self.calendar().check_date_compatibility(other.calendar())?;
Ok(self
.calendar
.as_calendar()
.until(self.inner(), other.inner(), options))
}
#[inline]
pub fn from_raw(inner: <A::Calendar as Calendar>::DateInner, calendar: A) -> Self {
Self { inner, calendar }
}
#[inline]
pub fn inner(&self) -> &<A::Calendar as Calendar>::DateInner {
&self.inner
}
#[inline]
pub fn calendar(&self) -> &A::Calendar {
self.calendar.as_calendar()
}
#[inline]
pub(crate) fn into_calendar(self) -> A {
self.calendar
}
#[inline]
pub fn calendar_wrapper(&self) -> &A {
&self.calendar
}
}
impl<A: AsCalendar<Calendar = C>, C: Calendar<Year = EraYear>> Date<A> {
pub fn era_year(&self) -> EraYear {
self.calendar.as_calendar().year_info(self.inner())
}
}
impl<A: AsCalendar<Calendar = C>, C: Calendar<Year = CyclicYear>> Date<A> {
pub fn cyclic_year(&self) -> CyclicYear {
self.calendar.as_calendar().year_info(self.inner())
}
}
impl Date<Iso> {
pub fn week_of_year(&self) -> IsoWeekOfYear {
let week_of = WeekCalculator::ISO
.week_of(
365 + calendrical_calculations::gregorian::is_leap_year(self.inner.0.year() - 1)
as u16,
self.days_in_year(),
self.day_of_year().0,
self.weekday(),
)
.unwrap_or_else(|_| {
debug_assert!(false);
WeekOf {
week: 1,
unit: RelativeUnit::Current,
}
});
IsoWeekOfYear {
week_number: week_of.week,
iso_year: match week_of.unit {
RelativeUnit::Current => self.inner.0.year(),
RelativeUnit::Next => self.inner.0.year() + 1,
RelativeUnit::Previous => self.inner.0.year() - 1,
},
}
}
}
impl<A: AsCalendar> Date<A> {
#[cfg(feature = "alloc")]
pub fn into_ref_counted(self) -> Date<Rc<A>> {
Date::from_raw(self.inner, Rc::new(self.calendar))
}
#[cfg(feature = "alloc")]
pub fn into_atomic_ref_counted(self) -> Date<Arc<A>> {
Date::from_raw(self.inner, Arc::new(self.calendar))
}
pub fn as_borrowed(&self) -> Date<Ref<'_, A>> {
Date::from_raw(self.inner, Ref(&self.calendar))
}
}
impl<C, A, B> PartialEq<Date<B>> for Date<A>
where
C: Calendar,
A: AsCalendar<Calendar = C>,
B: AsCalendar<Calendar = C>,
{
fn eq(&self, other: &Date<B>) -> bool {
match self.calendar().check_date_compatibility(other.calendar()) {
Ok(_) => self.inner.eq(&other.inner),
Err(_) => false,
}
}
}
impl<A: AsCalendar> Eq for Date<A> {}
impl<C, A, B> PartialOrd<Date<B>> for Date<A>
where
C: Calendar,
A: AsCalendar<Calendar = C>,
B: AsCalendar<Calendar = C>,
{
fn partial_cmp(&self, other: &Date<B>) -> Option<core::cmp::Ordering> {
match self.calendar().check_date_compatibility(other.calendar()) {
Ok(_) => self.inner.partial_cmp(&other.inner),
Err(_) => None,
}
}
}
impl<C, A> Ord for Date<A>
where
C: Calendar,
C::DateInner: Ord,
A: AsCalendar<Calendar = C>,
{
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match self.calendar().check_date_compatibility(other.calendar()) {
Ok(_) => self.inner().cmp(other.inner()),
Err(_) => {
self.inner().cmp(other.inner())
}
}
}
}
impl<A: AsCalendar> fmt::Debug for Date<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let month = self.month().ordinal;
let day = self.day_of_month().0;
let calendar = self.calendar.as_calendar().debug_name();
match self.year() {
types::YearInfo::Era(EraYear { year, era, .. }) => {
write!(
f,
"Date({year}-{month}-{day}, {era} era, for calendar {calendar})"
)
}
types::YearInfo::Cyclic(CyclicYear { year, related_iso }) => {
write!(
f,
"Date({year}-{month}-{day}, ISO year {related_iso}, for calendar {calendar})"
)
}
}
}
}
impl<A: AsCalendar + Clone> Clone for Date<A> {
fn clone(&self) -> Self {
Self {
inner: self.inner,
calendar: self.calendar.clone(),
}
}
}
impl<A> Copy for Date<A> where A: AsCalendar + Copy {}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cal::{Buddhist, Hebrew},
types::{Month, Weekday},
Gregorian,
};
#[test]
fn test_ord() {
let dates_in_order = [
Date::try_new_iso(-10, 1, 1).unwrap(),
Date::try_new_iso(-10, 1, 2).unwrap(),
Date::try_new_iso(-10, 2, 1).unwrap(),
Date::try_new_iso(-1, 1, 1).unwrap(),
Date::try_new_iso(-1, 1, 2).unwrap(),
Date::try_new_iso(-1, 2, 1).unwrap(),
Date::try_new_iso(0, 1, 1).unwrap(),
Date::try_new_iso(0, 1, 2).unwrap(),
Date::try_new_iso(0, 2, 1).unwrap(),
Date::try_new_iso(1, 1, 1).unwrap(),
Date::try_new_iso(1, 1, 2).unwrap(),
Date::try_new_iso(1, 2, 1).unwrap(),
Date::try_new_iso(10, 1, 1).unwrap(),
Date::try_new_iso(10, 1, 2).unwrap(),
Date::try_new_iso(10, 2, 1).unwrap(),
];
for (i, i_date) in dates_in_order.iter().enumerate() {
for (j, j_date) in dates_in_order.iter().enumerate() {
let result1 = i_date.cmp(j_date);
let result2 = j_date.cmp(i_date);
assert_eq!(result1.reverse(), result2);
assert_eq!(i.cmp(&j), i_date.cmp(j_date));
}
}
}
#[test]
fn test_weekday() {
assert_eq!(
Date::try_new_iso(2021, 6, 23).unwrap().weekday(),
Weekday::Wednesday,
);
assert_eq!(
Date::try_new_iso(1983, 2, 2).unwrap().weekday(),
Weekday::Wednesday,
);
assert_eq!(
Date::try_new_iso(2020, 1, 21).unwrap().weekday(),
Weekday::Tuesday,
);
}
#[test]
fn test_to_calendar() {
let date = Date::try_new_gregorian(2025, 12, 9).unwrap();
let date2 = date.to_calendar(Buddhist).to_calendar(Gregorian);
let date3 = date.to_calendar(Hebrew).to_calendar(Gregorian);
assert_eq!(date, date2);
assert_eq!(date2, date3);
}
#[test]
fn test_try_new() {
use crate::cal::Japanese;
use crate::types::YearInput;
let date = Date::try_new(2025.into(), Month::new(1), 1, Gregorian).unwrap();
assert_eq!(date, Date::try_new_gregorian(2025, 1, 1).unwrap());
let date2 = Date::try_new(
YearInput::EraYear("reiwa", 7),
Month::new(1),
1,
Japanese::new(),
)
.unwrap();
let date2_expected =
Date::try_new_japanese_with_calendar("reiwa", 7, 1, 1, Japanese::new()).unwrap();
assert_eq!(date2, date2_expected);
}
#[test]
fn date_add_options_default_is_constrain() {
use crate::cal::ChineseTraditional;
use crate::duration::DateDuration;
use crate::types::Month;
let mut date = Date::try_new(5787.into(), Month::leap(5), 10, Hebrew).unwrap();
date.try_add_with_options(DateDuration::for_years(1), DateAddOptions::default())
.unwrap();
assert_eq!(date.month().to_input(), Month::new(6));
let mut date = Date::try_new(
2025.into(),
Month::leap(6),
1,
ChineseTraditional::default(),
)
.unwrap();
date.try_add_with_options(DateDuration::for_years(1), DateAddOptions::default())
.unwrap();
assert_eq!(date.month().to_input(), Month::new(6));
}
}