• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 6815798908

09 Nov 2023 05:17PM UTC coverage: 72.607% (-2.4%) from 75.01%
6815798908

push

github

web-flow
Implement `Any/BufferProvider` for some smart pointers (#4255)

Allows storing them as a `Box<dyn Any/BufferProvider>` without using a
wrapper type that implements the trait.

44281 of 60987 relevant lines covered (72.61%)

201375.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.09
/components/datetime/src/options/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\] Options for constructing DateTimeFormatter objects by each component style.
6
//!
7
//! ✨ *Enabled with the `experimental` Cargo feature.*
8
//!
9
//! <div class="stab unstable">
10
//! 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
11
//! including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature
12
//! of the icu meta-crate. Use with caution.
13
//! <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
14
//! </div>
15
//!
16
//! # Implementation status
17
//!
18
//! This module is available by enabling the `"experimental"` Cargo feature.
19
//! It may change in breaking ways, including across minor releases.
20
//!
21
//! This is currently only a partial implementation of the UTS-35 skeleton matching algorithm.
22
//!
23
//! | Algorithm step | Status |
24
//! |----------------|--------|
25
//! | Match skeleton fields according to a ranking             | Implemented |
26
//! | Adjust the matched pattern to have certain widths        | Implemented |
27
//! | Match date and times separately, and them combine them   | Implemented |
28
//! | Use appendItems to fill in a pattern with missing fields | Not yet, and may not be fully implemented. See [issue #586](https://github.com/unicode-org/icu4x/issues/586) |
29
//!
30
//! # Description
31
//!
32
//! A [`components::Bag`](struct.Bag.html) is a model of encoding information on how to format date
33
//! and time by specifying a list of components the user wants to be visible in the formatted string
34
//! and how each field should be displayed.
35
//!
36
//! This model closely corresponds to `ECMA402` API and allows for high level of customization
37
//! compared to `Length` model.
38
//!
39
//! Additionally, the bag contains an optional set of `Preferences` which represent user
40
//! preferred adjustments that can be applied onto the pattern right before formatting.
41
//!
42
//! ## Pattern Selection
43
//!
44
//! The [`components::Bag`](struct.Bag.html) is a way for the developer to describe which components
45
//! should be included in in a datetime, and how they should be displayed. There is not a strict
46
//! guarantee in how the final date will be displayed to the end user. The user's preferences and
47
//! locale information can override the developer preferences.
48
//!
49
//! The fields in the [`components::Bag`](struct.Bag.html) are matched against available patterns in
50
//! the `CLDR` locale data. A best fit is found, and presented to the user. This means that in
51
//! certain situations, and component combinations, fields will not have a match, or the match will
52
//! have a different type of presentation for a given locale.
53
//!
54
//!
55
//! # Examples
56
//!
57
//! ```
58
//! use icu::datetime::options::components;
59
//! use icu::datetime::DateTimeFormatterOptions;
60
//!
61
//! let mut bag = components::Bag::default();
62
//! bag.year = Some(components::Year::Numeric);
63
//! bag.month = Some(components::Month::Long);
64
//! bag.day = Some(components::Day::NumericDayOfMonth);
65
//!
66
//! bag.hour = Some(components::Numeric::TwoDigit);
67
//! bag.minute = Some(components::Numeric::TwoDigit);
68
//!
69
//! // The options can be created manually.
70
//! let options = DateTimeFormatterOptions::Components(bag);
71
//! ```
72
//!
73
//! Or the options can be inferred through the `.into()` trait.
74
//!
75
//! ```
76
//! use icu::datetime::options::components;
77
//! use icu::datetime::DateTimeFormatterOptions;
78
//! let options: DateTimeFormatterOptions = components::Bag::default().into();
79
//! ```
80
//!
81
//! *Note*: The exact result returned from [`TypedDateTimeFormatter`](crate::TypedDateTimeFormatter) is a subject to change over
82
//! time. Formatted result should be treated as opaque and displayed to the user as-is,
83
//! and it is strongly recommended to never write tests that expect a particular formatted output.
84

85
use crate::{
86
    fields::{self, Field, FieldLength, FieldSymbol},
87
    pattern::{runtime::PatternPlurals, PatternItem},
88
};
89

90
use super::preferences;
91
#[cfg(feature = "serde")]
92
use serde::{Deserialize, Serialize};
93

