/rust/registry/src/index.crates.io-1949cf8c6b5b557f/icu_calendar-1.5.2/src/coptic.rs
Line | Count | Source |
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::{coptic::Coptic, Date, DateTime}; |
9 | | //! |
10 | | //! // `Date` type |
11 | | //! let date_iso = Date::try_new_iso_date(1970, 1, 2) |
12 | | //! .expect("Failed to initialize ISO Date instance."); |
13 | | //! let date_coptic = Date::new_from_iso(date_iso, Coptic); |
14 | | //! |
15 | | //! // `DateTime` type |
16 | | //! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0) |
17 | | //! .expect("Failed to initialize ISO DateTime instance."); |
18 | | //! let datetime_coptic = DateTime::new_from_iso(datetime_iso, Coptic); |
19 | | //! |
20 | | //! // `Date` checks |
21 | | //! assert_eq!(date_coptic.year().number, 1686); |
22 | | //! assert_eq!(date_coptic.month().ordinal, 4); |
23 | | //! assert_eq!(date_coptic.day_of_month().0, 24); |
24 | | //! |
25 | | //! // `DateTime` type |
26 | | //! assert_eq!(datetime_coptic.date.year().number, 1686); |
27 | | //! assert_eq!(datetime_coptic.date.month().ordinal, 4); |
28 | | //! assert_eq!(datetime_coptic.date.day_of_month().0, 24); |
29 | | //! assert_eq!(datetime_coptic.time.hour.number(), 13); |
30 | | //! assert_eq!(datetime_coptic.time.minute.number(), 1); |
31 | | //! assert_eq!(datetime_coptic.time.second.number(), 0); |
32 | | //! ``` |
33 | | |
34 | | use crate::any_calendar::AnyCalendarKind; |
35 | | use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic}; |
36 | | use crate::iso::Iso; |
37 | | use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime, Time}; |
38 | | use calendrical_calculations::helpers::I32CastError; |
39 | | use calendrical_calculations::rata_die::RataDie; |
40 | | use tinystr::tinystr; |
41 | | |
42 | | /// The [Coptic Calendar] |
43 | | /// |
44 | | /// The [Coptic calendar] is a solar calendar used by the Coptic Orthodox Church, with twelve normal months |
45 | | /// and a thirteenth small epagomenal month. |
46 | | /// |
47 | | /// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar. |
48 | | /// |
49 | | /// [Coptic calendar]: https://en.wikipedia.org/wiki/Coptic_calendar |
50 | | /// |
51 | | /// # Era codes |
52 | | /// |
53 | | /// This calendar supports two era codes: `"bd"`, and `"ad"`, corresponding to the Before Diocletian and After Diocletian/Anno Martyrum |
54 | | /// eras. 1 A.M. is equivalent to 284 C.E. |
55 | | /// |
56 | | /// # Month codes |
57 | | /// |
58 | | /// This calendar supports 13 solar month codes (`"M01" - "M13"`), with `"M13"` being used for the short epagomenal month |
59 | | /// at the end of the year. |
60 | | #[derive(Copy, Clone, Debug, Hash, Default, Eq, PartialEq, PartialOrd, Ord)] |
61 | | #[allow(clippy::exhaustive_structs)] // this type is stable |
62 | | pub struct Coptic; |
63 | | |
64 | | /// The inner date type used for representing [`Date`]s of [`Coptic`]. See [`Date`] and [`Coptic`] for more details. |
65 | | #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] |
66 | | pub struct CopticDateInner(pub(crate) ArithmeticDate<Coptic>); |
67 | | |
68 | | impl CalendarArithmetic for Coptic { |
69 | | type YearInfo = (); |
70 | | |
71 | 0 | fn month_days(year: i32, month: u8, _data: ()) -> u8 { |
72 | 0 | if (1..=12).contains(&month) { |
73 | 0 | 30 |
74 | 0 | } else if month == 13 { |
75 | 0 | if Self::is_leap_year(year, ()) { |
76 | 0 | 6 |
77 | | } else { |
78 | 0 | 5 |
79 | | } |
80 | | } else { |
81 | 0 | 0 |
82 | | } |
83 | 0 | } |
84 | | |
85 | 0 | fn months_for_every_year(_: i32, _data: ()) -> u8 { |
86 | 0 | 13 |
87 | 0 | } |
88 | | |
89 | 0 | fn is_leap_year(year: i32, _data: ()) -> bool { |
90 | 0 | year.rem_euclid(4) == 3 |
91 | 0 | } |
92 | | |
93 | 0 | fn last_month_day_in_year(year: i32, _data: ()) -> (u8, u8) { |
94 | 0 | if Self::is_leap_year(year, ()) { |
95 | 0 | (13, 6) |
96 | | } else { |
97 | 0 | (13, 5) |
98 | | } |
99 | 0 | } |
100 | | |
101 | 0 | fn days_in_provided_year(year: i32, _data: ()) -> u16 { |
102 | 0 | if Self::is_leap_year(year, ()) { |
103 | 0 | 366 |
104 | | } else { |
105 | 0 | 365 |
106 | | } |
107 | 0 | } |
108 | | } |
109 | | |
110 | | impl Calendar for Coptic { |
111 | | type DateInner = CopticDateInner; |
112 | 0 | fn date_from_codes( |
113 | 0 | &self, |
114 | 0 | era: types::Era, |
115 | 0 | year: i32, |
116 | 0 | month_code: types::MonthCode, |
117 | 0 | day: u8, |
118 | 0 | ) -> Result<Self::DateInner, CalendarError> { |
119 | 0 | let year = if era.0 == tinystr!(16, "ad") { |
120 | 0 | if year <= 0 { |
121 | 0 | return Err(CalendarError::OutOfRange); |
122 | 0 | } |
123 | 0 | year |
124 | 0 | } else if era.0 == tinystr!(16, "bd") { |
125 | 0 | if year <= 0 { |
126 | 0 | return Err(CalendarError::OutOfRange); |
127 | 0 | } |
128 | 0 | 1 - year |
129 | | } else { |
130 | 0 | return Err(CalendarError::UnknownEra(era.0, self.debug_name())); |
131 | | }; |
132 | | |
133 | 0 | ArithmeticDate::new_from_codes(self, year, month_code, day).map(CopticDateInner) |
134 | 0 | } |
135 | 0 | fn date_from_iso(&self, iso: Date<Iso>) -> CopticDateInner { |
136 | 0 | let fixed_iso = Iso::fixed_from_iso(*iso.inner()); |
137 | 0 | Self::coptic_from_fixed(fixed_iso) |
138 | 0 | } |
139 | | |
140 | 0 | fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> { |
141 | 0 | let fixed_coptic = Coptic::fixed_from_coptic(date.0); |
142 | 0 | Iso::iso_from_fixed(fixed_coptic) |
143 | 0 | } |
144 | | |
145 | 0 | fn months_in_year(&self, date: &Self::DateInner) -> u8 { |
146 | 0 | date.0.months_in_year() |
147 | 0 | } |
148 | | |
149 | 0 | fn days_in_year(&self, date: &Self::DateInner) -> u16 { |
150 | 0 | date.0.days_in_year() |
151 | 0 | } |
152 | | |
153 | 0 | fn days_in_month(&self, date: &Self::DateInner) -> u8 { |
154 | 0 | date.0.days_in_month() |
155 | 0 | } |
156 | | |
157 | 0 | fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday { |
158 | 0 | Iso.day_of_week(Coptic.date_to_iso(date).inner()) |
159 | 0 | } |
160 | | |
161 | 0 | fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) { |
162 | 0 | date.0.offset_date(offset, &()); |
163 | 0 | } |
164 | | |
165 | | #[allow(clippy::field_reassign_with_default)] |
166 | 0 | fn until( |
167 | 0 | &self, |
168 | 0 | date1: &Self::DateInner, |
169 | 0 | date2: &Self::DateInner, |
170 | 0 | _calendar2: &Self, |
171 | 0 | _largest_unit: DateDurationUnit, |
172 | 0 | _smallest_unit: DateDurationUnit, |
173 | 0 | ) -> DateDuration<Self> { |
174 | 0 | date1.0.until(date2.0, _largest_unit, _smallest_unit) |
175 | 0 | } |
176 | | |
177 | 0 | fn year(&self, date: &Self::DateInner) -> types::FormattableYear { |
178 | 0 | year_as_coptic(date.0.year) |
179 | 0 | } |
180 | | |
181 | 0 | fn is_in_leap_year(&self, date: &Self::DateInner) -> bool { |
182 | 0 | Self::is_leap_year(date.0.year, ()) |
183 | 0 | } |
184 | | |
185 | 0 | fn month(&self, date: &Self::DateInner) -> types::FormattableMonth { |
186 | 0 | date.0.month() |
187 | 0 | } |
188 | | |
189 | 0 | fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { |
190 | 0 | date.0.day_of_month() |
191 | 0 | } |
192 | | |
193 | 0 | fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo { |
194 | 0 | let prev_year = date.0.year - 1; |
195 | 0 | let next_year = date.0.year + 1; |
196 | 0 | types::DayOfYearInfo { |
197 | 0 | day_of_year: date.0.day_of_year(), |
198 | 0 | days_in_year: date.0.days_in_year(), |
199 | 0 | prev_year: year_as_coptic(prev_year), |
200 | 0 | days_in_prev_year: Coptic::days_in_year_direct(prev_year), |
201 | 0 | next_year: year_as_coptic(next_year), |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | 0 | fn debug_name(&self) -> &'static str { |
206 | 0 | "Coptic" |
207 | 0 | } |
208 | | |
209 | 0 | fn any_calendar_kind(&self) -> Option<AnyCalendarKind> { |
210 | 0 | Some(AnyCalendarKind::Coptic) |
211 | 0 | } |
212 | | } |
213 | | |
214 | | impl Coptic { |
215 | 0 | fn fixed_from_coptic(date: ArithmeticDate<Coptic>) -> RataDie { |
216 | 0 | calendrical_calculations::coptic::fixed_from_coptic(date.year, date.month, date.day) |
217 | 0 | } |
218 | | |
219 | 0 | pub(crate) fn coptic_from_fixed(date: RataDie) -> CopticDateInner { |
220 | 0 | let (year, month, day) = match calendrical_calculations::coptic::coptic_from_fixed(date) { |
221 | 0 | Err(I32CastError::BelowMin) => return CopticDateInner(ArithmeticDate::min_date()), |
222 | 0 | Err(I32CastError::AboveMax) => return CopticDateInner(ArithmeticDate::max_date()), |
223 | 0 | Ok(ymd) => ymd, |
224 | | }; |
225 | | |
226 | 0 | CopticDateInner(ArithmeticDate::new_unchecked(year, month, day)) |
227 | 0 | } |
228 | | |
229 | 0 | fn days_in_year_direct(year: i32) -> u16 { |
230 | 0 | if Coptic::is_leap_year(year, ()) { |
231 | 0 | 366 |
232 | | } else { |
233 | 0 | 365 |
234 | | } |
235 | 0 | } |
236 | | } |
237 | | |
238 | | impl Date<Coptic> { |
239 | | /// Construct new Coptic Date. |
240 | | /// |
241 | | /// Negative years are in the B.D. era, starting with 0 = 1 B.D. |
242 | | /// |
243 | | /// ```rust |
244 | | /// use icu::calendar::Date; |
245 | | /// |
246 | | /// let date_coptic = Date::try_new_coptic_date(1686, 5, 6) |
247 | | /// .expect("Failed to initialize Coptic Date instance."); |
248 | | /// |
249 | | /// assert_eq!(date_coptic.year().number, 1686); |
250 | | /// assert_eq!(date_coptic.month().ordinal, 5); |
251 | | /// assert_eq!(date_coptic.day_of_month().0, 6); |
252 | | /// ``` |
253 | 0 | pub fn try_new_coptic_date( |
254 | 0 | year: i32, |
255 | 0 | month: u8, |
256 | 0 | day: u8, |
257 | 0 | ) -> Result<Date<Coptic>, CalendarError> { |
258 | 0 | ArithmeticDate::new_from_ordinals(year, month, day) |
259 | 0 | .map(CopticDateInner) |
260 | 0 | .map(|inner| Date::from_raw(inner, Coptic)) |
261 | 0 | } |
262 | | } |
263 | | |
264 | | impl DateTime<Coptic> { |
265 | | /// Construct a new Coptic datetime from integers. |
266 | | /// |
267 | | /// Negative years are in the B.D. era, starting with 0 = 1 B.D. |
268 | | /// |
269 | | /// ```rust |
270 | | /// use icu::calendar::DateTime; |
271 | | /// |
272 | | /// let datetime_coptic = |
273 | | /// DateTime::try_new_coptic_datetime(1686, 5, 6, 13, 1, 0) |
274 | | /// .expect("Failed to initialize Coptic DateTime instance."); |
275 | | /// |
276 | | /// assert_eq!(datetime_coptic.date.year().number, 1686); |
277 | | /// assert_eq!(datetime_coptic.date.month().ordinal, 5); |
278 | | /// assert_eq!(datetime_coptic.date.day_of_month().0, 6); |
279 | | /// assert_eq!(datetime_coptic.time.hour.number(), 13); |
280 | | /// assert_eq!(datetime_coptic.time.minute.number(), 1); |
281 | | /// assert_eq!(datetime_coptic.time.second.number(), 0); |
282 | | /// ``` |
283 | 0 | pub fn try_new_coptic_datetime( |
284 | 0 | year: i32, |
285 | 0 | month: u8, |
286 | 0 | day: u8, |
287 | 0 | hour: u8, |
288 | 0 | minute: u8, |
289 | 0 | second: u8, |
290 | 0 | ) -> Result<DateTime<Coptic>, CalendarError> { |
291 | | Ok(DateTime { |
292 | 0 | date: Date::try_new_coptic_date(year, month, day)?, |
293 | 0 | time: Time::try_new(hour, minute, second, 0)?, |
294 | | }) |
295 | 0 | } |
296 | | } |
297 | | |
298 | 0 | fn year_as_coptic(year: i32) -> types::FormattableYear { |
299 | 0 | if year > 0 { |
300 | 0 | types::FormattableYear { |
301 | 0 | era: types::Era(tinystr!(16, "ad")), |
302 | 0 | number: year, |
303 | 0 | cyclic: None, |
304 | 0 | related_iso: None, |
305 | 0 | } |
306 | | } else { |
307 | 0 | types::FormattableYear { |
308 | 0 | era: types::Era(tinystr!(16, "bd")), |
309 | 0 | number: 1 - year, |
310 | 0 | cyclic: None, |
311 | 0 | related_iso: None, |
312 | 0 | } |
313 | | } |
314 | 0 | } |
315 | | |
316 | | #[cfg(test)] |
317 | | mod tests { |
318 | | use super::*; |
319 | | #[test] |
320 | | fn test_coptic_regression() { |
321 | | // https://github.com/unicode-org/icu4x/issues/2254 |
322 | | 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 | | } |