Skip to main content

icu_calendar/cal/
abstract_gregorian.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
5use crate::cal::iso::{IsoDateInner, IsoEra};
6use crate::calendar_arithmetic::{ArithmeticDate, DateFieldsResolver};
7use crate::error::{DateError, DateFromFieldsError, EcmaReferenceYearError, UnknownEraError};
8use crate::options::DateFromFieldsOptions;
9use crate::options::{DateAddOptions, DateDifferenceOptions};
10use crate::preferences::CalendarAlgorithm;
11use crate::types::EraYear;
12use crate::{types, Calendar, RangeError};
13use calendrical_calculations::helpers::I32CastError;
14use calendrical_calculations::rata_die::RataDie;
15
16#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub(crate) struct AbstractGregorian<Y: GregorianYears>(pub Y);
18
19pub(crate) trait GregorianYears: Clone + core::fmt::Debug {
20    // Positive if after 0 CE
21    const EXTENDED_YEAR_OFFSET: i32 = 0;
22
23    fn extended_from_era_year(&self, era: Option<&[u8]>, year: i32)
24        -> Result<i32, UnknownEraError>;
25
26    fn era_year_from_extended(&self, extended_year: i32, month: u8, day: u8) -> EraYear;
27
28    fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
29        None
30    }
31
32    fn debug_name(&self) -> &'static str;
33}
34
35impl ArithmeticDate<AbstractGregorian<IsoEra>> {
36    pub(crate) fn new_gregorian<Y: GregorianYears>(
37        year: i32,
38        month: u8,
39        day: u8,
40    ) -> Result<Self, RangeError> {
41        ArithmeticDate::try_from_ymd(year + Y::EXTENDED_YEAR_OFFSET, month, day)
42    }
43}
44
45pub(crate) const REFERENCE_YEAR: i32 = 1972;
46#[cfg(test)]
47pub(crate) const LAST_DAY_OF_REFERENCE_YEAR: RataDie =
48    calendrical_calculations::gregorian::day_before_year(REFERENCE_YEAR + 1);
49
50impl<Y: GregorianYears> DateFieldsResolver for AbstractGregorian<Y> {
51    // Gregorian year
52    type YearInfo = i32;
53
54    fn days_in_provided_month(year: i32, month: u8) -> u8 {
55        // https://www.youtube.com/watch?v=J9KijLyP-yg&t=1394s
56        if month == 2 {
57            28 + calendrical_calculations::gregorian::is_leap_year(year) as u8
58        } else {
59            30 | month ^ (month >> 3)
60        }
61    }
62
63    fn months_in_provided_year(_: i32) -> u8 {
64        12
65    }
66
67    #[inline]
68    fn year_info_from_era(
69        &self,
70        era: &[u8],
71        era_year: i32,
72    ) -> Result<Self::YearInfo, UnknownEraError> {
73        Ok(self.0.extended_from_era_year(Some(era), era_year)? + Y::EXTENDED_YEAR_OFFSET)
74    }
75
76    #[inline]
77    fn year_info_from_extended(&self, extended_year: i32) -> Self::YearInfo {
78        extended_year + Y::EXTENDED_YEAR_OFFSET
79    }
80
81    #[inline]
82    fn reference_year_from_month_day(
83        &self,
84        _month_code: types::ValidMonthCode,
85        _day: u8,
86    ) -> Result<Self::YearInfo, EcmaReferenceYearError> {
87        Ok(REFERENCE_YEAR)
88    }
89}
90
91impl<Y: GregorianYears> crate::cal::scaffold::UnstableSealed for AbstractGregorian<Y> {}
92
93impl<Y: GregorianYears> Calendar for AbstractGregorian<Y> {
94    type DateInner = ArithmeticDate<AbstractGregorian<IsoEra>>;
95    type Year = types::EraYear;
96    type DifferenceError = core::convert::Infallible;
97
98    fn from_codes(
99        &self,
100        era: Option<&str>,
101        year: i32,
102        month_code: types::MonthCode,
103        day: u8,
104    ) -> Result<Self::DateInner, DateError> {
105        ArithmeticDate::from_codes(era, year, month_code, day, self).map(ArithmeticDate::cast)
106    }
107
108    #[cfg(feature = "unstable")]
109    fn from_fields(
110        &self,
111        fields: types::DateFields,
112        options: DateFromFieldsOptions,
113    ) -> Result<Self::DateInner, DateFromFieldsError> {
114        ArithmeticDate::from_fields(fields, options, self).map(ArithmeticDate::cast)
115    }
116
117    fn from_rata_die(&self, date: RataDie) -> Self::DateInner {
118        let iso = match calendrical_calculations::gregorian::gregorian_from_fixed(date) {
119            Err(I32CastError::BelowMin) => {
120                ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(i32::MIN, 1, 1)
121            }
122            Err(I32CastError::AboveMax) => ArithmeticDate::new_unchecked(i32::MAX, 12, 31),
123            Ok((year, month, day)) => ArithmeticDate::new_unchecked(year, month, day),
124        };
125
126        if iso.year.checked_sub(Y::EXTENDED_YEAR_OFFSET).is_none() {
127            if Y::EXTENDED_YEAR_OFFSET < 0 {
128                ArithmeticDate::new_unchecked(i32::MIN - Y::EXTENDED_YEAR_OFFSET, 1, 1)
129            } else {
130                ArithmeticDate::new_unchecked(i32::MAX - Y::EXTENDED_YEAR_OFFSET, 12, 31)
131            }
132        } else {
133            iso
134        }
135    }
136
137    fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
138        calendrical_calculations::gregorian::fixed_from_gregorian(date.year, date.month, date.day)
139    }
140
141    fn has_cheap_iso_conversion(&self) -> bool {
142        true
143    }
144
145    fn from_iso(&self, iso: IsoDateInner) -> Self::DateInner {
146        iso.0
147    }
148
149    fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner {
150        IsoDateInner(*date)
151    }
152
153    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
154        AbstractGregorian::<IsoEra>::months_in_provided_year(date.year)
155    }
156
157    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
158        365 + calendrical_calculations::gregorian::is_leap_year(date.year) as u16
159    }
160
161    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
162        AbstractGregorian::<IsoEra>::days_in_provided_month(date.year, date.month)
163    }
164
165    #[cfg(feature = "unstable")]
166    fn add(
167        &self,
168        date: &Self::DateInner,
169        duration: types::DateDuration,
170        options: DateAddOptions,
171    ) -> Result<Self::DateInner, DateError> {
172        date.added(duration, &AbstractGregorian(IsoEra), options)
173    }
174
175    #[cfg(feature = "unstable")]
176    fn until(
177        &self,
178        date1: &Self::DateInner,
179        date2: &Self::DateInner,
180        options: DateDifferenceOptions,
181    ) -> Result<types::DateDuration, Self::DifferenceError> {
182        Ok(date1.until(date2, &AbstractGregorian(IsoEra), options))
183    }
184
185    fn year_info(&self, date: &Self::DateInner) -> Self::Year {
186        self.0
187            .era_year_from_extended(date.year - Y::EXTENDED_YEAR_OFFSET, date.month, date.day)
188    }
189
190    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
191        calendrical_calculations::gregorian::is_leap_year(date.year)
192    }
193
194    fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
195        types::MonthInfo::non_lunisolar(date.month)
196    }
197
198    fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
199        types::DayOfMonth(date.day)
200    }
201
202    fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
203        types::DayOfYear(
204            calendrical_calculations::gregorian::days_before_month(date.year, date.month)
205                + date.day as u16,
206        )
207    }
208
209    fn debug_name(&self) -> &'static str {
210        self.0.debug_name()
211    }
212
213    fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
214        self.0.calendar_algorithm()
215    }
216}
217
218macro_rules! impl_with_abstract_gregorian {
219    ($cal_ty:ty, $inner_date_ty:ident, $eras_ty:ty, $self_ident:ident, $eras_expr:expr) => {
220        #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
221        pub struct $inner_date_ty(
222            pub(crate)  ArithmeticDate<
223                crate::cal::abstract_gregorian::AbstractGregorian<crate::cal::iso::IsoEra>,
224            >,
225        );
226
227        impl crate::cal::scaffold::UnstableSealed for $cal_ty {}
228        impl crate::Calendar for $cal_ty {
229            type DateInner = $inner_date_ty;
230            type Year = types::EraYear;
231            type DifferenceError = core::convert::Infallible;
232
233            fn from_codes(
234                &self,
235                era: Option<&str>,
236                year: i32,
237                month_code: types::MonthCode,
238                day: u8,
239            ) -> Result<Self::DateInner, crate::error::DateError> {
240                let $self_ident = self;
241                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr)
242                    .from_codes(era, year, month_code, day)
243                    .map($inner_date_ty)
244            }
245
246            #[cfg(feature = "unstable")]
247            fn from_fields(
248                &self,
249                fields: crate::types::DateFields,
250                options: crate::options::DateFromFieldsOptions,
251            ) -> Result<Self::DateInner, crate::error::DateFromFieldsError> {
252                let $self_ident = self;
253                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr)
254                    .from_fields(fields, options)
255                    .map($inner_date_ty)
256            }
257
258            fn from_rata_die(&self, rd: crate::types::RataDie) -> Self::DateInner {
259                let $self_ident = self;
260                $inner_date_ty(
261                    crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).from_rata_die(rd),
262                )
263            }
264
265            fn to_rata_die(&self, date: &Self::DateInner) -> crate::types::RataDie {
266                let $self_ident = self;
267                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).to_rata_die(&date.0)
268            }
269
270            fn has_cheap_iso_conversion(&self) -> bool {
271                let $self_ident = self;
272                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr)
273                    .has_cheap_iso_conversion()
274            }
275
276            fn from_iso(&self, iso: crate::cal::iso::IsoDateInner) -> Self::DateInner {
277                let $self_ident = self;
278                $inner_date_ty(
279                    crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).from_iso(iso),
280                )
281            }
282
283            fn to_iso(&self, date: &Self::DateInner) -> crate::cal::iso::IsoDateInner {
284                let $self_ident = self;
285                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).to_iso(&date.0)
286            }
287
288            fn months_in_year(&self, date: &Self::DateInner) -> u8 {
289                let $self_ident = self;
290                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr)
291                    .months_in_year(&date.0)
292            }
293
294            fn days_in_year(&self, date: &Self::DateInner) -> u16 {
295                let $self_ident = self;
296                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).days_in_year(&date.0)
297            }
298
299            fn days_in_month(&self, date: &Self::DateInner) -> u8 {
300                let $self_ident = self;
301                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).days_in_month(&date.0)
302            }
303
304            #[cfg(feature = "unstable")]
305            fn add(
306                &self,
307                date: &Self::DateInner,
308                duration: crate::types::DateDuration,
309                options: crate::options::DateAddOptions,
310            ) -> Result<Self::DateInner, DateError> {
311                let $self_ident = self;
312                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr)
313                    .add(&date.0, duration, options)
314                    .map($inner_date_ty)
315            }
316
317            #[cfg(feature = "unstable")]
318            fn until(
319                &self,
320                date1: &Self::DateInner,
321                date2: &Self::DateInner,
322                options: crate::options::DateDifferenceOptions,
323            ) -> Result<crate::types::DateDuration, Self::DifferenceError> {
324                let $self_ident = self;
325                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr)
326                    .until(&date1.0, &date2.0, options)
327            }
328
329            fn year_info(&self, date: &Self::DateInner) -> Self::Year {
330                let $self_ident = self;
331                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).year_info(&date.0)
332            }
333
334            fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
335                let $self_ident = self;
336                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr)
337                    .is_in_leap_year(&date.0)
338            }
339
340            fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
341                let $self_ident = self;
342                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).month(&date.0)
343            }
344
345            fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
346                let $self_ident = self;
347                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).day_of_month(&date.0)
348            }
349
350            fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
351                let $self_ident = self;
352                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).day_of_year(&date.0)
353            }
354
355            fn debug_name(&self) -> &'static str {
356                let $self_ident = self;
357                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).debug_name()
358            }
359
360            fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
361                let $self_ident = self;
362                crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).calendar_algorithm()
363            }
364        }
365    };
366}
367pub(crate) use impl_with_abstract_gregorian;