94
/// See the [module-level](./index.html) docs for more information.
95
///
96
/// <div class="stab unstable">
97
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
98
/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature
99
/// of the icu meta-crate. Use with caution.
100
/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
101
/// </div>
102
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
28✔
103
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
×
104
#[non_exhaustive]
105
pub struct Bag {
106
    /// Include the era, such as "AD" or "CE".
107
    pub era: Option<Text>,
14✔
108
    /// Include the year, such as "1970" or "70".
109
    pub year: Option<Year>,
14✔
110
    /// Include the month, such as "April" or "Apr".
111
    pub month: Option<Month>,
14✔
112
    /// Include the week number, such as "51st" or "51" for week 51.
113
    pub week: Option<Week>,
14✔
114
    /// Include the day of the month/year, such as "07" or "7".
115
    pub day: Option<Day>,
14✔
116
    /// Include the weekday, such as "Wednesday" or "Wed".
117
    pub weekday: Option<Text>,
14✔
118

119
    /// Include the hour such as "2" or "14".
120
    pub hour: Option<Numeric>,
14✔
121
    /// Include the minute such as "3" or "03".
122
    pub minute: Option<Numeric>,
14✔
123
    /// Include the second such as "3" or "03".
124
    pub second: Option<Numeric>,
14✔
125
    /// Specify the number of fractional second digits such as 1 (".3") or 3 (".003").
126
    pub fractional_second: Option<u8>,
14✔
127

128
    /// Include the time zone, such as "GMT+05:00".
129
    pub time_zone_name: Option<TimeZoneName>,
14✔
130

131
    /// Adjust the preferences for the date, such as setting the hour cycle.
132
    pub preferences: Option<preferences::Bag>,
14✔
133
}
134

