icu_calendar/cal/
coptic.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! This module contains types and implementations for the Coptic calendar.
6//!
7//! ```rust
8//! use icu::calendar::{cal::Coptic, Date};
9//!
10//! let date_iso = Date::try_new_iso(1970, 1, 2)
11//!     .expect("Failed to initialize ISO Date instance.");
12//! let date_coptic = Date::new_from_iso(date_iso, Coptic);
13//!
14//! assert_eq!(date_coptic.era_year().year, 1686);
15//! assert_eq!(date_coptic.month().ordinal, 4);
16//! assert_eq!(date_coptic.day_of_month().0, 24);
17//! ```
18
19use crate::cal::iso::{Iso, IsoDateInner};
20use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
21use crate::error::DateError;
22use crate::{types, Calendar, Date, DateDuration, DateDurationUnit, RangeError};
23use calendrical_calculations::helpers::I32CastError;
24use calendrical_calculations::rata_die::RataDie;
25use tinystr::tinystr;
26
27/// The [Coptic Calendar]
28///
29/// The [Coptic calendar] is a solar calendar used by the Coptic Orthodox Church, with twelve normal months
30/// and a thirteenth small epagomenal month.
31///
32/// This type can be used with [`Date`] to represent dates in this calendar.
33///
34/// [Coptic calendar]: https://en.wikipedia.org/wiki/Coptic_calendar
35///
36/// # Era codes
37///
38/// This calendar uses a single code: `am`, corresponding to the After Diocletian/Anno Martyrum
39/// era. 1 A.M. is equivalent to 284 C.E.
40///
41/// # Month codes
42///
43/// This calendar supports 13 solar month codes (`"M01" - "M13"`), with `"M13"` being used for the short epagomenal month
44/// at the end of the year.
45#[derive(Copy, Clone, Debug, Hash, Default, Eq, PartialEq, PartialOrd, Ord)]
46#[allow(clippy::exhaustive_structs)] // this type is stable
47pub struct Coptic;
48
49/// The inner date type used for representing [`Date`]s of [`Coptic`]. See [`Date`] and [`Coptic`] for more details.
50#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
51pub struct CopticDateInner(pub(crate) ArithmeticDate<Coptic>);
52
53impl CalendarArithmetic for Coptic {
54    type YearInfo = i32;
55
56    fn days_in_provided_month(year: i32, month: u8) -> u8 {
57        if (1..=12).contains(&month) {
58            30
59        } else if month == 13 {
60            if Self::provided_year_is_leap(year) {
61                6
62            } else {
63                5
64            }
65        } else {
66            0
67        }
68    }
69
70    fn months_in_provided_year(_: i32) -> u8 {
71        13
72    }
73
74    fn provided_year_is_leap(year: i32) -> bool {
75        year.rem_euclid(4) == 3
76    }
77
78    fn last_month_day_in_provided_year(year: i32) -> (u8, u8) {
79        if Self::provided_year_is_leap(year) {
80            (13, 6)
81        } else {
82            (13, 5)
83        }
84    }
85
86    fn days_in_provided_year(year: i32) -> u16 {
87        if Self::provided_year_is_leap(year) {
88            366
89        } else {
90            365
91        }
92    }
93}
94
95impl crate::cal::scaffold::UnstableSealed for Coptic {}
96impl Calendar for Coptic {
97    type DateInner = CopticDateInner;
98    type Year = types::EraYear;
99    fn from_codes(
100        &self,
101        era: Option<&str>,
102        year: i32,
103        month_code: types::MonthCode,
104        day: u8,
105    ) -> Result<Self::DateInner, DateError> {
106        let year = match era {
107            Some("am") | None => year,
108            Some(_) => return Err(DateError::UnknownEra),
109        };
110
111        ArithmeticDate::new_from_codes(self, year, month_code, day).map(CopticDateInner)
112    }
113
114    fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
115        CopticDateInner(
116            match calendrical_calculations::coptic::coptic_from_fixed(rd) {
117                Err(I32CastError::BelowMin) => ArithmeticDate::min_date(),
118                Err(I32CastError::AboveMax) => ArithmeticDate::max_date(),
119                Ok((year, month, day)) => ArithmeticDate::new_unchecked(year, month, day),
120            },
121        )
122    }
123
124    fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
125        calendrical_calculations::coptic::fixed_from_coptic(date.0.year, date.0.month, date.0.day)
126    }
127
128    fn from_iso(&self, iso: IsoDateInner) -> CopticDateInner {
129        self.from_rata_die(Iso.to_rata_die(&iso))
130    }
131
132    fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner {
133        Iso.from_rata_die(self.to_rata_die(date))
134    }
135
136    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
137        date.0.months_in_year()
138    }
139
140    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
141        date.0.days_in_year()
142    }
143
144    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
145        date.0.days_in_month()
146    }
147
148    fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
149        date.0.offset_date(offset, &());
150    }
151
152    #[allow(clippy::field_reassign_with_default)]
153    fn until(
154        &self,
155        date1: &Self::DateInner,
156        date2: &Self::DateInner,
157        _calendar2: &Self,
158        _largest_unit: DateDurationUnit,
159        _smallest_unit: DateDurationUnit,
160    ) -> DateDuration<Self> {
161        date1.0.until(date2.0, _largest_unit, _smallest_unit)
162    }
163
164    fn year_info(&self, date: &Self::DateInner) -> Self::Year {
165        let year = self.extended_year(date);
166        types::EraYear {
167            era: tinystr!(16, "am"),
168            era_index: Some(0),
169            year,
170            ambiguity: types::YearAmbiguity::CenturyRequired,
171        }
172    }
173
174    fn extended_year(&self, date: &Self::DateInner) -> i32 {
175        date.0.extended_year()
176    }
177
178    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
179        Self::provided_year_is_leap(date.0.year)
180    }
181
182    fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
183        date.0.month()
184    }
185
186    fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
187        date.0.day_of_month()
188    }
189
190    fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
191        date.0.day_of_year()
192    }
193
194    fn debug_name(&self) -> &'static str {
195        "Coptic"
196    }
197
198    fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
199        Some(crate::preferences::CalendarAlgorithm::Coptic)
200    }
201}
202
203impl Date<Coptic> {
204    /// Construct new Coptic Date.
205    ///
206    /// ```rust
207    /// use icu::calendar::Date;
208    ///
209    /// let date_coptic = Date::try_new_coptic(1686, 5, 6)
210    ///     .expect("Failed to initialize Coptic Date instance.");
211    ///
212    /// assert_eq!(date_coptic.era_year().year, 1686);
213    /// assert_eq!(date_coptic.month().ordinal, 5);
214    /// assert_eq!(date_coptic.day_of_month().0, 6);
215    /// ```
216    pub fn try_new_coptic(year: i32, month: u8, day: u8) -> Result<Date<Coptic>, RangeError> {
217        ArithmeticDate::new_from_ordinals(year, month, day)
218            .map(CopticDateInner)
219            .map(|inner| Date::from_raw(inner, Coptic))
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226    #[test]
227    fn test_coptic_regression() {
228        // https://github.com/unicode-org/icu4x/issues/2254
229        let iso_date = Date::try_new_iso(-100, 3, 3).unwrap();
230        let coptic = iso_date.to_calendar(Coptic);
231        let recovered_iso = coptic.to_iso();
232        assert_eq!(iso_date, recovered_iso);
233    }
234}