use crate::any_calendar::AnyCalendarKind;
use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
use crate::helpers::quotient;
use crate::iso::Iso;
use crate::julian::Julian;
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime};
use core::marker::PhantomData;
use tinystr::tinystr;
#[derive(Copy, Clone, Debug, Hash, Default, Eq, PartialEq)]
#[allow(clippy::exhaustive_structs)] pub struct Coptic;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct CopticDateInner(pub(crate) ArithmeticDate<Coptic>);
impl CalendarArithmetic for Coptic {
fn month_days(year: i32, month: u8) -> u8 {
if (1..=12).contains(&month) {
30
} else if month == 13 {
if Self::is_leap_year(year) {
6
} else {
5
}
} else {
0
}
}
fn months_for_every_year(_: i32) -> u8 {
13
}
fn is_leap_year(year: i32) -> bool {
year % 4 == 3
}
fn days_in_provided_year(year: i32) -> u32 {
if Self::is_leap_year(year) {
366
} else {
365
}
}
}
impl Calendar for Coptic {
type DateInner = CopticDateInner;
fn date_from_codes(
&self,
era: types::Era,
year: i32,
month_code: types::MonthCode,
day: u8,
) -> Result<Self::DateInner, CalendarError> {
let year = if era.0 == tinystr!(16, "ad") {
if year <= 0 {
return Err(CalendarError::OutOfRange);
}
year
} else if era.0 == tinystr!(16, "bd") {
if year <= 0 {
return Err(CalendarError::OutOfRange);
}
1 - year
} else {
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
};
ArithmeticDate::new_from_solar(self, year, month_code, day).map(CopticDateInner)
}
fn date_from_iso(&self, iso: Date<Iso>) -> CopticDateInner {
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
Self::coptic_from_fixed(fixed_iso)
}
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
let fixed_coptic = Coptic::fixed_from_coptic(date.0);
Iso::iso_from_fixed(fixed_coptic)
}
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
date.0.months_in_year()
}
fn days_in_year(&self, date: &Self::DateInner) -> u32 {
date.0.days_in_year()
}
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
date.0.days_in_month()
}
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
Iso.day_of_week(Coptic.date_to_iso(date).inner())
}
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
date.0.offset_date(offset);
}
#[allow(clippy::field_reassign_with_default)]
fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
_largest_unit: DateDurationUnit,
_smallest_unit: DateDurationUnit,
) -> DateDuration<Self> {
date1.0.until(date2.0, _largest_unit, _smallest_unit)
}
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
year_as_coptic(date.0.year)
}
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
date.0.solar_month()
}
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
date.0.day_of_month()
}
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
let prev_year = date.0.year - 1;
let next_year = date.0.year + 1;
types::DayOfYearInfo {
day_of_year: date.0.day_of_year(),
days_in_year: date.0.days_in_year(),
prev_year: year_as_coptic(prev_year),
days_in_prev_year: Coptic::days_in_year_direct(prev_year),
next_year: year_as_coptic(next_year),
}
}
fn debug_name(&self) -> &'static str {
"Coptic"
}
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
Some(AnyCalendarKind::Coptic)
}
}
pub(crate) const COPTIC_EPOCH: i32 = Julian::fixed_from_julian_integers(284, 8, 29);
impl Coptic {
fn fixed_from_coptic(date: ArithmeticDate<Coptic>) -> i32 {
COPTIC_EPOCH - 1
+ 365 * (date.year - 1)
+ quotient(date.year, 4)
+ 30 * (date.month as i32 - 1)
+ date.day as i32
}
pub(crate) fn fixed_from_coptic_integers(year: i32, month: u8, day: u8) -> i32 {
Self::fixed_from_coptic(ArithmeticDate {
year,
month,
day,
marker: PhantomData,
})
}
pub(crate) fn coptic_from_fixed(date: i32) -> CopticDateInner {
let year = quotient(4 * (date - COPTIC_EPOCH) + 1463, 1461);
let month = (quotient(date - Self::fixed_from_coptic_integers(year, 1, 1), 30) + 1) as u8; let day = (date + 1 - Self::fixed_from_coptic_integers(year, month, 1)) as u8;
#[allow(clippy::unwrap_used)] *Date::try_new_coptic_date(year, month, day).unwrap().inner()
}
fn days_in_year_direct(year: i32) -> u32 {
if Coptic::is_leap_year(year) {
366
} else {
365
}
}
}
impl Date<Coptic> {
pub fn try_new_coptic_date(
year: i32,
month: u8,
day: u8,
) -> Result<Date<Coptic>, CalendarError> {
let inner = ArithmeticDate {
year,
month,
day,
marker: PhantomData,
};
let bound = inner.days_in_month();
if day > bound {
return Err(CalendarError::OutOfRange);
}
Ok(Date::from_raw(CopticDateInner(inner), Coptic))
}
}
impl DateTime<Coptic> {
pub fn try_new_coptic_datetime(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
) -> Result<DateTime<Coptic>, CalendarError> {
Ok(DateTime {
date: Date::try_new_coptic_date(year, month, day)?,
time: types::Time::try_new(hour, minute, second, 0)?,
})
}
}
fn year_as_coptic(year: i32) -> types::FormattableYear {
if year > 0 {
types::FormattableYear {
era: types::Era(tinystr!(16, "ad")),
number: year,
related_iso: None,
}
} else {
types::FormattableYear {
era: types::Era(tinystr!(16, "bd")),
number: 1 - year,
related_iso: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coptic_regression() {
let iso_date = Date::try_new_iso_date(-100, 3, 3).unwrap();
let coptic = iso_date.to_calendar(Coptic);
let recovered_iso = coptic.to_iso();
assert_eq!(iso_date, recovered_iso);
}
}