use crate::calendar_arithmetic::{ArithmeticDate, DateFieldsResolver};
use crate::error::{
DateAddError, DateFromFieldsError, DateNewError, EcmaReferenceYearError, UnknownEraError,
};
use crate::options::DateFromFieldsOptions;
use crate::options::{DateAddOptions, DateDifferenceOptions};
use crate::{types, Calendar, Date, RangeError};
use calendrical_calculations::rata_die::RataDie;
use tinystr::tinystr;
#[derive(Copy, Clone, Debug, Hash, Default, Eq, PartialEq, PartialOrd, Ord)]
#[allow(clippy::exhaustive_structs)] pub struct Coptic;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct CopticDateInner(pub(crate) ArithmeticDate<Coptic>);
impl DateFieldsResolver for Coptic {
type YearInfo = i32;
fn days_in_provided_month(year: i32, month: u8) -> u8 {
if month == 13 {
5 + calendrical_calculations::coptic::is_leap_year(year) as u8
} else {
30
}
}
fn months_in_provided_year(_: i32) -> u8 {
13
}
#[inline]
fn min_months_from(_start: Self::YearInfo, years: i32) -> i32 {
13 * years
}
#[inline]
fn extended_year_from_era_year_unchecked(
&self,
era: &[u8],
era_year: i32,
) -> Result<i32, UnknownEraError> {
match era {
b"am" => Ok(era_year),
_ => Err(UnknownEraError),
}
}
#[inline]
fn year_info_from_extended(&self, extended_year: i32) -> Self::YearInfo {
extended_year
}
#[inline]
fn reference_year_from_month_day(
&self,
month: types::Month,
day: u8,
) -> Result<Self::YearInfo, EcmaReferenceYearError> {
Coptic::reference_year_from_month_day(month, day)
}
fn to_rata_die_inner(year: Self::YearInfo, month: u8, day: u8) -> RataDie {
calendrical_calculations::coptic::fixed_from_coptic(year, month, day)
}
}
impl Coptic {
pub(crate) fn reference_year_from_month_day(
month: types::Month,
day: u8,
) -> Result<i32, EcmaReferenceYearError> {
let (ordinal_month, false) = (month.number(), month.is_leap()) else {
return Err(EcmaReferenceYearError::MonthNotInCalendar);
};
let anno_martyrum_year = if ordinal_month < 4 || (ordinal_month == 4 && day <= 22) {
1689
} else if ordinal_month == 13 && day >= 6 {
1687
} else {
1688
};
Ok(anno_martyrum_year)
}
}
impl crate::cal::scaffold::UnstableSealed for Coptic {}
impl Calendar for Coptic {
type DateInner = CopticDateInner;
type Year = types::EraYear;
type DateCompatibilityError = core::convert::Infallible;
fn new_date(
&self,
year: types::YearInput,
month: types::Month,
day: u8,
) -> Result<Self::DateInner, DateNewError> {
ArithmeticDate::from_input_year_month_code_day(year, month, day, self).map(CopticDateInner)
}
fn from_fields(
&self,
fields: types::DateFields,
options: DateFromFieldsOptions,
) -> Result<Self::DateInner, DateFromFieldsError> {
ArithmeticDate::from_fields(fields, options, self).map(CopticDateInner)
}
fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
let (year, month, day) =
calendrical_calculations::coptic::coptic_from_fixed(rd).unwrap_or((1, 1, 1));
CopticDateInner(ArithmeticDate::new_unchecked(year, month, day))
}
fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
date.0.to_rata_die()
}
fn has_cheap_iso_conversion(&self) -> bool {
false
}
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
Self::months_in_provided_year(date.0.year())
}
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
if self.is_in_leap_year(date) {
366
} else {
365
}
}
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
Self::days_in_provided_month(date.0.year(), date.0.month())
}
fn add(
&self,
date: &Self::DateInner,
duration: types::DateDuration,
options: DateAddOptions,
) -> Result<Self::DateInner, DateAddError> {
date.0.added(duration, self, options).map(CopticDateInner)
}
fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
options: DateDifferenceOptions,
) -> types::DateDuration {
date1.0.until(&date2.0, self, options)
}
fn check_date_compatibility(&self, &Self: &Self) -> Result<(), Self::DateCompatibilityError> {
Ok(())
}
fn year_info(&self, date: &Self::DateInner) -> Self::Year {
let year = date.0.year();
types::EraYear {
era: tinystr!(16, "am"),
era_index: Some(0),
year,
extended_year: year,
ambiguity: types::YearAmbiguity::CenturyRequired,
}
}
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
date.0.year().rem_euclid(4) == 3
}
fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
types::MonthInfo::new(self, date.0)
}
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
types::DayOfMonth(date.0.day())
}
fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
types::DayOfYear(30 * (date.0.month() as u16 - 1) + date.0.day() as u16)
}
fn debug_name(&self) -> &'static str {
"Coptic"
}
fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
Some(crate::preferences::CalendarAlgorithm::Coptic)
}
}
impl Date<Coptic> {
pub fn try_new_coptic(year: i32, month: u8, day: u8) -> Result<Date<Coptic>, RangeError> {
ArithmeticDate::from_year_month_day(year, month, day, &Coptic)
.map(CopticDateInner)
.map(|inner| Date::from_raw(inner, Coptic))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::options::{DateFromFieldsOptions, MissingFieldsStrategy, Overflow};
use crate::types::{DateFields, Month};
#[test]
fn test_coptic_regression() {
let rd = Date::try_new_iso(-100, 3, 3).unwrap().to_rata_die();
assert_eq!(Date::from_rata_die(rd, Coptic).to_rata_die(), rd);
}
#[test]
fn test_from_fields_monthday_constrain() {
let fields = DateFields {
month: Some(Month::new(13)),
day: Some(7),
..Default::default()
};
let options = DateFromFieldsOptions {
overflow: Some(Overflow::Constrain),
missing_fields_strategy: Some(MissingFieldsStrategy::Ecma),
..Default::default()
};
let date = Date::try_from_fields(fields, options, Coptic).unwrap();
assert_eq!(date.day_of_month().0, 6, "Day was successfully constrained");
}
}