diff --git a/Cargo.lock b/Cargo.lock index 54eb86ed2e3..43cc082619b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1369,6 +1369,7 @@ dependencies = [ "iai", "icu", "icu_benchmark_macros", + "icu_provider", "litemap", "postcard", "potential_utf", diff --git a/README.md b/README.md index ff0305fb9e3..7862ee18fd3 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,12 @@ icu = "1.5.0" ```rust use icu::calendar::DateTime; -use icu::datetime::{DateTimeFormatter, NeoSkeletonLength, fieldset::YMDHMS}; +use icu::datetime::{DateTimeFormatter, NeoSkeletonLength, fieldset::YMDT}; use icu::locale::locale; let dtf = DateTimeFormatter::try_new( &locale!("es").into(), - YMDHMS::long() + YMDT::long() ) .expect("locale should be present in compiled data"); diff --git a/components/calendar/src/buddhist.rs b/components/calendar/src/buddhist.rs index d9e50ce99a7..7d7876c9f6e 100644 --- a/components/calendar/src/buddhist.rs +++ b/components/calendar/src/buddhist.rs @@ -224,6 +224,7 @@ fn iso_year_as_buddhist(year: i32) -> types::YearInfo { standard_era: tinystr!(16, "buddhist").into(), formatting_era: types::FormattingEra::Index(0, tinystr!(16, "BE")), era_year: buddhist_year, + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } diff --git a/components/calendar/src/coptic.rs b/components/calendar/src/coptic.rs index af47758567c..ffdd851221f 100644 --- a/components/calendar/src/coptic.rs +++ b/components/calendar/src/coptic.rs @@ -313,6 +313,7 @@ fn year_as_coptic(year: i32) -> types::YearInfo { standard_era: tinystr!(16, "coptic").into(), formatting_era: types::FormattingEra::Index(1, tinystr!(16, "AD")), era_year: year, + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } else { @@ -322,6 +323,7 @@ fn year_as_coptic(year: i32) -> types::YearInfo { standard_era: tinystr!(16, "coptic-inverse").into(), formatting_era: types::FormattingEra::Index(0, tinystr!(16, "BD")), era_year: 1 - year, + ambiguity: types::YearAmbiguity::EraAndCenturyRequired, }, ) } diff --git a/components/calendar/src/ethiopian.rs b/components/calendar/src/ethiopian.rs index 86055806f11..5415e6b3dc4 100644 --- a/components/calendar/src/ethiopian.rs +++ b/components/calendar/src/ethiopian.rs @@ -304,6 +304,7 @@ impl Ethiopian { standard_era: tinystr!(16, "ethioaa").into(), formatting_era: types::FormattingEra::Index(0, tinystr!(16, "Anno Mundi")), era_year: year + AMETE_ALEM_OFFSET, + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } else if year > 0 { @@ -313,6 +314,7 @@ impl Ethiopian { standard_era: tinystr!(16, "ethiopic").into(), formatting_era: types::FormattingEra::Index(2, tinystr!(16, "Incarnation")), era_year: year, + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } else { @@ -322,6 +324,7 @@ impl Ethiopian { standard_era: tinystr!(16, "ethiopic-inverse").into(), formatting_era: types::FormattingEra::Index(1, tinystr!(16, "Pre-Incarnation")), era_year: 1 - year, + ambiguity: types::YearAmbiguity::EraAndCenturyRequired, }, ) } diff --git a/components/calendar/src/gregorian.rs b/components/calendar/src/gregorian.rs index 8f039697985..28d40dda42a 100644 --- a/components/calendar/src/gregorian.rs +++ b/components/calendar/src/gregorian.rs @@ -238,6 +238,12 @@ fn year_as_gregorian(year: i32) -> types::YearInfo { standard_era: tinystr!(16, "gregory").into(), formatting_era: types::FormattingEra::Index(1, tinystr!(16, "CE")), era_year: year, + ambiguity: match year { + ..=999 => types::YearAmbiguity::EraAndCenturyRequired, + 1000..=1949 => types::YearAmbiguity::CenturyRequired, + 1950..=2049 => types::YearAmbiguity::Unambiguous, + 2050.. => types::YearAmbiguity::CenturyRequired, + }, }, ) } else { @@ -247,6 +253,7 @@ fn year_as_gregorian(year: i32) -> types::YearInfo { standard_era: tinystr!(16, "gregory-inverse").into(), formatting_era: types::FormattingEra::Index(0, tinystr!(16, "BCE")), era_year: 1_i32.saturating_sub(year), + ambiguity: types::YearAmbiguity::EraAndCenturyRequired, }, ) } diff --git a/components/calendar/src/hebrew.rs b/components/calendar/src/hebrew.rs index ede81b7ca99..aafc5764511 100644 --- a/components/calendar/src/hebrew.rs +++ b/components/calendar/src/hebrew.rs @@ -344,6 +344,7 @@ impl Hebrew { formatting_era: types::FormattingEra::Index(0, tinystr!(16, "AM")), standard_era: tinystr!(16, "hebrew").into(), era_year: civil_year, + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } diff --git a/components/calendar/src/indian.rs b/components/calendar/src/indian.rs index 03427c7309f..48107f89be7 100644 --- a/components/calendar/src/indian.rs +++ b/components/calendar/src/indian.rs @@ -240,6 +240,7 @@ fn year_as_saka(year: i32) -> types::YearInfo { formatting_era: types::FormattingEra::Index(0, tinystr!(16, "Saka")), standard_era: tinystr!(16, "saka").into(), era_year: year, + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } diff --git a/components/calendar/src/islamic.rs b/components/calendar/src/islamic.rs index 6fc6db81e80..69708c4c2a0 100644 --- a/components/calendar/src/islamic.rs +++ b/components/calendar/src/islamic.rs @@ -61,6 +61,7 @@ fn year_as_islamic(standard_era: tinystr::TinyStr16, year: i32) -> types::YearIn formatting_era: types::FormattingEra::Index(0, tinystr!(16, "AH")), standard_era: standard_era.into(), era_year: year, + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } diff --git a/components/calendar/src/iso.rs b/components/calendar/src/iso.rs index cd823611f39..0b99ef2c063 100644 --- a/components/calendar/src/iso.rs +++ b/components/calendar/src/iso.rs @@ -370,6 +370,7 @@ impl Iso { formatting_era: types::FormattingEra::Index(0, tinystr!(16, "")), standard_era: tinystr!(16, "default").into(), era_year: year, + ambiguity: types::YearAmbiguity::Unambiguous, }, ) } diff --git a/components/calendar/src/japanese.rs b/components/calendar/src/japanese.rs index 2f90facd5a9..7e7d67b62bc 100644 --- a/components/calendar/src/japanese.rs +++ b/components/calendar/src/japanese.rs @@ -278,6 +278,7 @@ impl Calendar for Japanese { formatting_era: types::FormattingEra::Code(date.era.into()), standard_era: date.era.into(), era_year: date.adjusted_year, + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } @@ -505,11 +506,6 @@ impl Date { .new_japanese_date_inner(era, year, month, day)?; Ok(Date::from_raw(inner, japanext_calendar)) } - - #[doc(hidden)] // for testing - pub fn into_japanese_date(self) -> Date { - Date::from_raw(self.inner, self.calendar.0) - } } impl DateTime { diff --git a/components/calendar/src/julian.rs b/components/calendar/src/julian.rs index 2c83ae56212..0af197576dd 100644 --- a/components/calendar/src/julian.rs +++ b/components/calendar/src/julian.rs @@ -228,6 +228,7 @@ fn year_as_julian(year: i32) -> types::YearInfo { standard_era: tinystr!(16, "julian").into(), formatting_era: types::FormattingEra::Index(1, tinystr!(16, "AD")), era_year: year, + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } else { @@ -237,6 +238,7 @@ fn year_as_julian(year: i32) -> types::YearInfo { standard_era: tinystr!(16, "julian-inverse").into(), formatting_era: types::FormattingEra::Index(0, tinystr!(16, "BC")), era_year: 1_i32.saturating_sub(year), + ambiguity: types::YearAmbiguity::EraAndCenturyRequired, }, ) } diff --git a/components/calendar/src/persian.rs b/components/calendar/src/persian.rs index 75e4cb1d93a..bc3f7d91c27 100644 --- a/components/calendar/src/persian.rs +++ b/components/calendar/src/persian.rs @@ -228,6 +228,7 @@ impl Persian { standard_era: tinystr!(16, "persian").into(), formatting_era: types::FormattingEra::Index(0, tinystr!(16, "AH")), era_year: year, + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } diff --git a/components/calendar/src/roc.rs b/components/calendar/src/roc.rs index 5dae4d2aa81..e3662b1cc7e 100644 --- a/components/calendar/src/roc.rs +++ b/components/calendar/src/roc.rs @@ -275,6 +275,7 @@ pub(crate) fn year_as_roc(year: i64) -> types::YearInfo { standard_era: tinystr!(16, "roc").into(), formatting_era: types::FormattingEra::Index(1, tinystr!(16, "ROC")), era_year: year_i32.saturating_sub(ROC_ERA_OFFSET), + ambiguity: types::YearAmbiguity::CenturyRequired, }, ) } else { @@ -284,6 +285,7 @@ pub(crate) fn year_as_roc(year: i64) -> types::YearInfo { standard_era: tinystr!(16, "roc-inverse").into(), formatting_era: types::FormattingEra::Index(0, tinystr!(16, "B. ROC")), era_year: (ROC_ERA_OFFSET + 1).saturating_sub(year_i32), + ambiguity: types::YearAmbiguity::EraAndCenturyRequired, }, ) } diff --git a/components/calendar/src/types.rs b/components/calendar/src/types.rs index 50555900346..b27e9f43292 100644 --- a/components/calendar/src/types.rs +++ b/components/calendar/src/types.rs @@ -84,6 +84,14 @@ impl YearInfo { } } + /// Get the year ambiguity. + pub fn year_ambiguity(self) -> YearAmbiguity { + match self.kind { + YearKind::Cyclic(_) => YearAmbiguity::EraRequired, + YearKind::Era(e) => e.ambiguity, + } + } + /// Get *some* year number that can be displayed /// /// Gets the eraYear for era dates, otherwise falls back to Extended Year @@ -123,6 +131,23 @@ impl YearInfo { } } +/// Defines whether the era or century is required to interpret the year. +/// +/// For example 2024 AD can be formatted as `2024`, or even `24`, but 1931 AD +/// should not be formatted as `31`, and 2024 BC should not be formatted as `2024`. +#[derive(Copy, Clone, Debug, PartialEq)] +#[allow(clippy::exhaustive_enums)] // logically complete +pub enum YearAmbiguity { + /// The year is unambiguous without a century or era. + Unambiguous, + /// The century is required, the era may be included. + CenturyRequired, + /// The era is required, the century may be included. + EraRequired, + /// The century and era are required. + EraAndCenturyRequired, +} + /// Information about the era as usable for formatting /// /// This is optimized for storing datetime formatting data. @@ -146,7 +171,7 @@ pub enum FormattingEra { impl FormattingEra { /// Get a fallback era name suitable for display to the user when the real era name is not availabe - pub fn fallback_era(self) -> TinyStr16 { + pub fn fallback_name(self) -> TinyStr16 { match self { Self::Index(_idx, fallback) => fallback, Self::Code(era) => era.0, @@ -172,6 +197,8 @@ pub struct EraYear { pub standard_era: Era, /// The numeric year in that era pub era_year: i32, + /// The ambiguity when formatting this year + pub ambiguity: YearAmbiguity, } /// Year information for a year that is specified as a cyclic year @@ -466,6 +493,12 @@ macro_rules! dt_unit { pub fn try_sub(self, other: $storage) -> Option { self.0.checked_sub(other).map(Self) } + + /// Returns whether the value is zero. + #[inline] + pub fn is_zero(self) -> bool { + self.0 == 0 + } } }; } diff --git a/components/calendar/src/week_of.rs b/components/calendar/src/week_of.rs index 1536e906a9d..ad1e39dc5dc 100644 --- a/components/calendar/src/week_of.rs +++ b/components/calendar/src/week_of.rs @@ -34,10 +34,6 @@ impl WeekCalculator { icu_provider::gen_any_buffer_data_constructors!( (locale) -> error: DataError, /// Creates a new [`WeekCalculator`] from compiled data. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) ); #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)] diff --git a/components/collator/Cargo.toml b/components/collator/Cargo.toml index 9747c4ddc80..d0d83defd15 100644 --- a/components/collator/Cargo.toml +++ b/components/collator/Cargo.toml @@ -22,6 +22,7 @@ all-features = true displaydoc = { workspace = true } icu_collections = { workspace = true } icu_normalizer = { workspace = true } +icu_locale_core = { workspace = true } icu_properties = { workspace = true } icu_provider = { workspace = true, features = ["macros"] } utf8_iter = { workspace = true } diff --git a/components/collator/README.md b/components/collator/README.md index ef01efad01a..a36268d1d7f 100644 --- a/components/collator/README.md +++ b/components/collator/README.md @@ -23,18 +23,16 @@ use core::cmp::Ordering; use icu::collator::*; use icu::locale::locale; -let locale_es = locale!("es-u-co-trad").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Primary); -let collator_es = Collator::try_new(&locale_es, options).unwrap(); +let collator_es = Collator::try_new(locale!("es-u-co-trad").into(), options).unwrap(); // "pollo" > "polvo" in traditional Spanish assert_eq!(collator_es.compare("pollo", "polvo"), Ordering::Greater); -let locale_en = locale!("en").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Primary); -let collator_en = Collator::try_new(&locale_en, options).unwrap(); +let collator_en = Collator::try_new(locale!("en").into(), options).unwrap(); // "pollo" < "polvo" according to English rules assert_eq!(collator_en.compare("pollo", "polvo"), Ordering::Less); @@ -58,7 +56,7 @@ use icu::collator::*; let mut options_l1 = CollatorOptions::default(); options_l1.strength = Some(Strength::Primary); let collator_l1 = - Collator::try_new(&Default::default(), options_l1).unwrap(); + Collator::try_new(Default::default(), options_l1).unwrap(); assert_eq!(collator_l1.compare("a", "b"), Ordering::Less); // primary assert_eq!(collator_l1.compare("as", "às"), Ordering::Equal); // secondary @@ -72,7 +70,7 @@ assert_eq!(collator_l1.compare("A", "Ⓐ"), Ordering::Equal); let mut options_l2 = CollatorOptions::default(); options_l2.strength = Some(Strength::Secondary); let collator_l2 = - Collator::try_new(&Default::default(), options_l2).unwrap(); + Collator::try_new(Default::default(), options_l2).unwrap(); assert_eq!(collator_l2.compare("a", "b"), Ordering::Less); // primary assert_eq!(collator_l2.compare("as", "às"), Ordering::Less); // secondary @@ -86,7 +84,7 @@ assert_eq!(collator_l2.compare("A", "Ⓐ"), Ordering::Equal); let mut options_l3 = CollatorOptions::default(); options_l3.strength = Some(Strength::Tertiary); let collator_l3 = - Collator::try_new(&Default::default(), options_l3).unwrap(); + Collator::try_new(Default::default(), options_l3).unwrap(); assert_eq!(collator_l3.compare("a", "b"), Ordering::Less); // primary assert_eq!(collator_l3.compare("as", "às"), Ordering::Less); // secondary @@ -118,7 +116,7 @@ let mut options_3n = CollatorOptions::default(); options_3n.strength = Some(Strength::Tertiary); options_3n.alternate_handling = Some(AlternateHandling::NonIgnorable); let collator_3n = - Collator::try_new(&Default::default(), options_3n).unwrap(); + Collator::try_new(Default::default(), options_3n).unwrap(); assert_eq!(collator_3n.compare("di Silva", "Di Silva"), Ordering::Less); assert_eq!(collator_3n.compare("Di Silva", "diSilva"), Ordering::Less); @@ -133,7 +131,7 @@ let mut options_3s = CollatorOptions::default(); options_3s.strength = Some(Strength::Tertiary); options_3s.alternate_handling = Some(AlternateHandling::Shifted); let collator_3s = - Collator::try_new(&Default::default(), options_3s).unwrap(); + Collator::try_new(Default::default(), options_3s).unwrap(); assert_eq!(collator_3s.compare("di Silva", "diSilva"), Ordering::Equal); assert_eq!(collator_3s.compare("diSilva", "Di Silva"), Ordering::Less); @@ -144,7 +142,7 @@ let mut options_4s = CollatorOptions::default(); options_4s.strength = Some(Strength::Quaternary); options_4s.alternate_handling = Some(AlternateHandling::Shifted); let collator_4s = - Collator::try_new(&Default::default(), options_4s).unwrap(); + Collator::try_new(Default::default(), options_4s).unwrap(); assert_eq!(collator_4s.compare("di Silva", "diSilva"), Ordering::Less); assert_eq!(collator_4s.compare("diSilva", "Di Silva"), Ordering::Less); @@ -167,7 +165,7 @@ let mut options = CollatorOptions::default(); options.strength = Some(Strength::Primary); options.case_level = Some(CaseLevel::Off); let primary = - Collator::try_new(&Default::default(), + Collator::try_new(Default::default(), options).unwrap(); assert_eq!(primary.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Equal); @@ -179,7 +177,7 @@ assert_eq!(primary.compare("dejavu", "déjavu"), Ordering::Equal); options.strength = Some(Strength::Primary); options.case_level = Some(CaseLevel::On); let primary_and_case = - Collator::try_new(&Default::default(), + Collator::try_new(Default::default(), options).unwrap(); assert_eq!(primary_and_case.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Less); @@ -191,7 +189,7 @@ assert_eq!(primary_and_case.compare("dejavu", "déjavu"), Ordering::Equal); options.strength = Some(Strength::Secondary); options.case_level = Some(CaseLevel::On); let secondary_and_case = - Collator::try_new(&Default::default(), + Collator::try_new(Default::default(), options).unwrap(); assert_eq!(secondary_and_case.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Less); @@ -203,7 +201,7 @@ assert_eq!(secondary_and_case.compare("dejavu", "déjavu"), Ordering::Less); // options.strength = Some(Strength::Tertiary); options.case_level = Some(CaseLevel::Off); let tertiary = - Collator::try_new(&Default::default(), + Collator::try_new(Default::default(), options).unwrap(); assert_eq!(tertiary.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Less); @@ -235,7 +233,7 @@ use icu::collator::*; let mut options_num_off = CollatorOptions::default(); options_num_off.numeric = Some(Numeric::Off); let collator_num_off = - Collator::try_new(&Default::default(), options_num_off).unwrap(); + Collator::try_new(Default::default(), options_num_off).unwrap(); assert_eq!(collator_num_off.compare("a10b", "a2b"), Ordering::Less); // Numerical sorting on @@ -243,7 +241,7 @@ assert_eq!(collator_num_off.compare("a10b", "a2b"), Ordering::Less); let mut options_num_on = CollatorOptions::default(); options_num_on.numeric = Some(Numeric::On); let collator_num_on = - Collator::try_new(&Default::default(), options_num_on).unwrap(); + Collator::try_new(Default::default(), options_num_on).unwrap(); assert_eq!(collator_num_on.compare("a10b", "a2b"), Ordering::Greater); ``` diff --git a/components/collator/benches/bench.rs b/components/collator/benches/bench.rs index ca3f51662ee..c94d2738e09 100644 --- a/components/collator/benches/bench.rs +++ b/components/collator/benches/bench.rs @@ -5,15 +5,7 @@ use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use icu::collator::*; -use icu::locale::Locale; -use icu_provider::DataLocale; - -fn to_data_locale(locale_str: &str) -> DataLocale { - locale_str - .parse::() - .expect("Failed to parse locale") - .into() -} +use icu::locale::locale; pub fn collator_with_locale(criterion: &mut Criterion) { // Load file content in reverse order vector. @@ -99,36 +91,36 @@ pub fn collator_with_locale(criterion: &mut Criterion) { Strength::Identical, ]; let performance_parameters = [ - (to_data_locale("en_US"), vec![&content_latin], &all_strength), - (to_data_locale("da_DK"), vec![&content_latin], &all_strength), - (to_data_locale("fr_CA"), vec![&content_latin], &all_strength), + (locale!("en-US"), vec![&content_latin], &all_strength), + (locale!("da-DK"), vec![&content_latin], &all_strength), + (locale!("fr-CA"), vec![&content_latin], &all_strength), ( - to_data_locale("ja_JP"), + locale!("ja-JP"), vec![&content_latin, &content_jp_h, &content_jp_k, &content_asian], &all_strength, ), ( - to_data_locale("zh-u-co-pinyin"), + locale!("zh-u-co-pinyin"), vec![&content_latin, &content_chinese], &all_strength, ), // zh_CN ( - to_data_locale("zh-u-co-stroke"), + locale!("zh-u-co-stroke"), vec![&content_latin, &content_chinese], &all_strength, ), // zh_TW ( - to_data_locale("ru_RU"), + locale!("ru-RU"), vec![&content_latin, &content_russian], &all_strength, ), ( - to_data_locale("th"), + locale!("th"), vec![&content_latin, &content_thai], &all_strength, ), ( - to_data_locale("ko_KR"), + locale!("ko-KR"), vec![&content_latin, &content_korean], &all_strength, ), @@ -156,7 +148,9 @@ pub fn collator_with_locale(criterion: &mut Criterion) { for (index, strength) in benched_strength.iter().enumerate() { let mut options = CollatorOptions::default(); options.strength = Some(*strength); - let collator = Collator::try_new(&locale_under_bench, options).unwrap(); + let collator = + Collator::try_new(CollatorPreferences::from(&locale_under_bench), options) + .unwrap(); // ICU4X collator performance, sort is locale-aware group.bench_function( BenchmarkId::new( diff --git a/components/collator/src/comparison.rs b/components/collator/src/comparison.rs index e1c22fb8637..7a3efedd73f 100644 --- a/components/collator/src/comparison.rs +++ b/components/collator/src/comparison.rs @@ -70,11 +70,22 @@ struct LocaleSpecificDataHolder { lithuanian_dot_above: bool, } +icu_locale_core::preferences::define_preferences!( + /// The preferences for collation. + CollatorPreferences, + { + /// The collation type. This corresponds to the `-u-co` BCP-47 tag. + collation_type: icu_locale_core::preferences::extensions::unicode::keywords::CollationType + } +); + +impl Copy for CollatorPreferences {} + impl LocaleSpecificDataHolder { /// The constructor code reused between owned and borrowed cases. fn try_new_unstable_internal( provider: &D, - locale: &DataLocale, + prefs: CollatorPreferences, options: CollatorOptions, ) -> Result where @@ -84,15 +95,19 @@ impl LocaleSpecificDataHolder { + DataProvider + ?Sized, { - let id = DataIdentifierBorrowed::for_marker_attributes_and_locale( - DataMarkerAttributes::from_str_or_panic( - locale.get_single_unicode_ext("co").unwrap_or_default(), - ), - locale, - ); + let marker_attributes = prefs + .collation_type + .as_ref() + // all collation types are valid marker attributes + .map(|c| DataMarkerAttributes::from_str_or_panic(c.as_str())) + .unwrap_or_default(); + + let data_locale = + DataLocale::from_preferences_locale::(prefs.locale_prefs); + let id = DataIdentifierCow::from_borrowed_and_owned(marker_attributes, data_locale.clone()); let req = DataRequest { - id, + id: id.as_borrowed(), metadata: { let mut metadata = DataRequestMetadata::default(); metadata.silent = true; @@ -100,8 +115,11 @@ impl LocaleSpecificDataHolder { }, }; + let fallback_id = + DataIdentifierCow::from_borrowed_and_owned(Default::default(), data_locale); + let fallback_req = DataRequest { - id: DataIdentifierBorrowed::for_locale(locale), + id: fallback_id.as_borrowed(), ..Default::default() }; @@ -228,14 +246,14 @@ impl Collator { /// Creates `CollatorBorrowed` for the given locale and options from compiled data. #[cfg(feature = "compiled_data")] pub fn try_new( - locale: &DataLocale, + prefs: CollatorPreferences, options: CollatorOptions, ) -> Result, DataError> { - CollatorBorrowed::try_new(locale, options) + CollatorBorrowed::try_new(prefs, options) } icu_provider::gen_any_buffer_data_constructors!( - (locale, options: CollatorOptions) -> error: DataError, + (prefs: CollatorPreferences, options: CollatorOptions) -> error: DataError, functions: [ try_new: skip, try_new_with_any_provider, @@ -248,7 +266,7 @@ impl Collator { #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)] pub fn try_new_unstable( provider: &D, - locale: &DataLocale, + prefs: CollatorPreferences, options: CollatorOptions, ) -> Result where @@ -270,7 +288,7 @@ impl Collator { provider.load(Default::default())?.payload, provider.load(Default::default())?.payload, || provider.load(Default::default()).map(|r| r.payload), - locale, + prefs, options, ) } @@ -286,7 +304,7 @@ impl Collator { DataPayload, DataError, >, - locale: &DataLocale, + prefs: CollatorPreferences, options: CollatorOptions, ) -> Result where @@ -298,7 +316,7 @@ impl Collator { + ?Sized, { let locale_dependent = - LocaleSpecificDataHolder::try_new_unstable_internal(provider, locale, options)?; + LocaleSpecificDataHolder::try_new_unstable_internal(provider, prefs, options)?; // TODO: redesign Korean search collation handling if jamo.get().ce32s.len() != JAMO_COUNT { @@ -355,9 +373,13 @@ pub struct CollatorBorrowed<'a> { impl CollatorBorrowed<'static> { /// Creates a collator for the given locale and options from compiled data. #[cfg(feature = "compiled_data")] - pub fn try_new(locale: &DataLocale, options: CollatorOptions) -> Result { + pub fn try_new( + prefs: CollatorPreferences, + options: CollatorOptions, + ) -> Result { // These are assigned to locals in order to keep the code after these assignments // copypaste-compatible with `Collator::try_new_unstable_internal`. + let provider = &crate::provider::Baked; let decompositions = icu_normalizer::provider::Baked::SINGLETON_CANONICAL_DECOMPOSITION_DATA_V2_MARKER; @@ -367,7 +389,7 @@ impl CollatorBorrowed<'static> { let jamo = crate::provider::Baked::SINGLETON_COLLATION_JAMO_V1_MARKER; let locale_dependent = - LocaleSpecificDataHolder::try_new_unstable_internal(provider, locale, options)?; + LocaleSpecificDataHolder::try_new_unstable_internal(provider, prefs, options)?; // TODO: redesign Korean search collation handling if jamo.ce32s.len() != JAMO_COUNT { diff --git a/components/collator/src/lib.rs b/components/collator/src/lib.rs index b098bf0dbda..d8ce49dc3cb 100644 --- a/components/collator/src/lib.rs +++ b/components/collator/src/lib.rs @@ -43,18 +43,16 @@ //! use icu::collator::*; //! use icu::locale::locale; //! -//! let locale_es = locale!("es-u-co-trad").into(); //! let mut options = CollatorOptions::default(); //! options.strength = Some(Strength::Primary); -//! let collator_es = Collator::try_new(&locale_es, options).unwrap(); +//! let collator_es = Collator::try_new(locale!("es-u-co-trad").into(), options).unwrap(); //! //! // "pollo" > "polvo" in traditional Spanish //! assert_eq!(collator_es.compare("pollo", "polvo"), Ordering::Greater); //! -//! let locale_en = locale!("en").into(); //! let mut options = CollatorOptions::default(); //! options.strength = Some(Strength::Primary); -//! let collator_en = Collator::try_new(&locale_en, options).unwrap(); +//! let collator_en = Collator::try_new(locale!("en").into(), options).unwrap(); //! //! // "pollo" < "polvo" according to English rules //! assert_eq!(collator_en.compare("pollo", "polvo"), Ordering::Less); @@ -78,7 +76,7 @@ //! let mut options_l1 = CollatorOptions::default(); //! options_l1.strength = Some(Strength::Primary); //! let collator_l1 = -//! Collator::try_new(&Default::default(), options_l1).unwrap(); +//! Collator::try_new(Default::default(), options_l1).unwrap(); //! //! assert_eq!(collator_l1.compare("a", "b"), Ordering::Less); // primary //! assert_eq!(collator_l1.compare("as", "às"), Ordering::Equal); // secondary @@ -92,7 +90,7 @@ //! let mut options_l2 = CollatorOptions::default(); //! options_l2.strength = Some(Strength::Secondary); //! let collator_l2 = -//! Collator::try_new(&Default::default(), options_l2).unwrap(); +//! Collator::try_new(Default::default(), options_l2).unwrap(); //! //! assert_eq!(collator_l2.compare("a", "b"), Ordering::Less); // primary //! assert_eq!(collator_l2.compare("as", "às"), Ordering::Less); // secondary @@ -106,7 +104,7 @@ //! let mut options_l3 = CollatorOptions::default(); //! options_l3.strength = Some(Strength::Tertiary); //! let collator_l3 = -//! Collator::try_new(&Default::default(), options_l3).unwrap(); +//! Collator::try_new(Default::default(), options_l3).unwrap(); //! //! assert_eq!(collator_l3.compare("a", "b"), Ordering::Less); // primary //! assert_eq!(collator_l3.compare("as", "às"), Ordering::Less); // secondary @@ -138,7 +136,7 @@ //! options_3n.strength = Some(Strength::Tertiary); //! options_3n.alternate_handling = Some(AlternateHandling::NonIgnorable); //! let collator_3n = -//! Collator::try_new(&Default::default(), options_3n).unwrap(); +//! Collator::try_new(Default::default(), options_3n).unwrap(); //! //! assert_eq!(collator_3n.compare("di Silva", "Di Silva"), Ordering::Less); //! assert_eq!(collator_3n.compare("Di Silva", "diSilva"), Ordering::Less); @@ -153,7 +151,7 @@ //! options_3s.strength = Some(Strength::Tertiary); //! options_3s.alternate_handling = Some(AlternateHandling::Shifted); //! let collator_3s = -//! Collator::try_new(&Default::default(), options_3s).unwrap(); +//! Collator::try_new(Default::default(), options_3s).unwrap(); //! //! assert_eq!(collator_3s.compare("di Silva", "diSilva"), Ordering::Equal); //! assert_eq!(collator_3s.compare("diSilva", "Di Silva"), Ordering::Less); @@ -164,7 +162,7 @@ //! options_4s.strength = Some(Strength::Quaternary); //! options_4s.alternate_handling = Some(AlternateHandling::Shifted); //! let collator_4s = -//! Collator::try_new(&Default::default(), options_4s).unwrap(); +//! Collator::try_new(Default::default(), options_4s).unwrap(); //! //! assert_eq!(collator_4s.compare("di Silva", "diSilva"), Ordering::Less); //! assert_eq!(collator_4s.compare("diSilva", "Di Silva"), Ordering::Less); @@ -187,7 +185,7 @@ //! options.strength = Some(Strength::Primary); //! options.case_level = Some(CaseLevel::Off); //! let primary = -//! Collator::try_new(&Default::default(), +//! Collator::try_new(Default::default(), //! options).unwrap(); //! //! assert_eq!(primary.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Equal); @@ -199,7 +197,7 @@ //! options.strength = Some(Strength::Primary); //! options.case_level = Some(CaseLevel::On); //! let primary_and_case = -//! Collator::try_new(&Default::default(), +//! Collator::try_new(Default::default(), //! options).unwrap(); //! //! assert_eq!(primary_and_case.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Less); @@ -211,7 +209,7 @@ //! options.strength = Some(Strength::Secondary); //! options.case_level = Some(CaseLevel::On); //! let secondary_and_case = -//! Collator::try_new(&Default::default(), +//! Collator::try_new(Default::default(), //! options).unwrap(); //! //! assert_eq!(secondary_and_case.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Less); @@ -223,7 +221,7 @@ //! options.strength = Some(Strength::Tertiary); //! options.case_level = Some(CaseLevel::Off); //! let tertiary = -//! Collator::try_new(&Default::default(), +//! Collator::try_new(Default::default(), //! options).unwrap(); //! //! assert_eq!(tertiary.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Less); @@ -255,7 +253,7 @@ //! let mut options_num_off = CollatorOptions::default(); //! options_num_off.numeric = Some(Numeric::Off); //! let collator_num_off = -//! Collator::try_new(&Default::default(), options_num_off).unwrap(); +//! Collator::try_new(Default::default(), options_num_off).unwrap(); //! assert_eq!(collator_num_off.compare("a10b", "a2b"), Ordering::Less); //! //! // Numerical sorting on @@ -263,7 +261,7 @@ //! let mut options_num_on = CollatorOptions::default(); //! options_num_on.numeric = Some(Numeric::On); //! let collator_num_on = -//! Collator::try_new(&Default::default(), options_num_on).unwrap(); +//! Collator::try_new(Default::default(), options_num_on).unwrap(); //! assert_eq!(collator_num_on.compare("a10b", "a2b"), Ordering::Greater); //! ``` @@ -284,6 +282,7 @@ extern crate alloc; pub use comparison::Collator; pub use comparison::CollatorBorrowed; +pub use comparison::CollatorPreferences; pub use options::AlternateHandling; pub use options::BackwardSecondLevel; pub use options::CaseFirst; diff --git a/components/collator/src/options.rs b/components/collator/src/options.rs index e228ae8f0d4..476624d35c3 100644 --- a/components/collator/src/options.rs +++ b/components/collator/src/options.rs @@ -32,7 +32,7 @@ pub enum Strength { /// /// let mut options = CollatorOptions::default(); /// options.strength = Some(Strength::Primary); - /// let collator = Collator::try_new(&Default::default(), options).unwrap(); + /// let collator = Collator::try_new(Default::default(), options).unwrap(); /// assert_eq!(collator.compare("E", "é"), core::cmp::Ordering::Equal); /// ``` Primary = 0, @@ -46,7 +46,7 @@ pub enum Strength { /// /// let mut options = CollatorOptions::default(); /// options.strength = Some(Strength::Secondary); - /// let collator = Collator::try_new(&Default::default(), options).unwrap(); + /// let collator = Collator::try_new(Default::default(), options).unwrap(); /// assert_eq!(collator.compare("E", "e"), core::cmp::Ordering::Equal); /// assert_eq!(collator.compare("e", "é"), core::cmp::Ordering::Less); /// assert_eq!(collator.compare("あ", "ア"), core::cmp::Ordering::Equal); @@ -67,11 +67,12 @@ pub enum Strength { /// /// ``` /// use icu::collator::*; + /// use icu::locale::locale; /// /// let mut options = CollatorOptions::default(); /// options.strength = Some(Strength::Tertiary); /// let collator = - /// Collator::try_new(&Default::default(), + /// Collator::try_new(Default::default(), /// options).unwrap(); /// assert_eq!(collator.compare("E", "e"), /// core::cmp::Ordering::Greater); @@ -86,9 +87,8 @@ pub enum Strength { /// assert_eq!(collator.compare("e", "e"), // Full-width e /// core::cmp::Ordering::Less); /// - /// let locale = icu::locale::locale!("ja").into(); /// let ja_collator = - /// Collator::try_new(&locale, options).unwrap(); + /// Collator::try_new(locale!("ja").into(), options).unwrap(); /// assert_eq!(ja_collator.compare("E", "e"), /// core::cmp::Ordering::Greater); /// assert_eq!(ja_collator.compare("e", "é"), @@ -112,13 +112,13 @@ pub enum Strength { /// /// ``` /// use icu::collator::*; + /// use icu::locale::locale; /// /// let mut options = CollatorOptions::default(); /// options.strength = Some(Strength::Quaternary); /// - /// let ja_locale = icu::locale::locale!("ja").into(); /// let ja_collator = - /// Collator::try_new(&ja_locale, options).unwrap(); + /// Collator::try_new(locale!("ja").into(), options).unwrap(); /// assert_eq!(ja_collator.compare("あ", "ア"), /// core::cmp::Ordering::Less); /// assert_eq!(ja_collator.compare("ア", "ア"), @@ -129,7 +129,7 @@ pub enum Strength { /// // Even this level doesn't distinguish everything, /// // e.g. Hebrew cantillation marks are still ignored. /// let collator = - /// Collator::try_new(&Default::default(), + /// Collator::try_new(Default::default(), /// options).unwrap(); /// assert_eq!(collator.compare("דחי", "דחי֭"), /// core::cmp::Ordering::Equal); @@ -148,20 +148,20 @@ pub enum Strength { /// /// ``` /// use icu::collator::*; + /// use icu::locale::locale; /// /// let mut options = CollatorOptions::default(); /// options.strength = Some(Strength::Identical); /// - /// let ja_locale = icu::locale::locale!("ja").into(); /// let ja_collator = - /// Collator::try_new(&ja_locale, options).unwrap(); + /// Collator::try_new(locale!("ja").into(), options).unwrap(); /// assert_eq!(ja_collator.compare("ア", "ア"), /// core::cmp::Ordering::Less); /// assert_eq!(ja_collator.compare("e", "e"), // Full-width e /// core::cmp::Ordering::Less); /// /// let collator = - /// Collator::try_new(&Default::default(), + /// Collator::try_new(Default::default(), /// options).unwrap(); /// assert_eq!(collator.compare("דחי", "דחי֭"), /// core::cmp::Ordering::Less); diff --git a/components/collator/tests/tests.rs b/components/collator/tests/tests.rs index 33a00b9e061..f8586852558 100644 --- a/components/collator/tests/tests.rs +++ b/components/collator/tests/tests.rs @@ -7,7 +7,7 @@ use core::cmp::Ordering; use atoi::FromRadix16; use icu_collator::provider::*; use icu_collator::*; -use icu_locale_core::{langid, locale, Locale}; +use icu_locale_core::{langid, locale}; use icu_provider::prelude::*; struct TestingProvider; @@ -107,7 +107,7 @@ fn test_implicit_unihan() { let mut options = CollatorOptions::default(); options.strength = Some(Strength::Quaternary); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); assert_eq!(collator.compare("\u{4E00}", "\u{4E00}"), Ordering::Equal); assert_eq!(collator.compare("\u{4E00}", "\u{4E01}"), Ordering::Less); assert_eq!(collator.compare("\u{4E01}", "\u{4E00}"), Ordering::Greater); @@ -125,7 +125,7 @@ fn test_currency() { let mut options = CollatorOptions::default(); options.strength = Some(Strength::Quaternary); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); // Iterating as chars and re-encoding due to // https://github.com/rust-lang/rust/issues/83871 being nightly-only. :-( let mut lower_buf = [0u8; 4]; @@ -143,9 +143,9 @@ fn test_currency() { #[test] fn test_bo() { - let locale: Locale = ("bo").parse().unwrap(); + let prefs = locale!("bo").into(); let options = CollatorOptions::default(); - let collator = Collator::try_new(&locale.into(), options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); assert_eq!( collator.compare( @@ -218,16 +218,16 @@ fn test_bs() { ]; { - let locale: Locale = ("bs").parse().unwrap(); - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + let prefs = locale!("bs").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); for (left, right) in left_side.iter().zip(right_side.iter()) { assert_eq!(collator.compare(left, right), Ordering::Greater); } } { - let locale: Locale = ("bs-Cyrl").parse().unwrap(); - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + let prefs = locale!("bs-Cyrl").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); let expect_bs_cyrl = vec![ Ordering::Less, // Ordering changes in Cyrillic Ordering::Greater, @@ -328,7 +328,7 @@ fn test_de() { { // Note: German uses the root collation - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); check_expectations(&collator, &left, &right, &expect_primary); } @@ -336,19 +336,19 @@ fn test_de() { { // Note: German uses the root collation - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); check_expectations(&collator, &left, &right, &expect_tertiary); } { - let locale: Locale = "de-AT-u-co-phonebk".parse().unwrap(); - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + let prefs = locale!("de-AT-u-co-phonebk").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); check_expectations(&collator, &left, &right, &expect_de_at); } { - let locale: Locale = "de-DE-u-co-phonebk".parse().unwrap(); - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + let prefs = locale!("de-DE-u-co-phonebk").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); check_expectations(&collator, &left, &right, &expect_de_de); } } @@ -522,7 +522,7 @@ fn test_en() { { // Note: English uses the root collation - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); check_expectations(&collator, &left[..38], &right[..38], &expectations[..38]); } @@ -530,7 +530,7 @@ fn test_en() { { // Note: English uses the root collation - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); check_expectations( &collator, &left[38..43], @@ -543,7 +543,7 @@ fn test_en() { { // Note: English uses the root collation - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); check_expectations(&collator, &left[43..], &right[43..], &expectations[43..]); } } @@ -552,14 +552,14 @@ fn test_en() { fn test_en_bugs() { // Adapted from encoll.cpp in ICU4C let bugs = ["a", "A", "e", "E", "é", "è", "ê", "ë", "ea", "x"]; - // let locale = locale!("en").into(); - let locale = Default::default(); // English uses the root collation + // let prefs = locale!("en").into(); + let prefs = Default::default(); // English uses the root collation let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); { - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let mut outer = bugs.iter(); while let Some(left) = outer.next() { let inner = outer.clone(); @@ -597,14 +597,14 @@ fn test_ja_tertiary() { Ordering::Less, Ordering::Less, // Prolonged sound mark sorts BEFORE equivalent vowel ]; - let locale = locale!("ja").into(); + let prefs = locale!("ja").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); options.case_level = Some(CaseLevel::On); { - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectations); } } @@ -614,12 +614,12 @@ fn test_ja_base() { // Adapted from `CollationKanaTest::TestBase` in jacoll.cpp of ICU4C. let cases = ["カ", "カキ", "キ", "キキ"]; - let locale = locale!("ja").into(); + let prefs = locale!("ja").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Primary); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let mut case_iter = cases.iter(); while let Some(lower) = case_iter.next() { let tail = case_iter.clone(); @@ -634,12 +634,12 @@ fn test_ja_plain_dakuten_handakuten() { // Adapted from `CollationKanaTest::TestPlainDakutenHandakuten` in jacoll.cpp of ICU4C. let cases = ["ハカ", "バカ", "ハキ", "バキ"]; - let locale = locale!("ja").into(); + let prefs = locale!("ja").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Secondary); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let mut case_iter = cases.iter(); while let Some(lower) = case_iter.next() { let tail = case_iter.clone(); @@ -654,13 +654,13 @@ fn test_ja_small_large() { // Adapted from `CollationKanaTest::TestSmallLarge` in jacoll.cpp of ICU4C. let cases = ["ッハ", "ツハ", "ッバ", "ツバ"]; - let locale = locale!("ja").into(); + let prefs = locale!("ja").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); options.case_level = Some(CaseLevel::On); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let mut case_iter = cases.iter(); while let Some(lower) = case_iter.next() { let tail = case_iter.clone(); @@ -675,13 +675,13 @@ fn test_ja_hiragana_katakana() { // Adapted from `CollationKanaTest::TestKatakanaHiragana` in jacoll.cpp of ICU4C. let cases = ["あッ", "アッ", "あツ", "アツ"]; - let locale = locale!("ja").into(); + let prefs = locale!("ja").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Quaternary); options.case_level = Some(CaseLevel::On); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let mut case_iter = cases.iter(); while let Some(lower) = case_iter.next() { let tail = case_iter.clone(); @@ -701,13 +701,13 @@ fn test_ja_hiragana_katakana_utf16() { &[0x30A2u16, 0x30C4u16], ]; - let locale = locale!("ja").into(); + let prefs = locale!("ja").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Quaternary); options.case_level = Some(CaseLevel::On); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let mut case_iter = cases.iter(); while let Some(lower) = case_iter.next() { let tail = case_iter.clone(); @@ -734,13 +734,13 @@ fn test_ja_chooon_kigoo() { "キイア", ]; - let locale = locale!("ja").into(); + let prefs = locale!("ja").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Quaternary); options.case_level = Some(CaseLevel::On); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let mut case_iter = cases.iter(); while let Some(lower) = case_iter.next() { let tail = case_iter.clone(); @@ -756,9 +756,9 @@ fn test_ja_unihan() { let right = vec!["州", "京都", "愛", "飞行机"]; { - let locale: Locale = ("ja").parse().unwrap(); + let prefs = locale!("ja").into(); let options = CollatorOptions::default(); - let collator = Collator::try_new(&locale.into(), options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let expectations_ja = vec![ Ordering::Greater, Ordering::Greater, @@ -769,8 +769,8 @@ fn test_ja_unihan() { } { - let locale: Locale = ("ja-u-co-unihan").parse().unwrap(); - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + let prefs = locale!("ja-u-co-unihan").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); let expectations_ja_unihan = vec![ Ordering::Less, // Ordering changes Ordering::Greater, @@ -786,28 +786,28 @@ fn test_ja_unihan() { #[test] fn test_region_fallback() { // There's no explicit fi-FI data. - let locale = locale!("fi-FI"); + let prefs = locale!("fi-FI").into(); - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); assert_eq!(collator.compare("ä", "z"), Ordering::Greater); } #[test] fn test_reordering() { - let locale = locale!("bn").into(); + let prefs = locale!("bn").into(); // অ is Bangla // ऄ is Devanagari { - let collator = Collator::try_new(&Default::default(), Default::default()).unwrap(); + let collator = Collator::try_new(Default::default(), Default::default()).unwrap(); assert_eq!(collator.compare("অ", "a"), Ordering::Greater); assert_eq!(collator.compare("ऄ", "a"), Ordering::Greater); assert_eq!(collator.compare("অ", "ऄ"), Ordering::Greater); } { - let collator = Collator::try_new(&locale, Default::default()).unwrap(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); assert_eq!(collator.compare("অ", "a"), Ordering::Less); assert_eq!(collator.compare("ऄ", "a"), Ordering::Less); assert_eq!(collator.compare("অ", "ऄ"), Ordering::Less); @@ -816,7 +816,7 @@ fn test_reordering() { #[test] fn test_reordering_owned() { - let locale = locale!("bn").into(); + let prefs = locale!("bn").into(); // অ is Bangla // ऄ is Devanagari @@ -824,7 +824,7 @@ fn test_reordering_owned() { { let owned = Collator::try_new_unstable( &TestingProvider, - &Default::default(), + Default::default(), CollatorOptions::default(), ) .unwrap(); @@ -836,7 +836,7 @@ fn test_reordering_owned() { { let owned = - Collator::try_new_unstable(&TestingProvider, &locale, Default::default()).unwrap(); + Collator::try_new_unstable(&TestingProvider, prefs, Default::default()).unwrap(); let collator = owned.as_borrowed(); assert_eq!(collator.compare("অ", "a"), Ordering::Less); assert_eq!(collator.compare("ऄ", "a"), Ordering::Less); @@ -847,8 +847,8 @@ fn test_reordering_owned() { #[test] fn test_vi() { { - let locale = locale!("vi").into(); - let collator = Collator::try_new(&locale, Default::default()).unwrap(); + let prefs = locale!("vi").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); assert_eq!(collator.compare("a", "b"), Ordering::Less); assert_eq!(collator.compare("a", "á"), Ordering::Less); @@ -881,9 +881,9 @@ fn test_vi() { #[test] fn test_vi_owned() { { - let locale = locale!("vi").into(); + let prefs = locale!("vi").into(); let owned = - Collator::try_new_unstable(&TestingProvider, &locale, Default::default()).unwrap(); + Collator::try_new_unstable(&TestingProvider, prefs, Default::default()).unwrap(); let collator = owned.as_borrowed(); assert_eq!(collator.compare("a", "b"), Ordering::Less); @@ -901,7 +901,7 @@ fn test_vi_owned() { { let owned = Collator::try_new_unstable( &TestingProvider, - &Default::default(), + Default::default(), CollatorOptions::default(), ) .unwrap(); @@ -926,27 +926,27 @@ fn test_zh() { assert_root(Default::default()); - assert_pinyin("zh".parse().unwrap()); - assert_pinyin("zh-Hans".parse().unwrap()); - assert_pinyin("zh-Hans-HK".parse().unwrap()); - assert_pinyin("zh-Hant-u-co-pinyin".parse().unwrap()); - assert_pinyin("zh-TW-u-co-pinyin".parse().unwrap()); - assert_pinyin("yue-CN".parse().unwrap()); - - assert_stroke("zh-Hant".parse().unwrap()); - assert_stroke("zh-HK".parse().unwrap()); - assert_stroke("zh-MO".parse().unwrap()); - assert_stroke("zh-TW".parse().unwrap()); - assert_stroke("zh-CN-u-co-stroke".parse().unwrap()); - assert_stroke("zh-Hans-u-co-stroke".parse().unwrap()); - assert_stroke("yue".parse().unwrap()); - - assert_zhuyin("zh-u-co-zhuyin".parse().unwrap()); - assert_unihan("zh-u-co-unihan".parse().unwrap()); + assert_pinyin(locale!("zh").into()); + assert_pinyin(locale!("zh-Hans").into()); + assert_pinyin(locale!("zh-Hans-HK").into()); + assert_pinyin(locale!("zh-Hant-u-co-pinyin").into()); + assert_pinyin(locale!("zh-TW-u-co-pinyin").into()); + assert_pinyin(locale!("yue-CN").into()); + + assert_stroke(locale!("zh-Hant").into()); + assert_stroke(locale!("zh-HK").into()); + assert_stroke(locale!("zh-MO").into()); + assert_stroke(locale!("zh-TW").into()); + assert_stroke(locale!("zh-CN-u-co-stroke").into()); + assert_stroke(locale!("zh-Hans-u-co-stroke").into()); + assert_stroke(locale!("yue").into()); + + assert_zhuyin(locale!("zh-u-co-zhuyin").into()); + assert_unihan(locale!("zh-u-co-unihan").into()); // See SourceDataProvider test_zh_non_baked for gb2312 and big5han tests - fn assert_root(locale: Locale) { - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + fn assert_root(prefs: CollatorPreferences) { + let collator = Collator::try_new(prefs, Default::default()).unwrap(); assert_eq!(collator.compare("艾", "a"), Ordering::Greater); assert_eq!(collator.compare("佰", "a"), Ordering::Greater); assert_eq!(collator.compare("ㄅ", "a"), Ordering::Greater); @@ -957,8 +957,8 @@ fn test_zh() { assert_eq!(collator.compare("不", "把"), Ordering::Less); } - fn assert_pinyin(locale: Locale) { - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + fn assert_pinyin(prefs: CollatorPreferences) { + let collator = Collator::try_new(prefs, Default::default()).unwrap(); assert_eq!(collator.compare("艾", "a"), Ordering::Less); assert_eq!(collator.compare("佰", "a"), Ordering::Less); assert_eq!(collator.compare("ㄅ", "a"), Ordering::Greater); @@ -969,8 +969,8 @@ fn test_zh() { assert_eq!(collator.compare("不", "把"), Ordering::Greater); } - fn assert_stroke(locale: Locale) { - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + fn assert_stroke(prefs: CollatorPreferences) { + let collator = Collator::try_new(prefs, Default::default()).unwrap(); assert_eq!(collator.compare("艾", "a"), Ordering::Less); assert_eq!(collator.compare("佰", "a"), Ordering::Less); assert_eq!(collator.compare("ㄅ", "a"), Ordering::Less); @@ -981,8 +981,8 @@ fn test_zh() { assert_eq!(collator.compare("不", "把"), Ordering::Less); } - fn assert_zhuyin(locale: Locale) { - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + fn assert_zhuyin(prefs: CollatorPreferences) { + let collator = Collator::try_new(prefs, Default::default()).unwrap(); assert_eq!(collator.compare("艾", "a"), Ordering::Less); assert_eq!(collator.compare("佰", "a"), Ordering::Less); assert_eq!(collator.compare("ㄅ", "a"), Ordering::Less); @@ -993,8 +993,8 @@ fn test_zh() { assert_eq!(collator.compare("不", "把"), Ordering::Greater); } - fn assert_unihan(locale: Locale) { - let collator = Collator::try_new(&locale.into(), Default::default()).unwrap(); + fn assert_unihan(prefs: CollatorPreferences) { + let collator = Collator::try_new(prefs, Default::default()).unwrap(); assert_eq!(collator.compare("艾", "a"), Ordering::Less); assert_eq!(collator.compare("佰", "a"), Ordering::Less); assert_eq!(collator.compare("ㄅ", "a"), Ordering::Less); @@ -1023,13 +1023,13 @@ fn test_es_tertiary() { Ordering::Less, Ordering::Less, ]; - let locale = locale!("es").into(); + let prefs = locale!("es").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); { - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectations); } } @@ -1045,13 +1045,13 @@ fn test_es_primary() { Ordering::Less, Ordering::Equal, ]; - let locale = locale!("es").into(); + let prefs = locale!("es").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Primary); { - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectations); } } @@ -1059,12 +1059,12 @@ fn test_es_primary() { #[test] fn test_el_secondary() { // Adapted from `CollationRegressionTest::Test4095316` in regcoll.cpp of ICU4C. - let locale: Locale = Locale::default(); // Greek uses the root collation + let prefs = Default::default(); // Greek uses the root collation let mut options = CollatorOptions::default(); options.strength = Some(Strength::Secondary); - let collator = Collator::try_new(&locale.into(), options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); assert_eq!(collator.compare("ϔ", "Ϋ"), Ordering::Equal); } @@ -1074,12 +1074,12 @@ fn test_th_dictionary() { let dict = include_str!("data/riwords.txt") .strip_prefix('\u{FEFF}') .unwrap(); - let locale = locale!("th").into(); + let prefs = locale!("th").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Quaternary); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let mut lines = dict.lines(); let mut prev = loop { if let Some(line) = lines.next() { @@ -1159,10 +1159,10 @@ fn test_th_corner_cases() { Ordering::Less, Ordering::Less, ]; - let locale = locale!("th").into(); + let prefs = locale!("th").into(); { // TODO(#2013): Check why the commented-out cases fail - let collator = Collator::try_new(&locale, Default::default()).unwrap(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); check_expectations(&collator, &left, &right, &expectations); } } @@ -1197,13 +1197,13 @@ fn test_th_reordering() { Ordering::Equal, // Ordering::Equal, ]; - let locale = locale!("th").into(); + let prefs = locale!("th").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Secondary); { - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectations); } } @@ -1223,13 +1223,13 @@ fn test_tr_tertiary() { Ordering::Less, Ordering::Greater, ]; - let locale = locale!("tr").into(); + let prefs = locale!("tr").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); { - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectations); } } @@ -1240,13 +1240,13 @@ fn test_tr_primary() { let left = ["üoid", "voıd", "idea"]; let right = ["void", "void", "Idea"]; let expectations = [Ordering::Less, Ordering::Less, Ordering::Greater]; - let locale = locale!("tr").into(); + let prefs = locale!("tr").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); { - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectations); } } @@ -1257,13 +1257,13 @@ fn test_tr_primary_owned() { let left = ["üoid", "voıd", "idea"]; let right = ["void", "void", "Idea"]; let expectations = [Ordering::Less, Ordering::Less, Ordering::Greater]; - let locale = locale!("tr").into(); + let prefs = locale!("tr").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); { - let owned = Collator::try_new_unstable(&TestingProvider, &locale, options).unwrap(); + let owned = Collator::try_new_unstable(&TestingProvider, prefs, options).unwrap(); let collator = owned.as_borrowed(); check_expectations(&collator, &left, &right, &expectations); } @@ -1292,13 +1292,13 @@ fn test_lt_tertiary() { Ordering::Equal, Ordering::Greater, ]; - let locale = locale!("lt").into(); + let prefs = locale!("lt").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); { - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectations); } } @@ -1326,13 +1326,13 @@ fn test_lt_tertiary_owned() { Ordering::Equal, Ordering::Greater, ]; - let locale = locale!("lt").into(); + let prefs = locale!("lt").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); { - let owned = Collator::try_new_unstable(&TestingProvider, &locale, options).unwrap(); + let owned = Collator::try_new_unstable(&TestingProvider, prefs, options).unwrap(); let collator = owned.as_borrowed(); check_expectations(&collator, &left, &right, &expectations); } @@ -1343,13 +1343,13 @@ fn test_lt_primary() { let left = ["ž"]; let right = ["z"]; let expectations = [Ordering::Greater]; - let locale = locale!("lt").into(); + let prefs = locale!("lt").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Primary); { - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectations); } } @@ -1379,15 +1379,15 @@ fn test_fi() { Ordering::Greater, Ordering::Equal, ]; - let locale = locale!("fi").into(); + let prefs = locale!("fi").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectation); options.strength = Some(Strength::Primary); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectation); } @@ -1422,15 +1422,15 @@ fn test_sv() { Ordering::Greater, Ordering::Equal, ]; - let locale = locale!("sv").into(); + let prefs = locale!("sv").into(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectations); options.strength = Some(Strength::Primary); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); check_expectations(&collator, &left, &right, &expectations); } @@ -1440,8 +1440,8 @@ fn test_nb_nn_no() { let expected = &["y", "ü", "ø", "å"]; // Test "no" macro language WITH fallback (should equal expected) - let locale = locale!("no").into(); - let collator = Collator::try_new(&locale, Default::default()).unwrap(); + let prefs = locale!("no").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); let mut strs = input.clone(); strs.sort_by(|a, b| collator.compare(a, b)); assert_eq!(strs, expected); @@ -1449,7 +1449,10 @@ fn test_nb_nn_no() { DataProvider::::load( &icu_collator::provider::Baked, DataRequest { - id: DataIdentifierBorrowed::for_locale(&locale), + id: DataIdentifierCow::from_locale(DataLocale::from_preferences_locale::< + CollationTailoringV1Marker, + >(prefs.locale_prefs)) + .as_borrowed(), ..Default::default() } ) @@ -1460,8 +1463,8 @@ fn test_nb_nn_no() { ); // Now "nb" should work - let locale = locale!("nb").into(); - let collator = Collator::try_new(&locale, Default::default()).unwrap(); + let prefs = locale!("nb").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); let mut strs = input.clone(); strs.sort_by(|a, b| collator.compare(a, b)); assert_eq!(strs, expected); @@ -1469,7 +1472,10 @@ fn test_nb_nn_no() { DataProvider::::load( &icu_collator::provider::Baked, DataRequest { - id: DataIdentifierBorrowed::for_locale(&locale), + id: DataIdentifierCow::from_locale(DataLocale::from_preferences_locale::< + CollationTailoringV1Marker, + >(prefs.locale_prefs)) + .as_borrowed(), ..Default::default() } ) @@ -1480,8 +1486,8 @@ fn test_nb_nn_no() { ); // And "nn" should work, too - let locale = locale!("nn").into(); - let collator = Collator::try_new(&locale, Default::default()).unwrap(); + let prefs = locale!("nn").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); let mut strs = input.clone(); strs.sort_by(|a, b| collator.compare(a, b)); assert_eq!(strs, expected); @@ -1489,7 +1495,10 @@ fn test_nb_nn_no() { DataProvider::::load( &icu_collator::provider::Baked, DataRequest { - id: DataIdentifierBorrowed::for_locale(&locale), + id: DataIdentifierCow::from_locale(DataLocale::from_preferences_locale::< + CollationTailoringV1Marker, + >(prefs.locale_prefs)) + .as_borrowed(), ..Default::default() } ) @@ -1518,7 +1527,7 @@ fn test_basics() { options.strength = Some(Strength::Tertiary); { - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); check_expectations(&collator, &left, &right, &expectations); } } @@ -1528,7 +1537,7 @@ fn test_numeric_long() { let mut options = CollatorOptions::default(); options.numeric = Some(Numeric::On); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); let mut left = String::new(); let mut right = String::new(); // We'll make left larger than right numerically. However, first, let's use @@ -1563,7 +1572,7 @@ fn test_numeric_after() { let mut options = CollatorOptions::default(); options.numeric = Some(Numeric::On); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); assert_eq!(collator.compare("0001000b", "1000a"), Ordering::Greater); } @@ -1572,7 +1581,7 @@ fn test_unpaired_surrogates() { let mut options = CollatorOptions::default(); options.strength = Some(Strength::Quaternary); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); assert_eq!( collator.compare_utf16(&[0xD801u16], &[0xD802u16]), Ordering::Equal @@ -1585,7 +1594,7 @@ fn test_backward_second_level() { options.strength = Some(Strength::Secondary); { - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); let cases = ["cote", "coté", "côte", "côté"]; let mut case_iter = cases.iter(); @@ -1600,7 +1609,7 @@ fn test_backward_second_level() { options.backward_second_level = Some(BackwardSecondLevel::On); { - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); { let cases = ["cote", "côte", "coté", "côté"]; @@ -1627,13 +1636,13 @@ fn test_backward_second_level() { #[test] fn test_cantillation() { - let locale: Locale = Locale::default(); + let prefs = Default::default(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Quaternary); { - let collator = Collator::try_new(&(&locale).into(), options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); assert_eq!( collator.compare( "\u{05D3}\u{05D7}\u{05D9}\u{05AD}", @@ -1646,7 +1655,7 @@ fn test_cantillation() { options.strength = Some(Strength::Identical); { - let collator = Collator::try_new(&locale.into(), options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); assert_eq!( collator.compare( "\u{05D3}\u{05D7}\u{05D9}\u{05AD}", @@ -1659,13 +1668,13 @@ fn test_cantillation() { #[test] fn test_cantillation_utf8() { - let locale: Locale = Locale::default(); + let prefs = Default::default(); let mut options = CollatorOptions::default(); options.strength = Some(Strength::Quaternary); { - let collator = Collator::try_new(&(&locale).into(), options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); assert_eq!( collator.compare_utf8( "\u{05D3}\u{05D7}\u{05D9}\u{05AD}".as_bytes(), @@ -1678,7 +1687,7 @@ fn test_cantillation_utf8() { options.strength = Some(Strength::Identical); { - let collator = Collator::try_new(&locale.into(), options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); assert_eq!( collator.compare( "\u{05D3}\u{05D7}\u{05D9}\u{05AD}", @@ -1699,7 +1708,7 @@ fn test_conformance_shifted() { options.strength = Some(Strength::Quaternary); options.alternate_handling = Some(AlternateHandling::Shifted); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); let mut lines = dict.split(|b| b == &b'\n'); let mut prev = loop { if let Some(line) = lines.next() { @@ -1742,7 +1751,7 @@ fn test_conformance_non_ignorable() { options.strength = Some(Strength::Quaternary); options.alternate_handling = Some(AlternateHandling::NonIgnorable); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); let mut lines = dict.split(|b| b == &b'\n'); let mut prev = loop { if let Some(line) = lines.next() { @@ -1780,7 +1789,7 @@ fn test_case_level() { let mut options = CollatorOptions::default(); options.strength = Some(Strength::Primary); options.case_level = Some(CaseLevel::On); - let collator_with_case = Collator::try_new(&Default::default(), options).unwrap(); + let collator_with_case = Collator::try_new(Default::default(), options).unwrap(); assert_eq!( collator_with_case.compare("aA", "Aa"), core::cmp::Ordering::Less @@ -1789,7 +1798,7 @@ fn test_case_level() { #[test] fn test_default_resolved_options() { - let collator = Collator::try_new(&Default::default(), Default::default()).unwrap(); + let collator = Collator::try_new(Default::default(), Default::default()).unwrap(); let resolved = collator.resolved_options(); assert_eq!(resolved.strength, Strength::Tertiary); assert_eq!(resolved.alternate_handling, AlternateHandling::NonIgnorable); @@ -1805,8 +1814,8 @@ fn test_default_resolved_options() { #[test] fn test_data_resolved_options_th() { - let locale = locale!("th").into(); - let collator = Collator::try_new(&locale, Default::default()).unwrap(); + let prefs = locale!("th").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); let resolved = collator.resolved_options(); assert_eq!(resolved.strength, Strength::Tertiary); assert_eq!(resolved.alternate_handling, AlternateHandling::Shifted); @@ -1823,8 +1832,8 @@ fn test_data_resolved_options_th() { #[test] fn test_data_resolved_options_da() { - let locale = locale!("da").into(); - let collator = Collator::try_new(&locale, Default::default()).unwrap(); + let prefs = locale!("da").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); let resolved = collator.resolved_options(); assert_eq!(resolved.strength, Strength::Tertiary); assert_eq!(resolved.alternate_handling, AlternateHandling::NonIgnorable); @@ -1840,8 +1849,8 @@ fn test_data_resolved_options_da() { #[test] fn test_data_resolved_options_fr_ca() { - let locale = locale!("fr-CA").into(); - let collator = Collator::try_new(&locale, Default::default()).unwrap(); + let prefs = locale!("fr-CA").into(); + let collator = Collator::try_new(prefs, Default::default()).unwrap(); let resolved = collator.resolved_options(); assert_eq!(resolved.strength, Strength::Tertiary); assert_eq!(resolved.alternate_handling, AlternateHandling::NonIgnorable); @@ -1860,12 +1869,12 @@ fn test_data_resolved_options_fr_ca() { #[test] fn test_manual_and_data_resolved_options_fr_ca() { - let locale = locale!("fr-CA").into(); + let prefs = locale!("fr-CA").into(); let mut options = CollatorOptions::default(); options.case_first = Some(CaseFirst::UpperFirst); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let resolved = collator.resolved_options(); assert_eq!(resolved.strength, Strength::Tertiary); assert_eq!(resolved.alternate_handling, AlternateHandling::NonIgnorable); @@ -1884,12 +1893,12 @@ fn test_manual_and_data_resolved_options_fr_ca() { #[test] fn test_manual_resolved_options_da() { - let locale = locale!("da").into(); + let prefs = locale!("da").into(); let mut options = CollatorOptions::default(); options.case_first = Some(CaseFirst::Off); - let collator = Collator::try_new(&locale, options).unwrap(); + let collator = Collator::try_new(prefs, options).unwrap(); let resolved = collator.resolved_options(); assert_eq!(resolved.strength, Strength::Tertiary); assert_eq!(resolved.alternate_handling, AlternateHandling::NonIgnorable); @@ -1909,7 +1918,7 @@ fn test_ecma_sensitivity() { // base let mut options = CollatorOptions::default(); options.strength = Some(Strength::Primary); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); assert_eq!(collator.compare("a", "á"), core::cmp::Ordering::Equal); assert_eq!(collator.compare("a", "A"), core::cmp::Ordering::Equal); } @@ -1917,7 +1926,7 @@ fn test_ecma_sensitivity() { // accent let mut options = CollatorOptions::default(); options.strength = Some(Strength::Secondary); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); assert_ne!(collator.compare("a", "á"), core::cmp::Ordering::Equal); assert_eq!(collator.compare("a", "A"), core::cmp::Ordering::Equal); } @@ -1926,7 +1935,7 @@ fn test_ecma_sensitivity() { let mut options = CollatorOptions::default(); options.strength = Some(Strength::Primary); options.case_level = Some(CaseLevel::On); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); assert_eq!(collator.compare("a", "á"), core::cmp::Ordering::Equal); assert_ne!(collator.compare("a", "A"), core::cmp::Ordering::Equal); } @@ -1934,7 +1943,7 @@ fn test_ecma_sensitivity() { // variant let mut options = CollatorOptions::default(); options.strength = Some(Strength::Tertiary); - let collator = Collator::try_new(&Default::default(), options).unwrap(); + let collator = Collator::try_new(Default::default(), options).unwrap(); assert_ne!(collator.compare("a", "á"), core::cmp::Ordering::Equal); assert_ne!(collator.compare("a", "A"), core::cmp::Ordering::Equal); } diff --git a/components/collections/codepointtrie_builder/cpp/Makefile b/components/collections/codepointtrie_builder/cpp/Makefile index aaa91c6779e..1f87f5738f0 100644 --- a/components/collections/codepointtrie_builder/cpp/Makefile +++ b/components/collections/codepointtrie_builder/cpp/Makefile @@ -30,7 +30,7 @@ wasm_obj/icu4c/%.o: $(ICU4C_SOURCE)/%.cpp wasm_obj/ucptrie_wrap.o: ucptrie_wrap.cpp mkdir -p wasm_obj $(CXX) --target=wasm32-unknown-wasi \ - -I/usr/include/wasm32-wasi \ + -I/usr/include/wasm32-wasip1 \ --compile \ -flto \ -I$(ICU4C_SOURCE)/common \ diff --git a/components/datetime/README.md b/components/datetime/README.md index 9d983e7d545..55fa30284ad 100644 --- a/components/datetime/README.md +++ b/components/datetime/README.md @@ -28,20 +28,20 @@ programmer to pick the calendar at compile time. ```rust use icu::calendar::{DateTime, Gregorian}; -use icu::datetime::fieldset::YMDHM; +use icu::datetime::fieldset::YMDT; use icu::datetime::{DateTimeFormatter, FixedCalendarDateTimeFormatter}; use icu::locale::{locale, Locale}; use writeable::assert_try_writeable_eq; // You can work with a formatter that can select the calendar at runtime: let locale = Locale::try_from_str("en-u-ca-gregory").unwrap(); -let dtf = DateTimeFormatter::try_new(&locale.into(), YMDHM::medium()) +let dtf = DateTimeFormatter::try_new(&locale.into(), YMDT::medium().hm()) .expect("should successfully create DateTimeFormatter instance"); // Or one that selects a calendar at compile time: let typed_dtf = FixedCalendarDateTimeFormatter::::try_new( &locale!("en").into(), - YMDHM::medium(), + YMDT::medium().hm(), ) .expect( "should successfully create FixedCalendarDateTimeFormatter instance", diff --git a/components/datetime/benches/datetime.rs b/components/datetime/benches/datetime.rs index 3abd5b5dd80..4481db47809 100644 --- a/components/datetime/benches/datetime.rs +++ b/components/datetime/benches/datetime.rs @@ -50,7 +50,7 @@ fn datetime_benches(c: &mut Criterion) { let skeleton = setup.options.semantic.unwrap(); let dtf = { - FixedCalendarDateTimeFormatter::::try_new_with_skeleton( + FixedCalendarDateTimeFormatter::::try_new( &locale.into(), skeleton, ) diff --git a/components/datetime/benches/fixtures/mod.rs b/components/datetime/benches/fixtures/mod.rs index fd1e3fb7d48..e9aca9c1a62 100644 --- a/components/datetime/benches/fixtures/mod.rs +++ b/components/datetime/benches/fixtures/mod.rs @@ -2,7 +2,7 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -use icu_datetime::options::DateTimeFormatterOptions; +use icu_datetime::{neo_skeleton, options::components}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -22,9 +22,9 @@ pub struct TestInput { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct TestOptions { - pub length: Option, - pub components: Option, - pub semantic: Option, + pub length: Option, + pub components: Option, + pub semantic: Option, pub preferences: Option, } @@ -47,15 +47,32 @@ pub enum TestLength { } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct PatternsFixture(pub Vec); +pub struct TestComponentsBag { + pub era: Option, + pub year: Option, + pub month: Option, + pub week: Option, + pub day: Option, + pub weekday: Option, + + pub hour: Option, + pub minute: Option, + pub second: Option, + pub fractional_second: Option, + + pub time_zone_name: Option, -#[allow(dead_code)] -pub fn get_options(input: &TestOptions) -> Option { - if let Some(bag) = input.length { - return Some(bag.into()); - } - if let Some(bag) = input.components { - return Some(bag.into()); - } - None + pub hour_cycle: Option, } + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum TestHourCycle { + H11, + H12, + H23, + H24, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PatternsFixture(pub Vec); diff --git a/components/datetime/benches/fixtures/tests/components.json b/components/datetime/benches/fixtures/tests/components.json index f2a984c5693..e073007273e 100644 --- a/components/datetime/benches/fixtures/tests/components.json +++ b/components/datetime/benches/fixtures/tests/components.json @@ -249,8 +249,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["weekday", "hour", "minute"], - "length": "medium" + "fieldSet": ["weekday", "time"], + "length": "medium", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h12" } } @@ -265,8 +266,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["weekday", "hour", "minute", "second"], - "length": "medium" + "fieldSet": ["weekday", "time"], + "length": "medium", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h12" } } @@ -280,8 +282,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["weekday", "hour", "minute"], - "length": "medium" + "fieldSet": ["weekday", "time"], + "length": "medium", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h23" } } @@ -296,8 +299,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["weekday", "hour", "minute", "second"], - "length": "medium" + "fieldSet": ["weekday", "time"], + "length": "medium", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h23" } } @@ -309,8 +313,9 @@ "hour": "numeric" }, "semantic": { - "fieldSet": ["hour"], - "length": "medium" + "fieldSet": ["time"], + "length": "medium", + "timePrecision": "hourExact" }, "preferences": { "hourCycle": "h12" } } @@ -323,8 +328,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "medium" + "fieldSet": ["time"], + "length": "medium", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h12" } } @@ -338,8 +344,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute", "second"], - "length": "medium" + "fieldSet": ["time"], + "length": "medium", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h12" } } @@ -351,8 +358,9 @@ "hour": "numeric" }, "semantic": { - "fieldSet": ["hour"], - "length": "medium" + "fieldSet": ["time"], + "length": "medium", + "timePrecision": "hourExact" }, "preferences": { "hourCycle": "h23" } } @@ -365,8 +373,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "medium" + "fieldSet": ["time"], + "length": "medium", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h23" } } @@ -380,8 +389,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute", "second"], - "length": "medium" + "fieldSet": ["time"], + "length": "medium", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h23" } } @@ -394,8 +404,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "medium" + "fieldSet": ["time"], + "length": "medium", + "timePrecision": "minuteExact" } } }, @@ -439,8 +450,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute"], - "length": "medium" + "fieldSet": ["year", "month", "day", "weekday", "time"], + "length": "medium", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h12" } } @@ -458,8 +470,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute"], - "length": "long" + "fieldSet": ["year", "month", "day", "weekday", "time"], + "length": "long", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h12" } } @@ -472,8 +485,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h11" } } @@ -486,8 +500,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h24" } } @@ -500,8 +515,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h11" } } @@ -514,8 +530,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h12" } } @@ -528,8 +545,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h23" } } @@ -542,8 +560,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h24" } } diff --git a/components/datetime/benches/fixtures/tests/lengths.json b/components/datetime/benches/fixtures/tests/lengths.json index d3ee07c234e..b5daeb37155 100644 --- a/components/datetime/benches/fixtures/tests/lengths.json +++ b/components/datetime/benches/fixtures/tests/lengths.json @@ -61,8 +61,9 @@ "time": "medium" }, "semantic": { - "fieldSet": ["hour", "minute", "second"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "secondPlus" } } }, @@ -74,8 +75,9 @@ "time": "short" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" } } }, @@ -87,8 +89,9 @@ "time": "medium" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute", "second"], - "length": "long" + "fieldSet": ["year", "month", "day", "weekday", "time"], + "length": "long", + "timePrecision": "secondPlus" } } }, @@ -100,8 +103,9 @@ "time": "short" }, "semantic": { - "fieldSet": ["year", "month", "day", "hour", "minute"], - "length": "long" + "fieldSet": ["year", "month", "day", "time"], + "length": "long", + "timePrecision": "minuteExact" } } }, @@ -113,8 +117,9 @@ "time": "medium" }, "semantic": { - "fieldSet": ["year", "month", "day", "hour", "minute", "second"], - "length": "medium" + "fieldSet": ["year", "month", "day", "time"], + "length": "medium", + "timePrecision": "secondPlus" } } }, @@ -126,8 +131,9 @@ "time": "short" }, "semantic": { - "fieldSet": ["year", "month", "day", "hour", "minute"], - "length": "short" + "fieldSet": ["year", "month", "day", "time"], + "length": "short", + "timePrecision": "minuteExact" } } } diff --git a/components/datetime/benches/fixtures/tests/lengths_with_zones.json b/components/datetime/benches/fixtures/tests/lengths_with_zones.json index f01f414a13d..af2e9b71de1 100644 --- a/components/datetime/benches/fixtures/tests/lengths_with_zones.json +++ b/components/datetime/benches/fixtures/tests/lengths_with_zones.json @@ -9,8 +9,9 @@ "time": "full" }, "semantic": { - "fieldSet": ["hour", "minute", "second", "zoneSpecific"], - "length": "long" + "fieldSet": ["time", "zoneSpecific"], + "length": "long", + "timePrecision": "secondPlus" } } }, @@ -22,8 +23,9 @@ "time": "long" }, "semantic": { - "fieldSet": ["hour", "minute", "second", "zoneSpecific"], - "length": "medium" + "fieldSet": ["time", "zoneSpecific"], + "length": "medium", + "timePrecision": "secondPlus" } } }, @@ -35,8 +37,9 @@ "time": "full" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute", "second", "zoneSpecific"], - "length": "long" + "fieldSet": ["year", "month", "day", "weekday", "time", "zoneSpecific"], + "length": "long", + "timePrecision": "secondPlus" } } }, @@ -48,8 +51,9 @@ "time": "long" }, "semantic": { - "fieldSet": ["year", "month", "day", "hour", "minute", "second", "zoneSpecific"], - "length": "medium" + "fieldSet": ["year", "month", "day", "time", "zoneSpecific"], + "length": "medium", + "timePrecision": "secondPlus" } } } diff --git a/components/datetime/examples/work_log.rs b/components/datetime/examples/work_log.rs index 354e5a458b7..8c4c91af5d2 100644 --- a/components/datetime/examples/work_log.rs +++ b/components/datetime/examples/work_log.rs @@ -10,7 +10,7 @@ icu_benchmark_macros::instrument!(); use icu_benchmark_macros::println; use icu_calendar::DateTime; -use icu_datetime::{fieldset::YMDHM, FixedCalendarDateTimeFormatter}; +use icu_datetime::{fieldset::YMDT, FixedCalendarDateTimeFormatter}; use icu_locale_core::locale; const DATES_ISO: &[(i32, u8, u8, u8, u8, u8)] = &[ @@ -27,7 +27,7 @@ const DATES_ISO: &[(i32, u8, u8, u8, u8, u8)] = &[ ]; fn main() { - let dtf = FixedCalendarDateTimeFormatter::try_new(&locale!("en").into(), YMDHM::medium()) + let dtf = FixedCalendarDateTimeFormatter::try_new(&locale!("en").into(), YMDT::medium()) .expect("Failed to create FixedCalendarDateTimeFormatter instance."); println!("\n====== Work Log (en) example ============"); diff --git a/components/datetime/src/combo.rs b/components/datetime/src/combo.rs index 5b60efb8328..174bfe0ed37 100644 --- a/components/datetime/src/combo.rs +++ b/components/datetime/src/combo.rs @@ -4,27 +4,30 @@ use core::marker::PhantomData; -use crate::{format::neo::*, neo_skeleton::*, provider::neo::*, scaffold::*}; -use icu_provider::marker::NeverMarker; +use crate::{format::neo::*, provider::neo::*, scaffold::*}; -/// Struct for combining date, time, and zone fields. +/// Struct for combining date/time fields with zone fields. /// /// This struct produces "composite field sets" as defined in UTS 35. /// +/// This struct does not have its own constructor, but it can be produced via +/// factory functions on the other field sets. +/// /// # Examples /// /// Format the weekday, hour, and location-based zone: /// /// ``` -/// use icu::datetime::fieldset::{Combo, E, HM, L}; +/// use icu::datetime::fieldset::{Combo, ET, L}; /// use icu::datetime::DateTimeFormatter; /// use icu::locale::locale; /// use icu::timezone::IxdtfParser; /// use writeable::assert_try_writeable_eq; /// -/// let formatter = DateTimeFormatter::try_new( +/// // Note: Combo type can be elided, but it is shown here for demonstration +/// let formatter = DateTimeFormatter::>::try_new( /// &locale!("en-US").into(), -/// Combo::::short(), +/// ET::short().hm().l(), /// ) /// .unwrap(); /// @@ -43,15 +46,16 @@ use icu_provider::marker::NeverMarker; /// /// ``` /// use icu::calendar::Gregorian; -/// use icu::datetime::fieldset::{Combo, E, HM, L}; +/// use icu::datetime::fieldset::{Combo, ET, L}; /// use icu::datetime::FixedCalendarDateTimeFormatter; /// use icu::locale::locale; /// use icu::timezone::IxdtfParser; /// use writeable::assert_try_writeable_eq; /// -/// let formatter = FixedCalendarDateTimeFormatter::try_new( +/// // Note: Combo type can be elided, but it is shown here for demonstration +/// let formatter = FixedCalendarDateTimeFormatter::<_, Combo>::try_new( /// &locale!("en-US").into(), -/// Combo::::short(), +/// ET::short().hm().l(), /// ) /// .unwrap(); /// @@ -66,221 +70,68 @@ use icu_provider::marker::NeverMarker; /// "Fri, 3:44 PM Los Angeles Time" /// ); /// ``` -#[derive(Debug)] -pub struct Combo { - _d: PhantomData, - _t: PhantomData, +/// +/// Mix a dynamic [`DateFieldSet`](crate::fieldset::dynamic::DateFieldSet) +/// with a static time zone: +/// +/// ``` +/// use icu::datetime::fieldset::{Combo, YMD, Vs, dynamic::DateFieldSet}; +/// use icu::datetime::DateTimeFormatter; +/// use icu::locale::locale; +/// use icu::timezone::IxdtfParser; +/// use writeable::assert_try_writeable_eq; +/// +/// // Note: Combo type can be elided, but it is shown here for demonstration +/// let formatter = DateTimeFormatter::>::try_new( +/// &locale!("en-US").into(), +/// DateFieldSet::YMD(YMD::long()).v(), +/// ) +/// .unwrap(); +/// +/// let zdt = IxdtfParser::new().try_location_only_from_str( +/// "2024-10-18T15:44[America/Los_Angeles]", +/// ) +/// .unwrap(); +/// +/// assert_try_writeable_eq!( +/// formatter.convert_and_format(&zdt), +/// "October 18, 2024 PT" +/// ); +/// ``` +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Combo { + date_time_field_set: DT, _z: PhantomData, - /// Desired formatting length. - pub length: NeoSkeletonLength, - /// Alignment option. - pub alignment: Option, - /// Era display option. - pub year_style: Option, - /// Fractional second digits option. - pub fractional_second_digits: Option, } -impl UnstableSealed for Combo {} - -impl Combo { - /// Creates a date/time/zone skeleton with the given formatting length. - pub const fn with_length(length: NeoSkeletonLength) -> Self { +impl Combo { + #[inline] + pub(crate) const fn new(date_time_field_set: DT) -> Self { Self { - _d: PhantomData, - _t: PhantomData, + date_time_field_set, _z: PhantomData, - length, - alignment: None, - year_style: None, - fractional_second_digits: None, } } - /// Creates a date/time/zone skeleton with a long length. - pub const fn long() -> Self { - Self::with_length(NeoSkeletonLength::Long) - } - /// Creates a date/time/zone skeleton with a medium length. - pub const fn medium() -> Self { - Self::with_length(NeoSkeletonLength::Medium) - } - /// Creates a date/time/zone skeleton with a short length. - pub const fn short() -> Self { - Self::with_length(NeoSkeletonLength::Short) - } -} - -impl_get_field!( Combo, never); -impl_get_field!( Combo, length, yes); -impl_get_field!( Combo, alignment, yes); -impl_get_field!( Combo, year_style, yes); -impl_get_field!( Combo, fractional_second_digits, yes); - -impl DateTimeNamesMarker for Combo -where - D: DateTimeNamesMarker, -{ - type YearNames = D::YearNames; - type MonthNames = D::MonthNames; - type WeekdayNames = D::WeekdayNames; - type DayPeriodNames = NeverMarker<()>; - type ZoneEssentials = NeverMarker<()>; - type ZoneLocations = NeverMarker<()>; - type ZoneGenericLong = NeverMarker<()>; - type ZoneGenericShort = NeverMarker<()>; - type ZoneSpecificLong = NeverMarker<()>; - type ZoneSpecificShort = NeverMarker<()>; - type MetazoneLookup = NeverMarker<()>; -} - -impl HasConstComponents for Combo -where - D: HasConstDateComponents, -{ - const COMPONENTS: NeoComponents = NeoComponents::Date(D::COMPONENTS); } -impl DateTimeMarkers for Combo -where - D: DateTimeMarkers, -{ - type D = D; - type T = NeoNeverMarker; - type Z = NeoNeverMarker; - type LengthOption = NeoSkeletonLength; // always needed for date - type AlignmentOption = D::AlignmentOption; - type YearStyleOption = D::YearStyleOption; - type FractionalSecondDigitsOption = (); - type GluePatternV1Marker = NeverMarker>; -} +impl UnstableSealed for Combo {} -impl DateTimeNamesMarker for Combo -where - T: DateTimeNamesMarker, -{ - type YearNames = NeverMarker<()>; - type MonthNames = NeverMarker<()>; - type WeekdayNames = NeverMarker<()>; - type DayPeriodNames = T::DayPeriodNames; - type ZoneEssentials = NeverMarker<()>; - type ZoneLocations = NeverMarker<()>; - type ZoneGenericLong = NeverMarker<()>; - type ZoneGenericShort = NeverMarker<()>; - type ZoneSpecificLong = NeverMarker<()>; - type ZoneSpecificShort = NeverMarker<()>; - type MetazoneLookup = NeverMarker<()>; -} - -impl HasConstComponents for Combo -where - T: HasConstTimeComponents, -{ - const COMPONENTS: NeoComponents = NeoComponents::Time(T::COMPONENTS); -} - -impl DateTimeMarkers for Combo -where - T: DateTimeMarkers, -{ - type D = NeoNeverMarker; - type T = T; - type Z = NeoNeverMarker; - type LengthOption = NeoSkeletonLength; // always needed for time - type AlignmentOption = Option; // always needed for time - type YearStyleOption = (); // no year in a time-only format - type FractionalSecondDigitsOption = T::FractionalSecondDigitsOption; - type GluePatternV1Marker = NeverMarker>; -} - -impl DateTimeNamesMarker for Combo -where - Z: DateTimeNamesMarker, -{ - type YearNames = NeverMarker<()>; - type MonthNames = NeverMarker<()>; - type WeekdayNames = NeverMarker<()>; - type DayPeriodNames = NeverMarker<()>; - type ZoneEssentials = Z::ZoneEssentials; - type ZoneLocations = Z::ZoneLocations; - type ZoneGenericLong = Z::ZoneGenericLong; - type ZoneGenericShort = Z::ZoneGenericShort; - type ZoneSpecificLong = Z::ZoneSpecificLong; - type ZoneSpecificShort = Z::ZoneSpecificShort; - type MetazoneLookup = Z::MetazoneLookup; -} - -impl HasConstComponents for Combo -where - Z: HasConstZoneComponent, -{ - const COMPONENTS: NeoComponents = NeoComponents::Zone(Z::COMPONENT); -} - -impl DateTimeMarkers for Combo -where - Z: DateTimeMarkers, -{ - type D = NeoNeverMarker; - type T = NeoNeverMarker; - type Z = Z; - type LengthOption = Z::LengthOption; // no date or time: inherit from zone - type AlignmentOption = Z::AlignmentOption; // no date or time: inherit from zone - type YearStyleOption = (); // no year in a zone-only format - type FractionalSecondDigitsOption = (); - type GluePatternV1Marker = GluePatternV1Marker; -} - -impl DateTimeNamesMarker for Combo -where - D: DateTimeNamesMarker, - T: DateTimeNamesMarker, -{ - type YearNames = D::YearNames; - type MonthNames = D::MonthNames; - type WeekdayNames = D::WeekdayNames; - type DayPeriodNames = T::DayPeriodNames; - type ZoneEssentials = NeverMarker<()>; - type ZoneLocations = NeverMarker<()>; - type ZoneGenericLong = NeverMarker<()>; - type ZoneGenericShort = NeverMarker<()>; - type ZoneSpecificLong = NeverMarker<()>; - type ZoneSpecificShort = NeverMarker<()>; - type MetazoneLookup = NeverMarker<()>; -} - -impl HasConstComponents for Combo -where - D: HasConstDateComponents, - T: HasConstTimeComponents, -{ - const COMPONENTS: NeoComponents = NeoComponents::DateTime(D::COMPONENTS, T::COMPONENTS); -} - -impl DateTimeMarkers for Combo -where - D: DateTimeMarkers, - T: DateTimeMarkers, -{ - type D = D; - type T = T; - type Z = NeoNeverMarker; - type LengthOption = NeoSkeletonLength; // always needed for date/time - type AlignmentOption = Option; // always needed for date/time - type YearStyleOption = D::YearStyleOption; - type FractionalSecondDigitsOption = T::FractionalSecondDigitsOption; - type GluePatternV1Marker = GluePatternV1Marker; +impl Combo { + #[inline] + pub(crate) fn dt(self) -> DT { + self.date_time_field_set + } } -impl DateTimeNamesMarker for Combo +impl DateTimeNamesMarker for Combo where - D: DateTimeNamesMarker, - T: DateTimeNamesMarker, + DT: DateTimeNamesMarker, Z: DateTimeNamesMarker, { - type YearNames = D::YearNames; - type MonthNames = D::MonthNames; - type WeekdayNames = D::WeekdayNames; - type DayPeriodNames = T::DayPeriodNames; + type YearNames = DT::YearNames; + type MonthNames = DT::MonthNames; + type WeekdayNames = DT::WeekdayNames; + type DayPeriodNames = DT::DayPeriodNames; type ZoneEssentials = Z::ZoneEssentials; type ZoneLocations = Z::ZoneLocations; type ZoneGenericLong = Z::ZoneGenericLong; @@ -290,30 +141,13 @@ where type MetazoneLookup = Z::MetazoneLookup; } -impl HasConstComponents for Combo +impl DateTimeMarkers for Combo where - D: HasConstDateComponents, - T: HasConstTimeComponents, - Z: HasConstZoneComponent, -{ - const COMPONENTS: NeoComponents = - NeoComponents::DateTimeZone(D::COMPONENTS, T::COMPONENTS, Z::COMPONENT); -} - -impl DateTimeMarkers for Combo -where - D: DateTimeMarkers, - T: DateTimeMarkers, + DT: DateTimeMarkers, Z: DateTimeMarkers, { - type D = D; - type T = T; - type Z = Z; - type LengthOption = NeoSkeletonLength; // always needed for date/time - type AlignmentOption = Option; // always needed for date/time - type YearStyleOption = D::YearStyleOption; - type FractionalSecondDigitsOption = T::FractionalSecondDigitsOption; - type GluePatternV1Marker = GluePatternV1Marker; + type D = DT::D; + type T = DT::T; + type Z = Z::Z; + type GluePatternV1Marker = datetime_marker_helper!(@glue, yes); } - -// TODO: Fill in the missing Combos, like DZ and TZ diff --git a/components/datetime/src/dynamic.rs b/components/datetime/src/dynamic.rs new file mode 100644 index 00000000000..6f66caf6d44 --- /dev/null +++ b/components/datetime/src/dynamic.rs @@ -0,0 +1,483 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#[cfg(feature = "serde")] +use crate::neo_serde::*; +use crate::options::preferences::HourCycle; +use crate::raw::neo::RawNeoOptions; +use crate::scaffold::GetField; +use crate::{fields, fieldset, NeoSkeletonLength}; +use icu_provider::prelude::*; + +/// An enumeration over all possible date field sets. +/// +/// 📏 Note: This enum can be used as the field set parameter of +/// [`DateTimeFormatter`](crate::DateTimeFormatter), but doing so may link +/// more formatting data compared to the individual field set structs. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum DateFieldSet { + /// The day of the month, as in + /// “on the 1st”. + D(fieldset::D), + /// The month and day of the month, as in + /// “January 1st”. + MD(fieldset::MD), + /// The year, month, and day of the month, as in + /// “January 1st, 2000”. + YMD(fieldset::YMD), + /// The day of the month and day of the week, as in + /// “Saturday 1st”. + DE(fieldset::DE), + /// The month, day of the month, and day of the week, as in + /// “Saturday, January 1st”. + MDE(fieldset::MDE), + /// The year, month, day of the month, and day of the week, as in + /// “Saturday, January 1st, 2000”. + YMDE(fieldset::YMDE), + /// The day of the week alone, as in + /// “Saturday”. + E(fieldset::E), +} + +/// An enumeration over all possible calendar period field sets. +/// +/// 📏 Note: This enum can be used as the field set parameter of +/// [`DateTimeFormatter`](crate::DateTimeFormatter), but doing so may link +/// more formatting data compared to the individual field set structs. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum CalendarPeriodFieldSet { + /// A standalone month, as in + /// “January”. + M(fieldset::M), + /// A month and year, as in + /// “January 2000”. + YM(fieldset::YM), + /// A year, as in + /// “2000”. + Y(fieldset::Y), + // TODO: Add support for week-of-year + // /// The year and week of the year, as in + // /// “52nd week of 1999”. + // YW(fieldset::YW), + // TODO(#501): Consider adding support for Quarter and YearQuarter. +} + +/// An enumeration over all possible time field sets. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum TimeFieldSet { + /// A time of day. + T(fieldset::T), +} + +/// An enumeration over all possible zone field sets. +/// +/// 📏 Note: This enum can be used as the field set parameter of +/// [`DateTimeFormatter`](crate::DateTimeFormatter), but doing so may link +/// more formatting data compared to the individual field set structs. +/// +/// Note: [`fieldset::Zs`] and [`fieldset::Vs`] are not included in this enum +/// because they are data size optimizations only. +/// +/// # Time Zone Data Size +/// +/// Time zone names contribute a lot of data size. For resource-constrained +/// environments, the following formats require the least amount of data: +/// +/// - [`fieldset::Zs`] +/// - [`fieldset::O`] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum ZoneFieldSet { + /// The specific non-location format, as in + /// “Pacific Daylight Time”. + Z(fieldset::Z), + /// The offset format, as in + /// “GMT−8”. + O(fieldset::O), + /// The generic non-location format, as in + /// “Pacific Time”. + V(fieldset::V), + /// The location format, as in + /// “Los Angeles time”. + L(fieldset::L), +} + +/// An enumeration over all possible zone styles. +/// +/// This is similar to [`ZoneFieldSet`], except the fields are not +/// self-contained semantic skeletons: they do not contain the length. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum ZoneStyle { + /// The specific non-location format, as in + /// “Pacific Daylight Time”. + Z, + /// The offset format, as in + /// “GMT−8”. + O, + /// The generic non-location format, as in + /// “Pacific Time”. + V, + /// The location format, as in + /// “Los Angeles time”. + L, +} + +/// An enumeration over all possible date+time composite field sets. +/// +/// 📏 Note: This enum can be used as the field set parameter of +/// [`DateTimeFormatter`](crate::DateTimeFormatter), but doing so may link +/// more formatting data compared to the individual field set structs. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum DateAndTimeFieldSet { + /// The day of the month with time of day, as in + /// “on the 1st at 10:31 AM”. + DT(fieldset::DT), + /// The month and day of the month with time of day, as in + /// “January 1st at 10:31 AM”. + MDT(fieldset::MDT), + /// The year, month, and day of the month with time of day, as in + /// “January 1st, 2000 at 10:31 AM”. + YMDT(fieldset::YMDT), + /// The day of the month and day of the week with time of day, as in + /// “Saturday 1st at 10:31 AM”. + DET(fieldset::DET), + /// The month, day of the month, and day of the week with time of day, as in + /// “Saturday, January 1st at 10:31 AM”. + MDET(fieldset::MDET), + /// The year, month, day of the month, and day of the week with time of day, as in + /// “Saturday, January 1st, 2000 at 10:31 AM”. + YMDET(fieldset::YMDET), + /// The day of the week alone with time of day, as in + /// “Saturday at 10:31 AM”. + ET(fieldset::ET), +} + +/// An enum supporting date, calendar period, time, and date+time field sets +/// and options. Time zones are not supported with this enum. +/// +/// This enum is useful when formatting a type that does not contain a +/// time zone or to avoid storing time zone data. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum CompositeDateTimeFieldSet { + /// Field set for a date. + Date(DateFieldSet), + /// Field set for a calendar period. + CalendarPeriod(CalendarPeriodFieldSet), + /// Field set for a time. + Time(TimeFieldSet), + /// Field set for a date and a time together. + DateTime(DateAndTimeFieldSet), +} + +impl CompositeDateTimeFieldSet { + /// If the [`CompositeFieldSet`] does not contain a time zone, + /// returns the corresponding [`CompositeDateTimeFieldSet`]. + pub fn try_from_composite_field_set(field_set: CompositeFieldSet) -> Option { + match field_set { + CompositeFieldSet::Date(v) => Some(Self::Date(v)), + CompositeFieldSet::CalendarPeriod(v) => Some(Self::CalendarPeriod(v)), + CompositeFieldSet::Time(v) => Some(Self::Time(v)), + CompositeFieldSet::Zone(_) => None, + CompositeFieldSet::DateTime(v) => Some(Self::DateTime(v)), + CompositeFieldSet::DateZone(_, _) => None, + CompositeFieldSet::TimeZone(_, _) => None, + CompositeFieldSet::DateTimeZone(_, _) => None, + } + } + + /// Returns the [`CompositeFieldSet`] corresponding to this + /// [`CompositeDateTimeFieldSet`]. + pub fn to_composite_field_set(self) -> CompositeFieldSet { + match self { + Self::Date(v) => CompositeFieldSet::Date(v), + Self::CalendarPeriod(v) => CompositeFieldSet::CalendarPeriod(v), + Self::Time(v) => CompositeFieldSet::Time(v), + Self::DateTime(v) => CompositeFieldSet::DateTime(v), + } + } +} + +impl GetField for CompositeDateTimeFieldSet { + fn get_field(&self) -> CompositeFieldSet { + self.to_composite_field_set() + } +} + +/// An enum supporting all possible field sets and options. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + serde(try_from = "SemanticSkeletonSerde", into = "SemanticSkeletonSerde") +)] +#[non_exhaustive] +pub enum CompositeFieldSet { + /// Field set for a date. + Date(DateFieldSet), + /// Field set for a calendar period. + CalendarPeriod(CalendarPeriodFieldSet), + /// Field set for a time. + Time(TimeFieldSet), + /// Field set for a time zone. + Zone(ZoneFieldSet), + /// Field set for a date and a time together. + DateTime(DateAndTimeFieldSet), + /// Field set for a date and a time zone together. + DateZone(DateFieldSet, ZoneStyle), + /// Field set for a time and a time zone together. + TimeZone(TimeFieldSet, ZoneStyle), + /// Field set for a date, a time, and a time zone together. + DateTimeZone(DateAndTimeFieldSet, ZoneStyle), +} + +macro_rules! first { + ($first:literal, $($remainder:literal,)*) => { + $first + }; +} + +macro_rules! impl_attrs { + (@attrs, $type:path, [$(($attr_var:ident, $str_var:ident, $value:literal),)+]) => { + impl $type { + $( + const $attr_var: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic($value); + )+ + /// All attributes associated with this enum. + /// + /// # Encoding Details + /// + /// The string is based roughly on the UTS 35 symbol table with the following exceptions: + /// + /// 1. Lowercase letters are chosen where there is no ambiguity: `E` becomes `e` + /// 2. Capitals are replaced with their lowercase and a number 0: `M` becomes `m0` + /// 3. A single symbol is included for each component: length doesn't matter + /// 4. Time fields are encoded with their hour field only: `j`, `h`, or `h0` + /// + /// # Examples + /// + /// ``` + #[doc = concat!("use icu::datetime::fieldset::dynamic::", stringify!($type), " as FS;")] + /// use icu_provider::DataMarkerAttributes; + /// + /// assert!(FS::ALL_DATA_MARKER_ATTRIBUTES.contains( + #[doc = concat!(" &DataMarkerAttributes::from_str_or_panic(\"", first!($($value,)*), "\")")] + /// )); + /// ``` + pub const ALL_DATA_MARKER_ATTRIBUTES: &'static [&'static DataMarkerAttributes] = &[ + $( + Self::$attr_var, + )+ + ]; + } + }; + (@id_str, $type:path, [$(($variant:ident, $attr_var:ident)),+,]) => { + impl $type { + /// Returns a stable string identifying this set of fields. + pub(crate) const fn id_str(self) -> &'static DataMarkerAttributes { + match self { + $( + Self::$variant(_) => Self::$attr_var, + )+ + } + } + } + }; + (@to_raw_options, $type:path, [$($variant:ident),+,]) => { + impl $type { + pub(crate) fn to_raw_options(self) -> RawNeoOptions { + match self { + $( + Self::$variant(variant) => variant.to_raw_options(), + )+ + } + } + } + }; + (@composite, $type:path, $variant:ident) => { + impl $type { + #[inline] + pub(crate) fn to_enum(self) -> $type { + self + } + } + impl GetField for $type { + #[inline] + fn get_field(&self) -> CompositeFieldSet { + CompositeFieldSet::$variant(self.to_enum()) + } + } + }; + (@date, $type:path, [$(($variant:ident, $attr_var:ident, $str_var:ident, $value:literal)),+,]) => { + impl_attrs! { @attrs, $type, [$(($attr_var, $str_var, $value)),+,] } + impl_attrs! { @id_str, $type, [$(($variant, $attr_var)),+,] } + impl_attrs! { @to_raw_options, $type, [$($variant),+,] } + impl_attrs! { @composite, $type, Date } + }; + (@calendar_period, $type:path, [$(($variant:ident, $attr_var:ident, $str_var:ident, $value:literal)),+,]) => { + impl_attrs! { @attrs, $type, [$(($attr_var, $str_var, $value)),+,] } + impl_attrs! { @to_raw_options, $type, [$($variant),+,] } + impl_attrs! { @composite, $type, CalendarPeriod } + impl_attrs! { @id_str, $type, [$(($variant, $attr_var)),+,] } + }; + (@time, $type:path, [$(($attr_var:ident, $str_var:ident, $value:literal)),+,]) => { + impl_attrs! { @attrs, $type, [$(($attr_var, $str_var, $value)),+,] } + impl_attrs! { @to_raw_options, $type, [T,] } + impl_attrs! { @composite, $type, Time } + }; + (@zone, $type:path, [$($variant:ident),+,]) => { + impl_attrs! { @composite, $type, Zone } + impl $type { + pub(crate) fn to_field(self) -> (fields::TimeZone, fields::FieldLength) { + match self { + $( + Self::$variant(variant) => variant.to_field(), + )+ + } + } + } + }; + (@datetime, $type:path, [$(($d_variant:ident, $variant:ident)),+,]) => { + impl_attrs! { @to_raw_options, $type, [$($variant),+,] } + impl_attrs! { @composite, $type, DateTime } + impl $type { + pub(crate) fn to_date_field_set(self) -> DateFieldSet { + match self { + $( + Self::$variant(variant) => DateFieldSet::$d_variant(variant.to_date_field_set()), + )+ + } + } + pub(crate) fn to_time_field_set(self) -> TimeFieldSet { + let (length, time_precision, alignment) = match self { + $( + Self::$variant(variant) => (variant.length, variant.time_precision, variant.alignment), + )+ + }; + TimeFieldSet::T(fieldset::T { + length, + time_precision, + alignment, + }) + } + #[cfg(feature = "serde")] + pub(crate) fn from_date_field_set_with_raw_options(date_field_set: DateFieldSet, options: RawNeoOptions) -> Self { + match date_field_set { + $( + DateFieldSet::$d_variant(_) => Self::$variant(fieldset::$variant::from_raw_options(options)), + )+ + } + } + } + }; +} + +impl_attrs! { + @date, + DateFieldSet, + [ + (D, ATTR_D, STR_D, "d"), + (MD, ATTR_MD, STR_MD, "m0d"), + (YMD, ATTR_YMD, STR_YMD, "ym0d"), + (DE, ATTR_DE, STR_DE, "de"), + (MDE, ATTR_MDE, STR_MDE, "m0de"), + (YMDE, ATTR_YMDE, STR_YMDE, "ym0de"), + (E, ATTR_E, STR_E, "e"), + ] +} + +impl_attrs! { + @calendar_period, + CalendarPeriodFieldSet, + [ + (M, ATTR_M, STR_M, "m0"), + (YM, ATTR_YM, STR_YM, "ym0"), + (Y, ATTR_Y, STR_Y, "y"), + ] +} + +impl_attrs! { + @time, + TimeFieldSet, + [ + (ATTR_T, STR_T, "j"), + (ATTR_T12, STR_T12, "h"), + (ATTR_T24, STR_T24, "h0"), + ] +} + +impl TimeFieldSet { + pub(crate) const fn id_str_for_hour_cycle( + self, + hour_cycle: Option, + ) -> &'static DataMarkerAttributes { + use HourCycle::*; + match hour_cycle { + None => Self::ATTR_T, + Some(H11 | H12) => Self::ATTR_T12, + Some(H23 | H24) => Self::ATTR_T24, + } + } +} + +impl_attrs! { + @zone, + ZoneFieldSet, + [ + Z, + O, + V, + L, + ] +} + +impl ZoneFieldSet { + pub(crate) fn from_time_zone_style_and_length( + style: ZoneStyle, + length: NeoSkeletonLength, + ) -> Self { + match style { + ZoneStyle::Z => Self::Z(fieldset::Z::with_length(length)), + ZoneStyle::O => Self::O(fieldset::O::with_length(length)), + ZoneStyle::V => Self::V(fieldset::V::with_length(length)), + ZoneStyle::L => Self::L(fieldset::L::with_length(length)), + } + } +} + +impl_attrs! { + @attrs, + DateAndTimeFieldSet, + [ + (ATTR_ET, STR_ET, "ej"), + ] +} + +impl_attrs! { + @datetime, + DateAndTimeFieldSet, + [ + (D, DT), + (MD, MDT), + (YMD, YMDT), + (DE, DET), + (MDE, MDET), + (YMDE, YMDET), + (E, ET), + ] +} + +impl DateAndTimeFieldSet { + pub(crate) const fn id_str(self) -> Option<&'static DataMarkerAttributes> { + match self { + DateAndTimeFieldSet::ET(_) => Some(Self::ATTR_ET), + _ => None, + } + } +} diff --git a/components/datetime/src/error.rs b/components/datetime/src/error.rs index c29d6da2b46..dfaa5a5ed7b 100644 --- a/components/datetime/src/error.rs +++ b/components/datetime/src/error.rs @@ -2,8 +2,19 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). +use crate::fields::Field; use displaydoc::Display; -use icu_calendar::any_calendar::AnyCalendarKind; +use icu_calendar::{ + any_calendar::AnyCalendarKind, + types::{FormattingEra, MonthCode}, +}; + +#[cfg(doc)] +use crate::TypedDateTimeNames; +#[cfg(doc)] +use icu_calendar::types::YearInfo; +#[cfg(doc)] +use icu_decimal::FixedDecimalFormatter; /// An error from mixing calendar types in a formatter. #[derive(Display, Debug, Copy, Clone, PartialEq)] @@ -16,3 +27,72 @@ pub struct MismatchedCalendarError { /// Can be `None` if the input calendar was not specified. pub date_kind: Option, } + +#[non_exhaustive] +#[derive(Debug, PartialEq, Copy, Clone, displaydoc::Display)] +/// Error for `TryWriteable` implementations +pub enum DateTimeWriteError { + /// The [`MonthCode`] of the input is not valid for this calendar. + /// + /// This is guaranteed not to happen for `icu::calendar` inputs, but may happen for custom inputs. + /// + /// The output will contain the raw [`MonthCode`] as a fallback value. + #[displaydoc("Invalid month {0:?}")] + InvalidMonthCode(MonthCode), + /// The [`FormattingEra`] of the input is not valid for this calendar. + /// + /// This is guaranteed not to happen for `icu::calendar` inputs, but may happen for custom inputs. + /// + /// The output will contain [`FormattingEra::fallback_name`] as the fallback. + #[displaydoc("Invalid era {0:?}")] + InvalidEra(FormattingEra), + /// The [`YearInfo::cyclic`] of the input is not valid for this calendar. + /// + /// This is guaranteed not to happen for `icu::calendar` inputs, but may happen for custom inputs. + /// + /// The output will contain [`YearInfo::extended_year`] as a fallback value. + #[displaydoc("Invalid cyclic year {value} (maximum {max})")] + InvalidCyclicYear { + /// Value + value: usize, + /// Max + max: usize, + }, + + /// The [`FixedDecimalFormatter`] has not been loaded. + /// + /// This *only* happens if the formatter has been created using + /// [`TypedDateTimeNames::with_pattern`], the pattern requires decimal + /// formatting, and the decimal formatter was not loaded. + /// + /// The output will contain fallback values using Latin numerals. + #[displaydoc("FixedDecimalFormatter not loaded")] + FixedDecimalFormatterNotLoaded, + /// The localized names for a field have not been loaded. + /// + /// This *only* happens if the formatter has been created using + /// [`TypedDateTimeNames::with_pattern`], and the pattern requires names + /// that were not loaded. + /// + /// The output will contain fallback values using field identifiers (such as `tue` for `IsoWeekday::Tuesday`, + /// `M02` for month 2, etc.). + #[displaydoc("Names for {0:?} not loaded")] + NamesNotLoaded(Field), + /// An input field (such as "hour" or "month") is missing. + /// + /// This *only* happens if the formatter has been created using + /// [`TypedDateTimeNames::with_pattern`], and the pattern requires fields + /// that are not returned by the input type. + /// + /// The output will contain the string `{X}` instead, where `X` is the symbol for which the input is missing. + #[displaydoc("Incomplete input, missing value for {0:?}")] + MissingInputField(&'static str), + /// Unsupported field + /// + /// This *only* happens if the formatter has been created using + /// [`TypedDateTimeNames::with_pattern`], and the pattern contains unsupported fields. + /// + /// The output will contain the string `{unsupported:X}`, where `X` is the symbol of the unsupported field. + #[displaydoc("Unsupported field {0:?}")] + UnsupportedField(Field), +} diff --git a/components/datetime/src/fields/length.rs b/components/datetime/src/fields/length.rs index 85d4595c96e..b42a9d8ef41 100644 --- a/components/datetime/src/fields/length.rs +++ b/components/datetime/src/fields/length.rs @@ -32,21 +32,29 @@ impl std::error::Error for LengthError {} #[cfg_attr(feature = "serde", derive(serde::Deserialize))] #[allow(clippy::exhaustive_enums)] // part of data struct pub enum FieldLength { - /// Typical style is 1-2 digits. For numeric-only fields. + /// Numeric: minimum digits + /// + /// Text: same as [`Self::Three`] One, - /// Typical style is 2 digits. For numeric-only fields. - TwoDigit, - /// Abbreviated (spellout) format. - Abbreviated, - /// Wide / Long / Full (spellout) format. - Wide, - /// Narrow / Long / Full (spellout) format. - Narrow, - /// Meaning is field-dependent, for patterns that are 6 characters long. Ex: a [`Weekday`](super::Weekday) pattern like - /// `EEEEEE` means "Short", but `jjjjjj` or `CCCCCC` for [`Hour`](super::Hour) may mean - /// "Numeric hour (2 digits, zero pad if needed), narrow dayPeriod if used". See the - /// [LDML documentation in UTS 35](https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns) - /// for more details. + /// Numeric: pad to 2 digits + /// + /// Text: same as [`Self::Three`] + Two, + /// Numeric: pad to 3 digits + /// + /// Text: Abbreviated format. + Three, + /// Numeric: pad to 4 digits + /// + /// Text: Wide format. + Four, + /// Numeric: pad to 5 digits + /// + /// Text: Narrow format. + Five, + /// Numeric: pad to 6 digits + /// + /// Text: Short format. Six, /// FieldLength::One (numeric), but overridden with a different numbering system NumericOverride(FieldNumericOverrides), @@ -65,10 +73,10 @@ impl FieldLength { pub(crate) fn idx(&self) -> u8 { match self { FieldLength::One => 1, - FieldLength::TwoDigit => 2, - FieldLength::Abbreviated => 3, - FieldLength::Wide => 4, - FieldLength::Narrow => 5, + FieldLength::Two => 2, + FieldLength::Three => 3, + FieldLength::Four => 4, + FieldLength::Five => 5, FieldLength::Six => 6, FieldLength::NumericOverride(o) => FIRST_NUMERIC_OVERRIDE .saturating_add(*o as u8) @@ -80,10 +88,10 @@ impl FieldLength { pub(crate) fn from_idx(idx: u8) -> Result { Ok(match idx { 1 => Self::One, - 2 => Self::TwoDigit, - 3 => Self::Abbreviated, - 4 => Self::Wide, - 5 => Self::Narrow, + 2 => Self::Two, + 3 => Self::Three, + 4 => Self::Four, + 5 => Self::Five, 6 => Self::Six, idx if (FIRST_NUMERIC_OVERRIDE..=LAST_NUMERIC_OVERRIDE).contains(&idx) => { Self::NumericOverride((idx - FIRST_NUMERIC_OVERRIDE).try_into()?) @@ -96,10 +104,10 @@ impl FieldLength { pub(crate) fn to_len(self) -> usize { match self { FieldLength::One => 1, - FieldLength::TwoDigit => 2, - FieldLength::Abbreviated => 3, - FieldLength::Wide => 4, - FieldLength::Narrow => 5, + FieldLength::Two => 2, + FieldLength::Three => 3, + FieldLength::Four => 4, + FieldLength::Five => 5, FieldLength::Six => 6, FieldLength::NumericOverride(o) => FIRST_NUMERIC_OVERRIDE as usize + o as usize, } @@ -111,7 +119,7 @@ impl FieldLength { /// This function maps field lengths 1 and 2 to field length 3. pub(crate) fn numeric_to_abbr(self) -> Self { match self { - FieldLength::One | FieldLength::TwoDigit => FieldLength::Abbreviated, + FieldLength::One | FieldLength::Two => FieldLength::Three, other => other, } } diff --git a/components/datetime/src/fields/mod.rs b/components/datetime/src/fields/mod.rs index c08886f7934..21515b1657f 100644 --- a/components/datetime/src/fields/mod.rs +++ b/components/datetime/src/fields/mod.rs @@ -113,27 +113,27 @@ mod test { fn test_field_as_ule() { let samples = [ ( - Field::from((FieldSymbol::Minute, FieldLength::TwoDigit)), - [FieldSymbol::Minute.idx(), FieldLength::TwoDigit.idx()], + Field::from((FieldSymbol::Minute, FieldLength::Two)), + [FieldSymbol::Minute.idx(), FieldLength::Two.idx()], ), ( - Field::from((FieldSymbol::Year(Year::Calendar), FieldLength::Wide)), + Field::from((FieldSymbol::Year(Year::Calendar), FieldLength::Four)), [ FieldSymbol::Year(Year::Calendar).idx(), - FieldLength::Wide.idx(), + FieldLength::Four.idx(), ], ), ( - Field::from((FieldSymbol::Year(Year::WeekOf), FieldLength::Wide)), + Field::from((FieldSymbol::Year(Year::Cyclic), FieldLength::Four)), [ - FieldSymbol::Year(Year::WeekOf).idx(), - FieldLength::Wide.idx(), + FieldSymbol::Year(Year::Cyclic).idx(), + FieldLength::Four.idx(), ], ), ( - Field::from((FieldSymbol::Second(Second::Millisecond), FieldLength::One)), + Field::from((FieldSymbol::Second(Second::MillisInDay), FieldLength::One)), [ - FieldSymbol::Second(Second::Millisecond).idx(), + FieldSymbol::Second(Second::MillisInDay).idx(), FieldLength::One.idx(), ], ), @@ -151,16 +151,16 @@ mod test { fn test_field_ule() { let samples = [( [ - Field::from((FieldSymbol::Year(Year::Calendar), FieldLength::Wide)), - Field::from((FieldSymbol::Second(Second::Millisecond), FieldLength::One)), + Field::from((FieldSymbol::Year(Year::Calendar), FieldLength::Four)), + Field::from((FieldSymbol::Second(Second::MillisInDay), FieldLength::One)), ], [ [ FieldSymbol::Year(Year::Calendar).idx(), - FieldLength::Wide.idx(), + FieldLength::Four.idx(), ], [ - FieldSymbol::Second(Second::Millisecond).idx(), + FieldSymbol::Second(Second::MillisInDay).idx(), FieldLength::One.idx(), ], ], diff --git a/components/datetime/src/fields/symbols.rs b/components/datetime/src/fields/symbols.rs index f612a3df76c..3a6bbb20184 100644 --- a/components/datetime/src/fields/symbols.rs +++ b/components/datetime/src/fields/symbols.rs @@ -203,7 +203,7 @@ impl FieldSymbol { | FieldSymbol::Year(Year::Cyclic) | FieldSymbol::Weekday(Weekday::Format) | FieldSymbol::DayPeriod(_) - | FieldSymbol::TimeZone(TimeZone::LowerZ | TimeZone::UpperZ) + | FieldSymbol::TimeZone(TimeZone::SpecificNonLocation) ) } } @@ -279,17 +279,19 @@ impl FieldSymbol { match self { Self::Era => 0, Self::Year(Year::Calendar) => 1, - Self::Year(Year::WeekOf) => 2, + // Self::Year(Year::WeekOf) => 2, Self::Year(Year::Cyclic) => 3, Self::Year(Year::RelatedIso) => 4, Self::Month(Month::Format) => 5, Self::Month(Month::StandAlone) => 6, - Self::Week(Week::WeekOfYear) => 7, - Self::Week(Week::WeekOfMonth) => 8, + // TODO(#5643): Add week fields back + // Self::Week(Week::WeekOfYear) => 7, + // Self::Week(Week::WeekOfMonth) => 8, + Self::Week(_) => unreachable!(), // ZST references aren't uninhabited Self::Day(Day::DayOfMonth) => 9, Self::Day(Day::DayOfYear) => 10, Self::Day(Day::DayOfWeekInMonth) => 11, - Self::Day(Day::ModifiedJulianDay) => 12, + // Self::Day(Day::ModifiedJulianDay) => 12, Self::Weekday(Weekday::Format) => 13, Self::Weekday(Weekday::Local) => 14, Self::Weekday(Weekday::StandAlone) => 15, @@ -301,7 +303,7 @@ impl FieldSymbol { Self::Hour(Hour::H24) => 21, Self::Minute => 22, Self::Second(Second::Second) => 23, - Self::Second(Second::Millisecond) => 24, + Self::Second(Second::MillisInDay) => 24, Self::DecimalSecond(DecimalSecond::SecondF1) => 31, Self::DecimalSecond(DecimalSecond::SecondF2) => 32, Self::DecimalSecond(DecimalSecond::SecondF3) => 33, @@ -311,13 +313,12 @@ impl FieldSymbol { Self::DecimalSecond(DecimalSecond::SecondF7) => 37, Self::DecimalSecond(DecimalSecond::SecondF8) => 38, Self::DecimalSecond(DecimalSecond::SecondF9) => 39, - Self::TimeZone(TimeZone::LowerZ) => 100, - Self::TimeZone(TimeZone::UpperZ) => 101, - Self::TimeZone(TimeZone::UpperO) => 102, - Self::TimeZone(TimeZone::LowerV) => 103, - Self::TimeZone(TimeZone::UpperV) => 104, - Self::TimeZone(TimeZone::LowerX) => 105, - Self::TimeZone(TimeZone::UpperX) => 106, + Self::TimeZone(TimeZone::SpecificNonLocation) => 100, + Self::TimeZone(TimeZone::LocalizedOffset) => 102, + Self::TimeZone(TimeZone::GenericNonLocation) => 103, + Self::TimeZone(TimeZone::Location) => 104, + Self::TimeZone(TimeZone::Iso) => 105, + Self::TimeZone(TimeZone::IsoWithZ) => 106, } } } @@ -387,8 +388,8 @@ impl Ord for FieldSymbol { } macro_rules! field_type { - ($(#[$enum_attr:meta])* $i:ident; { $( $(#[$variant_attr:meta])* $key:literal => $val:ident = $idx:expr,)* }; $length_type:ident; $ule_name:ident) => ( - field_type!($(#[$enum_attr])* $i; {$( $(#[$variant_attr])* $key => $val = $idx,)*}; $ule_name); + ($(#[$enum_attr:meta])* $i:ident; { $( $(#[$variant_attr:meta])* $key:literal => $val:ident = $idx:expr,)* }; $length_type:ident; $($ule_name:ident)?) => ( + field_type!($(#[$enum_attr])* $i; {$( $(#[$variant_attr])* $key => $val = $idx,)*}; $($ule_name)?); #[cfg(feature = "datagen")] impl LengthType for $i { @@ -397,7 +398,7 @@ macro_rules! field_type { } } ); - ($(#[$enum_attr:meta])* $i:ident; { $( $(#[$variant_attr:meta])* $key:literal => $val:ident = $idx:expr,)* }; $ule_name:ident) => ( + ($(#[$enum_attr:meta])* $i:ident; { $( $(#[$variant_attr:meta])* $key:literal => $val:ident = $idx:expr,)* }; $($ule_name:ident)?) => ( #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, yoke::Yokeable, zerofrom::ZeroFrom)] // FIXME: This should be replaced with a custom derive. // See: https://github.com/unicode-org/icu4x/issues/1044 @@ -405,9 +406,11 @@ macro_rules! field_type { #[cfg_attr(feature = "datagen", databake(path = icu_datetime::fields))] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] #[allow(clippy::enum_variant_names)] - #[repr(u8)] - #[zerovec::make_ule($ule_name)] - #[zerovec::derive(Debug)] + $( + #[repr(u8)] + #[zerovec::make_ule($ule_name)] + #[zerovec::derive(Debug)] + )? #[allow(clippy::exhaustive_enums)] // used in data struct $(#[$enum_attr])* pub enum $i { @@ -419,6 +422,10 @@ macro_rules! field_type { )* } + $( + #[allow(path_statements)] // #5643 impl conditional on $ule_name + const _: () = { $ule_name; }; + impl $i { /// Retrieves an index of the field variant. /// @@ -461,6 +468,7 @@ macro_rules! field_type { .ok_or(SymbolError::InvalidIndex(idx)) } } + )? impl TryFrom for $i { type Error = SymbolError; @@ -498,16 +506,16 @@ field_type! ( Year; { /// Field symbol for calendar year (numeric). /// - /// 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. + /// 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. 'y' => Calendar = 0, - /// Field symbol for year in "week of year". - /// - /// 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`]. - 'Y' => WeekOf = 1, /// Field symbol for cyclic year; used in calendars where years are tracked in cycles, such as the Chinese or Dangi calendars. - 'U' => Cyclic = 2, + 'U' => Cyclic = 1, /// 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. - 'r' => RelatedIso = 3, + 'r' => RelatedIso = 2, + // /// Field symbol for year in "week of year". + // /// + // /// 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`]. + // 'Y' => WeekOf = 3, }; YearULE ); @@ -540,10 +548,10 @@ impl LengthType for Month { match length { FieldLength::One => TextOrNumeric::Numeric, FieldLength::NumericOverride(_) => TextOrNumeric::Numeric, - FieldLength::TwoDigit => TextOrNumeric::Numeric, - FieldLength::Abbreviated => TextOrNumeric::Text, - FieldLength::Wide => TextOrNumeric::Text, - FieldLength::Narrow => TextOrNumeric::Text, + FieldLength::Two => TextOrNumeric::Numeric, + FieldLength::Three => TextOrNumeric::Text, + FieldLength::Four => TextOrNumeric::Text, + FieldLength::Five => TextOrNumeric::Text, FieldLength::Six => TextOrNumeric::Text, } } @@ -560,10 +568,10 @@ field_type!( /// /// For the example `"2nd Wed in July"`, this field would provide `"2"`. Should likely be paired with the [`Weekday`] field. 'F' => DayOfWeekInMonth = 2, - /// Field symbol for the modified Julian day (numeric). - /// - /// 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. - 'g' => ModifiedJulianDay = 3, + // /// Field symbol for the modified Julian day (numeric). + // /// + // /// 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. + // 'g' => ModifiedJulianDay = 3, }; Numeric; DayULE @@ -596,7 +604,7 @@ field_type!( /// Field symbol for milliseconds in day (numeric). /// /// This field behaves exactly like a composite of all time-related fields, not including the zone fields. - 'A' => Millisecond = 1, + 'A' => MillisInDay = 1, }; Numeric; SecondULE @@ -605,17 +613,60 @@ field_type!( field_type!( /// An enum for the possible symbols of a week field in a date pattern. Week; { - /// Field symbol for week of year (numeric). - /// - /// When used in a pattern with year, use [`Year::WeekOf`] for the year field instead of [`Year::Calendar`]. - 'w' => WeekOfYear = 0, - /// Field symbol for week of month (numeric). - 'W' => WeekOfMonth = 1, + // /// Field symbol for week of year (numeric). + // /// + // /// When used in a pattern with year, use [`Year::WeekOf`] for the year field instead of [`Year::Calendar`]. + // 'w' => WeekOfYear = 0, + // /// Field symbol for week of month (numeric). + // 'W' => WeekOfMonth = 1, }; Numeric; - WeekULE + // TODO(#5643): Recover ULE once the type is inhabited + // WeekULE ); +impl Week { + /// Retrieves an index of the field variant. + /// + /// # Examples + /// + /// ```ignore + /// use icu::datetime::fields::Month; + /// + /// assert_eq!(Month::StandAlone::idx(), 1); + /// ``` + /// + /// # Stability + /// + /// This is mostly useful for serialization, + /// and does not guarantee index stability between ICU4X + /// versions. + #[inline] + pub(crate) fn idx(self) -> u8 { + 0 + } + + /// Retrieves a field variant from an index. + /// + /// # Examples + /// + /// ```ignore + /// use icu::datetime::fields::Month; + /// + /// assert_eq!(Month::from_idx(0), Month::Format); + /// ``` + /// + /// # Stability + /// + /// This is mostly useful for serialization, + /// and does not guarantee index stability between ICU4X + /// versions. + #[inline] + pub(crate) fn from_idx(idx: u8) -> Result { + Err(SymbolError::InvalidIndex(idx)) + } +} + field_type!( /// An enum for the possible symbols of a weekday field in a date pattern. Weekday; { @@ -639,7 +690,7 @@ impl LengthType for Weekday { match self { Self::Format => TextOrNumeric::Text, Self::Local | Self::StandAlone => match length { - FieldLength::One | FieldLength::TwoDigit => TextOrNumeric::Numeric, + FieldLength::One | FieldLength::Two => TextOrNumeric::Numeric, _ => TextOrNumeric::Text, }, } @@ -676,54 +727,36 @@ field_type!( /// Field symbol for the specific non-location format of a time zone. /// /// For example: "Pacific Standard Time" - 'z' => LowerZ = 0, - /// Field symbol for any of: the ISO8601 basic format with hours, minutes and optional seconds fields, the - /// long localized offset format, or the ISO8601 extended format with hours, minutes and optional seconds fields. - 'Z' => UpperZ = 1, + 'z' => SpecificNonLocation = 0, /// Field symbol for the localized offset format of a time zone. /// /// For example: "GMT-07:00" - 'O' => UpperO = 2, + 'O' => LocalizedOffset = 1, /// Field symbol for the generic non-location format of a time zone. /// /// For example: "Pacific Time" - 'v' => LowerV = 3, + 'v' => GenericNonLocation = 2, /// Field symbol for any of: the time zone id, time zone exemplar city, or generic location format. - 'V' => UpperV = 4, - /// Field symbol for either the ISO8601 basic format or ISO8601 extended format, with an optional ISO8601 UTC indicator `Z`. - 'x' => LowerX = 5, - /// Field symbol for either the ISO8601 basic format or ISO8601 extended format. This does not allow an - /// optional ISO8601 UTC indicator `Z`, whereas [`TimeZone::LowerX`] allows the optional `Z`. - 'X' => UpperX = 6, + 'V' => Location = 3, + /// Field symbol for either the ISO-8601 basic format or ISO-8601 extended format. This does not use an + /// optional ISO-8601 UTC indicator `Z`, whereas [`TimeZone::IsoWithZ`] produces `Z`. + 'x' => Iso = 4, + /// Field symbol for either the ISO-8601 basic format or ISO-8601 extended format, with the ISO-8601 UTC indicator `Z`. + 'X' => IsoWithZ = 5, }; TimeZoneULE ); #[cfg(feature = "datagen")] impl LengthType for TimeZone { - fn get_length_type(&self, length: FieldLength) -> TextOrNumeric { + fn get_length_type(&self, _: FieldLength) -> TextOrNumeric { use TextOrNumeric::*; match self { - // It is reasonable to default to Text on release builds instead of panicking. - // - // Erroneous symbols are gracefully handled by returning error Results - // in the formatting code. - // - // The default cases may want to be updated to return errors themselves - // if the skeleton matching code ever becomes fallible. - Self::UpperZ => match length.idx() { - 1..=3 => Numeric, - 4 => Text, - 5 => Numeric, - _ => Text, - }, - Self::UpperO => match length.idx() { - 1 => Text, - 4 => Numeric, - _ => Text, - }, - Self::LowerX | Self::UpperX => Numeric, - Self::LowerZ | Self::LowerV | Self::UpperV => Text, + Self::Iso | Self::IsoWithZ => Numeric, + Self::LocalizedOffset + | Self::SpecificNonLocation + | Self::GenericNonLocation + | Self::Location => Text, } } } diff --git a/components/datetime/src/fieldset.rs b/components/datetime/src/fieldset.rs index a616b92f813..e6eb9c6801d 100644 --- a/components/datetime/src/fieldset.rs +++ b/components/datetime/src/fieldset.rs @@ -7,26 +7,35 @@ pub use crate::combo::Combo; use crate::{ + dynamic::*, + fields, format::neo::*, neo_skeleton::*, provider::{neo::*, time_zones::tz, *}, + raw::neo::RawNeoOptions, scaffold::*, }; use icu_calendar::{ types::{ DayOfMonth, IsoHour, IsoMinute, IsoSecond, IsoWeekday, MonthInfo, NanoSecond, YearInfo, }, - AnyCalendarKind, Date, Iso, Time, + Date, Iso, Time, }; use icu_provider::marker::NeverMarker; use icu_timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant}; +/// Enumerations over field sets. +pub mod dynamic { + // TODO: Rename to `pub mod enums` + pub use crate::dynamic::*; +} + #[cfg(doc)] use icu_timezone::TimeZoneInfo; /// Maps the token `yes` to the given ident macro_rules! yes_to { - ($any:ident, yes) => { + ($any:expr, yes) => { $any }; () => { @@ -34,6 +43,27 @@ macro_rules! yes_to { }; } +macro_rules! yes_or { + ($fallback:expr, $actual:expr) => { + $actual + }; + ($fallback:expr,) => { + $fallback + }; +} + +macro_rules! ternary { + ($present:expr, $missing:expr, yes) => { + $present + }; + ($present:expr, $missing:expr, $any:literal) => { + $present + }; + ($present:expr, $missing:expr,) => { + $missing + }; +} + /// Generates the options argument passed into the docs test constructor macro_rules! length_option_helper { ($type:ty, $length:ident) => { @@ -41,6 +71,23 @@ macro_rules! length_option_helper { }; } +macro_rules! impl_composite { + ($type:ident, $variant:ident, $enum:ident) => { + impl $type { + #[inline] + pub(crate) fn to_enum(self) -> $enum { + $enum::$type(self) + } + } + impl GetField for $type { + #[inline] + fn get_field(&self) -> CompositeFieldSet { + CompositeFieldSet::$variant(self.to_enum()) + } + } + }; +} + macro_rules! impl_marker_with_options { ( $(#[$attr:meta])* @@ -48,10 +95,11 @@ macro_rules! impl_marker_with_options { $(sample_length: $sample_length:ident,)? $(alignment: $alignment_yes:ident,)? $(year_style: $yearstyle_yes:ident,)? - $(fractional_second_digits: $fractionalsecondigits_yes:ident,)? + $(time_precision: $timeprecision_yes:ident,)? + $(enumerated: $enumerated_yes:ident,)? ) => { $(#[$attr])* - #[derive(Debug)] + #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct $type { $( @@ -73,10 +121,10 @@ macro_rules! impl_marker_with_options { pub year_style: datetime_marker_helper!(@option/yearstyle, $yearstyle_yes), )? $( - /// How many fractional seconds to display. + /// How precisely to display the time of day /// - /// See: [`FractionalSecondDigits`] - pub fractional_second_digits: datetime_marker_helper!(@option/fractionalsecondigits, $fractionalsecondigits_yes), + /// See: [`TimePrecision`] + pub time_precision: datetime_marker_helper!(@option/timeprecision, $timeprecision_yes), )? } impl $type { @@ -91,7 +139,7 @@ macro_rules! impl_marker_with_options { year_style: yes_to!(None, $yearstyle_yes), )? $( - fractional_second_digits: yes_to!(None, $fractionalsecondigits_yes), + time_precision: yes_to!(None, $timeprecision_yes), )? } } @@ -108,6 +156,33 @@ macro_rules! impl_marker_with_options { Self::with_length(NeoSkeletonLength::Short) } } + #[allow(dead_code)] + impl $type { + $( + const _: () = yes_to!((), $enumerated_yes); // condition for this macro block + #[warn(dead_code)] + )? + pub(crate) fn to_raw_options(self) -> RawNeoOptions { + RawNeoOptions { + length: self.length, + alignment: ternary!(self.alignment, None, $($alignment_yes)?), + year_style: ternary!(self.year_style, None, $($yearstyle_yes)?), + time_precision: ternary!(self.time_precision, None, $($timeprecision_yes)?), + } + } + $( + const _: () = yes_to!((), $enumerated_yes); // condition for this macro block + #[warn(dead_code)] + )? + pub(crate) fn from_raw_options(options: RawNeoOptions) -> Self { + Self { + length: options.length, + $(alignment: yes_to!(options.alignment, $alignment_yes),)? + $(year_style: yes_to!(options.year_style, $yearstyle_yes),)? + $(time_precision: yes_to!(options.time_precision, $timeprecision_yes),)? + } + } + } impl_get_field!($type, never); impl_get_field!($type, length, yes); $( @@ -131,11 +206,21 @@ macro_rules! impl_marker_with_options { } )? $( - impl_get_field!($type, fractional_second_digits, $fractionalsecondigits_yes); + impl_get_field!($type, time_precision, $timeprecision_yes); impl $type { - /// Sets the fractional second digits option. - pub const fn with_fractional_second_digits(mut self, digits: FractionalSecondDigits) -> Self { - self.fractional_second_digits = Some(digits); + /// Sets the time precision option. + pub const fn with_time_precision(mut self, time_precision: TimePrecision) -> Self { + self.time_precision = Some(time_precision); + self + } + /// Sets the time precision to [`TimePrecision::MinuteExact`] + pub fn hm(mut self) -> Self { + self.time_precision = Some(TimePrecision::MinuteExact); + self + } + /// Sets the time precision to [`TimePrecision::SecondPlus`] + pub fn hms(mut self) -> Self { + self.time_precision = Some(TimePrecision::SecondPlus); self } } @@ -143,6 +228,80 @@ macro_rules! impl_marker_with_options { }; } +macro_rules! impl_combo_get_field { + ($type:ident, $composite:ident, $enum:ident, $wrap:ident, $in:ident, $out:ident) => { + impl GetField for Combo<$type, $in> { + #[inline] + fn get_field(&self) -> CompositeFieldSet { + CompositeFieldSet::$composite(self.dt().to_enum(), ZoneStyle::$out) + } + } + }; +} + +macro_rules! impl_combo_generic_fns { + ($type:ident) => { + impl Combo<$type, Z> { + #[doc = concat!("Creates a ", stringify!($type), " skeleton with the given formatting length and a time zone.")] + pub fn with_length(length: NeoSkeletonLength) -> Self { + Self::new($type::with_length(length)) + } + #[doc = concat!("Creates a ", stringify!($type), " skeleton with a long length and a time zone.")] + pub const fn long() -> Self { + Self::new($type::long()) + } + #[doc = concat!("Creates a ", stringify!($type), " skeleton with a medium length and a time zone.")] + pub const fn medium() -> Self { + Self::new($type::medium()) + } + #[doc = concat!("Creates a ", stringify!($type), " skeleton with a short length and a time zone.")] + pub const fn short() -> Self { + Self::new($type::short()) + } + } + }; +} + +macro_rules! impl_zone_combo_helpers { + ( + $type:ident, + $composite:ident, + $enum:ident, + $wrap:ident + ) => { + impl $type { + /// Associates this field set with a specific non-location format time zone, as in + /// “Pacific Daylight Time”. + #[inline] + pub fn z(self) -> Combo { + Combo::new(self) + } + /// Associates this field set with an offset format time zone, as in + /// “GMT−8”. + #[inline] + pub fn o(self) -> Combo { + Combo::new(self) + } + /// Associates this field set with a generic non-location format time zone, as in + /// “Pacific Time”. + #[inline] + pub fn v(self) -> Combo { + Combo::new(self) + } + /// Associates this field set with a location format time zone, as in + /// “Los Angeles time”. + #[inline] + pub fn l(self) -> Combo { + Combo::new(self) + } + } + impl_combo_get_field!($type, $composite, $enum, $wrap, Zs, Z); + impl_combo_get_field!($type, $composite, $enum, $wrap, O, O); + impl_combo_get_field!($type, $composite, $enum, $wrap, Vs, V); + impl_combo_get_field!($type, $composite, $enum, $wrap, L, L); + }; +} + /// Internal helper macro used by [`impl_date_marker`] and [`impl_calendar_period_marker`] macro_rules! impl_date_or_calendar_period_marker { ( @@ -249,8 +408,8 @@ macro_rules! impl_date_or_calendar_period_marker { type YearInput = datetime_marker_helper!(@input/year, $($year_yes)?); type MonthInput = datetime_marker_helper!(@input/month, $($month_yes)?); type DayOfMonthInput = datetime_marker_helper!(@input/day_of_month, $($day_of_month_yes)?); + type DayOfYearInput = datetime_marker_helper!(@input/day_of_year, $($day_of_year_yes)?); type DayOfWeekInput = datetime_marker_helper!(@input/day_of_week, $($day_of_week_yes)?); - type AnyCalendarKindInput = datetime_marker_helper!(@input/any_calendar_kind, $($any_calendar_kind_yes)?); } impl TypedDateDataMarkers for $type { type DateSkeletonPatternsV1Marker = datetime_marker_helper!(@dates/typed, yes); @@ -268,10 +427,6 @@ macro_rules! impl_date_or_calendar_period_marker { type D = Self; type T = NeoNeverMarker; type Z = NeoNeverMarker; - type LengthOption = datetime_marker_helper!(@option/length, $sample_length); - type AlignmentOption = datetime_marker_helper!(@option/alignment, $($months_yes)?); - type YearStyleOption = datetime_marker_helper!(@option/yearstyle, $($year_yes)?); - type FractionalSecondDigitsOption = datetime_marker_helper!(@option/fractionalsecondigits,); type GluePatternV1Marker = datetime_marker_helper!(@glue,); } }; @@ -288,10 +443,12 @@ macro_rules! impl_date_marker { ( $(#[$attr:meta])* $type:ident, + $type_time:ident, $components:expr, description = $description:literal, sample_length = $sample_length:ident, sample = $sample:literal, + sample_time = $sample_time:literal, $(years = $years_yes:ident,)? $(months = $months_yes:ident,)? $(dates = $dates_yes:ident,)? @@ -322,11 +479,123 @@ macro_rules! impl_date_marker { $(input_any_calendar_kind = $any_calendar_kind_yes,)? $(option_alignment = $option_alignment_yes,)? ); - impl HasConstDateComponents for $type { - const COMPONENTS: NeoDateComponents = $components; + impl_zone_combo_helpers!($type, DateZone, DateFieldSet, wrap); + impl_combo_generic_fns!($type); + impl_composite!($type, Date, DateFieldSet); + impl_marker_with_options!( + #[doc = concat!("**“", $sample, "**” ⇒ ", $description)] + /// + /// # Examples + /// + /// In [`DateTimeFormatter`](crate::neo::DateTimeFormatter): + /// + /// ``` + /// use icu::calendar::DateTime; + /// use icu::datetime::DateTimeFormatter; + #[doc = concat!("use icu::datetime::fieldset::", stringify!($type_time), ";")] + /// use icu::locale::locale; + /// use writeable::assert_try_writeable_eq; + #[doc = concat!("let fmt = DateTimeFormatter::<", stringify!($type_time), ">::try_new(")] + /// &locale!("en").into(), + #[doc = concat!(" ", length_option_helper!($type_time, $sample_length), ",")] + /// ) + /// .unwrap(); + /// let dt = DateTime::try_new_iso(2024, 5, 17, 15, 47, 50).unwrap(); + /// + /// assert_try_writeable_eq!( + /// fmt.convert_and_format(&dt), + #[doc = concat!(" \"", $sample_time, "\"")] + /// ); + /// ``` + /// + /// In [`FixedCalendarDateTimeFormatter`](crate::neo::FixedCalendarDateTimeFormatter): + /// + /// ``` + /// use icu::calendar::DateTime; + /// use icu::calendar::Gregorian; + /// use icu::datetime::FixedCalendarDateTimeFormatter; + #[doc = concat!("use icu::datetime::fieldset::", stringify!($type_time), ";")] + /// use icu::locale::locale; + /// use writeable::assert_try_writeable_eq; + /// + #[doc = concat!("let fmt = FixedCalendarDateTimeFormatter::::try_new(")] + /// &locale!("en").into(), + #[doc = concat!(" ", length_option_helper!($type_time, $sample_length), ",")] + /// ) + /// .unwrap(); + /// let dt = DateTime::try_new_gregorian(2024, 5, 17, 15, 47, 50).unwrap(); + /// + /// assert_try_writeable_eq!( + /// fmt.format(&dt), + #[doc = concat!(" \"", $sample_time, "\"")] + /// ); + /// ``` + $(#[$attr])* + $type_time, + sample_length: $sample_length, + alignment: yes, + $(year_style: $year_yes,)? + time_precision: yes, + ); + impl_zone_combo_helpers!($type_time, DateTimeZone, DateAndTimeFieldSet, wrap); + impl_combo_generic_fns!($type_time); + impl UnstableSealed for $type_time {} + impl DateTimeNamesMarker for $type_time { + type YearNames = datetime_marker_helper!(@names/year, $($years_yes)?); + type MonthNames = datetime_marker_helper!(@names/month, $($months_yes)?); + type WeekdayNames = datetime_marker_helper!(@names/weekday, $($weekdays_yes)?); + type DayPeriodNames = datetime_marker_helper!(@names/dayperiod, yes); + type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials,); + type ZoneLocations = datetime_marker_helper!(@names/zone/locations,); + type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long,); + type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short,); + type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long,); + type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short,); + type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods,); } - impl HasConstComponents for $type { - const COMPONENTS: NeoComponents = NeoComponents::Date($components); + impl DateInputMarkers for $type_time { + type YearInput = datetime_marker_helper!(@input/year, $($year_yes)?); + type MonthInput = datetime_marker_helper!(@input/month, $($month_yes)?); + type DayOfMonthInput = datetime_marker_helper!(@input/day_of_month, $($day_of_month_yes)?); + type DayOfYearInput = datetime_marker_helper!(@input/day_of_year, $($day_of_year_yes)?); + type DayOfWeekInput = datetime_marker_helper!(@input/day_of_week, $($day_of_week_yes)?); + } + impl TypedDateDataMarkers for $type_time { + type DateSkeletonPatternsV1Marker = datetime_marker_helper!(@dates/typed, yes); + type YearNamesV1Marker = datetime_marker_helper!(@years/typed, $($years_yes)?); + type MonthNamesV1Marker = datetime_marker_helper!(@months/typed, $($months_yes)?); + type WeekdayNamesV1Marker = datetime_marker_helper!(@weekdays, $($weekdays_yes)?); + } + impl DateDataMarkers for $type_time { + type Skel = datetime_marker_helper!(@calmarkers, yes); + type Year = datetime_marker_helper!(@calmarkers, $($years_yes)?); + type Month = datetime_marker_helper!(@calmarkers, $($months_yes)?); + type WeekdayNamesV1Marker = datetime_marker_helper!(@weekdays, $($weekdays_yes)?); + } + impl TimeMarkers for $type_time { + // TODO: Consider making dayperiods optional again + type DayPeriodNamesV1Marker = datetime_marker_helper!(@dayperiods, yes); + type TimeSkeletonPatternsV1Marker = datetime_marker_helper!(@times, yes); + type HourInput = datetime_marker_helper!(@input/hour, yes); + type MinuteInput = datetime_marker_helper!(@input/minute, yes); + type SecondInput = datetime_marker_helper!(@input/second, yes); + type NanoSecondInput = datetime_marker_helper!(@input/nanosecond, yes); + } + impl DateTimeMarkers for $type_time { + type D = Self; + type T = Self; + type Z = NeoNeverMarker; + type GluePatternV1Marker = datetime_marker_helper!(@glue, yes); + } + impl_composite!($type_time, DateTime, DateAndTimeFieldSet); + impl $type_time { + pub(crate) fn to_date_field_set(self) -> $type { + $type { + length: self.length, + $(alignment: yes_to!(self.alignment, $option_alignment_yes),)? + $(year_style: yes_to!(self.year_style, $years_yes),)? + } + } } }; } @@ -368,9 +637,7 @@ macro_rules! impl_calendar_period_marker { $(input_any_calendar_kind = $any_calendar_kind_yes,)? $(option_alignment = $option_alignment_yes,)? ); - impl HasConstComponents for $type { - const COMPONENTS: NeoComponents = NeoComponents::CalendarPeriod($components); - } + impl_composite!($type, CalendarPeriod, CalendarPeriodFieldSet); }; } @@ -458,8 +725,10 @@ macro_rules! impl_time_marker { $type, sample_length: $sample_length, alignment: yes, - $(fractional_second_digits: $nanosecond_yes,)? + time_precision: yes, ); + impl_zone_combo_helpers!($type, TimeZone, TimeFieldSet, wrap); + impl_combo_generic_fns!($type); impl UnstableSealed for $type {} impl DateTimeNamesMarker for $type { type YearNames = datetime_marker_helper!(@names/year,); @@ -474,9 +743,6 @@ macro_rules! impl_time_marker { type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short,); type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods,); } - impl HasConstTimeComponents for $type { - const COMPONENTS: NeoTimeComponents = $components; - } impl TimeMarkers for $type { type DayPeriodNamesV1Marker = datetime_marker_helper!(@dayperiods, $($dayperiods_yes)?); type TimeSkeletonPatternsV1Marker = datetime_marker_helper!(@times, yes); @@ -489,15 +755,9 @@ macro_rules! impl_time_marker { type D = NeoNeverMarker; type T = Self; type Z = NeoNeverMarker; - type LengthOption = datetime_marker_helper!(@option/length, $sample_length); - type AlignmentOption = datetime_marker_helper!(@option/alignment, yes); - type YearStyleOption = datetime_marker_helper!(@option/yearstyle,); - type FractionalSecondDigitsOption = datetime_marker_helper!(@option/fractionalsecondigits, $($nanosecond_yes)?); type GluePatternV1Marker = datetime_marker_helper!(@glue,); } - impl HasConstComponents for $type { - const COMPONENTS: NeoComponents = NeoComponents::Time($components); - } + impl_composite!($type, Time, TimeFieldSet); }; } @@ -521,6 +781,14 @@ macro_rules! impl_zone_marker { sample_length = $sample_length:ident, // A sample string. A docs test will be generated! sample = $sample:literal, + // The field symbol and field length when the semantic length is short/medium. + field_short = $field_short:expr, + // The field symbol and field length when the semantic length is long. + field_long = $field_long:expr, + // The type in ZoneFieldSet for this field set + resolved_type = $resolved_type:ident, + // Whether to skip tests and render a message instead. + $(skip_tests = $skip_tests:literal,)? // Whether zone-essentials should be loaded. $(zone_essentials = $zone_essentials_yes:ident,)? // Whether locations formats can occur. @@ -541,15 +809,19 @@ macro_rules! impl_zone_marker { $(input_variant = $variant_input_yes:ident,)? // Whether to require the Local Time $(input_localtime = $localtime_input_yes:ident,)? + // Whether this time zone style is enumerated in ZoneFieldSet + $(enumerated = $enumerated_yes:ident,)? ) => { impl_marker_with_options!( #[doc = concat!("**“", $sample, "**” ⇒ ", $description)] /// + #[doc = yes_or!("", $($skip_tests)?)] + /// /// # Examples /// /// In [`DateTimeFormatter`](crate::neo::DateTimeFormatter): /// - /// ``` + #[doc = concat!("```", ternary!("compile_fail", "", $($skip_tests)?))] /// use icu::calendar::{Date, Time}; /// use icu::timezone::{TimeZoneBcp47Id, TimeZoneInfo, UtcOffset, ZoneVariant}; /// use icu::datetime::DateTimeFormatter; @@ -578,7 +850,7 @@ macro_rules! impl_zone_marker { /// /// In [`FixedCalendarDateTimeFormatter`](crate::neo::FixedCalendarDateTimeFormatter): /// - /// ``` + #[doc = concat!("```", ternary!("compile_fail", "", $($skip_tests)?))] /// use icu::calendar::{Date, Time}; /// use icu::timezone::{TimeZoneBcp47Id, TimeZoneInfo, UtcOffset, ZoneVariant}; /// use icu::calendar::Gregorian; @@ -623,9 +895,6 @@ macro_rules! impl_zone_marker { type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short, $($zone_specific_short_yes)?); type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods, $($metazone_periods_yes)?); } - impl HasConstZoneComponent for $type { - const COMPONENT: NeoTimeZoneStyle = $components; - } impl ZoneMarkers for $type { type TimeZoneIdInput = datetime_marker_helper!(@input/timezone/id, $($tzid_input_yes)?); type TimeZoneOffsetInput = datetime_marker_helper!(@input/timezone/offset, yes); @@ -643,87 +912,30 @@ macro_rules! impl_zone_marker { type D = NeoNeverMarker; type T = NeoNeverMarker; type Z = Self; - type LengthOption = datetime_marker_helper!(@option/length, yes); - type AlignmentOption = datetime_marker_helper!(@option/alignment,); - type YearStyleOption = datetime_marker_helper!(@option/yearstyle,); - type FractionalSecondDigitsOption = datetime_marker_helper!(@option/fractionalsecondigits,); type GluePatternV1Marker = datetime_marker_helper!(@glue,); } - impl HasConstComponents for $type { - const COMPONENTS: NeoComponents = NeoComponents::Zone($components); - } + $( + const _: () = yes_to!((), $enumerated_yes); // condition for this macro block + impl_composite!($type, Zone, ZoneFieldSet); + impl $type { + pub(crate) fn to_field(self) -> (fields::TimeZone, fields::FieldLength) { + match self.length { + NeoSkeletonLength::Short | NeoSkeletonLength::Medium => $field_short, + NeoSkeletonLength::Long => $field_long, + } + } + } + )? }; } -macro_rules! impl_datetime_marker { - ( - $type:ident, - description = $description:literal, - sample_length = $sample_length:ident, - sample = $sample:literal, - date = $date:path, - time = $time:path, - ) => { - #[doc = concat!("**“", $sample, "**” ⇒ ", $description)] - /// - /// # Examples - /// - /// In [`DateTimeFormatter`](crate::neo::DateTimeFormatter): - /// - /// ``` - /// use icu::calendar::DateTime; - /// use icu::datetime::DateTimeFormatter; - #[doc = concat!("use icu::datetime::fieldset::", stringify!($type), ";")] - /// use icu::locale::locale; - /// use writeable::assert_try_writeable_eq; - /// - #[doc = concat!("let fmt = DateTimeFormatter::<", stringify!($type), ">::try_new(")] - /// &locale!("en").into(), - #[doc = concat!(" ", length_option_helper!($type, $sample_length), ",")] - /// ) - /// .unwrap(); - /// let dt = DateTime::try_new_iso(2024, 5, 17, 15, 47, 50).unwrap(); - /// - /// assert_try_writeable_eq!( - /// fmt.convert_and_format(&dt), - #[doc = concat!(" \"", $sample, "\"")] - /// ); - /// ``` - /// - /// In [`FixedCalendarDateTimeFormatter`](crate::neo::FixedCalendarDateTimeFormatter): - /// - /// ``` - /// use icu::calendar::DateTime; - /// use icu::calendar::Gregorian; - /// use icu::datetime::FixedCalendarDateTimeFormatter; - #[doc = concat!("use icu::datetime::fieldset::", stringify!($type), ";")] - /// use icu::locale::locale; - /// use writeable::assert_try_writeable_eq; - /// - #[doc = concat!("let fmt = FixedCalendarDateTimeFormatter::::try_new(")] - /// &locale!("en").into(), - #[doc = concat!(" ", length_option_helper!($type, $sample_length), ",")] - /// ) - /// .unwrap(); - /// let dt = DateTime::try_new_gregorian(2024, 5, 17, 15, 47, 50).unwrap(); - /// - /// assert_try_writeable_eq!( - /// fmt.format(&dt), - #[doc = concat!(" \"", $sample, "\"")] - /// ); - /// ``` - pub type $type = Combo<$date, $time, NeoNeverMarker>; - } -} - macro_rules! impl_zoneddatetime_marker { ( $type:ident, description = $description:literal, sample_length = $sample_length:ident, sample = $sample:literal, - date = $date:path, - time = $time:path, + datetime = $datetime:path, zone = $zone:path, ) => { #[doc = concat!("**“", $sample, "**” ⇒ ", $description)] @@ -780,7 +992,7 @@ macro_rules! impl_zoneddatetime_marker { #[doc = concat!(" \"", $sample, "\"")] /// ); /// ``` - pub type $type = Combo<$date, $time, $zone>; + pub type $type = Combo<$datetime, $zone>; } } @@ -788,10 +1000,12 @@ impl_date_marker!( /// This format may use ordinal formatting, such as "the 17th", /// in the future. See CLDR-18040. D, + DT, NeoDateComponents::Day, description = "day of month (standalone)", sample_length = short, sample = "17", + sample_time = "17, 3:47:50 PM", input_day_of_month = yes, input_any_calendar_kind = yes, option_alignment = yes, @@ -799,10 +1013,12 @@ impl_date_marker!( impl_date_marker!( E, + ET, NeoDateComponents::Weekday, description = "weekday (standalone)", sample_length = long, sample = "Friday", + sample_time = "Friday 3:47:50 PM", weekdays = yes, input_day_of_week = yes, ); @@ -811,10 +1027,12 @@ impl_date_marker!( /// This format may use ordinal formatting, such as "Friday the 17th", /// in the future. See CLDR-18040. DE, + DET, NeoDateComponents::DayWeekday, description = "day of month and weekday", sample_length = long, sample = "17 Friday", + sample_time = "17 Friday, 3:47:50 PM", weekdays = yes, input_day_of_month = yes, input_day_of_week = yes, @@ -823,10 +1041,12 @@ impl_date_marker!( impl_date_marker!( MD, + MDT, NeoDateComponents::MonthDay, description = "month and day", sample_length = medium, sample = "May 17", + sample_time = "May 17, 3:47:50 PM", months = yes, input_month = yes, input_day_of_month = yes, @@ -837,10 +1057,12 @@ impl_date_marker!( impl_date_marker!( /// See CLDR-18040 for progress on improving this format. MDE, + MDET, NeoDateComponents::MonthDayWeekday, description = "month, day, and weekday", sample_length = medium, sample = "Fri, May 17", + sample_time = "Fri, May 17, 3:47:50 PM", months = yes, weekdays = yes, input_month = yes, @@ -852,10 +1074,12 @@ impl_date_marker!( impl_date_marker!( YMD, + YMDT, NeoDateComponents::YearMonthDay, description = "year, month, and day", sample_length = short, sample = "5/17/24", + sample_time = "5/17/24, 3:47:50 PM", years = yes, months = yes, input_year = yes, @@ -867,10 +1091,12 @@ impl_date_marker!( impl_date_marker!( YMDE, + YMDET, NeoDateComponents::YearMonthDayWeekday, description = "year, month, day, and weekday", sample_length = short, sample = "Fri, 5/17/24", + sample_time = "Fri, 5/17/24, 3:47:50 PM", years = yes, months = yes, weekdays = yes, @@ -921,30 +1147,9 @@ impl_calendar_period_marker!( ); impl_time_marker!( - H, - NeoTimeComponents::Hour, - description = "hour (locale-dependent hour cycle)", - sample_length = medium, - sample = "3 PM", - dayperiods = yes, - input_hour = yes, -); - -impl_time_marker!( - HM, - NeoTimeComponents::HourMinute, - description = "hour and minute (locale-dependent hour cycle)", - sample_length = medium, - sample = "3:47 PM", - dayperiods = yes, - input_hour = yes, - input_minute = yes, -); - -impl_time_marker!( - HMS, - NeoTimeComponents::HourMinuteSecond, - description = "hour, minute, and second (locale-dependent hour cycle)", + T, + NeoTimeComponents::Time, + description = "time (locale-dependent hour cycle)", sample_length = medium, sample = "3:47:50 PM", dayperiods = yes, @@ -954,26 +1159,9 @@ impl_time_marker!( input_nanosecond = yes, ); -impl_datetime_marker!( - YMDHM, - description = "year, month, day, hour, and minute", - sample_length = medium, - sample = "May 17, 2024, 3:47 PM", - date = YMD, - time = HM, -); - -impl_datetime_marker!( - YMDHMS, - description = "year, month, day, hour, minute, and second", - sample_length = medium, - sample = "May 17, 2024, 3:47:50 PM", - date = YMD, - time = HMS, -); - impl_zone_marker!( - /// When a display name is unavailable, falls back to the offset format: + /// When a display name is unavailable, falls back to the localized offset format for short lengths, and + /// to the location format for long lengths: /// /// ``` /// use icu::calendar::{Date, Time}; @@ -985,21 +1173,32 @@ impl_zone_marker!( /// use tinystr::tinystr; /// use writeable::assert_try_writeable_eq; /// + /// // Time zone info for Europe/Istanbul in the winter + /// let zone = TimeZoneBcp47Id(tinystr!(8, "trist")) + /// .with_offset("+02".parse().ok()) + /// .at_time((Date::try_new_iso(2022, 1, 29).unwrap(), Time::midnight())) + /// .with_zone_variant(ZoneVariant::Standard); + /// /// let fmt = FixedCalendarDateTimeFormatter::::try_new( /// &locale!("en").into(), /// Z::short(), /// ) /// .unwrap(); /// - /// // Time zone info for America/Sao_Paulo in the winter - /// let zone = TimeZoneBcp47Id(tinystr!(8, "brsao")) - /// .with_offset("-03".parse().ok()) - /// .at_time((Date::try_new_iso(2022, 1, 29).unwrap(), Time::midnight())) - /// .with_zone_variant(ZoneVariant::Standard); + /// assert_try_writeable_eq!( + /// fmt.format(&zone), + /// "GMT+2" + /// ); + /// + /// let fmt = FixedCalendarDateTimeFormatter::::try_new( + /// &locale!("en").into(), + /// Z::long(), + /// ) + /// .unwrap(); /// /// assert_try_writeable_eq!( /// fmt.format(&zone), - /// "GMT-3" + /// "Türkiye Standard Time" /// ); /// ``` /// @@ -1034,68 +1233,21 @@ impl_zone_marker!( description = "time zone in specific non-location format", sample_length = long, sample = "Central Daylight Time", + field_short = (fields::TimeZone::SpecificNonLocation, fields::FieldLength::One), + field_long = (fields::TimeZone::SpecificNonLocation, fields::FieldLength::Four), + resolved_type = Z, zone_essentials = yes, + zone_locations = yes, zone_specific_long = yes, zone_specific_short = yes, metazone_periods = yes, input_tzid = yes, input_variant = yes, input_localtime = yes, + enumerated = yes, ); impl_zone_marker!( - /// This marker only loads data for the short length. Useful when combined with other fields: - /// - /// ``` - /// use icu::calendar::{Date, Time}; - /// use icu::timezone::{TimeZoneInfo, IxdtfParser}; - /// use icu::calendar::Gregorian; - /// use icu::datetime::DateTimeFormatter; - /// use icu::datetime::fieldset::MD; - /// use icu::datetime::fieldset::HM; - /// use icu::datetime::fieldset::Zs; - /// use icu::datetime::fieldset::Combo; - /// use icu::locale::locale; - /// use tinystr::tinystr; - /// use writeable::assert_try_writeable_eq; - /// - /// type MyDateTimeZoneSet = Combo< - /// MD, - /// HM, - /// Zs, - /// >; - /// - /// let fmt = DateTimeFormatter::try_new( - /// &locale!("en-US").into(), - /// MyDateTimeZoneSet::long(), - /// ) - /// .unwrap(); - /// - /// let dtz = IxdtfParser::new().try_from_str("2024-09-17T15:47:50-05:00[America/Chicago]").unwrap(); - /// - /// assert_try_writeable_eq!( - /// fmt.convert_and_format(&dtz), - /// "September 17, 3:47 PM CDT" - /// ); - /// ``` - /// - /// Don't use long length if it is the only field: - /// - /// ``` - /// use icu::calendar::Gregorian; - /// use icu::datetime::FixedCalendarDateTimeFormatter; - /// use icu::datetime::fieldset::Zs; - /// use icu::datetime::LoadError; - /// use icu::locale::locale; - /// - /// let result = FixedCalendarDateTimeFormatter::::try_new( - /// &locale!("en").into(), - /// Zs::long(), - /// ); - /// - /// assert!(matches!(result, Err(LoadError::TypeTooNarrow(_)))); - /// ``` - /// /// This style requires a [`ZoneVariant`], so /// only a full time zone info can be formatted with this style. /// For example, [`TimeZoneInfo`] cannot be formatted. @@ -1103,7 +1255,7 @@ impl_zone_marker!( /// ```compile_fail,E0271 /// use icu::calendar::{DateTime, Iso}; /// use icu::datetime::FixedCalendarDateTimeFormatter; - /// use icu::datetime::fieldset::Zs; + /// use icu::datetime::fieldset::{Combo, T, Zs}; /// use icu::timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant}; /// use tinystr::tinystr; /// use icu::locale::locale; @@ -1115,7 +1267,7 @@ impl_zone_marker!( /// /// let formatter = FixedCalendarDateTimeFormatter::try_new( /// &locale!("en-US").into(), - /// Zs::medium(), + /// Combo::::medium(), /// ) /// .unwrap(); /// @@ -1128,6 +1280,10 @@ impl_zone_marker!( description = "time zone in specific non-location format (only short)", sample_length = short, sample = "CDT", + field_short = (fields::TimeZone::SpecificNonLocation, fields::FieldLength::One), + field_long = (fields::TimeZone::SpecificNonLocation, fields::FieldLength::One), + resolved_type = Z, + skip_tests = "This field set can be used only in combination with others.", zone_essentials = yes, zone_specific_short = yes, metazone_periods = yes, @@ -1188,7 +1344,11 @@ impl_zone_marker!( description = "UTC offset", sample_length = medium, sample = "GMT-5", + field_short = (fields::TimeZone::LocalizedOffset, fields::FieldLength::One), + field_long = (fields::TimeZone::LocalizedOffset, fields::FieldLength::Four), + resolved_type = O, zone_essentials = yes, + enumerated = yes, ); impl_zone_marker!( @@ -1204,21 +1364,20 @@ impl_zone_marker!( /// use tinystr::tinystr; /// use writeable::assert_try_writeable_eq; /// + /// // Time zone info for Europe/Istanbul + /// let zone = TimeZoneBcp47Id(tinystr!(8, "trist")) + /// .without_offset() + /// .at_time((Date::try_new_iso(2022, 1, 29).unwrap(), Time::midnight())); + /// /// let fmt = FixedCalendarDateTimeFormatter::::try_new( /// &locale!("en").into(), /// V::short(), /// ) /// .unwrap(); /// - /// // Time zone info for America/Sao_Paulo in the winter - /// let zone = TimeZoneBcp47Id(tinystr!(8, "brsao")) - /// .with_offset("-03".parse().ok()) - /// .at_time((Date::try_new_iso(2022, 1, 29).unwrap(), Time::midnight())) - /// .with_zone_variant(ZoneVariant::Standard); - /// /// assert_try_writeable_eq!( /// fmt.format(&zone), - /// "Sao Paulo Time" + /// "Türkiye Time" /// ); /// ``` /// @@ -1234,7 +1393,7 @@ impl_zone_marker!( /// use icu::locale::locale; /// use writeable::assert_try_writeable_eq; /// - /// let time_zone_basic = TimeZoneBcp47Id(tinystr!(8, "uschi")).with_offset("-06".parse().ok()); + /// let time_zone_basic = TimeZoneBcp47Id(tinystr!(8, "uschi")).without_offset(); /// /// let formatter = FixedCalendarDateTimeFormatter::try_new( /// &locale!("en-US").into(), @@ -1251,6 +1410,9 @@ impl_zone_marker!( description = "time zone in generic non-location format", sample_length = long, sample = "Central Time", + field_short = (fields::TimeZone::GenericNonLocation, fields::FieldLength::One), + field_long = (fields::TimeZone::GenericNonLocation, fields::FieldLength::Four), + resolved_type = V, zone_essentials = yes, zone_locations = yes, zone_generic_long = yes, @@ -1258,61 +1420,10 @@ impl_zone_marker!( metazone_periods = yes, input_tzid = yes, input_localtime = yes, + enumerated = yes, ); impl_zone_marker!( - /// This marker only loads data for the short length. Useful when combined with other fields: - /// - /// ``` - /// use icu::calendar::{Date, Time}; - /// use icu::timezone::{TimeZoneInfo, IxdtfParser}; - /// use icu::calendar::Gregorian; - /// use icu::datetime::DateTimeFormatter; - /// use icu::datetime::fieldset::MD; - /// use icu::datetime::fieldset::HM; - /// use icu::datetime::fieldset::Vs; - /// use icu::datetime::fieldset::Combo; - /// use icu::locale::locale; - /// use tinystr::tinystr; - /// use writeable::assert_try_writeable_eq; - /// - /// type MyDateTimeZoneSet = Combo< - /// MD, - /// HM, - /// Vs, - /// >; - /// - /// let fmt = DateTimeFormatter::try_new( - /// &locale!("en-US").into(), - /// MyDateTimeZoneSet::long(), - /// ) - /// .unwrap(); - /// - /// let dtz = IxdtfParser::new().try_from_str("2024-09-17T15:47:50-05:00[America/Chicago]").unwrap(); - /// - /// assert_try_writeable_eq!( - /// fmt.convert_and_format(&dtz), - /// "September 17, 3:47 PM CT" - /// ); - /// ``` - /// - /// Don't use long length if it is the only field: - /// - /// ``` - /// use icu::calendar::Gregorian; - /// use icu::datetime::FixedCalendarDateTimeFormatter; - /// use icu::datetime::fieldset::Vs; - /// use icu::datetime::LoadError; - /// use icu::locale::locale; - /// - /// let result = FixedCalendarDateTimeFormatter::::try_new( - /// &locale!("en").into(), - /// Vs::long(), - /// ); - /// - /// assert!(matches!(result, Err(LoadError::TypeTooNarrow(_)))); - /// ``` - /// /// Since non-location names might change over time, /// this time zone style requires a reference time. /// @@ -1342,6 +1453,10 @@ impl_zone_marker!( description = "time zone in generic non-location format (only short)", sample_length = short, sample = "CT", + field_short = (fields::TimeZone::GenericNonLocation, fields::FieldLength::One), + field_long = (fields::TimeZone::GenericNonLocation, fields::FieldLength::One), + resolved_type = V, + skip_tests = "This field set can be used only in combination with others.", zone_essentials = yes, zone_locations = yes, zone_generic_short = yes, @@ -1380,37 +1495,44 @@ impl_zone_marker!( description = "time zone in location format", sample_length = long, sample = "Chicago Time", + field_short = (fields::TimeZone::Location, fields::FieldLength::Four), + field_long = (fields::TimeZone::Location, fields::FieldLength::Four), + resolved_type = L, zone_essentials = yes, zone_locations = yes, input_tzid = yes, + enumerated = yes, ); impl_zoneddatetime_marker!( - YMDHMSV, + YMDTV, description = "locale-dependent date and time fields with a time zone", sample_length = medium, sample = "17 May 2024, 15:47:50 GMT", - date = YMD, - time = HMS, - zone = V, + datetime = YMDT, + zone = Vs, ); impl_zoneddatetime_marker!( - YMDHMSZ, + YMDTZ, description = "locale-dependent date and time fields with a time zone", sample_length = medium, sample = "17 May 2024, 15:47:50 BST", - date = YMD, - time = HMS, - zone = Z, + datetime = YMDT, + zone = Zs, ); impl_zoneddatetime_marker!( - YMDHMSO, + YMDTO, description = "locale-dependent date and time fields with a time zone", sample_length = medium, sample = "17 May 2024, 15:47:50 GMT+1", - date = YMD, - time = HMS, + datetime = YMDT, zone = O, ); + +impl_zone_combo_helpers!(DateFieldSet, DateZone, UNREACHABLE, no_wrap); + +impl_zone_combo_helpers!(TimeFieldSet, TimeZone, UNREACHABLE, no_wrap); + +impl_zone_combo_helpers!(DateAndTimeFieldSet, DateTimeZone, UNREACHABLE, no_wrap); diff --git a/components/datetime/src/format/datetime.rs b/components/datetime/src/format/datetime.rs index 8f5ad9ff02d..d8272f18f7b 100644 --- a/components/datetime/src/format/datetime.rs +++ b/components/datetime/src/format/datetime.rs @@ -3,25 +3,20 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use super::neo::RawDateTimeNamesBorrowed; -use super::GetNameForDayPeriodError; +use super::time_zone::{FormatTimeZone, FormatTimeZoneError, Iso8601Format, TimeZoneFormatterUnit}; use super::{ - GetNameForMonthError, GetNameForWeekdayError, GetSymbolForEraError, MonthPlaceholderValue, + GetNameForDayPeriodError, GetNameForMonthError, GetNameForWeekdayError, + GetSymbolForCyclicYearError, GetSymbolForEraError, MonthPlaceholderValue, }; -use crate::fields::{self, Day, Field, FieldLength, FieldSymbol, Second, Week, Year}; +use crate::error::DateTimeWriteError; +use crate::fields::{self, FieldLength, FieldSymbol, Second, Year}; use crate::input::ExtractedInput; use crate::provider::pattern::runtime::PatternMetadata; use crate::provider::pattern::PatternItem; -use crate::time_zone::{ - FormatTimeZone, FormatTimeZoneError, Iso8601Format, IsoFormat, IsoMinutes, IsoSeconds, - ResolvedNeoTimeZoneSkeleton, -}; use core::fmt::{self, Write}; use fixed_decimal::FixedDecimal; -use icu_calendar::types::{ - FormattingEra, {DayOfWeekInMonth, IsoWeekday, MonthCode}, -}; -use icu_calendar::AnyCalendarKind; +use icu_calendar::types::{DayOfWeekInMonth, IsoWeekday}; use icu_decimal::FixedDecimalFormatter; use writeable::{Part, Writeable}; @@ -35,32 +30,14 @@ fn try_write_number( where W: writeable::PartsWrite + ?Sized, { - if let Some(fdf) = fixed_decimal_format { - match length { - FieldLength::One | FieldLength::NumericOverride(_) => {} - FieldLength::TwoDigit => { - num.pad_start(2); - num.set_max_position(2); - } - FieldLength::Abbreviated => { - num.pad_start(3); - } - FieldLength::Wide => { - num.pad_start(4); - } - FieldLength::Narrow => { - num.pad_start(5); - } - FieldLength::Six => { - num.pad_start(6); - } - } + num.pad_start(length.to_len() as i16); + if let Some(fdf) = fixed_decimal_format { fdf.format(&num).write_to(result)?; Ok(Ok(())) } else { result.with_part(writeable::Part::ERROR, |r| num.write_to(r))?; - Ok(Err(DateTimeWriteError::MissingFixedDecimalFormatter)) + Ok(Err(DateTimeWriteError::FixedDecimalFormatterNotLoaded)) } } @@ -95,53 +72,6 @@ where Ok(r) } -#[non_exhaustive] -#[derive(Debug, PartialEq, Copy, Clone, displaydoc::Display)] -/// Error for `TryWriteable` implementations -pub enum DateTimeWriteError { - // Data not loaded - /// Missing FixedDecimalFormatter - #[displaydoc("FixedDecimalFormatter not loaded")] - MissingFixedDecimalFormatter, - /// Missing OrdinalRules - #[displaydoc("OrdinalRules not loaded")] - MissingOrdinalRules, - /// Missing WeekCalculator - #[displaydoc("WeekCalculator not loaded")] - MissingWeekCalculator, - /// TODO - #[displaydoc("Names for {0:?} not loaded")] - MissingNames(Field), - - // Something not found in data - // TODO: Are these actionable? Can clients even invent their own months and days? - /// Missing month symbol - #[displaydoc("Cannot find symbol for month {0:?}")] - MissingMonthSymbol(MonthCode), - /// Missing era symbol - #[displaydoc("Cannot find symbol for era {0:?}")] - MissingEraSymbol(FormattingEra), - /// Missing weekday symbol - #[displaydoc("Cannot find symbol for weekday {0:?}")] - MissingWeekdaySymbol(IsoWeekday), - - // Invalid input - /// Incomplete input - #[displaydoc("Incomplete input, missing value for {0:?}")] - MissingInputField(&'static str), - /// Cyclic year overflow - #[displaydoc("Cyclic year overflow, found {value}, maximum {max}")] - CyclicYearOverflow { - /// Value - value: usize, - /// Max - max: usize, - }, - /// Unsupported field - #[displaydoc("Unsupported field {0:?}")] - UnsupportedField(Field), -} - // This function assumes that the correct decision has been // made regarding availability of symbols in the caller. // @@ -158,181 +88,142 @@ fn try_write_field( where W: writeable::PartsWrite + ?Sized, { - // Writes an error string for the given symbol - fn write_value_missing( - w: &mut (impl writeable::PartsWrite + ?Sized), - field: fields::Field, - ) -> Result<(), fmt::Error> { - w.with_part(Part::ERROR, |w| { - "{".write_to(w)?; - char::from(field.symbol).write_to(w)?; - "}".write_to(w) - }) + macro_rules! input { + ($name:ident = $input:expr) => { + let Some($name) = $input else { + write_value_missing(w, field)?; + return Ok(Err(DateTimeWriteError::MissingInputField(stringify!( + $name + )))); + }; + }; } Ok(match (field.symbol, field.length) { - (FieldSymbol::Era, l) => match input.year { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("year")) - } - Some(year) => match year.formatting_era() { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("era")) - } - Some(era) => { - let era_symbol = datetime_names - .get_name_for_era(l, era) - .map_err(|e| match e { - GetSymbolForEraError::Missing => { - DateTimeWriteError::MissingEraSymbol(era) - } - GetSymbolForEraError::MissingNames(f) => { - DateTimeWriteError::MissingNames(f) - } - }); - match era_symbol { - Err(e) => { - w.with_part(Part::ERROR, |w| w.write_str(&era.fallback_era()))?; - Err(e) - } - Ok(era) => Ok(w.write_str(era)?), - } - } - }, - }, - (FieldSymbol::Year(Year::Calendar), l) => match input.year { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("year")) - } - Some(year) => try_write_number(w, fdf, year.era_year_or_extended().into(), l)?, - }, - (FieldSymbol::Year(Year::Cyclic), l) => match input.year { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("year")) - } - Some(year) => { - let r = year - .cyclic() - .ok_or(DateTimeWriteError::MissingInputField("cyclic")) - .and_then(|cyclic| { - // TODO(#3761): This is a hack, we should use actual data for cyclic years - let cyclics: &[&str; 60] = match input.any_calendar_kind { - Some(AnyCalendarKind::Dangi) => &[ - "갑자", "을축", "병인", "정묘", "무진", "기사", "경오", "신미", - "임신", "계유", "갑술", "을해", "병자", "정축", "무인", "기묘", - "경진", "신사", "임오", "계미", "갑신", "을유", "병술", "정해", - "무자", "기축", "경인", "신묘", "임진", "계사", "갑오", "을미", - "병신", "정유", "무술", "기해", "경자", "신축", "임인", "계묘", - "갑진", "을사", "병오", "정미", "무신", "기유", "경술", "신해", - "임자", "계축", "갑인", "을묘", "병진", "정사", "무오", "기미", - "경신", "신유", "임술", "계해", - ], - // for now assume all other calendars use the stem-branch model - _ => &[ - "甲子", "乙丑", "丙寅", "丁卯", "戊辰", "己巳", "庚午", "辛未", - "壬申", "癸酉", "甲戌", "乙亥", "丙子", "丁丑", "戊寅", "己卯", - "庚辰", "辛巳", "壬午", "癸未", "甲申", "乙酉", "丙戌", "丁亥", - "戊子", "己丑", "庚寅", "辛卯", "壬辰", "癸巳", "甲午", "乙未", - "丙申", "丁酉", "戊戌", "己亥", "庚子", "辛丑", "壬寅", "癸卯", - "甲辰", "乙巳", "丙午", "丁未", "戊申", "己酉", "庚戌", "辛亥", - "壬子", "癸丑", "甲寅", "乙卯", "丙辰", "丁巳", "戊午", "己未", - "庚申", "辛酉", "壬戌", "癸亥", - ], - }; - let value: usize = cyclic.get() as usize; - cyclics - .get(value - 1) - .ok_or(DateTimeWriteError::CyclicYearOverflow { - value, - max: cyclics.len() + 1, - }) - }); - match r { - Err(e) => { - w.with_part(Part::ERROR, |w| { - try_write_number(w, fdf, year.era_year_or_extended().into(), l) - .map(|_| ()) - })?; - Err(e) - } - Ok(cyclic_str) => Ok(w.write_str(cyclic_str)?), - } - } - }, - (FieldSymbol::Year(Year::RelatedIso), l) => { - match input - .year - .ok_or(DateTimeWriteError::MissingInputField("year")) - .and_then(|year| { - year.related_iso() - .ok_or(DateTimeWriteError::MissingInputField("related_iso")) - }) { + (FieldSymbol::Era, l) => { + input!(year = input.year); + input!(era = year.formatting_era()); + let era_symbol = datetime_names + .get_name_for_era(l, era) + .map_err(|e| match e { + GetSymbolForEraError::Invalid => DateTimeWriteError::InvalidEra(era), + GetSymbolForEraError::NotLoaded => DateTimeWriteError::NamesNotLoaded(field), + }); + match era_symbol { Err(e) => { - write_value_missing(w, field)?; + w.with_part(Part::ERROR, |w| w.write_str(&era.fallback_name()))?; Err(e) } - Ok(iso) => try_write_number(w, fdf, iso.into(), l)?, + Ok(era) => Ok(w.write_str(era)?), } } - (FieldSymbol::Month(_), l @ (FieldLength::One | FieldLength::TwoDigit)) => { - match input.month { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("month")) - } - Some(month) => try_write_number(w, fdf, month.ordinal.into(), l)?, + (FieldSymbol::Year(Year::Calendar), l) => { + input!(year = input.year); + let mut year = FixedDecimal::from(year.era_year_or_extended()); + if matches!(l, FieldLength::Two) { + // 'yy' and 'YY' truncate + year.set_max_position(2); } + try_write_number(w, fdf, year, l)? } - (FieldSymbol::Month(month), l) => match input.month { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("month")) - } - Some(month_info) => match datetime_names - .get_name_for_month(month, l, month_info.formatting_code) - .map_err(|e| match e { - GetNameForMonthError::Missing => { - DateTimeWriteError::MissingMonthSymbol(month_info.formatting_code) - } - GetNameForMonthError::MissingNames(f) => DateTimeWriteError::MissingNames(f), - }) { + (FieldSymbol::Year(Year::Cyclic), l) => { + input!(year = input.year); + input!(cyclic = year.cyclic()); + + match datetime_names.get_name_for_cyclic(l, cyclic) { + Ok(name) => Ok(w.write_str(name)?), Err(e) => { - w.with_part(Part::ERROR, |w| w.write_str(&month_info.formatting_code.0))?; - Err(e) + w.with_part(Part::ERROR, |w| { + try_write_number( + w, + fdf, + year.era_year_or_extended().into(), + FieldLength::One, + ) + .map(|_| ()) + })?; + return Ok(Err(match e { + GetSymbolForCyclicYearError::Invalid { max } => { + DateTimeWriteError::InvalidCyclicYear { + value: cyclic.get() as usize, + max, + } + } + GetSymbolForCyclicYearError::NotLoaded => { + DateTimeWriteError::NamesNotLoaded(field) + } + })); } + } + } + (FieldSymbol::Year(Year::RelatedIso), l) => { + input!(year = input.year); + input!(related_iso = year.related_iso()); + + // Always in latin digits according to spec + FixedDecimal::from(related_iso) + .padded_start(l.to_len() as i16) + .write_to(w)?; + Ok(()) + } + (FieldSymbol::Month(_), l @ (FieldLength::One | FieldLength::Two)) => { + input!(month = input.month); + try_write_number(w, fdf, month.ordinal.into(), l)? + } + (FieldSymbol::Month(symbol), l) => { + input!(month = input.month); + match datetime_names.get_name_for_month(symbol, l, month.formatting_code) { Ok(MonthPlaceholderValue::PlainString(symbol)) => { w.write_str(symbol)?; Ok(()) } Ok(MonthPlaceholderValue::Numeric) => { - try_write_number(w, fdf, month_info.ordinal.into(), l)? + debug_assert!(l == FieldLength::One); + try_write_number(w, fdf, month.ordinal.into(), l)? } Ok(MonthPlaceholderValue::NumericPattern(substitution_pattern)) => { - w.write_str(substitution_pattern.get_prefix())?; - let r = try_write_number(w, fdf, month_info.ordinal.into(), l)?; - w.write_str(substitution_pattern.get_suffix())?; - r + debug_assert!(l == FieldLength::One); + if let Some(fdf) = fdf { + substitution_pattern + .interpolate([fdf.format( + &FixedDecimal::from(month.ordinal).padded_start(l.to_len() as i16), + )]) + .write_to(w)?; + Ok(()) + } else { + w.with_part(Part::ERROR, |w| { + substitution_pattern + .interpolate([FixedDecimal::from(month.ordinal) + .padded_start(l.to_len() as i16)]) + .write_to(w) + })?; + Err(DateTimeWriteError::FixedDecimalFormatterNotLoaded) + } + } + Err(e) => { + w.with_part(Part::ERROR, |w| w.write_str(&month.formatting_code.0))?; + Err(match e { + GetNameForMonthError::Invalid => { + DateTimeWriteError::InvalidMonthCode(month.formatting_code) + } + GetNameForMonthError::NotLoaded => { + DateTimeWriteError::NamesNotLoaded(field) + } + }) } - }, - }, - (FieldSymbol::Weekday(weekday), l) => match input.iso_weekday { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("iso_weekday")) } - Some(wd) => match datetime_names - .get_name_for_weekday(weekday, l, wd) + } + (FieldSymbol::Week(w), _) => match w {}, + (FieldSymbol::Weekday(weekday), l) => { + input!(iso_weekday = input.iso_weekday); + match datetime_names + .get_name_for_weekday(weekday, l, iso_weekday) .map_err(|e| match e { - GetNameForWeekdayError::Missing => DateTimeWriteError::MissingWeekdaySymbol(wd), - GetNameForWeekdayError::MissingNames(f) => DateTimeWriteError::MissingNames(f), + GetNameForWeekdayError::NotLoaded => DateTimeWriteError::NamesNotLoaded(field), }) { Err(e) => { w.with_part(Part::ERROR, |w| { - w.write_str(match wd { + w.write_str(match iso_weekday { IsoWeekday::Monday => "mon", IsoWeekday::Tuesday => "tue", IsoWeekday::Wednesday => "wed", @@ -345,209 +236,254 @@ where Err(e) } Ok(s) => Ok(w.write_str(s)?), - }, - }, - (FieldSymbol::Day(fields::Day::DayOfMonth), l) => match input.day_of_month { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("day_of_month")) } - Some(d) => try_write_number(w, fdf, d.0.into(), l)?, - }, + } + (FieldSymbol::Day(fields::Day::DayOfMonth), l) => { + input!(day_of_month = input.day_of_month); + try_write_number(w, fdf, day_of_month.0.into(), l)? + } (FieldSymbol::Day(fields::Day::DayOfWeekInMonth), l) => { - match input.day_of_month.map(DayOfWeekInMonth::from) { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("day_of_month")) - } - Some(d) => try_write_number(w, fdf, d.0.into(), l)?, - } + input!(day_of_month = input.day_of_month); + try_write_number(w, fdf, DayOfWeekInMonth::from(day_of_month).0.into(), l)? } - (FieldSymbol::Hour(hour), l) => match input.hour { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("hour")) - } - Some(h) => { - let h = usize::from(h) as isize; - let h = match hour { - fields::Hour::H11 => h % 12, - fields::Hour::H12 => { - let v = h % 12; - if v == 0 { - 12 - } else { - v - } + (FieldSymbol::Day(fields::Day::DayOfYear), l) => { + input!(day_of_year = input.day_of_year); + try_write_number(w, fdf, day_of_year.day_of_year.into(), l)? + } + (FieldSymbol::Hour(symbol), l) => { + input!(hour = input.hour); + let h = hour.number(); + let h = match symbol { + fields::Hour::H11 => h % 12, + fields::Hour::H12 => { + let v = h % 12; + if v == 0 { + 12 + } else { + v } - fields::Hour::H23 => h, - fields::Hour::H24 => { - if h == 0 { - 24 - } else { - h - } + } + fields::Hour::H23 => h, + fields::Hour::H24 => { + if h == 0 { + 24 + } else { + h } - }; - try_write_number(w, fdf, h.into(), l)? - } - }, - (FieldSymbol::Minute, l) => match input.minute { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("minute")) - } - Some(iso_minute) => try_write_number(w, fdf, usize::from(iso_minute).into(), l)?, - }, - (FieldSymbol::Second(Second::Second), l) => match input.second { - None => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("second")) - } - Some(second) => { - // Normal seconds formatting with no fractional second digits - try_write_number(w, fdf, usize::from(second).into(), l)? - } - }, - (FieldSymbol::DecimalSecond(decimal_second), l) => { - match (input.second, input.nanosecond) { - (None, _) | (_, None) => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("second")) } - (Some(second), Some(ns)) => { - // Formatting with fractional seconds - let mut s = FixedDecimal::from(usize::from(second)); - let _infallible = - s.concatenate_end(FixedDecimal::from(usize::from(ns)).multiplied_pow10(-9)); - debug_assert!(_infallible.is_ok()); - let position = -(decimal_second as i16); - s.trunc(position); - s.pad_end(position); - try_write_number(w, fdf, s, l)? + }; + try_write_number(w, fdf, h.into(), l)? + } + (FieldSymbol::Minute, l) => { + input!(minute = input.minute); + try_write_number(w, fdf, minute.number().into(), l)? + } + (FieldSymbol::Second(Second::Second), l) => { + input!(second = input.second); + try_write_number(w, fdf, second.number().into(), l)? + } + (FieldSymbol::Second(Second::MillisInDay), l) => { + input!(hour = input.hour); + input!(minute = input.minute); + input!(second = input.second); + input!(nanosecond = input.nanosecond); + + let milliseconds = (((hour.number() as u32 * 60) + minute.number() as u32) * 60 + + second.number() as u32) + * 1000 + + nanosecond.number() / 1_000_000; + try_write_number(w, fdf, milliseconds.into(), l)? + } + (FieldSymbol::DecimalSecond(decimal_second), l) => { + input!(second = input.second); + input!(nanosecond = input.nanosecond); + + // Formatting with fractional seconds + let mut s = FixedDecimal::from(second.number()); + let _infallible = + s.concatenate_end(FixedDecimal::from(nanosecond.number()).multiplied_pow10(-9)); + debug_assert!(_infallible.is_ok()); + let position = -(decimal_second as i16); + s.trunc(position); + s.pad_end(position); + try_write_number(w, fdf, s, l)? + } + (FieldSymbol::DayPeriod(period), l) => { + input!(hour = input.hour); + + match datetime_names.get_name_for_day_period( + period, + l, + hour, + pattern_metadata.time_granularity().is_top_of_hour( + input.minute.unwrap_or_default().number(), + input.second.unwrap_or_default().number(), + input.nanosecond.unwrap_or_default().number(), + ), + ) { + Err(e) => { + w.with_part(Part::ERROR, |w| { + w.write_str(if hour.number() < 12 { "AM" } else { "PM" }) + })?; + Err(match e { + GetNameForDayPeriodError::NotLoaded => { + DateTimeWriteError::NamesNotLoaded(field) + } + }) } + Ok(s) => Ok(w.write_str(s)?), + } + } + (FieldSymbol::TimeZone(fields::TimeZone::SpecificNonLocation), FieldLength::Four) => { + perform_timezone_fallback( + w, + input, + datetime_names, + fdf, + field, + &[ + TimeZoneFormatterUnit::SpecificNonLocation(FieldLength::Four), + TimeZoneFormatterUnit::SpecificLocation, + TimeZoneFormatterUnit::LocalizedOffset(FieldLength::Four), + ], + )? + } + (FieldSymbol::TimeZone(fields::TimeZone::SpecificNonLocation), l) => { + perform_timezone_fallback( + w, + input, + datetime_names, + fdf, + field, + &[ + TimeZoneFormatterUnit::SpecificNonLocation(l), + TimeZoneFormatterUnit::LocalizedOffset(l), + ], + )? + } + (FieldSymbol::TimeZone(fields::TimeZone::GenericNonLocation), l) => { + perform_timezone_fallback( + w, + input, + datetime_names, + fdf, + field, + &[ + TimeZoneFormatterUnit::GenericNonLocation(l), + TimeZoneFormatterUnit::GenericLocation, + TimeZoneFormatterUnit::LocalizedOffset(l), + ], + )? + } + (FieldSymbol::TimeZone(fields::TimeZone::Location), FieldLength::Four) => { + perform_timezone_fallback( + w, + input, + datetime_names, + fdf, + field, + &[ + TimeZoneFormatterUnit::GenericLocation, + TimeZoneFormatterUnit::LocalizedOffset(FieldLength::Four), + ], + )? + } + (FieldSymbol::TimeZone(fields::TimeZone::LocalizedOffset), l) => perform_timezone_fallback( + w, + input, + datetime_names, + fdf, + field, + &[TimeZoneFormatterUnit::LocalizedOffset(l)], + )?, + (FieldSymbol::TimeZone(fields::TimeZone::Location), _) => perform_timezone_fallback( + w, + input, + datetime_names, + fdf, + field, + &[TimeZoneFormatterUnit::Bcp47Id], + )?, + (FieldSymbol::TimeZone(fields::TimeZone::IsoWithZ), l) => perform_timezone_fallback( + w, + input, + datetime_names, + fdf, + field, + &[TimeZoneFormatterUnit::Iso8601(Iso8601Format::with_z(l))], + )?, + (FieldSymbol::TimeZone(fields::TimeZone::Iso), l) => perform_timezone_fallback( + w, + input, + datetime_names, + fdf, + field, + &[TimeZoneFormatterUnit::Iso8601(Iso8601Format::without_z(l))], + )?, + }) +} + +// Writes an error string for the given symbol +fn write_value_missing( + w: &mut (impl writeable::PartsWrite + ?Sized), + field: fields::Field, +) -> Result<(), fmt::Error> { + w.with_part(Part::ERROR, |w| { + "{".write_to(w)?; + char::from(field.symbol).write_to(w)?; + "}".write_to(w) + }) +} + +fn perform_timezone_fallback( + w: &mut (impl writeable::PartsWrite + ?Sized), + input: &ExtractedInput, + datetime_names: &RawDateTimeNamesBorrowed, + fdf: Option<&FixedDecimalFormatter>, + field: fields::Field, + units: &[TimeZoneFormatterUnit], +) -> Result, core::fmt::Error> { + let payloads = datetime_names.get_payloads(); + let mut r = Err(FormatTimeZoneError::Fallback); + for unit in units { + match unit.format(w, input, payloads, fdf)? { + Err(FormatTimeZoneError::Fallback) => { + // Expected, try the next unit + continue; + } + r2 => { + r = r2; + break; } } - (FieldSymbol::DayPeriod(period), l) => match input.hour { - None => { + } + + Ok(match r { + Ok(()) => Ok(()), + Err(e) => { + if let Some(offset) = input.offset { + w.with_part(Part::ERROR, |w| { + Iso8601Format::without_z(field.length).format_infallible(w, offset) + })?; + } else { write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField("hour")) } - Some(hour) => { - match datetime_names - .get_name_for_day_period( - period, - l, - hour, - pattern_metadata.time_granularity().is_top_of_hour( - input.minute.map(u8::from).unwrap_or(0), - input.second.map(u8::from).unwrap_or(0), - input.nanosecond.map(u32::from).unwrap_or(0), - ), - ) - .map_err(|e| match e { - GetNameForDayPeriodError::MissingNames(f) => { - DateTimeWriteError::MissingNames(f) - } - }) { - Err(e) => { - w.with_part(Part::ERROR, |w| { - w.write_str(if usize::from(hour) < 12 { "AM" } else { "PM" }) - })?; - Err(e) - } - Ok(s) => Ok(w.write_str(s)?), + match e { + FormatTimeZoneError::FixedDecimalFormatterNotLoaded => { + Err(DateTimeWriteError::FixedDecimalFormatterNotLoaded) } - } - }, - (FieldSymbol::TimeZone(time_zone), length) => { - // TODO: Implement proper formatting logic here - match ResolvedNeoTimeZoneSkeleton::from_field(time_zone, length) { - None => { - w.with_part(Part::ERROR, |w| { - w.write_str("{unsupported:")?; - w.write_char(char::from(field.symbol))?; - w.write_str("}") - })?; - Err(DateTimeWriteError::UnsupportedField(field)) + FormatTimeZoneError::NamesNotLoaded => { + Err(DateTimeWriteError::NamesNotLoaded(field)) } - Some(time_zone) => { - let payloads = datetime_names.get_payloads(); - let mut r = Err(FormatTimeZoneError::Fallback); - for formatter in time_zone.units() { - match formatter.format(w, input, payloads, fdf)? { - Err(FormatTimeZoneError::Fallback) => { - // Expected common case: the unit needs fall back to the next one - continue; - } - r2 => { - r = r2; - break; - } - } - } - - match r { - Ok(()) => Ok(()), - Err(FormatTimeZoneError::MissingInputField(f)) => { - write_value_missing(w, field)?; - Err(DateTimeWriteError::MissingInputField(f)) - } - Err( - e @ (FormatTimeZoneError::MissingFixedDecimalFormatter - | FormatTimeZoneError::MissingZoneSymbols), - ) => { - if let Some(offset) = input.offset { - w.with_part(Part::ERROR, |w| { - Iso8601Format { - format: IsoFormat::Basic, - minutes: IsoMinutes::Required, - seconds: IsoSeconds::Optional, - } - .format_infallible(w, offset) - })?; - } else { - write_value_missing(w, field)?; - } - Err(match e { - FormatTimeZoneError::MissingFixedDecimalFormatter => { - DateTimeWriteError::MissingFixedDecimalFormatter - } - FormatTimeZoneError::MissingZoneSymbols => { - DateTimeWriteError::MissingNames(field) - } - _ => unreachable!(), - }) - } - Err(FormatTimeZoneError::Fallback) => { - // unreachable because our current fallback chains don't fall through - w.with_part(Part::ERROR, |w| { - w.write_str("{unsupported:")?; - w.write_char(char::from(field.symbol))?; - w.write_str("}") - })?; - Err(DateTimeWriteError::UnsupportedField(field)) - } - } + FormatTimeZoneError::MissingInputField(f) => { + Err(DateTimeWriteError::MissingInputField(f)) + } + FormatTimeZoneError::Fallback => { + debug_assert!(false, "timezone fallback chain fell through {input:?}"); + Ok(()) } } } - ( - FieldSymbol::Year(Year::WeekOf) - | FieldSymbol::Week(Week::WeekOfYear) - | FieldSymbol::Week(Week::WeekOfMonth) - | FieldSymbol::Day(Day::DayOfYear) - | FieldSymbol::Day(Day::ModifiedJulianDay) - | FieldSymbol::Second(Second::Millisecond), - _, - ) => { - w.with_part(Part::ERROR, |w| { - w.write_str("{unsupported:")?; - w.write_char(char::from(field.symbol))?; - w.write_str("}") - })?; - Err(DateTimeWriteError::UnsupportedField(field)) - } }) } @@ -556,48 +492,16 @@ where #[cfg(feature = "compiled_data")] mod tests { use super::*; - use crate::{fieldset::YMD, neo_skeleton::NeoSkeletonLength, provider::pattern::runtime}; - use icu_calendar::types::FormattingEra; use icu_decimal::options::{FixedDecimalFormatterOptions, GroupingStrategy}; - use tinystr::tinystr; - - #[test] - fn test_mixed_calendar_eras() { - use crate::neo::DateTimeFormatter; - use crate::options::length; - use icu_calendar::cal::JapaneseExtended; - use icu_calendar::Date; - - let locale = icu::locale::locale!("en-u-ca-japanese"); - let dtf = DateTimeFormatter::try_new(&locale.into(), YMD::medium()) - .expect("DateTimeFormat construction succeeds"); - - let date = Date::try_new_gregorian(1800, 9, 1).expect("Failed to construct Date."); - let date = date - .to_calendar(JapaneseExtended::new()) - .into_japanese_date() - .to_any(); - - writeable::assert_try_writeable_eq!( - dtf.strict_format(&date).unwrap(), - "Sep 1, 12 kansei-1789", - Err(DateTimeWriteError::MissingEraSymbol(FormattingEra::Code( - tinystr!(16, "kansei-1789").into() - ))) - ); - } #[test] fn test_format_number() { let values = &[2, 20, 201, 2017, 20173]; let samples = &[ (FieldLength::One, ["2", "20", "201", "2017", "20173"]), - (FieldLength::TwoDigit, ["02", "20", "01", "17", "73"]), - ( - FieldLength::Abbreviated, - ["002", "020", "201", "2017", "20173"], - ), - (FieldLength::Wide, ["0002", "0020", "0201", "2017", "20173"]), + (FieldLength::Two, ["02", "20", "201", "2017", "20173"]), + (FieldLength::Three, ["002", "020", "201", "2017", "20173"]), + (FieldLength::Four, ["0002", "0020", "0201", "2017", "20173"]), ]; let mut fixed_decimal_format_options = FixedDecimalFormatterOptions::default(); diff --git a/components/datetime/src/format/mod.rs b/components/datetime/src/format/mod.rs index dc0d212b06b..93555c400e7 100644 --- a/components/datetime/src/format/mod.rs +++ b/components/datetime/src/format/mod.rs @@ -2,33 +2,37 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -pub mod datetime; -pub mod neo; +pub(crate) mod datetime; +pub(crate) mod neo; +pub(crate) mod time_zone; -use crate::fields::Field; -use crate::provider::neo::SimpleSubstitutionPattern; +use icu_pattern::SinglePlaceholderPattern; pub(crate) enum GetNameForMonthError { - Missing, - MissingNames(Field), + Invalid, + NotLoaded, } pub(crate) enum GetNameForWeekdayError { - Missing, - MissingNames(Field), + NotLoaded, } pub(crate) enum GetSymbolForEraError { - Missing, - MissingNames(Field), + Invalid, + NotLoaded, +} + +pub(crate) enum GetSymbolForCyclicYearError { + Invalid { max: usize }, + NotLoaded, } pub(crate) enum GetNameForDayPeriodError { - MissingNames(Field), + NotLoaded, } /// Internal enum to represent the kinds of month symbols for interpolation pub(crate) enum MonthPlaceholderValue<'a> { PlainString(&'a str), Numeric, - NumericPattern(&'a SimpleSubstitutionPattern<'a>), + NumericPattern(&'a SinglePlaceholderPattern), } diff --git a/components/datetime/src/format/neo.rs b/components/datetime/src/format/neo.rs index 01c34999070..f022f35b789 100644 --- a/components/datetime/src/format/neo.rs +++ b/components/datetime/src/format/neo.rs @@ -2,18 +2,17 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -use super::datetime::{try_write_pattern_items, DateTimeWriteError}; +use super::datetime::try_write_pattern_items; use super::{ - GetNameForDayPeriodError, GetNameForMonthError, GetNameForWeekdayError, GetSymbolForEraError, - MonthPlaceholderValue, + GetNameForDayPeriodError, GetNameForMonthError, GetNameForWeekdayError, + GetSymbolForCyclicYearError, GetSymbolForEraError, MonthPlaceholderValue, }; +use crate::dynamic::CompositeDateTimeFieldSet; use crate::external_loaders::*; use crate::fields::{self, Field, FieldLength, FieldSymbol}; -use crate::helpers::size_test; use crate::input; use crate::input::ExtractedInput; use crate::neo_pattern::{DateTimePattern, DateTimePatternBorrowed}; -use crate::neo_skeleton::NeoDateTimeSkeleton; use crate::provider::neo::*; use crate::provider::pattern::PatternItem; use crate::provider::time_zones::tz; @@ -22,10 +21,11 @@ use crate::scaffold::{ AllInputMarkers, DateInputMarkers, DateTimeMarkers, GetField, IsInCalendar, NeoNeverMarker, TimeMarkers, TypedDateDataMarkers, ZoneMarkers, }; -use crate::time_zone::ResolvedNeoTimeZoneSkeleton; -use crate::time_zone::TimeZoneDataPayloadsBorrowed; +use crate::size_test_macro::size_test; +use crate::DateTimeWriteError; use core::fmt; use core::marker::PhantomData; +use core::num::NonZeroU8; use icu_calendar::types::FormattingEra; use icu_calendar::types::MonthCode; use icu_decimal::options::FixedDecimalFormatterOptions; @@ -69,15 +69,15 @@ impl_holder_trait!(tz::MzPeriodV1Marker); #[non_exhaustive] pub enum MaybePayloadError2 { - TypeTooNarrow, - InsufficientStorage, + TypeTooSpecific, + ConflictingField, } impl MaybePayloadError2 { - fn into_single_load_error(self, field: Field) -> SingleLoadError { + fn into_load_error(self, field: Field) -> PatternLoadError { match self { - Self::TypeTooNarrow => SingleLoadError::TypeTooNarrow(field), - Self::InsufficientStorage => SingleLoadError::DuplicateField(field), + Self::TypeTooSpecific => PatternLoadError::TypeTooSpecific(field), + Self::ConflictingField => PatternLoadError::ConflictingField(field), } } } @@ -150,7 +150,7 @@ where return Ok(Ok(())); } OptionalNames::SingleLength { .. } => { - return Err(MaybePayloadError2::InsufficientStorage); + return Err(MaybePayloadError2::ConflictingField); } OptionalNames::None => (), }; @@ -187,7 +187,7 @@ impl MaybePayload2 for () { P: BoundDataProvider + ?Sized, Self: Sized, { - Err(MaybePayloadError2::TypeTooNarrow) + Err(MaybePayloadError2::TypeTooSpecific) } #[allow(clippy::needless_lifetimes)] // Yokeable is involved #[inline] @@ -275,14 +275,14 @@ size_test!( /// 1. The calendar chosen at compile time for additional type safety /// 2. A components object type containing the fields that might be formatted /// -/// By default, the components object is set to [`NeoDateTimeComponents`], +/// By default, the components object is set to [`CompositeDateTimeFieldSet`], /// meaning that dates and times, but not time zones, are supported. A smaller /// components object results in smaller stack size. /// -/// To support all fields including time zones, use [`NeoComponents`]. +/// To support all fields including time zones, use [`CompositeFieldSet`]. /// -/// [`NeoComponents`]: crate::neo_skeleton::NeoComponents -/// [`NeoDateTimeComponents`]: crate::neo_skeleton::NeoDateTimeComponents +/// [`CompositeFieldSet`]: crate::fieldset::dynamic::CompositeFieldSet +/// [`CompositeDateTimeFieldSet`]: crate::fieldset::dynamic::CompositeDateTimeFieldSet /// /// # Examples /// @@ -300,11 +300,11 @@ size_test!( /// let mut names: TypedDateTimeNames = /// TypedDateTimeNames::try_new(&locale!("uk").into()).unwrap(); /// names -/// .include_month_names(fields::Month::Format, FieldLength::Abbreviated) +/// .include_month_names(fields::Month::Format, FieldLength::Three) /// .unwrap() -/// .include_weekday_names(fields::Weekday::Format, FieldLength::Abbreviated) +/// .include_weekday_names(fields::Weekday::Format, FieldLength::Three) /// .unwrap() -/// .include_day_period_names(FieldLength::Abbreviated) +/// .include_day_period_names(FieldLength::Three) /// .unwrap(); /// /// // Create a pattern from a pattern string (note: K is the hour with h11 hour cycle): @@ -324,13 +324,13 @@ size_test!( /// use icu::datetime::{DateTimeWriteError, TypedDateTimeNames}; /// use icu::datetime::fields::{Field, FieldLength, FieldSymbol, Weekday}; /// use icu::datetime::neo_pattern::DateTimePattern; -/// use icu::datetime::neo_skeleton::NeoSkeleton; +/// use icu::datetime::fieldset::dynamic::CompositeFieldSet; /// use icu::locale::locale; /// use icu::timezone::{TimeZoneInfo, IxdtfParser}; /// use writeable::{Part, assert_try_writeable_parts_eq}; /// -/// // Create an instance that can format all fields (NeoSkeleton): -/// let mut names: TypedDateTimeNames = +/// // Create an instance that can format all fields (CompositeFieldSet): +/// let mut names: TypedDateTimeNames = /// TypedDateTimeNames::try_new(&locale!("en").into()).unwrap(); /// /// // Create a pattern from a pattern string: @@ -346,7 +346,7 @@ size_test!( /// assert_try_writeable_parts_eq!( /// names.with_pattern(&pattern).format(&dtz), /// "It is: mon M11 20 2023 CE at 11:35:03.000 AM +0000", -/// Err(DateTimeWriteError::MissingNames(Field { symbol: FieldSymbol::Weekday(Weekday::Format), length: FieldLength::One })), +/// Err(DateTimeWriteError::NamesNotLoaded(Field { symbol: FieldSymbol::Weekday(Weekday::Format), length: FieldLength::One })), /// [ /// (7, 10, Part::ERROR), // mon /// (11, 14, Part::ERROR), // M11 @@ -401,7 +401,7 @@ size_test!( /// ); /// ``` #[derive(Debug)] -pub struct TypedDateTimeNames { +pub struct TypedDateTimeNames { locale: DataLocale, inner: RawDateTimeNames, _calendar: PhantomData, @@ -648,12 +648,12 @@ impl TypedDateTimeNames { /// use icu::datetime::{DateTimeWriteError, TypedDateTimeNames}; /// use icu::datetime::fields::{Field, FieldLength, FieldSymbol, Weekday}; /// use icu::datetime::neo_pattern::DateTimePattern; - /// use icu::datetime::neo_skeleton::NeoDateSkeleton; + /// use icu::datetime::fieldset::dynamic::DateFieldSet; /// use icu::locale::locale; /// use writeable::{Part, assert_try_writeable_parts_eq}; /// - /// // Create an instance that can format all fields (NeoSkeleton): - /// let names: TypedDateTimeNames = + /// // Create an instance that can format only date fields: + /// let names: TypedDateTimeNames = /// TypedDateTimeNames::new_without_number_formatting(&locale!("en").into()); /// /// // Create a pattern from a pattern string: @@ -669,12 +669,12 @@ impl TypedDateTimeNames { /// // (note that the padding is ignored in this fallback mode) /// assert_try_writeable_parts_eq!( /// names.with_pattern(&pattern).format(&date), - /// "It is: 2024-7-1", - /// Err(DateTimeWriteError::MissingFixedDecimalFormatter), + /// "It is: 2024-07-01", + /// Err(DateTimeWriteError::FixedDecimalFormatterNotLoaded), /// [ /// (7, 11, Part::ERROR), // 2024 - /// (12, 13, Part::ERROR), // 7 - /// (14, 15, Part::ERROR), // 1 + /// (12, 14, Part::ERROR), // 07 + /// (15, 17, Part::ERROR), // 01 /// ] /// ); /// ``` @@ -693,7 +693,7 @@ impl TypedDateTimeNames { &mut self, provider: &P, field_length: FieldLength, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where P: DataProvider + ?Sized, { @@ -714,7 +714,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::fields::FieldLength; - /// use icu::datetime::SingleLoadError; + /// use icu::datetime::PatternLoadError; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// @@ -723,22 +723,22 @@ impl TypedDateTimeNames { /// .unwrap(); /// /// // First length is successful: - /// names.include_year_names(FieldLength::Wide).unwrap(); + /// names.include_year_names(FieldLength::Four).unwrap(); /// /// // Attempting to load the first length a second time will succeed: - /// names.include_year_names(FieldLength::Wide).unwrap(); + /// names.include_year_names(FieldLength::Four).unwrap(); /// /// // But loading a new length fails: /// assert!(matches!( - /// names.include_year_names(FieldLength::Abbreviated), - /// Err(SingleLoadError::DuplicateField(_)) + /// names.include_year_names(FieldLength::Three), + /// Err(PatternLoadError::ConflictingField(_)) /// )); /// ``` #[cfg(feature = "compiled_data")] pub fn include_year_names( &mut self, field_length: FieldLength, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where crate::provider::Baked: icu_provider::DataProvider<::YearNamesV1Marker>, { @@ -753,7 +753,7 @@ impl TypedDateTimeNames { provider: &P, field_symbol: fields::Month, field_length: FieldLength, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where P: DataProvider + ?Sized, { @@ -775,7 +775,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::fields::FieldLength; - /// use icu::datetime::SingleLoadError; + /// use icu::datetime::PatternLoadError; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// @@ -787,22 +787,22 @@ impl TypedDateTimeNames { /// /// // First length is successful: /// names - /// .include_month_names(field_symbol, FieldLength::Wide) + /// .include_month_names(field_symbol, FieldLength::Four) /// .unwrap(); /// /// // Attempting to load the first length a second time will succeed: /// names - /// .include_month_names(field_symbol, FieldLength::Wide) + /// .include_month_names(field_symbol, FieldLength::Four) /// .unwrap(); /// /// // But loading a new symbol or length fails: /// assert!(matches!( - /// names.include_month_names(alt_field_symbol, FieldLength::Wide), - /// Err(SingleLoadError::DuplicateField(_)) + /// names.include_month_names(alt_field_symbol, FieldLength::Four), + /// Err(PatternLoadError::ConflictingField(_)) /// )); /// assert!(matches!( - /// names.include_month_names(field_symbol, FieldLength::Abbreviated), - /// Err(SingleLoadError::DuplicateField(_)) + /// names.include_month_names(field_symbol, FieldLength::Three), + /// Err(PatternLoadError::ConflictingField(_)) /// )); /// ``` #[cfg(feature = "compiled_data")] @@ -810,7 +810,7 @@ impl TypedDateTimeNames { &mut self, field_symbol: fields::Month, field_length: FieldLength, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where crate::provider::Baked: icu_provider::DataProvider<::MonthNamesV1Marker>, { @@ -824,7 +824,7 @@ impl TypedDateTimeNames { &mut self, provider: &P, field_length: FieldLength, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where P: DataProvider + ?Sized, { @@ -843,7 +843,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::fields::FieldLength; - /// use icu::datetime::SingleLoadError; + /// use icu::datetime::PatternLoadError; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// @@ -852,22 +852,22 @@ impl TypedDateTimeNames { /// .unwrap(); /// /// // First length is successful: - /// names.include_day_period_names(FieldLength::Wide).unwrap(); + /// names.include_day_period_names(FieldLength::Four).unwrap(); /// /// // Attempting to load the first length a second time will succeed: - /// names.include_day_period_names(FieldLength::Wide).unwrap(); + /// names.include_day_period_names(FieldLength::Four).unwrap(); /// /// // But loading a new length fails: /// assert!(matches!( - /// names.include_day_period_names(FieldLength::Abbreviated), - /// Err(SingleLoadError::DuplicateField(_)) + /// names.include_day_period_names(FieldLength::Three), + /// Err(PatternLoadError::ConflictingField(_)) /// )); /// ``` #[cfg(feature = "compiled_data")] pub fn include_day_period_names( &mut self, field_length: FieldLength, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where crate::provider::Baked: icu_provider::DataProvider, { @@ -882,7 +882,7 @@ impl TypedDateTimeNames { provider: &P, field_symbol: fields::Weekday, field_length: FieldLength, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where P: DataProvider + ?Sized, { @@ -904,7 +904,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::fields::FieldLength; - /// use icu::datetime::SingleLoadError; + /// use icu::datetime::PatternLoadError; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// @@ -916,22 +916,22 @@ impl TypedDateTimeNames { /// /// // First length is successful: /// names - /// .include_weekday_names(field_symbol, FieldLength::Wide) + /// .include_weekday_names(field_symbol, FieldLength::Four) /// .unwrap(); /// /// // Attempting to load the first length a second time will succeed: /// names - /// .include_weekday_names(field_symbol, FieldLength::Wide) + /// .include_weekday_names(field_symbol, FieldLength::Four) /// .unwrap(); /// /// // But loading a new symbol or length fails: /// assert!(matches!( - /// names.include_weekday_names(alt_field_symbol, FieldLength::Wide), - /// Err(SingleLoadError::DuplicateField(_)) + /// names.include_weekday_names(alt_field_symbol, FieldLength::Four), + /// Err(PatternLoadError::ConflictingField(_)) /// )); /// assert!(matches!( - /// names.include_weekday_names(field_symbol, FieldLength::Abbreviated), - /// Err(SingleLoadError::DuplicateField(_)) + /// names.include_weekday_names(field_symbol, FieldLength::Three), + /// Err(PatternLoadError::ConflictingField(_)) /// )); /// ``` #[cfg(feature = "compiled_data")] @@ -939,7 +939,7 @@ impl TypedDateTimeNames { &mut self, field_symbol: fields::Weekday, field_length: FieldLength, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where crate::provider::Baked: icu_provider::DataProvider, { @@ -950,7 +950,7 @@ impl TypedDateTimeNames { pub fn load_time_zone_essentials

( &mut self, provider: &P, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where P: DataProvider + ?Sized, { @@ -969,7 +969,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::neo_pattern::DateTimePattern; - /// use icu::datetime::neo_skeleton::NeoTimeZoneSkeleton; + /// use icu::datetime::fieldset::dynamic::ZoneFieldSet; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// use icu::timezone::IxdtfParser; @@ -987,7 +987,7 @@ impl TypedDateTimeNames { /// .zone; /// /// let mut names = - /// TypedDateTimeNames::::try_new( + /// TypedDateTimeNames::::try_new( /// &locale!("en-GB").into(), /// ) /// .unwrap(); @@ -1039,7 +1039,7 @@ impl TypedDateTimeNames { /// ); /// ``` #[cfg(feature = "compiled_data")] - pub fn include_time_zone_essentials(&mut self) -> Result<&mut Self, SingleLoadError> + pub fn include_time_zone_essentials(&mut self) -> Result<&mut Self, PatternLoadError> where crate::provider::Baked: icu_provider::DataProvider, { @@ -1050,7 +1050,7 @@ impl TypedDateTimeNames { pub fn load_time_zone_location_names

( &mut self, provider: &P, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where P: DataProvider + ?Sized, { @@ -1072,7 +1072,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::neo_pattern::DateTimePattern; - /// use icu::datetime::neo_skeleton::NeoTimeZoneSkeleton; + /// use icu::datetime::fieldset::dynamic::ZoneFieldSet; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// use icu::timezone::IxdtfParser; @@ -1085,7 +1085,7 @@ impl TypedDateTimeNames { /// .zone; /// /// let mut names = - /// TypedDateTimeNames::::try_new( + /// TypedDateTimeNames::::try_new( /// &locale!("en-GB").into(), /// ) /// .unwrap(); @@ -1103,7 +1103,7 @@ impl TypedDateTimeNames { /// ); /// ``` #[cfg(feature = "compiled_data")] - pub fn include_time_zone_location_names(&mut self) -> Result<&mut Self, SingleLoadError> + pub fn include_time_zone_location_names(&mut self) -> Result<&mut Self, PatternLoadError> where crate::provider::Baked: icu_provider::DataProvider, { @@ -1114,7 +1114,7 @@ impl TypedDateTimeNames { pub fn load_time_zone_generic_long_names

( &mut self, provider: &P, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where P: DataProvider + DataProvider + ?Sized, { @@ -1139,7 +1139,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::neo_pattern::DateTimePattern; - /// use icu::datetime::neo_skeleton::NeoTimeZoneSkeleton; + /// use icu::datetime::fieldset::dynamic::ZoneFieldSet; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// use icu::timezone::IxdtfParser; @@ -1157,7 +1157,7 @@ impl TypedDateTimeNames { /// .zone; /// /// let mut names = - /// TypedDateTimeNames::::try_new( + /// TypedDateTimeNames::::try_new( /// &locale!("en-GB").into(), /// ) /// .unwrap(); @@ -1179,7 +1179,7 @@ impl TypedDateTimeNames { /// ); /// ``` #[cfg(feature = "compiled_data")] - pub fn include_time_zone_generic_long_names(&mut self) -> Result<&mut Self, SingleLoadError> + pub fn include_time_zone_generic_long_names(&mut self) -> Result<&mut Self, PatternLoadError> where crate::provider::Baked: icu_provider::DataProvider, { @@ -1190,7 +1190,7 @@ impl TypedDateTimeNames { pub fn load_time_zone_generic_short_names

( &mut self, provider: &P, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where P: DataProvider + DataProvider + ?Sized, { @@ -1215,7 +1215,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::neo_pattern::DateTimePattern; - /// use icu::datetime::neo_skeleton::NeoTimeZoneSkeleton; + /// use icu::datetime::fieldset::dynamic::ZoneFieldSet; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// use icu::timezone::IxdtfParser; @@ -1233,7 +1233,7 @@ impl TypedDateTimeNames { /// .zone; /// /// let mut names = - /// TypedDateTimeNames::::try_new( + /// TypedDateTimeNames::::try_new( /// &locale!("en-GB").into(), /// ) /// .unwrap(); @@ -1255,7 +1255,7 @@ impl TypedDateTimeNames { /// ); /// ``` #[cfg(feature = "compiled_data")] - pub fn include_time_zone_generic_short_names(&mut self) -> Result<&mut Self, SingleLoadError> + pub fn include_time_zone_generic_short_names(&mut self) -> Result<&mut Self, PatternLoadError> where crate::provider::Baked: icu_provider::DataProvider, { @@ -1266,7 +1266,7 @@ impl TypedDateTimeNames { pub fn load_time_zone_specific_long_names

( &mut self, provider: &P, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where P: DataProvider + DataProvider + ?Sized, { @@ -1291,7 +1291,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::neo_pattern::DateTimePattern; - /// use icu::datetime::neo_skeleton::NeoTimeZoneSkeleton; + /// use icu::datetime::fieldset::dynamic::ZoneFieldSet; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// use icu::timezone::IxdtfParser; @@ -1309,7 +1309,7 @@ impl TypedDateTimeNames { /// .zone; /// /// let mut names = - /// TypedDateTimeNames::::try_new( + /// TypedDateTimeNames::::try_new( /// &locale!("en-GB").into(), /// ) /// .unwrap(); @@ -1331,7 +1331,7 @@ impl TypedDateTimeNames { /// ); /// ``` #[cfg(feature = "compiled_data")] - pub fn include_time_zone_specific_long_names(&mut self) -> Result<&mut Self, SingleLoadError> + pub fn include_time_zone_specific_long_names(&mut self) -> Result<&mut Self, PatternLoadError> where crate::provider::Baked: icu_provider::DataProvider, { @@ -1342,7 +1342,7 @@ impl TypedDateTimeNames { pub fn load_time_zone_specific_short_names

( &mut self, provider: &P, - ) -> Result<&mut Self, SingleLoadError> + ) -> Result<&mut Self, PatternLoadError> where P: DataProvider + DataProvider + ?Sized, { @@ -1367,7 +1367,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::neo_pattern::DateTimePattern; - /// use icu::datetime::neo_skeleton::NeoTimeZoneSkeleton; + /// use icu::datetime::fieldset::dynamic::ZoneFieldSet; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// use icu::timezone::IxdtfParser; @@ -1385,7 +1385,7 @@ impl TypedDateTimeNames { /// .zone; /// /// let mut names = - /// TypedDateTimeNames::::try_new( + /// TypedDateTimeNames::::try_new( /// &locale!("en-GB").into(), /// ) /// .unwrap(); @@ -1407,7 +1407,7 @@ impl TypedDateTimeNames { /// ); /// ``` #[cfg(feature = "compiled_data")] - pub fn include_time_zone_specific_short_names(&mut self) -> Result<&mut Self, SingleLoadError> + pub fn include_time_zone_specific_short_names(&mut self) -> Result<&mut Self, PatternLoadError> where crate::provider::Baked: icu_provider::DataProvider, { @@ -1432,7 +1432,7 @@ impl TypedDateTimeNames { /// ``` /// use icu::calendar::Time; /// use icu::datetime::neo_pattern::DateTimePattern; - /// use icu::datetime::neo_skeleton::NeoTimeSkeleton; + /// use icu::datetime::fieldset::dynamic::TimeFieldSet; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// use writeable::assert_try_writeable_eq; @@ -1440,7 +1440,7 @@ impl TypedDateTimeNames { /// let locale = &locale!("bn").into(); /// /// let mut names = - /// TypedDateTimeNames::<(), NeoTimeSkeleton>::try_new(&locale).unwrap(); + /// TypedDateTimeNames::<(), TimeFieldSet>::try_new(&locale).unwrap(); /// names.include_fixed_decimal_formatter(); /// /// // Create a pattern for the time, which is all numbers @@ -1484,7 +1484,7 @@ impl TypedDateTimeNames { &'l mut self, provider: &P, pattern: &'l DateTimePattern, - ) -> Result, LoadError> + ) -> Result, PatternLoadError> where P: DataProvider + DataProvider @@ -1516,9 +1516,7 @@ impl TypedDateTimeNames { &tz::MzPeriodV1Marker::bind(provider), &ExternalLoaderUnstable(provider), locale, - pattern - .iter_items() - .filter_map(FieldForDataLoading::try_from_pattern_item), + pattern.iter_items(), )?; Ok(DateTimePatternFormatter { inner: self.inner.with_pattern(pattern.as_borrowed()), @@ -1565,7 +1563,7 @@ impl TypedDateTimeNames { pub fn include_for_pattern<'l>( &'l mut self, pattern: &'l DateTimePattern, - ) -> Result, LoadError> + ) -> Result, PatternLoadError> where crate::provider::Baked: DataProvider + DataProvider @@ -1589,9 +1587,7 @@ impl TypedDateTimeNames { &tz::MzPeriodV1Marker::bind(&crate::provider::Baked), &ExternalLoaderCompiledData, locale, - pattern - .iter_items() - .filter_map(FieldForDataLoading::try_from_pattern_item), + pattern.iter_items(), )?; Ok(DateTimePatternFormatter { inner: self.inner.with_pattern(pattern.as_borrowed()), @@ -1601,59 +1597,30 @@ impl TypedDateTimeNames { } } -#[derive(Debug, Clone, Copy, PartialEq, displaydoc::Display)] -#[non_exhaustive] -/// Error returned from [`TypedDateTimeNames`]'s load methods. -pub enum SingleLoadError { - /// Duplicate field in pattern - DuplicateField(Field), - /// ICU4X does not support this field - UnsupportedField(Field), - /// The specific type does not support this field - TypeTooNarrow(Field), - /// An error arising from the [`DataProvider`] - Data(DataError), -} - #[derive(Debug, Clone, Copy, PartialEq, displaydoc::Display)] /// Error returned from [`TypedDateTimeNames`]'s pattern load methods. #[non_exhaustive] -pub enum LoadError { - /// DuplicateField - DuplicateField(Field), - /// ICU4X does not support this field - UnsupportedField(Field), - /// The specific type does not support this field - TypeTooNarrow(Field), - /// An error arising from the [`DataProvider`] +pub enum PatternLoadError { + /// A field conflicts with a previous field. + /// + /// Fields conflict if they require the same type of data, for example the + /// `EEE` and `EEEE` fields (short vs long weekday) conflict, or the `M` + /// and `L` (format vs standalone month) conflict. + #[displaydoc("A field conflicts with a previous field.")] + ConflictingField(Field), + /// The field symbol is not supported in that length. + /// + /// Some fields, such as `O` are not defined for all lengths (e.g. `OO`). + #[displaydoc("The field symbol is not supported in that length.")] + UnsupportedLength(Field), + /// The specific type does not support this field. + /// + /// This happens for example when trying to load a month field + /// on a [`TypedDateTimeNames`]. + #[displaydoc("The specific type does not support this field.")] + TypeTooSpecific(Field), + /// An error arising from the [`DataProvider`]. Data(DataError), - /// MissingNames - MissingNames(Field), -} - -impl From for LoadError { - fn from(value: SingleLoadError) -> Self { - match value { - SingleLoadError::Data(e) => LoadError::Data(e), - SingleLoadError::UnsupportedField(f) => LoadError::UnsupportedField(f), - SingleLoadError::TypeTooNarrow(f) => LoadError::TypeTooNarrow(f), - SingleLoadError::DuplicateField(f) => LoadError::DuplicateField(f), - } - } -} - -pub(crate) enum FieldForDataLoading { - Field(Field), - TimeZone(ResolvedNeoTimeZoneSkeleton), -} - -impl FieldForDataLoading { - pub(crate) fn try_from_pattern_item(pattern_item: PatternItem) -> Option { - match pattern_item { - PatternItem::Field(field) => Some(Self::Field(field)), - PatternItem::Literal(_) => None, - } - } } impl RawDateTimeNames { @@ -1699,7 +1666,7 @@ impl RawDateTimeNames { provider: &P, locale: &DataLocale, field_length: FieldLength, - ) -> Result<(), SingleLoadError> + ) -> Result<(), PatternLoadError> where P: BoundDataProvider + ?Sized, { @@ -1715,10 +1682,10 @@ impl RawDateTimeNames { marker_attrs::name_attr_for( marker_attrs::Context::Format, match field_length { - FieldLength::Abbreviated => marker_attrs::Length::Abbr, - FieldLength::Narrow => marker_attrs::Length::Narrow, - FieldLength::Wide => marker_attrs::Length::Wide, - _ => return Err(SingleLoadError::UnsupportedField(field)), + FieldLength::Three => marker_attrs::Length::Abbr, + FieldLength::Five => marker_attrs::Length::Narrow, + FieldLength::Four => marker_attrs::Length::Wide, + _ => return Err(PatternLoadError::UnsupportedLength(field)), }, ), locale, @@ -1727,8 +1694,8 @@ impl RawDateTimeNames { }; self.year_names .load_put(provider, req, variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; Ok(()) } @@ -1738,7 +1705,7 @@ impl RawDateTimeNames { locale: &DataLocale, field_symbol: fields::Month, field_length: FieldLength, - ) -> Result<(), SingleLoadError> + ) -> Result<(), PatternLoadError> where P: BoundDataProvider + ?Sized, { @@ -1755,10 +1722,10 @@ impl RawDateTimeNames { fields::Month::StandAlone => marker_attrs::Context::Standalone, }, match field_length { - FieldLength::Abbreviated => marker_attrs::Length::Abbr, - FieldLength::Narrow => marker_attrs::Length::Narrow, - FieldLength::Wide => marker_attrs::Length::Wide, - _ => return Err(SingleLoadError::UnsupportedField(field)), + FieldLength::Three => marker_attrs::Length::Abbr, + FieldLength::Five => marker_attrs::Length::Narrow, + FieldLength::Four => marker_attrs::Length::Wide, + _ => return Err(PatternLoadError::UnsupportedLength(field)), }, ), locale, @@ -1767,8 +1734,8 @@ impl RawDateTimeNames { }; self.month_names .load_put(provider, req, variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; Ok(()) } @@ -1777,7 +1744,7 @@ impl RawDateTimeNames { provider: &P, locale: &DataLocale, field_length: FieldLength, - ) -> Result<(), SingleLoadError> + ) -> Result<(), PatternLoadError> where P: BoundDataProvider + ?Sized, { @@ -1794,10 +1761,10 @@ impl RawDateTimeNames { marker_attrs::name_attr_for( marker_attrs::Context::Format, match field_length { - FieldLength::Abbreviated => marker_attrs::Length::Abbr, - FieldLength::Narrow => marker_attrs::Length::Narrow, - FieldLength::Wide => marker_attrs::Length::Wide, - _ => return Err(SingleLoadError::UnsupportedField(field)), + FieldLength::Three => marker_attrs::Length::Abbr, + FieldLength::Five => marker_attrs::Length::Narrow, + FieldLength::Four => marker_attrs::Length::Wide, + _ => return Err(PatternLoadError::UnsupportedLength(field)), }, ), locale, @@ -1806,8 +1773,8 @@ impl RawDateTimeNames { }; self.dayperiod_names .load_put(provider, req, variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; Ok(()) } @@ -1817,7 +1784,7 @@ impl RawDateTimeNames { locale: &DataLocale, field_symbol: fields::Weekday, field_length: FieldLength, - ) -> Result<(), SingleLoadError> + ) -> Result<(), PatternLoadError> where P: BoundDataProvider + ?Sized, { @@ -1844,11 +1811,11 @@ impl RawDateTimeNames { fields::Weekday::StandAlone => marker_attrs::Context::Standalone, }, match field_length { - FieldLength::Abbreviated => marker_attrs::Length::Abbr, - FieldLength::Narrow => marker_attrs::Length::Narrow, - FieldLength::Wide => marker_attrs::Length::Wide, + FieldLength::Three => marker_attrs::Length::Abbr, + FieldLength::Five => marker_attrs::Length::Narrow, + FieldLength::Four => marker_attrs::Length::Wide, FieldLength::Six => marker_attrs::Length::Short, - _ => return Err(SingleLoadError::UnsupportedField(field)), + _ => return Err(PatternLoadError::UnsupportedLength(field)), }, ), locale, @@ -1857,8 +1824,8 @@ impl RawDateTimeNames { }; self.weekday_names .load_put(provider, req, variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; Ok(()) } @@ -1866,13 +1833,13 @@ impl RawDateTimeNames { &mut self, provider: &P, locale: &DataLocale, - ) -> Result<(), SingleLoadError> + ) -> Result<(), PatternLoadError> where P: BoundDataProvider + ?Sized, { let field = fields::Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::UpperZ), - length: FieldLength::Wide, + symbol: FieldSymbol::TimeZone(fields::TimeZone::LocalizedOffset), + length: FieldLength::Four, }; let variables = (); let req = DataRequest { @@ -1881,8 +1848,8 @@ impl RawDateTimeNames { }; self.zone_essentials .load_put(provider, req, variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; Ok(()) } @@ -1890,13 +1857,13 @@ impl RawDateTimeNames { &mut self, provider: &P, locale: &DataLocale, - ) -> Result<(), SingleLoadError> + ) -> Result<(), PatternLoadError> where P: BoundDataProvider + ?Sized, { let field = fields::Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::UpperV), - length: FieldLength::Wide, + symbol: FieldSymbol::TimeZone(fields::TimeZone::Location), + length: FieldLength::Four, }; let variables = (); let req = DataRequest { @@ -1905,12 +1872,12 @@ impl RawDateTimeNames { }; self.locations_root .load_put(provider, Default::default(), variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; self.locations .load_put(provider, req, variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; Ok(()) } @@ -1919,10 +1886,10 @@ impl RawDateTimeNames { mz_generic_long_provider: &(impl BoundDataProvider + ?Sized), mz_period_provider: &(impl BoundDataProvider + ?Sized), locale: &DataLocale, - ) -> Result<(), SingleLoadError> { + ) -> Result<(), PatternLoadError> { let field = fields::Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV), - length: FieldLength::Wide, + symbol: FieldSymbol::TimeZone(fields::TimeZone::GenericNonLocation), + length: FieldLength::Four, }; let variables = (); let req = DataRequest { @@ -1931,12 +1898,12 @@ impl RawDateTimeNames { }; self.mz_generic_long .load_put(mz_generic_long_provider, req, variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; self.mz_periods .load_put(mz_period_provider, Default::default(), variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; Ok(()) } @@ -1945,9 +1912,9 @@ impl RawDateTimeNames { mz_generic_short_provider: &(impl BoundDataProvider + ?Sized), mz_period_provider: &(impl BoundDataProvider + ?Sized), locale: &DataLocale, - ) -> Result<(), SingleLoadError> { + ) -> Result<(), PatternLoadError> { let field = fields::Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV), + symbol: FieldSymbol::TimeZone(fields::TimeZone::GenericNonLocation), length: FieldLength::One, }; let variables = (); @@ -1957,12 +1924,12 @@ impl RawDateTimeNames { }; self.mz_generic_short .load_put(mz_generic_short_provider, req, variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; self.mz_periods .load_put(mz_period_provider, Default::default(), variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; Ok(()) } @@ -1971,10 +1938,10 @@ impl RawDateTimeNames { mz_specific_long_provider: &(impl BoundDataProvider + ?Sized), mz_period_provider: &(impl BoundDataProvider + ?Sized), locale: &DataLocale, - ) -> Result<(), SingleLoadError> { + ) -> Result<(), PatternLoadError> { let field = fields::Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerZ), - length: FieldLength::Wide, + symbol: FieldSymbol::TimeZone(fields::TimeZone::SpecificNonLocation), + length: FieldLength::Four, }; let variables = (); let req = DataRequest { @@ -1983,12 +1950,12 @@ impl RawDateTimeNames { }; self.mz_specific_long .load_put(mz_specific_long_provider, req, variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; self.mz_periods .load_put(mz_period_provider, Default::default(), variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; Ok(()) } @@ -1997,9 +1964,9 @@ impl RawDateTimeNames { mz_specific_short_provider: &(impl BoundDataProvider + ?Sized), mz_period_provider: &(impl BoundDataProvider + ?Sized), locale: &DataLocale, - ) -> Result<(), SingleLoadError> { + ) -> Result<(), PatternLoadError> { let field = fields::Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerZ), + symbol: FieldSymbol::TimeZone(fields::TimeZone::SpecificNonLocation), length: FieldLength::One, }; let variables = (); @@ -2009,12 +1976,12 @@ impl RawDateTimeNames { }; self.mz_specific_short .load_put(mz_specific_short_provider, req, variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; self.mz_periods .load_put(mz_period_provider, Default::default(), variables) - .map_err(|e| MaybePayloadError2::into_single_load_error(e, field))? - .map_err(SingleLoadError::Data)?; + .map_err(|e| MaybePayloadError2::into_load_error(e, field))? + .map_err(PatternLoadError::Data)?; Ok(()) } @@ -2066,170 +2033,186 @@ impl RawDateTimeNames { mz_period_provider: &(impl BoundDataProvider + ?Sized), fixed_decimal_formatter_loader: &impl FixedDecimalFormatterLoader, locale: &DataLocale, - pattern_items: impl Iterator, - ) -> Result<(), LoadError> { - let mut numeric_field = None; + pattern_items: impl Iterator, + ) -> Result<(), PatternLoadError> { + let mut load_fdf = false; + for item in pattern_items { - let item = match item { - FieldForDataLoading::Field(Field { - symbol: FieldSymbol::TimeZone(field_symbol), - length, - }) => { - match ResolvedNeoTimeZoneSkeleton::from_field(field_symbol, length) { - Some(time_zone) => FieldForDataLoading::TimeZone(time_zone), - None => { - // Unknown time zone field: ignore for data loading - continue; - } - } - } - _ => item, + let PatternItem::Field(field) = item else { + continue; }; - let field = match item { - FieldForDataLoading::Field(field) => field, - FieldForDataLoading::TimeZone(time_zone) => { - match time_zone { - // `z..zzz` - ResolvedNeoTimeZoneSkeleton::SpecificShort => { - self.load_time_zone_essentials(zone_essentials_provider, locale)?; - self.load_fixed_decimal_formatter( - fixed_decimal_formatter_loader, - locale, - ) - .map_err(LoadError::Data)?; - self.load_time_zone_specific_short_names( - mz_specific_short_provider, - mz_period_provider, - locale, - )?; - } - // `zzzz` - ResolvedNeoTimeZoneSkeleton::SpecificLong => { - self.load_time_zone_essentials(zone_essentials_provider, locale)?; - self.load_fixed_decimal_formatter( - fixed_decimal_formatter_loader, - locale, - ) - .map_err(LoadError::Data)?; - self.load_time_zone_specific_long_names( - mz_specific_long_provider, - mz_period_provider, - locale, - )?; - } - // 'v' - ResolvedNeoTimeZoneSkeleton::GenericShort => { - self.load_time_zone_essentials(zone_essentials_provider, locale)?; - self.load_fixed_decimal_formatter( - fixed_decimal_formatter_loader, - locale, - ) - .map_err(LoadError::Data)?; - self.load_time_zone_generic_short_names( - mz_generic_short_provider, - mz_period_provider, - locale, - )?; - // For fallback: - self.load_time_zone_location_names(locations_provider, locale)?; - } - // 'vvvv' - ResolvedNeoTimeZoneSkeleton::GenericLong => { - self.load_time_zone_essentials(zone_essentials_provider, locale)?; - self.load_fixed_decimal_formatter( - fixed_decimal_formatter_loader, - locale, - ) - .map_err(LoadError::Data)?; - self.load_time_zone_generic_long_names( - mz_generic_long_provider, - mz_period_provider, - locale, - )?; - // For fallback: - self.load_time_zone_location_names(locations_provider, locale)?; - } - // 'VVVV' (note: `V..VV` are for identifiers only) - ResolvedNeoTimeZoneSkeleton::Location => { - self.load_time_zone_essentials(zone_essentials_provider, locale)?; - self.load_fixed_decimal_formatter( - fixed_decimal_formatter_loader, - locale, - ) - .map_err(LoadError::Data)?; - self.load_time_zone_location_names(locations_provider, locale)?; - } - ResolvedNeoTimeZoneSkeleton::OffsetShort - | ResolvedNeoTimeZoneSkeleton::OffsetLong => { - self.load_time_zone_essentials(zone_essentials_provider, locale)?; - self.load_fixed_decimal_formatter( - fixed_decimal_formatter_loader, - locale, - ) - .map_err(LoadError::Data)?; - } - ResolvedNeoTimeZoneSkeleton::Isox - | ResolvedNeoTimeZoneSkeleton::Isoxx - | ResolvedNeoTimeZoneSkeleton::Isoxxx - | ResolvedNeoTimeZoneSkeleton::Isoxxxx - | ResolvedNeoTimeZoneSkeleton::Isoxxxxx - | ResolvedNeoTimeZoneSkeleton::IsoX - | ResolvedNeoTimeZoneSkeleton::IsoXX - | ResolvedNeoTimeZoneSkeleton::IsoXXX - | ResolvedNeoTimeZoneSkeleton::IsoXXXX - | ResolvedNeoTimeZoneSkeleton::IsoXXXXX - | ResolvedNeoTimeZoneSkeleton::Bcp47Id => { - // no data required - } - }; - continue; - } - }; - match field.symbol { + + use fields::*; + use FieldLength::*; + use FieldSymbol as FS; + + match (field.symbol, field.length) { ///// Textual symbols ///// - FieldSymbol::Era => { + + // G..GGGGG + (FS::Era, One | Two | Three | Four | Five) => { + self.load_year_names(year_provider, locale, field.length)?; + } + + // U..UUUUU + (FS::Year(Year::Cyclic), One | Two | Three | Four | Five) => { + load_fdf = true; self.load_year_names(year_provider, locale, field.length)?; } - FieldSymbol::Month(symbol) => match field.length { - FieldLength::One => numeric_field = Some(field), - FieldLength::TwoDigit => numeric_field = Some(field), - _ => { - self.load_month_names(month_provider, locale, symbol, field.length)?; - } - }, - // 'E' is always text - // 'e' and 'c' are either numeric or text - FieldSymbol::Weekday(symbol) => match field.length { - FieldLength::One | FieldLength::TwoDigit - if !matches!(symbol, fields::Weekday::Format) => - { - numeric_field = Some(field) - } - _ => { - self.load_weekday_names(weekday_provider, locale, symbol, field.length)?; - } - }, - FieldSymbol::DayPeriod(_) => { + + // MMM..MMMMM + (FS::Month(Month::Format), Three | Four | Five) => { + self.load_month_names(month_provider, locale, Month::Format, field.length)?; + } + + // LLL..LLLLL + (FS::Month(Month::StandAlone), Three | Four | Five) => { + self.load_month_names(month_provider, locale, Month::StandAlone, field.length)?; + } + + // E..EE + (FS::Weekday(Weekday::Format), One | Two) => { + self.load_weekday_names( + weekday_provider, + locale, + Weekday::Format, + field.length, + )?; + } + // EEE..EEEEEE, eee..eeeeee, ccc..cccccc + (FS::Weekday(symbol), Three | Four | Five | Six) => { + self.load_weekday_names(weekday_provider, locale, symbol, field.length)?; + } + + // a..aaaaa, b..bbbbb + (FS::DayPeriod(_), One | Two | Three | Four | Five) => { self.load_day_period_names(dayperiod_provider, locale, field.length)?; } - FieldSymbol::TimeZone(_) => { - debug_assert!(false, "handled above"); + + ///// Time zone symbols ///// + + // z..zzz + (FS::TimeZone(TimeZone::SpecificNonLocation), One | Two | Three) => { + load_fdf = true; + self.load_time_zone_essentials(zone_essentials_provider, locale)?; + self.load_time_zone_specific_short_names( + mz_specific_short_provider, + mz_period_provider, + locale, + )?; + } + // zzzz + (FS::TimeZone(TimeZone::SpecificNonLocation), Four) => { + load_fdf = true; + self.load_time_zone_essentials(zone_essentials_provider, locale)?; + self.load_time_zone_specific_long_names( + mz_specific_long_provider, + mz_period_provider, + locale, + )?; + self.load_time_zone_location_names(locations_provider, locale)?; + } + + // v + (FS::TimeZone(TimeZone::GenericNonLocation), One) => { + load_fdf = true; + self.load_time_zone_essentials(zone_essentials_provider, locale)?; + self.load_time_zone_generic_short_names( + mz_generic_short_provider, + mz_period_provider, + locale, + )?; + // For fallback: + self.load_time_zone_location_names(locations_provider, locale)?; + } + // vvvv + (FS::TimeZone(TimeZone::GenericNonLocation), Four) => { + load_fdf = true; + self.load_time_zone_essentials(zone_essentials_provider, locale)?; + self.load_time_zone_generic_long_names( + mz_generic_long_provider, + mz_period_provider, + locale, + )?; + // For fallback: + self.load_time_zone_location_names(locations_provider, locale)?; + } + + // V + (FS::TimeZone(TimeZone::Location), One) => { + // no data required + } + // VVVV + (FS::TimeZone(TimeZone::Location), Four) => { + load_fdf = true; + self.load_time_zone_essentials(zone_essentials_provider, locale)?; + self.load_time_zone_location_names(locations_provider, locale)?; + } + + // O, OOOO + (FS::TimeZone(TimeZone::LocalizedOffset), One | Four) => { + self.load_time_zone_essentials(zone_essentials_provider, locale)?; + load_fdf = true; + } + + // X..XXXXX, x..xxxxx + ( + FS::TimeZone(TimeZone::IsoWithZ | TimeZone::Iso), + One | Two | Three | Four | Five, + ) => { + // no data required } ///// Numeric symbols ///// - FieldSymbol::Year(_) => numeric_field = Some(field), - FieldSymbol::Week(_) => numeric_field = Some(field), - FieldSymbol::Day(_) => numeric_field = Some(field), - FieldSymbol::Hour(_) => numeric_field = Some(field), - FieldSymbol::Minute => numeric_field = Some(field), - FieldSymbol::Second(_) => numeric_field = Some(field), - FieldSymbol::DecimalSecond(_) => numeric_field = Some(field), - }; + + // y+ + (FS::Year(Year::Calendar), _) => load_fdf = true, + // r+ + (FS::Year(Year::RelatedIso), _) => { + // always formats as ASCII + } + + // M..MM, L..LL + (FS::Month(_), One | Two) => load_fdf = true, + + // e..ee, c..cc + (FS::Weekday(Weekday::Local | Weekday::StandAlone), One | Two) => { + // TODO(#5643): Requires locale-aware day-of-week calculation + return Err(PatternLoadError::UnsupportedLength(field)); + } + + // d..dd + (FS::Day(Day::DayOfMonth), One | Two) => load_fdf = true, + // D..DDD + (FS::Day(Day::DayOfYear), One | Two | Three) => load_fdf = true, + // F + (FS::Day(Day::DayOfWeekInMonth), One) => load_fdf = true, + + // K..KK, h..hh, H..HH, k..kk + (FS::Hour(_), One | Two) => load_fdf = true, + + // m..mm + (FS::Minute, One | Two) => load_fdf = true, + + // s..ss + (FS::Second(Second::Second), One | Two) => load_fdf = true, + + // A+ + (FS::Second(Second::MillisInDay), _) => load_fdf = true, + + // s.S+, ss.S+ (s is modelled by length, S+ by symbol) + (FS::DecimalSecond(_), One | Two) => load_fdf = true, + + ///// Unsupported symbols ///// + _ => { + return Err(PatternLoadError::UnsupportedLength(field)); + } + } } - if numeric_field.is_some() { + if load_fdf { self.load_fixed_decimal_formatter(fixed_decimal_formatter_loader, locale) - .map_err(LoadError::Data)?; + .map_err(PatternLoadError::Data)?; } Ok(()) @@ -2287,9 +2270,9 @@ where /// let mut names: TypedDateTimeNames = /// TypedDateTimeNames::try_new(&locale!("en-GB").into()).unwrap(); /// names - /// .include_month_names(fields::Month::Format, FieldLength::Wide) + /// .include_month_names(fields::Month::Format, FieldLength::Four) /// .unwrap() - /// .include_year_names(FieldLength::Wide) + /// .include_year_names(FieldLength::Four) /// .unwrap(); /// /// // Create a pattern from a pattern string: @@ -2317,7 +2300,7 @@ where + GetField<::MonthInput> + GetField<::DayOfMonthInput> + GetField<::DayOfWeekInput> - + GetField<::AnyCalendarKindInput> + + GetField<::DayOfYearInput> + GetField<()>, { FormattedDateTimePattern { @@ -2346,7 +2329,7 @@ where /// let mut names: TypedDateTimeNames = /// TypedDateTimeNames::try_new(&locale!("en-US").into()).unwrap(); /// names - /// .include_day_period_names(FieldLength::Abbreviated) + /// .include_day_period_names(FieldLength::Three) /// .unwrap(); /// /// // Create a pattern from a pattern string: @@ -2401,7 +2384,7 @@ where /// ``` /// use icu::calendar::Gregorian; /// use icu::datetime::neo_pattern::DateTimePattern; - /// use icu::datetime::neo_skeleton::NeoTimeZoneSkeleton; + /// use icu::datetime::fieldset::dynamic::ZoneFieldSet; /// use icu::datetime::TypedDateTimeNames; /// use icu::locale::locale; /// use icu::timezone::IxdtfParser; @@ -2419,7 +2402,7 @@ where /// .to_calendar(Gregorian); /// /// let mut names = - /// TypedDateTimeNames::::try_new( + /// TypedDateTimeNames::::try_new( /// &locale!("en-GB").into(), /// ) /// .unwrap(); @@ -2494,19 +2477,15 @@ impl<'data> RawDateTimeNamesBorrowed<'data> { field_length: FieldLength, code: MonthCode, ) -> Result { - let field = fields::Field { - symbol: FieldSymbol::Month(field_symbol), - length: field_length, - }; let month_names = self .month_names .get_with_variables((field_symbol, field_length)) - .ok_or(GetNameForMonthError::MissingNames(field))?; + .ok_or(GetNameForMonthError::NotLoaded)?; let Some((month_number, is_leap)) = code.parsed() else { - return Err(GetNameForMonthError::Missing); + return Err(GetNameForMonthError::Invalid); }; let Some(month_index) = month_number.checked_sub(1) else { - return Err(GetNameForMonthError::Missing); + return Err(GetNameForMonthError::Invalid); }; let month_index = usize::from(month_index); let name = match month_names { @@ -2538,7 +2517,7 @@ impl<'data> RawDateTimeNamesBorrowed<'data> { // Note: Always return `false` for the second argument since neo MonthNames // knows how to handle leap months and we don't need the fallback logic name.map(MonthPlaceholderValue::PlainString) - .ok_or(GetNameForMonthError::Missing) + .ok_or(GetNameForMonthError::Invalid) } pub(crate) fn get_name_for_weekday( @@ -2547,10 +2526,6 @@ impl<'data> RawDateTimeNamesBorrowed<'data> { field_length: FieldLength, day: input::IsoWeekday, ) -> Result<&str, GetNameForWeekdayError> { - let field = fields::Field { - symbol: FieldSymbol::Weekday(field_symbol), - length: field_length, - }; // UTS 35 says that "e" and "E" have the same non-numeric names let field_symbol = field_symbol.to_format_symbol(); // UTS 35 says that "E..EEE" are all Abbreviated @@ -2563,11 +2538,12 @@ impl<'data> RawDateTimeNamesBorrowed<'data> { let weekday_names = self .weekday_names .get_with_variables((field_symbol, field_length)) - .ok_or(GetNameForWeekdayError::MissingNames(field))?; + .ok_or(GetNameForWeekdayError::NotLoaded)?; weekday_names .names .get((day as usize) % 7) - .ok_or(GetNameForWeekdayError::Missing) + // TODO: make weekday_names length 7 in the type system + .ok_or(GetNameForWeekdayError::NotLoaded) } /// Gets the era symbol, or `None` if data is loaded but symbol isn't found. @@ -2579,32 +2555,49 @@ impl<'data> RawDateTimeNamesBorrowed<'data> { field_length: FieldLength, era: FormattingEra, ) -> Result<&str, GetSymbolForEraError> { - let field = fields::Field { - symbol: FieldSymbol::Era, - length: field_length, - }; // UTS 35 says that "G..GGG" are all Abbreviated let field_length = field_length.numeric_to_abbr(); let year_names = self .year_names .get_with_variables(field_length) - .ok_or(GetSymbolForEraError::MissingNames(field))?; + .ok_or(GetSymbolForEraError::NotLoaded)?; match (year_names, era) { (YearNamesV1::VariableEras(era_names), FormattingEra::Code(era_code)) => era_names .get(era_code.0.as_str().into()) - .ok_or(GetSymbolForEraError::Missing), + .ok_or(GetSymbolForEraError::Invalid), (YearNamesV1::FixedEras(era_names), FormattingEra::Index(index, _fallback)) => { era_names .get(index.into()) - .ok_or(GetSymbolForEraError::Missing) + .ok_or(GetSymbolForEraError::Invalid) } - _ => Err(GetSymbolForEraError::Missing), + _ => Err(GetSymbolForEraError::Invalid), } } -} -impl RawDateTimeNamesBorrowed<'_> { + pub(crate) fn get_name_for_cyclic( + &self, + field_length: FieldLength, + cyclic: NonZeroU8, + ) -> Result<&str, GetSymbolForCyclicYearError> { + // UTS 35 says that "U..UUU" are all Abbreviated + let field_length = field_length.numeric_to_abbr(); + let year_names = self + .year_names + .get_with_variables(field_length) + .ok_or(GetSymbolForCyclicYearError::NotLoaded)?; + + let YearNamesV1::Cyclic(cyclics) = year_names else { + return Err(GetSymbolForCyclicYearError::Invalid { max: 0 }); + }; + + cyclics + .get((cyclic.get() as usize) - 1) + .ok_or(GetSymbolForCyclicYearError::Invalid { + max: cyclics.len() + 1, + }) + } + pub(crate) fn get_name_for_day_period( &self, field_symbol: fields::DayPeriod, @@ -2613,28 +2606,45 @@ impl RawDateTimeNamesBorrowed<'_> { is_top_of_hour: bool, ) -> Result<&str, GetNameForDayPeriodError> { use fields::DayPeriod::NoonMidnight; - let field = fields::Field { - symbol: FieldSymbol::DayPeriod(field_symbol), - length: field_length, - }; // UTS 35 says that "a..aaa" are all Abbreviated let field_length = field_length.numeric_to_abbr(); let dayperiod_names = self .dayperiod_names .get_with_variables(field_length) - .ok_or(GetNameForDayPeriodError::MissingNames(field))?; + .ok_or(GetNameForDayPeriodError::NotLoaded)?; let option_value: Option<&str> = match (field_symbol, u8::from(hour), is_top_of_hour) { (NoonMidnight, 00, true) => dayperiod_names.midnight().or_else(|| dayperiod_names.am()), (NoonMidnight, 12, true) => dayperiod_names.noon().or_else(|| dayperiod_names.pm()), (_, hour, _) if hour < 12 => dayperiod_names.am(), _ => dayperiod_names.pm(), }; - option_value.ok_or(GetNameForDayPeriodError::MissingNames(field)) + option_value.ok_or(GetNameForDayPeriodError::NotLoaded) } } +/// A container contains all data payloads for time zone formatting (borrowed version). +#[derive(Debug, Copy, Clone, Default)] +pub(crate) struct TimeZoneDataPayloadsBorrowed<'a> { + /// The data that contains meta information about how to display content. + pub(crate) essentials: Option<&'a tz::EssentialsV1<'a>>, + /// The root location names, e.g. Toronto + pub(crate) locations_root: Option<&'a tz::LocationsV1<'a>>, + /// The language specific location names, e.g. Italy + pub(crate) locations: Option<&'a tz::LocationsV1<'a>>, + /// The generic long metazone names, e.g. Pacific Time + pub(crate) mz_generic_long: Option<&'a tz::MzGenericV1<'a>>, + /// The generic short metazone names, e.g. PT + pub(crate) mz_generic_short: Option<&'a tz::MzGenericV1<'a>>, + /// The specific long metazone names, e.g. Pacific Daylight Time + pub(crate) mz_specific_long: Option<&'a tz::MzSpecificV1<'a>>, + /// The specific short metazone names, e.g. Pacific Daylight Time + pub(crate) mz_specific_short: Option<&'a tz::MzSpecificV1<'a>>, + /// The metazone lookup + pub(crate) mz_periods: Option<&'a tz::MzPeriodV1<'a>>, +} + impl<'data> RawDateTimeNamesBorrowed<'data> { - pub(crate) fn get_payloads(&self) -> crate::time_zone::TimeZoneDataPayloadsBorrowed<'data> { + pub(crate) fn get_payloads(&self) -> TimeZoneDataPayloadsBorrowed<'data> { TimeZoneDataPayloadsBorrowed { essentials: self.zone_essentials.get_option(), locations_root: self.locations_root.get_option(), @@ -2665,18 +2675,18 @@ mod tests { .load_month_names( &crate::provider::Baked, fields::Month::Format, - fields::FieldLength::Wide, + fields::FieldLength::Four, ) .unwrap() .load_weekday_names( &crate::provider::Baked, fields::Weekday::Format, - fields::FieldLength::Abbreviated, + fields::FieldLength::Three, ) .unwrap() - .load_year_names(&crate::provider::Baked, FieldLength::Narrow) + .load_year_names(&crate::provider::Baked, FieldLength::Five) .unwrap() - .load_day_period_names(&crate::provider::Baked, FieldLength::Abbreviated) + .load_day_period_names(&crate::provider::Baked, FieldLength::Three) .unwrap(); let pattern: DateTimePattern = "'It is' E, MMMM d, y GGGGG 'at' hh:mm a'!'" .parse() @@ -2703,27 +2713,27 @@ mod tests { let cases = [ TestCase { pattern: "", - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "<н. е.>", }, TestCase { pattern: "", - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "<н. е.>", }, TestCase { pattern: "", - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "<н. е.>", }, TestCase { pattern: "", - field_length: FieldLength::Wide, + field_length: FieldLength::Four, expected: "<нашої ери>", }, TestCase { pattern: "", - field_length: FieldLength::Narrow, + field_length: FieldLength::Five, expected: "<н.е.>", }, ]; @@ -2762,38 +2772,38 @@ mod tests { TestCase { pattern: "", field_symbol: fields::Month::Format, - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "<лист.>", }, TestCase { pattern: "", field_symbol: fields::Month::Format, - field_length: FieldLength::Wide, + field_length: FieldLength::Four, expected: "<листопада>", }, TestCase { pattern: "", field_symbol: fields::Month::Format, - field_length: FieldLength::Narrow, + field_length: FieldLength::Five, expected: "<л>", }, // 'L' and 'LL' are numeric TestCase { pattern: "", field_symbol: fields::Month::StandAlone, - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "<лист.>", }, TestCase { pattern: "", field_symbol: fields::Month::StandAlone, - field_length: FieldLength::Wide, + field_length: FieldLength::Four, expected: "<листопад>", }, TestCase { pattern: "", field_symbol: fields::Month::StandAlone, - field_length: FieldLength::Narrow, + field_length: FieldLength::Five, expected: "<Л>", }, ]; @@ -2831,31 +2841,31 @@ mod tests { TestCase { pattern: "", field_symbol: fields::Weekday::Format, - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "<пт>", }, TestCase { pattern: "", field_symbol: fields::Weekday::Format, - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "<пт>", }, TestCase { pattern: "", field_symbol: fields::Weekday::Format, - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "<пт>", }, TestCase { pattern: "", field_symbol: fields::Weekday::Format, - field_length: FieldLength::Wide, + field_length: FieldLength::Four, expected: "<пʼятниця>", }, TestCase { pattern: "", field_symbol: fields::Weekday::Format, - field_length: FieldLength::Narrow, + field_length: FieldLength::Five, expected: "<П>", }, TestCase { @@ -2868,19 +2878,19 @@ mod tests { TestCase { pattern: "", field_symbol: fields::Weekday::Format, - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "<пт>", }, TestCase { pattern: "", field_symbol: fields::Weekday::Format, - field_length: FieldLength::Wide, + field_length: FieldLength::Four, expected: "<пʼятниця>", }, TestCase { pattern: "", field_symbol: fields::Weekday::Format, - field_length: FieldLength::Narrow, + field_length: FieldLength::Five, expected: "<П>", }, TestCase { @@ -2893,19 +2903,19 @@ mod tests { TestCase { pattern: "", field_symbol: fields::Weekday::StandAlone, - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "<пт>", }, TestCase { pattern: "", field_symbol: fields::Weekday::StandAlone, - field_length: FieldLength::Wide, + field_length: FieldLength::Four, expected: "<пʼятниця>", }, TestCase { pattern: "", field_symbol: fields::Weekday::StandAlone, - field_length: FieldLength::Narrow, + field_length: FieldLength::Five, expected: "<П>", }, TestCase { @@ -2949,52 +2959,52 @@ mod tests { let cases = [ TestCase { pattern: "", - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "", }, TestCase { pattern: "", - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "", }, TestCase { pattern: "", - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "", }, TestCase { pattern: "", - field_length: FieldLength::Wide, + field_length: FieldLength::Four, expected: "<หลังเที่ยง>", }, TestCase { pattern: "", - field_length: FieldLength::Narrow, + field_length: FieldLength::Five, expected: "

", }, TestCase { pattern: "", - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "", }, TestCase { pattern: "", - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "", }, TestCase { pattern: "", - field_length: FieldLength::Abbreviated, + field_length: FieldLength::Three, expected: "", }, TestCase { pattern: "", - field_length: FieldLength::Wide, + field_length: FieldLength::Four, expected: "<หลังเที่ยง>", }, TestCase { pattern: "", - field_length: FieldLength::Narrow, + field_length: FieldLength::Five, expected: "

", }, ]; diff --git a/components/datetime/src/format/time_zone.rs b/components/datetime/src/format/time_zone.rs new file mode 100644 index 00000000000..ed76adffb61 --- /dev/null +++ b/components/datetime/src/format/time_zone.rs @@ -0,0 +1,630 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +//! A formatter specifically for the time zone. + +use super::neo::TimeZoneDataPayloadsBorrowed; +use crate::provider::time_zones::MetazoneId; +use crate::{fields::FieldLength, input::ExtractedInput}; +use core::fmt; +use fixed_decimal::FixedDecimal; +use icu_calendar::{Date, Iso, Time}; +use icu_decimal::FixedDecimalFormatter; +use icu_timezone::provider::EPOCH; +use icu_timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant}; +use writeable::Writeable; + +impl crate::provider::time_zones::MetazonePeriodV1<'_> { + fn resolve( + &self, + time_zone_id: TimeZoneBcp47Id, + (date, time): (Date, Time), + ) -> Option { + let cursor = self.0.get0(&time_zone_id)?; + let mut metazone_id = None; + let minutes_since_epoch_walltime = (date.to_fixed() - EPOCH) as i32 * 24 * 60 + + (time.hour.number() as i32 * 60 + time.minute.number() as i32); + for (minutes, id) in cursor.iter1() { + if minutes_since_epoch_walltime + >= ::from_unaligned(*minutes) + { + metazone_id = id.get() + } else { + break; + } + } + metazone_id + } +} + +// An enum for time zone format unit. +#[derive(Debug, Clone, Copy, PartialEq)] +pub(super) enum TimeZoneFormatterUnit { + GenericNonLocation(FieldLength), + SpecificNonLocation(FieldLength), + GenericLocation, + SpecificLocation, + #[allow(dead_code)] + GenericPartialLocation(FieldLength), + LocalizedOffset(FieldLength), + Iso8601(Iso8601Format), + Bcp47Id, +} + +#[derive(Debug)] +pub(super) enum FormatTimeZoneError { + NamesNotLoaded, + FixedDecimalFormatterNotLoaded, + Fallback, + MissingInputField(&'static str), +} + +pub(super) trait FormatTimeZone { + /// Tries to write the timezone to the sink. If a DateTimeError is returned, the sink + /// has not been touched, so another format can be attempted. + fn format( + &self, + sink: &mut W, + input: &ExtractedInput, + data_payloads: TimeZoneDataPayloadsBorrowed, + fdf: Option<&FixedDecimalFormatter>, + ) -> Result, fmt::Error>; +} + +impl FormatTimeZone for TimeZoneFormatterUnit { + fn format( + &self, + sink: &mut W, + input: &ExtractedInput, + data_payloads: TimeZoneDataPayloadsBorrowed, + fdf: Option<&FixedDecimalFormatter>, + ) -> Result, fmt::Error> { + match *self { + Self::GenericNonLocation(length) => { + GenericNonLocationFormat(length).format(sink, input, data_payloads, fdf) + } + Self::SpecificNonLocation(length) => { + SpecificNonLocationFormat(length).format(sink, input, data_payloads, fdf) + } + Self::GenericLocation => GenericLocationFormat.format(sink, input, data_payloads, fdf), + Self::SpecificLocation => { + SpecificLocationFormat.format(sink, input, data_payloads, fdf) + } + Self::GenericPartialLocation(length) => { + GenericPartialLocationFormat(length).format(sink, input, data_payloads, fdf) + } + Self::LocalizedOffset(length) => { + LocalizedOffsetFormat(length).format(sink, input, data_payloads, fdf) + } + Self::Iso8601(iso) => iso.format(sink, input, data_payloads, fdf), + Self::Bcp47Id => Bcp47IdFormat.format(sink, input, data_payloads, fdf), + } + } +} + +// PT / Pacific Time +struct GenericNonLocationFormat(FieldLength); + +impl FormatTimeZone for GenericNonLocationFormat { + /// Writes the time zone in generic non-location format as defined by the UTS-35 spec. + /// + fn format( + &self, + sink: &mut W, + input: &ExtractedInput, + data_payloads: TimeZoneDataPayloadsBorrowed, + _fdf: Option<&FixedDecimalFormatter>, + ) -> Result, fmt::Error> { + let Some(time_zone_id) = input.time_zone_id else { + return Ok(Err(FormatTimeZoneError::MissingInputField("time_zone_id"))); + }; + let Some(local_time) = input.local_time else { + return Ok(Err(FormatTimeZoneError::MissingInputField("local_time"))); + }; + let Some(names) = (match self.0 { + FieldLength::Four => data_payloads.mz_generic_long.as_ref(), + _ => data_payloads.mz_generic_short.as_ref(), + }) else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + let Some(metazone_period) = data_payloads.mz_periods else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + + let Some(name) = names.overrides.get(&time_zone_id).or_else(|| { + names + .defaults + .get(&metazone_period.resolve(time_zone_id, local_time)?) + }) else { + return Ok(Err(FormatTimeZoneError::Fallback)); + }; + + sink.write_str(name)?; + + Ok(Ok(())) + } +} + +// PDT / Pacific Daylight Time +struct SpecificNonLocationFormat(FieldLength); + +impl FormatTimeZone for SpecificNonLocationFormat { + /// Writes the time zone in short specific non-location format as defined by the UTS-35 spec. + /// + fn format( + &self, + sink: &mut W, + input: &ExtractedInput, + data_payloads: TimeZoneDataPayloadsBorrowed, + _fdf: Option<&FixedDecimalFormatter>, + ) -> Result, fmt::Error> { + let Some(time_zone_id) = input.time_zone_id else { + return Ok(Err(FormatTimeZoneError::MissingInputField("time_zone_id"))); + }; + let Some(zone_variant) = input.zone_variant else { + return Ok(Err(FormatTimeZoneError::MissingInputField("zone_variant"))); + }; + let Some(local_time) = input.local_time else { + return Ok(Err(FormatTimeZoneError::MissingInputField("local_time"))); + }; + + let Some(names) = (match self.0 { + FieldLength::Four => data_payloads.mz_specific_long.as_ref(), + _ => data_payloads.mz_specific_short.as_ref(), + }) else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + let Some(metazone_period) = data_payloads.mz_periods else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + + let Some(name) = names + .overrides + .get(&(time_zone_id, zone_variant)) + .or_else(|| { + names.defaults.get(&( + metazone_period.resolve(time_zone_id, local_time)?, + zone_variant, + )) + }) + else { + return Ok(Err(FormatTimeZoneError::Fallback)); + }; + + sink.write_str(name)?; + + Ok(Ok(())) + } +} + +// UTC+7:00 +struct LocalizedOffsetFormat(FieldLength); + +impl FormatTimeZone for LocalizedOffsetFormat { + /// Writes the time zone in localized offset format according to the CLDR localized hour format. + /// This goes explicitly against the UTS-35 spec, which specifies long or short localized + /// offset formats regardless of locale. + /// + /// You can see more information about our decision to resolve this conflict here: + /// + fn format( + &self, + sink: &mut W, + input: &ExtractedInput, + data_payloads: TimeZoneDataPayloadsBorrowed, + fdf: Option<&FixedDecimalFormatter>, + ) -> Result, fmt::Error> { + let Some(essentials) = data_payloads.essentials else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + let Some(fdf) = fdf else { + return Ok(Err(FormatTimeZoneError::FixedDecimalFormatterNotLoaded)); + }; + let Some(offset) = input.offset else { + sink.write_str(&essentials.offset_unknown)?; + return Ok(Ok(())); + }; + Ok(if offset.is_zero() { + sink.write_str(&essentials.offset_zero)?; + Ok(()) + } else { + struct FormattedOffset<'a> { + offset: UtcOffset, + separator: &'a str, + fdf: &'a FixedDecimalFormatter, + length: FieldLength, + } + + impl Writeable for FormattedOffset<'_> { + fn write_to_parts( + &self, + sink: &mut S, + ) -> fmt::Result { + self.fdf + .format( + &FixedDecimal::from(self.offset.hours_part()) + .with_sign_display(fixed_decimal::SignDisplay::Always) + .padded_start(if self.length == FieldLength::Four { + 2 + } else { + 0 + }), + ) + .write_to(sink)?; + + if self.length == FieldLength::Four + || self.offset.minutes_part() != 0 + || self.offset.seconds_part() != 0 + { + sink.write_str(self.separator)?; + self.fdf + .format(&FixedDecimal::from(self.offset.minutes_part()).padded_start(2)) + .write_to(sink)?; + } + + if self.offset.seconds_part() != 0 { + sink.write_str(self.separator)?; + self.fdf + .format(&FixedDecimal::from(self.offset.seconds_part()).padded_start(2)) + .write_to(sink)?; + } + + Ok(()) + } + } + + essentials + .offset_pattern + .interpolate([FormattedOffset { + offset, + separator: &essentials.offset_separator, + fdf, + length: self.0, + }]) + .write_to(sink)?; + + Ok(()) + }) + } +} + +// Los Angeles Time +struct GenericLocationFormat; + +impl FormatTimeZone for GenericLocationFormat { + /// Writes the time zone in generic location format as defined by the UTS-35 spec. + /// e.g. France Time + /// + fn format( + &self, + sink: &mut W, + input: &ExtractedInput, + data_payloads: TimeZoneDataPayloadsBorrowed, + _fdf: Option<&FixedDecimalFormatter>, + ) -> Result, fmt::Error> { + let Some(time_zone_id) = input.time_zone_id else { + return Ok(Err(FormatTimeZoneError::MissingInputField("time_zone_id"))); + }; + + let Some(locations) = data_payloads.locations else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + + let Some(locations_root) = data_payloads.locations_root else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + + let Some(location) = locations + .locations + .get(&time_zone_id) + .or_else(|| locations_root.locations.get(&time_zone_id)) + else { + return Ok(Err(FormatTimeZoneError::Fallback)); + }; + + locations + .pattern_generic + .interpolate([location]) + .write_to(sink)?; + + Ok(Ok(())) + } +} + +// Los Angeles Daylight Time +struct SpecificLocationFormat; + +impl FormatTimeZone for SpecificLocationFormat { + /// Writes the time zone in a specific location format as defined by the UTS-35 spec. + /// e.g. France Time + /// + fn format( + &self, + sink: &mut W, + input: &ExtractedInput, + data_payloads: TimeZoneDataPayloadsBorrowed, + _fdf: Option<&FixedDecimalFormatter>, + ) -> Result, fmt::Error> { + let Some(time_zone_id) = input.time_zone_id else { + return Ok(Err(FormatTimeZoneError::MissingInputField("time_zone_id"))); + }; + let Some(zone_variant) = input.zone_variant else { + return Ok(Err(FormatTimeZoneError::MissingInputField("zone_variant"))); + }; + let Some(locations) = data_payloads.locations else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + let Some(locations_root) = data_payloads.locations_root else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + + let Some(location) = locations + .locations + .get(&time_zone_id) + .or_else(|| locations_root.locations.get(&time_zone_id)) + else { + return Ok(Err(FormatTimeZoneError::Fallback)); + }; + + match zone_variant { + ZoneVariant::Standard => &locations.pattern_standard, + ZoneVariant::Daylight => &locations.pattern_daylight, + // Compiles out due to tilde dependency on `icu_timezone` + _ => unreachable!(), + } + .interpolate([location]) + .write_to(sink)?; + + Ok(Ok(())) + } +} + +// Pacific Time (Los Angeles) / PT (Los Angeles) +struct GenericPartialLocationFormat(FieldLength); + +impl FormatTimeZone for GenericPartialLocationFormat { + /// Writes the time zone in a long generic partial location format as defined by the UTS-35 spec. + /// + fn format( + &self, + sink: &mut W, + input: &ExtractedInput, + data_payloads: TimeZoneDataPayloadsBorrowed, + _fdf: Option<&FixedDecimalFormatter>, + ) -> Result, fmt::Error> { + let Some(time_zone_id) = input.time_zone_id else { + return Ok(Err(FormatTimeZoneError::MissingInputField("time_zone_id"))); + }; + let Some(local_time) = input.local_time else { + return Ok(Err(FormatTimeZoneError::MissingInputField("local_time"))); + }; + + let Some(locations) = data_payloads.locations else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + let Some(locations_root) = data_payloads.locations_root else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + let Some(non_locations) = (match self.0 { + FieldLength::Four => data_payloads.mz_generic_long.as_ref(), + _ => data_payloads.mz_generic_short.as_ref(), + }) else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + let Some(metazone_period) = data_payloads.mz_periods else { + return Ok(Err(FormatTimeZoneError::NamesNotLoaded)); + }; + let Some(location) = locations + .locations + .get(&time_zone_id) + .or_else(|| locations_root.locations.get(&time_zone_id)) + else { + return Ok(Err(FormatTimeZoneError::Fallback)); + }; + let Some(non_location) = non_locations.overrides.get(&time_zone_id).or_else(|| { + non_locations + .defaults + .get(&metazone_period.resolve(time_zone_id, local_time)?) + }) else { + return Ok(Err(FormatTimeZoneError::Fallback)); + }; + + locations + .pattern_partial_location + .interpolate((location, non_location)) + .write_to(sink)?; + + Ok(Ok(())) + } +} + +/// Whether the minutes field should be optional or required in ISO-8601 format. +#[derive(Debug, Clone, Copy, PartialEq)] +enum IsoMinutes { + /// Minutes are always displayed. + Required, + + /// Minutes are displayed only if they are non-zero. + Optional, +} + +/// Whether the seconds field should be optional or excluded in ISO-8601 format. +#[derive(Debug, Clone, Copy, PartialEq)] +enum IsoSeconds { + /// Seconds are displayed only if they are non-zero. + Optional, + + /// Seconds are not displayed. + Never, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct Iso8601Format { + // 1000 vs 10:00 + extended: bool, + // 00:00 vs Z + z: bool, + minutes: IsoMinutes, + seconds: IsoSeconds, +} + +impl Iso8601Format { + pub(crate) fn with_z(length: FieldLength) -> Self { + match length { + FieldLength::One => Self { + extended: false, + z: true, + minutes: IsoMinutes::Optional, + seconds: IsoSeconds::Never, + }, + FieldLength::Two => Self { + extended: false, + z: true, + minutes: IsoMinutes::Required, + seconds: IsoSeconds::Never, + }, + FieldLength::Three => Self { + extended: true, + z: true, + minutes: IsoMinutes::Required, + seconds: IsoSeconds::Never, + }, + FieldLength::Four => Self { + extended: false, + z: true, + minutes: IsoMinutes::Required, + seconds: IsoSeconds::Optional, + }, + _ => Self { + extended: true, + z: true, + minutes: IsoMinutes::Required, + seconds: IsoSeconds::Optional, + }, + } + } + + pub(crate) fn without_z(length: FieldLength) -> Self { + match length { + FieldLength::One => Self { + extended: false, + z: false, + minutes: IsoMinutes::Optional, + seconds: IsoSeconds::Never, + }, + FieldLength::Two => Self { + extended: false, + z: false, + minutes: IsoMinutes::Required, + seconds: IsoSeconds::Never, + }, + FieldLength::Three => Self { + extended: true, + z: false, + minutes: IsoMinutes::Required, + seconds: IsoSeconds::Never, + }, + FieldLength::Four => Self { + extended: false, + z: false, + minutes: IsoMinutes::Required, + seconds: IsoSeconds::Optional, + }, + _ => Self { + extended: true, + z: false, + minutes: IsoMinutes::Required, + seconds: IsoSeconds::Optional, + }, + } + } +} + +impl FormatTimeZone for Iso8601Format { + /// Writes a [`UtcOffset`](crate::input::UtcOffset) in ISO-8601 format according to the + /// given formatting options. + /// + /// [`IsoFormat`] determines whether the format should be Basic or Extended, + /// and whether a zero-offset should be formatted numerically or with + /// The UTC indicator: "Z" + /// - Basic e.g. +0800 + /// - Extended e.g. +08:00 + /// + /// [`IsoMinutes`] can be required or optional. + /// [`IsoSeconds`] can be optional or never. + fn format( + &self, + sink: &mut W, + input: &ExtractedInput, + _data_payloads: TimeZoneDataPayloadsBorrowed, + _fdf: Option<&FixedDecimalFormatter>, + ) -> Result, fmt::Error> { + let Some(offset) = input.offset else { + sink.write_str("+?")?; + return Ok(Ok(())); + }; + self.format_infallible(sink, offset).map(|()| Ok(())) + } +} + +impl Iso8601Format { + pub(crate) fn format_infallible( + &self, + sink: &mut W, + offset: UtcOffset, + ) -> Result<(), fmt::Error> { + if offset.is_zero() && self.z { + return sink.write_char('Z'); + } + + // Always in latin digits according to spec + FixedDecimal::from(offset.hours_part()) + .padded_start(2) + .with_sign_display(fixed_decimal::SignDisplay::Always) + .write_to(sink)?; + + if self.minutes == IsoMinutes::Required + || (self.minutes == IsoMinutes::Optional && offset.minutes_part() != 0) + { + if self.extended { + sink.write_char(':')?; + } + FixedDecimal::from(offset.minutes_part()) + .padded_start(2) + .write_to(sink)?; + } + + if self.seconds == IsoSeconds::Optional && offset.seconds_part() != 0 { + if self.extended { + sink.write_char(':')?; + } + FixedDecimal::from(offset.seconds_part()) + .padded_start(2) + .write_to(sink)?; + } + + Ok(()) + } +} + +// It is only used for pattern in special case and not public to users. +struct Bcp47IdFormat; + +impl FormatTimeZone for Bcp47IdFormat { + fn format( + &self, + sink: &mut W, + input: &ExtractedInput, + _data_payloads: TimeZoneDataPayloadsBorrowed, + _fdf: Option<&FixedDecimalFormatter>, + ) -> Result, fmt::Error> { + let time_zone_id = input + .time_zone_id + .unwrap_or(TimeZoneBcp47Id(tinystr::tinystr!(8, "unk"))); + + sink.write_str(&time_zone_id)?; + + Ok(Ok(())) + } +} diff --git a/components/datetime/src/helpers.rs b/components/datetime/src/helpers.rs deleted file mode 100644 index bf66e9b6299..00000000000 --- a/components/datetime/src/helpers.rs +++ /dev/null @@ -1,85 +0,0 @@ -// This file is part of ICU4X. For terms of use, please see the file -// called LICENSE at the top level of the ICU4X source tree -// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). - -/// Generates a test that checks the stack size of an item and a macro -/// that should be used with `#[doc]` to document it. -/// -/// ```ignore -/// size_test!(MyType, my_type_size, 32); -/// -/// // Add this annotation to the type's docs: -/// #[doc = my_type_size!()] -/// ``` -/// -/// The size should correspond to the Rust version in rust-toolchain.toml. -/// -/// If the size on latest beta differs from rust-toolchain.toml, use the -/// named arguments version of this macro to specify both sizes: -/// -/// ```ignore -/// size_test!(MyType, my_type_size, pinned = 32, beta = 24, nightly = 24); -/// ``` -/// -/// The test is ignored by default but runs in CI. To run the test locally, -/// run `cargo test -- --include-ignored` -macro_rules! size_test { - ($ty:ty, $id:ident, pinned = $pinned:literal, beta = $beta:literal, nightly = $nightly:literal) => { - macro_rules! $id { - () => { - concat!( - "\n", - "📏 This item has a stack size of ", - stringify!($pinned), - " bytes on the stable toolchain and ", - stringify!($beta), - " bytes on beta toolchain at release date." - ) - }; - } - #[test] - #[cfg_attr(not(icu4x_run_size_tests), ignore)] // Doesn't work on arbitrary Rust versions - fn $id() { - let size = core::mem::size_of::<$ty>(); - let success = match option_env!("CI_TOOLCHAIN") { - Some("nightly") => size == $nightly, - Some("beta") => size == $beta, - Some("pinned-stable") => size == $pinned, - // Manual invocation: match either size - _ => matches!(size, $pinned | $beta | $nightly), - }; - assert!( - success, - "size_of {} = {}.\n** To reproduce this failure, run `cargo test -- --ignored` **", - stringify!($ty), - size, - ); - } - }; - ($ty:ty, $id:ident, $size:literal) => { - macro_rules! $id { - () => { - concat!( - "📏 This item has a stack size of ", - stringify!($size), - " bytes on the stable toolchain at release date." - ) - }; - } - #[test] - #[cfg_attr(not(icu4x_run_size_tests), ignore)] // Doesn't work on arbitrary Rust versions - fn $id() { - let size = core::mem::size_of::<$ty>(); - let expected = $size; - assert_eq!( - size, - expected, - "size_of {} = {}.\n** To reproduce this failure, run `cargo test -- --ignored` **", - stringify!($ty), - size, - ); - } - }; -} - -pub(crate) use size_test; diff --git a/components/datetime/src/input.rs b/components/datetime/src/input.rs index 1fe26066f2c..9f7c87a9707 100644 --- a/components/datetime/src/input.rs +++ b/components/datetime/src/input.rs @@ -6,7 +6,7 @@ //! formatting operations. use crate::scaffold::{DateInputMarkers, GetField, TimeMarkers, ZoneMarkers}; -use icu_calendar::any_calendar::AnyCalendarKind; +use icu_calendar::types::DayOfYearInfo; use icu_calendar::{Date, Iso, Time}; use icu_timezone::scaffold::IntoOption; use icu_timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant}; @@ -22,7 +22,7 @@ pub(crate) struct ExtractedInput { pub(crate) month: Option, pub(crate) day_of_month: Option, pub(crate) iso_weekday: Option, - pub(crate) any_calendar_kind: Option, + pub(crate) day_of_year: Option, pub(crate) hour: Option, pub(crate) minute: Option, pub(crate) second: Option, @@ -45,7 +45,7 @@ impl ExtractedInput { + GetField + GetField + GetField - + GetField + + GetField + GetField + GetField + GetField @@ -60,7 +60,7 @@ impl ExtractedInput { month: GetField::::get_field(input).into_option(), day_of_month: GetField::::get_field(input).into_option(), iso_weekday: GetField::::get_field(input).into_option(), - any_calendar_kind: GetField::::get_field(input).into_option(), + day_of_year: GetField::::get_field(input).into_option(), hour: GetField::::get_field(input).into_option(), minute: GetField::::get_field(input).into_option(), second: GetField::::get_field(input).into_option(), diff --git a/components/datetime/src/lib.rs b/components/datetime/src/lib.rs index 1d0a4ae5b35..2fbb566c508 100644 --- a/components/datetime/src/lib.rs +++ b/components/datetime/src/lib.rs @@ -28,20 +28,20 @@ //! //! ``` //! use icu::calendar::{DateTime, Gregorian}; -//! use icu::datetime::fieldset::YMDHM; +//! use icu::datetime::fieldset::YMDT; //! use icu::datetime::{DateTimeFormatter, FixedCalendarDateTimeFormatter}; //! use icu::locale::{locale, Locale}; //! use writeable::assert_try_writeable_eq; //! //! // You can work with a formatter that can select the calendar at runtime: //! let locale = Locale::try_from_str("en-u-ca-gregory").unwrap(); -//! let dtf = DateTimeFormatter::try_new(&locale.into(), YMDHM::medium()) +//! let dtf = DateTimeFormatter::try_new(&locale.into(), YMDT::medium().hm()) //! .expect("should successfully create DateTimeFormatter instance"); //! //! // Or one that selects a calendar at compile time: //! let typed_dtf = FixedCalendarDateTimeFormatter::::try_new( //! &locale!("en").into(), -//! YMDHM::medium(), +//! YMDT::medium().hm(), //! ) //! .expect( //! "should successfully create FixedCalendarDateTimeFormatter instance", @@ -88,13 +88,12 @@ extern crate alloc; mod combo; +mod dynamic; mod error; mod external_loaders; pub mod fields; pub mod fieldset; mod format; -#[macro_use] -pub(crate) mod helpers; pub mod input; mod neo; mod neo_marker; @@ -106,12 +105,10 @@ pub mod options; pub mod provider; pub(crate) mod raw; pub mod scaffold; -mod time_zone; -mod tz_registry; +pub(crate) mod size_test_macro; -pub use error::MismatchedCalendarError; -pub use format::datetime::DateTimeWriteError; -pub use format::neo::{FormattedDateTimePattern, LoadError, SingleLoadError, TypedDateTimeNames}; +pub use error::{DateTimeWriteError, MismatchedCalendarError}; +pub use format::neo::{FormattedDateTimePattern, PatternLoadError, TypedDateTimeNames}; pub use neo::DateTimeFormatter; pub use neo::FixedCalendarDateTimeFormatter; diff --git a/components/datetime/src/neo.rs b/components/datetime/src/neo.rs index c914fb33ef2..c6eb195d39b 100644 --- a/components/datetime/src/neo.rs +++ b/components/datetime/src/neo.rs @@ -4,28 +4,27 @@ //! High-level entrypoints for Neo DateTime Formatter +use crate::dynamic::CompositeFieldSet; use crate::external_loaders::*; use crate::format::datetime::try_write_pattern_items; -use crate::format::datetime::DateTimeWriteError; use crate::format::neo::*; use crate::input::ExtractedInput; use crate::neo_pattern::DateTimePattern; -use crate::neo_skeleton::{NeoComponents, NeoSkeletonLength}; use crate::options::preferences::HourCycle; use crate::raw::neo::*; use crate::scaffold::*; use crate::scaffold::{ AllInputMarkers, ConvertCalendar, DateDataMarkers, DateInputMarkers, DateTimeMarkers, GetField, - HasConstComponents, IsAnyCalendarKind, IsInCalendar, IsRuntimeComponents, TimeMarkers, - TypedDateDataMarkers, ZoneMarkers, + IsAnyCalendarKind, IsInCalendar, TimeMarkers, TypedDateDataMarkers, ZoneMarkers, }; +use crate::size_test_macro::size_test; +use crate::DateTimeWriteError; use crate::MismatchedCalendarError; use core::fmt; use core::marker::PhantomData; use icu_calendar::any_calendar::IntoAnyCalendar; use icu_calendar::AnyCalendar; use icu_provider::prelude::*; -use icu_timezone::scaffold::IntoOption; use writeable::TryWriteable; /// Helper macro for generating any/buffer constructors in this file. @@ -35,8 +34,8 @@ macro_rules! gen_any_buffer_constructors_with_external_loader { pub fn $any_fn

( provider: &P, locale: &DataLocale, - skeleton: $fset, - ) -> Result + field_set: $fset, + ) -> Result where P: AnyProvider + ?Sized, { @@ -44,8 +43,7 @@ macro_rules! gen_any_buffer_constructors_with_external_loader { &provider.as_downcasting(), &ExternalLoaderAny(provider), locale, - RawNeoOptions::from_field_set_and_locale(&skeleton, locale), - skeleton.get_field(), + field_set.get_field(), ) } #[doc = icu_provider::gen_any_buffer_unstable_docs!(BUFFER, Self::$compiled_fn)] @@ -53,8 +51,8 @@ macro_rules! gen_any_buffer_constructors_with_external_loader { pub fn $buffer_fn

( provider: &P, locale: &DataLocale, - skeleton: $fset, - ) -> Result + field_set: $fset, + ) -> Result where P: BufferProvider + ?Sized, { @@ -62,8 +60,7 @@ macro_rules! gen_any_buffer_constructors_with_external_loader { &provider.as_deserializing(), &ExternalLoaderBuffer(provider), locale, - RawNeoOptions::from_field_set_and_locale(&skeleton, locale), - skeleton.get_field(), + field_set.get_field(), ) } }; @@ -72,8 +69,8 @@ macro_rules! gen_any_buffer_constructors_with_external_loader { pub fn $any_fn

( provider: &P, locale: &DataLocale, - options: $fset, - ) -> Result + field_set: $fset, + ) -> Result where P: AnyProvider + ?Sized, { @@ -81,8 +78,7 @@ macro_rules! gen_any_buffer_constructors_with_external_loader { &provider.as_downcasting(), &ExternalLoaderAny(provider), locale, - RawNeoOptions::from_field_set_and_locale(&options, locale), - $fset::COMPONENTS, + field_set.get_field(), ) } #[doc = icu_provider::gen_any_buffer_unstable_docs!(BUFFER, Self::$compiled_fn)] @@ -90,8 +86,8 @@ macro_rules! gen_any_buffer_constructors_with_external_loader { pub fn $buffer_fn

( provider: &P, locale: &DataLocale, - options: $fset, - ) -> Result + field_set: $fset, + ) -> Result where P: BufferProvider + ?Sized, { @@ -99,45 +95,42 @@ macro_rules! gen_any_buffer_constructors_with_external_loader { &provider.as_deserializing(), &ExternalLoaderBuffer(provider), locale, - RawNeoOptions::from_field_set_and_locale(&options, locale), - $fset::COMPONENTS, + field_set.get_field(), ) } }; } -impl RawNeoOptions { - pub(crate) fn from_field_set_and_locale(field_set: &FSet, locale: &DataLocale) -> Self - where - FSet: DateTimeMarkers, - FSet: GetField, - FSet: GetField, - FSet: GetField, - FSet: GetField, - { - // TODO: Return an error if there are more options than field set - let hour_cycle = locale - .get_unicode_ext(&icu_locale_core::extensions::unicode::key!("hc")) - .as_ref() - .and_then(HourCycle::from_locale_value); - Self { - length: match GetField::::get_field(field_set).into_option() { - Some(length) => length, - None => { - debug_assert!(false, "unreachable"); - NeoSkeletonLength::Medium - } - }, - alignment: GetField::::get_field(field_set).into_option(), - year_style: GetField::::get_field(field_set).into_option(), - fractional_second_digits: GetField::::get_field( - field_set, - ) - .into_option(), - hour_cycle, - } - } -} +// impl RawNeoOptions { +// pub(crate) fn from_field_set_and_locale(field_set: &FSet, locale: &DataLocale) -> Self +// where +// FSet: DateTimeMarkers, +// FSet: GetField, +// FSet: GetField, +// FSet: GetField, +// FSet: GetField, +// { +// // TODO: Return an error if there are more options than field set +// let hour_cycle = locale +// .get_unicode_ext(&icu_locale_core::extensions::unicode::key!("hc")) +// .as_ref() +// .and_then(HourCycle::from_locale_value); +// Self { +// length: match GetField::::get_field(field_set).into_option() { +// Some(length) => length, +// None => { +// debug_assert!(false, "unreachable"); +// NeoSkeletonLength::Medium +// } +// }, +// alignment: GetField::::get_field(field_set).into_option(), +// year_style: GetField::::get_field(field_set).into_option(), +// time_precision: GetField::::get_field(field_set) +// .into_option(), +// hour_cycle, +// } +// } +// } size_test!(FixedCalendarDateTimeFormatter, typed_neo_year_month_day_formatter_size, 456); @@ -154,16 +147,12 @@ pub struct FixedCalendarDateTimeFormatter, } -impl - FixedCalendarDateTimeFormatter +impl FixedCalendarDateTimeFormatter where FSet::D: TypedDateDataMarkers, FSet::T: TimeMarkers, FSet::Z: ZoneMarkers, - FSet: GetField, - FSet: GetField, - FSet: GetField, - FSet: GetField, + FSet: GetField, { /// Creates a new [`FixedCalendarDateTimeFormatter`] from compiled data with /// datetime components specified at build time. @@ -197,7 +186,7 @@ where /// ); /// ``` #[cfg(feature = "compiled_data")] - pub fn try_new(locale: &DataLocale, field_set: FSet) -> Result + pub fn try_new(locale: &DataLocale, field_set: FSet) -> Result where crate::provider::Baked: AllFixedCalendarFormattingDataMarkers, { @@ -205,8 +194,7 @@ where &crate::provider::Baked, &ExternalLoaderCompiledData, locale, - RawNeoOptions::from_field_set_and_locale(&field_set, locale), - FSet::COMPONENTS, + field_set.get_field(), ) } @@ -224,7 +212,7 @@ where provider: &P, locale: &DataLocale, field_set: FSet, - ) -> Result + ) -> Result where P: ?Sized + AllFixedCalendarFormattingDataMarkers @@ -234,166 +222,7 @@ where provider, &ExternalLoaderUnstable(provider), locale, - RawNeoOptions::from_field_set_and_locale(&field_set, locale), - FSet::COMPONENTS, - ) - } -} - -impl - FixedCalendarDateTimeFormatter -where - FSet::D: TypedDateDataMarkers, - FSet::T: TimeMarkers, - FSet::Z: ZoneMarkers, - FSet: GetField, - FSet: GetField, - FSet: GetField, - FSet: GetField, -{ - /// Creates a new [`FixedCalendarDateTimeFormatter`] from compiled data with - /// datetime components specified at runtime. - /// - /// If you know the datetime components at build time, use - /// [`FixedCalendarDateTimeFormatter::try_new`] for smaller data size and memory use. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) - /// - /// # Examples - /// - /// Date components: - /// - /// ``` - /// use icu::calendar::Date; - /// use icu::calendar::Gregorian; - /// use icu::datetime::neo_skeleton::NeoDateComponents; - /// use icu::datetime::neo_skeleton::NeoDateSkeleton; - /// use icu::datetime::FixedCalendarDateTimeFormatter; - /// use icu::locale::locale; - /// use writeable::assert_try_writeable_eq; - /// - /// let fmt = - /// FixedCalendarDateTimeFormatter::::try_new_with_skeleton( - /// &locale!("es-MX").into(), - /// NeoDateComponents::DayWeekday.medium(), - /// ) - /// .unwrap(); - /// let dt = Date::try_new_gregorian(2024, 1, 10).unwrap(); - /// - /// assert_try_writeable_eq!(fmt.format(&dt), "mié 10"); - /// ``` - /// - /// Calendar period components: - /// - /// ``` - /// use icu::calendar::Date; - /// use icu::calendar::Gregorian; - /// use icu::datetime::neo_skeleton::NeoCalendarPeriodComponents; - /// use icu::datetime::neo_skeleton::NeoCalendarPeriodSkeleton; - /// use icu::datetime::FixedCalendarDateTimeFormatter; - /// use icu::locale::locale; - /// use writeable::assert_try_writeable_eq; - /// - /// let fmt = - /// FixedCalendarDateTimeFormatter::::try_new_with_skeleton( - /// &locale!("es-MX").into(), - /// NeoCalendarPeriodComponents::YearMonth.medium(), - /// ) - /// .unwrap(); - /// let dt = Date::try_new_gregorian(2024, 1, 10).unwrap(); - /// - /// assert_try_writeable_eq!(fmt.format(&dt), "ene 2024"); - /// ``` - /// - /// Time components: - /// - /// ``` - /// use icu::calendar::Gregorian; - /// use icu::calendar::Time; - /// use icu::datetime::neo_skeleton::NeoTimeComponents; - /// use icu::datetime::FixedCalendarDateTimeFormatter; - /// use icu::locale::locale; - /// use writeable::assert_try_writeable_eq; - /// - /// let fmt = - /// FixedCalendarDateTimeFormatter::::try_new_with_skeleton( - /// &locale!("es-MX").into(), - /// NeoTimeComponents::Hour.medium(), - /// ) - /// .unwrap(); - /// let dt = Time::try_new(16, 20, 0, 0).unwrap(); - /// - /// assert_try_writeable_eq!(fmt.format(&dt), "04 p.m."); - /// ``` - /// - /// Date and time components: - /// - /// ``` - /// use icu::calendar::DateTime; - /// use icu::calendar::Gregorian; - /// use icu::datetime::neo_skeleton::NeoDateComponents; - /// use icu::datetime::neo_skeleton::NeoDateTimeComponents; - /// use icu::datetime::neo_skeleton::NeoTimeComponents; - /// use icu::datetime::FixedCalendarDateTimeFormatter; - /// use icu::locale::locale; - /// use writeable::assert_try_writeable_eq; - /// - /// let fmt = - /// FixedCalendarDateTimeFormatter::::try_new_with_skeleton( - /// &locale!("es-MX").into(), - /// NeoDateTimeComponents::DateTime( - /// NeoDateComponents::Weekday, - /// NeoTimeComponents::HourMinute, - /// ) - /// .long(), - /// ) - /// .unwrap(); - /// let dt = DateTime::try_new_gregorian(2024, 1, 10, 16, 20, 0).unwrap(); - /// - /// assert_try_writeable_eq!(fmt.format(&dt), "miércoles 4:20 p.m."); - /// ``` - #[cfg(feature = "compiled_data")] - pub fn try_new_with_skeleton(locale: &DataLocale, skeleton: FSet) -> Result - where - crate::provider::Baked: AllFixedCalendarFormattingDataMarkers, - { - Self::try_new_internal( - &crate::provider::Baked, - &ExternalLoaderCompiledData, - locale, - RawNeoOptions::from_field_set_and_locale(&skeleton, locale), - skeleton.get_field(), - ) - } - - gen_any_buffer_constructors_with_external_loader!( - @runtime_fset, - FSet, - try_new_with_skeleton, - try_new_with_skeleton_with_any_provider, - try_new_with_skeleton_with_buffer_provider, - try_new_internal - ); - - #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)] - pub fn try_new_with_skeleton_unstable

( - provider: &P, - locale: &DataLocale, - skeleton: FSet, - ) -> Result - where - P: ?Sized - + AllFixedCalendarFormattingDataMarkers - + AllFixedCalendarExternalDataMarkers, - { - Self::try_new_internal( - provider, - &ExternalLoaderUnstable(provider), - locale, - RawNeoOptions::from_field_set_and_locale(&skeleton, locale), - skeleton.get_field(), + field_set.get_field(), ) } } @@ -408,19 +237,19 @@ where provider: &P, loader: &L, locale: &DataLocale, - options: RawNeoOptions, - components: NeoComponents, - ) -> Result + field_set: CompositeFieldSet, + ) -> Result where P: ?Sized + AllFixedCalendarFormattingDataMarkers, L: FixedDecimalFormatterLoader, { // TODO: Remove this when NeoOptions is gone - let mut options = options; - options.hour_cycle = locale - .get_unicode_ext(&icu_locale_core::extensions::unicode::key!("hc")) - .as_ref() - .and_then(HourCycle::from_locale_value); + let prefs = RawPreferences { + hour_cycle: locale + .get_unicode_ext(&icu_locale_core::extensions::unicode::key!("hc")) + .as_ref() + .and_then(HourCycle::from_locale_value), + }; // END TODO let selection = DateTimeZonePatternSelectionData::try_new_with_skeleton( @@ -428,10 +257,10 @@ where &::TimeSkeletonPatternsV1Marker::bind(provider), &FSet::GluePatternV1Marker::bind(provider), locale, - components, - options, + field_set, + prefs, ) - .map_err(LoadError::Data)?; + .map_err(PatternLoadError::Data)?; let mut names = RawDateTimeNames::new_without_number_formatting(); names.load_for_pattern( &>::YearNamesV1Marker::bind(provider), @@ -539,15 +368,12 @@ pub struct DateTimeFormatter { calendar: AnyCalendar, } -impl DateTimeFormatter +impl DateTimeFormatter where FSet::D: DateDataMarkers, FSet::T: TimeMarkers, FSet::Z: ZoneMarkers, - FSet: GetField, - FSet: GetField, - FSet: GetField, - FSet: GetField, + FSet: GetField, { /// Creates a new [`DateTimeFormatter`] from compiled data with /// datetime components specified at build time. @@ -592,7 +418,7 @@ where /// [`AnyCalendarKind`]: icu_calendar::AnyCalendarKind #[inline(never)] #[cfg(feature = "compiled_data")] - pub fn try_new(locale: &DataLocale, field_set: FSet) -> Result + pub fn try_new(locale: &DataLocale, field_set: FSet) -> Result where crate::provider::Baked: AllAnyCalendarFormattingDataMarkers, { @@ -600,8 +426,7 @@ where &crate::provider::Baked, &ExternalLoaderCompiledData, locale, - RawNeoOptions::from_field_set_and_locale(&field_set, locale), - FSet::COMPONENTS, + field_set.get_field(), ) } @@ -619,7 +444,7 @@ where provider: &P, locale: &DataLocale, field_set: FSet, - ) -> Result + ) -> Result where P: ?Sized + AllAnyCalendarFormattingDataMarkers + AllAnyCalendarExternalDataMarkers, { @@ -627,160 +452,7 @@ where provider, &ExternalLoaderUnstable(provider), locale, - RawNeoOptions::from_field_set_and_locale(&field_set, locale), - FSet::COMPONENTS, - ) - } -} - -impl DateTimeFormatter -where - FSet::D: DateDataMarkers, - FSet::T: TimeMarkers, - FSet::Z: ZoneMarkers, - FSet: GetField, - FSet: GetField, - FSet: GetField, - FSet: GetField, -{ - /// Creates a new [`DateTimeFormatter`] from compiled data with - /// datetime components specified at runtime. - /// - /// If you know the datetime components at build time, use - /// [`DateTimeFormatter::try_new`] for smaller data size and memory use. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) - /// - /// # Examples - /// - /// Date components: - /// - /// ``` - /// use icu::calendar::Date; - /// use icu::datetime::neo_skeleton::NeoDateComponents; - /// use icu::datetime::neo_skeleton::NeoDateSkeleton; - /// use icu::datetime::DateTimeFormatter; - /// use icu::locale::locale; - /// use writeable::assert_try_writeable_eq; - /// - /// let fmt = DateTimeFormatter::try_new_with_skeleton( - /// &locale!("es-MX").into(), - /// NeoDateComponents::DayWeekday.medium(), - /// ) - /// .unwrap(); - /// let dt = Date::try_new_iso(2024, 1, 10).unwrap(); - /// - /// assert_try_writeable_eq!(fmt.convert_and_format(&dt), "mié 10"); - /// ``` - /// - /// Calendar period components: - /// - /// ``` - /// use icu::calendar::Date; - /// use icu::datetime::neo_skeleton::NeoCalendarPeriodComponents; - /// use icu::datetime::neo_skeleton::NeoCalendarPeriodSkeleton; - /// use icu::datetime::DateTimeFormatter; - /// use icu::locale::locale; - /// use writeable::assert_try_writeable_eq; - /// - /// let fmt = DateTimeFormatter::try_new_with_skeleton( - /// &locale!("es-MX").into(), - /// NeoCalendarPeriodComponents::YearMonth.medium(), - /// ) - /// .unwrap(); - /// let dt = Date::try_new_iso(2024, 1, 10).unwrap(); - /// - /// assert_try_writeable_eq!(fmt.convert_and_format(&dt), "ene 2024"); - /// ``` - /// - /// Time components: - /// - /// ``` - /// use icu::calendar::Time; - /// use icu::datetime::neo_skeleton::NeoTimeComponents; - /// use icu::datetime::neo_skeleton::NeoTimeSkeleton; - /// use icu::datetime::DateTimeFormatter; - /// use icu::locale::locale; - /// use writeable::assert_try_writeable_eq; - /// - /// let fmt = DateTimeFormatter::try_new_with_skeleton( - /// &locale!("es-MX").into(), - /// NeoTimeComponents::Hour.medium(), - /// ) - /// .unwrap(); - /// let dt = Time::try_new(16, 20, 0, 0).unwrap(); - /// - /// assert_try_writeable_eq!(fmt.convert_and_format(&dt), "04 p.m."); - /// ``` - /// - /// Date and time components: - /// - /// ``` - /// use icu::calendar::DateTime; - /// use icu::datetime::neo_skeleton::NeoDateComponents; - /// use icu::datetime::neo_skeleton::NeoDateTimeComponents; - /// use icu::datetime::neo_skeleton::NeoDateTimeSkeleton; - /// use icu::datetime::neo_skeleton::NeoTimeComponents; - /// use icu::datetime::DateTimeFormatter; - /// use icu::locale::locale; - /// use writeable::assert_try_writeable_eq; - /// - /// let fmt = DateTimeFormatter::try_new_with_skeleton( - /// &locale!("es-MX").into(), - /// NeoDateTimeComponents::DateTime( - /// NeoDateComponents::Weekday, - /// NeoTimeComponents::HourMinute, - /// ) - /// .long(), - /// ) - /// .unwrap(); - /// let dt = DateTime::try_new_iso(2024, 1, 10, 16, 20, 0).unwrap(); - /// - /// assert_try_writeable_eq!( - /// fmt.convert_and_format(&dt), - /// "miércoles 4:20 p.m." - /// ); - /// ``` - #[cfg(feature = "compiled_data")] - pub fn try_new_with_skeleton(locale: &DataLocale, skeleton: FSet) -> Result - where - crate::provider::Baked: AllAnyCalendarFormattingDataMarkers, - { - Self::try_new_internal( - &crate::provider::Baked, - &ExternalLoaderCompiledData, - locale, - RawNeoOptions::from_field_set_and_locale(&skeleton, locale), - skeleton.get_field(), - ) - } - - gen_any_buffer_constructors_with_external_loader!( - @runtime_fset, - FSet, - try_new_with_skeleton, - try_new_with_skeleton_with_any_provider, - try_new_with_skeleton_with_buffer_provider, - try_new_internal - ); - - #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)] - pub fn try_new_with_skeleton_unstable

( - provider: &P, - locale: &DataLocale, - skeleton: FSet, - ) -> Result - where - P: ?Sized + AllAnyCalendarFormattingDataMarkers + AllAnyCalendarExternalDataMarkers, - { - Self::try_new_internal( - provider, - &ExternalLoaderUnstable(provider), - locale, - RawNeoOptions::from_field_set_and_locale(&skeleton, locale), - skeleton.get_field(), + field_set.get_field(), ) } } @@ -795,32 +467,32 @@ where provider: &P, loader: &L, locale: &DataLocale, - options: RawNeoOptions, - components: NeoComponents, - ) -> Result + field_set: CompositeFieldSet, + ) -> Result where P: ?Sized + AllAnyCalendarFormattingDataMarkers, L: FixedDecimalFormatterLoader + AnyCalendarLoader, { // TODO: Remove this when NeoOptions is gone - let mut options = options; - options.hour_cycle = locale - .get_unicode_ext(&icu_locale_core::extensions::unicode::key!("hc")) - .as_ref() - .and_then(HourCycle::from_locale_value); + let prefs = RawPreferences { + hour_cycle: locale + .get_unicode_ext(&icu_locale_core::extensions::unicode::key!("hc")) + .as_ref() + .and_then(HourCycle::from_locale_value), + }; // END TODO - let calendar = AnyCalendarLoader::load(loader, locale).map_err(LoadError::Data)?; + let calendar = AnyCalendarLoader::load(loader, locale).map_err(PatternLoadError::Data)?; let kind = calendar.kind(); let selection = DateTimeZonePatternSelectionData::try_new_with_skeleton( &AnyCalendarProvider::<::Skel, _>::new(provider, kind), &::TimeSkeletonPatternsV1Marker::bind(provider), &FSet::GluePatternV1Marker::bind(provider), locale, - components, - options, + field_set, + prefs, ) - .map_err(LoadError::Data)?; + .map_err(PatternLoadError::Data)?; let mut names = RawDateTimeNames::new_without_number_formatting(); names.load_for_pattern( &AnyCalendarProvider::<::Year, _>::new(provider, kind), @@ -907,16 +579,7 @@ where where I: ?Sized + IsAnyCalendarKind + AllInputMarkers, { - if !datetime.is_any_calendar_kind(self.calendar.kind()) { - return Err(crate::MismatchedCalendarError { - this_kind: self.calendar.kind(), - date_kind: - GetField::<::AnyCalendarKindInput>::get_field( - datetime, - ) - .into_option(), - }); - } + datetime.check_any_calendar_kind(self.calendar.kind())?; let datetime = ExtractedInput::extract_from_neo_input::(datetime); Ok(FormattedNeoDateTime { diff --git a/components/datetime/src/neo_marker.rs b/components/datetime/src/neo_marker.rs index fb78e1350b0..bed8f739636 100644 --- a/components/datetime/src/neo_marker.rs +++ b/components/datetime/src/neo_marker.rs @@ -146,7 +146,7 @@ //! //! ``` //! use icu::calendar::Time; -//! use icu::datetime::fieldset::HM; +//! use icu::datetime::fieldset::T; //! use icu::datetime::FixedCalendarDateTimeFormatter; //! use icu::locale::locale; //! use writeable::assert_try_writeable_eq; @@ -156,7 +156,7 @@ //! //! let formatter = FixedCalendarDateTimeFormatter::<(), _>::try_new( //! &locale!("en-US-u-hc-h12").into(), -//! HM::short(), +//! T::short().hm(), //! ) //! .unwrap(); //! assert_try_writeable_eq!( @@ -166,7 +166,7 @@ //! //! let formatter = FixedCalendarDateTimeFormatter::<(), _>::try_new( //! &locale!("en-US-u-hc-h23").into(), -//! HM::short(), +//! T::short().hm(), //! ) //! .unwrap(); //! assert_try_writeable_eq!( @@ -176,7 +176,7 @@ //! //! let formatter = FixedCalendarDateTimeFormatter::<(), _>::try_new( //! &locale!("fr-FR-u-hc-h12").into(), -//! HM::short(), +//! T::short().hm(), //! ) //! .unwrap(); //! assert_try_writeable_eq!( @@ -186,7 +186,7 @@ //! //! let formatter = FixedCalendarDateTimeFormatter::<(), _>::try_new( //! &locale!("fr-FR-u-hc-h23").into(), -//! HM::short(), +//! T::short().hm(), //! ) //! .unwrap(); //! assert_try_writeable_eq!( @@ -199,14 +199,14 @@ //! //! ``` //! use icu::calendar::Time; -//! use icu::datetime::fieldset::HM; +//! use icu::datetime::fieldset::T; //! use icu::datetime::FixedCalendarDateTimeFormatter; //! use icu::locale::locale; //! use writeable::assert_try_writeable_eq; //! //! let formatter = FixedCalendarDateTimeFormatter::<(), _>::try_new( //! &locale!("und-u-hc-h11").into(), -//! HM::short(), +//! T::short().hm(), //! ) //! .unwrap(); //! assert_try_writeable_eq!( @@ -216,7 +216,7 @@ //! //! let formatter = FixedCalendarDateTimeFormatter::<(), _>::try_new( //! &locale!("und-u-hc-h24").into(), -//! HM::short(), +//! T::short().hm(), //! ) //! .unwrap(); //! assert_try_writeable_eq!( @@ -225,6 +225,64 @@ //! ); //! ``` //! +//! ## Time Precision +//! +//! The time can be displayed with hour, minute, or second precision, and +//! zero-valued fields can be automatically hidden. +//! +//! ``` +//! use icu::calendar::Time; +//! use icu::datetime::fieldset::T; +//! use icu::datetime::neo_skeleton::FractionalSecondDigits; +//! use icu::datetime::neo_skeleton::TimePrecision; +//! use icu::datetime::FixedCalendarDateTimeFormatter; +//! use icu::locale::locale; +//! use writeable::assert_try_writeable_eq; +//! +//! let formatters = [ +//! TimePrecision::HourPlus, +//! TimePrecision::HourExact, +//! TimePrecision::MinutePlus, +//! TimePrecision::MinuteExact, +//! TimePrecision::SecondPlus, +//! TimePrecision::SecondExact(FractionalSecondDigits::F0), +//! ].map(|time_precision| { +//! FixedCalendarDateTimeFormatter::<(), _>::try_new( +//! &locale!("en-US").into(), +//! T::short().with_time_precision(time_precision), +//! ) +//! .unwrap() +//! }); +//! +//! let times = [ +//! Time::try_new(7, 0, 0, 0).unwrap(), +//! Time::try_new(7, 0, 10, 0).unwrap(), +//! Time::try_new(7, 12, 20, 5).unwrap(), +//! ]; +//! +//! // TODO(#5782): the Plus variants should render fractional digits +//! let expected_value_table = [ +//! // 7:00:00, 7:00:10, 7:12:20.5432 +//! ["7 AM", "7:00:10 AM", "7:12:20 AM"], // HourPlus +//! ["7 AM", "7 AM", "7 AM"], // HourExact +//! ["7:00 AM", "7:00:10 AM", "7:12:20 AM"], // MinutePlus +//! ["7:00 AM", "7:00 AM", "7:12 AM"], // MinuteExact +//! ["7:00:00 AM", "7:00:10 AM", "7:12:20 AM"], // SecondPlus +//! ["7:00:00 AM", "7:00:10 AM", "7:12:20 AM"], // SecondExact +//! ]; +//! +//! for (expected_value_row, formatter) in expected_value_table.iter().zip(formatters.iter()) { +//! for (expected_value, time) in expected_value_row.iter().zip(times.iter()) { +//! assert_try_writeable_eq!( +//! formatter.format(time), +//! *expected_value, +//! Ok(()), +//! "{formatter:?} @ {time:?}" +//! ); +//! } +//! } +//! ``` +//! //! ## Fractional Second Digits //! //! Times can be displayed with a custom number of fractional digits from 0-9: @@ -232,15 +290,16 @@ //! ``` //! use icu::calendar::Gregorian; //! use icu::calendar::Time; -//! use icu::datetime::fieldset::HMS; +//! use icu::datetime::fieldset::T; //! use icu::datetime::neo_skeleton::FractionalSecondDigits; +//! use icu::datetime::neo_skeleton::TimePrecision; //! use icu::datetime::FixedCalendarDateTimeFormatter; //! use icu::locale::locale; //! use writeable::assert_try_writeable_eq; //! //! let formatter = FixedCalendarDateTimeFormatter::<(), _>::try_new( //! &locale!("en-US").into(), -//! HMS::short().with_fractional_second_digits(FractionalSecondDigits::F2), +//! T::short().with_time_precision(TimePrecision::SecondExact(FractionalSecondDigits::F2)), //! ) //! .unwrap(); //! @@ -305,325 +364,3 @@ #[cfg(doc)] use crate::{fieldset::*, DateTimeFormatter}; - -use crate::{ - format::neo::*, - neo_skeleton::*, - provider::{neo::*, time_zones::tz, *}, - scaffold::*, -}; -use icu_calendar::{ - types::{ - DayOfMonth, IsoHour, IsoMinute, IsoSecond, IsoWeekday, MonthInfo, NanoSecond, YearInfo, - }, - AnyCalendarKind, Date, Iso, Time, -}; -use icu_provider::marker::NeverMarker; -use icu_timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant}; - -impl GetField for NeoDateSkeleton { - fn get_field(&self) -> NeoComponents { - self.components.into() - } -} - -impl UnstableSealed for NeoDateSkeleton {} - -impl IsRuntimeComponents for NeoDateSkeleton {} - -impl DateTimeNamesMarker for NeoDateSkeleton { - type YearNames = datetime_marker_helper!(@names/year, yes); - type MonthNames = datetime_marker_helper!(@names/month, yes); - type WeekdayNames = datetime_marker_helper!(@names/weekday, yes); - type DayPeriodNames = datetime_marker_helper!(@names/dayperiod,); - type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials,); - type ZoneLocations = datetime_marker_helper!(@names/zone/locations,); - type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long,); - type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short,); - type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long,); - type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short,); - type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods,); -} - -impl DateInputMarkers for NeoDateSkeleton { - type YearInput = datetime_marker_helper!(@input/year, yes); - type MonthInput = datetime_marker_helper!(@input/month, yes); - type DayOfMonthInput = datetime_marker_helper!(@input/day_of_month, yes); - type DayOfWeekInput = datetime_marker_helper!(@input/day_of_week, yes); - type AnyCalendarKindInput = datetime_marker_helper!(@input/any_calendar_kind, yes); -} - -impl TypedDateDataMarkers for NeoDateSkeleton { - type DateSkeletonPatternsV1Marker = datetime_marker_helper!(@dates/typed, yes); - type YearNamesV1Marker = datetime_marker_helper!(@years/typed, yes); - type MonthNamesV1Marker = datetime_marker_helper!(@months/typed, yes); - type WeekdayNamesV1Marker = datetime_marker_helper!(@weekdays, yes); -} - -impl DateDataMarkers for NeoDateSkeleton { - type Skel = datetime_marker_helper!(@calmarkers, yes); - type Year = datetime_marker_helper!(@calmarkers, yes); - type Month = datetime_marker_helper!(@calmarkers, yes); - type WeekdayNamesV1Marker = datetime_marker_helper!(@weekdays, yes); -} - -impl DateTimeMarkers for NeoDateSkeleton { - type D = Self; - type T = NeoNeverMarker; - type Z = NeoNeverMarker; - type LengthOption = datetime_marker_helper!(@option/length, yes); - type AlignmentOption = datetime_marker_helper!(@option/alignment, yes); - type YearStyleOption = datetime_marker_helper!(@option/yearstyle, yes); - type FractionalSecondDigitsOption = datetime_marker_helper!(@option/fractionalsecondigits,); - type GluePatternV1Marker = datetime_marker_helper!(@glue,); -} - -impl_get_field!(NeoDateSkeleton, never); -impl_get_field!(NeoDateSkeleton, length, yes); -impl_get_field!(NeoDateSkeleton, alignment, yes); -impl_get_field!(NeoDateSkeleton, year_style, yes); - -impl UnstableSealed for NeoCalendarPeriodSkeleton {} - -impl GetField for NeoCalendarPeriodSkeleton { - fn get_field(&self) -> NeoComponents { - self.components.into() - } -} - -impl IsRuntimeComponents for NeoCalendarPeriodSkeleton {} - -impl DateTimeNamesMarker for NeoCalendarPeriodSkeleton { - type YearNames = datetime_marker_helper!(@names/year, yes); - type MonthNames = datetime_marker_helper!(@names/month, yes); - type WeekdayNames = datetime_marker_helper!(@names/weekday,); - type DayPeriodNames = datetime_marker_helper!(@names/dayperiod,); - type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials,); - type ZoneLocations = datetime_marker_helper!(@names/zone/locations,); - type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long,); - type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short,); - type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long,); - type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short,); - type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods,); -} - -impl DateInputMarkers for NeoCalendarPeriodSkeleton { - type YearInput = datetime_marker_helper!(@input/year, yes); - type MonthInput = datetime_marker_helper!(@input/month, yes); - type DayOfMonthInput = datetime_marker_helper!(@input/day_of_month,); - type DayOfWeekInput = datetime_marker_helper!(@input/day_of_week,); - type AnyCalendarKindInput = datetime_marker_helper!(@input/any_calendar_kind, yes); -} - -impl TypedDateDataMarkers for NeoCalendarPeriodSkeleton { - type DateSkeletonPatternsV1Marker = datetime_marker_helper!(@dates/typed, yes); - type YearNamesV1Marker = datetime_marker_helper!(@years/typed, yes); - type MonthNamesV1Marker = datetime_marker_helper!(@months/typed, yes); - type WeekdayNamesV1Marker = datetime_marker_helper!(@weekdays,); -} - -impl DateDataMarkers for NeoCalendarPeriodSkeleton { - type Skel = datetime_marker_helper!(@calmarkers, yes); - type Year = datetime_marker_helper!(@calmarkers, yes); - type Month = datetime_marker_helper!(@calmarkers, yes); - type WeekdayNamesV1Marker = datetime_marker_helper!(@weekdays,); -} - -impl DateTimeMarkers for NeoCalendarPeriodSkeleton { - type D = Self; - type T = NeoNeverMarker; - type Z = NeoNeverMarker; - type LengthOption = datetime_marker_helper!(@option/length, yes); - type AlignmentOption = datetime_marker_helper!(@option/alignment, yes); - type YearStyleOption = datetime_marker_helper!(@option/yearstyle, yes); - type FractionalSecondDigitsOption = datetime_marker_helper!(@option/fractionalsecondigits,); - type GluePatternV1Marker = datetime_marker_helper!(@glue,); -} - -impl_get_field!(NeoCalendarPeriodSkeleton, never); -impl_get_field!(NeoCalendarPeriodSkeleton, length, yes); -impl_get_field!(NeoCalendarPeriodSkeleton, alignment, yes); -impl_get_field!(NeoCalendarPeriodSkeleton, year_style, yes); - -impl UnstableSealed for NeoTimeSkeleton {} - -impl GetField for NeoTimeSkeleton { - fn get_field(&self) -> NeoComponents { - self.components.into() - } -} - -impl IsRuntimeComponents for NeoTimeSkeleton {} - -impl DateTimeNamesMarker for NeoTimeSkeleton { - type YearNames = datetime_marker_helper!(@names/year,); - type MonthNames = datetime_marker_helper!(@names/month,); - type WeekdayNames = datetime_marker_helper!(@names/weekday,); - type DayPeriodNames = datetime_marker_helper!(@names/dayperiod, yes); - type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials,); - type ZoneLocations = datetime_marker_helper!(@names/zone/locations,); - type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long,); - type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short,); - type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long,); - type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short,); - type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods,); -} - -impl TimeMarkers for NeoTimeSkeleton { - type DayPeriodNamesV1Marker = datetime_marker_helper!(@dayperiods, yes); - type TimeSkeletonPatternsV1Marker = datetime_marker_helper!(@times, yes); - type HourInput = datetime_marker_helper!(@input/hour, yes); - type MinuteInput = datetime_marker_helper!(@input/minute, yes); - type SecondInput = datetime_marker_helper!(@input/second, yes); - type NanoSecondInput = datetime_marker_helper!(@input/nanosecond, yes); -} - -impl DateTimeMarkers for NeoTimeSkeleton { - type D = NeoNeverMarker; - type T = Self; - type Z = NeoNeverMarker; - type LengthOption = datetime_marker_helper!(@option/length, yes); - type AlignmentOption = datetime_marker_helper!(@option/alignment, yes); - type YearStyleOption = datetime_marker_helper!(@option/yearstyle,); - type FractionalSecondDigitsOption = datetime_marker_helper!(@option/fractionalsecondigits, yes); - type GluePatternV1Marker = datetime_marker_helper!(@glue,); -} - -impl_get_field!(NeoTimeSkeleton, never); -impl_get_field!(NeoTimeSkeleton, length, yes); -impl_get_field!(NeoTimeSkeleton, alignment, yes); -impl_get_field!(NeoTimeSkeleton, fractional_second_digits, yes); - -impl UnstableSealed for NeoTimeZoneSkeleton {} - -impl GetField for NeoTimeZoneSkeleton { - fn get_field(&self) -> NeoComponents { - self.style.into() - } -} - -impl IsRuntimeComponents for NeoTimeZoneSkeleton {} - -impl DateTimeNamesMarker for NeoTimeZoneSkeleton { - type YearNames = datetime_marker_helper!(@names/year,); - type MonthNames = datetime_marker_helper!(@names/month,); - type WeekdayNames = datetime_marker_helper!(@names/weekday,); - type DayPeriodNames = datetime_marker_helper!(@names/dayperiod,); - type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials, yes); - type ZoneLocations = datetime_marker_helper!(@names/zone/locations, yes); - type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long, yes); - type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short, yes); - type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long, yes); - type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short, yes); - type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods, yes); -} - -impl ZoneMarkers for NeoTimeZoneSkeleton { - type TimeZoneIdInput = datetime_marker_helper!(@input/timezone/id, yes); - type TimeZoneOffsetInput = datetime_marker_helper!(@input/timezone/offset, yes); - type TimeZoneVariantInput = datetime_marker_helper!(@input/timezone/variant, yes); - type TimeZoneLocalTimeInput = datetime_marker_helper!(@input/timezone/local_time, yes); - type EssentialsV1Marker = datetime_marker_helper!(@data/zone/essentials, yes); - type LocationsV1Marker = datetime_marker_helper!(@data/zone/locations, yes); - type GenericLongV1Marker = datetime_marker_helper!(@data/zone/generic_long, yes); - type GenericShortV1Marker = datetime_marker_helper!(@data/zone/generic_short, yes); - type SpecificLongV1Marker = datetime_marker_helper!(@data/zone/specific_long, yes); - type SpecificShortV1Marker = datetime_marker_helper!(@data/zone/specific_short, yes); - type MetazonePeriodV1Marker = datetime_marker_helper!(@data/zone/metazone_periods, yes); -} - -impl DateTimeMarkers for NeoTimeZoneSkeleton { - type D = NeoNeverMarker; - type T = NeoNeverMarker; - type Z = Self; - type LengthOption = datetime_marker_helper!(@option/length, yes); - type AlignmentOption = datetime_marker_helper!(@option/alignment,); - type YearStyleOption = datetime_marker_helper!(@option/yearstyle,); - type FractionalSecondDigitsOption = datetime_marker_helper!(@option/fractionalsecondigits,); - type GluePatternV1Marker = datetime_marker_helper!(@glue,); -} - -impl_get_field!(NeoTimeZoneSkeleton, never); -impl_get_field!(NeoTimeZoneSkeleton, length, yes); - -impl UnstableSealed for NeoDateTimeSkeleton {} - -impl GetField for NeoDateTimeSkeleton { - fn get_field(&self) -> NeoComponents { - self.components.into() - } -} - -impl IsRuntimeComponents for NeoDateTimeSkeleton {} - -impl DateTimeNamesMarker for NeoDateTimeSkeleton { - type YearNames = datetime_marker_helper!(@names/year, yes); - type MonthNames = datetime_marker_helper!(@names/month, yes); - type WeekdayNames = datetime_marker_helper!(@names/weekday, yes); - type DayPeriodNames = datetime_marker_helper!(@names/dayperiod, yes); - type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials,); - type ZoneLocations = datetime_marker_helper!(@names/zone/locations,); - type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long,); - type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short,); - type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long,); - type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short,); - type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods,); -} - -impl DateTimeMarkers for NeoDateTimeSkeleton { - type D = NeoDateSkeleton; - type T = NeoTimeSkeleton; - type Z = NeoNeverMarker; - type LengthOption = datetime_marker_helper!(@option/length, yes); - type AlignmentOption = datetime_marker_helper!(@option/alignment, yes); - type YearStyleOption = datetime_marker_helper!(@option/yearstyle, yes); - type FractionalSecondDigitsOption = datetime_marker_helper!(@option/fractionalsecondigits, yes); - type GluePatternV1Marker = datetime_marker_helper!(@glue, yes); -} - -impl_get_field!(NeoDateTimeSkeleton, never); -impl_get_field!(NeoDateTimeSkeleton, length, yes); -impl_get_field!(NeoDateTimeSkeleton, alignment, yes); -impl_get_field!(NeoDateTimeSkeleton, year_style, yes); -impl_get_field!(NeoDateTimeSkeleton, fractional_second_digits, yes); - -impl UnstableSealed for NeoSkeleton {} - -impl GetField for NeoSkeleton { - fn get_field(&self) -> NeoComponents { - self.components - } -} - -impl IsRuntimeComponents for NeoSkeleton {} - -impl DateTimeNamesMarker for NeoSkeleton { - type YearNames = datetime_marker_helper!(@names/year, yes); - type MonthNames = datetime_marker_helper!(@names/month, yes); - type WeekdayNames = datetime_marker_helper!(@names/weekday, yes); - type DayPeriodNames = datetime_marker_helper!(@names/dayperiod, yes); - type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials, yes); - type ZoneLocations = datetime_marker_helper!(@names/zone/locations, yes); - type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long, yes); - type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short, yes); - type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long, yes); - type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short, yes); - type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods, yes); -} - -impl DateTimeMarkers for NeoSkeleton { - type D = NeoDateSkeleton; - type T = NeoTimeSkeleton; - type Z = NeoTimeZoneSkeleton; - type LengthOption = datetime_marker_helper!(@option/length, yes); - type AlignmentOption = datetime_marker_helper!(@option/alignment, yes); - type YearStyleOption = datetime_marker_helper!(@option/yearstyle, yes); - type FractionalSecondDigitsOption = datetime_marker_helper!(@option/fractionalsecondigits, yes); - type GluePatternV1Marker = datetime_marker_helper!(@glue, yes); -} - -impl_get_field!(NeoSkeleton, never); -impl_get_field!(NeoSkeleton, length, yes); -impl_get_field!(NeoSkeleton, alignment, yes); -impl_get_field!(NeoSkeleton, year_style, yes); -impl_get_field!(NeoSkeleton, fractional_second_digits, yes); diff --git a/components/datetime/src/neo_pattern.rs b/components/datetime/src/neo_pattern.rs index 37080627338..e9b245bf5e4 100644 --- a/components/datetime/src/neo_pattern.rs +++ b/components/datetime/src/neo_pattern.rs @@ -8,8 +8,8 @@ use core::str::FromStr; use writeable::{impl_display_with_writeable, Writeable}; -use crate::helpers::size_test; use crate::provider::pattern::{runtime, PatternError, PatternItem}; +use crate::size_test_macro::size_test; size_test!(DateTimePattern, date_time_pattern_size, 32); diff --git a/components/datetime/src/neo_serde.rs b/components/datetime/src/neo_serde.rs index 2acc613dff1..4e2ae5f5c6a 100644 --- a/components/datetime/src/neo_serde.rs +++ b/components/datetime/src/neo_serde.rs @@ -4,11 +4,7 @@ //! Serde definitions for semantic skeleta -use crate::neo_skeleton::{ - Alignment, FractionalSecondDigits, NeoCalendarPeriodComponents, NeoComponents, - NeoDateComponents, NeoSkeleton, NeoSkeletonLength, NeoTimeComponents, NeoTimeZoneStyle, - YearStyle, -}; +use crate::{dynamic::*, fieldset, neo_skeleton::*, raw::neo::RawNeoOptions}; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; @@ -26,37 +22,200 @@ pub(crate) enum Error { #[derive(Serialize, Deserialize)] pub(crate) struct SemanticSkeletonSerde { #[serde(rename = "fieldSet")] - pub(crate) field_set: NeoComponents, + pub(crate) field_set: FieldSetSerde, pub(crate) length: NeoSkeletonLength, pub(crate) alignment: Option, #[serde(rename = "yearStyle")] pub(crate) year_style: Option, - #[serde(rename = "fractionalSecondDigits")] - pub(crate) fractional_second_digits: Option, + #[serde(rename = "timePrecision")] + pub(crate) time_precision: Option, } -impl From for SemanticSkeletonSerde { - fn from(value: NeoSkeleton) -> Self { +impl From for SemanticSkeletonSerde { + fn from(value: CompositeFieldSet) -> Self { + let (serde_field, options) = match value { + CompositeFieldSet::Date(v) => FieldSetSerde::from_date_field_set(v), + CompositeFieldSet::CalendarPeriod(v) => { + FieldSetSerde::from_calendar_period_field_set(v) + } + CompositeFieldSet::Time(v) => FieldSetSerde::from_time_field_set(v), + CompositeFieldSet::Zone(v) => FieldSetSerde::from_zone_field_set(v), + CompositeFieldSet::DateTime(v) => { + let (date_serde, date_options) = + FieldSetSerde::from_date_field_set(v.to_date_field_set()); + let (time_serde, time_options) = + FieldSetSerde::from_time_field_set(v.to_time_field_set()); + ( + date_serde.extend(time_serde), + date_options.merge(time_options), + ) + } + CompositeFieldSet::DateZone(v, z) => { + let (date_serde, date_options) = FieldSetSerde::from_date_field_set(v); + let zone_serde = FieldSetSerde::from_time_zone_style(z); + (date_serde.extend(zone_serde), date_options) + } + CompositeFieldSet::TimeZone(v, z) => { + let (time_serde, time_options) = FieldSetSerde::from_time_field_set(v); + let zone_serde = FieldSetSerde::from_time_zone_style(z); + (time_serde.extend(zone_serde), time_options) + } + CompositeFieldSet::DateTimeZone(v, z) => { + let (date_serde, date_options) = + FieldSetSerde::from_date_field_set(v.to_date_field_set()); + let (time_serde, time_options) = + FieldSetSerde::from_time_field_set(v.to_time_field_set()); + let zone_serde = FieldSetSerde::from_time_zone_style(z); + ( + date_serde.extend(time_serde).extend(zone_serde), + date_options.merge(time_options), + ) + } + }; Self { - field_set: value.components, - length: value.length, - alignment: value.alignment, - year_style: value.year_style, - fractional_second_digits: value.fractional_second_digits, + field_set: serde_field, + length: options.length, + alignment: options.alignment, + year_style: options.year_style, + time_precision: options.time_precision, } } } -impl TryFrom for NeoSkeleton { - type Error = core::convert::Infallible; +impl TryFrom for CompositeFieldSet { + type Error = Error; fn try_from(value: SemanticSkeletonSerde) -> Result { - Ok(NeoSkeleton { + let date = value.field_set.date_only(); + let time = value.field_set.time_only(); + let zone = value.field_set.zone_only(); + let options = RawNeoOptions { length: value.length, - components: value.field_set, alignment: value.alignment, year_style: value.year_style, - fractional_second_digits: value.fractional_second_digits, - }) + time_precision: value.time_precision, + }; + match (!date.is_empty(), !time.is_empty(), !zone.is_empty()) { + (true, false, false) => date + .to_date_field_set(options) + .map(CompositeFieldSet::Date) + .or_else(|| { + date.to_calendar_period_field_set(options) + .map(CompositeFieldSet::CalendarPeriod) + }) + .ok_or(Error::InvalidFields), + (false, true, false) => time + .to_time_field_set(options) + .map(CompositeFieldSet::Time) + .ok_or(Error::InvalidFields), + (false, false, true) => zone + .to_zone_field_set(options) + .map(CompositeFieldSet::Zone) + .ok_or(Error::InvalidFields), + (true, true, false) => date + .to_date_field_set(options) + .map(|date_field_set| { + CompositeFieldSet::DateTime( + DateAndTimeFieldSet::from_date_field_set_with_raw_options( + date_field_set, + options, + ), + ) + }) + .ok_or(Error::InvalidFields), + (true, false, true) => date + .to_date_field_set(options) + .and_then(|date_field_set| { + zone.to_time_zone_style() + .map(|style| CompositeFieldSet::DateZone(date_field_set, style)) + }) + .ok_or(Error::InvalidFields), + (false, true, true) => time + .to_time_field_set(options) + .and_then(|time_field_set| { + zone.to_time_zone_style() + .map(|style| CompositeFieldSet::TimeZone(time_field_set, style)) + }) + .ok_or(Error::InvalidFields), + (true, true, true) => date + .to_date_field_set(options) + .and_then(|date_field_set| { + zone.to_time_zone_style().map(|style| { + CompositeFieldSet::DateTimeZone( + DateAndTimeFieldSet::from_date_field_set_with_raw_options( + date_field_set, + options, + ), + style, + ) + }) + }) + .ok_or(Error::InvalidFields), + (false, false, false) => Err(Error::NoFields), + } + } +} + +#[derive(Copy, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) enum TimePrecisionSerde { + HourPlus, + HourExact, + MinutePlus, + MinuteExact, + SecondPlus, + SecondF0, + SecondF1, + SecondF2, + SecondF3, + SecondF4, + SecondF5, + SecondF6, + SecondF7, + SecondF8, + SecondF9, +} + +impl From for TimePrecisionSerde { + fn from(value: TimePrecision) -> Self { + match value { + TimePrecision::HourPlus => TimePrecisionSerde::HourPlus, + TimePrecision::HourExact => TimePrecisionSerde::HourExact, + TimePrecision::MinutePlus => TimePrecisionSerde::MinutePlus, + TimePrecision::MinuteExact => TimePrecisionSerde::MinuteExact, + TimePrecision::SecondPlus => TimePrecisionSerde::SecondPlus, + TimePrecision::SecondExact(FractionalSecondDigits::F0) => TimePrecisionSerde::SecondF0, + TimePrecision::SecondExact(FractionalSecondDigits::F1) => TimePrecisionSerde::SecondF1, + TimePrecision::SecondExact(FractionalSecondDigits::F2) => TimePrecisionSerde::SecondF2, + TimePrecision::SecondExact(FractionalSecondDigits::F3) => TimePrecisionSerde::SecondF3, + TimePrecision::SecondExact(FractionalSecondDigits::F4) => TimePrecisionSerde::SecondF4, + TimePrecision::SecondExact(FractionalSecondDigits::F5) => TimePrecisionSerde::SecondF5, + TimePrecision::SecondExact(FractionalSecondDigits::F6) => TimePrecisionSerde::SecondF6, + TimePrecision::SecondExact(FractionalSecondDigits::F7) => TimePrecisionSerde::SecondF7, + TimePrecision::SecondExact(FractionalSecondDigits::F8) => TimePrecisionSerde::SecondF8, + TimePrecision::SecondExact(FractionalSecondDigits::F9) => TimePrecisionSerde::SecondF9, + } + } +} + +impl From for TimePrecision { + fn from(value: TimePrecisionSerde) -> Self { + match value { + TimePrecisionSerde::HourPlus => TimePrecision::HourPlus, + TimePrecisionSerde::HourExact => TimePrecision::HourExact, + TimePrecisionSerde::MinutePlus => TimePrecision::MinutePlus, + TimePrecisionSerde::MinuteExact => TimePrecision::MinuteExact, + TimePrecisionSerde::SecondPlus => TimePrecision::SecondPlus, + TimePrecisionSerde::SecondF0 => TimePrecision::SecondExact(FractionalSecondDigits::F0), + TimePrecisionSerde::SecondF1 => TimePrecision::SecondExact(FractionalSecondDigits::F1), + TimePrecisionSerde::SecondF2 => TimePrecision::SecondExact(FractionalSecondDigits::F2), + TimePrecisionSerde::SecondF3 => TimePrecision::SecondExact(FractionalSecondDigits::F3), + TimePrecisionSerde::SecondF4 => TimePrecision::SecondExact(FractionalSecondDigits::F4), + TimePrecisionSerde::SecondF5 => TimePrecision::SecondExact(FractionalSecondDigits::F5), + TimePrecisionSerde::SecondF6 => TimePrecision::SecondExact(FractionalSecondDigits::F6), + TimePrecisionSerde::SecondF7 => TimePrecision::SecondExact(FractionalSecondDigits::F7), + TimePrecisionSerde::SecondF8 => TimePrecision::SecondExact(FractionalSecondDigits::F8), + TimePrecisionSerde::SecondF9 => TimePrecision::SecondExact(FractionalSecondDigits::F9), + } } } @@ -71,9 +230,7 @@ enum FieldSetField { WeekOfYear = 5, WeekOfMonth = 6, // Time Fields - Hour = 16, - Minute = 17, - Second = 18, + Time = 16, // Zone Fields ZoneGeneric = 32, ZoneGenericShort = 33, @@ -91,9 +248,7 @@ impl FieldSetField { Month, Day, Weekday, - Hour, - Minute, - Second, + Time, WeekOfYear, WeekOfMonth, ZoneGeneric, @@ -155,18 +310,15 @@ impl FieldSetSerde { const MONTH: Self = Self::from_fields(&[Month]); const YEAR_MONTH: Self = Self::from_fields(&[Year, Month]); const YEAR: Self = Self::from_fields(&[Year]); - const YEAR_WEEK: Self = Self::from_fields(&[Year, WeekOfYear]); // Time Components - const HOUR: Self = Self::from_fields(&[Hour]); - const HOUR_MINUTE: Self = Self::from_fields(&[Hour, Minute]); - const HOUR_MINUTE_SECOND: Self = Self::from_fields(&[Hour, Minute, Second]); + const TIME: Self = Self::from_fields(&[Time]); // Zone Components - const ZONE_GENERIC: Self = Self::from_fields(&[ZoneGeneric]); const ZONE_SPECIFIC: Self = Self::from_fields(&[ZoneSpecific]); - const ZONE_LOCATION: Self = Self::from_fields(&[ZoneLocation]); const ZONE_OFFSET: Self = Self::from_fields(&[ZoneOffset]); + const ZONE_GENERIC: Self = Self::from_fields(&[ZoneGeneric]); + const ZONE_LOCATION: Self = Self::from_fields(&[ZoneLocation]); const fn from_fields(fields: &[FieldSetField]) -> Self { let mut bit_fields = 0; @@ -245,184 +397,129 @@ impl<'de> Deserialize<'de> for FieldSetSerde { } } -impl From for FieldSetSerde { - fn from(value: NeoDateComponents) -> Self { +impl FieldSetSerde { + fn from_date_field_set(value: DateFieldSet) -> (Self, RawNeoOptions) { match value { - NeoDateComponents::Day => Self::DAY, - NeoDateComponents::MonthDay => Self::MONTH_DAY, - NeoDateComponents::YearMonthDay => Self::YEAR_MONTH_DAY, - NeoDateComponents::DayWeekday => Self::DAY_WEEKDAY, - NeoDateComponents::MonthDayWeekday => Self::MONTH_DAY_WEEKDAY, - NeoDateComponents::YearMonthDayWeekday => Self::YEAR_MONTH_DAY_WEEKDAY, - NeoDateComponents::Weekday => Self::WEEKDAY, - // TODO: support auto? - NeoDateComponents::Auto => Self::YEAR_MONTH_DAY, - NeoDateComponents::AutoWeekday => Self::YEAR_MONTH_DAY_WEEKDAY, + DateFieldSet::D(v) => (Self::DAY, v.to_raw_options()), + DateFieldSet::MD(v) => (Self::MONTH_DAY, v.to_raw_options()), + DateFieldSet::YMD(v) => (Self::YEAR_MONTH_DAY, v.to_raw_options()), + DateFieldSet::DE(v) => (Self::DAY_WEEKDAY, v.to_raw_options()), + DateFieldSet::MDE(v) => (Self::MONTH_DAY_WEEKDAY, v.to_raw_options()), + DateFieldSet::YMDE(v) => (Self::YEAR_MONTH_DAY_WEEKDAY, v.to_raw_options()), + DateFieldSet::E(v) => (Self::WEEKDAY, v.to_raw_options()), } } -} -impl TryFrom for NeoDateComponents { - type Error = Error; - fn try_from(value: FieldSetSerde) -> Result { - match value { - FieldSetSerde::DAY => Ok(Self::Day), - FieldSetSerde::MONTH_DAY => Ok(Self::MonthDay), - FieldSetSerde::YEAR_MONTH_DAY => Ok(Self::YearMonthDay), - FieldSetSerde::DAY_WEEKDAY => Ok(Self::DayWeekday), - FieldSetSerde::MONTH_DAY_WEEKDAY => Ok(Self::MonthDayWeekday), - FieldSetSerde::YEAR_MONTH_DAY_WEEKDAY => Ok(Self::YearMonthDayWeekday), - FieldSetSerde::WEEKDAY => Ok(Self::Weekday), - _ => Err(Error::InvalidFields), + fn to_date_field_set(self, options: RawNeoOptions) -> Option { + use DateFieldSet::*; + match self { + Self::DAY => Some(D(fieldset::D::from_raw_options(options))), + Self::MONTH_DAY => Some(MD(fieldset::MD::from_raw_options(options))), + Self::YEAR_MONTH_DAY => Some(YMD(fieldset::YMD::from_raw_options(options))), + Self::DAY_WEEKDAY => Some(DE(fieldset::DE::from_raw_options(options))), + Self::MONTH_DAY_WEEKDAY => Some(MDE(fieldset::MDE::from_raw_options(options))), + Self::YEAR_MONTH_DAY_WEEKDAY => Some(YMDE(fieldset::YMDE::from_raw_options(options))), + Self::WEEKDAY => Some(E(fieldset::E::from_raw_options(options))), + _ => None, } } -} -impl From for FieldSetSerde { - fn from(value: NeoCalendarPeriodComponents) -> Self { + fn from_calendar_period_field_set(value: CalendarPeriodFieldSet) -> (Self, RawNeoOptions) { match value { - NeoCalendarPeriodComponents::Month => Self::MONTH, - NeoCalendarPeriodComponents::YearMonth => Self::YEAR_MONTH, - NeoCalendarPeriodComponents::Year => Self::YEAR, - NeoCalendarPeriodComponents::YearWeek => Self::YEAR_WEEK, + CalendarPeriodFieldSet::M(v) => (Self::MONTH, v.to_raw_options()), + CalendarPeriodFieldSet::YM(v) => (Self::YEAR_MONTH, v.to_raw_options()), + CalendarPeriodFieldSet::Y(v) => (Self::YEAR, v.to_raw_options()), } } -} -impl TryFrom for NeoCalendarPeriodComponents { - type Error = Error; - fn try_from(value: FieldSetSerde) -> Result { - match value { - FieldSetSerde::MONTH => Ok(Self::Month), - FieldSetSerde::YEAR_MONTH => Ok(Self::YearMonth), - FieldSetSerde::YEAR => Ok(Self::Year), - FieldSetSerde::YEAR_WEEK => Ok(Self::YearWeek), - _ => Err(Error::InvalidFields), + fn to_calendar_period_field_set( + self, + options: RawNeoOptions, + ) -> Option { + use CalendarPeriodFieldSet::*; + match self { + Self::MONTH => Some(M(fieldset::M::from_raw_options(options))), + Self::YEAR_MONTH => Some(YM(fieldset::YM::from_raw_options(options))), + Self::YEAR => Some(Y(fieldset::Y::from_raw_options(options))), + _ => None, } } -} -impl From for FieldSetSerde { - fn from(value: NeoTimeComponents) -> Self { + fn from_time_field_set(value: TimeFieldSet) -> (Self, RawNeoOptions) { match value { - NeoTimeComponents::Hour => Self::HOUR, - NeoTimeComponents::HourMinute => Self::HOUR_MINUTE, - NeoTimeComponents::HourMinuteSecond => Self::HOUR_MINUTE_SECOND, - // TODO: support auto? - NeoTimeComponents::Auto => Self::HOUR_MINUTE, - _ => todo!(), + TimeFieldSet::T(v) => (Self::TIME, v.to_raw_options()), } } -} -impl TryFrom for NeoTimeComponents { - type Error = Error; - fn try_from(value: FieldSetSerde) -> Result { - match value { - FieldSetSerde::HOUR => Ok(Self::Hour), - FieldSetSerde::HOUR_MINUTE => Ok(Self::HourMinute), - FieldSetSerde::HOUR_MINUTE_SECOND => Ok(Self::HourMinuteSecond), - _ => Err(Error::InvalidFields), + fn to_time_field_set(self, options: RawNeoOptions) -> Option { + use TimeFieldSet::*; + match self { + Self::TIME => Some(T(fieldset::T::from_raw_options(options))), + _ => None, } } -} -impl From for FieldSetSerde { - fn from(value: NeoTimeZoneStyle) -> Self { + fn from_time_zone_style(value: ZoneStyle) -> Self { match value { - NeoTimeZoneStyle::Location => Self::ZONE_LOCATION, - NeoTimeZoneStyle::Generic => Self::ZONE_GENERIC, - NeoTimeZoneStyle::Specific => Self::ZONE_SPECIFIC, - NeoTimeZoneStyle::Offset => Self::ZONE_OFFSET, - _ => todo!(), + ZoneStyle::Z => Self::ZONE_SPECIFIC, + ZoneStyle::O => Self::ZONE_OFFSET, + ZoneStyle::V => Self::ZONE_GENERIC, + ZoneStyle::L => Self::ZONE_LOCATION, } } -} -impl TryFrom for NeoTimeZoneStyle { - type Error = Error; - fn try_from(value: FieldSetSerde) -> Result { - match value { - FieldSetSerde::ZONE_LOCATION => Ok(NeoTimeZoneStyle::Location), - FieldSetSerde::ZONE_GENERIC => Ok(NeoTimeZoneStyle::Generic), - FieldSetSerde::ZONE_SPECIFIC => Ok(NeoTimeZoneStyle::Specific), - FieldSetSerde::ZONE_OFFSET => Ok(NeoTimeZoneStyle::Offset), - _ => Err(Error::InvalidFields), + fn to_time_zone_style(self) -> Option { + match self { + FieldSetSerde::ZONE_SPECIFIC => Some(ZoneStyle::Z), + FieldSetSerde::ZONE_OFFSET => Some(ZoneStyle::O), + FieldSetSerde::ZONE_GENERIC => Some(ZoneStyle::V), + FieldSetSerde::ZONE_LOCATION => Some(ZoneStyle::L), + _ => None, } } -} -impl From for FieldSetSerde { - fn from(value: NeoComponents) -> Self { + fn from_zone_field_set(value: ZoneFieldSet) -> (Self, RawNeoOptions) { match value { - NeoComponents::Date(date) => FieldSetSerde::from(date), - NeoComponents::CalendarPeriod(calendar_period) => FieldSetSerde::from(calendar_period), - NeoComponents::Time(time) => FieldSetSerde::from(time), - NeoComponents::Zone(zone) => FieldSetSerde::from(zone), - NeoComponents::DateTime(day, time) => { - FieldSetSerde::from(day).extend(FieldSetSerde::from(time)) - } - NeoComponents::DateZone(date, zone) => { - FieldSetSerde::from(date).extend(FieldSetSerde::from(zone)) - } - NeoComponents::TimeZone(time, zone) => { - FieldSetSerde::from(time).extend(FieldSetSerde::from(zone)) - } - NeoComponents::DateTimeZone(day, time, zone) => FieldSetSerde::from(day) - .extend(FieldSetSerde::from(time)) - .extend(FieldSetSerde::from(zone)), + ZoneFieldSet::Z(v) => (Self::ZONE_SPECIFIC, v.to_raw_options()), + ZoneFieldSet::O(v) => (Self::ZONE_OFFSET, v.to_raw_options()), + ZoneFieldSet::V(v) => (Self::ZONE_GENERIC, v.to_raw_options()), + ZoneFieldSet::L(v) => (Self::ZONE_LOCATION, v.to_raw_options()), } } -} -impl TryFrom for NeoComponents { - type Error = Error; - fn try_from(value: FieldSetSerde) -> Result { - let date = value.date_only(); - let time = value.time_only(); - let zone = value.zone_only(); - match (!date.is_empty(), !time.is_empty(), !zone.is_empty()) { - (true, false, false) => NeoDateComponents::try_from(date) - .map(NeoComponents::from) - .or_else(|_| NeoCalendarPeriodComponents::try_from(date).map(NeoComponents::from)), - (false, true, false) => Ok(NeoComponents::Time(time.try_into()?)), - (false, false, true) => Ok(NeoComponents::Zone(zone.try_into()?)), - (true, true, false) => Ok(NeoComponents::DateTime(date.try_into()?, time.try_into()?)), - (true, false, true) => Ok(NeoComponents::DateZone(date.try_into()?, zone.try_into()?)), - (false, true, true) => Ok(NeoComponents::TimeZone(time.try_into()?, zone.try_into()?)), - (true, true, true) => Ok(NeoComponents::DateTimeZone( - date.try_into()?, - time.try_into()?, - zone.try_into()?, - )), - (false, false, false) => Err(Error::NoFields), + fn to_zone_field_set(self, options: RawNeoOptions) -> Option { + use ZoneFieldSet::*; + match self { + Self::ZONE_SPECIFIC => Some(Z(fieldset::Z::from_raw_options(options))), + Self::ZONE_OFFSET => Some(O(fieldset::O::from_raw_options(options))), + Self::ZONE_GENERIC => Some(V(fieldset::V::from_raw_options(options))), + Self::ZONE_LOCATION => Some(L(fieldset::L::from_raw_options(options))), + _ => None, } } } #[test] fn test_basic() { - let skeleton = NeoSkeleton { - components: NeoComponents::DateTimeZone( - NeoDateComponents::YearMonthDayWeekday, - NeoTimeComponents::HourMinute, - NeoTimeZoneStyle::Generic, - ), - length: NeoSkeletonLength::Medium, - alignment: Some(Alignment::Column), - year_style: Some(YearStyle::Always), - fractional_second_digits: Some(FractionalSecondDigits::F3), - }; + let skeleton = CompositeFieldSet::DateTimeZone( + DateAndTimeFieldSet::YMDET(fieldset::YMDET { + length: NeoSkeletonLength::Medium, + alignment: Some(Alignment::Column), + year_style: Some(YearStyle::Always), + time_precision: Some(TimePrecision::SecondExact(FractionalSecondDigits::F3)), + }), + ZoneStyle::V, + ); let json_string = serde_json::to_string(&skeleton).unwrap(); assert_eq!( json_string, - r#"{"fieldSet":["year","month","day","weekday","hour","minute","zoneGeneric"],"length":"medium","alignment":"column","yearStyle":"always","fractionalSecondDigits":3}"# + r#"{"fieldSet":["year","month","day","weekday","time","zoneGeneric"],"length":"medium","alignment":"column","yearStyle":"always","timePrecision":"secondF3"}"# ); - let json_skeleton = serde_json::from_str::(&json_string).unwrap(); + let json_skeleton = serde_json::from_str::(&json_string).unwrap(); assert_eq!(skeleton, json_skeleton); let bincode_bytes = bincode::serialize(&skeleton).unwrap(); - let bincode_skeleton = bincode::deserialize::(&bincode_bytes).unwrap(); + let bincode_skeleton = bincode::deserialize::(&bincode_bytes).unwrap(); assert_eq!(skeleton, bincode_skeleton); } diff --git a/components/datetime/src/neo_skeleton.rs b/components/datetime/src/neo_skeleton.rs index 6c453c46ec5..fe533f52435 100644 --- a/components/datetime/src/neo_skeleton.rs +++ b/components/datetime/src/neo_skeleton.rs @@ -8,9 +8,6 @@ use crate::neo_serde::*; #[cfg(feature = "datagen")] use crate::options::{self, length}; -use crate::provider::pattern::CoarseHourCycle; -use crate::time_zone::ResolvedNeoTimeZoneSkeleton; -use icu_provider::DataMarkerAttributes; use icu_timezone::scaffold::IntoOption; /// A specification for the length of a date or component of a date. @@ -144,6 +141,95 @@ impl IntoOption for YearStyle { } } +/// A specification for how precisely to display the time of day. +/// +/// The examples below are based on the following inputs and hour cycles: +/// +/// 1. 11 o'clock with 12-hour time +/// 2. 16:20 (4:20 pm) with 24-hour time +/// 3. 7:15:01.85 with 24-hour time +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + serde(from = "TimePrecisionSerde", into = "TimePrecisionSerde") +)] +#[non_exhaustive] +pub enum TimePrecision { + /// Always display the hour. Display smaller fields if they are nonzero. + /// + /// Examples: + /// + /// 1. `11 am` + /// 2. `16:20` + /// 3. `07:15:01.85` + HourPlus, + /// Always display the hour. Hide all other time fields. + /// + /// Examples: + /// + /// 1. `11 am` + /// 2. `16h` + /// 3. `07h` + HourExact, + /// Always display the hour and minute. Display the second if nonzero. + /// + /// Examples: + /// + /// 1. `11:00 am` + /// 2. `16:20` + /// 3. `07:15:01.85` + MinutePlus, + /// Always display the hour and minute. Hide the second. + /// + /// Examples: + /// + /// 1. `11:00 am` + /// 2. `16:20` + /// 3. `07:15` + MinuteExact, + /// Display the hour, minute, and second. Display fractional seconds if nonzero. + /// + /// This is the default. + /// + /// Examples: + /// + /// 1. `11:00:00 am` + /// 2. `16:20:00` + /// 3. `07:15:01.85` + SecondPlus, + /// Display the hour, minute, and second with the given number of + /// fractional second digits. + /// + /// Examples with [`FractionalSecondDigits::F1`]: + /// + /// 1. `11:00:00.0 am` + /// 2. `16:20:00.0` + /// 3. `07:15:01.8` + SecondExact(FractionalSecondDigits), +} + +impl IntoOption for TimePrecision { + #[inline] + fn into_option(self) -> Option { + Some(self) + } +} + +impl TimePrecision { + /// Converts a [`length::Time`] to its nearest [`TimePrecision`]. + #[doc(hidden)] // the types involved in this mapping may change + #[cfg(feature = "datagen")] + pub fn from_time_length(time_length: length::Time) -> Self { + match time_length { + length::Time::Full => todo!(), + length::Time::Long => todo!(), + length::Time::Medium => Self::SecondPlus, + length::Time::Short => Self::MinuteExact, + } + } +} + /// A specification for how many fractional second digits to display. /// /// For example, to display the time with millisecond precision, use @@ -177,13 +263,6 @@ pub enum FractionalSecondDigits { F9, } -impl IntoOption for FractionalSecondDigits { - #[inline] - fn into_option(self) -> Option { - Some(self) - } -} - /// An error from constructing [`FractionalSecondDigits`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, displaydoc::Display)] #[non_exhaustive] @@ -229,1149 +308,3 @@ impl TryFrom for FractionalSecondDigits { } } } - -/// A specification for a set of parts of a date that specifies a single day (as -/// opposed to a whole month or a week). -/// -/// Only sets that yield “sensible” dates are allowed: this type cannot -/// describe a date such as “some Tuesday in 2023”. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum NeoDateComponents { - /// The day of the month, as in - /// “on the 1st”. - Day, - /// The month and day of the month, as in - /// “January 1st”. - MonthDay, - /// The year, month, and day of the month, as in - /// “January 1st, 2000”. - YearMonthDay, - /// The day of the month and day of the week, as in - /// “Saturday 1st”. - DayWeekday, - /// The month, day of the month, and day of the week, as in - /// “Saturday, January 1st”. - MonthDayWeekday, - /// The year, month, day of the month, and day of the week, as in - /// “Saturday, January 1st, 2000”. - YearMonthDayWeekday, - /// The day of the week alone, as in - /// “Saturday”. - Weekday, - /// Fields to represent the day chosen by locale. - /// - /// These are the _standard date patterns_ for types "long", "medium", and - /// "short" as defined in [UTS 35]. For "full", use - /// [`AutoWeekday`](NeoDateComponents::AutoWeekday). - /// - /// This is often, but not always, the same as - /// [`YearMonthDay`](NeoDateComponents::YearMonthDay). - /// - /// [UTS 35]: - Auto, - /// Fields to represent the day chosen by locale, hinting to also include - /// a weekday field. - /// - /// This is the _standard date pattern_ for type "full", extended with - /// skeleton data in the short and medium forms. - /// - /// This is often, but not always, the same as - /// [`YearMonthDayWeekday`](NeoDateComponents::YearMonthDayWeekday). - AutoWeekday, -} - -impl NeoDateComponents { - /// All values of this enum. - pub const VALUES: &'static [Self] = &[ - Self::Day, - Self::MonthDay, - Self::YearMonthDay, - Self::DayWeekday, - Self::MonthDayWeekday, - Self::YearMonthDayWeekday, - Self::Weekday, - Self::Auto, - Self::AutoWeekday, - ]; - - const DAY: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("d"); - const MONTH_DAY: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("m0d"); - const YEAR_MONTH_DAY: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("ym0d"); - const DAY_WEEKDAY: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("de"); - const MONTH_DAY_WEEKDAY: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("m0de"); - const YEAR_MONTH_DAY_WEEKDAY: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("ym0de"); - const WEEKDAY: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("e"); - const AUTO: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("a1"); - const AUTO_WEEKDAY: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("a1e"); - - // For matching - const DAY_STR: &'static str = Self::DAY.as_str(); - const MONTH_DAY_STR: &'static str = Self::MONTH_DAY.as_str(); - const YEAR_MONTH_DAY_STR: &'static str = Self::YEAR_MONTH_DAY.as_str(); - const DAY_WEEKDAY_STR: &'static str = Self::DAY_WEEKDAY.as_str(); - const MONTH_DAY_WEEKDAY_STR: &'static str = Self::MONTH_DAY_WEEKDAY.as_str(); - const YEAR_MONTH_DAY_WEEKDAY_STR: &'static str = Self::YEAR_MONTH_DAY_WEEKDAY.as_str(); - const WEEKDAY_STR: &'static str = Self::WEEKDAY.as_str(); - const AUTO_STR: &'static str = Self::AUTO.as_str(); - const AUTO_WEEKDAY_STR: &'static str = Self::AUTO_WEEKDAY.as_str(); - - /// Returns a stable string identifying this set of components. - /// - /// # Encoding Details - /// - /// The string is based roughly on the UTS 35 symbol table with the following exceptions: - /// - /// 1. Lowercase letters are chosen where there is no ambiguity: `G` becomes `g`\* - /// 2. Capitals are replaced with their lowercase and a number 0: `M` becomes `m0` - /// 3. A single symbol is included for each component: length doesn't matter - /// 4. The "auto" style is represented as `a1` - /// - /// \* `g` represents a different field, but it is never used in skeleta. - /// - /// # Examples - /// - /// ``` - /// use icu::datetime::neo_skeleton::NeoDateComponents; - /// - /// assert_eq!( - /// "ym0de", - /// NeoDateComponents::YearMonthDayWeekday.id_str().as_str() - /// ); - /// ``` - pub const fn id_str(self) -> &'static DataMarkerAttributes { - match self { - Self::Day => Self::DAY, - Self::MonthDay => Self::MONTH_DAY, - Self::YearMonthDay => Self::YEAR_MONTH_DAY, - Self::DayWeekday => Self::DAY_WEEKDAY, - Self::MonthDayWeekday => Self::MONTH_DAY_WEEKDAY, - Self::YearMonthDayWeekday => Self::YEAR_MONTH_DAY_WEEKDAY, - Self::Weekday => Self::WEEKDAY, - Self::Auto => Self::AUTO, - Self::AutoWeekday => Self::AUTO_WEEKDAY, - } - } - - /// Returns the set of components for the given stable string. - /// - /// # Examples - /// - /// ``` - /// use icu::datetime::neo_skeleton::NeoDateComponents; - /// use icu_provider::prelude::*; - /// - /// assert_eq!( - /// NeoDateComponents::from_id_str( - /// DataMarkerAttributes::from_str_or_panic("ym0de") - /// ), - /// Some(NeoDateComponents::YearMonthDayWeekday) - /// ); - /// ``` - pub fn from_id_str(id_str: &DataMarkerAttributes) -> Option { - match &**id_str { - Self::DAY_STR => Some(Self::Day), - Self::MONTH_DAY_STR => Some(Self::MonthDay), - Self::YEAR_MONTH_DAY_STR => Some(Self::YearMonthDay), - Self::DAY_WEEKDAY_STR => Some(Self::DayWeekday), - Self::MONTH_DAY_WEEKDAY_STR => Some(Self::MonthDayWeekday), - Self::YEAR_MONTH_DAY_WEEKDAY_STR => Some(Self::YearMonthDayWeekday), - Self::WEEKDAY_STR => Some(Self::Weekday), - Self::AUTO_STR => Some(Self::Auto), - Self::AUTO_WEEKDAY_STR => Some(Self::AutoWeekday), - _ => None, - } - } - - /// Whether this field set contains the year. - pub fn has_year(self) -> bool { - match self { - Self::Day => false, - Self::MonthDay => false, - Self::YearMonthDay => true, - Self::DayWeekday => false, - Self::MonthDayWeekday => false, - Self::YearMonthDayWeekday => true, - Self::Weekday => false, - Self::Auto => true, - Self::AutoWeekday => true, - } - } - - /// Whether this field set contains the month. - pub fn has_month(self) -> bool { - match self { - Self::Day => false, - Self::MonthDay => true, - Self::YearMonthDay => true, - Self::DayWeekday => false, - Self::MonthDayWeekday => true, - Self::YearMonthDayWeekday => true, - Self::Weekday => false, - Self::Auto => true, - Self::AutoWeekday => true, - } - } - - /// Whether this field set contains the day of the month. - pub fn has_day(self) -> bool { - match self { - Self::Day => true, - Self::MonthDay => true, - Self::YearMonthDay => true, - Self::DayWeekday => true, - Self::MonthDayWeekday => true, - Self::YearMonthDayWeekday => true, - Self::Weekday => false, - Self::Auto => true, - Self::AutoWeekday => true, - } - } - - /// Whether this field set contains the weekday. - pub fn has_weekday(self) -> bool { - match self { - Self::Day => false, - Self::MonthDay => false, - Self::YearMonthDay => false, - Self::DayWeekday => true, - Self::MonthDayWeekday => true, - Self::YearMonthDayWeekday => true, - Self::Weekday => true, - Self::Auto => false, - Self::AutoWeekday => true, - } - } - - /// Creates a skeleton for this field set with a long length. - pub fn long(self) -> NeoDateSkeleton { - NeoDateSkeleton::for_length_and_components(NeoSkeletonLength::Long, self) - } - - /// Creates a skeleton for this field set with a medium length. - pub fn medium(self) -> NeoDateSkeleton { - NeoDateSkeleton::for_length_and_components(NeoSkeletonLength::Medium, self) - } - - /// Creates a skeleton for this field set with a short length. - pub fn short(self) -> NeoDateSkeleton { - NeoDateSkeleton::for_length_and_components(NeoSkeletonLength::Short, self) - } -} - -/// A specification for a set of parts of a date. -/// Only sets that yield “sensible” dates are allowed: this type cannot describe -/// a date such as “August, Anno Domini”. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum NeoCalendarPeriodComponents { - /// A standalone month, as in - /// “January”. - Month, - /// A month and year, as in - /// “January 2000”. - YearMonth, - /// A year, as in - /// “2000”. - Year, - /// The year and week of the year, as in - /// “52nd week of 1999”. - YearWeek, - // TODO(#501): Consider adding support for Quarter and YearQuarter. -} - -impl NeoCalendarPeriodComponents { - /// All values of this enum. - pub const VALUES: &'static [Self] = &[Self::Month, Self::YearMonth, Self::Year, Self::YearWeek]; - - const MONTH: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("m0"); - const YEAR_MONTH: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("ym0"); - const YEAR: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("y"); - const YEAR_WEEK: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("y0w"); - - // For matching - const MONTH_STR: &'static str = Self::MONTH.as_str(); - const YEAR_MONTH_STR: &'static str = Self::YEAR_MONTH.as_str(); - const YEAR_STR: &'static str = Self::YEAR.as_str(); - const YEAR_WEEK_STR: &'static str = Self::YEAR_WEEK.as_str(); - - /// Returns a stable string identifying this set of components. - /// - /// For details, see [`NeoDateComponents::id_str()`]. - pub const fn id_str(self) -> &'static DataMarkerAttributes { - match self { - Self::Month => Self::MONTH, - Self::YearMonth => Self::YEAR_MONTH, - Self::Year => Self::YEAR, - Self::YearWeek => Self::YEAR_WEEK, - } - } - - /// Returns the set of components for the given stable string. - /// - /// For details, see [`NeoDateComponents::from_id_str()`]. - pub fn from_id_str(id_str: &DataMarkerAttributes) -> Option { - match &**id_str { - Self::MONTH_STR => Some(Self::Month), - Self::YEAR_MONTH_STR => Some(Self::YearMonth), - Self::YEAR_STR => Some(Self::Year), - Self::YEAR_WEEK_STR => Some(Self::YearWeek), - _ => None, - } - } - - /// Whether this field set contains the year. - pub fn has_year(self) -> bool { - match self { - Self::Month => false, - Self::YearMonth => true, - Self::Year => true, - Self::YearWeek => true, - } - } - - /// Whether this field set contains the month. - pub fn has_month(self) -> bool { - match self { - Self::Month => true, - Self::YearMonth => true, - Self::Year => false, - Self::YearWeek => false, - } - } - - /// Creates a skeleton for this field set with a long length. - pub fn long(self) -> NeoCalendarPeriodSkeleton { - NeoCalendarPeriodSkeleton::for_length_and_components(NeoSkeletonLength::Long, self) - } - - /// Creates a skeleton for this field set with a medium length. - pub fn medium(self) -> NeoCalendarPeriodSkeleton { - NeoCalendarPeriodSkeleton::for_length_and_components(NeoSkeletonLength::Medium, self) - } - - /// Creates a skeleton for this field set with a short length. - pub fn short(self) -> NeoCalendarPeriodSkeleton { - NeoCalendarPeriodSkeleton::for_length_and_components(NeoSkeletonLength::Short, self) - } -} - -/// A specification for a set of parts of a time. -/// Only sets that yield “sensible” time are allowed: this type cannot describe -/// a time such as “am, 5 minutes, 25 milliseconds”. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum NeoTimeComponents { - /// An hour (12-hour or 24-hour chosen by locale), as in - /// "4 pm" or "16h" - Hour, - /// An hour and minute (12-hour or 24-hour chosen by locale), as in - /// "4:03 pm" or "16:03" - HourMinute, - /// An hour, minute, and second (12-hour or 24-hour chosen by locale), as in - /// "4:03:51 pm" or "16:03:51" - HourMinuteSecond, - /// An hour with a 12-hour clock and day period, as in - /// "4 in the afternoon" - DayPeriodHour12, - /// An hour with a 12-hour clock, as in - /// "4 pm" - Hour12, - /// An hour and minute with a 12-hour clock and a day period, as in - /// "4:03 in the afternoon" - DayPeriodHour12Minute, - /// An hour and minute with a 12-hour clock, as in - /// "4:03 pm" - Hour12Minute, - /// An hour, minute, and second with a 12-hour clock and day period, as in - /// "4:03:51 in the afternoon" - DayPeriodHour12MinuteSecond, - /// An hour, minute, and second with a 12-hour clock, as in - /// "4:03:51 pm" - Hour12MinuteSecond, - /// An hour with a 24-hour clock, as in - /// "16h" - Hour24, - /// An hour and minute with a 24-hour clock, as in - /// "16:03" - Hour24Minute, - /// An hour, minute, and second with a 24-hour clock, as in - /// "16:03:51" - Hour24MinuteSecond, - /// Fields to represent the time chosen by the locale. - /// - /// These are the _standard time patterns_ for types "medium" and - /// "short" as defined in [UTS 35]. For "full" and "long", use a - /// formatter that includes a time zone. - /// - /// [UTS 35]: - Auto, -} - -impl NeoTimeComponents { - /// All values of this enum. - pub const VALUES: &'static [Self] = &[ - Self::Hour, - Self::HourMinute, - Self::HourMinuteSecond, - Self::DayPeriodHour12, - Self::Hour12, - Self::DayPeriodHour12Minute, - Self::Hour12Minute, - Self::DayPeriodHour12MinuteSecond, - Self::Hour12MinuteSecond, - Self::Hour24, - Self::Hour24Minute, - Self::Hour24MinuteSecond, - Self::Auto, - ]; - - const HOUR: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("j"); - const HOUR_MINUTE: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("jm"); - const HOUR_MINUTE_SECOND: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("jms"); - const DAY_PERIOD_HOUR12: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("bh"); - const HOUR12: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("h"); - const DAY_PERIOD_HOUR12_MINUTE: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("bhm"); - const HOUR12_MINUTE: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("hm"); - const DAY_PERIOD_HOUR12_MINUTE_SECOND: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("bhms"); - const HOUR12_MINUTE_SECOND: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("hms"); - const HOUR24: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("h0"); - const HOUR24_MINUTE: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("h0m"); - const HOUR24_MINUTE_SECOND: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("h0ms"); - const AUTO: &'static DataMarkerAttributes = DataMarkerAttributes::from_str_or_panic("a1"); - - // For matching - const HOUR_STR: &'static str = Self::HOUR.as_str(); - const HOUR_MINUTE_STR: &'static str = Self::HOUR_MINUTE.as_str(); - const HOUR_MINUTE_SECOND_STR: &'static str = Self::HOUR_MINUTE_SECOND.as_str(); - const DAY_PERIOD_HOUR12_STR: &'static str = Self::DAY_PERIOD_HOUR12.as_str(); - const HOUR12_STR: &'static str = Self::HOUR12.as_str(); - const DAY_PERIOD_HOUR12_MINUTE_STR: &'static str = Self::DAY_PERIOD_HOUR12_MINUTE.as_str(); - const HOUR12_MINUTE_STR: &'static str = Self::HOUR12_MINUTE.as_str(); - const DAY_PERIOD_HOUR12_MINUTE_SECOND_STR: &'static str = - Self::DAY_PERIOD_HOUR12_MINUTE_SECOND.as_str(); - const HOUR12_MINUTE_SECOND_STR: &'static str = Self::HOUR12_MINUTE_SECOND.as_str(); - const HOUR24_STR: &'static str = Self::HOUR24.as_str(); - const HOUR24_MINUTE_STR: &'static str = Self::HOUR24_MINUTE.as_str(); - const HOUR24_MINUTE_SECOND_STR: &'static str = Self::HOUR24_MINUTE_SECOND.as_str(); - const AUTO_STR: &'static str = Self::AUTO.as_str(); - - /// Returns a stable string identifying this set of components. - /// - /// For details, see [`NeoDateComponents::id_str()`]. - pub const fn id_str(self) -> &'static DataMarkerAttributes { - match self { - Self::Hour => Self::HOUR, - Self::HourMinute => Self::HOUR_MINUTE, - Self::HourMinuteSecond => Self::HOUR_MINUTE_SECOND, - Self::DayPeriodHour12 => Self::DAY_PERIOD_HOUR12, - Self::Hour12 => Self::HOUR12, - Self::DayPeriodHour12Minute => Self::DAY_PERIOD_HOUR12_MINUTE, - Self::Hour12Minute => Self::HOUR12_MINUTE, - Self::DayPeriodHour12MinuteSecond => Self::DAY_PERIOD_HOUR12_MINUTE_SECOND, - Self::Hour12MinuteSecond => Self::HOUR12_MINUTE_SECOND, - Self::Hour24 => Self::HOUR24, - Self::Hour24Minute => Self::HOUR24_MINUTE, - Self::Hour24MinuteSecond => Self::HOUR24_MINUTE_SECOND, - Self::Auto => Self::AUTO, - } - } - - /// Returns the set of components for the given stable string. - /// - /// For details, see [`NeoDateComponents::from_id_str()`]. - pub fn from_id_str(id_str: &DataMarkerAttributes) -> Option { - match &**id_str { - Self::HOUR_STR => Some(Self::Hour), - Self::HOUR_MINUTE_STR => Some(Self::HourMinute), - Self::HOUR_MINUTE_SECOND_STR => Some(Self::HourMinuteSecond), - Self::DAY_PERIOD_HOUR12_STR => Some(Self::DayPeriodHour12), - Self::HOUR12_STR => Some(Self::Hour12), - Self::DAY_PERIOD_HOUR12_MINUTE_STR => Some(Self::DayPeriodHour12Minute), - Self::HOUR12_MINUTE_STR => Some(Self::Hour12Minute), - Self::DAY_PERIOD_HOUR12_MINUTE_SECOND_STR => Some(Self::DayPeriodHour12MinuteSecond), - Self::HOUR12_MINUTE_SECOND_STR => Some(Self::Hour12MinuteSecond), - Self::HOUR24_STR => Some(Self::Hour24), - Self::HOUR24_MINUTE_STR => Some(Self::Hour24Minute), - Self::HOUR24_MINUTE_SECOND_STR => Some(Self::Hour24MinuteSecond), - Self::AUTO_STR => Some(Self::Auto), - _ => None, - } - } - - pub(crate) fn with_hour_cycle(self, hour_cycle: CoarseHourCycle) -> Self { - use CoarseHourCycle::*; - match (self, hour_cycle) { - (Self::Hour, H11H12) => Self::Hour12, - (Self::HourMinute, H11H12) => Self::Hour12Minute, - (Self::HourMinuteSecond, H11H12) => Self::Hour12MinuteSecond, - (Self::Hour, H23H24) => Self::Hour24, - (Self::HourMinute, H23H24) => Self::Hour24Minute, - (Self::HourMinuteSecond, H23H24) => Self::Hour24MinuteSecond, - _ => self, - } - } - - /// Converts a [`length::Time`] to its nearest [`NeoTimeComponents`]. - #[doc(hidden)] // the types involved in this mapping may change - #[cfg(feature = "datagen")] - pub fn from_time_length(time_length: length::Time) -> Self { - match time_length { - length::Time::Full => todo!(), - length::Time::Long => todo!(), - length::Time::Medium => NeoTimeComponents::HourMinuteSecond, - length::Time::Short => NeoTimeComponents::HourMinute, - } - } - - /// Whether this field set contains the hour. - pub fn has_hour(self) -> bool { - true - } - - /// Whether this field set contains the minute. - pub fn has_minute(self) -> bool { - matches!( - self, - NeoTimeComponents::HourMinute - | NeoTimeComponents::Hour12Minute - | NeoTimeComponents::Hour24Minute - | NeoTimeComponents::HourMinuteSecond - | NeoTimeComponents::Hour12MinuteSecond - | NeoTimeComponents::Hour24MinuteSecond - ) - } - - /// Whether this field set contains the second. - pub fn has_second(self) -> bool { - matches!( - self, - NeoTimeComponents::HourMinuteSecond - | NeoTimeComponents::Hour12MinuteSecond - | NeoTimeComponents::Hour24MinuteSecond - ) - } - - /// Creates a skeleton for this field set with a long length. - pub fn long(self) -> NeoTimeSkeleton { - NeoTimeSkeleton::for_length_and_components(NeoSkeletonLength::Long, self) - } - - /// Creates a skeleton for this field set with a medium length. - pub fn medium(self) -> NeoTimeSkeleton { - NeoTimeSkeleton::for_length_and_components(NeoSkeletonLength::Medium, self) - } - - /// Creates a skeleton for this field set with a short length. - pub fn short(self) -> NeoTimeSkeleton { - NeoTimeSkeleton::for_length_and_components(NeoSkeletonLength::Short, self) - } -} - -/// A specification of components for parts of a datetime. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum NeoDateTimeComponents { - /// Components for parts of a date. - Date(NeoDateComponents), - /// Components for parts of a date with fields larger than a date. - CalendarPeriod(NeoCalendarPeriodComponents), - /// Components for parts of a time. - Time(NeoTimeComponents), - /// Components for parts of a date and time together. - DateTime(NeoDateComponents, NeoTimeComponents), -} - -impl From for NeoDateTimeComponents { - fn from(value: NeoDateComponents) -> Self { - Self::Date(value) - } -} - -impl From for NeoDateTimeComponents { - fn from(value: NeoCalendarPeriodComponents) -> Self { - Self::CalendarPeriod(value) - } -} - -impl From for NeoDateTimeComponents { - fn from(value: NeoTimeComponents) -> Self { - Self::Time(value) - } -} - -impl NeoDateTimeComponents { - /// Returns a [`NeoDateTimeComponents`] if it is a subset of the [`NeoComponents`] argument. - /// - /// If the [`NeoComponents`] contains a time zone, this function returns `None`. - pub fn try_from_components(components: NeoComponents) -> Option { - match components { - NeoComponents::Date(d) => Some(Self::Date(d)), - NeoComponents::CalendarPeriod(cp) => Some(Self::CalendarPeriod(cp)), - NeoComponents::Time(t) => Some(Self::Time(t)), - NeoComponents::Zone(_) => None, - NeoComponents::DateTime(d, t) => Some(Self::DateTime(d, t)), - NeoComponents::DateZone(_, _) => None, - NeoComponents::TimeZone(_, _) => None, - NeoComponents::DateTimeZone(_, _, _) => None, - } - } - - /// Creates a skeleton for this field set with a long length. - pub fn long(self) -> NeoDateTimeSkeleton { - NeoDateTimeSkeleton::for_length_and_components(NeoSkeletonLength::Long, self) - } - - /// Creates a skeleton for this field set with a medium length. - pub fn medium(self) -> NeoDateTimeSkeleton { - NeoDateTimeSkeleton::for_length_and_components(NeoSkeletonLength::Medium, self) - } - - /// Creates a skeleton for this field set with a short length. - pub fn short(self) -> NeoDateTimeSkeleton { - NeoDateTimeSkeleton::for_length_and_components(NeoSkeletonLength::Short, self) - } -} - -/// A specification of components for parts of a datetime and/or time zone. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr( - feature = "serde", - serde(try_from = "FieldSetSerde", into = "FieldSetSerde") -)] -#[non_exhaustive] -pub enum NeoComponents { - /// Components for a date. - Date(NeoDateComponents), - /// Components for a calendar period. - CalendarPeriod(NeoCalendarPeriodComponents), - /// Components for a time. - Time(NeoTimeComponents), - /// Components for a time zone. - Zone(NeoTimeZoneStyle), - /// Components for a date and a time together. - DateTime(NeoDateComponents, NeoTimeComponents), - /// Components for a date and a time zone together. - DateZone(NeoDateComponents, NeoTimeZoneStyle), - /// Components for a time and a time zone together. - TimeZone(NeoTimeComponents, NeoTimeZoneStyle), - /// Components for a date, a time, and a time zone together. - DateTimeZone(NeoDateComponents, NeoTimeComponents, NeoTimeZoneStyle), -} - -impl From for NeoComponents { - fn from(value: NeoDateComponents) -> Self { - Self::Date(value) - } -} - -impl From for NeoComponents { - fn from(value: NeoCalendarPeriodComponents) -> Self { - Self::CalendarPeriod(value) - } -} - -impl From for NeoComponents { - fn from(value: NeoTimeComponents) -> Self { - Self::Time(value) - } -} - -impl From for NeoComponents { - fn from(value: NeoDateTimeComponents) -> Self { - match value { - NeoDateTimeComponents::Date(components) => components.into(), - NeoDateTimeComponents::CalendarPeriod(components) => components.into(), - NeoDateTimeComponents::Time(components) => components.into(), - NeoDateTimeComponents::DateTime(day, time) => NeoComponents::DateTime(day, time), - } - } -} - -impl From for NeoComponents { - fn from(value: NeoTimeZoneStyle) -> Self { - Self::Zone(value) - } -} - -impl NeoComponents { - // Attributes for skeleta that span date/time/zone - // TODO: Add variants for H, h, and B hours - const WEEKDAY_HOUR_MINUTE: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("ejm"); - const WEEKDAY_HOUR_MINUTE_SECOND: &'static DataMarkerAttributes = - DataMarkerAttributes::from_str_or_panic("ejms"); - - // For matching - const WEEKDAY_HOUR_MINUTE_STR: &'static str = Self::WEEKDAY_HOUR_MINUTE.as_str(); - const WEEKDAY_HOUR_MINUTE_SECOND_STR: &'static str = Self::WEEKDAY_HOUR_MINUTE_SECOND.as_str(); - - #[doc(hidden)] // for datagen - pub fn attributes_with_overrides() -> &'static [&'static DataMarkerAttributes] { - &[Self::WEEKDAY_HOUR_MINUTE, Self::WEEKDAY_HOUR_MINUTE_SECOND] - } - - /// Returns a stable string identifying this field set, - /// but only if this set has its own pattern override. - /// - /// For details, see [`NeoDateComponents::id_str()`]. - pub const fn id_str(self) -> Option<&'static DataMarkerAttributes> { - match self { - Self::DateTime(NeoDateComponents::Weekday, NeoTimeComponents::HourMinute) => { - Some(Self::WEEKDAY_HOUR_MINUTE) - } - Self::DateTime(NeoDateComponents::Weekday, NeoTimeComponents::HourMinuteSecond) => { - Some(Self::WEEKDAY_HOUR_MINUTE_SECOND) - } - _ => None, - } - } - - /// Returns the field set for the given stable string, - /// but only if this set has its own pattern override. - /// - /// For details, see [`NeoDateComponents::from_id_str()`]. - pub fn from_id_str(id_str: &DataMarkerAttributes) -> Option { - match &**id_str { - Self::WEEKDAY_HOUR_MINUTE_STR => Some(Self::DateTime( - NeoDateComponents::Weekday, - NeoTimeComponents::HourMinute, - )), - Self::WEEKDAY_HOUR_MINUTE_SECOND_STR => Some(Self::DateTime( - NeoDateComponents::Weekday, - NeoTimeComponents::HourMinuteSecond, - )), - _ => None, - } - } - - /// Whether this field set contains the year. - pub fn has_year(self) -> bool { - match self { - NeoComponents::Date(date_components) => date_components.has_year(), - NeoComponents::CalendarPeriod(calendar_period_components) => { - calendar_period_components.has_year() - } - NeoComponents::Time(_) => todo!(), - NeoComponents::Zone(_) => todo!(), - NeoComponents::DateTime(date_components, _) => date_components.has_year(), - NeoComponents::DateZone(date_components, _) => date_components.has_year(), - NeoComponents::TimeZone(_, _) => todo!(), - NeoComponents::DateTimeZone(date_components, _, _) => date_components.has_year(), - } - } - - /// Whether this field set contains the month. - pub fn has_month(self) -> bool { - match self { - NeoComponents::Date(date_components) => date_components.has_month(), - NeoComponents::CalendarPeriod(calendar_period_components) => { - calendar_period_components.has_month() - } - NeoComponents::Time(_) => todo!(), - NeoComponents::Zone(_) => todo!(), - NeoComponents::DateTime(date_components, _) => date_components.has_month(), - NeoComponents::DateZone(date_components, _) => date_components.has_month(), - NeoComponents::TimeZone(_, _) => todo!(), - NeoComponents::DateTimeZone(date_components, _, _) => date_components.has_month(), - } - } - - /// Whether this field set contains the day of the month. - pub fn has_day(self) -> bool { - match self { - NeoComponents::Date(date_components) => date_components.has_day(), - NeoComponents::CalendarPeriod(_) => false, - NeoComponents::Time(_) => todo!(), - NeoComponents::Zone(_) => todo!(), - NeoComponents::DateTime(date_components, _) => date_components.has_day(), - NeoComponents::DateZone(date_components, _) => date_components.has_day(), - NeoComponents::TimeZone(_, _) => todo!(), - NeoComponents::DateTimeZone(date_components, _, _) => date_components.has_day(), - } - } - - /// Whether this field set contains the weekday. - pub fn has_weekday(self) -> bool { - match self { - NeoComponents::Date(date_components) => date_components.has_weekday(), - NeoComponents::CalendarPeriod(_) => false, - NeoComponents::Time(_) => todo!(), - NeoComponents::Zone(_) => todo!(), - NeoComponents::DateTime(date_components, _) => date_components.has_weekday(), - NeoComponents::DateZone(date_components, _) => date_components.has_weekday(), - NeoComponents::TimeZone(_, _) => todo!(), - NeoComponents::DateTimeZone(date_components, _, _) => date_components.has_weekday(), - } - } - - /// Whether this field set contains the hour. - pub fn has_hour(self) -> bool { - match self { - NeoComponents::Date(_) => false, - NeoComponents::CalendarPeriod(_) => false, - NeoComponents::Time(time_components) => time_components.has_hour(), - NeoComponents::Zone(_) => false, - NeoComponents::DateTime(_, time_components) => time_components.has_hour(), - NeoComponents::DateZone(_, _) => false, - NeoComponents::TimeZone(time_components, _) => time_components.has_hour(), - NeoComponents::DateTimeZone(_, time_components, _) => time_components.has_hour(), - } - } - - /// Whether this field set contains the minute. - pub fn has_minute(self) -> bool { - match self { - NeoComponents::Date(_) => false, - NeoComponents::CalendarPeriod(_) => false, - NeoComponents::Time(time_components) => time_components.has_minute(), - NeoComponents::Zone(_) => false, - NeoComponents::DateTime(_, time_components) => time_components.has_minute(), - NeoComponents::DateZone(_, _) => false, - NeoComponents::TimeZone(time_components, _) => time_components.has_minute(), - NeoComponents::DateTimeZone(_, time_components, _) => time_components.has_minute(), - } - } - - /// Whether this field set contains the second. - pub fn has_second(self) -> bool { - match self { - NeoComponents::Date(_) => false, - NeoComponents::CalendarPeriod(_) => false, - NeoComponents::Time(time_components) => time_components.has_second(), - NeoComponents::Zone(_) => false, - NeoComponents::DateTime(_, time_components) => time_components.has_second(), - NeoComponents::DateZone(_, _) => false, - NeoComponents::TimeZone(time_components, _) => time_components.has_second(), - NeoComponents::DateTimeZone(_, time_components, _) => time_components.has_second(), - } - } - - /// Creates a skeleton for this field set with a long length. - pub fn long(self) -> NeoSkeleton { - NeoSkeleton::for_length_and_components(NeoSkeletonLength::Long, self) - } - - /// Creates a skeleton for this field set with a medium length. - pub fn medium(self) -> NeoSkeleton { - NeoSkeleton::for_length_and_components(NeoSkeletonLength::Medium, self) - } - - /// Creates a skeleton for this field set with a short length. - pub fn short(self) -> NeoSkeleton { - NeoSkeleton::for_length_and_components(NeoSkeletonLength::Short, self) - } -} - -/// Specification of the time zone display style. -/// -/// Time zone names contribute a lot of data size. For resource-constrained -/// environments, the following formats require the least amount of data: -/// -/// - [`NeoTimeZoneStyle::Specific`] + [`NeoSkeletonLength::Short`] -/// - [`NeoTimeZoneStyle::Offset`] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] -#[non_exhaustive] -pub enum NeoTimeZoneStyle { - /// The location format, e.g., “Los Angeles time” or specific non-location - /// format “Pacific Daylight Time”, whichever is idiomatic for the locale. - /// > Note: for now, this is always identical to - /// > [`NeoTimeZoneStyle::Specific`] (Pacific Daylight Time), but - /// > whether it is [`NeoTimeZoneStyle::Generic`] or - /// > [`NeoTimeZoneStyle::Specific`] will be locale-dependent in the - /// > future; see - /// > [CLDR-15566](https://unicode-org.atlassian.net/browse/CLDR-15566). - #[default] - Default, - /// The location format, e.g., “Los Angeles time”. - /// - /// When unavailable, falls back to [`NeoTimeZoneStyle::Offset`]. - Location, - /// The generic non-location format, e.g., “Pacific Time”. - /// - /// When unavailable, falls back to [`NeoTimeZoneStyle::Location`]. - Generic, - /// The specific non-location format, e.g., “Pacific Daylight Time”. - /// - /// When unavailable, falls back to [`NeoTimeZoneStyle::Offset`]. - Specific, - /// The offset format, e.g., “GMT−8”. - Offset, -} - -/// A skeleton for formatting a time zone. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub struct NeoTimeZoneSkeleton { - /// Desired formatting length. - pub length: NeoSkeletonLength, - /// The style, _i.e._, with `length`=[`NeoSkeletonLength::Short`], whether to format as - /// “GMT−8” ([`NeoTimeZoneStyle::Offset`]) or “PT” - /// ([`NeoTimeZoneStyle::Generic`]). - pub style: NeoTimeZoneStyle, -} - -impl NeoTimeZoneStyle { - pub(crate) fn resolve(self, length: NeoSkeletonLength) -> ResolvedNeoTimeZoneSkeleton { - crate::tz_registry::skeleton_to_resolved(self, length) - } - - /// Creates a skeleton for this time zone style with a long length. - pub fn long(self) -> NeoTimeZoneSkeleton { - NeoTimeZoneSkeleton::for_length_and_components(NeoSkeletonLength::Long, self) - } - - /// Creates a skeleton for this time zone style with a medium length. - pub fn medium(self) -> NeoTimeZoneSkeleton { - NeoTimeZoneSkeleton::for_length_and_components(NeoSkeletonLength::Medium, self) - } - - /// Creates a skeleton for this time zone style with a short length. - pub fn short(self) -> NeoTimeZoneSkeleton { - NeoTimeZoneSkeleton::for_length_and_components(NeoSkeletonLength::Short, self) - } -} - -impl NeoTimeZoneSkeleton { - /// Creates a skeleton from its length and components. - pub fn for_length_and_components(length: NeoSkeletonLength, style: NeoTimeZoneStyle) -> Self { - Self { length, style } - } -} - -/// A skeleton for formatting parts of a date (without time or time zone). -#[derive(Debug, Copy, Clone)] -#[non_exhaustive] -pub struct NeoDateSkeleton { - /// Desired formatting length. - pub length: NeoSkeletonLength, - /// Date components of the skeleton. - pub components: NeoDateComponents, - /// Alignment option. - pub alignment: Option, - /// Era display option. - pub year_style: Option, -} - -impl NeoDateSkeleton { - /// Creates a skeleton from its length and components. - pub fn for_length_and_components( - length: NeoSkeletonLength, - components: NeoDateComponents, - ) -> Self { - Self { - length, - components, - alignment: None, - year_style: None, - } - } - - /// Converts a [`length::Date`] to a [`NeoDateComponents`] and [`NeoSkeletonLength`]. - #[doc(hidden)] // the types involved in this mapping may change - #[cfg(feature = "datagen")] - pub fn from_date_length(date_length: length::Date) -> NeoDateSkeleton { - let (components, length) = match date_length { - length::Date::Full => (NeoDateComponents::AutoWeekday, NeoSkeletonLength::Long), - length::Date::Long => (NeoDateComponents::Auto, NeoSkeletonLength::Long), - length::Date::Medium => (NeoDateComponents::Auto, NeoSkeletonLength::Medium), - length::Date::Short => (NeoDateComponents::Auto, NeoSkeletonLength::Short), - }; - NeoDateSkeleton::for_length_and_components(length, components) - } -} - -/// A skeleton for formatting a calendar period (i.e. month or year). -#[derive(Debug, Copy, Clone)] -#[non_exhaustive] -pub struct NeoCalendarPeriodSkeleton { - /// Desired formatting length. - pub length: NeoSkeletonLength, - /// Date components of the skeleton. - pub components: NeoCalendarPeriodComponents, - /// Alignment option. - pub alignment: Option, - /// Era display option. - pub year_style: Option, -} - -impl NeoCalendarPeriodSkeleton { - /// Creates a skeleton from its length and components. - pub fn for_length_and_components( - length: NeoSkeletonLength, - components: NeoCalendarPeriodComponents, - ) -> Self { - Self { - length, - components, - alignment: None, - year_style: None, - } - } -} - -/// A skeleton for formatting parts of a time (without date or time zone). -#[derive(Debug, Copy, Clone)] -#[non_exhaustive] -pub struct NeoTimeSkeleton { - /// Desired formatting length. - pub length: NeoSkeletonLength, - /// Time components of the skeleton. - pub components: NeoTimeComponents, - /// Alignment option. - pub alignment: Option, - /// Fractional second digits option. - pub fractional_second_digits: Option, -} - -impl NeoTimeSkeleton { - /// Creates a skeleton from its length and components. - pub fn for_length_and_components( - length: NeoSkeletonLength, - components: NeoTimeComponents, - ) -> Self { - Self { - length, - components, - alignment: None, - fractional_second_digits: None, - } - } -} - -/// A skeleton for formatting parts of a date and time (without time zone). -#[derive(Debug, Copy, Clone)] -#[non_exhaustive] -pub struct NeoDateTimeSkeleton { - /// Desired formatting length. - pub length: NeoSkeletonLength, - /// Date and time components of the skeleton. - pub components: NeoDateTimeComponents, - /// Alignment option. - pub alignment: Option, - /// Era display option. - pub year_style: Option, - /// Fractional second digits option. - pub fractional_second_digits: Option, -} - -impl NeoDateTimeSkeleton { - /// Creates a skeleton from its length and components. - pub fn for_length_and_components( - length: NeoSkeletonLength, - components: NeoDateTimeComponents, - ) -> Self { - Self { - length, - components, - alignment: None, - year_style: None, - fractional_second_digits: None, - } - } -} - -/// A skeleton for formatting parts of a date, time, and optional time zone. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr( - feature = "serde", - serde(try_from = "SemanticSkeletonSerde", into = "SemanticSkeletonSerde") -)] -#[non_exhaustive] -pub struct NeoSkeleton { - /// Desired formatting length. - pub length: NeoSkeletonLength, - /// Components of the skeleton. - pub components: NeoComponents, - /// Alignment option. - pub alignment: Option, - /// Era display option. - pub year_style: Option, - /// Fractional second digits option. - pub fractional_second_digits: Option, -} - -impl From for NeoSkeleton { - fn from(value: NeoDateSkeleton) -> Self { - NeoSkeleton { - length: value.length, - components: value.components.into(), - alignment: value.alignment, - year_style: value.year_style, - fractional_second_digits: None, - } - } -} - -impl From for NeoSkeleton { - fn from(value: NeoTimeSkeleton) -> Self { - NeoSkeleton { - length: value.length, - components: value.components.into(), - alignment: value.alignment, - year_style: None, - fractional_second_digits: value.fractional_second_digits, - } - } -} - -impl From for NeoSkeleton { - fn from(value: NeoDateTimeSkeleton) -> Self { - NeoSkeleton { - length: value.length, - components: value.components.into(), - alignment: value.alignment, - year_style: value.year_style, - fractional_second_digits: value.fractional_second_digits, - } - } -} - -impl NeoSkeleton { - /// Creates a skeleton from its length and components. - pub fn for_length_and_components(length: NeoSkeletonLength, components: NeoComponents) -> Self { - Self { - length, - components, - alignment: None, - year_style: None, - fractional_second_digits: None, - } - } -} - -impl NeoDateTimeSkeleton { - #[doc(hidden)] // mostly internal; maps from old API to new API - #[cfg(feature = "datagen")] - pub fn from_date_time_length( - date_length: crate::options::length::Date, - time_length: crate::options::length::Time, - ) -> Self { - let date_skeleton = NeoDateSkeleton::from_date_length(date_length); - let time_components = NeoTimeComponents::from_time_length(time_length); - NeoDateTimeSkeleton { - length: date_skeleton.length, - components: NeoDateTimeComponents::DateTime(date_skeleton.components, time_components), - alignment: None, - year_style: None, - fractional_second_digits: None, - } - } -} diff --git a/components/datetime/src/options/components.rs b/components/datetime/src/options/components.rs index 311fa6e7010..7dad469d670 100644 --- a/components/datetime/src/options/components.rs +++ b/components/datetime/src/options/components.rs @@ -8,7 +8,7 @@ //! //!

@@ -98,12 +98,11 @@ use serde::{Deserialize, Serialize}; /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[non_exhaustive] pub struct Bag { /// Include the era, such as "AD" or "CE". @@ -185,9 +184,9 @@ impl Bag { // G..GGG AD Abbreviated // GGGG Anno Domini Wide // GGGGG A Narrow - Text::Short => FieldLength::Abbreviated, - Text::Long => FieldLength::Wide, - Text::Narrow => FieldLength::Narrow, + Text::Short => FieldLength::Three, + Text::Long => FieldLength::Four, + Text::Narrow => FieldLength::Five, }, }) } @@ -200,7 +199,9 @@ impl Bag { fields.push(Field { symbol: FieldSymbol::Year(match year { Year::Numeric | Year::TwoDigit => fields::Year::Calendar, - Year::NumericWeekOf | Year::TwoDigitWeekOf => fields::Year::WeekOf, + Year::NumericWeekOf | Year::TwoDigitWeekOf => { + unimplemented!("fields::Year::WeekOf") + } }), length: match year { // Calendar year (numeric). @@ -210,7 +211,7 @@ impl Bag { // yyyy 0002, 0020, 0201, 2017, 20173 (not implemented) // yyyyy+ ... (not implemented) Year::Numeric | Year::NumericWeekOf => FieldLength::One, - Year::TwoDigit | Year::TwoDigitWeekOf => FieldLength::TwoDigit, + Year::TwoDigit | Year::TwoDigitWeekOf => FieldLength::Two, }, }); } @@ -231,42 +232,48 @@ impl Bag { // MMMM September Wide // MMMMM S Narrow Month::Numeric => FieldLength::One, - Month::TwoDigit => FieldLength::TwoDigit, - Month::Long => FieldLength::Wide, - Month::Short => FieldLength::Abbreviated, - Month::Narrow => FieldLength::Narrow, + Month::TwoDigit => FieldLength::Two, + Month::Long => FieldLength::Four, + Month::Short => FieldLength::Three, + Month::Narrow => FieldLength::Five, }, }); } if let Some(week) = self.week { + #[allow(unreachable_code)] fields.push(Field { symbol: FieldSymbol::Week(match week { - Week::WeekOfMonth => fields::Week::WeekOfMonth, - Week::NumericWeekOfYear | Week::TwoDigitWeekOfYear => fields::Week::WeekOfYear, + Week::WeekOfMonth => unimplemented!("#5643 fields::Week::WeekOfMonth"), + Week::NumericWeekOfYear | Week::TwoDigitWeekOfYear => { + unimplemented!("#5643 fields::Week::WeekOfYear") + } }), length: match week { Week::WeekOfMonth | Week::NumericWeekOfYear => FieldLength::One, - Week::TwoDigitWeekOfYear => FieldLength::TwoDigit, + Week::TwoDigitWeekOfYear => FieldLength::Two, }, }); } if let Some(day) = self.day { // TODO(#591) Unimplemented day fields: - // D - Day of year // g - Modified Julian day. fields.push(Field { symbol: FieldSymbol::Day(match day { Day::NumericDayOfMonth | Day::TwoDigitDayOfMonth => fields::Day::DayOfMonth, Day::DayOfWeekInMonth => fields::Day::DayOfWeekInMonth, + Day::DayOfYear => fields::Day::DayOfYear, }), length: match day { // d 1 Numeric day of month: minimum digits // dd 01 Numeric day of month: 2 digits, zero pad if needed // F 1 Numeric day of week in month: minimum digits - Day::NumericDayOfMonth | Day::DayOfWeekInMonth => FieldLength::One, - Day::TwoDigitDayOfMonth => FieldLength::TwoDigit, + // D 1 Numeric day of year: minimum digits + Day::NumericDayOfMonth | Day::DayOfWeekInMonth | Day::DayOfYear => { + FieldLength::One + } + Day::TwoDigitDayOfMonth => FieldLength::Two, }, }); } @@ -284,9 +291,9 @@ impl Bag { // EEEE Tuesday Wide // EEEEE T Narrow // EEEEEE Tu Short - Text::Long => FieldLength::Wide, + Text::Long => FieldLength::Four, Text::Short => FieldLength::One, - Text::Narrow => FieldLength::Narrow, + Text::Narrow => FieldLength::Five, }, }); } @@ -332,7 +339,7 @@ impl Bag { // h 1, 12 Numeric: minimum digits // hh 01, 12 Numeric: 2 digits, zero pad if needed Numeric::Numeric => FieldLength::One, - Numeric::TwoDigit => FieldLength::TwoDigit, + Numeric::TwoDigit => FieldLength::Two, }, }); } @@ -344,7 +351,7 @@ impl Bag { symbol: FieldSymbol::Minute, length: match minute { Numeric::Numeric => FieldLength::One, - Numeric::TwoDigit => FieldLength::TwoDigit, + Numeric::TwoDigit => FieldLength::Two, }, }); } @@ -362,7 +369,7 @@ impl Bag { symbol, length: match second { Numeric::Numeric => FieldLength::One, - Numeric::TwoDigit => FieldLength::TwoDigit, + Numeric::TwoDigit => FieldLength::Two, }, }); // S - Fractional seconds. Represented as DecimalSecond. @@ -372,7 +379,7 @@ impl Bag { if self.time_zone_name.is_some() { // Only the lower "v" field is used in skeletons. fields.push(Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV), + symbol: FieldSymbol::TimeZone(fields::TimeZone::GenericNonLocation), length: FieldLength::One, }); } @@ -394,7 +401,7 @@ impl Bag { /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
@@ -416,7 +423,7 @@ pub enum Numeric { /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
@@ -443,7 +450,7 @@ pub enum Text { /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
@@ -471,7 +478,7 @@ pub enum Year { /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
@@ -504,7 +511,7 @@ pub enum Month { /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
@@ -528,7 +535,7 @@ pub enum Week { /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
@@ -546,6 +553,8 @@ pub enum Day { TwoDigitDayOfMonth, /// The day of week in this month, such as the "2" in 2nd Wednesday of July. DayOfWeekInMonth, + /// The day of year (numeric). + DayOfYear, } /// Options for displaying a time zone for the `components::`[`Bag`]. @@ -555,7 +564,7 @@ pub enum Day { /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
@@ -603,28 +612,28 @@ impl From for Field { fn from(time_zone_name: TimeZoneName) -> Self { match time_zone_name { TimeZoneName::ShortSpecific => Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerZ), + symbol: FieldSymbol::TimeZone(fields::TimeZone::SpecificNonLocation), length: FieldLength::One, }, TimeZoneName::LongSpecific => Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerZ), - length: FieldLength::Wide, + symbol: FieldSymbol::TimeZone(fields::TimeZone::SpecificNonLocation), + length: FieldLength::Four, }, TimeZoneName::LongOffset => Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::UpperO), - length: FieldLength::Wide, + symbol: FieldSymbol::TimeZone(fields::TimeZone::LocalizedOffset), + length: FieldLength::Four, }, TimeZoneName::ShortOffset => Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::UpperO), + symbol: FieldSymbol::TimeZone(fields::TimeZone::LocalizedOffset), length: FieldLength::One, }, TimeZoneName::ShortGeneric => Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV), + symbol: FieldSymbol::TimeZone(fields::TimeZone::GenericNonLocation), length: FieldLength::One, }, TimeZoneName::LongGeneric => Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::LowerV), - length: FieldLength::Wide, + symbol: FieldSymbol::TimeZone(fields::TimeZone::GenericNonLocation), + length: FieldLength::Four, }, } } @@ -664,22 +673,22 @@ impl From<&Pattern<'_>> for Bag { bag.era = Some(match field.length { FieldLength::One | FieldLength::NumericOverride(_) - | FieldLength::TwoDigit - | FieldLength::Abbreviated => Text::Short, - FieldLength::Wide => Text::Long, - FieldLength::Narrow | FieldLength::Six => Text::Narrow, + | FieldLength::Two + | FieldLength::Three => Text::Short, + FieldLength::Four => Text::Long, + FieldLength::Five | FieldLength::Six => Text::Narrow, }); } FieldSymbol::Year(year) => { bag.year = Some(match year { fields::Year::Calendar => match field.length { - FieldLength::TwoDigit => Year::TwoDigit, + FieldLength::Two => Year::TwoDigit, _ => Year::Numeric, }, - fields::Year::WeekOf => match field.length { - FieldLength::TwoDigit => Year::TwoDigitWeekOf, - _ => Year::NumericWeekOf, - }, + // fields::Year::WeekOf => match field.length { + // FieldLength::TwoDigit => Year::TwoDigitWeekOf, + // _ => Year::NumericWeekOf, + // }, // TODO(#3762): Add support for U and r _ => Year::Numeric, }); @@ -690,46 +699,42 @@ impl From<&Pattern<'_>> for Bag { bag.month = Some(match field.length { FieldLength::One => Month::Numeric, FieldLength::NumericOverride(_) => Month::Numeric, - FieldLength::TwoDigit => Month::TwoDigit, - FieldLength::Abbreviated => Month::Short, - FieldLength::Wide => Month::Long, - FieldLength::Narrow | FieldLength::Six => Month::Narrow, + FieldLength::Two => Month::TwoDigit, + FieldLength::Three => Month::Short, + FieldLength::Four => Month::Long, + FieldLength::Five | FieldLength::Six => Month::Narrow, }); } - FieldSymbol::Week(week) => { - bag.week = Some(match week { - fields::Week::WeekOfYear => match field.length { - FieldLength::TwoDigit => Week::TwoDigitWeekOfYear, - _ => Week::NumericWeekOfYear, - }, - fields::Week::WeekOfMonth => Week::WeekOfMonth, - }); + FieldSymbol::Week(_week) => { + // TODO(#5643): Add week fields back + // bag.week = Some(match week { + // fields::Week::WeekOfYear => match field.length { + // FieldLength::TwoDigit => Week::TwoDigitWeekOfYear, + // _ => Week::NumericWeekOfYear, + // }, + // fields::Week::WeekOfMonth => Week::WeekOfMonth, + // }); } FieldSymbol::Day(day) => { bag.day = Some(match day { fields::Day::DayOfMonth => match field.length { - FieldLength::TwoDigit => Day::TwoDigitDayOfMonth, + FieldLength::Two => Day::TwoDigitDayOfMonth, _ => Day::NumericDayOfMonth, }, - fields::Day::DayOfYear => unimplemented!("fields::Day::DayOfYear #591"), + fields::Day::DayOfYear => Day::DayOfYear, fields::Day::DayOfWeekInMonth => Day::DayOfWeekInMonth, - fields::Day::ModifiedJulianDay => { - unimplemented!("fields::Day::ModifiedJulianDay") - } }); } FieldSymbol::Weekday(weekday) => { bag.weekday = Some(match weekday { fields::Weekday::Format => match field.length { - FieldLength::One | FieldLength::TwoDigit | FieldLength::Abbreviated => { - Text::Short - } - FieldLength::Wide => Text::Long, + FieldLength::One | FieldLength::Two | FieldLength::Three => Text::Short, + FieldLength::Four => Text::Long, _ => Text::Narrow, }, fields::Weekday::StandAlone => match field.length { FieldLength::One - | FieldLength::TwoDigit + | FieldLength::Two | FieldLength::NumericOverride(_) => { // Stand-alone fields also support a numeric 1 digit per UTS-35, but there is // no way to request it using the current system. As of 2021-12-06 @@ -754,9 +759,9 @@ impl From<&Pattern<'_>> for Bag { // 'ccc, MMM d. y' unimplemented!("Numeric stand-alone fields are not supported.") } - FieldLength::Abbreviated => Text::Short, - FieldLength::Wide => Text::Long, - FieldLength::Narrow | FieldLength::Six => Text::Narrow, + FieldLength::Three => Text::Short, + FieldLength::Four => Text::Long, + FieldLength::Five | FieldLength::Six => Text::Narrow, }, fields::Weekday::Local => unimplemented!("fields::Weekday::Local"), }); @@ -766,7 +771,7 @@ impl From<&Pattern<'_>> for Bag { } FieldSymbol::Hour(hour) => { bag.hour = Some(match field.length { - FieldLength::TwoDigit => Numeric::TwoDigit, + FieldLength::Two => Numeric::TwoDigit, _ => Numeric::Numeric, }); bag.preferences = Some(preferences::Bag { @@ -780,27 +785,23 @@ impl From<&Pattern<'_>> for Bag { } FieldSymbol::Minute => { bag.minute = Some(match field.length { - FieldLength::TwoDigit => Numeric::TwoDigit, + FieldLength::Two => Numeric::TwoDigit, _ => Numeric::Numeric, }); } - FieldSymbol::Second(second) => { - match second { - fields::Second::Second => { - bag.second = Some(match field.length { - FieldLength::TwoDigit => Numeric::TwoDigit, - _ => Numeric::Numeric, - }); - } - fields::Second::Millisecond => { - // fields::Second::Millisecond is not implemented (#1834) - } + FieldSymbol::Second(second) => match second { + fields::Second::Second => { + bag.second = Some(match field.length { + FieldLength::Two => Numeric::TwoDigit, + _ => Numeric::Numeric, + }); } - } + fields::Second::MillisInDay => unimplemented!("fields::Second::MillisInDay"), + }, FieldSymbol::DecimalSecond(decimal_second) => { use FractionalSecondDigits::*; bag.second = Some(match field.length { - FieldLength::TwoDigit => Numeric::TwoDigit, + FieldLength::Two => Numeric::TwoDigit, _ => Numeric::Numeric, }); bag.fractional_second = Some(match decimal_second { @@ -817,22 +818,21 @@ impl From<&Pattern<'_>> for Bag { } FieldSymbol::TimeZone(time_zone_name) => { bag.time_zone_name = Some(match time_zone_name { - fields::TimeZone::LowerZ => match field.length { + fields::TimeZone::SpecificNonLocation => match field.length { FieldLength::One => TimeZoneName::ShortSpecific, _ => TimeZoneName::LongSpecific, }, - fields::TimeZone::LowerV => match field.length { + fields::TimeZone::GenericNonLocation => match field.length { FieldLength::One => TimeZoneName::ShortGeneric, _ => TimeZoneName::LongGeneric, }, - fields::TimeZone::UpperO => match field.length { + fields::TimeZone::LocalizedOffset => match field.length { FieldLength::One => TimeZoneName::ShortOffset, _ => TimeZoneName::LongOffset, }, - fields::TimeZone::UpperZ => unimplemented!("fields::TimeZone::UpperZ"), - fields::TimeZone::UpperV => unimplemented!("fields::TimeZone::UpperV"), - fields::TimeZone::LowerX => unimplemented!("fields::TimeZone::LowerX"), - fields::TimeZone::UpperX => unimplemented!("fields::TimeZone::UpperX"), + fields::TimeZone::Location => unimplemented!("fields::TimeZone::Location"), + fields::TimeZone::Iso => unimplemented!("fields::TimeZone::IsoZ"), + fields::TimeZone::IsoWithZ => unimplemented!("fields::TimeZone::Iso"), }); } } @@ -855,7 +855,8 @@ mod test { let bag = Bag { year: Some(Year::Numeric), month: Some(Month::Long), - week: Some(Week::WeekOfMonth), + // TODO(#5643): Add week fields back + week: None, day: Some(Day::NumericDayOfMonth), hour: Some(Numeric::Numeric), @@ -869,8 +870,7 @@ mod test { bag.to_vec_fields(preferences::HourCycle::H23), [ (Symbol::Year(fields::Year::Calendar), Length::One).into(), - (Symbol::Month(fields::Month::Format), Length::Wide).into(), - (Symbol::Week(fields::Week::WeekOfMonth), Length::One).into(), + (Symbol::Month(fields::Month::Format), Length::Four).into(), (Symbol::Day(fields::Day::DayOfMonth), Length::One).into(), (Symbol::Hour(fields::Hour::H23), Length::One).into(), (Symbol::Minute, Length::One).into(), @@ -895,7 +895,7 @@ mod test { bag.to_vec_fields(preferences::HourCycle::H23), [ (Symbol::Year(fields::Year::Calendar), Length::One).into(), - (Symbol::Month(fields::Month::Format), Length::TwoDigit).into(), + (Symbol::Month(fields::Month::Format), Length::Two).into(), (Symbol::Day(fields::Day::DayOfMonth), Length::One).into(), ] ); diff --git a/components/datetime/src/options/mod.rs b/components/datetime/src/options/mod.rs index 09a29e9a286..de82ffa9d35 100644 --- a/components/datetime/src/options/mod.rs +++ b/components/datetime/src/options/mod.rs @@ -69,7 +69,7 @@ pub enum DateTimeFormatterOptions { /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, - /// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature + /// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
diff --git a/components/datetime/src/options/preferences.rs b/components/datetime/src/options/preferences.rs index eb9ed23a9d7..8808bfb6b5a 100644 --- a/components/datetime/src/options/preferences.rs +++ b/components/datetime/src/options/preferences.rs @@ -14,7 +14,7 @@ //! //!
//! 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -//! including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +//! including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature //! of the icu meta-crate. Use with caution. //! #1317 //!
@@ -47,7 +47,7 @@ use icu_locale_core::{ /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
@@ -84,7 +84,7 @@ impl Bag { /// ///
/// 🚧 This code is experimental; it may change at any time, in breaking or non-breaking ways, -/// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature +/// including in SemVer minor releases. It can be enabled with the `experimental` Cargo feature /// of the icu meta-crate. Use with caution. /// #1317 ///
diff --git a/components/datetime/src/provider/calendar/mod.rs b/components/datetime/src/provider/calendar/mod.rs index f9f0945d005..417e353fe30 100644 --- a/components/datetime/src/provider/calendar/mod.rs +++ b/components/datetime/src/provider/calendar/mod.rs @@ -9,6 +9,7 @@ mod skeletons; mod symbols; use crate::provider::pattern; +use crate::size_test_macro::size_test; use icu_provider::prelude::*; #[cfg(feature = "datagen")] pub use skeletons::*; diff --git a/components/datetime/src/provider/calendar/symbols.rs b/components/datetime/src/provider/calendar/symbols.rs index 3cfbd4138b9..a8fce3431f0 100644 --- a/components/datetime/src/provider/calendar/symbols.rs +++ b/components/datetime/src/provider/calendar/symbols.rs @@ -5,6 +5,7 @@ // allowed for providers #![allow(clippy::exhaustive_structs, clippy::exhaustive_enums)] +use crate::size_test_macro::size_test; use alloc::borrow::Cow; use icu_calendar::types::MonthCode; use icu_provider::prelude::*; diff --git a/components/datetime/src/provider/neo/mod.rs b/components/datetime/src/provider/neo/mod.rs index 1a8e19ff3f0..e9d908b57bd 100644 --- a/components/datetime/src/provider/neo/mod.rs +++ b/components/datetime/src/provider/neo/mod.rs @@ -7,8 +7,9 @@ mod adapter; use crate::provider::pattern::runtime::{self, PatternULE}; +use crate::size_test_macro::size_test; use alloc::borrow::Cow; -use core::ops::Range; +use icu_pattern::SinglePlaceholderPattern; use icu_provider::prelude::*; use potential_utf::PotentialUtf8; use zerovec::{VarZeroVec, ZeroMap}; @@ -409,39 +410,16 @@ pub enum MonthNamesV1<'data> { /// leap month symbol. /// /// For numeric formatting only, on calendars with leap months - LeapNumeric(#[cfg_attr(feature = "serde", serde(borrow))] SimpleSubstitutionPattern<'data>), -} - -/// Represents a simple substitution pattern; -/// i.e. a string with a single placeholder -#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)] -#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))] -#[cfg_attr(feature = "datagen", databake(path = icu_datetime::provider::neo))] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -pub struct SimpleSubstitutionPattern<'data> { - /// The pattern - #[cfg_attr(feature = "serde", serde(borrow))] - pub pattern: Cow<'data, str>, - /// The byte index in which to substitute stuff. Weak invariant: `subst_index <= pattern.len()` - pub subst_index: usize, -} - -impl SimpleSubstitutionPattern<'_> { - pub(crate) fn get_prefix(&self) -> &str { - self.debug_unwrap_range(0..self.subst_index) - } - pub(crate) fn get_suffix(&self) -> &str { - self.debug_unwrap_range(self.subst_index..self.pattern.len()) - } - fn debug_unwrap_range(&self, range: Range) -> &str { - match self.pattern.get(range) { - Some(s) => s, - None => { - debug_assert!(false, "Invalid pattern: {self:?}"); - "" - } - } - } + LeapNumeric( + #[cfg_attr( + feature = "serde", + serde( + borrow, + deserialize_with = "icu_pattern::deserialize_borrowed_cow::" + ) + )] + Cow<'data, SinglePlaceholderPattern>, + ), } size_test!(LinearNamesV1, linear_names_v1_size, 24); diff --git a/components/datetime/src/provider/packed_pattern.rs b/components/datetime/src/provider/packed_pattern.rs index b3dab3799b7..cfd709d47ee 100644 --- a/components/datetime/src/provider/packed_pattern.rs +++ b/components/datetime/src/provider/packed_pattern.rs @@ -8,7 +8,7 @@ use super::pattern::{ runtime::{Pattern, PatternBorrowed, PatternMetadata}, PatternItem, }; -use crate::{helpers::size_test, NeoSkeletonLength}; +use crate::{size_test_macro::size_test, NeoSkeletonLength}; use alloc::vec::Vec; use icu_plurals::{ provider::{FourBitMetadata, PluralElementsPackedULE}, diff --git a/components/datetime/src/provider/pattern/common/serde.rs b/components/datetime/src/provider/pattern/common/serde.rs index 9f3080ac41c..a1e7934374e 100644 --- a/components/datetime/src/provider/pattern/common/serde.rs +++ b/components/datetime/src/provider/pattern/common/serde.rs @@ -99,7 +99,7 @@ mod reference { #[test] fn reference_pattern_serde_human_readable_test() { - let pattern: Pattern = "Y-m-d HH:mm".parse().expect("Failed to parse pattern"); + let pattern: Pattern = "y-M-d HH:mm".parse().expect("Failed to parse pattern"); let json = serde_json::to_string(&pattern).expect("Failed to serialize pattern"); let result: Pattern = serde_json::from_str(&json).expect("Failed to deserialize pattern"); @@ -108,7 +108,7 @@ mod reference { #[test] fn reference_pattern_serde_bincode_test() { - let pattern: Pattern = "Y-m-d HH:mm".parse().expect("Failed to parse pattern"); + let pattern: Pattern = "y-M-d HH:mm".parse().expect("Failed to parse pattern"); let bytes = bincode::serialize(&pattern).expect("Failed to serialize pattern"); let result: Pattern = bincode::deserialize(&bytes).expect("Failed to deserialize pattern"); @@ -201,7 +201,7 @@ mod runtime { #[test] fn runtime_pattern_serde_human_readable_test() { - let pattern: Pattern = "Y-m-d HH:mm".parse().expect("Failed to parse pattern"); + let pattern: Pattern = "y-M-d HH:mm".parse().expect("Failed to parse pattern"); let json = serde_json::to_string(&pattern).expect("Failed to serialize pattern"); let result: Pattern = serde_json::from_str(&json).expect("Failed to deserialize pattern"); @@ -210,7 +210,7 @@ mod runtime { #[test] fn runtime_pattern_serde_bincode_test() { - let pattern: Pattern = "Y-m-d HH:mm".parse().expect("Failed to parse pattern"); + let pattern: Pattern = "y-M-d HH:mm".parse().expect("Failed to parse pattern"); let bytes = bincode::serialize(&pattern).expect("Failed to serialize pattern"); let result: Pattern = bincode::deserialize(&bytes).expect("Failed to deserialize pattern"); diff --git a/components/datetime/src/provider/pattern/item/mod.rs b/components/datetime/src/provider/pattern/item/mod.rs index d40d5cf9bb4..9b734246f63 100644 --- a/components/datetime/src/provider/pattern/item/mod.rs +++ b/components/datetime/src/provider/pattern/item/mod.rs @@ -5,9 +5,7 @@ mod generic; mod ule; -use super::PatternError; use crate::fields::{Field, FieldLength, FieldSymbol}; -use core::convert::TryFrom; pub use generic::GenericPatternItem; /// An element of a [`Pattern`](super::runtime::Pattern). @@ -38,30 +36,6 @@ impl From<(FieldSymbol, FieldLength)> for PatternItem { } } -impl TryFrom<(FieldSymbol, u8)> for PatternItem { - type Error = PatternError; - fn try_from(input: (FieldSymbol, u8)) -> Result { - let length = FieldLength::from_idx(input.1) - .map_err(|_| PatternError::FieldLengthInvalid(input.0))?; - Ok(Self::Field(Field { - symbol: input.0, - length, - })) - } -} - -impl TryFrom<(char, u8)> for PatternItem { - type Error = PatternError; - fn try_from(input: (char, u8)) -> Result { - let symbol = - FieldSymbol::try_from(input.0).map_err(|_| PatternError::InvalidSymbol(input.0))?; - - let length = - FieldLength::from_idx(input.1).map_err(|_| PatternError::FieldLengthInvalid(symbol))?; - Ok(Self::Field(Field { symbol, length })) - } -} - impl From for PatternItem { fn from(input: char) -> Self { Self::Literal(input) diff --git a/components/datetime/src/provider/pattern/item/ule.rs b/components/datetime/src/provider/pattern/item/ule.rs index 657939008f7..b64215f429b 100644 --- a/components/datetime/src/provider/pattern/item/ule.rs +++ b/components/datetime/src/provider/pattern/item/ule.rs @@ -311,30 +311,30 @@ mod test { fn test_pattern_item_as_ule() { let samples = [ ( - PatternItem::from((FieldSymbol::Minute, FieldLength::TwoDigit)), - [0x80, FieldSymbol::Minute.idx(), FieldLength::TwoDigit.idx()], + PatternItem::from((FieldSymbol::Minute, FieldLength::Two)), + [0x80, FieldSymbol::Minute.idx(), FieldLength::Two.idx()], ), ( - PatternItem::from((FieldSymbol::Year(Year::Calendar), FieldLength::Wide)), + PatternItem::from((FieldSymbol::Year(Year::Calendar), FieldLength::Four)), [ 0x80, FieldSymbol::Year(Year::Calendar).idx(), - FieldLength::Wide.idx(), + FieldLength::Four.idx(), ], ), ( - PatternItem::from((FieldSymbol::Year(Year::WeekOf), FieldLength::Wide)), + PatternItem::from((FieldSymbol::Year(Year::Cyclic), FieldLength::Four)), [ 0x80, - FieldSymbol::Year(Year::WeekOf).idx(), - FieldLength::Wide.idx(), + FieldSymbol::Year(Year::Cyclic).idx(), + FieldLength::Four.idx(), ], ), ( - PatternItem::from((FieldSymbol::Second(Second::Millisecond), FieldLength::One)), + PatternItem::from((FieldSymbol::Second(Second::MillisInDay), FieldLength::One)), [ 0x80, - FieldSymbol::Second(Second::Millisecond).idx(), + FieldSymbol::Second(Second::MillisInDay).idx(), FieldLength::One.idx(), ], ), @@ -353,20 +353,20 @@ mod test { fn test_pattern_item_ule() { let samples = [( [ - PatternItem::from((FieldSymbol::Year(Year::Calendar), FieldLength::Wide)), + PatternItem::from((FieldSymbol::Year(Year::Calendar), FieldLength::Four)), PatternItem::from('z'), - PatternItem::from((FieldSymbol::Second(Second::Millisecond), FieldLength::One)), + PatternItem::from((FieldSymbol::Second(Second::MillisInDay), FieldLength::One)), ], [ [ 0x80, FieldSymbol::Year(Year::Calendar).idx(), - FieldLength::Wide.idx(), + FieldLength::Four.idx(), ], [0x00, 0x00, 0x7a], [ 0x80, - FieldSymbol::Second(Second::Millisecond).idx(), + FieldSymbol::Second(Second::MillisInDay).idx(), FieldLength::One.idx(), ], ], diff --git a/components/datetime/src/provider/pattern/reference/generic.rs b/components/datetime/src/provider/pattern/reference/generic.rs index 6b2d1656c73..d3e78f886e4 100644 --- a/components/datetime/src/provider/pattern/reference/generic.rs +++ b/components/datetime/src/provider/pattern/reference/generic.rs @@ -69,12 +69,12 @@ mod test { .parse() .expect("Failed to parse a generic pattern."); - let date = "Y/m/d".parse().expect("Failed to parse a date pattern."); + let date = "y/M/d".parse().expect("Failed to parse a date pattern."); let time = "HH:mm".parse().expect("Failed to parse a time pattern."); let pattern = pattern .combined(vec![date, time]) .expect("Failed to combine date and time."); - assert_eq!(pattern.to_runtime_pattern().to_string(), "Y/m/d 'at' HH:mm"); + assert_eq!(pattern.to_runtime_pattern().to_string(), "y/M/d 'at' HH:mm"); } } diff --git a/components/datetime/src/provider/pattern/reference/parser.rs b/components/datetime/src/provider/pattern/reference/parser.rs index 4e93a171561..b662364092f 100644 --- a/components/datetime/src/provider/pattern/reference/parser.rs +++ b/components/datetime/src/provider/pattern/reference/parser.rs @@ -8,14 +8,11 @@ use super::{ }; #[cfg(test)] use super::{GenericPattern, Pattern}; -use crate::fields::{self, Field, FieldLength, FieldSymbol}; +use crate::fields::{self, Field, FieldLength, FieldSymbol, TimeZone}; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; -use core::{ - convert::{TryFrom, TryInto}, - mem, -}; +use core::mem; #[derive(Debug, PartialEq)] struct SegmentSymbol { @@ -25,9 +22,10 @@ struct SegmentSymbol { impl SegmentSymbol { fn finish(self, result: &mut Vec) -> Result<(), PatternError> { - (self.symbol, self.length) - .try_into() - .map(|item| result.push(item)) + let length = FieldLength::from_idx(self.length) + .map_err(|_| PatternError::FieldLengthInvalid(self.symbol))?; + result.push(PatternItem::from((self.symbol, length))); + Ok(()) } } @@ -80,11 +78,46 @@ impl SegmentLiteral { } } +#[derive(Debug, PartialEq)] +struct SymbolAlias { + ch: char, + length: u8, +} + +impl SymbolAlias { + fn try_new(ch: char) -> Option { + matches!(ch, 'Z').then_some(Self { ch, length: 1 }) + } + + fn finish(self, result: &mut Vec) -> Result<(), PatternError> { + match (self.ch, self.length) { + // Z..ZZZ => xxxx + ('Z', 1..=3) => SegmentSymbol { + symbol: FieldSymbol::TimeZone(TimeZone::Iso), + length: 4, + }, + // ZZZZ => OOOO + ('Z', 4) => SegmentSymbol { + symbol: FieldSymbol::TimeZone(TimeZone::LocalizedOffset), + length: 4, + }, + // ZZZZZ => XXXXX + ('Z', 5) => SegmentSymbol { + symbol: FieldSymbol::TimeZone(TimeZone::IsoWithZ), + length: 5, + }, + _ => return Err(PatternError::UnknownSubstitution(self.ch)), + } + .finish(result) + } +} + #[derive(Debug, PartialEq)] enum Segment { Symbol(SegmentSymbol), SecondSymbol(SegmentSecondSymbol), Literal(SegmentLiteral), + SymbolAlias(SymbolAlias), } impl Segment { @@ -93,6 +126,7 @@ impl Segment { Self::Symbol(v) => v.finish(result), Self::SecondSymbol(v) => v.finish(result), Self::Literal(v) => v.finish(result), + Self::SymbolAlias(v) => v.finish(result), } } @@ -101,6 +135,7 @@ impl Segment { Self::Symbol(_) => unreachable!("no symbols in generic pattern"), Self::SecondSymbol(_) => unreachable!("no symbols in generic pattern"), Self::Literal(v) => v.finish_generic(result), + Self::SymbolAlias(_) => unreachable!("no symbols in generic pattern"), } } } @@ -242,6 +277,15 @@ impl<'p> Parser<'p> { .finish(&mut result)?; } } + } else if let Some(alias) = SymbolAlias::try_new(ch) { + match &mut self.state { + Segment::SymbolAlias(SymbolAlias { ch: ch2, length }) if *ch2 == ch => { + *length += 1; + } + state => { + mem::replace(state, Segment::SymbolAlias(alias)).finish(&mut result)?; + } + } } else { match &mut self.state { Segment::SecondSymbol( @@ -357,9 +401,9 @@ mod tests { ( "dd/MM/y", vec![ - (fields::Day::DayOfMonth.into(), FieldLength::TwoDigit).into(), + (fields::Day::DayOfMonth.into(), FieldLength::Two).into(), '/'.into(), - (fields::Month::Format.into(), FieldLength::TwoDigit).into(), + (fields::Month::Format.into(), FieldLength::Two).into(), '/'.into(), (fields::Year::Calendar.into(), FieldLength::One).into(), ], @@ -367,11 +411,11 @@ mod tests { ( "HH:mm:ss", vec![ - (fields::Hour::H23.into(), FieldLength::TwoDigit).into(), + (fields::Hour::H23.into(), FieldLength::Two).into(), ':'.into(), - (FieldSymbol::Minute, FieldLength::TwoDigit).into(), + (FieldSymbol::Minute, FieldLength::Two).into(), ':'.into(), - (fields::Second::Second.into(), FieldLength::TwoDigit).into(), + (fields::Second::Second.into(), FieldLength::Two).into(), ], ), ( @@ -388,15 +432,11 @@ mod tests { ( "HH:mm:ss.SS", vec![ - (fields::Hour::H23.into(), FieldLength::TwoDigit).into(), + (fields::Hour::H23.into(), FieldLength::Two).into(), ':'.into(), - (FieldSymbol::Minute, FieldLength::TwoDigit).into(), + (FieldSymbol::Minute, FieldLength::Two).into(), ':'.into(), - ( - fields::DecimalSecond::SecondF2.into(), - FieldLength::TwoDigit, - ) - .into(), + (fields::DecimalSecond::SecondF2.into(), FieldLength::Two).into(), ], ), ]; @@ -463,19 +503,19 @@ mod tests { ), ( "yy", - vec![(fields::Year::Calendar.into(), FieldLength::TwoDigit).into()], + vec![(fields::Year::Calendar.into(), FieldLength::Two).into()], ), ( "yyy", - vec![(fields::Year::Calendar.into(), FieldLength::Abbreviated).into()], + vec![(fields::Year::Calendar.into(), FieldLength::Three).into()], ), ( "yyyy", - vec![(fields::Year::Calendar.into(), FieldLength::Wide).into()], + vec![(fields::Year::Calendar.into(), FieldLength::Four).into()], ), ( "yyyyy", - vec![(fields::Year::Calendar.into(), FieldLength::Narrow).into()], + vec![(fields::Year::Calendar.into(), FieldLength::Five).into()], ), ( "yyyyyy", @@ -506,7 +546,7 @@ mod tests { ( "hh''a", vec![ - (fields::Hour::H12.into(), FieldLength::TwoDigit).into(), + (fields::Hour::H12.into(), FieldLength::Two).into(), '\''.into(), (fields::DayPeriod::AmPm.into(), FieldLength::One).into(), ], @@ -514,7 +554,7 @@ mod tests { ( "hh''b", vec![ - (fields::Hour::H12.into(), FieldLength::TwoDigit).into(), + (fields::Hour::H12.into(), FieldLength::Two).into(), '\''.into(), (fields::DayPeriod::NoonMidnight.into(), FieldLength::One).into(), ], @@ -554,7 +594,7 @@ mod tests { ( "hh 'o''clock' a", vec![ - (fields::Hour::H12.into(), FieldLength::TwoDigit).into(), + (fields::Hour::H12.into(), FieldLength::Two).into(), ' '.into(), 'o'.into(), '\''.into(), @@ -570,7 +610,7 @@ mod tests { ( "hh 'o''clock' b", vec![ - (fields::Hour::H12.into(), FieldLength::TwoDigit).into(), + (fields::Hour::H12.into(), FieldLength::Two).into(), ' '.into(), 'o'.into(), '\''.into(), @@ -586,7 +626,7 @@ mod tests { ( "hh''a", vec![ - (fields::Hour::H12.into(), FieldLength::TwoDigit).into(), + (fields::Hour::H12.into(), FieldLength::Two).into(), '\''.into(), (fields::DayPeriod::AmPm.into(), FieldLength::One).into(), ], @@ -594,7 +634,7 @@ mod tests { ( "hh''b", vec![ - (fields::Hour::H12.into(), FieldLength::TwoDigit).into(), + (fields::Hour::H12.into(), FieldLength::Two).into(), '\''.into(), (fields::DayPeriod::NoonMidnight.into(), FieldLength::One).into(), ], @@ -614,35 +654,47 @@ mod tests { '.'.into(), '.'.into(), ' '.into(), - (fields::TimeZone::LowerZ.into(), FieldLength::One).into(), + ( + fields::TimeZone::SpecificNonLocation.into(), + FieldLength::One, + ) + .into(), ], ), ( "s.SSz", vec![ (fields::DecimalSecond::SecondF2.into(), FieldLength::One).into(), - (fields::TimeZone::LowerZ.into(), FieldLength::One).into(), + ( + fields::TimeZone::SpecificNonLocation.into(), + FieldLength::One, + ) + .into(), ], ), ( "sSSz", vec![ (fields::DecimalSecond::SecondF2.into(), FieldLength::One).into(), - (fields::TimeZone::LowerZ.into(), FieldLength::One).into(), + ( + fields::TimeZone::SpecificNonLocation.into(), + FieldLength::One, + ) + .into(), ], ), ( "s.SSss", vec![ (fields::DecimalSecond::SecondF2.into(), FieldLength::One).into(), - (fields::Second::Second.into(), FieldLength::TwoDigit).into(), + (fields::Second::Second.into(), FieldLength::Two).into(), ], ), ( "sSSss", vec![ (fields::DecimalSecond::SecondF2.into(), FieldLength::One).into(), - (fields::Second::Second.into(), FieldLength::TwoDigit).into(), + (fields::Second::Second.into(), FieldLength::Two).into(), ], ), ( @@ -650,7 +702,11 @@ mod tests { vec![ (fields::Second::Second.into(), FieldLength::One).into(), '.'.into(), - (fields::TimeZone::LowerZ.into(), FieldLength::One).into(), + ( + fields::TimeZone::SpecificNonLocation.into(), + FieldLength::One, + ) + .into(), ], ), ( @@ -658,36 +714,60 @@ mod tests { vec![ (fields::Second::Second.into(), FieldLength::One).into(), '.'.into(), - (fields::Second::Second.into(), FieldLength::TwoDigit).into(), + (fields::Second::Second.into(), FieldLength::Two).into(), ], ), ( "z", - vec![(fields::TimeZone::LowerZ.into(), FieldLength::One).into()], + vec![( + fields::TimeZone::SpecificNonLocation.into(), + FieldLength::One, + ) + .into()], ), ( "Z", - vec![(fields::TimeZone::UpperZ.into(), FieldLength::One).into()], + vec![(fields::TimeZone::Iso.into(), FieldLength::Four).into()], + ), + ( + "ZZ", + vec![(fields::TimeZone::Iso.into(), FieldLength::Four).into()], + ), + ( + "ZZZ", + vec![(fields::TimeZone::Iso.into(), FieldLength::Four).into()], + ), + ( + "ZZZZ", + vec![(fields::TimeZone::LocalizedOffset.into(), FieldLength::Four).into()], + ), + ( + "ZZZZZ", + vec![(fields::TimeZone::IsoWithZ.into(), FieldLength::Five).into()], ), ( "O", - vec![(fields::TimeZone::UpperO.into(), FieldLength::One).into()], + vec![(fields::TimeZone::LocalizedOffset.into(), FieldLength::One).into()], ), ( "v", - vec![(fields::TimeZone::LowerV.into(), FieldLength::One).into()], + vec![( + fields::TimeZone::GenericNonLocation.into(), + FieldLength::One, + ) + .into()], ), ( "V", - vec![(fields::TimeZone::UpperV.into(), FieldLength::One).into()], + vec![(fields::TimeZone::Location.into(), FieldLength::One).into()], ), ( "x", - vec![(fields::TimeZone::LowerX.into(), FieldLength::One).into()], + vec![(fields::TimeZone::Iso.into(), FieldLength::One).into()], ), ( "X", - vec![(fields::TimeZone::UpperX.into(), FieldLength::One).into()], + vec![(fields::TimeZone::IsoWithZ.into(), FieldLength::One).into()], ), ]; @@ -697,6 +777,7 @@ mod tests { .parse() .expect("Parsing pattern failed."), pattern, + "{string}", ); } diff --git a/components/datetime/src/provider/pattern/runtime/generic.rs b/components/datetime/src/provider/pattern/runtime/generic.rs index e581ef33ec0..845cbd4805b 100644 --- a/components/datetime/src/provider/pattern/runtime/generic.rs +++ b/components/datetime/src/provider/pattern/runtime/generic.rs @@ -52,7 +52,7 @@ impl<'data> GenericPattern<'data> { /// ``` /// use icu::datetime::provider::pattern::runtime::{GenericPattern, Pattern}; /// - /// let date: Pattern = "Y-m-d".parse().expect("Failed to parse pattern"); + /// let date: Pattern = "y-M-d".parse().expect("Failed to parse pattern"); /// let time: Pattern = "HH:mm".parse().expect("Failed to parse pattern"); /// /// let glue: GenericPattern = "{1} 'at' {0}" @@ -62,7 +62,7 @@ impl<'data> GenericPattern<'data> { /// glue.combined(date, time) /// .expect("Failed to combine patterns") /// .to_string(), - /// "Y-m-d 'at' HH:mm" + /// "y-M-d 'at' HH:mm" /// ); /// ``` pub fn combined( @@ -139,7 +139,7 @@ mod test { .parse() .expect("Failed to parse a generic pattern."); - let date = "Y/m/d".parse().expect("Failed to parse a date pattern."); + let date = "y/M/d".parse().expect("Failed to parse a date pattern."); let time = "HH:mm".parse().expect("Failed to parse a time pattern."); @@ -147,6 +147,6 @@ mod test { .combined(date, time) .expect("Failed to combine date and time."); - assert_eq!(pattern.to_string(), "Y/m/d 'at' HH:mm"); + assert_eq!(pattern.to_string(), "y/M/d 'at' HH:mm"); } } diff --git a/components/datetime/src/provider/skeleton/error.rs b/components/datetime/src/provider/skeleton/error.rs index f7a282d8daa..523692b6665 100644 --- a/components/datetime/src/provider/skeleton/error.rs +++ b/components/datetime/src/provider/skeleton/error.rs @@ -62,7 +62,13 @@ impl From for SkeletonError { // TODO(#487) - Flexible day periods 'B' // TODO(#501) - Quarters - | 'Q' + | 'Q' | 'q' + // Extended year + | 'u' + // TODO(#5643) - Weeks + | 'Y' | 'w' | 'W' + // Modified Julian Day + | 'g' => Self::SymbolUnimplemented(ch), _ => Self::SymbolUnknown(ch), } diff --git a/components/datetime/src/provider/skeleton/helpers.rs b/components/datetime/src/provider/skeleton/helpers.rs index 279ca7e4b12..4db2b84a3aa 100644 --- a/components/datetime/src/provider/skeleton/helpers.rs +++ b/components/datetime/src/provider/skeleton/helpers.rs @@ -209,7 +209,7 @@ pub fn create_best_pattern_for_fields<'data>( // 4. Otherwise use length::Date::Short let length = match month_field { Some(field) => match field.length { - FieldLength::Wide => { + FieldLength::Four => { let weekday = fields .iter() .find(|f| matches!(f.symbol, FieldSymbol::Weekday(_))); @@ -220,7 +220,7 @@ pub fn create_best_pattern_for_fields<'data>( length::Date::Long } } - FieldLength::Abbreviated => length::Date::Medium, + FieldLength::Three => length::Date::Medium, _ => length::Date::Short, }, None => length::Date::Short, diff --git a/components/datetime/src/provider/skeleton/plural.rs b/components/datetime/src/provider/skeleton/plural.rs index af173119729..7d8f0353d30 100644 --- a/components/datetime/src/provider/skeleton/plural.rs +++ b/components/datetime/src/provider/skeleton/plural.rs @@ -194,6 +194,8 @@ mod test { use super::*; #[test] + #[ignore] // TODO(#5643) + #[allow(unreachable_code, unused_variables, unused_mut)] fn build_plural_pattern() { let red_pattern: Pattern = "'red' w".parse().unwrap(); let blue_pattern: Pattern = "'blue' w".parse().unwrap(); @@ -205,7 +207,7 @@ mod test { patterns.maybe_set_variant(PluralCategory::Few, red_pattern.clone()); patterns.maybe_set_variant(PluralCategory::Many, blue_pattern.clone()); - assert_eq!(patterns.pivot_field, Week::WeekOfYear); + // assert_eq!(patterns.pivot_field, Week::WeekOfYear); assert_eq!(patterns.zero, Some(red_pattern.clone())); assert_eq!(patterns.one, None); // duplicate `other assert_eq!(patterns.two, Some(red_pattern)); @@ -213,6 +215,8 @@ mod test { } #[test] + #[ignore] // TODO(#5643) + #[allow(unreachable_code, unused_variables)] fn normalize_pattern_plurals_switches_singletons_to_single_pattern() { let pattern: Pattern = "'red' w".parse().unwrap(); let patterns = PluralPattern::new(pattern.clone()).expect("PluralPattern::new failed"); diff --git a/components/datetime/src/provider/skeleton/reference.rs b/components/datetime/src/provider/skeleton/reference.rs index a702e4fc17b..76363271a8c 100644 --- a/components/datetime/src/provider/skeleton/reference.rs +++ b/components/datetime/src/provider/skeleton/reference.rs @@ -134,9 +134,6 @@ impl TryFrom<&str> for Skeleton { let mut iter = skeleton_string.chars().peekable(); while let Some(ch) = iter.next() { - // Convert the byte to a valid field symbol. - let field_symbol = FieldSymbol::try_from(ch)?; - // Go through the chars to count how often it's repeated. let mut field_length: u8 = 1; while let Some(next_ch) = iter.peek() { @@ -147,6 +144,23 @@ impl TryFrom<&str> for Skeleton { iter.next(); } + // Convert the byte to a valid field symbol. + let field_symbol = if ch == 'Z' { + match field_length { + 1..=3 => { + field_length = 4; + FieldSymbol::try_from('x')? + } + 4 => FieldSymbol::try_from('O')?, + 5 => { + field_length = 4; + FieldSymbol::try_from('X')? + } + _ => FieldSymbol::try_from(ch)?, + } + } else { + FieldSymbol::try_from(ch)? + }; let field = Field::from((field_symbol, FieldLength::from_idx(field_length)?)); match fields.binary_search(&field) { diff --git a/components/datetime/src/raw/neo.rs b/components/datetime/src/raw/neo.rs index d08b4591b48..1090ee19dfe 100644 --- a/components/datetime/src/raw/neo.rs +++ b/components/datetime/src/raw/neo.rs @@ -2,21 +2,18 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -use crate::fields::{self, FieldLength, FieldSymbol}; -use crate::format::neo::FieldForDataLoading; +use crate::dynamic::{CompositeFieldSet, TimeFieldSet, ZoneFieldSet}; +use crate::fields::{self, Field, FieldLength, FieldSymbol, TimeZone}; use crate::input::ExtractedInput; use crate::neo_pattern::DateTimePattern; -use crate::neo_skeleton::{ - Alignment, FractionalSecondDigits, NeoComponents, NeoSkeletonLength, NeoTimeComponents, - NeoTimeZoneStyle, YearStyle, -}; +use crate::neo_skeleton::*; use crate::options::preferences::HourCycle; use crate::provider::pattern::{ runtime::{self, PatternMetadata}, GenericPatternItem, PatternItem, }; use crate::provider::{neo::*, ErasedPackedPatterns, PackedSkeletonVariant}; -use crate::time_zone::ResolvedNeoTimeZoneSkeleton; +use icu_calendar::types::YearAmbiguity; use icu_provider::prelude::*; use marker_attrs::GlueType; use zerovec::ule::AsULE; @@ -27,7 +24,23 @@ pub(crate) struct RawNeoOptions { pub(crate) length: NeoSkeletonLength, pub(crate) alignment: Option, pub(crate) year_style: Option, - pub(crate) fractional_second_digits: Option, + pub(crate) time_precision: Option, +} + +impl RawNeoOptions { + #[cfg(feature = "serde")] + pub(crate) fn merge(self, other: RawNeoOptions) -> Self { + Self { + length: self.length, + alignment: self.alignment.or(other.alignment), + year_style: self.year_style.or(other.year_style), + time_precision: self.time_precision.or(other.time_precision), + } + } +} + +#[derive(Debug, Copy, Clone, Default)] +pub(crate) struct RawPreferences { pub(crate) hour_cycle: Option, } @@ -53,6 +66,7 @@ pub(crate) enum DatePatternDataBorrowed<'a> { pub(crate) enum OverlapPatternSelectionData { SkeletonDateTime { options: RawNeoOptions, + prefs: RawPreferences, payload: DataPayload, }, } @@ -61,6 +75,7 @@ pub(crate) enum OverlapPatternSelectionData { pub(crate) enum TimePatternSelectionData { SkeletonTime { options: RawNeoOptions, + prefs: RawPreferences, payload: DataPayload, }, } @@ -77,7 +92,7 @@ pub(crate) enum TimePatternDataBorrowed<'a> { #[derive(Debug)] pub(crate) enum ZonePatternSelectionData { - SinglePatternItem(ResolvedNeoTimeZoneSkeleton, ::ULE), + SinglePatternItem(TimeZone, FieldLength, ::ULE), } #[derive(Debug, Copy, Clone)] @@ -182,9 +197,7 @@ impl DatePatternSelectionData { /// Borrows a pattern containing all of the fields that need to be loaded. #[inline] - pub(crate) fn pattern_items_for_data_loading( - &self, - ) -> impl Iterator + '_ { + pub(crate) fn pattern_items_for_data_loading(&self) -> impl Iterator + '_ { let items: &ZeroSlice = match self { DatePatternSelectionData::SkeletonDate { options, payload } => { payload @@ -193,9 +206,7 @@ impl DatePatternSelectionData { .items } }; - items - .iter() - .filter_map(FieldForDataLoading::try_from_pattern_item) + items.iter() } /// Borrows a resolved pattern based on the given datetime @@ -203,7 +214,23 @@ impl DatePatternSelectionData { match self { DatePatternSelectionData::SkeletonDate { options, payload } => { let year_style = options.year_style.unwrap_or(YearStyle::Auto); - let variant = input.resolve_year_style(year_style); + let variant = match ( + year_style, + input + .year + .map(|y| y.year_ambiguity()) + .unwrap_or(YearAmbiguity::EraAndCenturyRequired), + ) { + (YearStyle::Always, _) | (_, YearAmbiguity::EraAndCenturyRequired) => { + PackedSkeletonVariant::Variant1 + } + (YearStyle::Full, _) | (_, YearAmbiguity::CenturyRequired) => { + PackedSkeletonVariant::Variant0 + } + (YearStyle::Auto, YearAmbiguity::Unambiguous | YearAmbiguity::EraRequired) => { + PackedSkeletonVariant::Standard + } + }; DatePatternDataBorrowed::Resolved( payload.get().get(options.length, variant), options.alignment, @@ -214,70 +241,31 @@ impl DatePatternSelectionData { } impl ExtractedInput { - fn resolve_year_style(&self, year_style: YearStyle) -> PackedSkeletonVariant { - use icu_calendar::AnyCalendarKind; - enum YearDistance { - /// A nearby year that could be rendered with partial-precision format. - Near, - /// A year with implied era but for which partial-precision should not be used. - Medium, - /// A year for which the era should always be displayed. - Distant, - } - - if matches!(year_style, YearStyle::Always) { - return PackedSkeletonVariant::Variant1; + fn resolve_time_precision( + &self, + time_precision: TimePrecision, + ) -> (PackedSkeletonVariant, Option) { + enum HourMinute { + Hour, + Minute, } - let year_distance = match self.any_calendar_kind { - // Unknown calendar: always display the era - None => YearDistance::Distant, - // TODO(#4478): This is extremely oversimplistic and it should be data-driven. - Some(AnyCalendarKind::Buddhist) - | Some(AnyCalendarKind::Coptic) - | Some(AnyCalendarKind::Ethiopian) - | Some(AnyCalendarKind::EthiopianAmeteAlem) - | Some(AnyCalendarKind::Hebrew) - | Some(AnyCalendarKind::Indian) - | Some(AnyCalendarKind::IslamicCivil) - | Some(AnyCalendarKind::IslamicObservational) - | Some(AnyCalendarKind::IslamicTabular) - | Some(AnyCalendarKind::IslamicUmmAlQura) - | Some(AnyCalendarKind::Japanese) - | Some(AnyCalendarKind::JapaneseExtended) - | Some(AnyCalendarKind::Persian) - | Some(AnyCalendarKind::Roc) => YearDistance::Medium, - Some(AnyCalendarKind::Chinese) - | Some(AnyCalendarKind::Dangi) - | Some(AnyCalendarKind::Iso) => YearDistance::Near, - Some(AnyCalendarKind::Gregorian) => match self.year { - None => YearDistance::Distant, - Some(year) if year.era_year_or_extended() < 1000 => YearDistance::Distant, - Some(year) - if !matches!( - year.formatting_era(), - Some(icu_calendar::types::FormattingEra::Index(1, _fallback)) - ) => - { - YearDistance::Distant - } - Some(year) - if year.era_year_or_extended() < 1950 - || year.era_year_or_extended() >= 2050 => - { - YearDistance::Medium - } - Some(_) => YearDistance::Near, - }, - Some(_) => { - debug_assert!(false, "unknown calendar during year style resolution"); - YearDistance::Distant - } + let smallest_required_field = match time_precision { + TimePrecision::HourExact => return (PackedSkeletonVariant::Standard, None), + TimePrecision::MinuteExact => return (PackedSkeletonVariant::Variant0, None), + TimePrecision::SecondExact(f) => return (PackedSkeletonVariant::Variant1, Some(f)), + TimePrecision::HourPlus => HourMinute::Hour, + TimePrecision::MinutePlus => HourMinute::Minute, + TimePrecision::SecondPlus => return (PackedSkeletonVariant::Variant1, None), }; - - match (year_style, year_distance) { - (YearStyle::Always, _) | (_, YearDistance::Distant) => PackedSkeletonVariant::Variant1, - (YearStyle::Full, _) | (_, YearDistance::Medium) => PackedSkeletonVariant::Variant0, - (YearStyle::Auto, YearDistance::Near) => PackedSkeletonVariant::Standard, + let minute = self.minute.unwrap_or_default(); + let second = self.second.unwrap_or_default(); + let nanosecond = self.nanosecond.unwrap_or_default(); + if !nanosecond.is_zero() || !second.is_zero() { + (PackedSkeletonVariant::Variant1, None) + } else if !minute.is_zero() || matches!(smallest_required_field, HourMinute::Minute) { + (PackedSkeletonVariant::Variant0, None) + } else { + (PackedSkeletonVariant::Standard, None) } } } @@ -299,6 +287,7 @@ impl OverlapPatternSelectionData { locale: &DataLocale, attributes: &DataMarkerAttributes, options: RawNeoOptions, + prefs: RawPreferences, ) -> Result { let payload = provider .load_bound(DataRequest { @@ -306,14 +295,16 @@ impl OverlapPatternSelectionData { ..Default::default() })? .payload; - Ok(Self::SkeletonDateTime { options, payload }) + Ok(Self::SkeletonDateTime { + options, + prefs, + payload, + }) } /// Borrows a pattern containing all of the fields that need to be loaded. #[inline] - pub(crate) fn pattern_items_for_data_loading( - &self, - ) -> impl Iterator + '_ { + pub(crate) fn pattern_items_for_data_loading(&self) -> impl Iterator + '_ { let items: &ZeroSlice = match self { OverlapPatternSelectionData::SkeletonDateTime { options, payload, .. @@ -324,22 +315,32 @@ impl OverlapPatternSelectionData { .items } }; - items - .iter() - .filter_map(FieldForDataLoading::try_from_pattern_item) + items.iter() } /// Borrows a resolved pattern based on the given datetime pub(crate) fn select(&self, input: &ExtractedInput) -> TimePatternDataBorrowed { match self { - OverlapPatternSelectionData::SkeletonDateTime { options, payload } => { - let year_style = options.year_style.unwrap_or(YearStyle::Auto); - let variant = input.resolve_year_style(year_style); + OverlapPatternSelectionData::SkeletonDateTime { + options, + prefs, + payload, + } => { + // Currently, none of the overlap patterns have a year field, + // so we can use the variant to select the time precision. + // + // We do not currently support overlap patterns with both a + // year and a time because that would involve 3*3 = 9 variants + // instead of 3 variants. + debug_assert!(options.year_style.is_none()); + let time_precision = options.time_precision.unwrap_or(TimePrecision::SecondPlus); + let (variant, fractional_second_digits) = + input.resolve_time_precision(time_precision); TimePatternDataBorrowed::Resolved( payload.get().get(options.length, variant), options.alignment, - options.hour_cycle, - options.fractional_second_digits, + prefs.hour_cycle, + fractional_second_digits, ) } } @@ -350,17 +351,18 @@ impl TimePatternSelectionData { pub(crate) fn try_new_with_skeleton( provider: &(impl BoundDataProvider + ?Sized), locale: &DataLocale, - components: NeoTimeComponents, + components: TimeFieldSet, options: RawNeoOptions, + prefs: RawPreferences, ) -> Result { // First try to load with the explicit hour cycle. If there is no explicit hour cycle, // or if loading the explicit hour cycle fails, then load with the default hour cycle. let mut maybe_payload = None; - if let Some(hour_cycle) = options.hour_cycle { + if let Some(hour_cycle) = prefs.hour_cycle { maybe_payload = provider .load_bound(DataRequest { id: DataIdentifierBorrowed::for_marker_attributes_and_locale( - components.with_hour_cycle(hour_cycle.into()).id_str(), + components.id_str_for_hour_cycle(Some(hour_cycle)), locale, ), ..Default::default() @@ -374,7 +376,7 @@ impl TimePatternSelectionData { provider .load_bound(DataRequest { id: DataIdentifierBorrowed::for_marker_attributes_and_locale( - components.id_str(), + components.id_str_for_hour_cycle(None), locale, ), ..Default::default() @@ -382,14 +384,16 @@ impl TimePatternSelectionData { .payload } }; - Ok(Self::SkeletonTime { options, payload }) + Ok(Self::SkeletonTime { + options, + prefs, + payload, + }) } /// Borrows a pattern containing all of the fields that need to be loaded. #[inline] - pub(crate) fn pattern_items_for_data_loading( - &self, - ) -> impl Iterator + '_ { + pub(crate) fn pattern_items_for_data_loading(&self) -> impl Iterator + '_ { let items: &ZeroSlice = match self { TimePatternSelectionData::SkeletonTime { options, payload, .. @@ -400,22 +404,25 @@ impl TimePatternSelectionData { .items } }; - items - .iter() - .filter_map(FieldForDataLoading::try_from_pattern_item) + items.iter() } /// Borrows a resolved pattern based on the given datetime - pub(crate) fn select(&self, _input: &ExtractedInput) -> TimePatternDataBorrowed { + pub(crate) fn select(&self, input: &ExtractedInput) -> TimePatternDataBorrowed { match self { - TimePatternSelectionData::SkeletonTime { options, payload } => { + TimePatternSelectionData::SkeletonTime { + options, + prefs, + payload, + } => { + let time_precision = options.time_precision.unwrap_or(TimePrecision::SecondPlus); + let (variant, fractional_second_digits) = + input.resolve_time_precision(time_precision); TimePatternDataBorrowed::Resolved( - payload - .get() - .get(options.length, PackedSkeletonVariant::Standard), + payload.get().get(options.length, variant), options.alignment, - options.hour_cycle, - options.fractional_second_digits, + prefs.hour_cycle, + fractional_second_digits, ) } } @@ -435,33 +442,29 @@ impl<'a> TimePatternDataBorrowed<'a> { } impl ZonePatternSelectionData { - pub(crate) fn new_with_skeleton( - components: NeoTimeZoneStyle, - options: RawNeoOptions, - is_only_field: bool, - ) -> Self { - let length = if is_only_field { - options.length - } else { - NeoSkeletonLength::Short - }; - let time_zone = components.resolve(length); - let pattern_item = PatternItem::Field(time_zone.to_field()); - Self::SinglePatternItem(time_zone, pattern_item.to_unaligned()) + pub(crate) fn new_with_skeleton(field_set: ZoneFieldSet) -> Self { + let (symbol, length) = field_set.to_field(); + let pattern_item = PatternItem::Field(Field { + symbol: FieldSymbol::TimeZone(symbol), + length, + }); + Self::SinglePatternItem(symbol, length, pattern_item.to_unaligned()) } /// Borrows a pattern containing all of the fields that need to be loaded. #[inline] - pub(crate) fn pattern_items_for_data_loading( - &self, - ) -> impl Iterator + '_ { - let Self::SinglePatternItem(time_zone, _) = self; - [FieldForDataLoading::TimeZone(*time_zone)].into_iter() + pub(crate) fn pattern_items_for_data_loading(&self) -> impl Iterator + '_ { + let Self::SinglePatternItem(symbol, length, _) = self; + [PatternItem::Field(Field { + symbol: FieldSymbol::TimeZone(*symbol), + length: *length, + })] + .into_iter() } /// Borrows a resolved pattern based on the given datetime pub(crate) fn select(&self, _input: &ExtractedInput) -> ZonePatternDataBorrowed { - let Self::SinglePatternItem(_, pattern_item) = self; + let Self::SinglePatternItem(_, _, pattern_item) = self; ZonePatternDataBorrowed::SinglePatternItem(pattern_item) } } @@ -483,45 +486,49 @@ impl DateTimeZonePatternSelectionData { time_provider: &(impl BoundDataProvider + ?Sized), glue_provider: &(impl BoundDataProvider + ?Sized), locale: &DataLocale, - components: NeoComponents, - options: RawNeoOptions, + skeleton: CompositeFieldSet, + prefs: RawPreferences, ) -> Result { - match components { - NeoComponents::Date(components) => { + match skeleton { + CompositeFieldSet::Date(field_set) => { + let options = field_set.to_raw_options(); let selection = DatePatternSelectionData::try_new_with_skeleton( date_provider, locale, - components.id_str(), + field_set.id_str(), options, )?; Ok(Self::Date(selection)) } - NeoComponents::CalendarPeriod(components) => { + CompositeFieldSet::CalendarPeriod(field_set) => { + let options = field_set.to_raw_options(); let selection = DatePatternSelectionData::try_new_with_skeleton( date_provider, locale, - components.id_str(), + field_set.id_str(), options, )?; Ok(Self::Date(selection)) } - NeoComponents::Time(components) => { + CompositeFieldSet::Time(field_set) => { + let options = field_set.to_raw_options(); let selection = TimePatternSelectionData::try_new_with_skeleton( time_provider, locale, - components, + field_set, options, + prefs, )?; Ok(Self::Time(selection)) } - NeoComponents::Zone(components) => { - let selection = - ZonePatternSelectionData::new_with_skeleton(components, options, true); + CompositeFieldSet::Zone(field_set) => { + let selection = ZonePatternSelectionData::new_with_skeleton(field_set); Ok(Self::Zone(selection)) } - NeoComponents::DateTime(date_components, time_components) => { + CompositeFieldSet::DateTime(field_set) => { + let options = field_set.to_raw_options(); // TODO(#5387): load the patterns for custom hour cycles here - if let (Some(attributes), None) = (components.id_str(), options.hour_cycle) { + if let (Some(attributes), None) = (field_set.id_str(), prefs.hour_cycle) { // Try loading an overlap pattern. if let Some(overlap) = OverlapPatternSelectionData::try_new_with_skeleton( // Note: overlap patterns are stored in the date provider @@ -529,6 +536,7 @@ impl DateTimeZonePatternSelectionData { locale, attributes, options, + prefs, ) .allow_identifier_not_found()? { @@ -538,57 +546,75 @@ impl DateTimeZonePatternSelectionData { let date = DatePatternSelectionData::try_new_with_skeleton( date_provider, locale, - date_components.id_str(), + field_set.to_date_field_set().id_str(), options, )?; let time = TimePatternSelectionData::try_new_with_skeleton( time_provider, locale, - time_components, + field_set.to_time_field_set(), options, + prefs, )?; let glue = Self::load_glue(glue_provider, locale, options, GlueType::DateTime)?; Ok(Self::DateTimeGlue { date, time, glue }) } - NeoComponents::DateZone(date_components, zone_components) => { + CompositeFieldSet::DateZone(field_set, time_zone_style) => { + let options = field_set.to_raw_options(); let date = DatePatternSelectionData::try_new_with_skeleton( date_provider, locale, - date_components.id_str(), + field_set.id_str(), options, )?; - let zone = - ZonePatternSelectionData::new_with_skeleton(zone_components, options, false); + // Always use the short length for time zones when mixed with another field (Date) + let zone_field_set = ZoneFieldSet::from_time_zone_style_and_length( + time_zone_style, + NeoSkeletonLength::Short, + ); + let zone = ZonePatternSelectionData::new_with_skeleton(zone_field_set); let glue = Self::load_glue(glue_provider, locale, options, GlueType::DateZone)?; Ok(Self::DateZoneGlue { date, zone, glue }) } - NeoComponents::TimeZone(time_components, zone_components) => { + CompositeFieldSet::TimeZone(field_set, time_zone_style) => { + let options = field_set.to_raw_options(); let time = TimePatternSelectionData::try_new_with_skeleton( time_provider, locale, - time_components, + field_set, options, + prefs, )?; - let zone = - ZonePatternSelectionData::new_with_skeleton(zone_components, options, false); + // Always use the short length for time zones when mixed with another field (Time) + let zone_field_set = ZoneFieldSet::from_time_zone_style_and_length( + time_zone_style, + NeoSkeletonLength::Short, + ); + let zone = ZonePatternSelectionData::new_with_skeleton(zone_field_set); let glue = Self::load_glue(glue_provider, locale, options, GlueType::TimeZone)?; Ok(Self::TimeZoneGlue { time, zone, glue }) } - NeoComponents::DateTimeZone(date_components, time_components, zone_components) => { + CompositeFieldSet::DateTimeZone(field_set, time_zone_style) => { + let options = field_set.to_raw_options(); let date = DatePatternSelectionData::try_new_with_skeleton( date_provider, locale, - date_components.id_str(), + field_set.to_date_field_set().id_str(), options, )?; let time = TimePatternSelectionData::try_new_with_skeleton( time_provider, locale, - time_components, + field_set.to_time_field_set(), options, + prefs, )?; - let zone = - ZonePatternSelectionData::new_with_skeleton(zone_components, options, false); + // Always use the short length for time zones when mixed with another field (Date + Time) + let zone_field_set = ZoneFieldSet::from_time_zone_style_and_length( + time_zone_style, + NeoSkeletonLength::Short, + ); + let zone = ZonePatternSelectionData::new_with_skeleton(zone_field_set); let glue = Self::load_glue(glue_provider, locale, options, GlueType::DateTimeZone)?; Ok(Self::DateTimeZoneGlue { date, @@ -628,9 +654,7 @@ impl DateTimeZonePatternSelectionData { /// Returns an iterator over the pattern items that may need to be loaded. #[inline] - pub(crate) fn pattern_items_for_data_loading( - &self, - ) -> impl Iterator + '_ { + pub(crate) fn pattern_items_for_data_loading(&self) -> impl Iterator + '_ { let (date, time, zone, overlap) = match self { DateTimeZonePatternSelectionData::Date(date) => (Some(date), None, None, None), DateTimeZonePatternSelectionData::Time(time) => (None, Some(time), None, None), @@ -865,7 +889,7 @@ impl<'a> ItemsAndOptions<'a> { | FieldSymbol::Hour(_) ) { - field.length = FieldLength::TwoDigit; + field.length = FieldLength::Two; } if let Some(hour_cycle) = self.hour_cycle { if let FieldSymbol::Hour(_) = field.symbol { diff --git a/components/datetime/src/scaffold/calendar.rs b/components/datetime/src/scaffold/calendar.rs index 53fffed9f3d..66faf8b9eb2 100644 --- a/components/datetime/src/scaffold/calendar.rs +++ b/components/datetime/src/scaffold/calendar.rs @@ -6,6 +6,7 @@ use crate::provider::{neo::*, *}; use crate::scaffold::UnstableSealed; +use crate::MismatchedCalendarError; use core::marker::PhantomData; use icu_calendar::any_calendar::AnyCalendarKind; use icu_calendar::cal::Chinese; @@ -441,51 +442,74 @@ impl ConvertCalendar for TimeZoneInfo { /// /// All formattable types should implement this trait. pub trait IsAnyCalendarKind { - /// Whether this type is compatible with the given calendar. + /// Checks whether this type is compatible with the given calendar. /// - /// Types that are agnostic to calendar systems should return `true`. - fn is_any_calendar_kind(&self, any_calendar_kind: AnyCalendarKind) -> bool; + /// Types that are agnostic to calendar systems should return `Ok(())`. + fn check_any_calendar_kind( + &self, + any_calendar_kind: AnyCalendarKind, + ) -> Result<(), MismatchedCalendarError>; } impl> IsAnyCalendarKind for Date { #[inline] - fn is_any_calendar_kind(&self, any_calendar_kind: AnyCalendarKind) -> bool { - self.calendar().any_calendar_kind() == Some(any_calendar_kind) + fn check_any_calendar_kind( + &self, + any_calendar_kind: AnyCalendarKind, + ) -> Result<(), MismatchedCalendarError> { + if self.calendar().any_calendar_kind() == Some(any_calendar_kind) { + Ok(()) + } else { + Err(MismatchedCalendarError { + this_kind: any_calendar_kind, + date_kind: self.calendar().any_calendar_kind(), + }) + } } } impl IsAnyCalendarKind for Time { #[inline] - fn is_any_calendar_kind(&self, _: AnyCalendarKind) -> bool { - true + fn check_any_calendar_kind(&self, _: AnyCalendarKind) -> Result<(), MismatchedCalendarError> { + Ok(()) } } impl> IsAnyCalendarKind for DateTime { #[inline] - fn is_any_calendar_kind(&self, any_calendar_kind: AnyCalendarKind) -> bool { - self.date.calendar().any_calendar_kind() == Some(any_calendar_kind) + fn check_any_calendar_kind( + &self, + any_calendar_kind: AnyCalendarKind, + ) -> Result<(), MismatchedCalendarError> { + if self.date.calendar().any_calendar_kind() == Some(any_calendar_kind) { + Ok(()) + } else { + Err(MismatchedCalendarError { + this_kind: any_calendar_kind, + date_kind: self.date.calendar().any_calendar_kind(), + }) + } } } impl, Z> IsAnyCalendarKind for CustomZonedDateTime { #[inline] - fn is_any_calendar_kind(&self, _: AnyCalendarKind) -> bool { - true + fn check_any_calendar_kind(&self, _: AnyCalendarKind) -> Result<(), MismatchedCalendarError> { + Ok(()) } } impl IsAnyCalendarKind for UtcOffset { #[inline] - fn is_any_calendar_kind(&self, _: AnyCalendarKind) -> bool { - true + fn check_any_calendar_kind(&self, _: AnyCalendarKind) -> Result<(), MismatchedCalendarError> { + Ok(()) } } impl IsAnyCalendarKind for TimeZoneInfo { #[inline] - fn is_any_calendar_kind(&self, _: AnyCalendarKind) -> bool { - true + fn check_any_calendar_kind(&self, _: AnyCalendarKind) -> Result<(), MismatchedCalendarError> { + Ok(()) } } diff --git a/components/datetime/src/scaffold/dynamic_impls.rs b/components/datetime/src/scaffold/dynamic_impls.rs new file mode 100644 index 00000000000..effb734c24b --- /dev/null +++ b/components/datetime/src/scaffold/dynamic_impls.rs @@ -0,0 +1,221 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use super::*; +use crate::provider::{neo::*, time_zones::tz, *}; +use crate::{dynamic::*, format::neo::DateTimeNamesMarker}; +use icu_calendar::{ + types::{ + DayOfMonth, DayOfYearInfo, IsoHour, IsoMinute, IsoSecond, IsoWeekday, MonthInfo, + NanoSecond, YearInfo, + }, + Date, Iso, Time, +}; +use icu_provider::marker::NeverMarker; +use icu_timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant}; + +impl UnstableSealed for DateFieldSet {} + +impl DateTimeNamesMarker for DateFieldSet { + type YearNames = datetime_marker_helper!(@names/year, yes); + type MonthNames = datetime_marker_helper!(@names/month, yes); + type WeekdayNames = datetime_marker_helper!(@names/weekday, yes); + type DayPeriodNames = datetime_marker_helper!(@names/dayperiod,); + type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials,); + type ZoneLocations = datetime_marker_helper!(@names/zone/locations,); + type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long,); + type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short,); + type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long,); + type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short,); + type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods,); +} + +impl DateInputMarkers for DateFieldSet { + type YearInput = datetime_marker_helper!(@input/year, yes); + type MonthInput = datetime_marker_helper!(@input/month, yes); + type DayOfMonthInput = datetime_marker_helper!(@input/day_of_month, yes); + type DayOfYearInput = datetime_marker_helper!(@input/day_of_year, yes); + type DayOfWeekInput = datetime_marker_helper!(@input/day_of_week, yes); +} + +impl TypedDateDataMarkers for DateFieldSet { + type DateSkeletonPatternsV1Marker = datetime_marker_helper!(@dates/typed, yes); + type YearNamesV1Marker = datetime_marker_helper!(@years/typed, yes); + type MonthNamesV1Marker = datetime_marker_helper!(@months/typed, yes); + type WeekdayNamesV1Marker = datetime_marker_helper!(@weekdays, yes); +} + +impl DateDataMarkers for DateFieldSet { + type Skel = datetime_marker_helper!(@calmarkers, yes); + type Year = datetime_marker_helper!(@calmarkers, yes); + type Month = datetime_marker_helper!(@calmarkers, yes); + type WeekdayNamesV1Marker = datetime_marker_helper!(@weekdays, yes); +} + +impl DateTimeMarkers for DateFieldSet { + type D = Self; + type T = NeoNeverMarker; + type Z = NeoNeverMarker; + type GluePatternV1Marker = datetime_marker_helper!(@glue,); +} + +impl UnstableSealed for CalendarPeriodFieldSet {} + +impl DateTimeNamesMarker for CalendarPeriodFieldSet { + type YearNames = datetime_marker_helper!(@names/year, yes); + type MonthNames = datetime_marker_helper!(@names/month, yes); + type WeekdayNames = datetime_marker_helper!(@names/weekday,); + type DayPeriodNames = datetime_marker_helper!(@names/dayperiod,); + type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials,); + type ZoneLocations = datetime_marker_helper!(@names/zone/locations,); + type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long,); + type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short,); + type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long,); + type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short,); + type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods,); +} + +impl DateInputMarkers for CalendarPeriodFieldSet { + type YearInput = datetime_marker_helper!(@input/year, yes); + type MonthInput = datetime_marker_helper!(@input/month, yes); + type DayOfMonthInput = datetime_marker_helper!(@input/day_of_month,); + type DayOfWeekInput = datetime_marker_helper!(@input/day_of_week,); + type DayOfYearInput = datetime_marker_helper!(@input/day_of_year,); +} + +impl TypedDateDataMarkers for CalendarPeriodFieldSet { + type DateSkeletonPatternsV1Marker = datetime_marker_helper!(@dates/typed, yes); + type YearNamesV1Marker = datetime_marker_helper!(@years/typed, yes); + type MonthNamesV1Marker = datetime_marker_helper!(@months/typed, yes); + type WeekdayNamesV1Marker = datetime_marker_helper!(@weekdays,); +} + +impl DateDataMarkers for CalendarPeriodFieldSet { + type Skel = datetime_marker_helper!(@calmarkers, yes); + type Year = datetime_marker_helper!(@calmarkers, yes); + type Month = datetime_marker_helper!(@calmarkers, yes); + type WeekdayNamesV1Marker = datetime_marker_helper!(@weekdays,); +} + +impl DateTimeMarkers for CalendarPeriodFieldSet { + type D = Self; + type T = NeoNeverMarker; + type Z = NeoNeverMarker; + type GluePatternV1Marker = datetime_marker_helper!(@glue,); +} + +impl UnstableSealed for TimeFieldSet {} + +impl DateTimeNamesMarker for TimeFieldSet { + type YearNames = datetime_marker_helper!(@names/year,); + type MonthNames = datetime_marker_helper!(@names/month,); + type WeekdayNames = datetime_marker_helper!(@names/weekday,); + type DayPeriodNames = datetime_marker_helper!(@names/dayperiod, yes); + type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials,); + type ZoneLocations = datetime_marker_helper!(@names/zone/locations,); + type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long,); + type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short,); + type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long,); + type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short,); + type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods,); +} + +impl TimeMarkers for TimeFieldSet { + type DayPeriodNamesV1Marker = datetime_marker_helper!(@dayperiods, yes); + type TimeSkeletonPatternsV1Marker = datetime_marker_helper!(@times, yes); + type HourInput = datetime_marker_helper!(@input/hour, yes); + type MinuteInput = datetime_marker_helper!(@input/minute, yes); + type SecondInput = datetime_marker_helper!(@input/second, yes); + type NanoSecondInput = datetime_marker_helper!(@input/nanosecond, yes); +} + +impl DateTimeMarkers for TimeFieldSet { + type D = NeoNeverMarker; + type T = Self; + type Z = NeoNeverMarker; + type GluePatternV1Marker = datetime_marker_helper!(@glue,); +} + +impl UnstableSealed for ZoneFieldSet {} + +impl DateTimeNamesMarker for ZoneFieldSet { + type YearNames = datetime_marker_helper!(@names/year,); + type MonthNames = datetime_marker_helper!(@names/month,); + type WeekdayNames = datetime_marker_helper!(@names/weekday,); + type DayPeriodNames = datetime_marker_helper!(@names/dayperiod,); + type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials, yes); + type ZoneLocations = datetime_marker_helper!(@names/zone/locations, yes); + type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long, yes); + type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short, yes); + type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long, yes); + type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short, yes); + type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods, yes); +} + +impl ZoneMarkers for ZoneFieldSet { + type TimeZoneIdInput = datetime_marker_helper!(@input/timezone/id, yes); + type TimeZoneOffsetInput = datetime_marker_helper!(@input/timezone/offset, yes); + type TimeZoneVariantInput = datetime_marker_helper!(@input/timezone/variant, yes); + type TimeZoneLocalTimeInput = datetime_marker_helper!(@input/timezone/local_time, yes); + type EssentialsV1Marker = datetime_marker_helper!(@data/zone/essentials, yes); + type LocationsV1Marker = datetime_marker_helper!(@data/zone/locations, yes); + type GenericLongV1Marker = datetime_marker_helper!(@data/zone/generic_long, yes); + type GenericShortV1Marker = datetime_marker_helper!(@data/zone/generic_short, yes); + type SpecificLongV1Marker = datetime_marker_helper!(@data/zone/specific_long, yes); + type SpecificShortV1Marker = datetime_marker_helper!(@data/zone/specific_short, yes); + type MetazonePeriodV1Marker = datetime_marker_helper!(@data/zone/metazone_periods, yes); +} + +impl DateTimeMarkers for ZoneFieldSet { + type D = NeoNeverMarker; + type T = NeoNeverMarker; + type Z = Self; + type GluePatternV1Marker = datetime_marker_helper!(@glue,); +} + +impl UnstableSealed for CompositeDateTimeFieldSet {} + +impl DateTimeNamesMarker for CompositeDateTimeFieldSet { + type YearNames = datetime_marker_helper!(@names/year, yes); + type MonthNames = datetime_marker_helper!(@names/month, yes); + type WeekdayNames = datetime_marker_helper!(@names/weekday, yes); + type DayPeriodNames = datetime_marker_helper!(@names/dayperiod, yes); + type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials,); + type ZoneLocations = datetime_marker_helper!(@names/zone/locations,); + type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long,); + type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short,); + type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long,); + type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short,); + type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods,); +} + +impl DateTimeMarkers for CompositeDateTimeFieldSet { + type D = DateFieldSet; + type T = TimeFieldSet; + type Z = NeoNeverMarker; + type GluePatternV1Marker = datetime_marker_helper!(@glue, yes); +} + +impl UnstableSealed for CompositeFieldSet {} + +impl DateTimeNamesMarker for CompositeFieldSet { + type YearNames = datetime_marker_helper!(@names/year, yes); + type MonthNames = datetime_marker_helper!(@names/month, yes); + type WeekdayNames = datetime_marker_helper!(@names/weekday, yes); + type DayPeriodNames = datetime_marker_helper!(@names/dayperiod, yes); + type ZoneEssentials = datetime_marker_helper!(@names/zone/essentials, yes); + type ZoneLocations = datetime_marker_helper!(@names/zone/locations, yes); + type ZoneGenericLong = datetime_marker_helper!(@names/zone/generic_long, yes); + type ZoneGenericShort = datetime_marker_helper!(@names/zone/generic_short, yes); + type ZoneSpecificLong = datetime_marker_helper!(@names/zone/specific_long, yes); + type ZoneSpecificShort = datetime_marker_helper!(@names/zone/specific_short, yes); + type MetazoneLookup = datetime_marker_helper!(@names/zone/metazone_periods, yes); +} + +impl DateTimeMarkers for CompositeFieldSet { + type D = DateFieldSet; + type T = TimeFieldSet; + type Z = ZoneFieldSet; + type GluePatternV1Marker = datetime_marker_helper!(@glue, yes); +} diff --git a/components/datetime/src/scaffold/fieldset_traits.rs b/components/datetime/src/scaffold/fieldset_traits.rs index d8ab610c99e..f058846a36d 100644 --- a/components/datetime/src/scaffold/fieldset_traits.rs +++ b/components/datetime/src/scaffold/fieldset_traits.rs @@ -4,7 +4,6 @@ use crate::{ format::neo::*, - neo_skeleton::*, provider::{neo::*, time_zones::tz, *}, scaffold::*, }; @@ -14,42 +13,16 @@ use icu_calendar::{ IslamicUmmAlQuraCacheV1Marker, JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, }, types::{ - DayOfMonth, IsoHour, IsoMinute, IsoSecond, IsoWeekday, MonthInfo, NanoSecond, YearInfo, + DayOfMonth, DayOfYearInfo, IsoHour, IsoMinute, IsoSecond, IsoWeekday, MonthInfo, + NanoSecond, YearInfo, }, - AnyCalendarKind, Date, Iso, Time, + Date, Iso, Time, }; use icu_decimal::provider::DecimalSymbolsV1Marker; use icu_provider::{marker::NeverMarker, prelude::*}; use icu_timezone::scaffold::IntoOption; use icu_timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant}; -/// Trait for components that can be formatted at runtime. -pub trait IsRuntimeComponents: UnstableSealed + GetField {} - -/// A trait associating [`NeoComponents`]. -pub trait HasConstComponents { - /// The associated components. - const COMPONENTS: NeoComponents; -} - -/// A trait associating [`NeoDateComponents`]. -pub trait HasConstDateComponents { - /// The associated components. - const COMPONENTS: NeoDateComponents; -} - -/// A trait associating [`NeoTimeComponents`]. -pub trait HasConstTimeComponents { - /// The associated components. - const COMPONENTS: NeoTimeComponents; -} - -/// A trait associating [`NeoTimeZoneStyle`]. -pub trait HasConstZoneComponent { - /// The associated component. - const COMPONENT: NeoTimeZoneStyle; -} - // TODO: Add WeekCalculator and FixedDecimalFormatter optional bindings here /// A trait associating types for date formatting in any calendar @@ -61,10 +34,10 @@ pub trait DateInputMarkers: UnstableSealed { type MonthInput: IntoOption; /// Marker for resolving the day-of-month input field. type DayOfMonthInput: IntoOption; + /// Marker for resolving the day-of-year input field. + type DayOfYearInput: IntoOption; /// Marker for resolving the day-of-week input field. type DayOfWeekInput: IntoOption; - /// Marker for resolving the any-calendar-kind input field. - type AnyCalendarKindInput: IntoOption; } /// A trait associating types for date formatting in a specific calendar @@ -152,14 +125,6 @@ pub trait DateTimeMarkers: UnstableSealed + DateTimeNamesMarker { /// /// Should implement [`ZoneMarkers`]. type Z; - /// Type of the length option in the constructor. - type LengthOption: IntoOption; - /// Type of the alignment option in the constructor. - type AlignmentOption: IntoOption; - /// Type of the year style option in the constructor. - type YearStyleOption: IntoOption; - /// Type of the fractional seconds display option in the constructor. - type FractionalSecondDigitsOption: IntoOption; /// Marker for loading the date/time glue pattern. type GluePatternV1Marker: DataMarker>; } @@ -170,7 +135,7 @@ pub trait AllInputMarkers: + GetField<::MonthInput> + GetField<::DayOfMonthInput> + GetField<::DayOfWeekInput> - + GetField<::AnyCalendarKindInput> + + GetField<::DayOfYearInput> + GetField<::HourInput> + GetField<::MinuteInput> + GetField<::SecondInput> @@ -196,7 +161,7 @@ where + GetField<::MonthInput> + GetField<::DayOfMonthInput> + GetField<::DayOfWeekInput> - + GetField<::AnyCalendarKindInput> + + GetField<::DayOfYearInput> + GetField<::HourInput> + GetField<::MinuteInput> + GetField<::SecondInput> @@ -446,8 +411,8 @@ impl DateInputMarkers for NeoNeverMarker { type YearInput = (); type MonthInput = (); type DayOfMonthInput = (); + type DayOfYearInput = (); type DayOfWeekInput = (); - type AnyCalendarKindInput = (); } impl TypedDateDataMarkers for NeoNeverMarker { @@ -554,8 +519,8 @@ macro_rules! datetime_marker_helper { (@option/alignment, yes) => { Option }; - (@option/fractionalsecondigits, yes) => { - Option + (@option/timeprecision, yes) => { + Option }; (@option/$any:ident,) => { () @@ -575,9 +540,6 @@ macro_rules! datetime_marker_helper { (@input/day_of_year, yes) => { DayOfYearInfo }; - (@input/any_calendar_kind, yes) => { - AnyCalendarKind - }; (@input/hour, yes) => { IsoHour }; diff --git a/components/datetime/src/scaffold/get_field.rs b/components/datetime/src/scaffold/get_field.rs index d24425ba71d..eef721c88e0 100644 --- a/components/datetime/src/scaffold/get_field.rs +++ b/components/datetime/src/scaffold/get_field.rs @@ -4,12 +4,11 @@ use crate::scaffold::*; use icu_calendar::{ - any_calendar::IntoAnyCalendar, types::{ DayOfMonth, DayOfYearInfo, IsoHour, IsoMinute, IsoSecond, IsoWeekday, MonthInfo, NanoSecond, YearInfo, }, - AnyCalendarKind, AsCalendar, Calendar, Date, DateTime, Iso, Time, + AsCalendar, Calendar, Date, DateTime, Iso, Time, }; use icu_timezone::{ CustomZonedDateTime, TimeZoneBcp47Id, TimeZoneInfo, TimeZoneModel, UtcOffset, ZoneVariant, @@ -49,10 +48,10 @@ macro_rules! impl_get_field { } } }; - ($(< $($generics0:tt),+ >)? $type:ident $(< $($generics1:tt),+ >)?, fractional_second_digits, yes) => { - impl $(<$($generics0),+>)? GetField> for $type $(<$($generics1),+>)? { - fn get_field(&self) -> Option { - self.fractional_second_digits + ($(< $($generics0:tt),+ >)? $type:ident $(< $($generics1:tt),+ >)?, time_precision, yes) => { + impl $(<$($generics0),+>)? GetField> for $type $(<$($generics1),+>)? { + fn get_field(&self) -> Option { + self.time_precision } } }; @@ -104,13 +103,6 @@ impl> GetField for Date< } } -impl> GetField for Date { - #[inline] - fn get_field(&self) -> AnyCalendarKind { - self.calendar().kind() - } -} - impl GetField for Time { #[inline] fn get_field(&self) -> IsoHour { @@ -174,13 +166,6 @@ impl> GetField for DateT } } -impl> GetField for DateTime { - #[inline] - fn get_field(&self) -> AnyCalendarKind { - self.date.calendar().kind() - } -} - impl> GetField for DateTime { #[inline] fn get_field(&self) -> IsoHour { @@ -252,15 +237,6 @@ impl, Z> GetField } } -impl, Z> GetField - for CustomZonedDateTime -{ - #[inline] - fn get_field(&self) -> AnyCalendarKind { - self.date.calendar().kind() - } -} - impl, Z> GetField for CustomZonedDateTime { #[inline] fn get_field(&self) -> IsoHour { diff --git a/components/datetime/src/scaffold/mod.rs b/components/datetime/src/scaffold/mod.rs index bd4ba24aa8a..70788f202e4 100644 --- a/components/datetime/src/scaffold/mod.rs +++ b/components/datetime/src/scaffold/mod.rs @@ -8,6 +8,7 @@ //! these items in userland code. mod calendar; +mod dynamic_impls; mod fieldset_traits; mod get_field; @@ -29,11 +30,6 @@ pub use fieldset_traits::AllInputMarkers; pub use fieldset_traits::DateDataMarkers; pub use fieldset_traits::DateInputMarkers; pub use fieldset_traits::DateTimeMarkers; -pub use fieldset_traits::HasConstComponents; -pub use fieldset_traits::HasConstDateComponents; -pub use fieldset_traits::HasConstTimeComponents; -pub use fieldset_traits::HasConstZoneComponent; -pub use fieldset_traits::IsRuntimeComponents; pub use fieldset_traits::NeoNeverMarker; pub use fieldset_traits::TimeMarkers; pub use fieldset_traits::TypedDateDataMarkers; diff --git a/components/datetime/src/size_test_macro.rs b/components/datetime/src/size_test_macro.rs new file mode 100644 index 00000000000..f6f8bb760ff --- /dev/null +++ b/components/datetime/src/size_test_macro.rs @@ -0,0 +1,90 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +/************************************************************************************* + * NOTE: PLEASE KEEP THIS FILE IN SYNC WITH ALL OTHER FILES NAMED size_test_macro.rs * + * TODO(#4467): Copy this file automatically * + *************************************************************************************/ + +/// Generates a test that checks the stack size of an item and a macro +/// that should be used with `#[doc]` to document it. +/// +/// ```text +/// size_test!(MyType, my_type_size, 32); +/// +/// // Add this annotation to the type's docs: +/// #[doc = my_type_size!()] +/// ``` +/// +/// The size should correspond to the Rust version in rust-toolchain.toml. +/// +/// If the size on latest beta differs from rust-toolchain.toml, use the +/// named arguments version of this macro to specify both sizes: +/// +/// ```text +/// size_test!(MyType, my_type_size, pinned = 32, beta = 24, nightly = 24); +/// ``` +/// +/// The test is ignored by default but runs in CI. To run the test locally, +/// run `cargo test -- --include-ignored` +macro_rules! size_test { + ($ty:ty, $id:ident, pinned = $pinned:literal, beta = $beta:literal, nightly = $nightly:literal) => { + macro_rules! $id { + () => { + concat!( + "\n", + "📏 This item has a stack size of ", + stringify!($pinned), + " bytes on the stable toolchain and ", + stringify!($beta), + " bytes on beta toolchain at release date." + ) + }; + } + #[test] + #[cfg_attr(not(icu4x_run_size_tests), ignore)] // Doesn't work on arbitrary Rust versions + fn $id() { + let size = core::mem::size_of::<$ty>(); + let success = match option_env!("CI_TOOLCHAIN") { + Some("nightly") => size == $nightly, + Some("beta") => size == $beta, + Some("pinned-stable") => size == $pinned, + // Manual invocation: match either size + _ => matches!(size, $pinned | $beta | $nightly), + }; + assert!( + success, + "size_of {} = {}.\n** To reproduce this failure, run `cargo test -- --ignored` **", + stringify!($ty), + size, + ); + } + }; + ($ty:ty, $id:ident, $size:literal) => { + macro_rules! $id { + () => { + concat!( + "📏 This item has a stack size of ", + stringify!($size), + " bytes on the stable toolchain at release date." + ) + }; + } + #[test] + #[cfg_attr(not(icu4x_run_size_tests), ignore)] // Doesn't work on arbitrary Rust versions + fn $id() { + let size = core::mem::size_of::<$ty>(); + let expected = $size; + assert_eq!( + size, + expected, + "size_of {} = {}.\n** To reproduce this failure, run `cargo test -- --ignored` **", + stringify!($ty), + size, + ); + } + }; +} + +pub(crate) use size_test; diff --git a/components/datetime/src/time_zone.rs b/components/datetime/src/time_zone.rs deleted file mode 100644 index 0e750fb4322..00000000000 --- a/components/datetime/src/time_zone.rs +++ /dev/null @@ -1,796 +0,0 @@ -// This file is part of ICU4X. For terms of use, please see the file -// called LICENSE at the top level of the ICU4X source tree -// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). - -//! A formatter specifically for the time zone. - -use crate::provider::time_zones::MetazoneId; -use crate::{ - fields::{FieldLength, TimeZone}, - input::ExtractedInput, - provider, -}; -use core::fmt; -use fixed_decimal::FixedDecimal; -use icu_calendar::{Date, Iso, Time}; -use icu_decimal::FixedDecimalFormatter; -use icu_timezone::provider::EPOCH; -use icu_timezone::{TimeZoneBcp47Id, UtcOffset, ZoneVariant}; -use writeable::Writeable; - -/// All time zone styles that this crate can format -#[derive(Debug, Copy, Clone)] -pub(crate) enum ResolvedNeoTimeZoneSkeleton { - Location, - GenericShort, - GenericLong, - SpecificShort, - SpecificLong, - OffsetShort, - OffsetLong, - Bcp47Id, - // UTS 35 defines 10 variants of ISO-8601-style time zone formats. - // They don't have their own names, so they are identified here by - // their datetime pattern strings. - Isox, - Isoxx, - Isoxxx, - Isoxxxx, - Isoxxxxx, - IsoX, - IsoXX, - IsoXXX, - IsoXXXX, - IsoXXXXX, - // TODO: - // `VV` "America/Los_Angeles" - // `VVV` "Los Angeles" -} - -impl ResolvedNeoTimeZoneSkeleton { - pub(crate) fn from_field(field_symbol: TimeZone, field_length: FieldLength) -> Option { - crate::tz_registry::field_to_resolved(field_symbol, field_length) - } - pub(crate) fn to_field(self) -> crate::fields::Field { - crate::tz_registry::resolved_to_field(self) - } - - pub(crate) fn units(self) -> impl Iterator { - match self { - // `z..zzzz` - ResolvedNeoTimeZoneSkeleton::SpecificShort - | ResolvedNeoTimeZoneSkeleton::SpecificLong => [ - Some(TimeZoneFormatterUnit::SpecificNonLocation( - self.to_field().length, - )), - Some(TimeZoneFormatterUnit::LocalizedOffset( - self.to_field().length, - )), - None, - ], - // 'v', 'vvvv' - ResolvedNeoTimeZoneSkeleton::GenericShort - | ResolvedNeoTimeZoneSkeleton::GenericLong => [ - Some(TimeZoneFormatterUnit::GenericNonLocation( - self.to_field().length, - )), - Some(TimeZoneFormatterUnit::GenericLocation), - Some(TimeZoneFormatterUnit::LocalizedOffset( - self.to_field().length, - )), - ], - // 'VVVV' - ResolvedNeoTimeZoneSkeleton::Location => [ - Some(TimeZoneFormatterUnit::GenericLocation), - Some(TimeZoneFormatterUnit::LocalizedOffset( - self.to_field().length, - )), - None, - ], - // `O`, `OOOO`, `ZZZZ` - ResolvedNeoTimeZoneSkeleton::OffsetShort | ResolvedNeoTimeZoneSkeleton::OffsetLong => [ - Some(TimeZoneFormatterUnit::LocalizedOffset( - self.to_field().length, - )), - None, - None, - ], - // 'V' - ResolvedNeoTimeZoneSkeleton::Bcp47Id => { - [Some(TimeZoneFormatterUnit::Bcp47Id), None, None] - } - // 'X' - ResolvedNeoTimeZoneSkeleton::IsoX => [ - Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format { - format: IsoFormat::UtcBasic, - minutes: IsoMinutes::Optional, - seconds: IsoSeconds::Never, - })), - None, - None, - ], - // 'XX' - ResolvedNeoTimeZoneSkeleton::IsoXX => [ - Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format { - format: IsoFormat::UtcBasic, - minutes: IsoMinutes::Required, - seconds: IsoSeconds::Never, - })), - None, - None, - ], - // 'XXX' - ResolvedNeoTimeZoneSkeleton::IsoXXX => [ - Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format { - format: IsoFormat::UtcExtended, - minutes: IsoMinutes::Required, - seconds: IsoSeconds::Never, - })), - None, - None, - ], - // 'XXXX' - ResolvedNeoTimeZoneSkeleton::IsoXXXX => [ - Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format { - format: IsoFormat::UtcBasic, - minutes: IsoMinutes::Required, - seconds: IsoSeconds::Optional, - })), - None, - None, - ], - // 'XXXXX', 'ZZZZZ' - ResolvedNeoTimeZoneSkeleton::IsoXXXXX => [ - Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format { - format: IsoFormat::UtcExtended, - minutes: IsoMinutes::Required, - seconds: IsoSeconds::Optional, - })), - None, - None, - ], - // 'x' - ResolvedNeoTimeZoneSkeleton::Isox => [ - Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format { - format: IsoFormat::Basic, - minutes: IsoMinutes::Optional, - seconds: IsoSeconds::Never, - })), - None, - None, - ], - // 'xx' - ResolvedNeoTimeZoneSkeleton::Isoxx => [ - Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format { - format: IsoFormat::Basic, - minutes: IsoMinutes::Required, - seconds: IsoSeconds::Never, - })), - None, - None, - ], - // 'xxx' - ResolvedNeoTimeZoneSkeleton::Isoxxx => [ - Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format { - format: IsoFormat::Extended, - minutes: IsoMinutes::Required, - seconds: IsoSeconds::Never, - })), - None, - None, - ], - // 'xxxx', 'Z', 'ZZ', 'ZZZ' - ResolvedNeoTimeZoneSkeleton::Isoxxxx => [ - Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format { - format: IsoFormat::Basic, - minutes: IsoMinutes::Required, - seconds: IsoSeconds::Optional, - })), - None, - None, - ], - // 'xxxxx', 'ZZZZZ' - ResolvedNeoTimeZoneSkeleton::Isoxxxxx => [ - Some(TimeZoneFormatterUnit::Iso8601(Iso8601Format { - format: IsoFormat::Extended, - minutes: IsoMinutes::Required, - seconds: IsoSeconds::Optional, - })), - None, - None, - ], - } - .into_iter() - .flatten() - } -} - -/// A container contains all data payloads for time zone formatting (borrowed version). -#[derive(Debug, Copy, Clone, Default)] -pub(crate) struct TimeZoneDataPayloadsBorrowed<'a> { - /// The data that contains meta information about how to display content. - pub(crate) essentials: Option<&'a provider::time_zones::TimeZoneEssentialsV1<'a>>, - /// The root location names, e.g. Toronto - pub(crate) locations_root: Option<&'a provider::time_zones::LocationsV1<'a>>, - /// The language specific location names, e.g. Italy - pub(crate) locations: Option<&'a provider::time_zones::LocationsV1<'a>>, - /// The generic long metazone names, e.g. Pacific Time - pub(crate) mz_generic_long: Option<&'a provider::time_zones::MetazoneGenericNamesV1<'a>>, - /// The generic short metazone names, e.g. PT - pub(crate) mz_generic_short: Option<&'a provider::time_zones::MetazoneGenericNamesV1<'a>>, - /// The specific long metazone names, e.g. Pacific Daylight Time - pub(crate) mz_specific_long: Option<&'a provider::time_zones::MetazoneSpecificNamesV1<'a>>, - /// The specific short metazone names, e.g. Pacific Daylight Time - pub(crate) mz_specific_short: Option<&'a provider::time_zones::MetazoneSpecificNamesV1<'a>>, - /// The metazone lookup - pub(crate) mz_periods: Option<&'a provider::time_zones::MetazonePeriodV1<'a>>, -} - -fn metazone( - time_zone_id: TimeZoneBcp47Id, - (date, time): (Date, Time), - metazone_period: &crate::provider::time_zones::MetazonePeriodV1, -) -> Option { - let cursor = metazone_period.0.get0(&time_zone_id)?; - let mut metazone_id = None; - let minutes_since_epoch_walltime = (date.to_fixed() - EPOCH) as i32 * 24 * 60 - + (time.hour.number() as i32 * 60 + time.minute.number() as i32); - for (minutes, id) in cursor.iter1() { - if minutes_since_epoch_walltime >= ::from_unaligned(*minutes) { - metazone_id = id.get() - } else { - break; - } - } - metazone_id -} - -/// Determines which ISO-8601 format should be used to format the timezone offset. -#[derive(Debug, Clone, Copy, PartialEq)] -#[allow(clippy::exhaustive_enums)] // this type is stable -pub enum IsoFormat { - /// ISO-8601 Basic Format. - /// Formats zero-offset numerically. - /// e.g. +0500, +0000 - Basic, - - /// ISO-8601 Extended Format. - /// Formats zero-offset numerically. - /// e.g. +05:00, +00:00 - Extended, - - /// ISO-8601 Basic Format. - /// Formats zero-offset with the ISO-8601 UTC indicator: "Z" - /// e.g. +0500, Z - UtcBasic, - - /// ISO-8601 Extended Format. - /// Formats zero-offset with the ISO-8601 UTC indicator: "Z" - /// e.g. +05:00, Z - UtcExtended, -} - -/// Whether the minutes field should be optional or required in ISO-8601 format. -#[derive(Debug, Clone, Copy, PartialEq)] -#[allow(clippy::exhaustive_enums)] // this type is stable -pub enum IsoMinutes { - /// Minutes are always displayed. - Required, - - /// Minutes are displayed only if they are non-zero. - Optional, -} - -/// Whether the seconds field should be optional or excluded in ISO-8601 format. -#[derive(Debug, Clone, Copy, PartialEq)] -#[allow(clippy::exhaustive_enums)] // this type is stable -pub enum IsoSeconds { - /// Seconds are displayed only if they are non-zero. - Optional, - - /// Seconds are not displayed. - Never, -} - -// An enum for time zone format unit. -#[derive(Debug, Clone, Copy, PartialEq)] -pub(super) enum TimeZoneFormatterUnit { - GenericNonLocation(FieldLength), - SpecificNonLocation(FieldLength), - GenericLocation, - #[allow(dead_code)] - SpecificLocation, - #[allow(dead_code)] - GenericPartialLocation(FieldLength), - LocalizedOffset(FieldLength), - Iso8601(Iso8601Format), - Bcp47Id, -} - -#[derive(Debug)] -pub(super) enum FormatTimeZoneError { - MissingZoneSymbols, - MissingFixedDecimalFormatter, - Fallback, - MissingInputField(&'static str), -} - -pub(super) trait FormatTimeZone { - /// Tries to write the timezone to the sink. If a DateTimeError is returned, the sink - /// has not been touched, so another format can be attempted. - fn format( - &self, - sink: &mut W, - input: &ExtractedInput, - data_payloads: TimeZoneDataPayloadsBorrowed, - fdf: Option<&FixedDecimalFormatter>, - ) -> Result, fmt::Error>; -} - -impl FormatTimeZone for TimeZoneFormatterUnit { - fn format( - &self, - sink: &mut W, - input: &ExtractedInput, - data_payloads: TimeZoneDataPayloadsBorrowed, - fdf: Option<&FixedDecimalFormatter>, - ) -> Result, fmt::Error> { - match *self { - Self::GenericNonLocation(length) => { - GenericNonLocationFormat(length).format(sink, input, data_payloads, fdf) - } - Self::SpecificNonLocation(length) => { - SpecificNonLocationFormat(length).format(sink, input, data_payloads, fdf) - } - Self::GenericLocation => GenericLocationFormat.format(sink, input, data_payloads, fdf), - Self::SpecificLocation => { - SpecificLocationFormat.format(sink, input, data_payloads, fdf) - } - Self::GenericPartialLocation(length) => { - GenericPartialLocationFormat(length).format(sink, input, data_payloads, fdf) - } - Self::LocalizedOffset(length) => { - LocalizedOffsetFormat(length).format(sink, input, data_payloads, fdf) - } - Self::Iso8601(iso) => iso.format(sink, input, data_payloads, fdf), - Self::Bcp47Id => Bcp47IdFormat.format(sink, input, data_payloads, fdf), - } - } -} - -// PT / Pacific Time -struct GenericNonLocationFormat(FieldLength); - -impl FormatTimeZone for GenericNonLocationFormat { - /// Writes the time zone in generic non-location format as defined by the UTS-35 spec. - /// - fn format( - &self, - sink: &mut W, - input: &ExtractedInput, - data_payloads: TimeZoneDataPayloadsBorrowed, - _fdf: Option<&FixedDecimalFormatter>, - ) -> Result, fmt::Error> { - let Some(time_zone_id) = input.time_zone_id else { - return Ok(Err(FormatTimeZoneError::MissingInputField("time_zone_id"))); - }; - let Some(local_time) = input.local_time else { - return Ok(Err(FormatTimeZoneError::MissingInputField("local_time"))); - }; - let Some(names) = (match self.0 { - FieldLength::Wide => data_payloads.mz_generic_long.as_ref(), - _ => data_payloads.mz_generic_short.as_ref(), - }) else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - let Some(metazone_period) = data_payloads.mz_periods else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - - let Some(name) = names.overrides.get(&time_zone_id).or_else(|| { - names - .defaults - .get(&metazone(time_zone_id, local_time, metazone_period)?) - }) else { - return Ok(Err(FormatTimeZoneError::Fallback)); - }; - - sink.write_str(name)?; - - Ok(Ok(())) - } -} - -// PDT / Pacific Daylight Time -struct SpecificNonLocationFormat(FieldLength); - -impl FormatTimeZone for SpecificNonLocationFormat { - /// Writes the time zone in short specific non-location format as defined by the UTS-35 spec. - /// - fn format( - &self, - sink: &mut W, - input: &ExtractedInput, - data_payloads: TimeZoneDataPayloadsBorrowed, - _fdf: Option<&FixedDecimalFormatter>, - ) -> Result, fmt::Error> { - let Some(time_zone_id) = input.time_zone_id else { - return Ok(Err(FormatTimeZoneError::MissingInputField("time_zone_id"))); - }; - let Some(zone_variant) = input.zone_variant else { - return Ok(Err(FormatTimeZoneError::MissingInputField("zone_variant"))); - }; - let Some(local_time) = input.local_time else { - return Ok(Err(FormatTimeZoneError::MissingInputField("local_time"))); - }; - - let Some(names) = (match self.0 { - FieldLength::Wide => data_payloads.mz_specific_long.as_ref(), - _ => data_payloads.mz_specific_short.as_ref(), - }) else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - let Some(metazone_period) = data_payloads.mz_periods else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - - let Some(name) = names - .overrides - .get(&(time_zone_id, zone_variant)) - .or_else(|| { - names.defaults.get(&( - metazone(time_zone_id, local_time, metazone_period)?, - zone_variant, - )) - }) - else { - return Ok(Err(FormatTimeZoneError::Fallback)); - }; - - sink.write_str(name)?; - - Ok(Ok(())) - } -} - -// UTC+7:00 -struct LocalizedOffsetFormat(FieldLength); - -impl FormatTimeZone for LocalizedOffsetFormat { - /// Writes the time zone in localized offset format according to the CLDR localized hour format. - /// This goes explicitly against the UTS-35 spec, which specifies long or short localized - /// offset formats regardless of locale. - /// - /// You can see more information about our decision to resolve this conflict here: - /// - fn format( - &self, - sink: &mut W, - input: &ExtractedInput, - data_payloads: TimeZoneDataPayloadsBorrowed, - fdf: Option<&FixedDecimalFormatter>, - ) -> Result, fmt::Error> { - let Some(essentials) = data_payloads.essentials else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - let Some(fdf) = fdf else { - return Ok(Err(FormatTimeZoneError::MissingFixedDecimalFormatter)); - }; - let Some(offset) = input.offset else { - sink.write_str(&essentials.offset_unknown)?; - return Ok(Ok(())); - }; - Ok(if offset.is_zero() { - sink.write_str(&essentials.offset_zero)?; - Ok(()) - } else { - struct FormattedOffset<'a> { - offset: UtcOffset, - separator: &'a str, - fdf: &'a FixedDecimalFormatter, - length: FieldLength, - } - - impl Writeable for FormattedOffset<'_> { - fn write_to_parts( - &self, - sink: &mut S, - ) -> fmt::Result { - self.fdf - .format( - &FixedDecimal::from(self.offset.hours_part()) - .with_sign_display(fixed_decimal::SignDisplay::Always) - .padded_start(if self.length == FieldLength::Wide { - 2 - } else { - 0 - }), - ) - .write_to(sink)?; - - if self.length == FieldLength::Wide - || self.offset.minutes_part() != 0 - || self.offset.seconds_part() != 0 - { - sink.write_str(self.separator)?; - self.fdf - .format(&FixedDecimal::from(self.offset.minutes_part()).padded_start(2)) - .write_to(sink)?; - } - - if self.offset.seconds_part() != 0 { - sink.write_str(self.separator)?; - self.fdf - .format(&FixedDecimal::from(self.offset.seconds_part()).padded_start(2)) - .write_to(sink)?; - } - - Ok(()) - } - } - - essentials - .offset_pattern - .interpolate([FormattedOffset { - offset, - separator: &essentials.offset_separator, - fdf, - length: self.0, - }]) - .write_to(sink)?; - - Ok(()) - }) - } -} - -// Los Angeles Time -struct GenericLocationFormat; - -impl FormatTimeZone for GenericLocationFormat { - /// Writes the time zone in generic location format as defined by the UTS-35 spec. - /// e.g. France Time - /// - fn format( - &self, - sink: &mut W, - input: &ExtractedInput, - data_payloads: TimeZoneDataPayloadsBorrowed, - _fdf: Option<&FixedDecimalFormatter>, - ) -> Result, fmt::Error> { - let Some(time_zone_id) = input.time_zone_id else { - return Ok(Err(FormatTimeZoneError::MissingInputField("time_zone_id"))); - }; - - let Some(locations) = data_payloads.locations else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - - let Some(locations_root) = data_payloads.locations_root else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - - let Some(location) = locations - .locations - .get(&time_zone_id) - .or_else(|| locations_root.locations.get(&time_zone_id)) - else { - return Ok(Err(FormatTimeZoneError::Fallback)); - }; - - locations - .pattern_generic - .interpolate([location]) - .write_to(sink)?; - - Ok(Ok(())) - } -} - -// Los Angeles Daylight Time -struct SpecificLocationFormat; - -impl FormatTimeZone for SpecificLocationFormat { - /// Writes the time zone in a specific location format as defined by the UTS-35 spec. - /// e.g. France Time - /// - fn format( - &self, - sink: &mut W, - input: &ExtractedInput, - data_payloads: TimeZoneDataPayloadsBorrowed, - _fdf: Option<&FixedDecimalFormatter>, - ) -> Result, fmt::Error> { - let Some(time_zone_id) = input.time_zone_id else { - return Ok(Err(FormatTimeZoneError::MissingInputField("time_zone_id"))); - }; - let Some(zone_variant) = input.zone_variant else { - return Ok(Err(FormatTimeZoneError::MissingInputField("zone_variant"))); - }; - let Some(locations) = data_payloads.locations else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - let Some(locations_root) = data_payloads.locations_root else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - - let Some(location) = locations - .locations - .get(&time_zone_id) - .or_else(|| locations_root.locations.get(&time_zone_id)) - else { - return Ok(Err(FormatTimeZoneError::Fallback)); - }; - - match zone_variant { - ZoneVariant::Standard => &locations.pattern_standard, - ZoneVariant::Daylight => &locations.pattern_daylight, - // Compiles out due to tilde dependency on `icu_timezone` - _ => unreachable!(), - } - .interpolate([location]) - .write_to(sink)?; - - Ok(Ok(())) - } -} - -// Pacific Time (Los Angeles) / PT (Los Angeles) -struct GenericPartialLocationFormat(FieldLength); - -impl FormatTimeZone for GenericPartialLocationFormat { - /// Writes the time zone in a long generic partial location format as defined by the UTS-35 spec. - /// - fn format( - &self, - sink: &mut W, - input: &ExtractedInput, - data_payloads: TimeZoneDataPayloadsBorrowed, - _fdf: Option<&FixedDecimalFormatter>, - ) -> Result, fmt::Error> { - let Some(time_zone_id) = input.time_zone_id else { - return Ok(Err(FormatTimeZoneError::MissingInputField("time_zone_id"))); - }; - let Some(local_time) = input.local_time else { - return Ok(Err(FormatTimeZoneError::MissingInputField("local_time"))); - }; - - let Some(locations) = data_payloads.locations else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - let Some(locations_root) = data_payloads.locations_root else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - let Some(non_locations) = (match self.0 { - FieldLength::Wide => data_payloads.mz_generic_long.as_ref(), - _ => data_payloads.mz_generic_short.as_ref(), - }) else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - let Some(metazone_period) = data_payloads.mz_periods else { - return Ok(Err(FormatTimeZoneError::MissingZoneSymbols)); - }; - let Some(location) = locations - .locations - .get(&time_zone_id) - .or_else(|| locations_root.locations.get(&time_zone_id)) - else { - return Ok(Err(FormatTimeZoneError::Fallback)); - }; - let Some(non_location) = non_locations.overrides.get(&time_zone_id).or_else(|| { - non_locations - .defaults - .get(&metazone(time_zone_id, local_time, metazone_period)?) - }) else { - return Ok(Err(FormatTimeZoneError::Fallback)); - }; - - locations - .pattern_partial_location - .interpolate((location, non_location)) - .write_to(sink)?; - - Ok(Ok(())) - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) struct Iso8601Format { - pub(crate) format: IsoFormat, - pub(crate) minutes: IsoMinutes, - pub(crate) seconds: IsoSeconds, -} - -impl FormatTimeZone for Iso8601Format { - /// Writes a [`UtcOffset`](crate::input::UtcOffset) in ISO-8601 format according to the - /// given formatting options. - /// - /// [`IsoFormat`] determines whether the format should be Basic or Extended, - /// and whether a zero-offset should be formatted numerically or with - /// The UTC indicator: "Z" - /// - Basic e.g. +0800 - /// - Extended e.g. +08:00 - /// - /// [`IsoMinutes`] can be required or optional. - /// [`IsoSeconds`] can be optional or never. - fn format( - &self, - sink: &mut W, - input: &ExtractedInput, - _data_payloads: TimeZoneDataPayloadsBorrowed, - _fdf: Option<&FixedDecimalFormatter>, - ) -> Result, fmt::Error> { - let Some(offset) = input.offset else { - sink.write_str("+?")?; - return Ok(Ok(())); - }; - self.format_infallible(sink, offset).map(|()| Ok(())) - } -} - -impl Iso8601Format { - pub(crate) fn format_infallible( - &self, - sink: &mut W, - offset: UtcOffset, - ) -> Result<(), fmt::Error> { - fn format_time_segment( - sink: &mut W, - n: u32, - ) -> fmt::Result { - if n < 10 { - sink.write_char('0')?; - } - n.write_to(sink) - } - - if offset.is_zero() && matches!(self.format, IsoFormat::UtcBasic | IsoFormat::UtcExtended) { - return sink.write_char('Z'); - } - - let extended_format = matches!(self.format, IsoFormat::Extended | IsoFormat::UtcExtended); - - sink.write_char(if offset.is_non_negative() { '+' } else { '-' })?; - - format_time_segment(sink, offset.hours_part().unsigned_abs())?; - - if self.minutes == IsoMinutes::Required - || (self.minutes == IsoMinutes::Optional && offset.minutes_part() != 0) - { - if extended_format { - sink.write_char(':')?; - } - format_time_segment(sink, offset.minutes_part())?; - } - - if self.seconds == IsoSeconds::Optional && offset.seconds_part() != 0 { - if extended_format { - sink.write_char(':')?; - } - format_time_segment(sink, offset.seconds_part())?; - } - - Ok(()) - } -} - -// It is only used for pattern in special case and not public to users. -struct Bcp47IdFormat; - -impl FormatTimeZone for Bcp47IdFormat { - fn format( - &self, - sink: &mut W, - input: &ExtractedInput, - _data_payloads: TimeZoneDataPayloadsBorrowed, - _fdf: Option<&FixedDecimalFormatter>, - ) -> Result, fmt::Error> { - let time_zone_id = input - .time_zone_id - .unwrap_or(TimeZoneBcp47Id(tinystr::tinystr!(8, "unk"))); - - sink.write_str(&time_zone_id)?; - - Ok(Ok(())) - } -} diff --git a/components/datetime/src/tz_registry.rs b/components/datetime/src/tz_registry.rs deleted file mode 100644 index c30bbcc38fe..00000000000 --- a/components/datetime/src/tz_registry.rs +++ /dev/null @@ -1,138 +0,0 @@ -// This file is part of ICU4X. For terms of use, please see the file -// called LICENSE at the top level of the ICU4X source tree -// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). - -use crate::fields::{self, FieldLength}; -use crate::fields::{Field, FieldSymbol}; -use crate::neo_skeleton::{NeoSkeletonLength, NeoTimeZoneStyle}; -use crate::time_zone::ResolvedNeoTimeZoneSkeleton; - -macro_rules! time_zone_style_registry { - ($cb:ident) => { - $cb! { - // Styles with function only for None-length - [ - (specific, Specific), - (offset, Offset), - (generic, Generic), - (location, Location), - ], - // Skeleton to resolved (for exhaustive match) - [ - (Specific, Short, SpecificShort), - (Specific, Medium, SpecificShort), - (Specific, Long, SpecificLong), - (Offset, Short, OffsetShort), - (Offset, Medium, OffsetShort), - (Offset, Long, OffsetLong), - (Generic, Short, GenericShort), - (Generic, Medium, GenericShort), - (Generic, Long, GenericLong), - (Location, Short, Location), - (Location, Medium, Location), - (Location, Long, Location), - // See comments above about Default behavior - (Default, Short, SpecificShort), - (Default, Medium, SpecificShort), - (Default, Long, SpecificLong), - ], - // Field to resolved (not already covered) - // Note: 'Z', 'ZZ', 'ZZZ', and 'xxxx' are the same. We use 'Z' as canonical. - // Note: 'ZZZZZ' and 'XXXXX' are the same. We use 'ZZZZZ' as canonical. - [ - (SpecificShort, LowerZ, TwoDigit), // 'zz' - (SpecificShort, LowerZ, Abbreviated), // 'zzz' - (Isoxxxx, UpperZ, TwoDigit), // 'ZZ' - (Isoxxxx, UpperZ, Abbreviated), // 'ZZZ' - (OffsetLong, UpperZ, Wide), // 'ZZZZ' - (Isoxxxx, LowerX, Wide), // 'xxxx' - (IsoXXXXX, UpperX, Narrow), // 'XXXXX' - ], - // Resolved to field (not already covered) - [ - (SpecificShort, LowerZ, One), // 'z' - (SpecificLong, LowerZ, Wide), // 'zzzz' - (OffsetShort, UpperO, One), // 'O' - (OffsetLong, UpperO, Wide), // 'OOOO' - (GenericShort, LowerV, One), // 'v' - (GenericLong, LowerV, Wide), // 'vvvv' - (Bcp47Id, UpperV, One), // 'V' - (Location, UpperV, Wide), // 'VVVV' - (Isoxxxx, UpperZ, One), // 'Z' - (IsoXXXXX, UpperZ, Narrow), // 'ZZZZZ' - (IsoX, UpperX, One), // 'X' - (IsoXX, UpperX, TwoDigit), // 'XX' - (IsoXXX, UpperX, Abbreviated), // 'XXX' - (IsoXXXX, UpperX, Wide), // 'XXXX' - (Isox, LowerX, One), // 'x' - (Isoxx, LowerX, TwoDigit), // 'xx' - (Isoxxx, LowerX, Abbreviated), // 'xxx' - (Isoxxxxx, LowerX, Narrow), // 'xxxxx' - ], - } - }; -} - -macro_rules! make_resolved_to_field_match { - ( - [$(($fn1:ident, $style1:ident)),+,], - [$(($style2:ident, $length2:ident, $resolved2:ident)),+,], - [$(($resolved3:ident, $field_symbol3:ident, $field_length3:ident)),+,], - [$(($resolved4:ident, $field_symbol4:ident, $field_length4:ident)),+,], - ) => { - pub(crate) fn resolved_to_field(resolved: ResolvedNeoTimeZoneSkeleton) -> Field { - match resolved { - $( - ResolvedNeoTimeZoneSkeleton::$resolved4 => Field { - symbol: FieldSymbol::TimeZone(fields::TimeZone::$field_symbol4), - length: FieldLength::$field_length4, - }, - )+ - } - } - }; -} - -time_zone_style_registry!(make_resolved_to_field_match); - -macro_rules! make_skeleton_to_resolved_match { - ( - [$(($fn1:ident, $style1:ident)),+,], - [$(($style2:ident, $length2:ident, $resolved2:ident)),+,], - [$(($resolved3:ident, $field_symbol3:ident, $field_length3:ident)),+,], - [$(($resolved4:ident, $field_symbol4:ident, $field_length4:ident)),+,], - ) => { - pub(crate) fn skeleton_to_resolved(style: NeoTimeZoneStyle, length: NeoSkeletonLength) -> ResolvedNeoTimeZoneSkeleton { - match (style, length) { - $( - (NeoTimeZoneStyle::$style2, NeoSkeletonLength::$length2) => ResolvedNeoTimeZoneSkeleton::$resolved2, - )+ - } - } - }; -} - -time_zone_style_registry!(make_skeleton_to_resolved_match); - -macro_rules! make_field_to_skeleton_match { - ( - [$(($fn1:ident, $style1:ident)),+,], - [$(($style2:ident, $length2:ident, $resolved2:ident)),+,], - [$(($resolved3:ident, $field_symbol3:ident, $field_length3:ident)),+,], - [$(($resolved4:ident, $field_symbol4:ident, $field_length4:ident)),+,], - ) => { - pub(crate) fn field_to_resolved(field_symbol: fields::TimeZone, field_length: fields::FieldLength) -> Option { - match (field_symbol, field_length) { - $( - (fields::TimeZone::$field_symbol3, FieldLength::$field_length3) => Some(ResolvedNeoTimeZoneSkeleton::$resolved3), - )+ - $( - (fields::TimeZone::$field_symbol4, FieldLength::$field_length4) => Some(ResolvedNeoTimeZoneSkeleton::$resolved4), - )+ - (_, _) => None, - } - } - }; -} - -time_zone_style_registry!(make_field_to_skeleton_match); diff --git a/components/datetime/tests/datetime.rs b/components/datetime/tests/datetime.rs index a67159e1176..80857da92ee 100644 --- a/components/datetime/tests/datetime.rs +++ b/components/datetime/tests/datetime.rs @@ -15,11 +15,10 @@ use icu_calendar::{ any_calendar::{AnyCalendarKind, IntoAnyCalendar}, AsCalendar, Calendar, DateTime, }; -use icu_datetime::neo_skeleton::{NeoDateTimeComponents, NeoDateTimeSkeleton}; +use icu_datetime::fieldset::dynamic::*; use icu_datetime::scaffold::CldrCalendar; use icu_datetime::{ neo_pattern::DateTimePattern, - neo_skeleton::NeoTimeZoneSkeleton, options::preferences::{self, HourCycle}, DateTimeFormatter, FixedCalendarDateTimeFormatter, TypedDateTimeNames, }; @@ -62,14 +61,12 @@ fn test_fixture(fixture_name: &str, file: &str) { let japanext = JapaneseExtended::new(); let skeleton = match fx.input.options.semantic { Some(semantic) => { - let mut skeleton = NeoDateTimeSkeleton::for_length_and_components( - semantic.length, - NeoDateTimeComponents::try_from_components(semantic.components).unwrap(), - ); - skeleton.alignment = semantic.alignment; - skeleton.fractional_second_digits = semantic.fractional_second_digits; - skeleton.year_style = semantic.year_style; - skeleton + match CompositeDateTimeFieldSet::try_from_composite_field_set(semantic) { + Some(v) => v, + None => { + panic!("Cannot handle field sets with time zones in this fn: {semantic:?}"); + } + } } None => { eprintln!("Warning: Skipping test with no semantic skeleton: {fx:?}"); @@ -261,7 +258,7 @@ fn assert_fixture_element( input_value: &DateTime, input_iso: &DateTime, output_value: &TestOutputItem, - skeleton: NeoDateTimeSkeleton, + skeleton: CompositeDateTimeFieldSet, description: &str, ) where A: AsCalendar + Clone, @@ -299,11 +296,9 @@ fn assert_fixture_element( zone: TimeZoneInfo::utc(), }; - let dtf = FixedCalendarDateTimeFormatter::try_new_with_skeleton(&locale.into(), skeleton) - .expect(description); + let dtf = FixedCalendarDateTimeFormatter::try_new(&locale.into(), skeleton).expect(description); - let any_dtf = - DateTimeFormatter::try_new_with_skeleton(&locale.into(), skeleton).expect(description); + let any_dtf = DateTimeFormatter::try_new(&locale.into(), skeleton).expect(description); let actual1 = dtf.format(&input_value); assert_try_writeable_eq!( @@ -368,11 +363,8 @@ fn test_fixture_with_time_zones(fixture_name: &str, file: &str) { apply_preference_bag_to_locale(preferences, &mut locale); } let dtf = { - FixedCalendarDateTimeFormatter::::try_new_with_skeleton( - &locale.into(), - skeleton, - ) - .unwrap() + FixedCalendarDateTimeFormatter::::try_new(&locale.into(), skeleton) + .unwrap() }; assert_writeable_eq!( writeable::adapters::LossyWrap(dtf.format(&zoned_datetime)), @@ -400,7 +392,7 @@ fn test_dayperiod_patterns() { let parsed_pattern = DateTimePattern::try_from_pattern_str(pattern_input).unwrap(); let mut pattern_formatter = - TypedDateTimeNames::::try_new( + TypedDateTimeNames::::try_new( &(&locale).into(), ) .unwrap(); @@ -441,11 +433,9 @@ fn test_time_zone_format_configs() { else { continue; }; - let tzf = FixedCalendarDateTimeFormatter::::try_new_with_skeleton( - &data_locale, - skeleton, - ) - .unwrap(); + let tzf = + FixedCalendarDateTimeFormatter::::try_new(&data_locale, skeleton) + .unwrap(); assert_writeable_eq!( writeable::adapters::LossyWrap(tzf.format(&zoned_datetime.zone)), *expect, @@ -507,8 +497,7 @@ fn test_time_zone_patterns() { } let parsed_pattern = DateTimePattern::try_from_pattern_str(pattern_input).unwrap(); let mut pattern_formatter = - TypedDateTimeNames::::try_new(&data_locale) - .unwrap(); + TypedDateTimeNames::::try_new(&data_locale).unwrap(); let formatted_datetime = pattern_formatter .include_for_pattern(&parsed_pattern) .unwrap() diff --git a/components/datetime/tests/fixtures/mod.rs b/components/datetime/tests/fixtures/mod.rs index 3f02a017e70..b1cf71bf2e5 100644 --- a/components/datetime/tests/fixtures/mod.rs +++ b/components/datetime/tests/fixtures/mod.rs @@ -4,6 +4,7 @@ #![cfg(feature = "serde")] +use icu_datetime::{neo_skeleton, options::components}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -25,12 +26,58 @@ pub struct TestInput { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct TestOptions { - pub length: Option, - pub components: Option, - pub semantic: Option, + pub length: Option, + pub components: Option, + pub semantic: Option, pub preferences: Option, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TestOptionsLength { + pub date: Option, + pub time: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum TestLength { + #[serde(rename = "short")] + Short, + #[serde(rename = "medium")] + Medium, + #[serde(rename = "long")] + Long, + #[serde(rename = "full")] + Full, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TestComponentsBag { + pub era: Option, + pub year: Option, + pub month: Option, + pub week: Option, + pub day: Option, + pub weekday: Option, + + pub hour: Option, + pub minute: Option, + pub second: Option, + pub fractional_second: Option, + + pub time_zone_name: Option, + + pub hour_cycle: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum TestHourCycle { + H11, + H12, + H23, + H24, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct TestOutput { // Key is locale, and value is expected test output. diff --git a/components/datetime/tests/fixtures/tests/components-combine-datetime.json b/components/datetime/tests/fixtures/tests/components-combine-datetime.json index 12998405161..6c0ad542b76 100644 --- a/components/datetime/tests/fixtures/tests/components-combine-datetime.json +++ b/components/datetime/tests/fixtures/tests/components-combine-datetime.json @@ -14,8 +14,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute"], - "length": "medium" + "fieldSet": ["year", "month", "day", "weekday", "time"], + "length": "medium", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h12" } } @@ -44,8 +45,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute"], - "length": "long" + "fieldSet": ["year", "month", "day", "weekday", "time"], + "length": "long", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h12" } } diff --git a/components/datetime/tests/fixtures/tests/components-exact-matches.json b/components/datetime/tests/fixtures/tests/components-exact-matches.json index 437f1bd5aaa..f984f5b6d1a 100644 --- a/components/datetime/tests/fixtures/tests/components-exact-matches.json +++ b/components/datetime/tests/fixtures/tests/components-exact-matches.json @@ -395,8 +395,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["weekday", "hour", "minute"], - "length": "medium" + "fieldSet": ["weekday", "time"], + "length": "medium", + "timePrecision": "minuteExact" } } }, @@ -418,8 +419,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["weekday", "hour", "minute", "second"], - "length": "medium" + "fieldSet": ["weekday", "time"], + "length": "medium", + "timePrecision": "secondPlus" } } }, @@ -440,8 +442,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["weekday", "hour", "minute"], - "length": "medium" + "fieldSet": ["weekday", "time"], + "length": "medium", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h23" } } @@ -464,8 +467,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["weekday", "hour", "minute", "second"], - "length": "medium" + "fieldSet": ["weekday", "time"], + "length": "medium", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h23" } } @@ -485,8 +489,9 @@ "hour": "numeric" }, "semantic": { - "fieldSet": ["hour"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "hourExact" }, "preferences": { "hourCycle": "h12" } } @@ -507,8 +512,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h12" } } @@ -530,8 +536,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute", "second"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h12" } } @@ -551,8 +558,9 @@ "hour": "numeric" }, "semantic": { - "fieldSet": ["hour"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "hourExact" }, "preferences": { "hourCycle": "h23" } } @@ -573,8 +581,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h23" } } @@ -596,8 +605,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute", "second"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h23" } } diff --git a/components/datetime/tests/fixtures/tests/components.json b/components/datetime/tests/fixtures/tests/components.json index da0ed180442..b6deb426454 100644 --- a/components/datetime/tests/fixtures/tests/components.json +++ b/components/datetime/tests/fixtures/tests/components.json @@ -14,8 +14,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute", "second"], + "fieldSet": ["year", "month", "day", "weekday", "time"], "length": "long", + "timePrecision": "secondPlus", "yearStyle": "always" }, "preferences": { "hourCycle": "h23" } @@ -25,14 +26,14 @@ "values": { "en": "Tuesday, January 21, 2020 AD, 08:25:07", "en-u-ca-buddhist": "Tuesday, January 21, 2563 BE, 08:25:07", - "en-u-ca-chinese": "Tuesday, Twelfth Month 27, 2019(己亥), 08:25:07", + "en-u-ca-chinese": "Tuesday, Twelfth Month 27, 2019(ji-hai), 08:25:07", "zh-u-ca-chinese": "2019己亥年腊月27星期二 08:25:07", "en-u-ca-japanese": "Tuesday, January 21, 2 Reiwa, 08:25:07", "ja-u-ca-japanese": "令和2年1月21日火曜日 8:25:07", "en-u-ca-coptic": "Tuesday, Toba 12, 1736 ERA1, 08:25:07", "fr-u-ca-coptic": "mardi 12 toubah 1736 ap. D., 08:25:07", - "en-u-ca-dangi": "Tuesday, Twelfth Month 27, 2019(기해), 08:25:07", - "fr-u-ca-dangi": "2019(기해) shí’èryuè 27, mardi, 08:25:07", + "en-u-ca-dangi": "Tuesday, Twelfth Month 27, 2019(ji-hai), 08:25:07", + "fr-u-ca-dangi": "2019(ji-hai) shí’èryuè 27, mardi, 08:25:07", "ko-u-ca-dangi": "2019년(기해년) 12월 27일 화요일 08:25:07", "en-u-ca-indian": "Tuesday, Magha 1, 1941 Saka, 08:25:07", "en-u-ca-islamic": "Tuesday, Jumada I 25, 1441 AH, 08:25:07", @@ -71,8 +72,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute", "second"], + "fieldSet": ["year", "month", "day", "weekday", "time"], "length": "long", + "timePrecision": "secondPlus", "yearStyle": "always" }, "preferences": { "hourCycle": "h23" } @@ -80,7 +82,7 @@ }, "output": { "values": { - "en-u-ca-chinese": "Thursday, Second Monthbis 2, 2023(癸卯), 14:15:07", + "en-u-ca-chinese": "Thursday, Second Monthbis 2, 2023(gui-mao), 14:15:07", "zh-u-ca-chinese": "2023癸卯年闰二月2星期四 14:15:07" } } @@ -100,8 +102,9 @@ "second": "numeric" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute", "second"], + "fieldSet": ["year", "month", "day", "weekday", "time"], "length": "long", + "timePrecision": "secondPlus", "yearStyle": "always" }, "preferences": { "hourCycle": "h23" } @@ -125,8 +128,9 @@ "fractional_second": 0 }, "semantic": { - "fieldSet": ["hour", "minute", "second"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h23" } } @@ -148,9 +152,9 @@ "fractional_second": 9 }, "semantic": { - "fieldSet": ["hour", "minute", "second"], + "fieldSet": ["time"], "length": "short", - "fractionalSecondDigits": 9 + "timePrecision": "secondF9" }, "preferences": { "hourCycle": "h23" } } diff --git a/components/datetime/tests/fixtures/tests/components_hour_cycle.json b/components/datetime/tests/fixtures/tests/components_hour_cycle.json index 0c409e13cfa..9a9e532dbec 100644 --- a/components/datetime/tests/fixtures/tests/components_hour_cycle.json +++ b/components/datetime/tests/fixtures/tests/components_hour_cycle.json @@ -9,8 +9,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h11" } } @@ -34,8 +35,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h12" } } @@ -59,8 +61,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h23" } } @@ -84,8 +87,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h24" } } @@ -110,8 +114,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h11" } } @@ -132,8 +137,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h12" } } @@ -154,8 +160,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h23" } } @@ -176,8 +183,9 @@ "minute": "numeric" }, "semantic": { - "fieldSet": ["hour", "minute"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "minuteExact" }, "preferences": { "hourCycle": "h24" } } @@ -197,8 +205,9 @@ "hour": "numeric" }, "semantic": { - "fieldSet": ["hour"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "hourExact" }, "preferences": { "hourCycle": "h11" } } @@ -218,8 +227,9 @@ "hour": "numeric" }, "semantic": { - "fieldSet": ["hour"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "hourExact" }, "preferences": { "hourCycle": "h12" } } @@ -239,8 +249,9 @@ "hour": "numeric" }, "semantic": { - "fieldSet": ["hour"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "hourExact" }, "preferences": { "hourCycle": "h23" } } @@ -260,8 +271,9 @@ "hour": "numeric" }, "semantic": { - "fieldSet": ["hour"], - "length": "short" + "fieldSet": ["time"], + "length": "short", + "timePrecision": "hourExact" }, "preferences": { "hourCycle": "h24" } } diff --git a/components/datetime/tests/fixtures/tests/components_with_zones.json b/components/datetime/tests/fixtures/tests/components_with_zones.json index 63334d0889e..4627b0a6420 100644 --- a/components/datetime/tests/fixtures/tests/components_with_zones.json +++ b/components/datetime/tests/fixtures/tests/components_with_zones.json @@ -15,8 +15,9 @@ "time_zone_name": "long-generic" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute", "second", "zoneGeneric"], - "length": "long" + "fieldSet": ["year", "month", "day", "weekday", "time", "zoneGeneric"], + "length": "long", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h23" } } @@ -43,8 +44,9 @@ "time_zone_name": "short-generic" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute", "second", "zoneGeneric"], - "length": "long" + "fieldSet": ["year", "month", "day", "weekday", "time", "zoneGeneric"], + "length": "long", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h23" } } @@ -71,8 +73,9 @@ "time_zone_name": "short-specific" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute", "second", "zoneSpecific"], - "length": "long" + "fieldSet": ["year", "month", "day", "weekday", "time", "zoneSpecific"], + "length": "long", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h23" } } @@ -99,8 +102,9 @@ "time_zone_name": "long-specific" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute", "second", "zoneSpecific"], - "length": "long" + "fieldSet": ["year", "month", "day", "weekday", "time", "zoneSpecific"], + "length": "long", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h23" } } @@ -127,8 +131,9 @@ "time_zone_name": "long-offset" }, "semantic": { - "fieldSet": ["year", "month", "day", "weekday", "hour", "minute", "second", "zoneOffset"], - "length": "long" + "fieldSet": ["year", "month", "day", "weekday", "time", "zoneOffset"], + "length": "long", + "timePrecision": "secondPlus" }, "preferences": { "hourCycle": "h23" } } diff --git a/components/datetime/tests/fixtures/tests/patterns.bin b/components/datetime/tests/fixtures/tests/patterns.bin index 2b2a2a80b8e..377bd975cdd 100644 Binary files a/components/datetime/tests/fixtures/tests/patterns.bin and b/components/datetime/tests/fixtures/tests/patterns.bin differ diff --git a/components/datetime/tests/fixtures/tests/patterns.json b/components/datetime/tests/fixtures/tests/patterns.json index cab8826adb2..a7da21f222b 100644 --- a/components/datetime/tests/fixtures/tests/patterns.json +++ b/components/datetime/tests/fixtures/tests/patterns.json @@ -7,13 +7,11 @@ "الأسبوع d من MMMM", "ᏒᎾᏙᏓᏆᏍᏗ’ d ’ᎾᎿ’ MMMM", "y yy yyy yyyy yyyyy", - "Y YY YYY YYYY YYYYY", "M MM MMM MMMM MMMMM", "L LL LLL LLLL LLLLL", "d dd", "D DD DDD", "F", - "g gg ggg gggg ggggg", "e ee eee eeee eeeee eeeeee", "c cc ccc cccc ccccc cccccc", "a aa aaa aaaa aaaaa", diff --git a/components/datetime/tests/fixtures/tests/skeletons.bin b/components/datetime/tests/fixtures/tests/skeletons.bin index 2e125a537d8..5ef86d08703 100644 Binary files a/components/datetime/tests/fixtures/tests/skeletons.bin and b/components/datetime/tests/fixtures/tests/skeletons.bin differ diff --git a/components/datetime/tests/patterns/time_zones.rs b/components/datetime/tests/patterns/time_zones.rs index b7605484f25..4262676e2e7 100644 --- a/components/datetime/tests/patterns/time_zones.rs +++ b/components/datetime/tests/patterns/time_zones.rs @@ -2,7 +2,10 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -use icu_datetime::neo_skeleton::{NeoSkeletonLength, NeoTimeZoneSkeleton, NeoTimeZoneStyle}; +use icu_datetime::{ + fieldset::{self, dynamic::ZoneFieldSet}, + neo_skeleton::NeoSkeletonLength, +}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -16,36 +19,15 @@ pub struct TimeZoneTest { pub expectations: HashMap, } -pub fn pattern_to_semantic_skeleton(p: &str) -> Option { +pub fn pattern_to_semantic_skeleton(p: &str) -> Option { Some(match p { - "vvvv" => NeoTimeZoneSkeleton::for_length_and_components( - NeoSkeletonLength::Long, - NeoTimeZoneStyle::Generic, - ), - "v" => NeoTimeZoneSkeleton::for_length_and_components( - NeoSkeletonLength::Short, - NeoTimeZoneStyle::Generic, - ), - "VVVV" => NeoTimeZoneSkeleton::for_length_and_components( - NeoSkeletonLength::Long, - NeoTimeZoneStyle::Location, - ), - "zzzz" => NeoTimeZoneSkeleton::for_length_and_components( - NeoSkeletonLength::Long, - NeoTimeZoneStyle::Specific, - ), - "z" => NeoTimeZoneSkeleton::for_length_and_components( - NeoSkeletonLength::Short, - NeoTimeZoneStyle::Specific, - ), - "OOOO" => NeoTimeZoneSkeleton::for_length_and_components( - NeoSkeletonLength::Long, - NeoTimeZoneStyle::Offset, - ), - "O" => NeoTimeZoneSkeleton::for_length_and_components( - NeoSkeletonLength::Short, - NeoTimeZoneStyle::Offset, - ), + "vvvv" => ZoneFieldSet::V(fieldset::V::with_length(NeoSkeletonLength::Long)), + "v" => ZoneFieldSet::V(fieldset::V::with_length(NeoSkeletonLength::Short)), + "VVVV" => ZoneFieldSet::L(fieldset::L::with_length(NeoSkeletonLength::Long)), + "zzzz" => ZoneFieldSet::Z(fieldset::Z::with_length(NeoSkeletonLength::Long)), + "z" => ZoneFieldSet::Z(fieldset::Z::with_length(NeoSkeletonLength::Short)), + "OOOO" => ZoneFieldSet::O(fieldset::O::with_length(NeoSkeletonLength::Long)), + "O" => ZoneFieldSet::O(fieldset::O::with_length(NeoSkeletonLength::Short)), _ => return None, }) } diff --git a/components/datetime/tests/resolved_components.rs b/components/datetime/tests/resolved_components.rs index 539423e53e0..73816cf4092 100644 --- a/components/datetime/tests/resolved_components.rs +++ b/components/datetime/tests/resolved_components.rs @@ -4,10 +4,11 @@ use icu_calendar::{Date, DateTime, Gregorian, Time}; use icu_datetime::{ - neo_skeleton::{ - Alignment, FractionalSecondDigits, NeoDateComponents, NeoDateTimeComponents, - NeoDateTimeSkeleton, NeoSkeletonLength, NeoTimeComponents, YearStyle, + fieldset::{ + self, + dynamic::{CompositeDateTimeFieldSet, DateAndTimeFieldSet, DateFieldSet, TimeFieldSet}, }, + neo_skeleton::{Alignment, FractionalSecondDigits, TimePrecision, YearStyle}, options::{components, preferences}, FixedCalendarDateTimeFormatter, }; @@ -15,15 +16,12 @@ use icu_locale_core::locale; use icu_locale_core::Locale; fn assert_resolved_components( - skeleton: NeoDateTimeSkeleton, + skeleton: CompositeDateTimeFieldSet, bag: &components::Bag, locale: Locale, ) { - let dtf = FixedCalendarDateTimeFormatter::::try_new_with_skeleton( - &locale.into(), - skeleton, - ) - .unwrap(); + let dtf = + FixedCalendarDateTimeFormatter::::try_new(&locale.into(), skeleton).unwrap(); let datetime = DateTime { date: Date::try_new_gregorian(2024, 1, 1).unwrap(), time: Time::midnight(), @@ -34,10 +32,7 @@ fn assert_resolved_components( #[test] fn test_length_date() { - let skeleton = NeoDateTimeSkeleton::for_length_and_components( - NeoSkeletonLength::Medium, - NeoDateTimeComponents::Date(NeoDateComponents::Auto), - ); + let skeleton = CompositeDateTimeFieldSet::Date(DateFieldSet::YMD(fieldset::YMD::medium())); let mut components_bag = components::Bag::default(); components_bag.year = Some(components::Year::Numeric); @@ -49,10 +44,7 @@ fn test_length_date() { #[test] fn test_length_time() { - let skeleton = NeoDateTimeSkeleton::for_length_and_components( - NeoSkeletonLength::Medium, - NeoDateTimeComponents::Time(NeoTimeComponents::Auto), - ); + let skeleton = CompositeDateTimeFieldSet::Time(TimeFieldSet::T(fieldset::T::medium().hms())); let mut components_bag = components::Bag::default(); components_bag.hour = Some(components::Numeric::Numeric); @@ -71,11 +63,11 @@ fn test_length_time() { #[test] fn test_length_time_preferences() { - let mut skeleton = NeoDateTimeSkeleton::for_length_and_components( - NeoSkeletonLength::Medium, - NeoDateTimeComponents::Time(NeoTimeComponents::Auto), - ); - skeleton.alignment = Some(Alignment::Column); + let skeleton = CompositeDateTimeFieldSet::Time(TimeFieldSet::T( + fieldset::T::medium() + .hms() + .with_alignment(Alignment::Column), + )); let mut components_bag = components::Bag::default(); components_bag.hour = Some(components::Numeric::TwoDigit); @@ -94,16 +86,12 @@ fn test_length_time_preferences() { #[test] fn test_date_and_time() { - let mut skeleton = NeoDateTimeSkeleton::for_length_and_components( - NeoSkeletonLength::Medium, - NeoDateTimeComponents::DateTime( - NeoDateComponents::YearMonthDayWeekday, - NeoTimeComponents::Auto, - ), - ); - skeleton.year_style = Some(YearStyle::Always); - skeleton.fractional_second_digits = Some(FractionalSecondDigits::F4); - skeleton.alignment = Some(Alignment::Column); + let skeleton = CompositeDateTimeFieldSet::DateTime(DateAndTimeFieldSet::YMDET( + fieldset::YMDET::medium() + .with_year_style(YearStyle::Always) + .with_alignment(Alignment::Column) + .with_time_precision(TimePrecision::SecondExact(FractionalSecondDigits::F4)), + )); let mut input_bag = components::Bag::default(); input_bag.era = Some(components::Text::Short); diff --git a/components/datetime/tests/simple_test.rs b/components/datetime/tests/simple_test.rs index 8558b2afb11..a44fa60ebb9 100644 --- a/components/datetime/tests/simple_test.rs +++ b/components/datetime/tests/simple_test.rs @@ -4,12 +4,10 @@ use icu_calendar::cal::Hebrew; use icu_calendar::{Date, DateTime, Time}; -use icu_datetime::fieldset::YMD; -use icu_datetime::neo_skeleton::{ - NeoDateComponents, NeoDateSkeleton, NeoDateTimeComponents, NeoDateTimeSkeleton, - NeoSkeletonLength, NeoTimeComponents, +use icu_datetime::fieldset::dynamic::{ + CompositeDateTimeFieldSet, DateAndTimeFieldSet, DateFieldSet, }; -use icu_datetime::options::length; +use icu_datetime::fieldset::{self, YMD}; use icu_datetime::FixedCalendarDateTimeFormatter; use icu_locale_core::{locale, Locale}; use writeable::assert_try_writeable_eq; @@ -25,19 +23,19 @@ const EXPECTED_DATETIME: &[&str] = &[ "शुक्रवार, 22 दिसंबर 2023, 9:22 pm", "December 22, 2023, 9:22:53 PM", "22 décembre 2023, 21:22:53", - "2023年12月22日 21:22:53", + "2023/12/22 21:22:53", // TODO(#5806) "2023年12月22日 21:22:53", "22 दिसंबर 2023, 9:22:53 pm", "December 22, 2023, 9:22 PM", "22 décembre 2023, 21:22", - "2023年12月22日 21:22", + "2023/12/22 21:22", // TODO(#5806) "2023年12月22日 21:22", "22 दिसंबर 2023, 9:22 pm", "Dec 22, 2023, 9:22:53 PM", "22 déc. 2023, 21:22:53", - "2023年12月22日 21:22:53", + "2023/12/22 21:22:53", // TODO(#5806) "2023年12月22日 21:22:53", "22 दिस॰ 2023, 9:22:53 pm", "Dec 22, 2023, 9:22 PM", "22 déc. 2023, 21:22", - "2023年12月22日 21:22", + "2023/12/22 21:22", // TODO(#5806) "2023年12月22日 21:22", "22 दिस॰ 2023, 9:22 pm", "12/22/23, 9:22:53 PM", "22/12/2023 21:22:53", @@ -56,11 +54,11 @@ const EXPECTED_DATE: &[&str] = &[ "शुक्रवार, 22 दिसंबर 2023", "December 22, 2023", "22 décembre 2023", - "2023年12月22日", + "2023/12/22", // TODO(#5806) "2023年12月22日", "22 दिसंबर 2023", "Dec 22, 2023", "22 déc. 2023", - "2023年12月22日", + "2023/12/22", // TODO(#5806) "2023年12月22日", "22 दिस॰ 2023", "12/22/23", "22/12/2023", @@ -72,38 +70,27 @@ const EXPECTED_DATE: &[&str] = &[ fn neo_datetime_lengths() { let datetime = DateTime::try_new_gregorian(2023, 12, 22, 21, 22, 53).unwrap(); let mut expected_iter = EXPECTED_DATETIME.iter(); - for date_length in [ - length::Date::Full, - length::Date::Long, - length::Date::Medium, - length::Date::Short, + for field_set in [ + DateAndTimeFieldSet::YMDET(fieldset::YMDET::long()), + DateAndTimeFieldSet::YMDET(fieldset::YMDET::long().hm()), + DateAndTimeFieldSet::YMDT(fieldset::YMDT::long()), + DateAndTimeFieldSet::YMDT(fieldset::YMDT::long().hm()), + DateAndTimeFieldSet::YMDT(fieldset::YMDT::medium()), + DateAndTimeFieldSet::YMDT(fieldset::YMDT::medium().hm()), + DateAndTimeFieldSet::YMDT(fieldset::YMDT::short()), + DateAndTimeFieldSet::YMDT(fieldset::YMDT::short().hm()), ] { - let date_skeleton = NeoDateSkeleton::from_date_length(date_length); - for time_length in [length::Time::Medium, length::Time::Short] { - let time_components = NeoTimeComponents::from_time_length(time_length); - for locale in [ - locale!("en").into(), - locale!("fr").into(), - locale!("zh").into(), - locale!("hi").into(), - ] { - let formatter = FixedCalendarDateTimeFormatter::try_new_with_skeleton( - &locale, - NeoDateTimeSkeleton::for_length_and_components( - date_skeleton.length, - NeoDateTimeComponents::DateTime(date_skeleton.components, time_components), - ), - ) - .unwrap(); - let formatted = formatter.format(&datetime); - let expected = expected_iter.next().unwrap(); - assert_try_writeable_eq!( - formatted, - *expected, - Ok(()), - "{date_skeleton:?} {time_components:?} {locale:?}" - ); - } + for locale in [ + locale!("en").into(), + locale!("fr").into(), + locale!("zh").into(), + locale!("hi").into(), + ] { + let skeleton = CompositeDateTimeFieldSet::DateTime(field_set); + let formatter = FixedCalendarDateTimeFormatter::try_new(&locale, skeleton).unwrap(); + let formatted = formatter.format(&datetime); + let expected = expected_iter.next().unwrap(); + assert_try_writeable_eq!(formatted, *expected, Ok(()), "{skeleton:?} {locale:?}"); } } } @@ -112,13 +99,13 @@ fn neo_datetime_lengths() { fn neo_date_lengths() { let datetime = DateTime::try_new_gregorian(2023, 12, 22, 21, 22, 53).unwrap(); let mut expected_iter = EXPECTED_DATE.iter(); - for date_length in [ - length::Date::Full, - length::Date::Long, - length::Date::Medium, - length::Date::Short, + for field_set in [ + DateFieldSet::YMDE(fieldset::YMDE::long()), + DateFieldSet::YMD(fieldset::YMD::long()), + DateFieldSet::YMD(fieldset::YMD::medium()), + DateFieldSet::YMD(fieldset::YMD::short()), ] { - let date_skeleton = NeoDateSkeleton::from_date_length(date_length); + let date_skeleton = CompositeDateTimeFieldSet::Date(field_set); for locale in [ locale!("en").into(), locale!("fr").into(), @@ -126,8 +113,7 @@ fn neo_date_lengths() { locale!("hi").into(), ] { let formatter = - FixedCalendarDateTimeFormatter::try_new_with_skeleton(&locale, date_skeleton) - .unwrap(); + FixedCalendarDateTimeFormatter::try_new(&locale, date_skeleton).unwrap(); let formatted = formatter.format(&datetime); let expected = expected_iter.next().unwrap(); assert_try_writeable_eq!(formatted, *expected, Ok(()), "{date_skeleton:?} {locale:?}"); @@ -143,66 +129,50 @@ fn overlap_patterns() { }; struct TestCase { locale: Locale, - components: NeoDateTimeComponents, - length: NeoSkeletonLength, + skeleton: CompositeDateTimeFieldSet, expected: &'static str, } let cases = [ // Note: in en-US, there is no comma in the overlap pattern TestCase { locale: locale!("en-US"), - components: NeoDateTimeComponents::DateTime( - NeoDateComponents::Weekday, - NeoTimeComponents::HourMinute, - ), - length: NeoSkeletonLength::Medium, - expected: "Fri 8:40\u{202f}PM", + skeleton: CompositeDateTimeFieldSet::DateTime(DateAndTimeFieldSet::ET( + fieldset::ET::medium(), + )), + expected: "Fri 8:40:07\u{202f}PM", }, TestCase { locale: locale!("en-US"), - components: NeoDateTimeComponents::DateTime( - NeoDateComponents::MonthDayWeekday, - NeoTimeComponents::HourMinute, - ), - length: NeoSkeletonLength::Medium, - expected: "Fri, Aug 9, 8:40\u{202f}PM", + skeleton: CompositeDateTimeFieldSet::DateTime(DateAndTimeFieldSet::MDET( + fieldset::MDET::medium(), + )), + expected: "Fri, Aug 9, 8:40:07\u{202f}PM", }, // Note: in ru, the standalone weekday name is used when it is the only one in the pattern // (but the strings are the same in data) TestCase { locale: locale!("ru"), - components: NeoDateTimeComponents::DateTime( - NeoDateComponents::Weekday, - NeoTimeComponents::HourMinute, - ), - length: NeoSkeletonLength::Medium, - expected: "пт 20:40", + skeleton: CompositeDateTimeFieldSet::DateTime(DateAndTimeFieldSet::ET( + fieldset::ET::medium(), + )), + expected: "пт 20:40:07", }, TestCase { locale: locale!("ru"), - components: NeoDateTimeComponents::Date(NeoDateComponents::Weekday), - length: NeoSkeletonLength::Medium, + skeleton: CompositeDateTimeFieldSet::Date(DateFieldSet::E(fieldset::E::medium())), expected: "пт", }, ]; for TestCase { locale, - components, - length, + skeleton, expected, } in cases { - let skeleton = NeoDateTimeSkeleton::for_length_and_components(length, components); let formatter = - FixedCalendarDateTimeFormatter::try_new_with_skeleton(&(&locale).into(), skeleton) - .unwrap(); + FixedCalendarDateTimeFormatter::try_new(&(&locale).into(), skeleton).unwrap(); let formatted = formatter.format(&datetime); - assert_try_writeable_eq!( - formatted, - expected, - Ok(()), - "{locale:?} {components:?} {length:?}" - ); + assert_try_writeable_eq!(formatted, expected, Ok(()), "{locale:?} {skeleton:?}"); } } @@ -221,42 +191,24 @@ fn hebrew_months() { #[test] fn test_5387() { let datetime = DateTime::try_new_gregorian(2024, 8, 16, 14, 15, 16).unwrap(); - let formatter_auto = FixedCalendarDateTimeFormatter::try_new_with_skeleton( + let formatter_auto = FixedCalendarDateTimeFormatter::try_new( &locale!("en").into(), - NeoDateTimeSkeleton::for_length_and_components( - NeoSkeletonLength::Medium, - NeoDateTimeComponents::DateTime( - NeoDateComponents::Weekday, - NeoTimeComponents::HourMinute, - ), - ), + CompositeDateTimeFieldSet::DateTime(DateAndTimeFieldSet::ET(fieldset::ET::medium())), ) .unwrap(); - let formatter_h12 = FixedCalendarDateTimeFormatter::try_new_with_skeleton( + let formatter_h12 = FixedCalendarDateTimeFormatter::try_new( &locale!("en-u-hc-h12").into(), - NeoDateTimeSkeleton::for_length_and_components( - NeoSkeletonLength::Medium, - NeoDateTimeComponents::DateTime( - NeoDateComponents::Weekday, - NeoTimeComponents::HourMinute, - ), - ), + CompositeDateTimeFieldSet::DateTime(DateAndTimeFieldSet::ET(fieldset::ET::medium())), ) .unwrap(); - let formatter_h24 = FixedCalendarDateTimeFormatter::try_new_with_skeleton( + let formatter_h24 = FixedCalendarDateTimeFormatter::try_new( &locale!("en-u-hc-h23").into(), - NeoDateTimeSkeleton::for_length_and_components( - NeoSkeletonLength::Medium, - NeoDateTimeComponents::DateTime( - NeoDateComponents::Weekday, - NeoTimeComponents::HourMinute, - ), - ), + CompositeDateTimeFieldSet::DateTime(DateAndTimeFieldSet::ET(fieldset::ET::medium())), ) .unwrap(); // TODO(#5387): All of these should resolve to a pattern without a comma - assert_try_writeable_eq!(formatter_auto.format(&datetime), "Fri 2:15\u{202f}PM"); - assert_try_writeable_eq!(formatter_h12.format(&datetime), "Fri, 2:15\u{202f}PM"); - assert_try_writeable_eq!(formatter_h24.format(&datetime), "Fri, 14:15"); + assert_try_writeable_eq!(formatter_auto.format(&datetime), "Fri 2:15:16\u{202f}PM"); + assert_try_writeable_eq!(formatter_h12.format(&datetime), "Fri, 2:15:16\u{202f}PM"); + assert_try_writeable_eq!(formatter_h24.format(&datetime), "Fri, 14:15:16"); } diff --git a/components/datetime/tests/skeleton_serialization.rs b/components/datetime/tests/skeleton_serialization.rs index 00e70ed9ca2..5ff74651acf 100644 --- a/components/datetime/tests/skeleton_serialization.rs +++ b/components/datetime/tests/skeleton_serialization.rs @@ -40,6 +40,11 @@ fn get_skeleton_bincode_from_file() -> Vec> { #[test] fn test_skeleton_json_serialization_roundtrip() { for skeleton_string in &get_skeleton_fixtures() { + if skeleton_string == "yw" { + // TODO(#5643) + continue; + } + // Wrap the string in quotes so it's a JSON string. let json_in: String = serde_json::to_string(skeleton_string).unwrap(); @@ -77,6 +82,12 @@ fn test_skeleton_bincode_serialization_roundtrip() { Some(get_skeleton_bincode_from_file()) }; + // TODO(#5643) + let skeletons = skeletons + .into_iter() + .filter(|s| s != "yw") + .collect::>(); + if let Some(ref expect_vec) = expect_vec { if expect_vec.len() != skeletons.len() { panic!( diff --git a/components/decimal/Cargo.toml b/components/decimal/Cargo.toml index dc00a91aec7..fe389493a29 100644 --- a/components/decimal/Cargo.toml +++ b/components/decimal/Cargo.toml @@ -55,6 +55,9 @@ compiled_data = ["dep:icu_decimal_data"] # Bench feature gets tested separately and is only relevant for CI denylist = ["bench"] +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(icu4x_run_size_tests)'] } + [[bench]] name = "fixed_decimal_format" harness = false diff --git a/components/decimal/src/lib.rs b/components/decimal/src/lib.rs index 31889326121..19d9f7df0f8 100644 --- a/components/decimal/src/lib.rs +++ b/components/decimal/src/lib.rs @@ -94,14 +94,18 @@ mod format; mod grouper; pub mod options; pub mod provider; +pub(crate) mod size_test_macro; pub use format::FormattedFixedDecimal; use alloc::string::String; use fixed_decimal::FixedDecimal; use icu_provider::prelude::*; +use size_test_macro::size_test; use writeable::Writeable; +size_test!(FixedDecimalFormatter, fixed_decimal_formatter_size, 208); + /// A formatter for [`FixedDecimal`], rendering decimal digits in an i18n-friendly way. /// /// [`FixedDecimalFormatter`] supports: @@ -113,6 +117,8 @@ use writeable::Writeable; /// Read more about the options in the [`options`] module. /// /// See the crate-level documentation for examples. +/// +#[doc = fixed_decimal_formatter_size!()] #[derive(Debug)] pub struct FixedDecimalFormatter { options: options::FixedDecimalFormatterOptions, @@ -127,13 +133,8 @@ impl AsRef for FixedDecimalFormatter { impl FixedDecimalFormatter { icu_provider::gen_any_buffer_data_constructors!( - (locale, options: options::FixedDecimalFormatterOptions) -> error: DataError, /// Creates a new [`FixedDecimalFormatter`] from compiled data and an options bag. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) ); #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)] diff --git a/components/decimal/src/size_test_macro.rs b/components/decimal/src/size_test_macro.rs new file mode 100644 index 00000000000..f6f8bb760ff --- /dev/null +++ b/components/decimal/src/size_test_macro.rs @@ -0,0 +1,90 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +/************************************************************************************* + * NOTE: PLEASE KEEP THIS FILE IN SYNC WITH ALL OTHER FILES NAMED size_test_macro.rs * + * TODO(#4467): Copy this file automatically * + *************************************************************************************/ + +/// Generates a test that checks the stack size of an item and a macro +/// that should be used with `#[doc]` to document it. +/// +/// ```text +/// size_test!(MyType, my_type_size, 32); +/// +/// // Add this annotation to the type's docs: +/// #[doc = my_type_size!()] +/// ``` +/// +/// The size should correspond to the Rust version in rust-toolchain.toml. +/// +/// If the size on latest beta differs from rust-toolchain.toml, use the +/// named arguments version of this macro to specify both sizes: +/// +/// ```text +/// size_test!(MyType, my_type_size, pinned = 32, beta = 24, nightly = 24); +/// ``` +/// +/// The test is ignored by default but runs in CI. To run the test locally, +/// run `cargo test -- --include-ignored` +macro_rules! size_test { + ($ty:ty, $id:ident, pinned = $pinned:literal, beta = $beta:literal, nightly = $nightly:literal) => { + macro_rules! $id { + () => { + concat!( + "\n", + "📏 This item has a stack size of ", + stringify!($pinned), + " bytes on the stable toolchain and ", + stringify!($beta), + " bytes on beta toolchain at release date." + ) + }; + } + #[test] + #[cfg_attr(not(icu4x_run_size_tests), ignore)] // Doesn't work on arbitrary Rust versions + fn $id() { + let size = core::mem::size_of::<$ty>(); + let success = match option_env!("CI_TOOLCHAIN") { + Some("nightly") => size == $nightly, + Some("beta") => size == $beta, + Some("pinned-stable") => size == $pinned, + // Manual invocation: match either size + _ => matches!(size, $pinned | $beta | $nightly), + }; + assert!( + success, + "size_of {} = {}.\n** To reproduce this failure, run `cargo test -- --ignored` **", + stringify!($ty), + size, + ); + } + }; + ($ty:ty, $id:ident, $size:literal) => { + macro_rules! $id { + () => { + concat!( + "📏 This item has a stack size of ", + stringify!($size), + " bytes on the stable toolchain at release date." + ) + }; + } + #[test] + #[cfg_attr(not(icu4x_run_size_tests), ignore)] // Doesn't work on arbitrary Rust versions + fn $id() { + let size = core::mem::size_of::<$ty>(); + let expected = $size; + assert_eq!( + size, + expected, + "size_of {} = {}.\n** To reproduce this failure, run `cargo test -- --ignored` **", + stringify!($ty), + size, + ); + } + }; +} + +pub(crate) use size_test; diff --git a/components/experimental/src/compactdecimal/formatter.rs b/components/experimental/src/compactdecimal/formatter.rs index 2cc8fd1007f..056cdf1315e 100644 --- a/components/experimental/src/compactdecimal/formatter.rs +++ b/components/experimental/src/compactdecimal/formatter.rs @@ -87,12 +87,13 @@ impl CompactDecimalFormatter { locale: &DataLocale, options: CompactDecimalFormatterOptions, ) -> Result { + let temp_loc = locale.clone().into_locale(); Ok(Self { fixed_decimal_formatter: FixedDecimalFormatter::try_new( locale, options.fixed_decimal_formatter_options, )?, - plural_rules: PluralRules::try_new_cardinal(locale)?, + plural_rules: PluralRules::try_new_cardinal(temp_loc.into())?, compact_data: DataProvider::::load( &crate::provider::Baked, DataRequest { @@ -128,13 +129,14 @@ impl CompactDecimalFormatter { + DataProvider + ?Sized, { + let temp_loc = locale.clone().into_locale(); Ok(Self { fixed_decimal_formatter: FixedDecimalFormatter::try_new_unstable( provider, locale, options.fixed_decimal_formatter_options, )?, - plural_rules: PluralRules::try_new_cardinal_unstable(provider, locale)?, + plural_rules: PluralRules::try_new_cardinal_unstable(provider, temp_loc.into())?, compact_data: DataProvider::::load( provider, DataRequest { @@ -171,12 +173,13 @@ impl CompactDecimalFormatter { locale: &DataLocale, options: CompactDecimalFormatterOptions, ) -> Result { + let temp_loc = locale.clone().into_locale(); Ok(Self { fixed_decimal_formatter: FixedDecimalFormatter::try_new( locale, options.fixed_decimal_formatter_options, )?, - plural_rules: PluralRules::try_new_cardinal(locale)?, + plural_rules: PluralRules::try_new_cardinal(temp_loc.into())?, compact_data: DataProvider::::load( &crate::provider::Baked, DataRequest { @@ -212,13 +215,14 @@ impl CompactDecimalFormatter { + DataProvider + ?Sized, { + let temp_loc = locale.clone().into_locale(); Ok(Self { fixed_decimal_formatter: FixedDecimalFormatter::try_new_unstable( provider, locale, options.fixed_decimal_formatter_options, )?, - plural_rules: PluralRules::try_new_cardinal_unstable(provider, locale)?, + plural_rules: PluralRules::try_new_cardinal_unstable(provider, temp_loc.into())?, compact_data: DataProvider::::load( provider, DataRequest { diff --git a/components/experimental/src/dimension/currency/long_formatter.rs b/components/experimental/src/dimension/currency/long_formatter.rs index 7572e15619a..9c286c6caba 100644 --- a/components/experimental/src/dimension/currency/long_formatter.rs +++ b/components/experimental/src/dimension/currency/long_formatter.rs @@ -80,7 +80,8 @@ impl LongCurrencyFormatter { let patterns = crate::provider::Baked.load(Default::default())?.payload; - let plural_rules = PluralRules::try_new_cardinal(locale)?; + let temp_loc = locale.clone().into_locale(); + let plural_rules = PluralRules::try_new_cardinal(temp_loc.into())?; Ok(Self { extended, @@ -127,7 +128,8 @@ impl LongCurrencyFormatter { let patterns = provider.load(Default::default())?.payload; - let plural_rules = PluralRules::try_new_cardinal_unstable(provider, locale)?; + let temp_loc = locale.clone().into_locale(); + let plural_rules = PluralRules::try_new_cardinal_unstable(provider, temp_loc.into())?; Ok(Self { extended, diff --git a/components/experimental/src/dimension/units/formatter.rs b/components/experimental/src/dimension/units/formatter.rs index 06ef055f08e..8c1785f11c0 100644 --- a/components/experimental/src/dimension/units/formatter.rs +++ b/components/experimental/src/dimension/units/formatter.rs @@ -82,7 +82,8 @@ impl UnitsFormatter { let fixed_decimal_formatter = FixedDecimalFormatter::try_new(locale, FixedDecimalFormatterOptions::default())?; - let plural_rules = PluralRules::try_new_cardinal(locale)?; + let temp_loc = locale.clone().into_locale(); + let plural_rules = PluralRules::try_new_cardinal(temp_loc.into())?; // TODO: Remove this allocation once we have separate markers for different widths. let attribute = Self::attribute(options.width, unit); @@ -126,7 +127,8 @@ impl UnitsFormatter { FixedDecimalFormatterOptions::default(), )?; - let plural_rules = PluralRules::try_new_cardinal_unstable(provider, locale)?; + let temp_loc = locale.clone().into_locale(); + let plural_rules = PluralRules::try_new_cardinal_unstable(provider, temp_loc.into())?; // TODO: Remove this allocation once we have separate markers for different widths. let attribute = Self::attribute(options.width, unit); diff --git a/components/experimental/src/displaynames/displaynames.rs b/components/experimental/src/displaynames/displaynames.rs index 0ec573a3ca3..71885a06b45 100644 --- a/components/experimental/src/displaynames/displaynames.rs +++ b/components/experimental/src/displaynames/displaynames.rs @@ -42,10 +42,6 @@ impl RegionDisplayNames { icu_provider::gen_any_buffer_data_constructors!( (locale, options: DisplayNamesOptions) -> error: DataError, /// Creates a new [`RegionDisplayNames`] from locale data and an options bag using compiled data. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) functions: [ try_new, try_new_with_any_provider, @@ -113,10 +109,6 @@ impl ScriptDisplayNames { icu_provider::gen_any_buffer_data_constructors!( (locale, options: DisplayNamesOptions) -> error: DataError, /// Creates a new [`ScriptDisplayNames`] from locale data and an options bag using compiled data. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) functions: [ try_new, try_new_with_any_provider, @@ -185,10 +177,6 @@ impl VariantDisplayNames { icu_provider::gen_any_buffer_data_constructors!( (locale, options: DisplayNamesOptions) -> error: DataError, /// Creates a new [`VariantDisplayNames`] from locale data and an options bag using compiled data. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) functions: [ try_new, try_new_with_any_provider, @@ -252,10 +240,6 @@ impl LanguageDisplayNames { icu_provider::gen_any_buffer_data_constructors!( (locale, options: DisplayNamesOptions) -> error: DataError, /// Creates a new [`LanguageDisplayNames`] from locale data and an options bag using compiled data. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) functions: [ try_new, try_new_with_any_provider, @@ -340,10 +324,6 @@ impl LocaleDisplayNamesFormatter { icu_provider::gen_any_buffer_data_constructors!( (locale, options: DisplayNamesOptions) -> error: DataError, /// Creates a new [`LocaleDisplayNamesFormatter`] from locale data and an options bag using compiled data. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) functions: [ try_new, try_new_with_any_provider, diff --git a/components/experimental/src/duration/formatter.rs b/components/experimental/src/duration/formatter.rs index 4a19d5f2a50..aa633a1db35 100644 --- a/components/experimental/src/duration/formatter.rs +++ b/components/experimental/src/duration/formatter.rs @@ -147,7 +147,7 @@ impl DurationUnitFormatter { } } -impl From for icu_list::ListLength { +impl From for icu_list::ListFormatterOptions { fn from(style: BaseStyle) -> Self { // Section 1.1.13 // 1. Let lfOpts be OrdinaryObjectCreate(null). @@ -157,11 +157,12 @@ impl From for icu_list::ListLength { // a. Set listStyle to "short". // 5. Perform ! CreateDataPropertyOrThrow(lfOpts, "style", listStyle). // 6. Let lf be ! Construct(%ListFormat%, « durationFormat.[[Locale]], lfOpts »). - match style { + let length = match style { BaseStyle::Long => ListLength::Wide, BaseStyle::Short | BaseStyle::Digital => ListLength::Short, BaseStyle::Narrow => ListLength::Narrow, - } + }; + Self::default().with_length(length) } } @@ -194,11 +195,12 @@ impl DurationFormatter { })? .payload; + let temp_loc = locale.clone().into_locale(); Ok(Self { digital, options, unit: DurationUnitFormatter::try_new(locale, options)?, - list: ListFormatter::try_new_unit_with_length(locale, options.base.into())?, + list: ListFormatter::try_new_unit(temp_loc.into(), options.base.into())?, fdf: FixedDecimalFormatter::try_new(locale, Default::default())?, }) } @@ -223,13 +225,14 @@ impl DurationFormatter { })? .payload; + let temp_loc = locale.clone().into_locale(); Ok(Self { digital, options, unit: DurationUnitFormatter::try_new_unstable(provider, locale, options)?, - list: ListFormatter::try_new_unit_with_length_unstable( + list: ListFormatter::try_new_unit_unstable( provider, - locale, + temp_loc.into(), options.base.into(), )?, fdf: FixedDecimalFormatter::try_new_unstable(provider, locale, Default::default())?, diff --git a/components/experimental/src/relativetime/relativetime.rs b/components/experimental/src/relativetime/relativetime.rs index 71d8e96451d..6feb8e91877 100644 --- a/components/experimental/src/relativetime/relativetime.rs +++ b/components/experimental/src/relativetime/relativetime.rs @@ -123,7 +123,8 @@ macro_rules! constructor { locale: &DataLocale, options: RelativeTimeFormatterOptions, ) -> Result { - let plural_rules = PluralRules::try_new_cardinal(locale)?; + let temp_loc = locale.clone().into_locale(); + let plural_rules = PluralRules::try_new_cardinal(temp_loc.into())?; // Initialize FixedDecimalFormatter with default options let fixed_decimal_format = FixedDecimalFormatter::try_new( locale, @@ -167,7 +168,8 @@ macro_rules! constructor { + DataProvider + ?Sized, { - let plural_rules = PluralRules::try_new_cardinal_unstable(provider, locale)?; + let temp_loc = locale.clone().into_locale(); + let plural_rules = PluralRules::try_new_cardinal_unstable(provider, temp_loc.into())?; // Initialize FixedDecimalFormatter with default options let fixed_decimal_format = FixedDecimalFormatter::try_new_unstable( provider, diff --git a/components/icu/examples/tui.rs b/components/icu/examples/tui.rs index 854fac6275d..8359d67139c 100644 --- a/components/icu/examples/tui.rs +++ b/components/icu/examples/tui.rs @@ -10,7 +10,7 @@ use icu::locale::locale; use icu::plurals::{PluralCategory, PluralRules}; use icu::timezone::TimeZoneInfo; use icu_collections::codepointinvlist::CodePointInversionListBuilder; -use icu_datetime::fieldset::YMDHMSO; +use icu_datetime::fieldset::YMDTO; use icu_datetime::FixedCalendarDateTimeFormatter; use icu_timezone::CustomZonedDateTime; use std::env; @@ -39,11 +39,9 @@ fn main() { println!("User: {user_name}"); { - let dtf = FixedCalendarDateTimeFormatter::::try_new( - &locale, - YMDHMSO::medium(), - ) - .expect("Failed to create zoned datetime formatter."); + let dtf = + FixedCalendarDateTimeFormatter::::try_new(&locale, YMDTO::medium()) + .expect("Failed to create zoned datetime formatter."); let date = Date::try_new_gregorian(2020, 10, 10).unwrap(); let time = Time::try_new(18, 56, 0, 0).unwrap(); let zone = TimeZoneInfo::utc(); @@ -69,7 +67,7 @@ fn main() { } { - let pr = PluralRules::try_new_cardinal(&locale!("en").into()) + let pr = PluralRules::try_new_cardinal(locale!("en").into()) .expect("Failed to create PluralRules."); match pr.category_for(email_count) { diff --git a/components/list/README.md b/components/list/README.md index a0b4680f27c..f7645ee8299 100644 --- a/components/list/README.md +++ b/components/list/README.md @@ -12,9 +12,10 @@ and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latte ### Formatting *and* lists in Spanish ```rust -let list_formatter = ListFormatter::try_new_and_with_length( - &locale!("es").into(), - ListLength::Wide, +let list_formatter = ListFormatter::try_new_and( + locale!("es").into(), + ListFormatterOptions::default() + .with_length(ListLength::Wide) ) .expect("locale should be present"); @@ -33,9 +34,10 @@ assert_writeable_eq!( ### Formatting *or* lists in Thai ```rust -let list_formatter = ListFormatter::try_new_or_with_length( - &locale!("th").into(), - ListLength::Short, +let list_formatter = ListFormatter::try_new_or( + locale!("th").into(), + ListFormatterOptions::default() + .with_length(ListLength::Short) ) .expect("locale should be present"); @@ -46,9 +48,10 @@ assert_writeable_eq!(list_formatter.format(1..=3), "1, 2 หรือ 3",); ### Formatting unit lists in English ```rust -let list_formatter = ListFormatter::try_new_unit_with_length( - &locale!("en").into(), - ListLength::Wide, +let list_formatter = ListFormatter::try_new_unit( + locale!("en").into(), + ListFormatterOptions::default() + .with_length(ListLength::Wide) ) .expect("locale should be present"); diff --git a/components/list/examples/and_list.rs b/components/list/examples/and_list.rs index 2f9bbcfd0da..3722b0a065a 100644 --- a/components/list/examples/and_list.rs +++ b/components/list/examples/and_list.rs @@ -6,12 +6,15 @@ icu_benchmark_macros::instrument!(); use icu_benchmark_macros::println; -use icu::list::{ListFormatter, ListLength}; +use icu::list::{ListFormatter, ListFormatterOptions, ListLength}; use icu::locale::locale; fn main() { - let list_formatter = - ListFormatter::try_new_and_with_length(&locale!("es").into(), ListLength::Wide).unwrap(); + let list_formatter = ListFormatter::try_new_and( + locale!("es").into(), + ListFormatterOptions::default().with_length(ListLength::Wide), + ) + .unwrap(); println!( "{}", diff --git a/components/list/src/lib.rs b/components/list/src/lib.rs index b57e302a7d7..69969dc0a52 100644 --- a/components/list/src/lib.rs +++ b/components/list/src/lib.rs @@ -12,13 +12,14 @@ //! ## Formatting *and* lists in Spanish //! //! ``` -//! # use icu::list::{ListFormatter, ListLength}; +//! # use icu::list::{ListFormatter, ListFormatterOptions, ListLength}; //! # use icu::locale::locale; //! # use writeable::*; //! # -//! let list_formatter = ListFormatter::try_new_and_with_length( -//! &locale!("es").into(), -//! ListLength::Wide, +//! let list_formatter = ListFormatter::try_new_and( +//! locale!("es").into(), +//! ListFormatterOptions::default() +//! .with_length(ListLength::Wide) //! ) //! .expect("locale should be present"); //! @@ -37,13 +38,14 @@ //! ## Formatting *or* lists in Thai //! //! ``` -//! # use icu::list::{ListFormatter, ListLength}; +//! # use icu::list::{ListFormatter, ListFormatterOptions, ListLength}; //! # use icu::locale::locale; //! # use writeable::*; //! # -//! let list_formatter = ListFormatter::try_new_or_with_length( -//! &locale!("th").into(), -//! ListLength::Short, +//! let list_formatter = ListFormatter::try_new_or( +//! locale!("th").into(), +//! ListFormatterOptions::default() +//! .with_length(ListLength::Short) //! ) //! .expect("locale should be present"); //! @@ -54,13 +56,14 @@ //! ## Formatting unit lists in English //! //! ``` -//! # use icu::list::{ListFormatter, ListLength}; +//! # use icu::list::{ListFormatter, ListFormatterOptions, ListLength}; //! # use icu::locale::locale; //! # use writeable::*; //! # -//! let list_formatter = ListFormatter::try_new_unit_with_length( -//! &locale!("en").into(), -//! ListLength::Wide, +//! let list_formatter = ListFormatter::try_new_unit( +//! locale!("en").into(), +//! ListFormatterOptions::default() +//! .with_length(ListLength::Wide) //! ) //! .expect("locale should be present"); //! @@ -91,25 +94,10 @@ extern crate alloc; mod lazy_automaton; mod list_formatter; +mod options; mod patterns; pub mod provider; pub use list_formatter::*; - -/// Represents the style of a list. See the -/// [CLDR spec](https://unicode.org/reports/tr35/tr35-general.html#ListPatterns) -/// for an explanation of the different styles. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Default)] -#[non_exhaustive] -pub enum ListLength { - /// A typical list - #[default] - Wide, - /// A shorter list - Short, - /// The shortest type of list - Narrow, - // *Important*: When adding a variant here, make sure the code in - // ListFormatterPatterns::{start, middle, end, pair} stays panic-free! -} +pub use options::*; diff --git a/components/list/src/list_formatter.rs b/components/list/src/list_formatter.rs index b344b3ecb41..4779089f2a4 100644 --- a/components/list/src/list_formatter.rs +++ b/components/list/src/list_formatter.rs @@ -3,8 +3,9 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::provider::*; -use crate::ListLength; +use crate::{ListFormatterOptions, ListLength}; use core::fmt::{self, Write}; +use icu_locale_core::preferences::define_preferences; use icu_provider::marker::ErasedMarker; use icu_provider::prelude::*; use writeable::*; @@ -12,6 +13,12 @@ use writeable::*; #[cfg(doc)] extern crate writeable; +define_preferences!( + /// The preferences for list formatting. + ListFormatterPreferences, + {} +); + /// A formatter that renders sequences of items in an i18n-friendly way. See the /// [crate-level documentation](crate) for more details. #[derive(Debug)] @@ -22,15 +29,11 @@ pub struct ListFormatter { macro_rules! constructor { ($name: ident, $name_any: ident, $name_buffer: ident, $name_unstable: ident, $marker: ty, $doc: literal) => { icu_provider::gen_any_buffer_data_constructors!( - (locale, style: ListLength) -> error: DataError, + (prefs: ListFormatterPreferences, options: ListFormatterOptions) -> error: DataError, #[doc = concat!("Creates a new [`ListFormatter`] that produces a ", $doc, "-type list using compiled data.")] /// /// See the [CLDR spec](https://unicode.org/reports/tr35/tr35-general.html#ListPatterns) for /// an explanation of the different types. - /// - /// ✨ *Enabled with the `compiled_data` Cargo feature.* - /// - /// [📚 Help choosing a constructor](icu_provider::constructors) functions: [ $name, $name_any, @@ -43,18 +46,21 @@ macro_rules! constructor { #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::$name)] pub fn $name_unstable( provider: &(impl DataProvider<$marker> + ?Sized), - locale: &DataLocale, - length: ListLength, + prefs: ListFormatterPreferences, + options: ListFormatterOptions, ) -> Result { + let length = match options.length.unwrap_or_default() { + ListLength::Narrow => ListFormatterPatternsV2::NARROW, + ListLength::Short => ListFormatterPatternsV2::SHORT, + ListLength::Wide => ListFormatterPatternsV2::WIDE, + }; + let locale = DataLocale::from_preferences_locale::<$marker>(prefs.locale_prefs); let data = provider .load(DataRequest { id: DataIdentifierBorrowed::for_marker_attributes_and_locale( - match length { - ListLength::Narrow => ListFormatterPatternsV2::NARROW, - ListLength::Short => ListFormatterPatternsV2::SHORT, - ListLength::Wide => ListFormatterPatternsV2::WIDE, - }, - locale), + length, + &locale + ), ..Default::default() })? .payload @@ -66,26 +72,26 @@ macro_rules! constructor { impl ListFormatter { constructor!( - try_new_and_with_length, - try_new_and_with_length_with_any_provider, - try_new_and_with_length_with_buffer_provider, - try_new_and_with_length_unstable, + try_new_and, + try_new_and_with_any_provider, + try_new_and_with_buffer_provider, + try_new_and_unstable, AndListV2Marker, "and" ); constructor!( - try_new_or_with_length, - try_new_or_with_length_with_any_provider, - try_new_or_with_length_with_buffer_provider, - try_new_or_with_length_unstable, + try_new_or, + try_new_or_with_any_provider, + try_new_or_with_buffer_provider, + try_new_or_unstable, OrListV2Marker, "or" ); constructor!( - try_new_unit_with_length, - try_new_unit_with_length_with_any_provider, - try_new_unit_with_length_with_buffer_provider, - try_new_unit_with_length_unstable, + try_new_unit, + try_new_unit_with_any_provider, + try_new_unit_with_buffer_provider, + try_new_unit_unstable, UnitListV2Marker, "unit" ); @@ -102,9 +108,10 @@ impl ListFormatter { /// use icu::list::*; /// # use icu::locale::locale; /// # use writeable::*; - /// let formatteur = ListFormatter::try_new_and_with_length( - /// &locale!("fr").into(), - /// ListLength::Wide, + /// let formatteur = ListFormatter::try_new_and( + /// locale!("fr").into(), + /// ListFormatterOptions::default() + /// .with_length(ListLength::Wide) /// ) /// .unwrap(); /// let pays = ["Italie", "France", "Espagne", "Allemagne"]; @@ -356,8 +363,8 @@ mod tests { macro_rules! test { ($locale:literal, $type:ident, $(($input:expr, $output:literal),)+) => { let f = ListFormatter::$type( - &icu::locale::locale!($locale).into(), - ListLength::Wide + icu::locale::locale!($locale).into(), + Default::default(), ).unwrap(); $( assert_writeable_eq!(f.format($input.iter()), $output); @@ -367,14 +374,14 @@ mod tests { #[test] fn test_basic() { - test!("fr", try_new_or_with_length, (["A", "B"], "A ou B"),); + test!("fr", try_new_or, (["A", "B"], "A ou B"),); } #[test] fn test_spanish() { test!( "es", - try_new_and_with_length, + try_new_and, (["x", "Mallorca"], "x y Mallorca"), (["x", "Ibiza"], "x e Ibiza"), (["x", "Hidalgo"], "x e Hidalgo"), @@ -383,7 +390,7 @@ mod tests { test!( "es", - try_new_or_with_length, + try_new_or, (["x", "Ibiza"], "x o Ibiza"), (["x", "Okinawa"], "x u Okinawa"), (["x", "8 más"], "x u 8 más"), @@ -400,18 +407,14 @@ mod tests { (["x", "11.000,92"], "x u 11.000,92"), ); - test!( - "es-AR", - try_new_and_with_length, - (["x", "Ibiza"], "x e Ibiza"), - ); + test!("es-AR", try_new_and, (["x", "Ibiza"], "x e Ibiza"),); } #[test] fn test_hebrew() { test!( "he", - try_new_and_with_length, + try_new_and, (["x", "יפו"], "x ויפו"), (["x", "Ibiza"], "x ו‑Ibiza"), ); diff --git a/components/list/src/options.rs b/components/list/src/options.rs new file mode 100644 index 00000000000..03132457a84 --- /dev/null +++ b/components/list/src/options.rs @@ -0,0 +1,55 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +/// A list of options set by the developer to adjust the behavior of the ListFormatter. +/// +/// # Examples +/// ``` +/// use icu::list::{ListFormatterOptions, ListLength}; +/// +/// let options = ListFormatterOptions::default() +/// .with_length(ListLength::Wide); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +pub struct ListFormatterOptions { + /// The length variant should reflect available space for the list. + pub length: Option, +} + +impl Default for ListFormatterOptions { + fn default() -> Self { + Self::default() + } +} + +impl ListFormatterOptions { + /// Constructs a new [`ListFormatterOptions`] struct. + pub const fn default() -> Self { + Self { length: None } + } + + /// Auguments the struct with the set [`ListLength`]. + pub const fn with_length(mut self, length: ListLength) -> Self { + self.length = Some(length); + self + } +} + +/// Represents the style of a list. See the +/// [CLDR spec](https://unicode.org/reports/tr35/tr35-general.html#ListPatterns) +/// for an explanation of the different styles. +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Default)] +#[non_exhaustive] +pub enum ListLength { + /// A typical list + #[default] + Wide, + /// A shorter list + Short, + /// The shortest type of list + Narrow, + // *Important*: When adding a variant here, make sure the code in + // ListFormatterPatterns::{start, middle, end, pair} stays panic-free! +} diff --git a/components/locale_core/Cargo.toml b/components/locale_core/Cargo.toml index 596eb74d440..a1f3b9af6da 100644 --- a/components/locale_core/Cargo.toml +++ b/components/locale_core/Cargo.toml @@ -25,15 +25,16 @@ litemap = { workspace = true, features = ["alloc"] } tinystr = { workspace = true, features = ["alloc"] } writeable = { workspace = true } -databake = { workspace = true, features = ["derive"], optional = true} +databake = { workspace = true, features = ["derive"], optional = true } serde = { workspace = true, features = ["alloc", "derive"], optional = true } zerovec = { workspace = true, optional = true } [dev-dependencies] iai = { workspace = true } icu = { path = "../../components/icu", default-features = false } +icu_provider = { workspace = true } icu_benchmark_macros = { path = "../../tools/benchmark/macros" } -litemap = { path = "../../utils/litemap", features = ["testing"]} +litemap = { path = "../../utils/litemap", features = ["testing"] } postcard = { workspace = true, features = ["use-std"] } potential_utf = { workspace = true } serde = { workspace = true, features = ["derive"] } @@ -51,7 +52,7 @@ zerovec = ["dep:zerovec", "tinystr/zerovec"] bench = ["serde"] [lib] -bench = false # This option is required for Benchmark CI +bench = false # This option is required for Benchmark CI [package.metadata.cargo-all-features] # Bench feature gets tested separately and is only relevant for CI diff --git a/components/locale_core/README.md b/components/locale_core/README.md index 919ad619264..f450134435a 100644 --- a/components/locale_core/README.md +++ b/components/locale_core/README.md @@ -8,7 +8,9 @@ This module is published as its own crate ([`icu_locale_core`](https://docs.rs/i and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project. The module provides algorithms for parsing a string into a well-formed language or locale identifier -as defined by [`UTS #35: Unicode LDML 3. Unicode Language and Locale Identifiers`]. +as defined by [`UTS #35: Unicode LDML 3. Unicode Language and Locale Identifiers`]. Additionally +the module provides [`preferences`] interface for operations on locale preferences and conversions +from and to locale unicode extensions. [`Locale`] is the most common structure to use for storing information about a language, script, region, variants and extensions. In almost all cases, this struct should be used as the diff --git a/components/locale_core/src/extensions/mod.rs b/components/locale_core/src/extensions/mod.rs index ef99e1365db..e94b8f6b378 100644 --- a/components/locale_core/src/extensions/mod.rs +++ b/components/locale_core/src/extensions/mod.rs @@ -37,6 +37,14 @@ //! assert_eq!(loc.extensions.unicode.keywords.get(&key), Some(&value)); //! ``` //! +//! # Syntactic vs Semantic Extension Handling +//! +//! This module is useful when you need to work with Locale extensions at a syntactic level, +//! perhaps for parsing or generating locale identifiers that include any syntactically valid +//! extensions. +//! For handling and validating known CLDR values with semantic meaning, see the +//! [`crate::preferences::extensions`] module. +//! //! [`LanguageIdentifier`]: super::LanguageIdentifier //! [`Locale`]: super::Locale //! [`subtags`]: super::subtags diff --git a/components/locale_core/src/extensions/unicode/keywords.rs b/components/locale_core/src/extensions/unicode/keywords.rs index a62e60c57da..7e6a354ab1e 100644 --- a/components/locale_core/src/extensions/unicode/keywords.rs +++ b/components/locale_core/src/extensions/unicode/keywords.rs @@ -237,7 +237,7 @@ impl Keywords { /// /// Returns the old Unicode extension keywords. /// - /// # Example + /// # Examples /// /// ``` /// use icu::locale::Locale; diff --git a/components/locale_core/src/extensions/unicode/subdivision.rs b/components/locale_core/src/extensions/unicode/subdivision.rs index 5fc32d8f140..bbd222f7f44 100644 --- a/components/locale_core/src/extensions/unicode/subdivision.rs +++ b/components/locale_core/src/extensions/unicode/subdivision.rs @@ -5,7 +5,7 @@ use core::str::FromStr; use crate::parser::ParseError; -use crate::subtags::Region; +use crate::subtags::{Region, Subtag}; impl_tinystr_subtag!( /// A subdivision suffix used in [`SubdivisionId`]. @@ -131,6 +131,12 @@ impl SubdivisionId { let suffix = SubdivisionSuffix::try_from_utf8(suffix_code_units)?; Ok(Self { region, suffix }) } + + /// Convert to [`Subtag`] + pub fn into_subtag(self) -> Subtag { + let result = self.region.to_tinystr().concat(self.suffix.to_tinystr()); + Subtag::from_tinystr_unvalidated(result) + } } impl writeable::Writeable for SubdivisionId { diff --git a/components/locale_core/src/lib.rs b/components/locale_core/src/lib.rs index 372a26e8f30..6344ec4a134 100644 --- a/components/locale_core/src/lib.rs +++ b/components/locale_core/src/lib.rs @@ -8,7 +8,9 @@ //! and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project. //! //! The module provides algorithms for parsing a string into a well-formed language or locale identifier -//! as defined by [`UTS #35: Unicode LDML 3. Unicode Language and Locale Identifiers`]. +//! as defined by [`UTS #35: Unicode LDML 3. Unicode Language and Locale Identifiers`]. Additionally +//! the module provides [`preferences`] interface for operations on locale preferences and conversions +//! from and to locale unicode extensions. //! //! [`Locale`] is the most common structure to use for storing information about a language, //! script, region, variants and extensions. In almost all cases, this struct should be used as the diff --git a/components/locale_core/src/parser/langid.rs b/components/locale_core/src/parser/langid.rs index 92ce6f9159b..68f77ca6274 100644 --- a/components/locale_core/src/parser/langid.rs +++ b/components/locale_core/src/parser/langid.rs @@ -231,7 +231,7 @@ pub const fn parse_locale_with_single_variant_single_keyword_unicode_extension_f } else { break; } - iter = iter.next_const().0 + iter = iter.next_const().0; } if let Some(k) = key { keyword = Some((k, current_type)); diff --git a/components/locale_core/src/preferences/extensions/mod.rs b/components/locale_core/src/preferences/extensions/mod.rs index 1de18efadc3..0869820249b 100644 --- a/components/locale_core/src/preferences/extensions/mod.rs +++ b/components/locale_core/src/preferences/extensions/mod.rs @@ -2,6 +2,22 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -//! TODO +//! A set of extensions which correspond to preferences. +//! +//! The module provides structures that represent known values for each keyword +//! in Locale [`extensions`](crate::extensions) with semantic meaning. +//! +//! # Syntactic vs Semantic Extension Handling +//! +//! This module ensures that only valid, recognized values are used, providing semantic validation. +//! It would reject invalid values such as `-u-hc-BB` because `BB` is not a known hour cycle. This +//! is ideal for applications that require strict adherence to standardized values and need to +//! prevent invalid or unrecognized data. +//! +//! If you need to construct syntactically valid Locale extensions without semantic validation, +//! allowing any valid key-value pair regardless of recognition, consider using the +//! [`crate::extensions`] module. +//! +//! [`Locale`]: crate::Locale pub mod unicode; diff --git a/components/locale_core/src/preferences/extensions/unicode/errors.rs b/components/locale_core/src/preferences/extensions/unicode/errors.rs index a44da5f4c86..5454d50c56c 100644 --- a/components/locale_core/src/preferences/extensions/unicode/errors.rs +++ b/components/locale_core/src/preferences/extensions/unicode/errors.rs @@ -2,14 +2,12 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -//! TODO +//! Errors related to parsing of Preferences. +/// Error returned by parsers of unicode extensions as preferences. #[non_exhaustive] #[derive(Debug)] -/// TODO pub enum PreferencesParseError { - /// TODO - UnknownKeyword, - /// TODO + /// The given keyword value is not a valid preference variant. InvalidKeywordValue, } diff --git a/components/locale_core/src/preferences/extensions/unicode/keywords/calendar.rs b/components/locale_core/src/preferences/extensions/unicode/keywords/calendar.rs index 2273bfb43b7..bc1ead3a3b1 100644 --- a/components/locale_core/src/preferences/extensions/unicode/keywords/calendar.rs +++ b/components/locale_core/src/preferences/extensions/unicode/keywords/calendar.rs @@ -6,36 +6,61 @@ use crate::preferences::extensions::unicode::enum_keyword; -// https://github.com/unicode-org/cldr/blob/main/common/bcp47/calendar.xml enum_keyword!( - /// TODO + /// Islamic Calendar sub-type + /// + /// The list is based on [`CLDR Calendars`](https://github.com/unicode-org/cldr/blob/main/common/bcp47/calendar.xml) IslamicCalendarAlgorithm { - "umalqura" => Umalqura, - "tbla" => Tbla, - "civil" => Civil, - "rgsa" => Rgsa + /// Islamic calendar, Umm al-Qura + Umalqura, + /// Hijri calendar, tabular (intercalary years \[2,5,7,10,13,16,18,21,24,26,29] - astronomical epoch) + Tbla, + /// Islamic calendar, tabular (intercalary years \[2,5,7,10,13,16,18,21,24,26,29] - civil epoch) + Civil, + /// Hijri calendar, Saudi Arabia sighting + Rgsa }); enum_keyword!( - /// TODO + /// A Unicode Calendar Identifier defines a type of calendar. + /// + /// This selects calendar-specific data within a locale used for formatting and parsing, + /// such as date/time symbols and patterns; it also selects supplemental calendarData used + /// for calendrical calculations. The value can affect the computation of the first day of the week. + /// + /// The valid values are listed in [LDML](https://unicode.org/reports/tr35/#UnicodeCalendarIdentifier). CalendarAlgorithm { - "buddhist" => Buddhist, - "chinese" => Chinese, - "coptic" => Coptic, - "dangi" => Dangi, - "ethioaa" => Ethioaa, - "ethiopic" => Ethiopic, - "gregory" => Gregory, - "hebrew" => Hebrew, - "indian" => Indian, - "islamic" => Islamic(IslamicCalendarAlgorithm) { - "umalqura" => Umalqura, - "tbla" => Tbla, - "civil" => Civil, - "rgsa" => Rgsa - }, - "iso8601" => Iso8601, - "japanese" => Japanese, - "persian" => Persian, - "roc" => Roc + /// Thai Buddhist calendar (same as Gregorian except for the year) + ("buddhist" => Buddhist), + /// Traditional Chinese calendar + ("chinese" => Chinese), + /// Coptic calendar + ("coptic" => Coptic), + /// Traditional Korean calendar + ("dangi" => Dangi), + /// Ethiopic calendar, Amete Alem (epoch approx. 5493 B.C.E) + ("ethioaa" => Ethioaa), + /// Ethiopic calendar, Amete Mihret (epoch approx, 8 C.E.) + ("ethiopic" => Ethiopic), + /// Gregorian calendar + ("gregory" => Gregory), + /// Traditional Hebrew calendar + ("hebrew" => Hebrew), + /// Indian calendar + ("indian" => Indian), + /// Islamic calendar + ("islamic" => Islamic(IslamicCalendarAlgorithm) { + ("umalqura" => Umalqura), + ("tbla" => Tbla), + ("civil" => Civil), + ("rgsa" => Rgsa) + }), + /// ISO calendar (Gregorian calendar using the ISO 8601 calendar week rules) + ("iso8601" => Iso8601), + /// Japanese Imperial calendar + ("japanese" => Japanese), + /// Persian calendar + ("persian" => Persian), + /// Republic of China calendar + ("roc" => Roc) }, "ca"); diff --git a/components/locale_core/src/preferences/extensions/unicode/keywords/collation.rs b/components/locale_core/src/preferences/extensions/unicode/keywords/collation.rs index a1942009b2a..4a23c2f24c9 100644 --- a/components/locale_core/src/preferences/extensions/unicode/keywords/collation.rs +++ b/components/locale_core/src/preferences/extensions/unicode/keywords/collation.rs @@ -5,25 +5,38 @@ use crate::preferences::extensions::unicode::enum_keyword; enum_keyword!( - /// TODO + /// A Unicode Collation Identifier defines a type of collation (sort order). + /// + /// The valid values are listed in [LDML](https://unicode.org/reports/tr35/#UnicodeCollationIdentifier). CollationType { - "big5han" => Big5han, - "compat" => Compat, - "dict" => Dict, - "direct" => Direct, - "ducet" => Ducet, - "emoji" => Emoji, - "eor" => Eor, - "gb2312" => Gb2312, - "phonebk" => Phonebk, - "phonetic" => Phonetic, - "pinyin" => Pinyin, - "reformed" => Reformed, - "search" => Search, - "searchjl" => Searchjl, - "standard" => Standard, - "stroke" => Stroke, - "trad" => Trad, - "unihan" => Unihan, - "zhuyin" => Zhuyin, + /// A previous version of the ordering, for compatibility + ("compat" => Compat), + /// Dictionary style ordering (such as in Sinhala) + ("dict" => Dict), + /// The default Unicode collation element table order + ("ducet" => Ducet), + /// Recommended ordering for emoji characters + ("emoji" => Emoji), + /// European ordering rules + ("eor" => Eor), + /// Phonebook style ordering (such as in German) + ("phonebk" => Phonebk), + /// Phonetic ordering (sorting based on pronunciation) + ("phonetic" => Phonetic), + /// Pinyin ordering for Latin and for CJK characters (used in Chinese) + ("pinyin" => Pinyin), + /// Special collation type for string search + ("search" => Search), + /// Special collation type for Korean initial consonant search + ("searchjl" => Searchjl), + /// Default ordering for each language + ("standard" => Standard), + /// Pinyin ordering for Latin, stroke order for CJK characters (used in Chinese) + ("stroke" => Stroke), + /// Traditional style ordering (such as in Spanish) + ("trad" => Trad), + /// Pinyin ordering for Latin, Unihan radical-stroke ordering for CJK characters (used in Chinese) + ("unihan" => Unihan), + /// Pinyin ordering for Latin, zhuyin order for Bopomofo and CJK characters (used in Chinese) + ("zhuyin" => Zhuyin), }, "co"); diff --git a/components/locale_core/src/preferences/extensions/unicode/keywords/currency.rs b/components/locale_core/src/preferences/extensions/unicode/keywords/currency.rs index 917366f335c..38f210a300e 100644 --- a/components/locale_core/src/preferences/extensions/unicode/keywords/currency.rs +++ b/components/locale_core/src/preferences/extensions/unicode/keywords/currency.rs @@ -8,7 +8,9 @@ use crate::{extensions::unicode::Value, subtags::Subtag}; use tinystr::TinyAsciiStr; struct_keyword!( - /// TODO + /// A Unicode Currency Identifier defines a type of currency. + /// + /// The valid values are listed in [LDML](https://unicode.org/reports/tr35/#UnicodeCurrencyIdentifier). CurrencyType, "cu", TinyAsciiStr<3>, diff --git a/components/locale_core/src/preferences/extensions/unicode/keywords/currency_format.rs b/components/locale_core/src/preferences/extensions/unicode/keywords/currency_format.rs index 489366d7cc5..259941c9c3e 100644 --- a/components/locale_core/src/preferences/extensions/unicode/keywords/currency_format.rs +++ b/components/locale_core/src/preferences/extensions/unicode/keywords/currency_format.rs @@ -5,8 +5,12 @@ use crate::preferences::extensions::unicode::enum_keyword; enum_keyword!( - /// TODO + /// A Unicode Currency Format Identifier defines a style for currency formatting. + /// + /// The valid values are listed in [LDML](https://unicode.org/reports/tr35/#UnicodeCurrencyFormatIdentifier). CurrencyFormatStyle { - "standard" => Standard, - "account" => Account + /// Negative numbers use the minusSign symbol (the default) + ("standard" => Standard), + /// Negative numbers use parentheses or equivalent + ("account" => Account) }, "cf"); diff --git a/components/locale_core/src/preferences/extensions/unicode/keywords/dictionary_break.rs b/components/locale_core/src/preferences/extensions/unicode/keywords/dictionary_break.rs index 2c266507bc1..c280808f939 100644 --- a/components/locale_core/src/preferences/extensions/unicode/keywords/dictionary_break.rs +++ b/components/locale_core/src/preferences/extensions/unicode/keywords/dictionary_break.rs @@ -10,7 +10,10 @@ use alloc::vec::Vec; use core::str::FromStr; struct_keyword!( - /// TODO + /// A Unicode Dictionary Break Exclusion Identifier specifies + /// scripts to be excluded from dictionary-based text break (for words and lines). + /// + /// The valid values are of one or more items of type [`Script`](crate::subtags::Script). DictionaryBreakScriptExclusions, "dx", Vec