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}