icu_datetime/provider/fields/components.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//! 🚧 \[Experimental\] Types for specifying fields in a classical datetime skeleton.
6//!
7//! <div class="stab unstable">
8//! 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
9//! including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature
10//! of the icu meta-crate. Use with caution.
11//! <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
12//! </div>
13//!
14//! # Examples
15//!
16//! ```
17//! use icu::datetime::provider::fields::components;
18//!
19//! let mut bag = components::Bag::default();
20//! bag.year = Some(components::Year::Numeric);
21//! bag.month = Some(components::Month::Long);
22//! bag.day = Some(components::Day::NumericDayOfMonth);
23//!
24//! bag.hour = Some(components::Numeric::TwoDigit);
25//! bag.minute = Some(components::Numeric::TwoDigit);
26//! ```
27//!
28//! *Note*: The exact formatted result is a subject to change over
29//! time. Formatted result should be treated as opaque and displayed to the user as-is,
30//! and it is strongly recommended to never write tests that expect a particular formatted output.
31
32use crate::{
33 options::SubsecondDigits,
34 provider::fields::{self, Field, FieldLength, FieldSymbol},
35 provider::pattern::{reference, runtime::Pattern, PatternItem},
36};
37
38use crate::pattern::DateTimePattern;
39use icu_locale_core::preferences::extensions::unicode::keywords::HourCycle;
40#[cfg(feature = "serde")]
41use serde::{Deserialize, Serialize};
42
43/// See the [module-level](./index.html) docs for more information.
44///
45/// <div class="stab unstable">
46/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
47/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature
48/// of the icu meta-crate. Use with caution.
49/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
50/// </div>
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
52#[non_exhaustive]
53pub struct Bag {
54 /// Include the era, such as "AD" or "CE".
55 pub era: Option<Text>,
56 /// Include the year, such as "1970" or "70".
57 pub year: Option<Year>,
58 /// Include the month, such as "April" or "Apr".
59 pub month: Option<Month>,
60 /// Include the week number, such as "51st" or "51" for week 51.
61 pub week: Option<Week>,
62 /// Include the day of the month/year, such as "07" or "7".
63 pub day: Option<Day>,
64 /// Include the weekday, such as "Wednesday" or "Wed".
65 pub weekday: Option<Text>,
66
67 /// Include the hour such as "2" or "14".
68 pub hour: Option<Numeric>,
69 /// Include the minute such as "3" or "03".
70 pub minute: Option<Numeric>,
71 /// Include the second such as "3" or "03".
72 pub second: Option<Numeric>,
73 /// Specify the number of fractional second digits such as 1 (".3") or 3 (".003").
74 pub subsecond: Option<SubsecondDigits>,
75
76 /// Include the time zone, such as "GMT+05:00".
77 pub time_zone_name: Option<TimeZoneName>,
78
79 /// An override of the hour cycle.
80 pub hour_cycle: Option<HourCycle>,
81}
82
83impl Bag {
84 /// Creates an empty components bag
85 ///
86 /// Has the same behavior as the [`Default`] implementation on this type.
87 pub fn empty() -> Self {
88 Self::default()
89 }
90
91 /// Merges the fields of other into self if non-None. If both fields are set, `other` is kept.
92 pub fn merge(self, other: Self) -> Self {
93 Self {
94 era: other.era.or(self.era),
95 year: other.year.or(self.year),
96 month: other.month.or(self.month),
97 week: other.week.or(self.week),
98 day: other.day.or(self.day),
99 weekday: other.weekday.or(self.weekday),
100 hour: other.hour.or(self.hour),
101 minute: other.minute.or(self.minute),
102 second: other.second.or(self.second),
103 subsecond: other.subsecond.or(self.subsecond),
104 time_zone_name: other.time_zone_name.or(self.time_zone_name),
105 hour_cycle: other.hour_cycle.or(self.hour_cycle),
106 }
107 }
108
109 /// Converts the components::Bag into a `Vec<Field>`. The fields will be ordered in from most
110 /// significant field to least significant. This is the order the fields are listed in
111 /// the UTS 35 table - <https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table>
112 ///
113 /// Arguments:
114 ///
115 /// - `default_hour_cycle` specifies the hour cycle to use for the hour field if not in the Bag.
116 /// `preferences::Bag::hour_cycle` takes precedence over this argument.
117 pub fn to_vec_fields(&self, default_hour_cycle: HourCycle) -> alloc::vec::Vec<Field> {
118 let mut fields = alloc::vec::Vec::new();
119 if let Some(era) = self.era {
120 fields.push(Field {
121 symbol: FieldSymbol::Era,
122 length: match era {
123 // Era name, format length.
124 //
125 // G..GGG AD Abbreviated
126 // GGGG Anno Domini Wide
127 // GGGGG A Narrow
128 Text::Short => FieldLength::Three,
129 Text::Long => FieldLength::Four,
130 Text::Narrow => FieldLength::Five,
131 },
132 })
133 }
134
135 if let Some(year) = self.year {
136 // Unimplemented year fields:
137 // u - Extended year
138 // U - Cyclic year name
139 // r - Related Gregorian year
140 fields.push(Field {
141 symbol: FieldSymbol::Year(match year {
142 Year::Numeric | Year::TwoDigit => fields::Year::Calendar,
143 Year::NumericWeekOf | Year::TwoDigitWeekOf => {
144 unimplemented!("#5643 fields::Year::WeekOf")
145 }
146 }),
147 length: match year {
148 // Calendar year (numeric).
149 // y 2, 20, 201, 2017, 20173
150 // yy 02, 20, 01, 17, 73
151 // yyy 002, 020, 201, 2017, 20173 (not implemented)
152 // yyyy 0002, 0020, 0201, 2017, 20173 (not implemented)
153 // yyyyy+ ... (not implemented)
154 Year::Numeric | Year::NumericWeekOf => FieldLength::One,
155 Year::TwoDigit | Year::TwoDigitWeekOf => FieldLength::Two,
156 },
157 });
158 }
159
160 // TODO(#501) - Unimplemented quarter fields:
161 // Q - Quarter number/name
162 // q - Stand-alone quarter
163
164 if let Some(month) = self.month {
165 fields.push(Field {
166 // Always choose Month::Format as Month::StandAlone is not used in skeletons.
167 symbol: FieldSymbol::Month(fields::Month::Format),
168 length: match month {
169 // (intended to be used in conjunction with ‘d’ for day number).
170 // M 9, 12 Numeric: minimum digits
171 // MM 09, 12 Numeric: 2 digits, zero pad if needed
172 // MMM Sep Abbreviated
173 // MMMM September Wide
174 // MMMMM S Narrow
175 Month::Numeric => FieldLength::One,
176 Month::TwoDigit => FieldLength::Two,
177 Month::Long => FieldLength::Four,
178 Month::Short => FieldLength::Three,
179 Month::Narrow => FieldLength::Five,
180 },
181 });
182 }
183
184 if let Some(week) = self.week {
185 #[allow(
186 unreachable_code,
187 reason = "Uninhabited MultipleVariants (due to pivot_field), see #7118"
188 )]
189 fields.push(Field {
190 symbol: FieldSymbol::Week(match week {
191 Week::WeekOfMonth => unimplemented!("#5643 fields::Week::WeekOfMonth"),
192 Week::NumericWeekOfYear | Week::TwoDigitWeekOfYear => {
193 unimplemented!("#5643 fields::Week::WeekOfYear")
194 }
195 }),
196 length: match week {
197 Week::WeekOfMonth | Week::NumericWeekOfYear => FieldLength::One,
198 Week::TwoDigitWeekOfYear => FieldLength::Two,
199 },
200 });
201 }
202
203 if let Some(day) = self.day {
204 // TODO(#591) Unimplemented day fields:
205 // g - Modified Julian day.
206 fields.push(Field {
207 symbol: FieldSymbol::Day(match day {
208 Day::NumericDayOfMonth | Day::TwoDigitDayOfMonth => fields::Day::DayOfMonth,
209 Day::DayOfWeekInMonth => fields::Day::DayOfWeekInMonth,
210 Day::DayOfYear => fields::Day::DayOfYear,
211 Day::ModifiedJulianDay => fields::Day::ModifiedJulianDay,
212 }),
213 length: match day {
214 // d 1 Numeric day of month: minimum digits
215 // dd 01 Numeric day of month: 2 digits, zero pad if needed
216 // F 1 Numeric day of week in month: minimum digits
217 // D 1 Numeric day of year: minimum digits
218 Day::NumericDayOfMonth
219 | Day::DayOfWeekInMonth
220 | Day::DayOfYear
221 | Day::ModifiedJulianDay => FieldLength::One,
222 Day::TwoDigitDayOfMonth => FieldLength::Two,
223 },
224 });
225 }
226
227 if let Some(weekday) = self.weekday {
228 // TODO(#593) Unimplemented fields
229 // e - Local day of week.
230 // c - Stand-alone local day of week.
231 fields.push(Field {
232 symbol: FieldSymbol::Weekday(fields::Weekday::Format),
233 length: match weekday {
234 // Day of week name, format length.
235 //
236 // E..EEE Tue Abbreviated
237 // EEEE Tuesday Wide
238 // EEEEE T Narrow
239 // EEEEEE Tu Short
240 Text::Long => FieldLength::Four,
241 Text::Short => FieldLength::One,
242 Text::Narrow => FieldLength::Five,
243 },
244 });
245 }
246
247 // The period fields are not included in skeletons:
248 // a - AM, PM
249 // b - am, pm, noon, midnight
250 // c - flexible day periods
251
252 if let Some(hour) = self.hour {
253 // fields::Hour::H11
254 // fields::Hour::H12
255 // fields::Hour::H23
256
257 let hour_cycle = self.hour_cycle.unwrap_or(default_hour_cycle);
258
259 // When used in skeleton data or in a skeleton passed in an API for flexible date
260 // pattern generation, it should match the 12-hour-cycle format preferred by the
261 // locale (h or K); it should not match a 24-hour-cycle format (H or k).
262 fields.push(Field {
263 symbol: FieldSymbol::Hour(match hour_cycle {
264 // Skeletons only contain the h12, not h11. The pattern that is matched
265 // is free to use h11 or h12.
266 HourCycle::H11 | HourCycle::H12 => {
267 // h - symbol
268 fields::Hour::H12
269 }
270 // Skeletons only contain the h23, not h24. The pattern that is matched
271 // is free to use h23 or h24.
272 HourCycle::H23 => {
273 // H - symbol
274 fields::Hour::H23
275 }
276 _ => unreachable!(),
277 }),
278 length: match hour {
279 // Example for h: (note that this is the same for k, K, and H)
280 // h 1, 12 Numeric: minimum digits
281 // hh 01, 12 Numeric: 2 digits, zero pad if needed
282 Numeric::Numeric => FieldLength::One,
283 Numeric::TwoDigit => FieldLength::Two,
284 },
285 });
286 }
287
288 if let Some(minute) = self.minute {
289 // m 8, 59 Numeric: minimum digits
290 // mm 08, 59 Numeric: 2 digits, zero pad if needed
291 fields.push(Field {
292 symbol: FieldSymbol::Minute,
293 length: match minute {
294 Numeric::Numeric => FieldLength::One,
295 Numeric::TwoDigit => FieldLength::Two,
296 },
297 });
298 }
299
300 if let Some(second) = self.second {
301 let symbol = match self.subsecond {
302 None => FieldSymbol::Second(fields::Second::Second),
303 Some(subsecond) => FieldSymbol::from_subsecond_digits(subsecond),
304 };
305 // s 8, 12 Numeric: minimum digits
306 // ss 08, 12 Numeric: 2 digits, zero pad if needed
307 fields.push(Field {
308 symbol,
309 length: match second {
310 Numeric::Numeric => FieldLength::One,
311 Numeric::TwoDigit => FieldLength::Two,
312 },
313 });
314 // S - Fractional seconds. Represented as DecimalSecond.
315 // A - Milliseconds in day. Not used in skeletons.
316 }
317
318 if self.time_zone_name.is_some() {
319 // Only the lower "v" field is used in skeletons.
320 fields.push(Field {
321 symbol: FieldSymbol::TimeZone(fields::TimeZone::GenericNonLocation),
322 length: FieldLength::One,
323 });
324 }
325
326 {
327 #![allow(clippy::indexing_slicing)] // debug
328 debug_assert!(
329 fields.windows(2).all(|f| f[0] < f[1]),
330 "The fields are sorted and unique."
331 );
332 }
333
334 fields
335 }
336}
337
338/// A numeric component for the `components::`[`Bag`]. It is used for the year, day, hour, minute,
339/// and second.
340///
341/// <div class="stab unstable">
342/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
343/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature
344/// of the icu meta-crate. Use with caution.
345/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
346/// </div>
347#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
348#[cfg_attr(
349 feature = "serde",
350 derive(Serialize, Deserialize),
351 serde(rename_all = "kebab-case")
352)]
353#[non_exhaustive]
354pub enum Numeric {
355 /// Display the numeric value. For instance in a year this would be "1970".
356 Numeric,
357 /// Display the two digit value. For instance in a year this would be "70".
358 TwoDigit,
359}
360
361/// A text component for the `components::`[`Bag`]. It is used for the era and weekday.
362///
363/// <div class="stab unstable">
364/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
365/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature
366/// of the icu meta-crate. Use with caution.
367/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
368/// </div>
369#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
370#[cfg_attr(
371 feature = "serde",
372 derive(Serialize, Deserialize),
373 serde(rename_all = "kebab-case")
374)]
375#[non_exhaustive]
376pub enum Text {
377 /// Display the long form of the text, such as "Wednesday" for the weekday.
378 /// In UTS-35, known as "Wide" (4 letters)
379 Long,
380 /// Display the short form of the text, such as "Wed" for the weekday.
381 /// In UTS-35, known as "Abbreviated" (3 letters)
382 Short,
383 /// Display the narrow form of the text, such as "W" for the weekday.
384 /// In UTS-35, known as "Narrow" (5 letters)
385 Narrow,
386}
387
388/// Options for displaying a Year for the `components::`[`Bag`].
389///
390/// <div class="stab unstable">
391/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
392/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature
393/// of the icu meta-crate. Use with caution.
394/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
395/// </div>
396#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
397#[cfg_attr(
398 feature = "serde",
399 derive(Serialize, Deserialize),
400 serde(rename_all = "kebab-case")
401)]
402#[non_exhaustive]
403pub enum Year {
404 /// The numeric value of the year, such as "2018" for 2018-12-31.
405 Numeric,
406 /// The two-digit value of the year, such as "18" for 2018-12-31.
407 TwoDigit,
408 /// The numeric value of the year in "week-of-year", such as "2019" in
409 /// "week 01 of 2019" for the week of 2018-12-31 according to the ISO calendar.
410 NumericWeekOf,
411 /// The numeric value of the year in "week-of-year", such as "19" in
412 /// "week 01 '19" for the week of 2018-12-31 according to the ISO calendar.
413 TwoDigitWeekOf,
414}
415
416/// Options for displaying a Month for the `components::`[`Bag`].
417///
418/// <div class="stab unstable">
419/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
420/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature
421/// of the icu meta-crate. Use with caution.
422/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
423/// </div>
424#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
425#[cfg_attr(
426 feature = "serde",
427 derive(Serialize, Deserialize),
428 serde(rename_all = "kebab-case")
429)]
430#[non_exhaustive]
431pub enum Month {
432 /// The numeric value of the month, such as "4".
433 Numeric,
434 /// The two-digit value of the month, such as "04".
435 TwoDigit,
436 /// The long value of the month, such as "April".
437 Long,
438 /// The short value of the month, such as "Apr".
439 Short,
440 /// The narrow value of the month, such as "A".
441 Narrow,
442}
443
444// Each enum variant is documented with the UTS 35 field information from:
445// https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
446
447/// Options for displaying the current week number for the `components::`[`Bag`].
448///
449/// Week numbers are relative to either a month or year, e.g. 'week 3 of January' or 'week 40 of 2000'.
450///
451/// <div class="stab unstable">
452/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
453/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature
454/// of the icu meta-crate. Use with caution.
455/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
456/// </div>
457#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
458#[cfg_attr(
459 feature = "serde",
460 derive(Serialize, Deserialize),
461 serde(rename_all = "kebab-case")
462)]
463#[non_exhaustive]
464pub enum Week {
465 /// The week of the month, such as the "3" in "week 3 of January".
466 WeekOfMonth,
467 /// The numeric value of the week of the year, such as the "8" in "week 8 of 2000".
468 NumericWeekOfYear,
469 /// The two-digit value of the week of the year, such as the "08" in "2000-W08".
470 TwoDigitWeekOfYear,
471}
472
473/// Options for displaying the current day of the month or year.
474///
475/// <div class="stab unstable">
476/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
477/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature
478/// of the icu meta-crate. Use with caution.
479/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
480/// </div>
481#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
482#[cfg_attr(
483 feature = "serde",
484 derive(Serialize, Deserialize),
485 serde(rename_all = "kebab-case")
486)]
487#[non_exhaustive]
488pub enum Day {
489 /// The numeric value of the day of month, such as the "2" in July 2 1984.
490 NumericDayOfMonth,
491 /// The two digit value of the day of month, such as the "02" in 1984-07-02.
492 TwoDigitDayOfMonth,
493 /// The day of week in this month, such as the "2" in 2nd Wednesday of July.
494 DayOfWeekInMonth,
495 /// The day of year (numeric).
496 DayOfYear,
497 /// The modified Julian day (numeric)
498 ModifiedJulianDay,
499}
500
501/// Options for displaying a time zone for the `components::`[`Bag`].
502///
503/// Note that the initial implementation is focusing on only supporting ECMA-402 compatible
504/// options.
505///
506/// <div class="stab unstable">
507/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
508/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature
509/// of the icu meta-crate. Use with caution.
510/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
511/// </div>
512#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
513#[cfg_attr(
514 feature = "serde",
515 derive(Serialize, Deserialize),
516 serde(rename_all = "kebab-case")
517)]
518#[non_exhaustive]
519pub enum TimeZoneName {
520 // UTS-35 fields: z..zzz
521 /// Short localized form, without the location. (e.g.: PST, GMT-8)
522 ShortSpecific,
523
524 // UTS-35 fields: zzzz
525 // Per UTS-35: [long form] specific non-location (falling back to long localized offset)
526 /// Long localized form, without the location (e.g., Pacific Standard Time, Nordamerikanische Westküsten-Normalzeit)
527 LongSpecific,
528
529 // UTS-35 fields: OOOO
530 // Per UTS-35: The long localized offset format. This is equivalent to the "OOOO" specifier
531 /// Long localized offset form, e.g. GMT-08:00
532 LongOffset,
533
534 // UTS-35 fields: O
535 // Per UTS-35: Short localized offset format
536 /// Short localized offset form, e.g. GMT-8
537 ShortOffset,
538
539 // UTS-35 fields: v
540 // * falling back to generic location (See UTS 35 for more specific rules)
541 // * falling back to short localized offset
542 /// Short generic non-location format (e.g.: PT, Los Angeles, Zeit).
543 ShortGeneric,
544
545 // UTS-35 fields: vvvv
546 // * falling back to generic location (See UTS 35 for more specific rules)
547 // * falling back to long localized offset
548 /// Long generic non-location format (e.g.: Pacific Time, Nordamerikanische Westküstenzeit),
549 LongGeneric,
550}
551
552impl From<TimeZoneName> for Field {
553 fn from(time_zone_name: TimeZoneName) -> Self {
554 match time_zone_name {
555 TimeZoneName::ShortSpecific => Field {
556 symbol: FieldSymbol::TimeZone(fields::TimeZone::SpecificNonLocation),
557 length: FieldLength::One,
558 },
559 TimeZoneName::LongSpecific => Field {
560 symbol: FieldSymbol::TimeZone(fields::TimeZone::SpecificNonLocation),
561 length: FieldLength::Four,
562 },
563 TimeZoneName::LongOffset => Field {
564 symbol: FieldSymbol::TimeZone(fields::TimeZone::LocalizedOffset),
565 length: FieldLength::Four,
566 },
567 TimeZoneName::ShortOffset => Field {
568 symbol: FieldSymbol::TimeZone(fields::TimeZone::LocalizedOffset),
569 length: FieldLength::One,
570 },
571 TimeZoneName::ShortGeneric => Field {
572 symbol: FieldSymbol::TimeZone(fields::TimeZone::GenericNonLocation),
573 length: FieldLength::One,
574 },
575 TimeZoneName::LongGeneric => Field {
576 symbol: FieldSymbol::TimeZone(fields::TimeZone::GenericNonLocation),
577 length: FieldLength::Four,
578 },
579 }
580 }
581}
582
583impl From<&DateTimePattern> for Bag {
584 fn from(value: &DateTimePattern) -> Self {
585 Self::from(value.as_borrowed().0)
586 }
587}
588
589impl From<&Pattern<'_>> for Bag {
590 fn from(pattern: &Pattern) -> Self {
591 Self::from_pattern_items(pattern.items.iter())
592 }
593}
594
595impl From<&reference::Pattern> for Bag {
596 fn from(pattern: &reference::Pattern) -> Self {
597 Self::from_pattern_items(pattern.items.iter().copied())
598 }
599}
600
601impl Bag {
602 fn from_pattern_items(pattern_items: impl Iterator<Item = PatternItem>) -> Self {
603 let mut bag: Bag = Default::default();
604
605 // Transform the fields into components per:
606 // https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
607 for item in pattern_items {
608 let field = match item {
609 PatternItem::Field(ref field) => field,
610 PatternItem::Literal(_) => continue,
611 };
612 match field.symbol {
613 FieldSymbol::Era => {
614 bag.era = Some(match field.length {
615 FieldLength::One
616 | FieldLength::NumericOverride(_)
617 | FieldLength::Two
618 | FieldLength::Three => Text::Short,
619 FieldLength::Four => Text::Long,
620 FieldLength::Five | FieldLength::Six => Text::Narrow,
621 });
622 }
623 FieldSymbol::Year(year) => {
624 bag.year = Some(match year {
625 fields::Year::Calendar => match field.length {
626 FieldLength::Two => Year::TwoDigit,
627 _ => Year::Numeric,
628 },
629 // fields::Year::WeekOf => match field.length {
630 // FieldLength::TwoDigit => Year::TwoDigitWeekOf,
631 // _ => Year::NumericWeekOf,
632 // },
633 // TODO(#3762): Add support for U and r
634 _ => Year::Numeric,
635 });
636 }
637 FieldSymbol::Month(_) => {
638 // `Month::StandAlone` is only relevant in the pattern, so only differentiate
639 // on the field length.
640 bag.month = Some(match field.length {
641 FieldLength::One => Month::Numeric,
642 FieldLength::NumericOverride(_) => Month::Numeric,
643 FieldLength::Two => Month::TwoDigit,
644 FieldLength::Three => Month::Short,
645 FieldLength::Four => Month::Long,
646 FieldLength::Five | FieldLength::Six => Month::Narrow,
647 });
648 }
649 FieldSymbol::Week(_week) => {
650 // TODO(#5643): Add week fields back
651 // bag.week = Some(match week {
652 // fields::Week::WeekOfYear => match field.length {
653 // FieldLength::TwoDigit => Week::TwoDigitWeekOfYear,
654 // _ => Week::NumericWeekOfYear,
655 // },
656 // fields::Week::WeekOfMonth => Week::WeekOfMonth,
657 // });
658 }
659 FieldSymbol::Day(day) => {
660 bag.day = Some(match day {
661 fields::Day::DayOfMonth => match field.length {
662 FieldLength::Two => Day::TwoDigitDayOfMonth,
663 _ => Day::NumericDayOfMonth,
664 },
665 fields::Day::DayOfYear => Day::DayOfYear,
666 fields::Day::DayOfWeekInMonth => Day::DayOfWeekInMonth,
667 fields::Day::ModifiedJulianDay => Day::ModifiedJulianDay,
668 });
669 }
670 FieldSymbol::Weekday(weekday) => {
671 bag.weekday = Some(match weekday {
672 fields::Weekday::Format => match field.length {
673 FieldLength::One | FieldLength::Two | FieldLength::Three => Text::Short,
674 FieldLength::Four => Text::Long,
675 _ => Text::Narrow,
676 },
677 fields::Weekday::StandAlone => match field.length {
678 FieldLength::One
679 | FieldLength::Two
680 | FieldLength::NumericOverride(_) => {
681 // Stand-alone fields also support a numeric 1 digit per UTS-35, but there is
682 // no way to request it using the current system. As of 2021-12-06
683 // no skeletons resolve to patterns containing this symbol.
684 //
685 // All resolved patterns from cldr-json:
686 // https://github.com/gregtatum/cldr-json/blob/d4779f9611a4cc1e3e6a0a4597e92ead32d9f118/stand-alone-week.js
687 // 'ccc',
688 // 'ccc d. MMM',
689 // 'ccc d. MMMM',
690 // 'cccc d. MMMM y',
691 // 'd, ccc',
692 // 'cccနေ့',
693 // 'ccc, d MMM',
694 // "ccc, d 'de' MMMM",
695 // "ccc, d 'de' MMMM 'de' y",
696 // 'ccc, h:mm B',
697 // 'ccc, h:mm:ss B',
698 // 'ccc, d',
699 // "ccc, dd.MM.y 'г'.",
700 // 'ccc, d.MM.y',
701 // 'ccc, MMM d. y'
702 unimplemented!("Numeric stand-alone fields are not supported.")
703 }
704 FieldLength::Three => Text::Short,
705 FieldLength::Four => Text::Long,
706 FieldLength::Five | FieldLength::Six => Text::Narrow,
707 },
708 fields::Weekday::Local => unimplemented!("fields::Weekday::Local"),
709 });
710 }
711 FieldSymbol::DayPeriod(_) => {
712 // Day period does not affect the resolved components.
713 }
714 FieldSymbol::Hour(hour) => {
715 bag.hour = Some(match field.length {
716 FieldLength::Two => Numeric::TwoDigit,
717 _ => Numeric::Numeric,
718 });
719 bag.hour_cycle = Some(match hour {
720 fields::Hour::H11 => HourCycle::H11,
721 fields::Hour::H12 => HourCycle::H12,
722 fields::Hour::H23 => HourCycle::H23,
723 });
724 }
725 FieldSymbol::Minute => {
726 bag.minute = Some(match field.length {
727 FieldLength::Two => Numeric::TwoDigit,
728 _ => Numeric::Numeric,
729 });
730 }
731 FieldSymbol::Second(second) => match second {
732 fields::Second::Second => {
733 bag.second = Some(match field.length {
734 FieldLength::Two => Numeric::TwoDigit,
735 _ => Numeric::Numeric,
736 });
737 }
738 fields::Second::MillisInDay => unimplemented!("fields::Second::MillisInDay"),
739 },
740 FieldSymbol::DecimalSecond(decimal_second) => {
741 use SubsecondDigits::*;
742 bag.second = Some(match field.length {
743 FieldLength::Two => Numeric::TwoDigit,
744 _ => Numeric::Numeric,
745 });
746 bag.subsecond = Some(match decimal_second {
747 fields::DecimalSecond::Subsecond1 => S1,
748 fields::DecimalSecond::Subsecond2 => S2,
749 fields::DecimalSecond::Subsecond3 => S3,
750 fields::DecimalSecond::Subsecond4 => S4,
751 fields::DecimalSecond::Subsecond5 => S5,
752 fields::DecimalSecond::Subsecond6 => S6,
753 fields::DecimalSecond::Subsecond7 => S7,
754 fields::DecimalSecond::Subsecond8 => S8,
755 fields::DecimalSecond::Subsecond9 => S9,
756 });
757 }
758 FieldSymbol::TimeZone(time_zone_name) => {
759 bag.time_zone_name = Some(match time_zone_name {
760 fields::TimeZone::SpecificNonLocation => match field.length {
761 FieldLength::One => TimeZoneName::ShortSpecific,
762 _ => TimeZoneName::LongSpecific,
763 },
764 fields::TimeZone::GenericNonLocation => match field.length {
765 FieldLength::One => TimeZoneName::ShortGeneric,
766 _ => TimeZoneName::LongGeneric,
767 },
768 fields::TimeZone::LocalizedOffset => match field.length {
769 FieldLength::One => TimeZoneName::ShortOffset,
770 _ => TimeZoneName::LongOffset,
771 },
772 fields::TimeZone::Location => unimplemented!("fields::TimeZone::Location"),
773 fields::TimeZone::Iso => unimplemented!("fields::TimeZone::IsoZ"),
774 fields::TimeZone::IsoWithZ => unimplemented!("fields::TimeZone::Iso"),
775 });
776 }
777 }
778 }
779
780 bag
781 }
782}
783
784#[cfg(test)]
785mod test {
786 use super::*;
787
788 // Shorten these for terser tests.
789 type Symbol = FieldSymbol;
790 type Length = FieldLength;
791
792 #[test]
793 fn test_component_bag_to_vec_field() {
794 let bag = Bag {
795 year: Some(Year::Numeric),
796 month: Some(Month::Long),
797 // TODO(#5643): Add week fields back
798 week: None,
799 day: Some(Day::NumericDayOfMonth),
800
801 hour: Some(Numeric::Numeric),
802 minute: Some(Numeric::Numeric),
803 second: Some(Numeric::Numeric),
804 subsecond: Some(SubsecondDigits::S3),
805
806 ..Default::default()
807 };
808 assert_eq!(
809 bag.to_vec_fields(HourCycle::H23),
810 [
811 (Symbol::Year(fields::Year::Calendar), Length::One).into(),
812 (Symbol::Month(fields::Month::Format), Length::Four).into(),
813 (Symbol::Day(fields::Day::DayOfMonth), Length::One).into(),
814 (Symbol::Hour(fields::Hour::H23), Length::One).into(),
815 (Symbol::Minute, Length::One).into(),
816 (
817 Symbol::DecimalSecond(fields::DecimalSecond::Subsecond3),
818 Length::One
819 )
820 .into(),
821 ]
822 );
823 }
824
825 #[test]
826 fn test_component_bag_to_vec_field2() {
827 let bag = Bag {
828 year: Some(Year::Numeric),
829 month: Some(Month::TwoDigit),
830 day: Some(Day::NumericDayOfMonth),
831 ..Default::default()
832 };
833 assert_eq!(
834 bag.to_vec_fields(HourCycle::H23),
835 [
836 (Symbol::Year(fields::Year::Calendar), Length::One).into(),
837 (Symbol::Month(fields::Month::Format), Length::Two).into(),
838 (Symbol::Day(fields::Day::DayOfMonth), Length::One).into(),
839 ]
840 );
841 }
842}