135
impl Bag {
136
    /// Creates an empty components bag
137
    ///
138
    /// Has the same behavior as the [`Default`] implementation on this type.
139
    pub fn empty() -> Self {
×
140
        Self::default()
×
141
    }
×
142

143
    #[allow(clippy::wrong_self_convention)]
144
    /// Converts the components::Bag into a Vec<Field>. The fields will be ordered in from most
145
    /// significant field to least significant. This is the order the fields are listed in
146
    /// the UTS 35 table - https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
147
    #[cfg(any(test, feature = "experimental"))] // only used in test and experimental code
148
    pub(crate) fn to_vec_fields(&self) -> alloc::vec::Vec<Field> {
312✔
149
        let mut fields = alloc::vec::Vec::new();
312✔
150
        if let Some(era) = self.era {
312✔
151
            fields.push(Field {
107✔
152
                symbol: FieldSymbol::Era,
107✔
153
                length: match era {
107✔
154
                    // Era name, format length.
155
                    //
156
                    // G..GGG  AD           Abbreviated
157
                    // GGGG    Anno Domini  Wide
158
                    // GGGGG   A            Narrow
159
                    Text::Short => FieldLength::Abbreviated,
15✔
160
                    Text::Long => FieldLength::Wide,
92✔
161
                    Text::Narrow => FieldLength::Narrow,
×
162
                },
163
            })
164
        }
165

166
        if let Some(year) = self.year {
312✔
167
            // Unimplemented year fields:
168
            // u - Extended year
169
            // U - Cyclic year name
170
            // r - Related Gregorian year
171
            fields.push(Field {
217✔
172
                symbol: FieldSymbol::Year(match year {
434✔
173
                    Year::Numeric | Year::TwoDigit => fields::Year::Calendar,
197✔
174
                    Year::NumericWeekOf | Year::TwoDigitWeekOf => fields::Year::WeekOf,
20✔
175
                }),
176
                length: match year {
217✔
177
                    // Calendar year (numeric).
178
                    // y       2, 20, 201, 2017, 20173
179
                    // yy      02, 20, 01, 17, 73
180
                    // yyy     002, 020, 201, 2017, 20173    (not implemented)
181
                    // yyyy    0002, 0020, 0201, 2017, 20173 (not implemented)
182
                    // yyyyy+  ...                           (not implemented)
183
                    Year::Numeric | Year::NumericWeekOf => FieldLength::One,
207✔
184
                    Year::TwoDigit | Year::TwoDigitWeekOf => FieldLength::TwoDigit,
10✔
185
                },
186
            });
187
        }
188

189
        // TODO(#501) - Unimplemented quarter fields:
190
        // Q - Quarter number/name
191
        // q - Stand-alone quarter
192

193
        if let Some(month) = self.month {
312✔
194
            fields.push(Field {
168✔
195
                // Always choose Month::Format as Month::StandAlone is not used in skeletons.
196
                symbol: FieldSymbol::Month(fields::Month::Format),
168✔
197
                length: match month {
168✔
198
                    // (intended to be used in conjunction with ‘d’ for day number).
199
                    // M      9, 12      Numeric: minimum digits
200
                    // MM     09, 12     Numeric: 2 digits, zero pad if needed
201
                    // MMM    Sep        Abbreviated
202
                    // MMMM   September  Wide
203
                    // MMMMM  S          Narrow
204
                    Month::Numeric => FieldLength::One,
21✔
205
                    Month::TwoDigit => FieldLength::TwoDigit,
11✔
206
                    Month::Long => FieldLength::Wide,
100✔
207
                    Month::Short => FieldLength::Abbreviated,
26✔
208
                    Month::Narrow => FieldLength::Narrow,
10✔
209
                },
210
            });
211
        }
212

213
        if let Some(week) = self.week {
312✔
214
            fields.push(Field {
31✔
215
                symbol: FieldSymbol::Week(match week {
62✔
216
                    Week::WeekOfMonth => fields::Week::WeekOfMonth,
5✔
217
                    Week::NumericWeekOfYear | Week::TwoDigitWeekOfYear => fields::Week::WeekOfYear,
26✔
218
                }),
219
                length: match week {
31✔
220
                    Week::WeekOfMonth | Week::NumericWeekOfYear => FieldLength::One,
28✔
221
                    Week::TwoDigitWeekOfYear => FieldLength::TwoDigit,
3✔
222
                },
223
            });
224
        }
225

226
        if let Some(day) = self.day {
312✔
227
            // TODO(#591) Unimplemented day fields:
228
            // D - Day of year
229
            // g - Modified Julian day.
230
            fields.push(Field {
156✔
231
                symbol: FieldSymbol::Day(match day {
312✔
232
                    Day::NumericDayOfMonth | Day::TwoDigitDayOfMonth => fields::Day::DayOfMonth,
154✔
233
                    Day::DayOfWeekInMonth => fields::Day::DayOfWeekInMonth,
2✔
234
                }),
235
                length: match day {
156✔
236
                    // d    1           Numeric day of month: minimum digits
237
                    // dd   01           Numeric day of month: 2 digits, zero pad if needed
238
                    // F    1            Numeric day of week in month: minimum digits
239
                    Day::NumericDayOfMonth | Day::DayOfWeekInMonth => FieldLength::One,
87✔
240
                    Day::TwoDigitDayOfMonth => FieldLength::TwoDigit,
69✔
241
                },
242
            });
243
        }
244

245
        if let Some(weekday) = self.weekday {
312✔
246
            // TODO(#593) Unimplemented fields
247
            // e - Local day of week.
248
            // c - Stand-alone local day of week.
249
            fields.push(Field {
107✔
250
                symbol: FieldSymbol::Weekday(fields::Weekday::Format),
107✔
251
                length: match weekday {
107✔
252
                    // Day of week name, format length.
253
                    //
254
                    // E..EEE   Tue      Abbreviated
255
                    // EEEE     Tuesday  Wide
256
                    // EEEEE    T              Narrow
257
                    // EEEEEE   Tu       Short
258
                    Text::Long => FieldLength::Wide,
84✔
259
                    Text::Short => FieldLength::One,
23✔
260
                    Text::Narrow => FieldLength::Narrow,
×
261
                },
262
            });
263
        }
264

265
        // The period fields are not included in skeletons:
266
        // a - AM, PM
267
        // b - am, pm, noon, midnight
268
        // c - flexible day periods
269

270
        if let Some(hour) = self.hour {
312✔
271
            // fields::Hour::H11
272
            // fields::Hour::H12
273
            // fields::Hour::H23
274
            // fields::Hour::H24
275

276
            // When used in skeleton data or in a skeleton passed in an API for flexible date
277
            // pattern generation, it should match the 12-hour-cycle format preferred by the
278
            // locale (h or K); it should not match a 24-hour-cycle format (H or k).
279
            fields.push(Field {
144✔
280
                symbol: FieldSymbol::Hour(match self.preferences {
288✔
281
                    Some(preferences::Bag {
282
                        hour_cycle: Some(hour_cycle),
48✔
283
                    }) => match hour_cycle {
48✔
284
                        // Skeletons only contain the h12, not h11. The pattern that is matched
285
                        // is free to use h11 or h12.
286
                        preferences::HourCycle::H11 | preferences::HourCycle::H12 => {
287
                            // h - symbol
288
                            fields::Hour::H12
26✔
289
                        }
290
                        // Skeletons only contain the h23, not h24. The pattern that is matched
291
                        // is free to use h23 or h24.
292
                        preferences::HourCycle::H24 | preferences::HourCycle::H23 => {
293
                            // H - symbol
294
                            fields::Hour::H23
22✔
295
                        }
296
                    },
297
                    // TODO(#594) - This should default should be the locale default, which is
298
                    // region-based (h12 for US, h23 for GB, etc). This is in CLDR, but we need
299
                    // to load it as well as think about the best architecture for where that
300
                    // data loading code should reside.
301
                    _ => fields::Hour::H23,
96✔
302
                }),
303
                length: match hour {
144✔
304
                    // Example for h: (note that this is the same for k, K, and H)
305
                    // h     1, 12  Numeric: minimum digits
306
                    // hh   01, 12  Numeric: 2 digits, zero pad if needed
307
                    Numeric::Numeric => FieldLength::One,
134✔
308
                    Numeric::TwoDigit => FieldLength::TwoDigit,
10✔
309
                },
310
            });
311
        }
312

313
        if let Some(minute) = self.minute {
312✔
314
            // m   8, 59    Numeric: minimum digits
315
            // mm  08, 59   Numeric: 2 digits, zero pad if needed
316
            fields.push(Field {
133✔
317
                symbol: FieldSymbol::Minute,
133✔
318
                length: match minute {
133✔
319
                    Numeric::Numeric => FieldLength::One,
120✔
320
                    Numeric::TwoDigit => FieldLength::TwoDigit,
13✔
321
                },
322
            });
323
        }
324

325
        if let Some(second) = self.second {
312✔
326
            // s    8, 12    Numeric: minimum digits
327
            // ss  08, 12    Numeric: 2 digits, zero pad if needed
328
            fields.push(Field {
103✔
329
                symbol: FieldSymbol::Second(fields::Second::Second),
103✔
330
                length: match second {
103✔
331
                    Numeric::Numeric => FieldLength::One,
84✔
332
                    Numeric::TwoDigit => FieldLength::TwoDigit,
19✔
333
                },
334
            });
335
            // A - Milliseconds in day. Not used in skeletons.
336
        }
337

338
        if let Some(precision) = self.fractional_second {
312✔
339
            // S - Fractional seconds.
340
            fields.push(Field {
10✔
341
                symbol: FieldSymbol::Second(fields::Second::FractionalSecond),
10✔
342
                length: FieldLength::Fixed(precision),
10✔
343
            });
344
        }
345

346
        if self.time_zone_name.is_some() {
312✔
347
            // Only the lower "v" field is used in skeletons.
348
            fields.push(Field {
14✔
349
                symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV),
14✔
350
                length: FieldLength::One,
14✔
351
            });
352
        }
353

354
        {
355
            #![allow(clippy::indexing_slicing)] // debug
356
            debug_assert!(
312✔
357
                fields.windows(2).all(|f| f[0] < f[1]),
1,191✔
358
                "The fields are sorted and unique."
359
            );
360
        }
361

362
        fields
363
    }
