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}