icu_calendar/
date.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::any_calendar::{AnyCalendar, IntoAnyCalendar};
6use crate::calendar_arithmetic::CalendarArithmetic;
7use crate::error::DateError;
8use crate::types::{CyclicYear, EraYear, IsoWeekOfYear};
9use crate::week::{RelativeUnit, WeekCalculator, WeekOf};
10use crate::{types, Calendar, DateDuration, DateDurationUnit, Iso};
11#[cfg(feature = "alloc")]
12use alloc::rc::Rc;
13#[cfg(feature = "alloc")]
14use alloc::sync::Arc;
15use calendrical_calculations::rata_die::RataDie;
16use core::fmt;
17use core::ops::Deref;
18
19/// Types that contain a calendar
20///
21/// This allows one to use [`Date`] with wrappers around calendars,
22/// e.g. reference counted calendars.
23pub trait AsCalendar {
24    /// The calendar being wrapped
25    type Calendar: Calendar;
26    /// Obtain the inner calendar
27    fn as_calendar(&self) -> &Self::Calendar;
28}
29
30impl<C: Calendar> AsCalendar for C {
31    type Calendar = C;
32    #[inline]
33    fn as_calendar(&self) -> &Self {
34        self
35    }
36}
37
38#[cfg(feature = "alloc")]
39impl<C: AsCalendar> AsCalendar for Rc<C> {
40    type Calendar = C::Calendar;
41    #[inline]
42    fn as_calendar(&self) -> &Self::Calendar {
43        self.as_ref().as_calendar()
44    }
45}
46
47#[cfg(feature = "alloc")]
48impl<C: AsCalendar> AsCalendar for Arc<C> {
49    type Calendar = C::Calendar;
50    #[inline]
51    fn as_calendar(&self) -> &Self::Calendar {
52        self.as_ref().as_calendar()
53    }
54}
55
56/// This exists as a wrapper around `&'a T` so that
57/// `Date<&'a C>` is possible for calendar `C`.
58///
59/// Unfortunately,
60/// [`AsCalendar`] cannot be implemented on `&'a T` directly because
61/// `&'a T` is `#[fundamental]` and the impl would clash with the one above with
62/// `AsCalendar` for `C: Calendar`.
63///
64/// Use `Date<Ref<'a, C>>` where you would use `Date<&'a C>`
65#[allow(clippy::exhaustive_structs)] // newtype
66#[derive(PartialEq, Eq, Debug)]
67pub struct Ref<'a, C>(pub &'a C);
68
69impl<C> Copy for Ref<'_, C> {}
70
71impl<C> Clone for Ref<'_, C> {
72    fn clone(&self) -> Self {
73        *self
74    }
75}
76
77impl<C: AsCalendar> AsCalendar for Ref<'_, C> {
78    type Calendar = C::Calendar;
79    #[inline]
80    fn as_calendar(&self) -> &Self::Calendar {
81        self.0.as_calendar()
82    }
83}
84
85impl<C> Deref for Ref<'_, C> {
86    type Target = C;
87    fn deref(&self) -> &C {
88        self.0
89    }
90}
91
92/// A date for a given calendar.
93///
94/// **The primary definition of this type is in the [`icu_calendar`](https://docs.rs/icu_calendar) crate. Other ICU4X crates re-export it for convenience.**
95///
96/// This can work with wrappers around [`Calendar`] types,
97/// e.g. `Rc<C>`, via the [`AsCalendar`] trait.
98///
99/// This can be constructed  constructed
100/// from its fields via [`Self::try_new_from_codes()`], or can be constructed with one of the
101/// `new_<calendar>_date()` per-calendar methods (and then freely converted between calendars).
102///
103/// ```rust
104/// use icu::calendar::Date;
105///
106/// // Example: creation of ISO date from integers.
107/// let date_iso = Date::try_new_iso(1970, 1, 2)
108///     .expect("Failed to initialize ISO Date instance.");
109///
110/// assert_eq!(date_iso.era_year().year, 1970);
111/// assert_eq!(date_iso.month().ordinal, 1);
112/// assert_eq!(date_iso.day_of_month().0, 2);
113/// ```
114pub struct Date<A: AsCalendar> {
115    pub(crate) inner: <A::Calendar as Calendar>::DateInner,
116    pub(crate) calendar: A,
117}
118
119impl<A: AsCalendar> Date<A> {
120    /// Construct a date from from era/month codes and fields, and some calendar representation
121    ///
122    /// The year is `extended_year` if no era is provided
123    #[inline]
124    pub fn try_new_from_codes(
125        era: Option<&str>,
126        year: i32,
127        month_code: types::MonthCode,
128        day: u8,
129        calendar: A,
130    ) -> Result<Self, DateError> {
131        let inner = calendar
132            .as_calendar()
133            .from_codes(era, year, month_code, day)?;
134        Ok(Date { inner, calendar })
135    }
136
137    /// Construct a date from a [`RataDie`] and some calendar representation
138    #[inline]
139    pub fn from_rata_die(rd: RataDie, calendar: A) -> Self {
140        Date {
141            inner: calendar.as_calendar().from_rata_die(rd),
142            calendar,
143        }
144    }
145
146    /// Convert the date to a [`RataDie`]
147    #[inline]
148    pub fn to_rata_die(&self) -> RataDie {
149        self.calendar.as_calendar().to_rata_die(self.inner())
150    }
151
152    /// Construct a date from an ISO date and some calendar representation
153    #[inline]
154    pub fn new_from_iso(iso: Date<Iso>, calendar: A) -> Self {
155        let inner = calendar.as_calendar().from_iso(iso.inner);
156        Date { inner, calendar }
157    }
158
159    /// Convert the Date to an ISO Date
160    #[inline]
161    pub fn to_iso(&self) -> Date<Iso> {
162        Date::from_raw(self.calendar.as_calendar().to_iso(self.inner()), Iso)
163    }
164
165    /// Convert the Date to a date in a different calendar
166    #[inline]
167    pub fn to_calendar<A2: AsCalendar>(&self, calendar: A2) -> Date<A2> {
168        Date::new_from_iso(self.to_iso(), calendar)
169    }
170
171    /// The number of months in the year of this date
172    #[inline]
173    pub fn months_in_year(&self) -> u8 {
174        self.calendar.as_calendar().months_in_year(self.inner())
175    }
176
177    /// The number of days in the year of this date
178    #[inline]
179    pub fn days_in_year(&self) -> u16 {
180        self.calendar.as_calendar().days_in_year(self.inner())
181    }
182
183    /// The number of days in the month of this date
184    #[inline]
185    pub fn days_in_month(&self) -> u8 {
186        self.calendar.as_calendar().days_in_month(self.inner())
187    }
188
189    /// The day of the week for this date
190    #[inline]
191    pub fn day_of_week(&self) -> types::Weekday {
192        self.to_rata_die().into()
193    }
194
195    /// Add a `duration` to this date, mutating it
196    #[doc(hidden)] // unstable
197    #[inline]
198    pub fn add(&mut self, duration: DateDuration<A::Calendar>) {
199        self.calendar
200            .as_calendar()
201            .offset_date(&mut self.inner, duration)
202    }
203
204    /// Add a `duration` to this date, returning the new one
205    #[doc(hidden)] // unstable
206    #[inline]
207    pub fn added(mut self, duration: DateDuration<A::Calendar>) -> Self {
208        self.add(duration);
209        self
210    }
211
212    /// Calculating the duration between `other - self`
213    #[doc(hidden)] // unstable
214    #[inline]
215    pub fn until<B: AsCalendar<Calendar = A::Calendar>>(
216        &self,
217        other: &Date<B>,
218        largest_unit: DateDurationUnit,
219        smallest_unit: DateDurationUnit,
220    ) -> DateDuration<A::Calendar> {
221        self.calendar.as_calendar().until(
222            self.inner(),
223            other.inner(),
224            other.calendar.as_calendar(),
225            largest_unit,
226            smallest_unit,
227        )
228    }
229
230    /// The calendar-specific year-info.
231    ///
232    /// This returns an enum, see [`Date::era_year()`] and [`Date::cyclic_year()`] which are available
233    /// for concrete calendar types and return concrete types.
234    #[inline]
235    pub fn year(&self) -> types::YearInfo {
236        self.calendar.as_calendar().year_info(&self.inner).into()
237    }
238
239    /// The "extended year", typically anchored with year 1 as the year 1 of either the most modern or
240    /// otherwise some "major" era for the calendar
241    ///
242    /// See [`Self::year()`] for more information about the year.
243    #[inline]
244    pub fn extended_year(&self) -> i32 {
245        self.calendar.as_calendar().extended_year(&self.inner)
246    }
247
248    /// Returns whether `self` is in a calendar-specific leap year
249    #[inline]
250    pub fn is_in_leap_year(&self) -> bool {
251        self.calendar.as_calendar().is_in_leap_year(&self.inner)
252    }
253
254    /// The calendar-specific month represented by `self`
255    #[inline]
256    pub fn month(&self) -> types::MonthInfo {
257        self.calendar.as_calendar().month(&self.inner)
258    }
259
260    /// The calendar-specific day-of-month represented by `self`
261    #[inline]
262    pub fn day_of_month(&self) -> types::DayOfMonth {
263        self.calendar.as_calendar().day_of_month(&self.inner)
264    }
265
266    /// The calendar-specific day-of-month represented by `self`
267    #[inline]
268    pub fn day_of_year(&self) -> types::DayOfYear {
269        self.calendar.as_calendar().day_of_year(&self.inner)
270    }
271
272    /// Construct a date from raw values for a given calendar. This does not check any
273    /// invariants for the date and calendar, and should only be called by calendar implementations.
274    ///
275    /// Calling this outside of calendar implementations is sound, but calendar implementations are not
276    /// expected to do anything sensible with such invalid dates.
277    ///
278    /// AnyCalendar *will* panic if AnyCalendar [`Date`] objects with mismatching
279    /// date and calendar types are constructed
280    #[inline]
281    pub fn from_raw(inner: <A::Calendar as Calendar>::DateInner, calendar: A) -> Self {
282        Self { inner, calendar }
283    }
284
285    /// Get the inner date implementation. Should not be called outside of calendar implementations
286    #[inline]
287    pub fn inner(&self) -> &<A::Calendar as Calendar>::DateInner {
288        &self.inner
289    }
290
291    /// Get a reference to the contained calendar
292    #[inline]
293    pub fn calendar(&self) -> &A::Calendar {
294        self.calendar.as_calendar()
295    }
296
297    /// Get a reference to the contained calendar wrapper
298    ///
299    /// (Useful in case the user wishes to e.g. clone an Rc)
300    #[inline]
301    pub fn calendar_wrapper(&self) -> &A {
302        &self.calendar
303    }
304}
305
306impl<A: AsCalendar<Calendar = C>, C: Calendar<Year = EraYear>> Date<A> {
307    /// Returns information about the era for calendars using eras.
308    pub fn era_year(&self) -> EraYear {
309        self.calendar.as_calendar().year_info(self.inner())
310    }
311}
312
313impl<A: AsCalendar<Calendar = C>, C: Calendar<Year = CyclicYear>> Date<A> {
314    /// Returns information about the year cycle, for cyclic calendars.
315    pub fn cyclic_year(&self) -> CyclicYear {
316        self.calendar.as_calendar().year_info(self.inner())
317    }
318}
319
320impl Date<Iso> {
321    /// The ISO week of the year containing this date.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// use icu::calendar::types::IsoWeekOfYear;
327    /// use icu::calendar::Date;
328    ///
329    /// let date = Date::try_new_iso(2022, 8, 26).unwrap();
330    ///
331    /// assert_eq!(
332    ///     date.week_of_year(),
333    ///     IsoWeekOfYear {
334    ///         week_number: 34,
335    ///         iso_year: 2022,
336    ///     }
337    /// );
338    /// ```
339    pub fn week_of_year(&self) -> IsoWeekOfYear {
340        let week_of = WeekCalculator::ISO
341            .week_of(
342                Iso::days_in_provided_year(self.inner.0.year.saturating_sub(1)),
343                self.days_in_year(),
344                self.day_of_year().0,
345                self.day_of_week(),
346            )
347            .unwrap_or_else(|_| {
348                // ISO calendar has more than 14 days per year
349                debug_assert!(false);
350                WeekOf {
351                    week: 1,
352                    unit: crate::week::RelativeUnit::Current,
353                }
354            });
355
356        IsoWeekOfYear {
357            week_number: week_of.week,
358            iso_year: match week_of.unit {
359                RelativeUnit::Current => self.inner.0.year,
360                RelativeUnit::Next => self.inner.0.year.saturating_add(1),
361                RelativeUnit::Previous => self.inner.0.year.saturating_sub(1),
362            },
363        }
364    }
365}
366
367impl<C: IntoAnyCalendar> Date<C> {
368    /// Type-erase the date, converting it to a date for [`AnyCalendar`]
369    pub fn to_any(self) -> Date<AnyCalendar> {
370        Date::from_raw(
371            self.calendar.date_to_any(&self.inner),
372            self.calendar.to_any(),
373        )
374    }
375}
376
377impl<A: AsCalendar> Date<A> {
378    /// Wrap the contained calendar type in `Rc<T>`, making it cheaper to clone.
379    ///
380    /// Useful when paired with [`Self::to_any()`] to obtain a `Date<Rc<AnyCalendar>>`
381    #[cfg(feature = "alloc")]
382    pub fn into_ref_counted(self) -> Date<Rc<A>> {
383        Date::from_raw(self.inner, Rc::new(self.calendar))
384    }
385
386    /// Wrap the contained calendar type in `Arc<T>`, making it cheaper to clone in a thread-safe manner.
387    ///
388    /// Useful when paired with [`Self::to_any()`] to obtain a `Date<Arc<AnyCalendar>>`
389    #[cfg(feature = "alloc")]
390    pub fn into_atomic_ref_counted(self) -> Date<Arc<A>> {
391        Date::from_raw(self.inner, Arc::new(self.calendar))
392    }
393
394    /// Wrap the calendar type in `Ref<T>`, making it cheaper to clone (by introducing a borrow)
395    ///
396    /// Useful for converting a `&Date<C>` into an equivalent `Date<D>` without cloning
397    /// the calendar.
398    pub fn as_borrowed(&self) -> Date<Ref<A>> {
399        Date::from_raw(self.inner, Ref(&self.calendar))
400    }
401}
402
403impl<C, A, B> PartialEq<Date<B>> for Date<A>
404where
405    C: Calendar,
406    A: AsCalendar<Calendar = C>,
407    B: AsCalendar<Calendar = C>,
408{
409    fn eq(&self, other: &Date<B>) -> bool {
410        self.inner.eq(&other.inner)
411    }
412}
413
414impl<A: AsCalendar> Eq for Date<A> {}
415
416impl<C, A, B> PartialOrd<Date<B>> for Date<A>
417where
418    C: Calendar,
419    C::DateInner: PartialOrd,
420    A: AsCalendar<Calendar = C>,
421    B: AsCalendar<Calendar = C>,
422{
423    fn partial_cmp(&self, other: &Date<B>) -> Option<core::cmp::Ordering> {
424        self.inner.partial_cmp(&other.inner)
425    }
426}
427
428impl<C, A> Ord for Date<A>
429where
430    C: Calendar,
431    C::DateInner: Ord,
432    A: AsCalendar<Calendar = C>,
433{
434    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
435        self.inner.cmp(&other.inner)
436    }
437}
438
439impl<A: AsCalendar> fmt::Debug for Date<A> {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
441        let month = self.month().ordinal;
442        let day = self.day_of_month().0;
443        let calendar = self.calendar.as_calendar().debug_name();
444        match self.year() {
445            types::YearInfo::Era(EraYear { year, era, .. }) => {
446                write!(
447                    f,
448                    "Date({year}-{month}-{day}, {era} era, for calendar {calendar})"
449                )
450            }
451            types::YearInfo::Cyclic(CyclicYear { year, related_iso }) => {
452                write!(
453                    f,
454                    "Date({year}-{month}-{day}, ISO year {related_iso}, for calendar {calendar})"
455                )
456            }
457        }
458    }
459}
460
461impl<A: AsCalendar + Clone> Clone for Date<A> {
462    fn clone(&self) -> Self {
463        Self {
464            inner: self.inner,
465            calendar: self.calendar.clone(),
466        }
467    }
468}
469
470impl<A> Copy for Date<A> where A: AsCalendar + Copy {}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475    use crate::types::Weekday;
476
477    #[test]
478    fn test_ord() {
479        let dates_in_order = [
480            Date::try_new_iso(-10, 1, 1).unwrap(),
481            Date::try_new_iso(-10, 1, 2).unwrap(),
482            Date::try_new_iso(-10, 2, 1).unwrap(),
483            Date::try_new_iso(-1, 1, 1).unwrap(),
484            Date::try_new_iso(-1, 1, 2).unwrap(),
485            Date::try_new_iso(-1, 2, 1).unwrap(),
486            Date::try_new_iso(0, 1, 1).unwrap(),
487            Date::try_new_iso(0, 1, 2).unwrap(),
488            Date::try_new_iso(0, 2, 1).unwrap(),
489            Date::try_new_iso(1, 1, 1).unwrap(),
490            Date::try_new_iso(1, 1, 2).unwrap(),
491            Date::try_new_iso(1, 2, 1).unwrap(),
492            Date::try_new_iso(10, 1, 1).unwrap(),
493            Date::try_new_iso(10, 1, 2).unwrap(),
494            Date::try_new_iso(10, 2, 1).unwrap(),
495        ];
496        for (i, i_date) in dates_in_order.iter().enumerate() {
497            for (j, j_date) in dates_in_order.iter().enumerate() {
498                let result1 = i_date.cmp(j_date);
499                let result2 = j_date.cmp(i_date);
500                assert_eq!(result1.reverse(), result2);
501                assert_eq!(i.cmp(&j), i_date.cmp(j_date));
502            }
503        }
504    }
505
506    #[test]
507    fn test_day_of_week() {
508        // June 23, 2021 is a Wednesday
509        assert_eq!(
510            Date::try_new_iso(2021, 6, 23).unwrap().day_of_week(),
511            Weekday::Wednesday,
512        );
513        // Feb 2, 1983 was a Wednesday
514        assert_eq!(
515            Date::try_new_iso(1983, 2, 2).unwrap().day_of_week(),
516            Weekday::Wednesday,
517        );
518        // Jan 21, 2021 was a Tuesday
519        assert_eq!(
520            Date::try_new_iso(2020, 1, 21).unwrap().day_of_week(),
521            Weekday::Tuesday,
522        );
523    }
524}