312✔
364
}
365

366
/// A numeric component for the `components::`[`Bag`]. It is used for the year, day, hour, minute,
367
/// and second.
368
///
369
/// <div class="stab unstable">
370
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
371
/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature
372
/// of the icu meta-crate. Use with caution.
373
/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
374
/// </div>
375
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9✔
376
#[cfg_attr(
377
    feature = "serde",
378
    derive(Serialize, Deserialize),
×
379
    serde(rename_all = "kebab-case")
380
)]
381
#[non_exhaustive]
382
pub enum Numeric {
383
    /// Display the numeric value. For instance in a year this would be "1970".
384
    Numeric,
385
    /// Display the two digit value. For instance in a year this would be "70".
386
    TwoDigit,
387
}
388

389
/// A text component for the `components::`[`Bag`]. It is used for the era and weekday.
390
///
391
/// <div class="stab unstable">
392
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
393
/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature
394
/// of the icu meta-crate. Use with caution.
395
/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
396
/// </div>
397
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2✔
398
#[cfg_attr(
399
    feature = "serde",
400
    derive(Serialize, Deserialize),
×
401
    serde(rename_all = "kebab-case")
402
)]
403
#[non_exhaustive]
404
pub enum Text {
405
    /// Display the long form of the text, such as "Wednesday" for the weekday.
406
    Long,
407
    /// Display the short form of the text, such as "Wed" for the weekday.
408
    Short,
409
    /// Display the narrow form of the text, such as "W" for the weekday.
410
    Narrow,
411
}
412

413
/// Options for displaying a Year for the `components::`[`Bag`].
414
///
415
/// <div class="stab unstable">
416
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
417
/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature
418
/// of the icu meta-crate. Use with caution.
419
/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
420
/// </div>
421
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4✔
422
#[cfg_attr(
423
    feature = "serde",
424
    derive(Serialize, Deserialize),
×
425
    serde(rename_all = "kebab-case")
426
)]
427
#[non_exhaustive]
428
pub enum Year {
429
    /// The numeric value of the year, such as "2018" for 2018-12-31.
430
    Numeric,
431
    /// The two-digit value of the year, such as "18" for 2018-12-31.
432
    TwoDigit,
433
    /// The numeric value of the year in "week-of-year", such as "2019" in
434
    /// "week 01 of 2019" for the week of 2018-12-31 according to the ISO calendar.
435
    NumericWeekOf,
436
    /// The numeric value of the year in "week-of-year", such as "19" in
437
    /// "week 01 '19" for the week of 2018-12-31 according to the ISO calendar.
438
    TwoDigitWeekOf,
439
}
440

