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