1use crate::any_calendar::AnyCalendarKind;
35use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
36use crate::iso::Iso;
37use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime, Time};
38use calendrical_calculations::helpers::I32CastError;
39use calendrical_calculations::rata_die::RataDie;
40use tinystr::tinystr;
41
42#[derive(Copy, Clone, Debug, Hash, Default, Eq, PartialEq, PartialOrd, Ord)]
61#[allow(clippy::exhaustive_structs)] pub struct Coptic;
63
64#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
66pub struct CopticDateInner(pub(crate) ArithmeticDate<Coptic>);
67
68impl CalendarArithmetic for Coptic {
69 type YearInfo = ();
70
71 fn month_days(year: i32, month: u8, _data: ()) -> u8 {
72 if (1..=12).contains(&month) {
73 30
74 } else if month == 13 {
75 if Self::is_leap_year(year, ()) {
76 6
77 } else {
78 5
79 }
80 } else {
81 0
82 }
83 }
84
85 fn months_for_every_year(_: i32, _data: ()) -> u8 {
86 13
87 }
88
89 fn is_leap_year(year: i32, _data: ()) -> bool {
90 year.rem_euclid(4) == 3
91 }
92
93 fn last_month_day_in_year(year: i32, _data: ()) -> (u8, u8) {
94 if Self::is_leap_year(year, ()) {
95 (13, 6)
96 } else {
97 (13, 5)
98 }
99 }
100
101 fn days_in_provided_year(year: i32, _data: ()) -> u16 {
102 if Self::is_leap_year(year, ()) {
103 366
104 } else {
105 365
106 }
107 }
108}
109
110impl Calendar for Coptic {
111 type DateInner = CopticDateInner;
112 fn date_from_codes(
113 &self,
114 era: types::Era,
115 year: i32,
116 month_code: types::MonthCode,
117 day: u8,
118 ) -> Result<Self::DateInner, CalendarError> {
119 let year = if era.0 == tinystr!(16, "ad") {
120 if year <= 0 {
121 return Err(CalendarError::OutOfRange);
122 }
123 year
124 } else if era.0 == tinystr!(16, "bd") {
125 if year <= 0 {
126 return Err(CalendarError::OutOfRange);
127 }
128 1 - year
129 } else {
130 return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
131 };
132
133 ArithmeticDate::new_from_codes(self, year, month_code, day).map(CopticDateInner)
134 }
135 fn date_from_iso(&self, iso: Date<Iso>) -> CopticDateInner {
136 let fixed_iso = Iso::fixed_from_iso(*iso.inner());
137 Self::coptic_from_fixed(fixed_iso)
138 }
139
140 fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
141 let fixed_coptic = Coptic::fixed_from_coptic(date.0);
142 Iso::iso_from_fixed(fixed_coptic)
143 }
144
145 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
146 date.0.months_in_year()
147 }
148
149 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
150 date.0.days_in_year()
151 }
152
153 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
154 date.0.days_in_month()
155 }
156
157 fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
158 Iso.day_of_week(Coptic.date_to_iso(date).inner())
159 }
160
161 fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
162 date.0.offset_date(offset, &());
163 }
164
165 #[allow(clippy::field_reassign_with_default)]
166 fn until(
167 &self,
168 date1: &Self::DateInner,
169 date2: &Self::DateInner,
170 _calendar2: &Self,
171 _largest_unit: DateDurationUnit,
172 _smallest_unit: DateDurationUnit,
173 ) -> DateDuration<Self> {
174 date1.0.until(date2.0, _largest_unit, _smallest_unit)
175 }
176
177 fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
178 year_as_coptic(date.0.year)
179 }
180
181 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
182 Self::is_leap_year(date.0.year, ())
183 }
184
185 fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
186 date.0.month()
187 }
188
189 fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
190 date.0.day_of_month()
191 }
192
193 fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
194 let prev_year = date.0.year - 1;
195 let next_year = date.0.year + 1;
196 types::DayOfYearInfo {
197 day_of_year: date.0.day_of_year(),
198 days_in_year: date.0.days_in_year(),
199 prev_year: year_as_coptic(prev_year),
200 days_in_prev_year: Coptic::days_in_year_direct(prev_year),
201 next_year: year_as_coptic(next_year),
202 }
203 }
204
205 fn debug_name(&self) -> &'static str {
206 "Coptic"
207 }
208
209 fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
210 Some(AnyCalendarKind::Coptic)
211 }
212}
213
214impl Coptic {
215 fn fixed_from_coptic(date: ArithmeticDate<Coptic>) -> RataDie {
216 calendrical_calculations::coptic::fixed_from_coptic(date.year, date.month, date.day)
217 }
218
219 pub(crate) fn coptic_from_fixed(date: RataDie) -> CopticDateInner {
220 let (year, month, day) = match calendrical_calculations::coptic::coptic_from_fixed(date) {
221 Err(I32CastError::BelowMin) => return CopticDateInner(ArithmeticDate::min_date()),
222 Err(I32CastError::AboveMax) => return CopticDateInner(ArithmeticDate::max_date()),
223 Ok(ymd) => ymd,
224 };
225
226 CopticDateInner(ArithmeticDate::new_unchecked(year, month, day))
227 }
228
229 fn days_in_year_direct(year: i32) -> u16 {
230 if Coptic::is_leap_year(year, ()) {
231 366
232 } else {
233 365
234 }
235 }
236}
237
238impl Date<Coptic> {
239 pub fn try_new_coptic_date(
254 year: i32,
255 month: u8,
256 day: u8,
257 ) -> Result<Date<Coptic>, CalendarError> {
258 ArithmeticDate::new_from_ordinals(year, month, day)
259 .map(CopticDateInner)
260 .map(|inner| Date::from_raw(inner, Coptic))
261 }
262}
263
264impl DateTime<Coptic> {
265 pub fn try_new_coptic_datetime(
284 year: i32,
285 month: u8,
286 day: u8,
287 hour: u8,
288 minute: u8,
289 second: u8,
290 ) -> Result<DateTime<Coptic>, CalendarError> {
291 Ok(DateTime {
292 date: Date::try_new_coptic_date(year, month, day)?,
293 time: Time::try_new(hour, minute, second, 0)?,
294 })
295 }
296}
297
298fn year_as_coptic(year: i32) -> types::FormattableYear {
299 if year > 0 {
300 types::FormattableYear {
301 era: types::Era(tinystr!(16, "ad")),
302 number: year,
303 cyclic: None,
304 related_iso: None,
305 }
306 } else {
307 types::FormattableYear {
308 era: types::Era(tinystr!(16, "bd")),
309 number: 1 - year,
310 cyclic: None,
311 related_iso: None,
312 }
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 #[test]
320 fn test_coptic_regression() {
321 let iso_date = Date::try_new_iso_date(-100, 3, 3).unwrap();
323 let coptic = iso_date.to_calendar(Coptic);
324 let recovered_iso = coptic.to_iso();
325 assert_eq!(iso_date, recovered_iso);
326 }
327}