441
/// Options for displaying a Month for the `components::`[`Bag`].
442
///
443
/// <div class="stab unstable">
444
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
445
/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature
446
/// of the icu meta-crate. Use with caution.
447
/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
448
/// </div>
449
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4✔
450
#[cfg_attr(
451
    feature = "serde",
452
    derive(Serialize, Deserialize),
×
453
    serde(rename_all = "kebab-case")
454
)]
455
#[non_exhaustive]
456
pub enum Month {
457
    /// The numeric value of the month, such as "4".
458
    Numeric,
459
    /// The two-digit value of the month, such as "04".
460
    TwoDigit,
461
    /// The long value of the month, such as "April".
462
    Long,
463
    /// The short value of the month, such as "Apr".
464
    Short,
465
    /// The narrow value of the month, such as "A".
466
    Narrow,
467
}
468

469
// Each enum variant is documented with the UTS 35 field information from:
470
// https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
471

472
/// Options for displaying the current week number for the `components::`[`Bag`].
473
///
474
/// Week numbers are relative to either a month or year, e.g. 'week 3 of January' or 'week 40 of 2000'.
475
///
476
/// <div class="stab unstable">
477
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
478
/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature
479
/// of the icu meta-crate. Use with caution.
480
/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
481
/// </div>
482
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
×
483
#[cfg_attr(
484
    feature = "serde",
485
    derive(Serialize, Deserialize),
×
486
    serde(rename_all = "kebab-case")
487
)]
488
#[non_exhaustive]
489
pub enum Week {
490
    /// The week of the month, such as the "3" in "week 3 of January".
491
    WeekOfMonth,
492
    /// The numeric value of the week of the year, such as the "8" in "week 8 of 2000".
493
    NumericWeekOfYear,
494
    /// The two-digit value of the week of the year, such as the "08" in "2000-W08".
495
    TwoDigitWeekOfYear,
496
}
497

498
/// Options for displaying the current day of the month or year.
499
///
500
/// <div class="stab unstable">
501
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
502
/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature
503
/// of the icu meta-crate. Use with caution.
504
/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
505
/// </div>
506
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4✔
507
#[cfg_attr(
508
    feature = "serde",
509
    derive(Serialize, Deserialize),
×
510
    serde(rename_all = "kebab-case")
511
)]
512
#[non_exhaustive]
513
pub enum Day {
514
    /// The numeric value of the day of month, such as the "2" in July 2 1984.
515
    NumericDayOfMonth,
516
    /// The two digit value of the day of month, such as the "02" in 1984-07-02.
517
    TwoDigitDayOfMonth,
518
    /// The day of week in this month, such as the "2" in 2nd Wednesday of July.
519
    DayOfWeekInMonth,
520
}
521

522
/// Options for displaying a time zone for the `components::`[`Bag`].
523
///
524
/// Note that the initial implementation is focusing on only supporting ECMA-402 compatible
525
/// options.
526
///
527
/// <div class="stab unstable">
528
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways,
529
/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature
530
/// of the icu meta-crate. Use with caution.
531
/// <a href="https://github.com/unicode-org/icu4x/issues/1317">#1317</a>
532
/// </div>
533
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
×
534
#[cfg_attr(
535
    feature = "serde",
536
    derive(Serialize, Deserialize),
×
537
    serde(rename_all = "kebab-case")
538
)]
539
#[non_exhaustive]
540
pub enum TimeZoneName {
541
    // UTS-35 fields: z..zzz
542
    /// Short localized form, without the location. (e.g.: PST, GMT-8)
543
    ShortSpecific,
544

545
    // UTS-35 fields: zzzz
546
    // Per UTS-35: [long form] specific non-location (falling back to long localized GMT)
547
    /// Long localized form, without the location (e.g., Pacific Standard Time, Nordamerikanische Westküsten-Normalzeit)
548
    LongSpecific,
549

550
    // UTS-35 fields: O, OOOO
551
    // Per UTS-35: The long localized GMT format. This is equivalent to the "OOOO" specifier
552
    // Per UTS-35: Short localized GMT format (e.g., GMT-8)
553
    // This enum variant is combining the two types of fields, as the CLDR specifies the preferred
554
    // hour-format for the locale, and ICU4X uses the preferred one.
555
    //   e.g.
556
    //   https://github.com/unicode-org/cldr-json/blob/c23635f13946292e40077fd62aee6a8e122e7689/cldr-json/cldr-dates-full/main/es-MX/timeZoneNames.json#L13
557
    /// Localized GMT format, in the locale's preferred hour format. (e.g., GMT-0800),
558
    GmtOffset,
559

560
    // UTS-35 fields: v
561
    //   * falling back to generic location (See UTS 35 for more specific rules)
562
    //   * falling back to short localized GMT
563
    /// Short generic non-location format (e.g.: PT, Los Angeles, Zeit).
564
    ShortGeneric,
565

566
    // UTS-35 fields: vvvv
567
    //  * falling back to generic location (See UTS 35 for more specific rules)
568
    //  * falling back to long localized GMT
569
    /// Long generic non-location format (e.g.: Pacific Time, Nordamerikanische Westküstenzeit),
570
    LongGeneric,
571
}
572

