icu_datetime/provider/fields/symbols.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
5use crate::options::SubsecondDigits;
6#[cfg(feature = "datagen")]
7use crate::provider::fields::FieldLength;
8use core::{cmp::Ordering, convert::TryFrom};
9use displaydoc::Display;
10use icu_locale_core::preferences::extensions::unicode::keywords::HourCycle;
11use icu_provider::prelude::*;
12use zerovec::ule::{AsULE, UleError, ULE};
13
14/// An error relating to the field symbol for a date pattern field.
15#[derive(Display, Debug, PartialEq, Copy, Clone)]
16#[non_exhaustive]
17pub enum SymbolError {
18 /// Invalid field symbol index.
19 #[displaydoc("Invalid field symbol index: {0}")]
20 InvalidIndex(u8),
21 /// Unknown field symbol.
22 #[displaydoc("Unknown field symbol: {0}")]
23 Unknown(char),
24 /// Invalid character for a field symbol.
25 #[displaydoc("Invalid character for a field symbol: {0}")]
26 Invalid(u8),
27}
28
29impl core::error::Error for SymbolError {}
30
31/// A field symbol for a date formatting pattern.
32///
33/// Field symbols are a more granular distinction
34/// for a pattern field within the category of a field type. Examples of field types are:
35/// `Year`, `Month`, `Hour`. Within the [`Hour`] field type, examples of field symbols are: [`Hour::H12`],
36/// [`Hour::H23`]. Each field symbol is represented within the date formatting pattern string
37/// by a distinct character from the set of `A..Z` and `a..z`.
38#[derive(Debug, Eq, PartialEq, Clone, Copy)]
39#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
40#[cfg_attr(feature = "datagen", databake(path = icu_datetime::fields))]
41#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
42#[allow(clippy::exhaustive_enums)] // part of data struct
43pub enum FieldSymbol {
44 /// Era name.
45 Era,
46 /// Year number or year name.
47 Year(Year),
48 /// Month number or month name.
49 Month(Month),
50 /// Week number or week name.
51 Week(Week),
52 /// Day number relative to a time period longer than a week (ex: month, year).
53 Day(Day),
54 /// Day number or day name relative to a week.
55 Weekday(Weekday),
56 /// Name of a period within a day.
57 DayPeriod(DayPeriod),
58 /// Hour number within a day, possibly with day period.
59 Hour(Hour),
60 /// Minute number within an hour.
61 Minute,
62 /// Seconds integer within a minute or milliseconds within a day.
63 Second(Second),
64 /// Time zone as a name, a zone ID, or a ISO 8601 numerical offset.
65 TimeZone(TimeZone),
66 /// Seconds with fractional digits. If seconds are an integer,
67 /// [`FieldSymbol::Second`] is used.
68 DecimalSecond(DecimalSecond),
69}
70
71impl FieldSymbol {
72 /// Symbols are necessary components of `Pattern` struct which
73 /// uses efficient byte serialization and deserialization via `zerovec`.
74 ///
75 /// The `FieldSymbol` impl provides non-public methods that can be used to efficiently
76 /// convert between `u8` and the symbol variant.
77 ///
78 /// The serialization model packages the variant in one byte.
79 ///
80 /// 1) The top four bits are used to determine the type of the field
81 /// using that type's `idx()/from_idx()` for the mapping.
82 /// (Examples: `Year`, `Month`, `Hour`)
83 ///
84 /// 2) The bottom four bits are used to determine the symbol of the type.
85 /// (Examples: `Year::Calendar`, `Hour::H11`)
86 ///
87 /// # Diagram
88 ///
89 /// ```text
90 /// ┌─┬─┬─┬─┬─┬─┬─┬─┐
91 /// ├─┴─┴─┴─┼─┴─┴─┴─┤
92 /// │ Type │Symbol │
93 /// └───────┴───────┘
94 /// ```
95 ///
96 /// # Optimization
97 ///
98 /// This model is optimized to package data efficiently when `FieldSymbol`
99 /// is used as a variant of `PatternItem`. See the documentation of `PatternItemULE`
100 /// for details on how it is composed.
101 ///
102 /// # Constraints
103 ///
104 /// This model limits the available number of possible types and symbols to 16 each.
105 #[inline]
106 pub(crate) fn idx(self) -> u8 {
107 let (high, low) = match self {
108 FieldSymbol::Era => (0, 0),
109 FieldSymbol::Year(year) => (1, year.idx()),
110 FieldSymbol::Month(month) => (2, month.idx()),
111 FieldSymbol::Week(w) => (3, w.idx()),
112 FieldSymbol::Day(day) => (4, day.idx()),
113 FieldSymbol::Weekday(wd) => (5, wd.idx()),
114 FieldSymbol::DayPeriod(dp) => (6, dp.idx()),
115 FieldSymbol::Hour(hour) => (7, hour.idx()),
116 FieldSymbol::Minute => (8, 0),
117 FieldSymbol::Second(second) => (9, second.idx()),
118 FieldSymbol::TimeZone(tz) => (10, tz.idx()),
119 FieldSymbol::DecimalSecond(second) => (11, second.idx()),
120 };
121 let result = high << 4;
122 result | low
123 }
124
125 #[inline]
126 pub(crate) fn from_idx(idx: u8) -> Result<Self, SymbolError> {
127 // extract the top four bits to determine the symbol.
128 let low = idx & 0b0000_1111;
129 // use the bottom four bits out of `u8` to disriminate the field type.
130 let high = idx >> 4;
131
132 Ok(match high {
133 0 if low == 0 => Self::Era,
134 1 => Self::Year(Year::from_idx(low)?),
135 2 => Self::Month(Month::from_idx(low)?),
136 3 => Self::Week(Week::from_idx(low)?),
137 4 => Self::Day(Day::from_idx(low)?),
138 5 => Self::Weekday(Weekday::from_idx(low)?),
139 6 => Self::DayPeriod(DayPeriod::from_idx(low)?),
140 7 => Self::Hour(Hour::from_idx(low)?),
141 8 if low == 0 => Self::Minute,
142 9 => Self::Second(Second::from_idx(low)?),
143 10 => Self::TimeZone(TimeZone::from_idx(low)?),
144 11 => Self::DecimalSecond(DecimalSecond::from_idx(low)?),
145 _ => return Err(SymbolError::InvalidIndex(idx)),
146 })
147 }
148
149 /// Returns the index associated with this FieldSymbol.
150 #[cfg(feature = "datagen")]
151 fn idx_for_skeleton(self) -> u8 {
152 match self {
153 FieldSymbol::Era => 0,
154 FieldSymbol::Year(_) => 1,
155 FieldSymbol::Month(_) => 2,
156 FieldSymbol::Week(_) => 3,
157 FieldSymbol::Day(_) => 4,
158 FieldSymbol::Weekday(_) => 5,
159 FieldSymbol::DayPeriod(_) => 6,
160 FieldSymbol::Hour(_) => 7,
161 FieldSymbol::Minute => 8,
162 FieldSymbol::Second(_) | FieldSymbol::DecimalSecond(_) => 9,
163 FieldSymbol::TimeZone(_) => 10,
164 }
165 }
166
167 /// Compares this enum with other solely based on the enum variant,
168 /// ignoring the enum's data.
169 ///
170 /// Second and DecimalSecond are considered equal.
171 #[cfg(feature = "datagen")]
172 pub(crate) fn skeleton_cmp(self, other: Self) -> Ordering {
173 self.idx_for_skeleton().cmp(&other.idx_for_skeleton())
174 }
175
176 pub(crate) fn from_subsecond_digits(subsecond_digits: SubsecondDigits) -> Self {
177 use SubsecondDigits::*;
178 match subsecond_digits {
179 S1 => FieldSymbol::DecimalSecond(DecimalSecond::Subsecond1),
180 S2 => FieldSymbol::DecimalSecond(DecimalSecond::Subsecond2),
181 S3 => FieldSymbol::DecimalSecond(DecimalSecond::Subsecond3),
182 S4 => FieldSymbol::DecimalSecond(DecimalSecond::Subsecond4),
183 S5 => FieldSymbol::DecimalSecond(DecimalSecond::Subsecond5),
184 S6 => FieldSymbol::DecimalSecond(DecimalSecond::Subsecond6),
185 S7 => FieldSymbol::DecimalSecond(DecimalSecond::Subsecond7),
186 S8 => FieldSymbol::DecimalSecond(DecimalSecond::Subsecond8),
187 S9 => FieldSymbol::DecimalSecond(DecimalSecond::Subsecond9),
188 }
189 }
190
191 /// UTS 35 defines several 1 and 2 symbols to be the same as 3 symbols (abbreviated).
192 /// For example, 'a' represents an abbreviated day period, the same as 'aaa'.
193 ///
194 /// This function maps field lengths 1 and 2 to field length 3.
195 #[cfg(feature = "datagen")]
196 pub(crate) fn is_at_least_abbreviated(self) -> bool {
197 matches!(
198 self,
199 FieldSymbol::Era
200 | FieldSymbol::Year(Year::Cyclic)
201 | FieldSymbol::Weekday(Weekday::Format)
202 | FieldSymbol::DayPeriod(_)
203 | FieldSymbol::TimeZone(TimeZone::SpecificNonLocation)
204 )
205 }
206}
207
208/// [`ULE`](zerovec::ule::ULE) type for [`FieldSymbol`]
209#[repr(transparent)]
210#[derive(Debug, Copy, Clone, PartialEq, Eq)]
211pub struct FieldSymbolULE(u8);
212
213impl AsULE for FieldSymbol {
214 type ULE = FieldSymbolULE;
215 fn to_unaligned(self) -> Self::ULE {
216 FieldSymbolULE(self.idx())
217 }
218 fn from_unaligned(unaligned: Self::ULE) -> Self {
219 #[allow(clippy::unwrap_used)] // OK because the ULE is pre-validated
220 Self::from_idx(unaligned.0).unwrap()
221 }
222}
223
224impl FieldSymbolULE {
225 #[inline]
226 pub(crate) fn validate_byte(byte: u8) -> Result<(), UleError> {
227 FieldSymbol::from_idx(byte)
228 .map(|_| ())
229 .map_err(|_| UleError::parse::<FieldSymbol>())
230 }
231}
232
233// Safety checklist for ULE:
234//
235// 1. Must not include any uninitialized or padding bytes (true since transparent over a ULE).
236// 2. Must have an alignment of 1 byte (true since transparent over a ULE).
237// 3. ULE::validate_bytes() checks that the given byte slice represents a valid slice.
238// 4. ULE::validate_bytes() checks that the given byte slice has a valid length
239// (true since transparent over a type of size 1).
240// 5. All other methods must be left with their default impl.
241// 6. Byte equality is semantic equality.
242unsafe impl ULE for FieldSymbolULE {
243 fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
244 for byte in bytes {
245 Self::validate_byte(*byte)?;
246 }
247 Ok(())
248 }
249}
250
251#[derive(Debug, Eq, PartialEq, Clone, Copy)]
252#[allow(clippy::exhaustive_enums)] // used in data struct
253#[cfg(feature = "datagen")]
254pub(crate) enum TextOrNumeric {
255 Text,
256 Numeric,
257}
258
259/// [`FieldSymbols`](FieldSymbol) can be either text or numeric. This categorization is important
260/// when matching skeletons with a components [`Bag`](crate::options::components::Bag).
261#[cfg(feature = "datagen")]
262pub(crate) trait LengthType {
263 fn get_length_type(self, length: FieldLength) -> TextOrNumeric;
264}
265
266impl FieldSymbol {
267 /// Skeletons are a Vec<Field>, and represent the Fields that can be used to match to a
268 /// specific pattern. The order of the Vec does not affect the Pattern that is output.
269 /// However, it's more performant when matching these fields, and it's more deterministic
270 /// when serializing them to present them in a consistent order.
271 ///
272 /// This ordering is taken by the order of the fields listed in the [UTS 35 Date Field Symbol Table]
273 /// (https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table), and are generally
274 /// ordered most significant to least significant.
275 fn get_canonical_order(self) -> u8 {
276 match self {
277 Self::Era => 0,
278 Self::Year(Year::Calendar) => 1,
279 // Self::Year(Year::WeekOf) => 2,
280 Self::Year(Year::Cyclic) => 3,
281 Self::Year(Year::RelatedIso) => 4,
282 Self::Month(Month::Format) => 5,
283 Self::Month(Month::StandAlone) => 6,
284 // TODO(#5643): Add week fields back
285 // Self::Week(Week::WeekOfYear) => 7,
286 // Self::Week(Week::WeekOfMonth) => 8,
287 Self::Week(_) => unreachable!(), // ZST references aren't uninhabited
288 Self::Day(Day::DayOfMonth) => 9,
289 Self::Day(Day::DayOfYear) => 10,
290 Self::Day(Day::DayOfWeekInMonth) => 11,
291 // Self::Day(Day::ModifiedJulianDay) => 12,
292 Self::Weekday(Weekday::Format) => 13,
293 Self::Weekday(Weekday::Local) => 14,
294 Self::Weekday(Weekday::StandAlone) => 15,
295 Self::DayPeriod(DayPeriod::AmPm) => 16,
296 Self::DayPeriod(DayPeriod::NoonMidnight) => 17,
297 Self::Hour(Hour::H11) => 18,
298 Self::Hour(Hour::H12) => 19,
299 Self::Hour(Hour::H23) => 20,
300 Self::Minute => 22,
301 Self::Second(Second::Second) => 23,
302 Self::Second(Second::MillisInDay) => 24,
303 Self::DecimalSecond(DecimalSecond::Subsecond1) => 31,
304 Self::DecimalSecond(DecimalSecond::Subsecond2) => 32,
305 Self::DecimalSecond(DecimalSecond::Subsecond3) => 33,
306 Self::DecimalSecond(DecimalSecond::Subsecond4) => 34,
307 Self::DecimalSecond(DecimalSecond::Subsecond5) => 35,
308 Self::DecimalSecond(DecimalSecond::Subsecond6) => 36,
309 Self::DecimalSecond(DecimalSecond::Subsecond7) => 37,
310 Self::DecimalSecond(DecimalSecond::Subsecond8) => 38,
311 Self::DecimalSecond(DecimalSecond::Subsecond9) => 39,
312 Self::TimeZone(TimeZone::SpecificNonLocation) => 100,
313 Self::TimeZone(TimeZone::LocalizedOffset) => 102,
314 Self::TimeZone(TimeZone::GenericNonLocation) => 103,
315 Self::TimeZone(TimeZone::Location) => 104,
316 Self::TimeZone(TimeZone::Iso) => 105,
317 Self::TimeZone(TimeZone::IsoWithZ) => 106,
318 }
319 }
320}
321
322impl TryFrom<char> for FieldSymbol {
323 type Error = SymbolError;
324 fn try_from(ch: char) -> Result<Self, Self::Error> {
325 if !ch.is_ascii_alphanumeric() {
326 return Err(SymbolError::Invalid(ch as u8));
327 }
328
329 (if ch == 'G' {
330 Ok(Self::Era)
331 } else {
332 Err(SymbolError::Unknown(ch))
333 })
334 .or_else(|_| Year::try_from(ch).map(Self::Year))
335 .or_else(|_| Month::try_from(ch).map(Self::Month))
336 .or_else(|_| Week::try_from(ch).map(Self::Week))
337 .or_else(|_| Day::try_from(ch).map(Self::Day))
338 .or_else(|_| Weekday::try_from(ch).map(Self::Weekday))
339 .or_else(|_| DayPeriod::try_from(ch).map(Self::DayPeriod))
340 .or_else(|_| Hour::try_from(ch).map(Self::Hour))
341 .or({
342 if ch == 'm' {
343 Ok(Self::Minute)
344 } else {
345 Err(SymbolError::Unknown(ch))
346 }
347 })
348 .or_else(|_| Second::try_from(ch).map(Self::Second))
349 .or_else(|_| TimeZone::try_from(ch).map(Self::TimeZone))
350 // Note: char-to-enum conversion for DecimalSecond is handled directly in the parser
351 }
352}
353
354impl From<FieldSymbol> for char {
355 fn from(symbol: FieldSymbol) -> Self {
356 match symbol {
357 FieldSymbol::Era => 'G',
358 FieldSymbol::Year(year) => year.into(),
359 FieldSymbol::Month(month) => month.into(),
360 FieldSymbol::Week(week) => week.into(),
361 FieldSymbol::Day(day) => day.into(),
362 FieldSymbol::Weekday(weekday) => weekday.into(),
363 FieldSymbol::DayPeriod(dayperiod) => dayperiod.into(),
364 FieldSymbol::Hour(hour) => hour.into(),
365 FieldSymbol::Minute => 'm',
366 FieldSymbol::Second(second) => second.into(),
367 FieldSymbol::TimeZone(time_zone) => time_zone.into(),
368 // Note: This is only used for representing the integer portion
369 FieldSymbol::DecimalSecond(_) => 's',
370 }
371 }
372}
373
374impl PartialOrd for FieldSymbol {
375 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
376 Some(self.cmp(other))
377 }
378}
379
380impl Ord for FieldSymbol {
381 fn cmp(&self, other: &Self) -> Ordering {
382 self.get_canonical_order().cmp(&other.get_canonical_order())
383 }
384}
385
386macro_rules! field_type {
387 ($(#[$enum_attr:meta])* $i:ident; { $( $(#[$variant_attr:meta])* $key:literal => $val:ident = $idx:expr,)* }; $length_type:ident; $($ule_name:ident)?) => (
388 field_type!($(#[$enum_attr])* $i; {$( $(#[$variant_attr])* $key => $val = $idx,)*}; $($ule_name)?);
389
390 #[cfg(feature = "datagen")]
391 impl LengthType for $i {
392 fn get_length_type(self, _length: FieldLength) -> TextOrNumeric {
393 TextOrNumeric::$length_type
394 }
395 }
396 );
397 ($(#[$enum_attr:meta])* $i:ident; { $( $(#[$variant_attr:meta])* $key:literal => $val:ident = $idx:expr,)* }; $($ule_name:ident)?) => (
398 #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, yoke::Yokeable, zerofrom::ZeroFrom)]
399 // TODO(#1044): This should be replaced with a custom derive.
400 #[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
401 #[cfg_attr(feature = "datagen", databake(path = icu_datetime::fields))]
402 #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
403 #[allow(clippy::enum_variant_names)]
404 $(
405 #[repr(u8)]
406 #[zerovec::make_ule($ule_name)]
407 #[zerovec::derive(Debug)]
408 )?
409 #[allow(clippy::exhaustive_enums)] // used in data struct
410 $(#[$enum_attr])*
411 pub enum $i {
412 $(
413 $(#[$variant_attr])*
414 #[doc = core::concat!("\n\nThis field symbol is represented by the character `", $key, "` in a date formatting pattern string.")]
415 #[doc = "\n\nFor more details, see documentation on [date field symbols](https://unicode.org/reports/tr35/tr35-dates.html#table-date-field-symbol-table)."]
416 $val = $idx,
417 )*
418 }
419
420 $(
421 #[allow(path_statements)] // #5643 impl conditional on $ule_name
422 const _: () = { $ule_name; };
423
424 impl $i {
425 /// Retrieves an index of the field variant.
426 ///
427 /// # Examples
428 ///
429 /// ```ignore
430 /// use icu::datetime::fields::Month;
431 ///
432 /// assert_eq!(Month::StandAlone::idx(), 1);
433 /// ```
434 ///
435 /// # Stability
436 ///
437 /// This is mostly useful for serialization,
438 /// and does not guarantee index stability between ICU4X
439 /// versions.
440 #[inline]
441 pub(crate) fn idx(self) -> u8 {
442 self as u8
443 }
444
445 /// Retrieves a field variant from an index.
446 ///
447 /// # Examples
448 ///
449 /// ```ignore
450 /// use icu::datetime::fields::Month;
451 ///
452 /// assert_eq!(Month::from_idx(0), Month::Format);
453 /// ```
454 ///
455 /// # Stability
456 ///
457 /// This is mostly useful for serialization,
458 /// and does not guarantee index stability between ICU4X
459 /// versions.
460 #[inline]
461 pub(crate) fn from_idx(idx: u8) -> Result<Self, SymbolError> {
462 Self::new_from_u8(idx)
463 .ok_or(SymbolError::InvalidIndex(idx))
464 }
465 }
466 )?
467
468 impl TryFrom<char> for $i {
469 type Error = SymbolError;
470
471 fn try_from(ch: char) -> Result<Self, Self::Error> {
472 match ch {
473 $(
474 $key => Ok(Self::$val),
475 )*
476 _ => Err(SymbolError::Unknown(ch)),
477 }
478 }
479 }
480
481 impl From<$i> for FieldSymbol {
482 fn from(input: $i) -> Self {
483 Self::$i(input)
484 }
485 }
486
487 impl From<$i> for char {
488 fn from(input: $i) -> char {
489 match input {
490 $(
491 $i::$val => $key,
492 )*
493 }
494 }
495 }
496 );
497}
498
499field_type! (
500 /// An enum for the possible symbols of a year field in a date pattern.
501 Year; {
502 /// Field symbol for calendar year (numeric).
503 ///
504 /// In most cases the length of this field specifies the minimum number of digits to display, zero-padded as necessary. For most use cases, [`Year::Calendar`] or `Year::WeekOf` should be adequate.
505 'y' => Calendar = 0,
506 /// Field symbol for cyclic year; used in calendars where years are tracked in cycles, such as the Chinese or Dangi calendars.
507 'U' => Cyclic = 1,
508 /// Field symbol for related ISO; some calendars which use different year numbering than ISO, or no year numbering, may express years in an ISO year corresponding to a calendar year.
509 'r' => RelatedIso = 2,
510 // /// Field symbol for year in "week of year".
511 // ///
512 // /// This works for “week of year” based calendars in which the year transition occurs on a week boundary; may differ from calendar year [`Year::Calendar`] near a year transition. This numeric year designation is used in conjunction with [`Week::WeekOfYear`], but can be used in non-Gregorian based calendar systems where week date processing is desired. The field length is interpreted in the same way as for [`Year::Calendar`].
513 // 'Y' => WeekOf = 3,
514 };
515 YearULE
516);
517
518#[cfg(feature = "datagen")]
519impl LengthType for Year {
520 fn get_length_type(self, _length: FieldLength) -> TextOrNumeric {
521 // https://unicode.org/reports/tr35/tr35-dates.html#dfst-year
522 match self {
523 Year::Cyclic => TextOrNumeric::Text,
524 _ => TextOrNumeric::Numeric,
525 }
526 }
527}
528
529field_type!(
530 /// An enum for the possible symbols of a month field in a date pattern.
531 Month; {
532 /// Field symbol for month number or name in a pattern that contains multiple fields.
533 'M' => Format = 0,
534 /// Field symbol for a "stand-alone" month number or name.
535 ///
536 /// The stand-alone month name is used when the month is displayed by itself. This may differ from the standard form based on the language and context.
537 'L' => StandAlone = 1,
538}; MonthULE);
539
540#[cfg(feature = "datagen")]
541impl LengthType for Month {
542 fn get_length_type(self, length: FieldLength) -> TextOrNumeric {
543 match length {
544 FieldLength::One => TextOrNumeric::Numeric,
545 FieldLength::NumericOverride(_) => TextOrNumeric::Numeric,
546 FieldLength::Two => TextOrNumeric::Numeric,
547 FieldLength::Three => TextOrNumeric::Text,
548 FieldLength::Four => TextOrNumeric::Text,
549 FieldLength::Five => TextOrNumeric::Text,
550 FieldLength::Six => TextOrNumeric::Text,
551 }
552 }
553}
554
555field_type!(
556 /// An enum for the possible symbols of a day field in a date pattern.
557 Day; {
558 /// Field symbol for day of month (numeric).
559 'd' => DayOfMonth = 0,
560 /// Field symbol for day of year (numeric).
561 'D' => DayOfYear = 1,
562 /// Field symbol for the day of week occurrence relative to the month (numeric).
563 ///
564 /// For the example `"2nd Wed in July"`, this field would provide `"2"`. Should likely be paired with the [`Weekday`] field.
565 'F' => DayOfWeekInMonth = 2,
566 // /// Field symbol for the modified Julian day (numeric).
567 // ///
568 // /// The value of this field differs from the conventional Julian day number in a couple of ways, which are based on measuring relative to the local time zone.
569 // 'g' => ModifiedJulianDay = 3,
570 };
571 Numeric;
572 DayULE
573);
574
575field_type!(
576 /// An enum for the possible symbols of an hour field in a date pattern.
577 Hour; {
578 /// Field symbol for numeric hour [0-11].
579 'K' => H11 = 0,
580 /// Field symbol for numeric hour [1-12].
581 'h' => H12 = 1,
582 /// Field symbol for numeric hour [0-23].
583 'H' => H23 = 2,
584 };
585 Numeric;
586 HourULE
587);
588
589impl Hour {
590 pub(crate) fn from_hour_cycle(hour_cycle: HourCycle) -> Self {
591 match hour_cycle {
592 HourCycle::H11 => Self::H11,
593 HourCycle::H12 => Self::H12,
594 HourCycle::H23 => Self::H23,
595 _ => unreachable!(),
596 }
597 }
598}
599
600// NOTE: 'S' Subsecond is represented via DecimalSecond,
601// so it is not included in the Second enum.
602
603field_type!(
604 /// An enum for the possible symbols of a second field in a date pattern.
605 Second; {
606 /// Field symbol for second (numeric).
607 's' => Second = 0,
608 /// Field symbol for milliseconds in day (numeric).
609 ///
610 /// This field behaves exactly like a composite of all time-related fields, not including the zone fields.
611 'A' => MillisInDay = 1,
612 };
613 Numeric;
614 SecondULE
615);
616
617field_type!(
618 /// An enum for the possible symbols of a week field in a date pattern.
619 Week; {
620 // /// Field symbol for week of year (numeric).
621 // ///
622 // /// When used in a pattern with year, use [`Year::WeekOf`] for the year field instead of [`Year::Calendar`].
623 // 'w' => WeekOfYear = 0,
624 // /// Field symbol for week of month (numeric).
625 // 'W' => WeekOfMonth = 1,
626 };
627 Numeric;
628 // TODO(#5643): Recover ULE once the type is inhabited
629 // WeekULE
630);
631
632impl Week {
633 /// Retrieves an index of the field variant.
634 ///
635 /// # Examples
636 ///
637 /// ```ignore
638 /// use icu::datetime::fields::Month;
639 ///
640 /// assert_eq!(Month::StandAlone::idx(), 1);
641 /// ```
642 ///
643 /// # Stability
644 ///
645 /// This is mostly useful for serialization,
646 /// and does not guarantee index stability between ICU4X
647 /// versions.
648 #[inline]
649 pub(crate) fn idx(self) -> u8 {
650 0
651 }
652
653 /// Retrieves a field variant from an index.
654 ///
655 /// # Examples
656 ///
657 /// ```ignore
658 /// use icu::datetime::fields::Month;
659 ///
660 /// assert_eq!(Month::from_idx(0), Month::Format);
661 /// ```
662 ///
663 /// # Stability
664 ///
665 /// This is mostly useful for serialization,
666 /// and does not guarantee index stability between ICU4X
667 /// versions.
668 #[inline]
669 pub(crate) fn from_idx(idx: u8) -> Result<Self, SymbolError> {
670 Err(SymbolError::InvalidIndex(idx))
671 }
672}
673
674field_type!(
675 /// An enum for the possible symbols of a weekday field in a date pattern.
676 Weekday; {
677 /// Field symbol for day of week (text format only).
678 'E' => Format = 0,
679 /// Field symbol for day of week; numeric formats produce a locale-dependent ordinal weekday number.
680 ///
681 /// For example, in de-DE, Monday is the 1st day of the week.
682 'e' => Local = 1,
683 /// Field symbol for stand-alone local day of week number/name.
684 ///
685 /// The stand-alone weekday name is used when the weekday is displayed by itself. This may differ from the standard form based on the language and context.
686 'c' => StandAlone = 2,
687 };
688 WeekdayULE
689);
690
691#[cfg(feature = "datagen")]
692impl LengthType for Weekday {
693 fn get_length_type(self, length: FieldLength) -> TextOrNumeric {
694 match self {
695 Self::Format => TextOrNumeric::Text,
696 Self::Local | Self::StandAlone => match length {
697 FieldLength::One | FieldLength::Two => TextOrNumeric::Numeric,
698 _ => TextOrNumeric::Text,
699 },
700 }
701 }
702}
703
704impl Weekday {
705 /// UTS 35 says that "e" (local weekday) and "E" (format weekday) have the same non-numeric names.
706 ///
707 /// This function normalizes "e" to "E".
708 pub(crate) fn to_format_symbol(self) -> Self {
709 match self {
710 Weekday::Local => Weekday::Format,
711 other => other,
712 }
713 }
714}
715
716field_type!(
717 /// An enum for the possible symbols of a day period field in a date pattern.
718 DayPeriod; {
719 /// Field symbol for the AM, PM day period. (Does not include noon, midnight.)
720 'a' => AmPm = 0,
721 /// Field symbol for the am, pm, noon, midnight day period.
722 'b' => NoonMidnight = 1,
723 };
724 Text;
725 DayPeriodULE
726);
727
728field_type!(
729 /// An enum for the possible symbols of a time zone field in a date pattern.
730 TimeZone; {
731 /// Field symbol for the specific non-location format of a time zone.
732 ///
733 /// For example: "Pacific Standard Time"
734 'z' => SpecificNonLocation = 0,
735 /// Field symbol for the localized offset format of a time zone.
736 ///
737 /// For example: "GMT-07:00"
738 'O' => LocalizedOffset = 1,
739 /// Field symbol for the generic non-location format of a time zone.
740 ///
741 /// For example: "Pacific Time"
742 'v' => GenericNonLocation = 2,
743 /// Field symbol for any of: the time zone id, time zone exemplar city, or generic location format.
744 'V' => Location = 3,
745 /// Field symbol for either the ISO-8601 basic format or ISO-8601 extended format. This does not use an
746 /// optional ISO-8601 UTC indicator `Z`, whereas [`TimeZone::IsoWithZ`] produces `Z`.
747 'x' => Iso = 4,
748 /// Field symbol for either the ISO-8601 basic format or ISO-8601 extended format, with the ISO-8601 UTC indicator `Z`.
749 'X' => IsoWithZ = 5,
750 };
751 TimeZoneULE
752);
753
754#[cfg(feature = "datagen")]
755impl LengthType for TimeZone {
756 fn get_length_type(self, _: FieldLength) -> TextOrNumeric {
757 use TextOrNumeric::*;
758 match self {
759 Self::Iso | Self::IsoWithZ => Numeric,
760 Self::LocalizedOffset
761 | Self::SpecificNonLocation
762 | Self::GenericNonLocation
763 | Self::Location => Text,
764 }
765 }
766}
767
768/// A second field with fractional digits.
769#[derive(
770 Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, yoke::Yokeable, zerofrom::ZeroFrom,
771)]
772#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
773#[cfg_attr(feature = "datagen", databake(path = icu_datetime::fields))]
774#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
775#[allow(clippy::enum_variant_names)]
776#[repr(u8)]
777#[zerovec::make_ule(DecimalSecondULE)]
778#[zerovec::derive(Debug)]
779#[allow(clippy::exhaustive_enums)] // used in data struct
780pub enum DecimalSecond {
781 /// A second with 1 fractional digit: "1.0"
782 Subsecond1 = 1,
783 /// A second with 2 fractional digits: "1.00"
784 Subsecond2 = 2,
785 /// A second with 3 fractional digits: "1.000"
786 Subsecond3 = 3,
787 /// A second with 4 fractional digits: "1.0000"
788 Subsecond4 = 4,
789 /// A second with 5 fractional digits: "1.00000"
790 Subsecond5 = 5,
791 /// A second with 6 fractional digits: "1.000000"
792 Subsecond6 = 6,
793 /// A second with 7 fractional digits: "1.0000000"
794 Subsecond7 = 7,
795 /// A second with 8 fractional digits: "1.00000000"
796 Subsecond8 = 8,
797 /// A second with 9 fractional digits: "1.000000000"
798 Subsecond9 = 9,
799}
800
801impl DecimalSecond {
802 #[inline]
803 pub(crate) fn idx(self) -> u8 {
804 self as u8
805 }
806 #[inline]
807 pub(crate) fn from_idx(idx: u8) -> Result<Self, SymbolError> {
808 Self::new_from_u8(idx).ok_or(SymbolError::InvalidIndex(idx))
809 }
810}
811impl From<DecimalSecond> for FieldSymbol {
812 fn from(input: DecimalSecond) -> Self {
813 Self::DecimalSecond(input)
814 }
815}