1use crate::{types, Calendar, CalendarError, DateDuration, DateDurationUnit};
6use core::cmp::Ordering;
7use core::convert::TryInto;
8use core::fmt::Debug;
9use core::hash::{Hash, Hasher};
10use core::marker::PhantomData;
11use tinystr::tinystr;
12
13#[derive(Debug)]
15#[allow(clippy::exhaustive_structs)] pub(crate) struct ArithmeticDate<C: CalendarArithmetic> {
17    pub year: i32,
18    pub month: u8,
20    pub day: u8,
22    pub year_info: C::YearInfo,
24    marker: PhantomData<C>,
25}
26
27impl<C: CalendarArithmetic> Copy for ArithmeticDate<C> {}
30impl<C: CalendarArithmetic> Clone for ArithmeticDate<C> {
31    fn clone(&self) -> Self {
32        *self
33    }
34}
35
36impl<C: CalendarArithmetic> PartialEq for ArithmeticDate<C> {
37    fn eq(&self, other: &Self) -> bool {
38        self.year == other.year && self.month == other.month && self.day == other.day
39    }
40}
41
42impl<C: CalendarArithmetic> Eq for ArithmeticDate<C> {}
43
44impl<C: CalendarArithmetic> Ord for ArithmeticDate<C> {
45    fn cmp(&self, other: &Self) -> Ordering {
46        self.year
47            .cmp(&other.year)
48            .then(self.month.cmp(&other.month))
49            .then(self.day.cmp(&other.day))
50    }
51}
52
53impl<C: CalendarArithmetic> PartialOrd for ArithmeticDate<C> {
54    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
55        Some(self.cmp(other))
56    }
57}
58
59impl<C: CalendarArithmetic> Hash for ArithmeticDate<C> {
60    fn hash<H>(&self, state: &mut H)
61    where
62        H: Hasher,
63    {
64        self.year.hash(state);
65        self.month.hash(state);
66        self.day.hash(state);
67    }
68}
69
70#[allow(dead_code)] pub(crate) const MAX_ITERS_FOR_DAYS_OF_MONTH: u8 = 33;
73
74pub(crate) trait CalendarArithmetic: Calendar {
75    type YearInfo: Copy + Debug;
79
80    fn month_days(year: i32, month: u8, year_info: Self::YearInfo) -> u8;
83    fn months_for_every_year(year: i32, year_info: Self::YearInfo) -> u8;
84    fn is_leap_year(year: i32, year_info: Self::YearInfo) -> bool;
85    fn last_month_day_in_year(year: i32, year_info: Self::YearInfo) -> (u8, u8);
86
87    fn days_in_provided_year(year: i32, year_info: Self::YearInfo) -> u16 {
94        let months_in_year = Self::months_for_every_year(year, year_info);
95        let mut days: u16 = 0;
96        for month in 1..=months_in_year {
97            days += Self::month_days(year, month, year_info) as u16;
98        }
99        days
100    }
101}
102
103pub(crate) trait PrecomputedDataSource<YearInfo> {
104    fn load_or_compute_info(&self, year: i32) -> YearInfo;
109}
110
111impl PrecomputedDataSource<()> for () {
112    fn load_or_compute_info(&self, _year: i32) {}
113}
114
115impl<C: CalendarArithmetic> ArithmeticDate<C> {
116    #[inline]
118    pub const fn new_unchecked(year: i32, month: u8, day: u8) -> Self
119    where
120        C: CalendarArithmetic<YearInfo = ()>,
121    {
122        Self::new_unchecked_with_info(year, month, day, ())
123    }
124    #[inline]
126    pub const fn new_unchecked_with_info(
127        year: i32,
128        month: u8,
129        day: u8,
130        year_info: C::YearInfo,
131    ) -> Self {
132        ArithmeticDate {
133            year,
134            month,
135            day,
136            year_info,
137            marker: PhantomData,
138        }
139    }
140
141    #[inline]
142    pub fn min_date() -> Self
143    where
144        C: CalendarArithmetic<YearInfo = ()>,
145    {
146        ArithmeticDate {
147            year: i32::MIN,
148            month: 1,
149            day: 1,
150            year_info: (),
151            marker: PhantomData,
152        }
153    }
154
155    #[inline]
156    pub fn max_date() -> Self
157    where
158        C: CalendarArithmetic<YearInfo = ()>,
159    {
160        let year = i32::MAX;
161        let (month, day) = C::last_month_day_in_year(year, ());
162        ArithmeticDate {
163            year: i32::MAX,
164            month,
165            day,
166            year_info: (),
167            marker: PhantomData,
168        }
169    }
170
171    #[inline]
172    fn offset_days(&mut self, mut day_offset: i32, data: &impl PrecomputedDataSource<C::YearInfo>) {
173        while day_offset != 0 {
174            let month_days = C::month_days(self.year, self.month, self.year_info);
175            if self.day as i32 + day_offset > month_days as i32 {
176                self.offset_months(1, data);
177                day_offset -= month_days as i32;
178            } else if self.day as i32 + day_offset < 1 {
179                self.offset_months(-1, data);
180                day_offset += C::month_days(self.year, self.month, self.year_info) as i32;
181            } else {
182                self.day = (self.day as i32 + day_offset) as u8;
183                day_offset = 0;
184            }
185        }
186    }
187
188    #[inline]
189    fn offset_months(
190        &mut self,
191        mut month_offset: i32,
192        data: &impl PrecomputedDataSource<C::YearInfo>,
193    ) {
194        while month_offset != 0 {
195            let year_months = C::months_for_every_year(self.year, self.year_info);
196            if self.month as i32 + month_offset > year_months as i32 {
197                self.year += 1;
198                self.year_info = data.load_or_compute_info(self.year);
199                month_offset -= year_months as i32;
200            } else if self.month as i32 + month_offset < 1 {
201                self.year -= 1;
202                self.year_info = data.load_or_compute_info(self.year);
203                month_offset += C::months_for_every_year(self.year, self.year_info) as i32;
204            } else {
205                self.month = (self.month as i32 + month_offset) as u8;
206                month_offset = 0
207            }
208        }
209    }
210
211    #[inline]
212    pub fn offset_date(
213        &mut self,
214        offset: DateDuration<C>,
215        data: &impl PrecomputedDataSource<C::YearInfo>,
216    ) {
217        if offset.years != 0 {
218            self.year += offset.years;
220            self.year_info = data.load_or_compute_info(self.year);
221        }
222
223        self.offset_months(offset.months, data);
224
225        let day_offset = offset.days + offset.weeks * 7 + self.day as i32 - 1;
226        self.day = 1;
227        self.offset_days(day_offset, data);
228    }
229
230    #[inline]
231    pub fn until(
232        &self,
233        date2: ArithmeticDate<C>,
234        _largest_unit: DateDurationUnit,
235        _smaller_unit: DateDurationUnit,
236    ) -> DateDuration<C> {
237        DateDuration::new(
240            self.year - date2.year,
241            self.month as i32 - date2.month as i32,
242            0,
243            self.day as i32 - date2.day as i32,
244        )
245    }
246
247    #[inline]
248    pub fn days_in_year(&self) -> u16 {
249        C::days_in_provided_year(self.year, self.year_info)
250    }
251
252    #[inline]
253    pub fn months_in_year(&self) -> u8 {
254        C::months_for_every_year(self.year, self.year_info)
255    }
256
257    #[inline]
258    pub fn days_in_month(&self) -> u8 {
259        C::month_days(self.year, self.month, self.year_info)
260    }
261
262    #[inline]
263    pub fn day_of_year(&self) -> u16 {
264        let mut day_of_year = 0;
265        for month in 1..self.month {
266            day_of_year += C::month_days(self.year, month, self.year_info) as u16;
267        }
268        day_of_year + (self.day as u16)
269    }
270
271    #[inline]
272    pub fn date_from_year_day(year: i32, year_day: u32) -> ArithmeticDate<C>
273    where
274        C: CalendarArithmetic<YearInfo = ()>,
275    {
276        let mut month = 1;
277        let mut day = year_day as i32;
278        while month <= C::months_for_every_year(year, ()) {
279            let month_days = C::month_days(year, month, ()) as i32;
280            if day <= month_days {
281                break;
282            } else {
283                day -= month_days;
284                month += 1;
285            }
286        }
287
288        debug_assert!(day <= C::month_days(year, month, ()) as i32);
289        #[allow(clippy::unwrap_used)]
290        ArithmeticDate {
292            year,
293            month,
294            day: day.try_into().unwrap_or(0),
295            year_info: (),
296            marker: PhantomData,
297        }
298    }
299
300    #[inline]
301    pub fn day_of_month(&self) -> types::DayOfMonth {
302        types::DayOfMonth(self.day.into())
303    }
304
305    #[inline]
313    pub fn month(&self) -> types::FormattableMonth {
314        let code = match self.month {
315            a if a > C::months_for_every_year(self.year, self.year_info) => tinystr!(4, "und"),
316            1 => tinystr!(4, "M01"),
317            2 => tinystr!(4, "M02"),
318            3 => tinystr!(4, "M03"),
319            4 => tinystr!(4, "M04"),
320            5 => tinystr!(4, "M05"),
321            6 => tinystr!(4, "M06"),
322            7 => tinystr!(4, "M07"),
323            8 => tinystr!(4, "M08"),
324            9 => tinystr!(4, "M09"),
325            10 => tinystr!(4, "M10"),
326            11 => tinystr!(4, "M11"),
327            12 => tinystr!(4, "M12"),
328            13 => tinystr!(4, "M13"),
329            _ => tinystr!(4, "und"),
330        };
331        types::FormattableMonth {
332            ordinal: self.month as u32,
333            code: types::MonthCode(code),
334        }
335    }
336
337    pub fn new_from_codes<C2: Calendar>(
341        cal: &C2,
344        year: i32,
345        month_code: types::MonthCode,
346        day: u8,
347    ) -> Result<Self, CalendarError>
348    where
349        C: CalendarArithmetic<YearInfo = ()>,
350    {
351        let month = if let Some((ordinal, false)) = month_code.parsed() {
352            ordinal
353        } else {
354            return Err(CalendarError::UnknownMonthCode(
355                month_code.0,
356                cal.debug_name(),
357            ));
358        };
359
360        if month > C::months_for_every_year(year, ()) {
361            return Err(CalendarError::UnknownMonthCode(
362                month_code.0,
363                cal.debug_name(),
364            ));
365        }
366
367        let max_day = C::month_days(year, month, ());
368        if day > max_day {
369            return Err(CalendarError::Overflow {
370                field: "day",
371                max: max_day as usize,
372            });
373        }
374
375        Ok(Self::new_unchecked(year, month, day))
376    }
377
378    pub fn new_from_ordinals(year: i32, month: u8, day: u8) -> Result<Self, CalendarError>
382    where
383        C: CalendarArithmetic<YearInfo = ()>,
384    {
385        Self::new_from_ordinals_with_info(year, month, day, ())
386    }
387
388    pub fn new_from_ordinals_with_info(
391        year: i32,
392        month: u8,
393        day: u8,
394        info: C::YearInfo,
395    ) -> Result<Self, CalendarError> {
396        let max_month = C::months_for_every_year(year, info);
397        if month > max_month {
398            return Err(CalendarError::Overflow {
399                field: "month",
400                max: max_month as usize,
401            });
402        }
403        let max_day = C::month_days(year, month, info);
404        if day > max_day {
405            return Err(CalendarError::Overflow {
406                field: "day",
407                max: max_day as usize,
408            });
409        }
410
411        Ok(Self::new_unchecked_with_info(year, month, day, info))
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418    use crate::Iso;
419
420    #[test]
421    fn test_ord() {
422        let dates_in_order = [
423            ArithmeticDate::<Iso>::new_unchecked(-10, 1, 1),
424            ArithmeticDate::<Iso>::new_unchecked(-10, 1, 2),
425            ArithmeticDate::<Iso>::new_unchecked(-10, 2, 1),
426            ArithmeticDate::<Iso>::new_unchecked(-1, 1, 1),
427            ArithmeticDate::<Iso>::new_unchecked(-1, 1, 2),
428            ArithmeticDate::<Iso>::new_unchecked(-1, 2, 1),
429            ArithmeticDate::<Iso>::new_unchecked(0, 1, 1),
430            ArithmeticDate::<Iso>::new_unchecked(0, 1, 2),
431            ArithmeticDate::<Iso>::new_unchecked(0, 2, 1),
432            ArithmeticDate::<Iso>::new_unchecked(1, 1, 1),
433            ArithmeticDate::<Iso>::new_unchecked(1, 1, 2),
434            ArithmeticDate::<Iso>::new_unchecked(1, 2, 1),
435            ArithmeticDate::<Iso>::new_unchecked(10, 1, 1),
436            ArithmeticDate::<Iso>::new_unchecked(10, 1, 2),
437            ArithmeticDate::<Iso>::new_unchecked(10, 2, 1),
438        ];
439        for (i, i_date) in dates_in_order.iter().enumerate() {
440            for (j, j_date) in dates_in_order.iter().enumerate() {
441                let result1 = i_date.cmp(j_date);
442                let result2 = j_date.cmp(i_date);
443                assert_eq!(result1.reverse(), result2);
444                assert_eq!(i.cmp(&j), i_date.cmp(j_date));
445            }
446        }
447    }
448}