573
impl From<TimeZoneName> for Field {
574
    fn from(time_zone_name: TimeZoneName) -> Self {
12✔
575
        match time_zone_name {
12✔
576
            TimeZoneName::ShortSpecific => Field {
1✔
577
                symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerZ),
1✔
578
                length: FieldLength::One,
1✔
579
            },
1✔
580
            TimeZoneName::LongSpecific => Field {
1✔
581
                symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerZ),
1✔
582
                length: FieldLength::Wide,
1✔
583
            },
1✔
584
            TimeZoneName::GmtOffset => Field {
8✔
585
                symbol: FieldSymbol::TimeZone(fields::TimeZone::UpperO),
8✔
586
                length: FieldLength::Wide,
8✔
587
            },
8✔
588
            TimeZoneName::ShortGeneric => Field {
1✔
589
                symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV),
1✔
590
                length: FieldLength::One,
1✔
591
            },
1✔
592
            TimeZoneName::LongGeneric => Field {
1✔
593
                symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV),
1✔
594
                length: FieldLength::Wide,
1✔
595
            },
1✔
596
        }
597
    }
12✔
598
}
599

600
/// Get the resolved components for a TypedDateTimeFormatter, via the PatternPlurals. In the case of
601
/// plurals resolve off of the required `other` pattern.
602
impl<'data> From<&PatternPlurals<'data>> for Bag {
603
    fn from(other: &PatternPlurals) -> Self {
6✔
604
        let pattern = match other {
6✔
605
            PatternPlurals::SinglePattern(pattern) => pattern,
6✔
606
            PatternPlurals::MultipleVariants(plural_pattern) => &plural_pattern.other,
×
607
        };
608

609
        let mut bag: Bag = Default::default();
6✔
610

611
        // Transform the fields into components per:
612
        // https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
613
        for item in pattern.items.iter() {
55✔
614
            let field = match item {
49✔
615
                PatternItem::Field(ref field) => field,
25✔
616
                PatternItem::Literal(_) => continue,
617
            };
618
            match field.symbol {
25✔
619
                FieldSymbol::Era => {
1✔
620
                    bag.era = Some(match field.length {
2✔
621
                        FieldLength::One
622
                        | FieldLength::NumericOverride(_)
623
                        | FieldLength::TwoDigit
624
                        | FieldLength::Abbreviated => Text::Short,
1✔
625
                        FieldLength::Wide => Text::Long,
×
626
                        FieldLength::Narrow | FieldLength::Six | FieldLength::Fixed(_) => {
627
                            Text::Narrow
×
628
                        }
629
                    });
630
                }
631
                FieldSymbol::Year(year) => {
4✔
632
                    bag.year = Some(match year {
8✔
633
                        fields::Year::Calendar => match field.length {
4✔
634
                            FieldLength::TwoDigit => Year::TwoDigit,
×
635
                            _ => Year::Numeric,
4✔
636
                        },
637
                        fields::Year::WeekOf => match field.length {
×
638
                            FieldLength::TwoDigit => Year::TwoDigitWeekOf,
×
639
                            _ => Year::NumericWeekOf,
×
640
                        },
641
                        _ => todo!("TODO(#3762): Add support for U and r"),
×
642
                    });
643
                }
4✔
644
                FieldSymbol::Month(_) => {
4✔
645
                    // `Month::StandAlone` is only relevant in the pattern, so only differentiate
646
                    // on the field length.
647
                    bag.month = Some(match field.length {
8✔
648
                        FieldLength::One => Month::Numeric,
×
649
                        FieldLength::NumericOverride(_) => Month::Numeric,
×
650
                        FieldLength::TwoDigit => Month::TwoDigit,
×
651
                        FieldLength::Abbreviated => Month::Short,
4✔
652
                        FieldLength::Wide => Month::Long,
×
653
                        FieldLength::Narrow | FieldLength::Six | FieldLength::Fixed(_) => {
654
                            Month::Narrow
×
655
                        }
656
                    });
657
                }
658
                FieldSymbol::Week(week) => {
×
659
                    bag.week = Some(match week {
×
660
                        fields::Week::WeekOfYear => match field.length {
×
661
                            FieldLength::TwoDigit => Week::TwoDigitWeekOfYear,
×
662
                            _ => Week::NumericWeekOfYear,
×
663
                        },
664
                        fields::Week::WeekOfMonth => Week::WeekOfMonth,
×
665
                    });
666
                }
×
667
                FieldSymbol::Day(day) => {
4✔
668
                    bag.day = Some(match day {
8✔
669
                        fields::Day::DayOfMonth => match field.length {
4✔
670
                            FieldLength::TwoDigit => Day::TwoDigitDayOfMonth,
1✔
671
                            _ => Day::NumericDayOfMonth,
3✔
672
                        },
673
                        fields::Day::DayOfYear => unimplemented!("fields::Day::DayOfYear #591"),
×
674
                        fields::Day::DayOfWeekInMonth => Day::DayOfWeekInMonth,
×
675
                        fields::Day::ModifiedJulianDay => {
676
                            unimplemented!("fields::Day::ModifiedJulianDay")
×
677
                        }
678
                    });
679
                }
4✔
680
                FieldSymbol::Weekday(weekday) => {
1✔
681
                    bag.weekday = Some(match weekday {
2✔
682
                        fields::Weekday::Format => match field.length {
1✔
683
                            FieldLength::One | FieldLength::TwoDigit | FieldLength::Abbreviated => {
684
                                Text::Short
×
685
                            }
686
                            FieldLength::Wide => Text::Long,
1✔
687
                            _ => Text::Narrow,
×
688
                        },
689
                        fields::Weekday::StandAlone => match field.length {
×
690
                            FieldLength::One
691
                            | FieldLength::TwoDigit
692
                            | FieldLength::NumericOverride(_) => {
693
                                // Stand-alone fields also support a numeric 1 digit per UTS-35, but there is
694
                                // no way to request it using the current system. As of 2021-12-06
695
                                // no skeletons resolve to patterns containing this symbol.
696
                                //
697
                                // All resolved patterns from cldr-json:
698
                                // https://github.com/gregtatum/cldr-json/blob/d4779f9611a4cc1e3e6a0a4597e92ead32d9f118/stand-alone-week.js
699
                                //     'ccc',
700
                                //     'ccc d. MMM',
701
                                //     'ccc d. MMMM',
702
                                //     'cccc d. MMMM y',
703
                                //     'd, ccc',
704
                                //     'cccနေ့',
705
                                //     'ccc, d MMM',
706
                                //     "ccc, d 'de' MMMM",
707
                                //     "ccc, d 'de' MMMM 'de' y",
708
                                //     'ccc, h:mm B',
709
                                //     'ccc, h:mm:ss B',
710
                                //     'ccc, d',
711
                                //     "ccc, dd.MM.y 'г'.",
712
                                //     'ccc, d.MM.y',
713
                                //     'ccc, MMM d. y'
714
                                unimplemented!("Numeric stand-alone fields are not supported.")
×
715
                            }
716
                            FieldLength::Abbreviated => Text::Short,
×
717
                            FieldLength::Wide => Text::Long,
×
718
                            FieldLength::Narrow | FieldLength::Six | FieldLength::Fixed(_) => {
719
                                Text::Narrow
×
720
                            }
721
                        },
722
                        fields::Weekday::Local => unimplemented!("fields::Weekday::Local"),
×
723
                    });
724
                }
1✔
725
                FieldSymbol::DayPeriod(_) => {
726
                    // Day period does not affect the resolved components.
727
                }
728
                FieldSymbol::Hour(hour) => {
3✔
729
                    bag.hour = Some(match field.length {
6✔
730
                        FieldLength::TwoDigit => Numeric::TwoDigit,
1✔
731
                        _ => Numeric::Numeric,
2✔
732
                    });
733
                    bag.preferences = Some(preferences::Bag {
3✔
734
                        hour_cycle: Some(match hour {
6✔
735
                            fields::Hour::H11 => preferences::HourCycle::H11,
×
736
                            fields::Hour::H12 => preferences::HourCycle::H12,
1✔
737
                            fields::Hour::H23 => preferences::HourCycle::H23,
1✔
738
                            fields::Hour::H24 => preferences::HourCycle::H24,
1✔
739
                        }),
740
                    });
741
                }
3✔
742
                FieldSymbol::Minute => {
3✔
743
                    bag.minute = Some(match field.length {
6✔
744
                        FieldLength::TwoDigit => Numeric::TwoDigit,
3✔
745
                        _ => Numeric::Numeric,
×
746
                    });
747
                }
748
                FieldSymbol::Second(second) => {
4✔
749
                    match second {
4✔
750
                        fields::Second::Second => {
3✔
751
                            bag.second = Some(match field.length {
6✔
752
                                FieldLength::TwoDigit => Numeric::TwoDigit,
3✔
753
                                _ => Numeric::Numeric,
×
754
                            });
755
                        }
756
                        fields::Second::FractionalSecond => {
757
                            if let FieldLength::Fixed(p) = field.length {
1✔
758
                                if p > 0 {
1✔
759
                                    bag.fractional_second = Some(p);
1✔
760
                                }
761
                            }
762
                        }
763
                        fields::Second::Millisecond => {
764
                            // fields::Second::Millisecond is not implemented (#1834)
765
                        }
766
                    }
767
                }
768
                FieldSymbol::TimeZone(time_zone_name) => {
×
769
                    bag.time_zone_name = Some(match time_zone_name {
×
770
                        fields::TimeZone::LowerZ => match field.length {
×
771
                            FieldLength::One => TimeZoneName::ShortSpecific,
×
772
                            _ => TimeZoneName::LongSpecific,
×
773
                        },
774
                        fields::TimeZone::LowerV => match field.length {
×
775
                            FieldLength::One => TimeZoneName::ShortGeneric,
×
776
                            _ => TimeZoneName::LongGeneric,
×
777
                        },
778
                        fields::TimeZone::UpperO => TimeZoneName::GmtOffset,
×
779
                        fields::TimeZone::UpperZ => unimplemented!("fields::TimeZone::UpperZ"),
×
780
                        fields::TimeZone::UpperV => unimplemented!("fields::TimeZone::UpperV"),
×
781
                        fields::TimeZone::LowerX => unimplemented!("fields::TimeZone::LowerX"),
×
782
                        fields::TimeZone::UpperX => unimplemented!("fields::TimeZone::UpperX"),
×
783
                    });
784
                }
×
785
            }
786
        }
787

788
        bag
789
    }
6✔
790
}
791

