icu_calendar/
types.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
5//! This module contains various types used by `icu::calendar` and `icu::datetime`
6
7#[doc(no_inline)]
8pub use calendrical_calculations::rata_die::RataDie;
9use core::fmt;
10use tinystr::TinyAsciiStr;
11use zerovec::ule::AsULE;
12
13// Export the duration types from here
14#[cfg(feature = "unstable")]
15pub use crate::duration::{DateDuration, DateDurationUnit};
16use crate::error::MonthCodeParseError;
17
18#[cfg(feature = "unstable")]
19pub use unstable::DateFields;
20#[cfg(not(feature = "unstable"))]
21pub(crate) use unstable::DateFields;
22
23mod unstable {
24    /// A bag of various ways of expressing the year, month, and/or day.
25    ///
26    /// Pass this into [`Date::try_from_fields`](crate::Date::try_from_fields).
27    ///
28    /// <div class="stab unstable">
29    /// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
30    /// including in SemVer minor releases. Do not use this type unless you are prepared for things to occasionally break.
31    ///
32    /// Graduation tracking issue: [issue #7161](https://github.com/unicode-org/icu4x/issues/7161).
33    /// </div>
34    ///
35    /// ✨ *Enabled with the `unstable` Cargo feature.*
36    #[derive(Copy, Clone, PartialEq, Default)]
37    #[non_exhaustive]
38    pub struct DateFields<'a> {
39        /// The era code as a UTF-8 string.
40        ///
41        /// The acceptable codes are defined by CLDR and documented on each calendar.
42        ///
43        /// If set, [`Self::era_year`] must also be set.
44        ///
45        /// # Examples
46        ///
47        /// To set the era field, use a byte string:
48        ///
49        /// ```
50        /// use icu::calendar::types::DateFields;
51        ///
52        /// let mut fields = DateFields::default();
53        ///
54        /// // As a byte string literal:
55        /// fields.era = Some(b"reiwa");
56        ///
57        /// // Using str::as_bytes:
58        /// fields.era = Some("reiwa".as_bytes());
59        /// ```
60        ///
61        /// For a full example, see [`Self::extended_year`].
62        pub era: Option<&'a [u8]>,
63        /// The numeric year in [`Self::era`].
64        ///
65        /// If set, [`Self::era`] must also be set.
66        ///
67        /// For an example, see [`Self::extended_year`].
68        pub era_year: Option<i32>,
69        /// See [`Date::extended_year()`](crate::Date::extended_year).
70        ///
71        /// If both this and [`Self::era`]/[`Self::era_year`] are set, they must
72        /// refer to the same year.
73        ///
74        /// # Examples
75        ///
76        /// Either `extended_year` or `era` + `era_year` can be used in DateFields:
77        ///
78        /// ```
79        /// use icu::calendar::cal::Japanese;
80        /// use icu::calendar::types::DateFields;
81        /// use icu::calendar::Date;
82        ///
83        /// let mut fields1 = DateFields::default();
84        /// fields1.era = Some(b"reiwa");
85        /// fields1.era_year = Some(7);
86        /// fields1.ordinal_month = Some(1);
87        /// fields1.day = Some(1);
88        ///
89        /// let date1 =
90        ///     Date::try_from_fields(fields1, Default::default(), Japanese::new())
91        ///         .expect("a well-defined Japanese date from era year");
92        ///
93        /// let mut fields2 = DateFields::default();
94        /// fields2.extended_year = Some(2025);
95        /// fields2.ordinal_month = Some(1);
96        /// fields2.day = Some(1);
97        ///
98        /// let date2 =
99        ///     Date::try_from_fields(fields2, Default::default(), Japanese::new())
100        ///         .expect("a well-defined Japanese date from extended year");
101        ///
102        /// assert_eq!(date1, date2);
103        ///
104        /// let year_info = date1.year().era().unwrap();
105        /// assert_eq!(year_info.year, 7);
106        /// assert_eq!(year_info.era.as_str(), "reiwa");
107        /// assert_eq!(year_info.extended_year, 2025);
108        /// ```
109        pub extended_year: Option<i32>,
110        /// The month code representing a valid month in this calendar year,
111        /// as a UTF-8 string.
112        ///
113        /// See [`MonthCode`](crate::types::MonthCode) for information on the syntax.
114        ///
115        /// # Examples
116        ///
117        /// To set the month code field, use a byte string:
118        ///
119        /// ```
120        /// use icu::calendar::types::DateFields;
121        ///
122        /// let mut fields = DateFields::default();
123        ///
124        /// // As a byte string literal:
125        /// fields.era = Some(b"M02L");
126        ///
127        /// // Using str::as_bytes:
128        /// fields.era = Some("M02L".as_bytes());
129        /// ```
130        ///
131        /// For a full example, see [`Self::ordinal_month`].
132        pub month_code: Option<&'a [u8]>,
133        /// See [`MonthInfo::ordinal`](crate::types::MonthInfo::ordinal).
134        ///
135        /// If both this and [`Self::month_code`] are set, they must refer to
136        /// the same month.
137        ///
138        /// Note: using [`Self::month_code`] is recommended, because the ordinal month numbers
139        /// can vary from year to year, as illustrated in the following example.
140        ///
141        /// # Examples
142        ///
143        /// Either `month_code` or `ordinal_month` can be used in DateFields, but they
144        /// might not resolve to the same month number:
145        ///
146        /// ```
147        /// use icu::calendar::cal::ChineseTraditional;
148        /// use icu::calendar::types::DateFields;
149        /// use icu::calendar::Date;
150        ///
151        /// // The 2023 Year of the Rabbit had a leap month after the 2nd month.
152        /// let mut fields1 = DateFields::default();
153        /// fields1.extended_year = Some(2023);
154        /// fields1.month_code = Some(b"M02L");
155        /// fields1.day = Some(1);
156        ///
157        /// let date1 = Date::try_from_fields(
158        ///     fields1,
159        ///     Default::default(),
160        ///     ChineseTraditional::new(),
161        /// )
162        /// .expect("a well-defined Chinese date from month code");
163        ///
164        /// let mut fields2 = DateFields::default();
165        /// fields2.extended_year = Some(2023);
166        /// fields2.ordinal_month = Some(3);
167        /// fields2.day = Some(1);
168        ///
169        /// let date2 = Date::try_from_fields(
170        ///     fields2,
171        ///     Default::default(),
172        ///     ChineseTraditional::new(),
173        /// )
174        /// .expect("a well-defined Chinese date from ordinal month");
175        ///
176        /// assert_eq!(date1, date2);
177        ///
178        /// let month_info = date1.month();
179        /// assert_eq!(month_info.ordinal, 3);
180        /// assert_eq!(month_info.standard_code.0, "M02L");
181        /// ```
182        pub ordinal_month: Option<u8>,
183        /// See [`DayOfMonth`](crate::types::DayOfMonth).
184        pub day: Option<u8>,
185    }
186}
187
188// Custom impl to stringify era and month_code where possible.
189impl fmt::Debug for DateFields<'_> {
190    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
191        // Ensures we catch future fields
192        let Self {
193            era,
194            era_year,
195            extended_year,
196            month_code,
197            ordinal_month,
198            day,
199        } = *self;
200        let mut builder = f.debug_struct("DateFields");
201        if let Some(s) = era.and_then(|s| core::str::from_utf8(s).ok()) {
202            builder.field("era", &Some(s));
203        } else {
204            builder.field("era", &era);
205        }
206        builder.field("era_year", &era_year);
207        builder.field("extended_year", &extended_year);
208        if let Some(s) = month_code.and_then(|s| core::str::from_utf8(s).ok()) {
209            builder.field("month_code", &Some(s));
210        } else {
211            builder.field("month_code", &month_code);
212        }
213        builder.field("ordinal_month", &ordinal_month);
214        builder.field("day", &day);
215        builder.finish()
216    }
217}
218
219/// The type of year: Calendars like Chinese don't have an era and instead format with cyclic years.
220#[derive(Copy, Clone, Debug, PartialEq)]
221#[non_exhaustive]
222pub enum YearInfo {
223    /// An era and a year in that era
224    Era(EraYear),
225    /// A cyclic year, and the related ISO year
226    ///
227    /// Knowing the cyclic year is typically not enough to pinpoint a date, however cyclic calendars
228    /// don't typically use eras, so disambiguation can be done by saying things like "Year 甲辰 (2024)"
229    Cyclic(CyclicYear),
230}
231
232impl From<EraYear> for YearInfo {
233    fn from(value: EraYear) -> Self {
234        Self::Era(value)
235    }
236}
237
238impl From<CyclicYear> for YearInfo {
239    fn from(value: CyclicYear) -> Self {
240        Self::Cyclic(value)
241    }
242}
243
244impl YearInfo {
245    /// Get *some* year number that can be displayed
246    ///
247    /// Gets the era year for era calendars, and the related ISO year for cyclic calendars.
248    pub fn era_year_or_related_iso(self) -> i32 {
249        match self {
250            YearInfo::Era(e) => e.year,
251            YearInfo::Cyclic(c) => c.related_iso,
252        }
253    }
254
255    /// Get the extended year (See [`Date::extended_year`](crate::Date::extended_year))
256    /// for more information
257    pub fn extended_year(self) -> i32 {
258        match self {
259            YearInfo::Era(e) => e.extended_year,
260            YearInfo::Cyclic(c) => c.related_iso,
261        }
262    }
263
264    /// Get the era year information, if available
265    pub fn era(self) -> Option<EraYear> {
266        match self {
267            Self::Era(e) => Some(e),
268            Self::Cyclic(_) => None,
269        }
270    }
271
272    /// Get the cyclic year informat, if available
273    pub fn cyclic(self) -> Option<CyclicYear> {
274        match self {
275            Self::Era(_) => None,
276            Self::Cyclic(c) => Some(c),
277        }
278    }
279}
280
281/// Defines whether the era or century is required to interpret the year.
282///
283/// For example 2024 AD can be formatted as `2024`, or even `24`, but 1931 AD
284/// should not be formatted as `31`, and 2024 BC should not be formatted as `2024`.
285#[derive(Copy, Clone, Debug, PartialEq)]
286#[allow(clippy::exhaustive_enums)] // logically complete
287pub enum YearAmbiguity {
288    /// The year is unambiguous without a century or era.
289    Unambiguous,
290    /// The century is required, the era may be included.
291    CenturyRequired,
292    /// The era is required, the century may be included.
293    EraRequired,
294    /// The century and era are required.
295    EraAndCenturyRequired,
296}
297
298/// Year information for a year that is specified with an era
299#[derive(Copy, Clone, Debug, PartialEq)]
300#[non_exhaustive]
301pub struct EraYear {
302    /// The numeric year in that era
303    pub year: i32,
304    /// See [`YearInfo::extended_year()`]
305    pub extended_year: i32,
306    /// The era code as defined by CLDR, expect for cases where CLDR does not define a code.
307    pub era: TinyAsciiStr<16>,
308    /// An era index, for calendars with a small set of eras.
309    ///
310    /// The only guarantee we make is that these values are stable. These do *not*
311    /// match the indices produced by ICU4C or CLDR.
312    ///
313    /// These are used by ICU4X datetime formatting for efficiently storing data.
314    pub era_index: Option<u8>,
315    /// The ambiguity of the era/year combination
316    pub ambiguity: YearAmbiguity,
317}
318
319/// Year information for a year that is specified as a cyclic year
320#[derive(Copy, Clone, Debug, PartialEq)]
321#[non_exhaustive]
322pub struct CyclicYear {
323    /// The year in the cycle, 1-based
324    pub year: u8,
325    /// The ISO year corresponding to this year
326    pub related_iso: i32,
327}
328
329/// Representation of a month in a year
330///
331/// Month codes typically look like `M01`, `M02`, etc, but can handle leap months
332/// (`M03L`) in lunar calendars. Solar calendars will have codes between `M01` and `M12`
333/// potentially with an `M13` for epagomenal months. Check the docs for a particular calendar
334/// for details on what its month codes are.
335///
336/// Month codes are shared with Temporal, [see Temporal proposal][era-proposal].
337///
338/// [era-proposal]: https://tc39.es/proposal-intl-era-monthcode/
339#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
340#[allow(clippy::exhaustive_structs)] // this is a newtype
341#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
342#[cfg_attr(feature = "datagen", databake(path = icu_calendar::types))]
343#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
344pub struct MonthCode(pub TinyAsciiStr<4>);
345
346impl MonthCode {
347    /// Returns an option which is `Some` containing the non-month version of a leap month
348    /// if the [`MonthCode`] this method is called upon is a leap month, and `None` otherwise.
349    /// This method assumes the [`MonthCode`] is valid.
350    #[deprecated(since = "2.1.0")]
351    pub fn get_normal_if_leap(self) -> Option<MonthCode> {
352        let bytes = self.0.all_bytes();
353        if bytes[3] == b'L' {
354            Some(MonthCode(TinyAsciiStr::try_from_utf8(&bytes[0..3]).ok()?))
355        } else {
356            None
357        }
358    }
359
360    #[deprecated(since = "2.1.0")]
361    /// Get the month number and whether or not it is leap from the month code
362    pub fn parsed(self) -> Option<(u8, bool)> {
363        ValidMonthCode::try_from_utf8(self.0.as_bytes())
364            .ok()
365            .map(ValidMonthCode::to_tuple)
366    }
367
368    /// Construct a "normal" month code given a number ("Mxx").
369    ///
370    /// Returns an error for months greater than 99
371    pub fn new_normal(number: u8) -> Option<Self> {
372        (1..=99)
373            .contains(&number)
374            .then(|| ValidMonthCode::new_unchecked(number, false).to_month_code())
375    }
376
377    /// Construct a "leap" month code given a number ("MxxL").
378    ///
379    /// Returns an error for months greater than 99
380    pub fn new_leap(number: u8) -> Option<Self> {
381        (1..=99)
382            .contains(&number)
383            .then(|| ValidMonthCode::new_unchecked(number, true).to_month_code())
384    }
385}
386
387#[test]
388fn test_get_normal_month_code_if_leap() {
389    #![allow(deprecated)]
390    assert_eq!(
391        MonthCode::new_leap(1).unwrap().get_normal_if_leap(),
392        MonthCode::new_normal(1)
393    );
394
395    assert_eq!(
396        MonthCode::new_leap(11).unwrap().get_normal_if_leap(),
397        MonthCode::new_normal(11)
398    );
399
400    assert_eq!(
401        MonthCode::new_normal(10).unwrap().get_normal_if_leap(),
402        None
403    );
404}
405
406impl AsULE for MonthCode {
407    type ULE = TinyAsciiStr<4>;
408    fn to_unaligned(self) -> TinyAsciiStr<4> {
409        self.0
410    }
411    fn from_unaligned(u: TinyAsciiStr<4>) -> Self {
412        Self(u)
413    }
414}
415
416#[cfg(feature = "alloc")]
417impl<'a> zerovec::maps::ZeroMapKV<'a> for MonthCode {
418    type Container = zerovec::ZeroVec<'a, MonthCode>;
419    type Slice = zerovec::ZeroSlice<MonthCode>;
420    type GetType = <MonthCode as AsULE>::ULE;
421    type OwnedType = MonthCode;
422}
423
424impl fmt::Display for MonthCode {
425    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
426        write!(f, "{}", self.0)
427    }
428}
429
430/// A [`MonthCode`] that has been parsed into its internal representation.
431#[derive(Copy, Clone, Debug, PartialEq)]
432pub(crate) struct ValidMonthCode {
433    /// Month number between 0 and 99
434    number: u8,
435    is_leap: bool,
436}
437
438impl ValidMonthCode {
439    #[inline]
440    pub(crate) fn try_from_utf8(bytes: &[u8]) -> Result<Self, MonthCodeParseError> {
441        match *bytes {
442            [b'M', tens, ones] => Ok(Self {
443                number: (tens - b'0') * 10 + ones - b'0',
444                is_leap: false,
445            }),
446            [b'M', tens, ones, b'L'] => Ok(Self {
447                number: (tens - b'0') * 10 + ones - b'0',
448                is_leap: true,
449            }),
450            _ => Err(MonthCodeParseError::InvalidSyntax),
451        }
452    }
453
454    /// Create a new ValidMonthCode without checking that the number is between 1 and 99
455    #[inline]
456    pub(crate) const fn new_unchecked(number: u8, is_leap: bool) -> Self {
457        debug_assert!(1 <= number && number <= 99);
458        Self { number, is_leap }
459    }
460
461    /// Returns the month number according to the month code.
462    ///
463    /// This is NOT the same as the ordinal month!
464    ///
465    /// # Examples
466    ///
467    /// ```ignore
468    /// use icu::calendar::Date;
469    /// use icu::calendar::cal::Hebrew;
470    ///
471    /// let hebrew_date = Date::try_new_iso(2024, 7, 1).unwrap().to_calendar(Hebrew);
472    /// let month_info = hebrew_date.month();
473    ///
474    /// // Hebrew year 5784 was a leap year, so the ordinal month and month number diverge.
475    /// assert_eq!(month_info.ordinal, 10);
476    /// assert_eq!(month_info.valid_month_code.number(), 9);
477    /// ```
478    #[inline]
479    pub fn number(self) -> u8 {
480        self.number
481    }
482
483    /// Returns whether the month is a leap month.
484    ///
485    /// This is true for intercalary months in [`Hebrew`] and [`LunarChinese`].
486    ///
487    /// [`Hebrew`]: crate::cal::Hebrew
488    /// [`LunarChinese`]: crate::cal::LunarChinese
489    #[inline]
490    pub fn is_leap(self) -> bool {
491        self.is_leap
492    }
493
494    #[inline]
495    pub(crate) fn to_tuple(self) -> (u8, bool) {
496        (self.number, self.is_leap)
497    }
498
499    pub(crate) fn to_month_code(self) -> MonthCode {
500        #[allow(clippy::unwrap_used)] // by construction
501        MonthCode(
502            TinyAsciiStr::try_from_raw([
503                b'M',
504                b'0' + self.number / 10,
505                b'0' + self.number % 10,
506                if self.is_leap { b'L' } else { 0 },
507            ])
508            .unwrap(),
509        )
510    }
511}
512
513/// Representation of a formattable month.
514#[derive(Copy, Clone, Debug, PartialEq)]
515#[non_exhaustive]
516pub struct MonthInfo {
517    /// The month number in this given year. For calendars with leap months, all months after
518    /// the leap month will end up with an incremented number.
519    ///
520    /// In general, prefer using the month code in generic code.
521    pub ordinal: u8,
522
523    /// The month code, used to distinguish months during leap years.
524    ///
525    /// Round-trips through `Date` constructors like [`Date::try_new_from_codes`] and [`Date::try_from_fields`].
526    ///
527    /// This follows [Temporal's specification](https://tc39.es/proposal-intl-era-monthcode/#table-additional-month-codes).
528    /// Months considered the "same" have the same code: This means that the Hebrew months "Adar" and "Adar II" ("Adar, but during a leap year")
529    /// are considered the same month and have the code M05.
530    ///
531    /// [`Date::try_new_from_codes`]: crate::Date::try_new_from_codes
532    /// [`Date::try_from_fields`]: crate::Date::try_from_fields
533    pub standard_code: MonthCode,
534
535    /// Same as [`Self::standard_code`] but with invariants validated.
536    pub(crate) valid_standard_code: ValidMonthCode,
537
538    /// A month code, useable for formatting.
539    ///
540    /// Does NOT necessarily round-trip through `Date` constructors like [`Date::try_new_from_codes`] and [`Date::try_from_fields`].
541    ///
542    /// This may not necessarily be the canonical month code for a month in cases where a month has different
543    /// formatting in a leap year, for example Adar/Adar II in the Hebrew calendar in a leap year has
544    /// the standard code M06, but for formatting specifically the Hebrew calendar will return M06L since it is formatted
545    /// differently.
546    ///
547    /// [`Date::try_new_from_codes`]: crate::Date::try_new_from_codes
548    /// [`Date::try_from_fields`]: crate::Date::try_from_fields
549    pub formatting_code: MonthCode,
550
551    /// Same as [`Self::formatting_code`] but with invariants validated.
552    pub(crate) valid_formatting_code: ValidMonthCode,
553}
554
555impl MonthInfo {
556    pub(crate) fn non_lunisolar(number: u8) -> Self {
557        Self::for_code_and_ordinal(ValidMonthCode::new_unchecked(number, false), number)
558    }
559
560    pub(crate) fn for_code_and_ordinal(code: ValidMonthCode, ordinal: u8) -> Self {
561        Self {
562            ordinal,
563            standard_code: code.to_month_code(),
564            valid_standard_code: code,
565            formatting_code: code.to_month_code(),
566            valid_formatting_code: code,
567        }
568    }
569
570    /// Gets the month number. A month number N is not necessarily the Nth month in the year
571    /// if there are leap months in the year, rather it is associated with the Nth month of a "regular"
572    /// year. There may be multiple month Ns in a year
573    pub fn month_number(self) -> u8 {
574        self.valid_standard_code.number()
575    }
576
577    /// Get whether the month is a leap month
578    pub fn is_leap(self) -> bool {
579        self.valid_standard_code.is_leap()
580    }
581
582    #[doc(hidden)]
583    pub fn formatting_month_number(self) -> u8 {
584        self.valid_formatting_code.number()
585    }
586
587    #[doc(hidden)]
588    pub fn formatting_is_leap(self) -> bool {
589        self.valid_formatting_code.is_leap()
590    }
591}
592
593/// The current day of the year, 1-based.
594#[derive(Copy, Clone, Debug, PartialEq)]
595#[allow(clippy::exhaustive_structs)] // this is a newtype
596pub struct DayOfYear(pub u16);
597
598/// A 1-based day number in a month.
599#[allow(clippy::exhaustive_structs)] // this is a newtype
600#[derive(Clone, Copy, Debug, PartialEq)]
601pub struct DayOfMonth(pub u8);
602
603/// A week number in a year
604#[derive(Clone, Copy, Debug, PartialEq)]
605#[allow(clippy::exhaustive_structs)] // this is a newtype
606pub struct IsoWeekOfYear {
607    /// The 1-based ISO week number
608    pub week_number: u8,
609    /// The ISO year
610    pub iso_year: i32,
611}
612
613/// A day of week in month. 1-based.
614#[derive(Clone, Copy, Debug, PartialEq)]
615#[allow(clippy::exhaustive_structs)] // this is a newtype
616pub struct DayOfWeekInMonth(pub u8);
617
618impl From<DayOfMonth> for DayOfWeekInMonth {
619    fn from(day_of_month: DayOfMonth) -> Self {
620        DayOfWeekInMonth(1 + ((day_of_month.0 - 1) / 7))
621    }
622}
623
624#[test]
625fn test_day_of_week_in_month() {
626    assert_eq!(DayOfWeekInMonth::from(DayOfMonth(1)).0, 1);
627    assert_eq!(DayOfWeekInMonth::from(DayOfMonth(7)).0, 1);
628    assert_eq!(DayOfWeekInMonth::from(DayOfMonth(8)).0, 2);
629}
630
631/// A weekday in a 7-day week, according to ISO-8601.
632///
633/// The discriminant values correspond to ISO-8601 weekday numbers (Monday = 1, Sunday = 7).
634///
635/// # Examples
636///
637/// ```
638/// use icu::calendar::types::Weekday;
639///
640/// assert_eq!(1, Weekday::Monday as usize);
641/// assert_eq!(7, Weekday::Sunday as usize);
642/// ```
643#[derive(Clone, Copy, Debug, PartialEq, Eq)]
644#[allow(missing_docs)] // The weekday variants should be self-obvious.
645#[repr(i8)]
646#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
647#[cfg_attr(feature = "datagen", databake(path = icu_calendar::types))]
648#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
649#[allow(clippy::exhaustive_enums)] // This is stable
650pub enum Weekday {
651    Monday = 1,
652    Tuesday,
653    Wednesday,
654    Thursday,
655    Friday,
656    Saturday,
657    Sunday,
658}
659
660// RD 0 is a Sunday
661const SUNDAY: RataDie = RataDie::new(0);
662
663impl From<RataDie> for Weekday {
664    fn from(value: RataDie) -> Self {
665        use Weekday::*;
666        match (value - SUNDAY).rem_euclid(7) {
667            0 => Sunday,
668            1 => Monday,
669            2 => Tuesday,
670            3 => Wednesday,
671            4 => Thursday,
672            5 => Friday,
673            6 => Saturday,
674            _ => unreachable!(),
675        }
676    }
677}
678
679impl Weekday {
680    /// Convert from an ISO-8601 weekday number to an [`Weekday`] enum. 0 is automatically converted
681    /// to 7 (Sunday). If the number is out of range, it is interpreted modulo 7.
682    ///
683    /// # Examples
684    ///
685    /// ```
686    /// use icu::calendar::types::Weekday;
687    ///
688    /// assert_eq!(Weekday::Sunday, Weekday::from_days_since_sunday(0));
689    /// assert_eq!(Weekday::Monday, Weekday::from_days_since_sunday(1));
690    /// assert_eq!(Weekday::Sunday, Weekday::from_days_since_sunday(7));
691    /// assert_eq!(Weekday::Monday, Weekday::from_days_since_sunday(8));
692    /// ```
693    pub fn from_days_since_sunday(input: isize) -> Self {
694        (SUNDAY + input as i64).into()
695    }
696
697    /// Returns the day after the current day.
698    pub(crate) fn next_day(self) -> Weekday {
699        use Weekday::*;
700        match self {
701            Monday => Tuesday,
702            Tuesday => Wednesday,
703            Wednesday => Thursday,
704            Thursday => Friday,
705            Friday => Saturday,
706            Saturday => Sunday,
707            Sunday => Monday,
708        }
709    }
710}