icu_datetime/
dynamic.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//! Enumerations over [field sets](crate::fieldsets).
6//!
7//! These enumerations can be used when the field set is not known at compile time. However,
8//! they may contribute negatively to the binary size of the formatters.
9//!
10//! The most general type is [`CompositeFieldSet`], which supports all field
11//! sets in a single enumeration. [`CompositeDateTimeFieldSet`] is a good
12//! choice when you don't need to format time zones.
13//!
14//! Summary of all the types:
15//!
16//! | Type | Supported Field Sets |
17//! |---|---|
18//! | [`DateFieldSet`] | Date |
19//! | [`CalendarPeriodFieldSet`] | Calendar Period |
20//! | [`TimeFieldSet`] | Time |
21//! | [`ZoneFieldSet`] | Zone |
22//! | [`DateAndTimeFieldSet`] | Date + Time |
23//! | [`CompositeDateTimeFieldSet`] | Date, Calendar Period, Time, Date + Time |
24//! | [`CompositeFieldSet`] | All |
25//!
26//! # Examples
27//!
28//! Format with the time display depending on a runtime boolean:
29//!
30//! ```
31//! use icu::calendar::Date;
32//! use icu::datetime::fieldsets;
33//! use icu::datetime::fieldsets::enums::CompositeDateTimeFieldSet;
34//! use icu::datetime::input::{DateTime, Time};
35//! use icu::datetime::DateTimeFormatter;
36//! use icu::locale::locale;
37//! use writeable::Writeable;
38//!
39//! fn composite_field_set(
40//!     should_display_time: bool,
41//! ) -> CompositeDateTimeFieldSet {
42//!     if should_display_time {
43//!         let field_set_with_options = fieldsets::MD::medium().with_time_hm();
44//!         CompositeDateTimeFieldSet::DateTime(
45//!             fieldsets::enums::DateAndTimeFieldSet::MDT(
46//!                 field_set_with_options,
47//!             ),
48//!         )
49//!     } else {
50//!         let field_set_with_options = fieldsets::MD::medium();
51//!         CompositeDateTimeFieldSet::Date(fieldsets::enums::DateFieldSet::MD(
52//!             field_set_with_options,
53//!         ))
54//!     }
55//! }
56//!
57//! let datetime = DateTime {
58//!     date: Date::try_new_iso(2025, 1, 15).unwrap(),
59//!     time: Time::try_new(16, 0, 0, 0).unwrap(),
60//! };
61//!
62//! let with_time = DateTimeFormatter::try_new(
63//!     locale!("en-US").into(),
64//!     composite_field_set(true),
65//! )
66//! .unwrap();
67//! let without_time = DateTimeFormatter::try_new(
68//!     locale!("en-US").into(),
69//!     composite_field_set(false),
70//! )
71//! .unwrap();
72//!
73//! assert_eq!(with_time.format(&datetime).to_string(), "Jan 15, 4:00 PM");
74//! assert_eq!(without_time.format(&datetime).to_string(), "Jan 15");
75//! ```
76
77use crate::fieldsets::{builder, Combo};
78use crate::raw::neo::RawOptions;
79use crate::scaffold::GetField;
80use crate::{fieldsets, provider};
81use icu_provider::prelude::*;
82
83/// An enumeration over all possible date field sets.
84///
85/// This is a dynamic field set. For more information, see [`enums`](crate::fieldsets::enums).
86#[derive(Debug, Copy, Clone, PartialEq, Eq)]
87#[non_exhaustive]
88pub enum DateFieldSet {
89    /// The day of the month, as in
90    /// “on the 1st”.
91    D(fieldsets::D),
92    /// The month and day of the month, as in
93    /// “January 1st”.
94    MD(fieldsets::MD),
95    /// The year, month, and day of the month, as in
96    /// “January 1st, 2000”.
97    YMD(fieldsets::YMD),
98    /// The day of the month and day of the week, as in
99    /// “Saturday 1st”.
100    DE(fieldsets::DE),
101    /// The month, day of the month, and day of the week, as in
102    /// “Saturday, January 1st”.
103    MDE(fieldsets::MDE),
104    /// The year, month, day of the month, and day of the week, as in
105    /// “Saturday, January 1st, 2000”.
106    YMDE(fieldsets::YMDE),
107    /// The day of the week alone, as in
108    /// “Saturday”.
109    E(fieldsets::E),
110}
111
112/// An enumeration over all possible calendar period field sets.
113///
114/// This is a dynamic field set. For more information, see [`enums`](crate::fieldsets::enums).
115#[derive(Debug, Copy, Clone, PartialEq, Eq)]
116#[non_exhaustive]
117pub enum CalendarPeriodFieldSet {
118    /// A standalone month, as in
119    /// “January”.
120    M(fieldsets::M),
121    /// A month and year, as in
122    /// “January 2000”.
123    YM(fieldsets::YM),
124    /// A year, as in
125    /// “2000”.
126    Y(fieldsets::Y),
127    // TODO(#5643): Add support for week-of-year
128    // /// The year and week of the year, as in
129    // /// “52nd week of 1999”.
130    // YW(fieldsets::YW),
131    // TODO(#501): Consider adding support for Quarter and YearQuarter.
132}
133
134/// An enumeration over all possible time field sets.
135///
136/// This is a dynamic field set. For more information, see [`enums`](crate::fieldsets::enums).
137#[derive(Debug, Copy, Clone, PartialEq, Eq)]
138#[non_exhaustive]
139pub enum TimeFieldSet {
140    /// A time of day.
141    T(fieldsets::T),
142}
143
144/// An enumeration over all possible zone field sets.
145///
146/// This is a dynamic field set. For more information, see [`enums`](crate::fieldsets::enums).
147///
148/// # Time Zone Data Size
149///
150/// Time zone names contribute a lot of data size. For resource-constrained
151/// environments, the following formats require the least amount of data:
152///
153/// - [`fieldsets::zone::SpecificShort`]
154/// - [`fieldsets::zone::LocalizedOffsetLong`]
155#[derive(Debug, Copy, Clone, PartialEq, Eq)]
156#[non_exhaustive]
157pub enum ZoneFieldSet {
158    /// The long specific non-location format, as in
159    /// “Pacific Daylight Time”.
160    SpecificLong(fieldsets::zone::SpecificLong),
161    /// The short specific non-location format, as in
162    /// “PDT”.
163    SpecificShort(fieldsets::zone::SpecificShort),
164    /// The long offset format, as in
165    /// “GMT−8:00”.
166    LocalizedOffsetLong(fieldsets::zone::LocalizedOffsetLong),
167    /// The short offset format, as in
168    /// “GMT−8”.
169    LocalizedOffsetShort(fieldsets::zone::LocalizedOffsetShort),
170    /// The long generic non-location format, as in
171    /// “Pacific Time”.
172    GenericLong(fieldsets::zone::GenericLong),
173    /// The short generic non-location format, as in
174    /// “PT”.
175    GenericShort(fieldsets::zone::GenericShort),
176    /// The location format, as in
177    /// “Los Angeles Time”.
178    Location(fieldsets::zone::Location),
179    /// The exemplar city format, as in
180    /// “Los Angeles.
181    ExemplarCity(fieldsets::zone::ExemplarCity),
182}
183
184/// An enumeration over all possible date+time composite field sets.
185///
186/// This is a dynamic field set. For more information, see [`enums`](crate::fieldsets::enums).
187#[derive(Debug, Copy, Clone, PartialEq, Eq)]
188#[non_exhaustive]
189pub enum DateAndTimeFieldSet {
190    /// The day of the month with time of day, as in
191    /// “on the 1st at 10:31 AM”.
192    DT(fieldsets::DT),
193    /// The month and day of the month with time of day, as in
194    /// “January 1st at 10:31 AM”.
195    MDT(fieldsets::MDT),
196    /// The year, month, and day of the month with time of day, as in
197    /// “January 1st, 2000 at 10:31 AM”.
198    YMDT(fieldsets::YMDT),
199    /// The day of the month and day of the week with time of day, as in
200    /// “Saturday 1st at 10:31 AM”.
201    DET(fieldsets::DET),
202    /// The month, day of the month, and day of the week with time of day, as in
203    /// “Saturday, January 1st at 10:31 AM”.
204    MDET(fieldsets::MDET),
205    /// The year, month, day of the month, and day of the week with time of day, as in
206    /// “Saturday, January 1st, 2000 at 10:31 AM”.
207    YMDET(fieldsets::YMDET),
208    /// The day of the week alone with time of day, as in
209    /// “Saturday at 10:31 AM”.
210    ET(fieldsets::ET),
211}
212
213/// An enum supporting date, calendar period, time, and date+time field sets
214/// and options.
215///
216/// Time zones are not supported with this enum.
217///
218/// This enum is useful when formatting a type that does not contain a
219/// time zone or to avoid storing time zone data.
220///
221/// This is a dynamic field set. For more information, see [`enums`](crate::fieldsets::enums).
222#[derive(Debug, Copy, Clone, PartialEq, Eq)]
223#[non_exhaustive]
224pub enum CompositeDateTimeFieldSet {
225    /// Field set for a date.
226    Date(DateFieldSet),
227    /// Field set for a calendar period.
228    CalendarPeriod(CalendarPeriodFieldSet),
229    /// Field set for a time.
230    Time(TimeFieldSet),
231    /// Field set for a date and a time together.
232    DateTime(DateAndTimeFieldSet),
233}
234
235impl CompositeDateTimeFieldSet {
236    /// If the [`CompositeFieldSet`] does not contain a time zone,
237    /// returns the corresponding [`CompositeDateTimeFieldSet`].
238    pub fn try_from_composite_field_set(field_set: CompositeFieldSet) -> Option<Self> {
239        match field_set {
240            CompositeFieldSet::Date(v) => Some(Self::Date(v)),
241            CompositeFieldSet::CalendarPeriod(v) => Some(Self::CalendarPeriod(v)),
242            CompositeFieldSet::Time(v) => Some(Self::Time(v)),
243            CompositeFieldSet::Zone(_) => None,
244            CompositeFieldSet::DateTime(v) => Some(Self::DateTime(v)),
245            CompositeFieldSet::DateZone(_) => None,
246            CompositeFieldSet::TimeZone(_) => None,
247            CompositeFieldSet::DateTimeZone(_) => None,
248        }
249    }
250
251    /// Returns the [`CompositeFieldSet`] corresponding to this
252    /// [`CompositeDateTimeFieldSet`].
253    pub fn to_composite_field_set(self) -> CompositeFieldSet {
254        match self {
255            Self::Date(v) => CompositeFieldSet::Date(v),
256            Self::CalendarPeriod(v) => CompositeFieldSet::CalendarPeriod(v),
257            Self::Time(v) => CompositeFieldSet::Time(v),
258            Self::DateTime(v) => CompositeFieldSet::DateTime(v),
259        }
260    }
261}
262
263impl GetField<CompositeFieldSet> for CompositeDateTimeFieldSet {
264    fn get_field(&self) -> CompositeFieldSet {
265        self.to_composite_field_set()
266    }
267}
268
269/// Type alias representing all possible date + time zone field sets.
270///
271/// This is a dynamic field set. For more information, see [`enums`](crate::fieldsets::enums).
272pub type ZonedDateFieldSet = Combo<DateFieldSet, ZoneFieldSet>;
273
274/// Type alias representing all possible time + time zone field sets.
275///
276/// This is a dynamic field set. For more information, see [`enums`](crate::fieldsets::enums).
277pub type ZonedTimeFieldSet = Combo<TimeFieldSet, ZoneFieldSet>;
278
279/// Type alias representing all possible date + time + time zone field sets.
280///
281/// This is a dynamic field set. For more information, see [`enums`](crate::fieldsets::enums).
282pub type ZonedDateAndTimeFieldSet = Combo<DateAndTimeFieldSet, ZoneFieldSet>;
283
284/// An enum supporting all possible field sets and options.
285///
286/// This is a dynamic field set. For more information, see [`enums`](crate::fieldsets::enums).
287#[derive(Debug, Copy, Clone, PartialEq, Eq)]
288#[non_exhaustive]
289pub enum CompositeFieldSet {
290    /// Field set for a date.
291    Date(DateFieldSet),
292    /// Field set for a calendar period.
293    CalendarPeriod(CalendarPeriodFieldSet),
294    /// Field set for a time.
295    Time(TimeFieldSet),
296    /// Field set for a time zone.
297    Zone(ZoneFieldSet),
298    /// Field set for a date and a time together.
299    DateTime(DateAndTimeFieldSet),
300    /// Field set for a date and a time zone together.
301    DateZone(ZonedDateFieldSet),
302    /// Field set for a time and a time zone together.
303    TimeZone(ZonedTimeFieldSet),
304    /// Field set for a date, a time, and a time zone together.
305    DateTimeZone(ZonedDateAndTimeFieldSet),
306}
307
308macro_rules! first {
309    ($first:literal, $($remainder:literal,)*) => {
310        $first
311    };
312}
313
314macro_rules! impl_attrs {
315    (@attrs, $type:path, [$(($attr_var:ident, $str_var:ident, $value:literal),)+]) => {
316        impl $type {
317            $(
318                const $attr_var: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic($value);
319            )+
320            /// All attributes associated with this enum.
321            ///
322            /// # Encoding Details
323            ///
324            /// The string is based roughly on the UTS 35 symbol table with the following exceptions:
325            ///
326            /// 1. Lowercase letters are chosen where there is no ambiguity: `E` becomes `e`
327            /// 2. Capitals are replaced with their lowercase and a number 0: `M` becomes `m0`
328            /// 3. A single symbol is included for each component: length doesn't matter
329            /// 4. Time fields are encoded with their hour field only: `j`, `h`, or `h0`
330            ///
331            /// # Examples
332            ///
333            /// ```
334            #[doc = concat!("use icu::datetime::fieldsets::enums::", stringify!($type), " as FS;")]
335            /// use icu_provider::DataMarkerAttributes;
336            ///
337            /// assert!(FS::ALL_DATA_MARKER_ATTRIBUTES.contains(
338            #[doc = concat!("    &DataMarkerAttributes::from_str_or_panic(\"", first!($($value,)*), "\")")]
339            /// ));
340            /// ```
341            pub const ALL_DATA_MARKER_ATTRIBUTES: &'static [&'static DataMarkerAttributes] = &[
342                $(
343                    Self::$attr_var,
344                )+
345            ];
346        }
347    };
348    (@id_str, $type:path, [$(($variant:ident, $attr_var:ident)),+,]) => {
349        impl $type {
350            /// Returns a stable string identifying this set of fields.
351            pub(crate) const fn id_str(self) -> &'static DataMarkerAttributes {
352                match self {
353                    $(
354                        Self::$variant(_) => Self::$attr_var,
355                    )+
356                }
357            }
358        }
359    };
360    (@to_raw_options, $type:path, [$($variant:ident),+,]) => {
361        impl $type {
362            pub(crate) fn to_raw_options(self) -> RawOptions {
363                match self {
364                    $(
365                        Self::$variant(variant) => variant.to_raw_options(),
366                    )+
367                }
368            }
369        }
370    };
371    (@composite, $type:path, $variant:ident) => {
372        impl $type {
373            #[inline]
374            pub(crate) fn to_enum(self) -> $type {
375                self
376            }
377        }
378        impl GetField<CompositeFieldSet> for $type {
379            #[inline]
380            fn get_field(&self) -> CompositeFieldSet {
381                CompositeFieldSet::$variant(self.to_enum())
382            }
383        }
384    };
385    (@date, $type:path, [$(($variant:ident, $attr_var:ident, $str_var:ident, $value:literal)),+,]) => {
386        impl_attrs! { @attrs, $type, [$(($attr_var, $str_var, $value)),+,] }
387        impl_attrs! { @id_str, $type, [$(($variant, $attr_var)),+,] }
388        impl_attrs! { @to_raw_options, $type, [$($variant),+,] }
389        impl_attrs! { @composite, $type, Date }
390    };
391    (@calendar_period, $type:path, [$(($variant:ident, $attr_var:ident, $str_var:ident, $value:literal)),+,]) => {
392        impl_attrs! { @attrs, $type, [$(($attr_var, $str_var, $value)),+,] }
393        impl_attrs! { @to_raw_options, $type, [$($variant),+,] }
394        impl_attrs! { @composite, $type, CalendarPeriod }
395        impl_attrs! { @id_str, $type, [$(($variant, $attr_var)),+,] }
396    };
397    (@time, $type:path, [$(($attr_var:ident, $str_var:ident, $value:literal)),+,]) => {
398        impl_attrs! { @attrs, $type, [$(($attr_var, $str_var, $value)),+,] }
399        impl_attrs! { @to_raw_options, $type, [T,] }
400        impl_attrs! { @composite, $type, Time }
401    };
402    (@zone, $type:path, [$($variant:ident),+,]) => {
403        impl_attrs! { @composite, $type, Zone }
404        impl $type {
405            pub(crate) fn to_field(self) -> (provider::fields::TimeZone, provider::fields::FieldLength) {
406                match self {
407                    $(
408                        Self::$variant(variant) => variant.to_field(),
409                    )+
410                }
411            }
412            pub(crate) fn to_zone_style(self) -> builder::ZoneStyle {
413                match self {
414                    $(
415                        Self::$variant(_) => builder::ZoneStyle::$variant,
416                    )+
417                }
418            }
419        }
420    };
421    (@datetime, $type:path, [$(($d_variant:ident, $variant:ident)),+,]) => {
422        impl_attrs! { @to_raw_options, $type, [$($variant),+,] }
423        impl_attrs! { @composite, $type, DateTime }
424        impl $type {
425            pub(crate) fn to_date_field_set(self) -> DateFieldSet {
426                match self {
427                    $(
428                        Self::$variant(variant) => DateFieldSet::$d_variant(variant.to_date_field_set()),
429                    )+
430                }
431            }
432            pub(crate) fn to_time_field_set(self) -> TimeFieldSet {
433                let (length, time_precision, alignment) = match self {
434                    $(
435                        Self::$variant(variant) => (variant.length, variant.time_precision, variant.alignment),
436                    )+
437                };
438                TimeFieldSet::T(fieldsets::T {
439                    length,
440                    time_precision,
441                    alignment,
442                })
443            }
444        }
445    };
446}
447
448impl_attrs! {
449    @date,
450    DateFieldSet,
451    [
452        (D, ATTR_D, STR_D, "d"),
453        (MD, ATTR_MD, STR_MD, "m0d"),
454        (YMD, ATTR_YMD, STR_YMD, "ym0d"),
455        (DE, ATTR_DE, STR_DE, "de"),
456        (MDE, ATTR_MDE, STR_MDE, "m0de"),
457        (YMDE, ATTR_YMDE, STR_YMDE, "ym0de"),
458        (E, ATTR_E, STR_E, "e"),
459    ]
460}
461
462impl_attrs! {
463    @calendar_period,
464    CalendarPeriodFieldSet,
465    [
466        (M, ATTR_M, STR_M, "m0"),
467        (YM, ATTR_YM, STR_YM, "ym0"),
468        (Y, ATTR_Y, STR_Y, "y"),
469    ]
470}
471
472impl_attrs! {
473    @time,
474    TimeFieldSet,
475    [
476        (ATTR_T, STR_T, "j"),
477        (ATTR_T12, STR_T12, "h"),
478        (ATTR_T24, STR_T24, "h0"),
479    ]
480}
481
482impl TimeFieldSet {
483    pub(crate) const fn id_str_for_hour_cycle(
484        self,
485        hour_cycle: Option<provider::fields::Hour>,
486    ) -> &'static DataMarkerAttributes {
487        use provider::fields::Hour::*;
488        match hour_cycle {
489            None => Self::ATTR_T,
490            Some(H11 | H12) => Self::ATTR_T12,
491            Some(H23) => Self::ATTR_T24,
492        }
493    }
494}
495
496impl_attrs! {
497    @zone,
498    ZoneFieldSet,
499    [
500        SpecificLong,
501        SpecificShort,
502        LocalizedOffsetLong,
503        LocalizedOffsetShort,
504        GenericLong,
505        GenericShort,
506        Location,
507        ExemplarCity,
508    ]
509}
510
511impl_attrs! {
512    @attrs,
513    DateAndTimeFieldSet,
514    [
515        (ATTR_ET, STR_ET, "ej"),
516    ]
517}
518
519impl_attrs! {
520    @datetime,
521    DateAndTimeFieldSet,
522    [
523        (D, DT),
524        (MD, MDT),
525        (YMD, YMDT),
526        (DE, DET),
527        (MDE, MDET),
528        (YMDE, YMDET),
529        (E, ET),
530    ]
531}
532
533impl DateAndTimeFieldSet {
534    pub(crate) const fn id_str(self) -> Option<&'static DataMarkerAttributes> {
535        match self {
536            DateAndTimeFieldSet::ET(_) => Some(Self::ATTR_ET),
537            _ => None,
538        }
539    }
540}