792
#[cfg(test)]
793
mod test {
794
    use super::*;
795

796
    // Shorten these for terser tests.
797
    type Symbol = FieldSymbol;
798
    type Length = FieldLength;
799

800
    #[test]
801
    fn test_component_bag_to_vec_field() {
2✔
802
        let bag = Bag {
1✔
803
            year: Some(Year::Numeric),
1✔
804
            month: Some(Month::Long),
1✔
805
            week: Some(Week::WeekOfMonth),
1✔
806
            day: Some(Day::NumericDayOfMonth),
1✔
807

808
            hour: Some(Numeric::Numeric),
1✔
809
            minute: Some(Numeric::Numeric),
1✔
810
            second: Some(Numeric::Numeric),
1✔
811
            fractional_second: Some(3),
1✔
812

813
            ..Default::default()
1✔
814
        };
815
        assert_eq!(
1✔
816
            bag.to_vec_fields(),
1✔
817
            [
1✔
818
                (Symbol::Year(fields::Year::Calendar), Length::One).into(),
1✔
819
                (Symbol::Month(fields::Month::Format), Length::Wide).into(),
1✔
820
                (Symbol::Week(fields::Week::WeekOfMonth), Length::One).into(),
1✔
821
                (Symbol::Day(fields::Day::DayOfMonth), Length::One).into(),
1✔
822
                (Symbol::Hour(fields::Hour::H23), Length::One).into(),
1✔
823
                (Symbol::Minute, Length::One).into(),
1✔
824
                (Symbol::Second(fields::Second::Second), Length::One).into(),
1✔
825
                (
1✔
826
                    Symbol::Second(fields::Second::FractionalSecond),
1✔
827
                    Length::Fixed(3)
1✔
828
                )
829
                    .into(),
830
            ]
831
        );
832
    }
2✔
833

834
    #[test]
835
    fn test_component_bag_to_vec_field2() {
2✔
836
        let bag = Bag {
1✔
837
            year: Some(Year::Numeric),
1✔
838
            month: Some(Month::TwoDigit),
1✔
839
            day: Some(Day::NumericDayOfMonth),
1✔
840
            ..Default::default()
1✔
841
        };
842
        assert_eq!(
1✔
843
            bag.to_vec_fields(),
1✔
844
            [
1✔
845
                (Symbol::Year(fields::Year::Calendar), Length::One).into(),
1✔
846
                (Symbol::Month(fields::Month::Format), Length::TwoDigit).into(),
1✔
847
                (Symbol::Day(fields::Day::DayOfMonth), Length::One).into(),
1✔
848
            ]
849
        );
850
    }
2✔
851
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc