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::week::{WeekCalculator, WeekOf};
7use crate::{types, Calendar, CalendarError, DateDuration, DateDurationUnit, Iso};
8use alloc::rc::Rc;
9use alloc::sync::Arc;
10use core::fmt;
11use core::ops::Deref;
12
13/// Types that contain a calendar
14///
15/// This allows one to use [`Date`] with wrappers around calendars,
16/// e.g. reference counted calendars.
17pub trait AsCalendar {
18    /// The calendar being wrapped
19    type Calendar: Calendar;
20    /// Obtain the inner calendar
21    fn as_calendar(&self) -> &Self::Calendar;
22}
23
24impl<C: Calendar> AsCalendar for C {
25    type Calendar = C;
26    #[inline]
27    fn as_calendar(&self) -> &Self {
28        self
29    }
30}
31
32impl<C: Calendar> AsCalendar for Rc<C> {
33    type Calendar = C;
34    #[inline]
35    fn as_calendar(&self) -> &C {
36        self
37    }
38}
39
40impl<C: Calendar> AsCalendar for Arc<C> {
41    type Calendar = C;
42    #[inline]
43    fn as_calendar(&self) -> &C {
44        self
45    }
46}
47
48/// This exists as a wrapper around `&'a T` so that
49/// `Date<&'a C>` is possible for calendar `C`.
50///
51/// Unfortunately,
52/// [`AsCalendar`] cannot be implemented on `&'a T` directly because
53/// `&'a T` is `#[fundamental]` and the impl would clash with the one above with
54/// `AsCalendar` for `C: Calendar`.
55///
56/// Use `Date<Ref<'a, C>>` where you would use `Date<&'a C>`
57#[allow(clippy::exhaustive_structs)] // newtype
58#[derive(PartialEq, Eq, Debug)]
59pub struct Ref<'a, C>(pub &'a C);
60
61impl<C> Copy for Ref<'_, C> {}
62
63impl<C> Clone for Ref<'_, C> {
64    fn clone(&self) -> Self {
65        *self
66    }
67}
68
69impl<C: Calendar> AsCalendar for Ref<'_, C> {
70    type Calendar = C;
71    #[inline]
72    fn as_calendar(&self) -> &C {
73        self.0
74    }
75}
76
77impl<'a, C> Deref for Ref<'a, C> {
78    type Target = C;
79    fn deref(&self) -> &C {
80        self.0
81    }
82}
83
84/// A date for a given calendar.
85///
86/// This can work with wrappers around [`Calendar`] types,
87/// e.g. `Rc<C>`, via the [`AsCalendar`] trait.
88///
89/// This can be constructed  constructed
90/// from its fields via [`Self::try_new_from_codes()`], or can be constructed with one of the
91/// `new_<calendar>_datetime()` per-calendar methods (and then freely converted between calendars).
92///
93/// ```rust
94/// use icu::calendar::Date;
95///
96/// // Example: creation of ISO date from integers.
97/// let date_iso = Date::try_new_iso_date(1970, 1, 2)
98///     .expect("Failed to initialize ISO Date instance.");
99///
100/// assert_eq!(date_iso.year().number, 1970);
101/// assert_eq!(date_iso.month().ordinal, 1);
102/// assert_eq!(date_iso.day_of_month().0, 2);
103/// ```
104pub struct Date<A: AsCalendar> {
105    pub(crate) inner: <A::Calendar as Calendar>::DateInner,
106    pub(crate) calendar: A,
107}
108
109impl<A: AsCalendar> Date<A> {
110    /// Construct a date from from era/month codes and fields, and some calendar representation
111    #[inline]
112    pub fn try_new_from_codes(
113        era: types::Era,
114        year: i32,
115        month_code: types::MonthCode,
116        day: u8,
117        calendar: A,
118    ) -> Result<Self, CalendarError> {
119        let inner = calendar
120            .as_calendar()
121            .date_from_codes(era, year, month_code, day)?;
122        Ok(Date { inner, calendar })
123    }
124
125    /// Construct a date from an ISO date and some calendar representation
126    #[inline]
127    pub fn new_from_iso(iso: Date<Iso>, calendar: A) -> Self {
128        let inner = calendar.as_calendar().date_from_iso(iso);
129        Date { inner, calendar }
130    }
131
132    /// Convert the Date to an ISO Date
133    #[inline]
134    pub fn to_iso(&self) -> Date<Iso> {
135        self.calendar.as_calendar().date_to_iso(self.inner())
136    }
137
138    /// Convert the Date to a date in a different calendar
139    #[inline]
140    pub fn to_calendar<A2: AsCalendar>(&self, calendar: A2) -> Date<A2> {
141        Date::new_from_iso(self.to_iso(), calendar)
142    }
143
144    /// The number of months in the year of this date
145    #[inline]
146    pub fn months_in_year(&self) -> u8 {
147        self.calendar.as_calendar().months_in_year(self.inner())
148    }
149
150    /// The number of days in the year of this date
151    #[inline]
152    pub fn days_in_year(&self) -> u16 {
153        self.calendar.as_calendar().days_in_year(self.inner())
154    }
155
156    /// The number of days in the month of this date
157    #[inline]
158    pub fn days_in_month(&self) -> u8 {
159        self.calendar.as_calendar().days_in_month(self.inner())
160    }
161
162    /// The day of the week for this date
163    ///
164    /// Monday is 1, Sunday is 7, according to ISO
165    #[inline]
166    pub fn day_of_week(&self) -> types::IsoWeekday {
167        self.calendar.as_calendar().day_of_week(self.inner())
168    }
169
170    /// Add a `duration` to this date, mutating it
171    ///
172    /// Currently unstable for ICU4X 1.0
173    #[doc(hidden)]
174    #[inline]
175    pub fn add(&mut self, duration: DateDuration<A::Calendar>) {
176        self.calendar
177            .as_calendar()
178            .offset_date(&mut self.inner, duration)
179    }
180
181    /// Add a `duration` to this date, returning the new one
182    ///
183    /// Currently unstable for ICU4X 1.0
184    #[doc(hidden)]
185    #[inline]
186    pub fn added(mut self, duration: DateDuration<A::Calendar>) -> Self {
187        self.add(duration);
188        self
189    }
190
191    /// Calculating the duration between `other - self`
192    ///
193    /// Currently unstable for ICU4X 1.0
194    #[doc(hidden)]
195    #[inline]
196    pub fn until<B: AsCalendar<Calendar = A::Calendar>>(
197        &self,
198        other: &Date<B>,
199        largest_unit: DateDurationUnit,
200        smallest_unit: DateDurationUnit,
201    ) -> DateDuration<A::Calendar> {
202        self.calendar.as_calendar().until(
203            self.inner(),
204            other.inner(),
205            other.calendar.as_calendar(),
206            largest_unit,
207            smallest_unit,
208        )
209    }
210
211    /// The calendar-specific year represented by `self`
212    #[inline]
213    pub fn year(&self) -> types::FormattableYear {
214        self.calendar.as_calendar().year(&self.inner)
215    }
216
217    /// Returns whether `self` is in a calendar-specific leap year
218    #[inline]
219    pub fn is_in_leap_year(&self) -> bool {
220        self.calendar.as_calendar().is_in_leap_year(&self.inner)
221    }
222
223    /// The calendar-specific month represented by `self`
224    #[inline]
225    pub fn month(&self) -> types::FormattableMonth {
226        self.calendar.as_calendar().month(&self.inner)
227    }
228
229    /// The calendar-specific day-of-month represented by `self`
230    #[inline]
231    pub fn day_of_month(&self) -> types::DayOfMonth {
232        self.calendar.as_calendar().day_of_month(&self.inner)
233    }
234
235    /// The calendar-specific day-of-month represented by `self`
236    #[inline]
237    pub fn day_of_year_info(&self) -> types::DayOfYearInfo {
238        self.calendar.as_calendar().day_of_year_info(&self.inner)
239    }
240
241    /// The week of the month containing this date.
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// use icu::calendar::types::IsoWeekday;
247    /// use icu::calendar::types::WeekOfMonth;
248    /// use icu::calendar::Date;
249    ///
250    /// let date = Date::try_new_iso_date(2022, 8, 10).unwrap(); // second Wednesday
251    ///
252    /// // The following info is usually locale-specific
253    /// let first_weekday = IsoWeekday::Sunday;
254    ///
255    /// assert_eq!(date.week_of_month(first_weekday), WeekOfMonth(2));
256    /// ```
257    pub fn week_of_month(&self, first_weekday: types::IsoWeekday) -> types::WeekOfMonth {
258        let config = WeekCalculator {
259            first_weekday,
260            min_week_days: 0, // ignored
261            weekend: None,
262        };
263        config.week_of_month(self.day_of_month(), self.day_of_week())
264    }
265
266    /// The week of the year containing this date.
267    ///
268    /// # Examples
269    ///
270    /// ```
271    /// use icu::calendar::week::RelativeUnit;
272    /// use icu::calendar::week::WeekCalculator;
273    /// use icu::calendar::week::WeekOf;
274    /// use icu::calendar::Date;
275    ///
276    /// let date = Date::try_new_iso_date(2022, 8, 26).unwrap();
277    ///
278    /// // The following info is usually locale-specific
279    /// let week_calculator = WeekCalculator::default();
280    ///
281    /// assert_eq!(
282    ///     date.week_of_year(&week_calculator),
283    ///     Ok(WeekOf {
284    ///         week: 35,
285    ///         unit: RelativeUnit::Current
286    ///     })
287    /// );
288    /// ```
289    pub fn week_of_year(&self, config: &WeekCalculator) -> Result<WeekOf, CalendarError> {
290        config.week_of_year(self.day_of_year_info(), self.day_of_week())
291    }
292
293    /// Construct a date from raw values for a given calendar. This does not check any
294    /// invariants for the date and calendar, and should only be called by calendar implementations.
295    ///
296    /// Calling this outside of calendar implementations is sound, but calendar implementations are not
297    /// expected to do anything sensible with such invalid dates.
298    ///
299    /// AnyCalendar *will* panic if AnyCalendar [`Date`] objects with mismatching
300    /// date and calendar types are constructed
301    #[inline]
302    pub fn from_raw(inner: <A::Calendar as Calendar>::DateInner, calendar: A) -> Self {
303        Self { inner, calendar }
304    }
305
306    /// Get the inner date implementation. Should not be called outside of calendar implementations
307    #[inline]
308    pub fn inner(&self) -> &<A::Calendar as Calendar>::DateInner {
309        &self.inner
310    }
311
312    /// Get a reference to the contained calendar
313    #[inline]
314    pub fn calendar(&self) -> &A::Calendar {
315        self.calendar.as_calendar()
316    }
317
318    /// Get a reference to the contained calendar wrapper
319    ///
320    /// (Useful in case the user wishes to e.g. clone an Rc)
321    #[inline]
322    pub fn calendar_wrapper(&self) -> &A {
323        &self.calendar
324    }
325
326    #[cfg(test)]
327    pub(crate) fn to_fixed(&self) -> calendrical_calculations::rata_die::RataDie {
328        Iso::fixed_from_iso(self.to_iso().inner)
329    }
330}
331
332impl<C: IntoAnyCalendar, A: AsCalendar<Calendar = C>> Date<A> {
333    /// Type-erase the date, converting it to a date for [`AnyCalendar`]
334    pub fn to_any(&self) -> Date<AnyCalendar> {
335        let cal = self.calendar();
336        Date::from_raw(cal.date_to_any(self.inner()), cal.to_any_cloned())
337    }
338}
339
340impl<C: Calendar> Date<C> {
341    /// Wrap the calendar type in `Rc<T>`
342    ///
343    /// Useful when paired with [`Self::to_any()`] to obtain a `Date<Rc<AnyCalendar>>`
344    pub fn wrap_calendar_in_rc(self) -> Date<Rc<C>> {
345        Date::from_raw(self.inner, Rc::new(self.calendar))
346    }
347
348    /// Wrap the calendar type in `Arc<T>`
349    ///
350    /// Useful when paired with [`Self::to_any()`] to obtain a `Date<Rc<AnyCalendar>>`
351    pub fn wrap_calendar_in_arc(self) -> Date<Arc<C>> {
352        Date::from_raw(self.inner, Arc::new(self.calendar))
353    }
354}
355
356impl<C, A, B> PartialEq<Date<B>> for Date<A>
357where
358    C: Calendar,
359    A: AsCalendar<Calendar = C>,
360    B: AsCalendar<Calendar = C>,
361{
362    fn eq(&self, other: &Date<B>) -> bool {
363        self.inner.eq(&other.inner)
364    }
365}
366
367impl<A: AsCalendar> Eq for Date<A> {}
368
369impl<C, A, B> PartialOrd<Date<B>> for Date<A>
370where
371    C: Calendar,
372    C::DateInner: PartialOrd,
373    A: AsCalendar<Calendar = C>,
374    B: AsCalendar<Calendar = C>,
375{
376    fn partial_cmp(&self, other: &Date<B>) -> Option<core::cmp::Ordering> {
377        self.inner.partial_cmp(&other.inner)
378    }
379}
380
381impl<C, A> Ord for Date<A>
382where
383    C: Calendar,
384    C::DateInner: Ord,
385    A: AsCalendar<Calendar = C>,
386{
387    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
388        self.inner.cmp(&other.inner)
389    }
390}
391
392impl<A: AsCalendar> fmt::Debug for Date<A> {
393    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
394        write!(
395            f,
396            "Date({}-{}-{}, {} era, for calendar {})",
397            self.year().number,
398            self.month().ordinal,
399            self.day_of_month().0,
400            self.year().era.0,
401            self.calendar.as_calendar().debug_name()
402        )
403    }
404}
405
406impl<A: AsCalendar + Clone> Clone for Date<A> {
407    fn clone(&self) -> Self {
408        Self {
409            inner: self.inner.clone(),
410            calendar: self.calendar.clone(),
411        }
412    }
413}
414
415impl<A> Copy for Date<A>
416where
417    A: AsCalendar + Copy,
418    <<A as AsCalendar>::Calendar as Calendar>::DateInner: Copy,
419{
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425
426    #[test]
427    fn test_ord() {
428        let dates_in_order = [
429            Date::try_new_iso_date(-10, 1, 1).unwrap(),
430            Date::try_new_iso_date(-10, 1, 2).unwrap(),
431            Date::try_new_iso_date(-10, 2, 1).unwrap(),
432            Date::try_new_iso_date(-1, 1, 1).unwrap(),
433            Date::try_new_iso_date(-1, 1, 2).unwrap(),
434            Date::try_new_iso_date(-1, 2, 1).unwrap(),
435            Date::try_new_iso_date(0, 1, 1).unwrap(),
436            Date::try_new_iso_date(0, 1, 2).unwrap(),
437            Date::try_new_iso_date(0, 2, 1).unwrap(),
438            Date::try_new_iso_date(1, 1, 1).unwrap(),
439            Date::try_new_iso_date(1, 1, 2).unwrap(),
440            Date::try_new_iso_date(1, 2, 1).unwrap(),
441            Date::try_new_iso_date(10, 1, 1).unwrap(),
442            Date::try_new_iso_date(10, 1, 2).unwrap(),
443            Date::try_new_iso_date(10, 2, 1).unwrap(),
444        ];
445        for (i, i_date) in dates_in_order.iter().enumerate() {
446            for (j, j_date) in dates_in_order.iter().enumerate() {
447                let result1 = i_date.cmp(j_date);
448                let result2 = j_date.cmp(i_date);
449                assert_eq!(result1.reverse(), result2);
450                assert_eq!(i.cmp(&j), i_date.cmp(j_date));
451            }
452        }
453    }
454}