diff --git a/components/datetime/Cargo.toml b/components/datetime/Cargo.toml index ee60bfa9f4c..2eefbb8e664 100644 --- a/components/datetime/Cargo.toml +++ b/components/datetime/Cargo.toml @@ -118,6 +118,10 @@ required-features = ["compiled_data"] name = "resolved_components" required-features = ["experimental", "compiled_data"] +[[test]] +name = "simple_test" +required-features = ["compiled_data"] + [[test]] name = "skeleton_serialization" required-features = ["experimental"] diff --git a/components/datetime/benches/datetime.rs b/components/datetime/benches/datetime.rs index 555c13af9cc..40390912026 100644 --- a/components/datetime/benches/datetime.rs +++ b/components/datetime/benches/datetime.rs @@ -8,6 +8,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use std::fmt::Write; use icu_calendar::{DateTime, Gregorian}; +use icu_datetime::neo::TypedNeoDateTimeFormatter; use icu_datetime::TypedDateTimeFormatter; use icu_datetime::{time_zone::TimeZoneFormatterOptions, TypedZonedDateTimeFormatter}; use icu_locid::Locale; @@ -69,6 +70,43 @@ fn datetime_benches(c: &mut Criterion) { #[cfg(feature = "experimental")] bench_datetime_with_fixture("components", include_str!("fixtures/tests/components.json")); + #[cfg(feature = "experimental")] + let mut bench_neo_datetime_with_fixture = |name, file| { + let fxs = serde_json::from_str::(file).unwrap(); + group.bench_function(&format!("neo/datetime_{name}"), |b| { + b.iter(|| { + for fx in &fxs.0 { + let datetimes: Vec> = fx + .values + .iter() + .map(|value| { + mock::parse_gregorian_from_str(value).expect("Failed to parse value.") + }) + .collect(); + for setup in &fx.setups { + let locale: Locale = setup.locale.parse().expect("Failed to parse locale."); + let options = fixtures::get_options(&setup.options).unwrap(); + let dtf = { + TypedNeoDateTimeFormatter::::try_new(&locale.into(), options) + .expect("Failed to create TypedNeoDateTimeFormatter.") + }; + + let mut result = String::new(); + + for dt in &datetimes { + let fdt = dtf.format(dt); + write!(result, "{fdt}").expect("Failed to write to date time format."); + result.clear(); + } + } + } + }) + }); + }; + + #[cfg(feature = "experimental")] + bench_neo_datetime_with_fixture("lengths", include_str!("fixtures/tests/lengths.json")); + let fxs = serde_json::from_str::(include_str!( "fixtures/tests/lengths_with_zones.json" )) diff --git a/components/datetime/src/calendar.rs b/components/datetime/src/calendar.rs index 66d97e5ecc4..69e5a1bdec6 100644 --- a/components/datetime/src/calendar.rs +++ b/components/datetime/src/calendar.rs @@ -62,6 +62,10 @@ pub trait CldrCalendar: InternalCldrCalendar { /// The data marker for loading month symbols for this calendar. type MonthNamesV1Marker: KeyedDataMarker>; + #[cfg(feature = "experimental")] + /// The data marker for loading a single date pattern for this calendar. + type DatePatternV1Marker: KeyedDataMarker>; + /// Checks if a given BCP 47 identifier is allowed to be used with this calendar /// /// By default, just checks against DEFAULT_BCP_47_IDENTIFIER @@ -91,6 +95,8 @@ impl CldrCalendar for Buddhist { type YearNamesV1Marker = BuddhistYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = BuddhistMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = BuddhistDatePatternV1Marker; } impl CldrCalendar for Chinese { @@ -101,6 +107,8 @@ impl CldrCalendar for Chinese { type YearNamesV1Marker = ChineseYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = ChineseMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = ChineseDatePatternV1Marker; } impl CldrCalendar for Coptic { @@ -111,6 +119,8 @@ impl CldrCalendar for Coptic { type YearNamesV1Marker = CopticYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = CopticMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = CopticDatePatternV1Marker; } impl CldrCalendar for Dangi { @@ -121,6 +131,8 @@ impl CldrCalendar for Dangi { type YearNamesV1Marker = DangiYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = DangiMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = DangiDatePatternV1Marker; } impl CldrCalendar for Ethiopian { @@ -131,6 +143,8 @@ impl CldrCalendar for Ethiopian { type YearNamesV1Marker = EthiopianYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = EthiopianMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = EthiopianDatePatternV1Marker; fn is_identifier_allowed_for_calendar(value: &Value) -> bool { *value == value!("ethiopic") || *value == value!("ethioaa") } @@ -144,6 +158,8 @@ impl CldrCalendar for Gregorian { type YearNamesV1Marker = GregorianYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = GregorianMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = GregorianDatePatternV1Marker; } impl CldrCalendar for Hebrew { @@ -154,6 +170,8 @@ impl CldrCalendar for Hebrew { type YearNamesV1Marker = HebrewYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = HebrewMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = HebrewDatePatternV1Marker; } impl CldrCalendar for Indian { @@ -164,6 +182,8 @@ impl CldrCalendar for Indian { type YearNamesV1Marker = IndianYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = IndianMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = IndianDatePatternV1Marker; } impl CldrCalendar for IslamicCivil { @@ -177,6 +197,8 @@ impl CldrCalendar for IslamicCivil { type YearNamesV1Marker = IslamicYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = IslamicMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = IslamicDatePatternV1Marker; fn is_identifier_allowed_for_calendar(value: &Value) -> bool { *value == value!("islamicc") || is_islamic_subcal(value, tinystr!(8, "civil")) } @@ -190,6 +212,8 @@ impl CldrCalendar for IslamicObservational { type YearNamesV1Marker = IslamicYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = IslamicMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = IslamicDatePatternV1Marker; } impl CldrCalendar for IslamicTabular { @@ -203,6 +227,8 @@ impl CldrCalendar for IslamicTabular { type YearNamesV1Marker = IslamicYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = IslamicMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = IslamicDatePatternV1Marker; fn is_identifier_allowed_for_calendar(value: &Value) -> bool { is_islamic_subcal(value, tinystr!(8, "tbla")) } @@ -219,6 +245,8 @@ impl CldrCalendar for IslamicUmmAlQura { type YearNamesV1Marker = IslamicYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = IslamicMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = IslamicDatePatternV1Marker; fn is_identifier_allowed_for_calendar(value: &Value) -> bool { is_islamic_subcal(value, tinystr!(8, "umalqura")) } @@ -232,6 +260,8 @@ impl CldrCalendar for Japanese { type YearNamesV1Marker = JapaneseYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = JapaneseMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = JapaneseDatePatternV1Marker; } impl CldrCalendar for JapaneseExtended { @@ -242,6 +272,8 @@ impl CldrCalendar for JapaneseExtended { type YearNamesV1Marker = JapaneseExtendedYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = JapaneseExtendedMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = JapaneseExtendedDatePatternV1Marker; } impl CldrCalendar for Persian { @@ -252,6 +284,8 @@ impl CldrCalendar for Persian { type YearNamesV1Marker = PersianYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = PersianMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = PersianDatePatternV1Marker; } impl CldrCalendar for Roc { @@ -262,6 +296,8 @@ impl CldrCalendar for Roc { type YearNamesV1Marker = RocYearNamesV1Marker; #[cfg(feature = "experimental")] type MonthNamesV1Marker = RocMonthNamesV1Marker; + #[cfg(feature = "experimental")] + type DatePatternV1Marker = RocDatePatternV1Marker; } impl InternalCldrCalendar for Buddhist {} diff --git a/components/datetime/src/external_loaders.rs b/components/datetime/src/external_loaders.rs new file mode 100644 index 00000000000..1eb11c15d13 --- /dev/null +++ b/components/datetime/src/external_loaders.rs @@ -0,0 +1,161 @@ +// 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 ). + +//! Internal traits and structs for loading data from other crates. + +use icu_calendar::provider::WeekDataV2Marker; +use icu_calendar::week::WeekCalculator; +use icu_calendar::CalendarError; +use icu_decimal::options::FixedDecimalFormatterOptions; +use icu_decimal::provider::DecimalSymbolsV1Marker; +use icu_decimal::{DecimalError, FixedDecimalFormatter}; +use icu_provider::prelude::*; + +/// Trait for loading a FixedDecimalFormatter. +/// +/// Implemented on the provider-specific loader types in this module. +pub(crate) trait FixedDecimalFormatterLoader { + fn load( + &self, + locale: &DataLocale, + options: FixedDecimalFormatterOptions, + ) -> Result; +} + +/// Trait for loading a WeekCalculator. +/// +/// Implemented on the provider-specific loader types in this module. +pub(crate) trait WeekCalculatorLoader { + fn load(&self, locale: &DataLocale) -> Result; +} + +/// Helper for type resolution with optional loader arguments +pub(crate) struct PhantomLoader { + _not_constructible: core::convert::Infallible, +} + +impl FixedDecimalFormatterLoader for PhantomLoader { + fn load( + &self, + _locale: &DataLocale, + _options: FixedDecimalFormatterOptions, + ) -> Result { + unreachable!() // not constructible + } +} + +impl WeekCalculatorLoader for PhantomLoader { + #[inline] + fn load(&self, _locale: &DataLocale) -> Result { + unreachable!() // not constructible + } +} + +/// Loader for types from other crates using compiled data. +#[cfg(feature = "compiled_data")] +pub(crate) struct ExternalLoaderCompiledData; + +#[cfg(feature = "compiled_data")] +impl FixedDecimalFormatterLoader for ExternalLoaderCompiledData { + #[inline] + fn load( + &self, + locale: &DataLocale, + options: FixedDecimalFormatterOptions, + ) -> Result { + FixedDecimalFormatter::try_new(locale, options) + } +} + +#[cfg(feature = "compiled_data")] +impl WeekCalculatorLoader for ExternalLoaderCompiledData { + #[inline] + fn load(&self, locale: &DataLocale) -> Result { + WeekCalculator::try_new(locale) + } +} + +/// Loader for types from other crates using [`AnyProvider`]. +pub(crate) struct ExternalLoaderAny<'a, P: ?Sized>(pub &'a P); + +impl

FixedDecimalFormatterLoader for ExternalLoaderAny<'_, P> +where + P: ?Sized + AnyProvider, +{ + #[inline] + fn load( + &self, + locale: &DataLocale, + options: FixedDecimalFormatterOptions, + ) -> Result { + FixedDecimalFormatter::try_new_with_any_provider(self.0, locale, options) + } +} + +impl

WeekCalculatorLoader for ExternalLoaderAny<'_, P> +where + P: ?Sized + AnyProvider, +{ + #[inline] + fn load(&self, locale: &DataLocale) -> Result { + WeekCalculator::try_new_with_any_provider(self.0, locale) + } +} + +/// Loader for types from other crates using [`BufferProvider`]. +#[cfg(feature = "serde")] +pub(crate) struct ExternalLoaderBuffer<'a, P: ?Sized>(pub &'a P); + +#[cfg(feature = "serde")] +impl

FixedDecimalFormatterLoader for ExternalLoaderBuffer<'_, P> +where + P: ?Sized + BufferProvider, +{ + #[inline] + fn load( + &self, + locale: &DataLocale, + options: FixedDecimalFormatterOptions, + ) -> Result { + FixedDecimalFormatter::try_new_with_buffer_provider(self.0, locale, options) + } +} + +#[cfg(feature = "serde")] +impl

WeekCalculatorLoader for ExternalLoaderBuffer<'_, P> +where + P: ?Sized + BufferProvider, +{ + #[inline] + fn load(&self, locale: &DataLocale) -> Result { + WeekCalculator::try_new_with_buffer_provider(self.0, locale) + } +} + +/// Loader for types from other crates using [`DataProvider`]. +pub(crate) struct ExternalLoaderUnstable<'a, P: ?Sized>(pub &'a P); + +impl

FixedDecimalFormatterLoader for ExternalLoaderUnstable<'_, P> +where + P: ?Sized + DataProvider, +{ + #[inline] + fn load( + &self, + locale: &DataLocale, + options: FixedDecimalFormatterOptions, + ) -> Result { + FixedDecimalFormatter::try_new_unstable(self.0, locale, options) + } +} + +impl

WeekCalculatorLoader for ExternalLoaderUnstable<'_, P> +where + P: ?Sized + DataProvider, +{ + #[inline] + fn load(&self, locale: &DataLocale) -> Result { + WeekCalculator::try_new_unstable(self.0, locale) + } +} diff --git a/components/datetime/src/format/datetime.rs b/components/datetime/src/format/datetime.rs index fd41c6a8395..8236107999c 100644 --- a/components/datetime/src/format/datetime.rs +++ b/components/datetime/src/format/datetime.rs @@ -7,6 +7,7 @@ use crate::fields::{self, Field, FieldLength, FieldSymbol, Second, Week, Year}; use crate::input::{ DateTimeInput, DateTimeInputWithWeekConfig, ExtractedDateTimeInput, LocalizedDateTimeInput, }; +use crate::pattern::runtime::PatternMetadata; use crate::pattern::{ runtime::{Pattern, PatternPlurals}, PatternItem, @@ -128,7 +129,8 @@ where } pub(crate) fn write_pattern<'data, T, W, DS, TS>( - pattern: &crate::pattern::runtime::Pattern, + pattern_items: impl Iterator, + pattern_metadata: PatternMetadata, date_symbols: Option<&DS>, time_symbols: Option<&TS>, loc_datetime: &impl LocalizedDateTimeInput, @@ -141,11 +143,11 @@ where DS: DateSymbols<'data>, TS: TimeSymbols, { - let mut iter = pattern.items.iter().peekable(); + let mut iter = pattern_items.peekable(); loop { match iter.next() { Some(PatternItem::Field(field)) => write_field( - pattern, + pattern_metadata, field, iter.peek(), date_symbols, @@ -179,7 +181,8 @@ where let loc_datetime = DateTimeInputWithWeekConfig::new(datetime, week_data); let pattern = patterns.select(&loc_datetime, ordinal_rules)?; write_pattern( - pattern, + pattern.items.iter(), + pattern.metadata, date_symbols, time_symbols, &loc_datetime, @@ -213,7 +216,7 @@ const PLACEHOLDER_LEAP_PREFIX: &str = "(leap)"; // update the matching query in `analyze_pattern` function. #[allow(clippy::too_many_arguments)] pub(super) fn write_field<'data, T, W, DS, TS>( - pattern: &crate::pattern::runtime::Pattern, + pattern_metadata: PatternMetadata, field: fields::Field, next_item: Option<&PatternItem>, date_symbols: Option<&DS>, @@ -495,7 +498,7 @@ where .datetime() .hour() .ok_or(Error::MissingInputField(Some("hour")))?, - pattern.metadata.time_granularity().is_top_of_hour( + pattern_metadata.time_granularity().is_top_of_hour( datetime.datetime().minute().map(u8::from).unwrap_or(0), datetime.datetime().second().map(u8::from).unwrap_or(0), datetime.datetime().nanosecond().map(u32::from).unwrap_or(0), @@ -594,6 +597,7 @@ pub fn analyze_patterns( #[cfg(feature = "compiled_data")] mod tests { use super::*; + use crate::pattern::runtime; use icu_decimal::options::{FixedDecimalFormatterOptions, GroupingStrategy}; use icu_locid::Locale; @@ -639,7 +643,7 @@ mod tests { .unwrap() .take_payload() .unwrap(); - let pattern = "MMM".parse().unwrap(); + let pattern: runtime::Pattern = "MMM".parse().unwrap(); let datetime = DateTime::try_new_gregorian_datetime(2020, 8, 1, 12, 34, 28).unwrap(); let fixed_decimal_format = FixedDecimalFormatter::try_new(&locale, Default::default()).unwrap(); @@ -647,7 +651,8 @@ mod tests { let mut sink = String::new(); let loc_datetime = DateTimeInputWithWeekConfig::new(&datetime, None); write_pattern( - &pattern, + pattern.items.iter(), + pattern.metadata, Some(date_data.get()), Some(time_data.get()), &loc_datetime, diff --git a/components/datetime/src/format/neo.rs b/components/datetime/src/format/neo.rs index a995cc3661d..6ac909a8519 100644 --- a/components/datetime/src/format/neo.rs +++ b/components/datetime/src/format/neo.rs @@ -5,6 +5,7 @@ use super::datetime::write_pattern; use crate::calendar::CldrCalendar; use crate::error::DateTimeError as Error; +use crate::external_loaders::*; use crate::fields::{self, FieldLength, FieldSymbol}; use crate::input; use crate::input::DateInput; @@ -12,7 +13,7 @@ use crate::input::DateTimeInput; use crate::input::DateTimeInputWithWeekConfig; use crate::input::ExtractedDateTimeInput; use crate::input::IsoTimeInput; -use crate::pattern::runtime::Pattern; +use crate::neo_pattern::{DateTimePattern, DateTimePatternBorrowed}; use crate::pattern::PatternItem; use crate::provider::date_time::{DateSymbols, MonthPlaceholderValue, TimeSymbols}; use crate::provider::neo::*; @@ -102,6 +103,18 @@ where } } +/// Helper for type resolution with optional DataProvider arguments +pub(crate) struct PhantomProvider { + _not_constructible: core::convert::Infallible, +} + +impl DataProvider for PhantomProvider { + #[inline] + fn load(&self, _req: DataRequest) -> Result, DataError> { + unreachable!() // not constructible + } +} + /// A low-level type that formats datetime patterns with localized symbols. /// ///

@@ -119,7 +132,7 @@ where /// use icu::datetime::TypedDateTimeNames; /// use icu::datetime::fields::FieldLength; /// use icu::datetime::fields; -/// use icu::datetime::pattern; +/// use icu::datetime::neo_pattern::DateTimePattern; /// use icu::locid::locale; /// use writeable::assert_writeable_eq; /// @@ -136,8 +149,7 @@ where /// /// // Create a pattern from a pattern string: /// let pattern_str = "E MMM d y -- h:mm a"; -/// let reference_pattern: pattern::reference::Pattern = pattern_str.parse().unwrap(); -/// let pattern: pattern::runtime::Pattern = (&reference_pattern).into(); +/// let pattern: DateTimePattern = pattern_str.parse().unwrap(); /// /// // Test it: /// let datetime = DateTime::try_new_gregorian_datetime(2023, 11, 20, 11, 35, 3).unwrap(); @@ -152,7 +164,7 @@ where /// use icu::datetime::TypedDateTimeNames; /// use icu::datetime::fields::FieldLength; /// use icu::datetime::fields; -/// use icu::datetime::pattern; +/// use icu::datetime::neo_pattern::DateTimePattern; /// use icu::locid::locale; /// use writeable::Writeable; /// @@ -162,8 +174,7 @@ where /// /// // Create a pattern from a pattern string: /// let pattern_str = "'It is:' E MMM d y 'at' h:mm a"; -/// let reference_pattern: pattern::reference::Pattern = pattern_str.parse().unwrap(); -/// let pattern: pattern::runtime::Pattern = (&reference_pattern).into(); +/// let pattern: DateTimePattern = pattern_str.parse().unwrap(); /// /// // The pattern string contains lots of symbols including "E", "MMM", and "a", but we did not load any data! /// let datetime = DateTime::try_new_gregorian_datetime(2023, 11, 20, 11, 35, 3).unwrap(); @@ -172,31 +183,30 @@ where /// ``` #[derive(Debug)] pub struct TypedDateTimeNames { + locale: DataLocale, inner: RawDateTimeNames, _calendar: PhantomData, } #[derive(Debug)] pub(crate) struct RawDateTimeNames { - locale: DataLocale, year_symbols: OptionalNames<(), DataPayload>, month_symbols: OptionalNames>, weekday_symbols: OptionalNames>, dayperiod_symbols: OptionalNames<(), DataPayload>, // TODO(#4340): Make the FixedDecimalFormatter optional - fixed_decimal_formatter: FixedDecimalFormatter, + fixed_decimal_formatter: Option, week_calculator: Option, } #[derive(Debug, Copy, Clone)] -struct RawDateTimeNamesBorrowed<'l> { +pub(crate) struct RawDateTimeNamesBorrowed<'l> { year_names: OptionalNames<(), &'l YearNamesV1<'l>>, month_names: OptionalNames>, weekday_names: OptionalNames>, dayperiod_names: OptionalNames<(), &'l LinearNamesV1<'l>>, - // TODO(#4340): Make the FixedDecimalFormatter optional - fixed_decimal_formatter: &'l FixedDecimalFormatter, - week_calculator: Option<&'l WeekCalculator>, + pub(crate) fixed_decimal_formatter: Option<&'l FixedDecimalFormatter>, + pub(crate) week_calculator: Option<&'l WeekCalculator>, } impl TypedDateTimeNames { @@ -209,14 +219,13 @@ impl TypedDateTimeNames { /// [📚 Help choosing a constructor](icu_provider::constructors) #[cfg(feature = "compiled_data")] pub fn try_new(locale: &DataLocale) -> Result { - let mut fixed_decimal_format_options = FixedDecimalFormatterOptions::default(); - fixed_decimal_format_options.grouping_strategy = GroupingStrategy::Never; - let fixed_decimal_formatter = - FixedDecimalFormatter::try_new(locale, fixed_decimal_format_options)?; - Ok(Self { - inner: RawDateTimeNames::new(locale.clone(), fixed_decimal_formatter), + let mut names = Self { + locale: locale.clone(), + inner: RawDateTimeNames::new_without_fixed_decimal_formatter(), _calendar: PhantomData, - }) + }; + names.include_fixed_decimal_formatter()?; + Ok(names) } #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new)] @@ -224,17 +233,13 @@ impl TypedDateTimeNames { where P: DataProvider + ?Sized, { - let mut fixed_decimal_format_options = FixedDecimalFormatterOptions::default(); - fixed_decimal_format_options.grouping_strategy = GroupingStrategy::Never; - let fixed_decimal_formatter = FixedDecimalFormatter::try_new_unstable( - provider, - locale, - fixed_decimal_format_options, - )?; - Ok(Self { - inner: RawDateTimeNames::new(locale.clone(), fixed_decimal_formatter), + let mut names = Self { + locale: locale.clone(), + inner: RawDateTimeNames::new_without_fixed_decimal_formatter(), _calendar: PhantomData, - }) + }; + names.load_fixed_decimal_formatter(provider)?; + Ok(names) } /// Loads year (era or cycle) names for the specified length. @@ -248,7 +253,8 @@ impl TypedDateTimeNames { where P: DataProvider + ?Sized, { - self.inner.load_year_names(provider, field_length)?; + self.inner + .load_year_names(provider, &self.locale, field_length)?; Ok(self) } @@ -266,10 +272,8 @@ impl TypedDateTimeNames { /// use icu::locid::locale; /// /// let mut names = - /// TypedDateTimeNames::::try_new( - /// &locale!("und").into(), - /// ) - /// .unwrap(); + /// TypedDateTimeNames::::try_new(&locale!("und").into()) + /// .unwrap(); /// /// // First length is successful: /// names.include_year_names(FieldLength::Wide).unwrap(); @@ -304,7 +308,7 @@ impl TypedDateTimeNames { P: DataProvider + ?Sized, { self.inner - .load_month_names(provider, field_symbol, field_length)?; + .load_month_names(provider, &self.locale, field_symbol, field_length)?; Ok(self) } @@ -322,10 +326,8 @@ impl TypedDateTimeNames { /// use icu::locid::locale; /// /// let mut names = - /// TypedDateTimeNames::::try_new( - /// &locale!("und").into(), - /// ) - /// .unwrap(); + /// TypedDateTimeNames::::try_new(&locale!("und").into()) + /// .unwrap(); /// let field_symbol = icu::datetime::fields::Month::Format; /// let alt_field_symbol = icu::datetime::fields::Month::StandAlone; /// @@ -345,8 +347,7 @@ impl TypedDateTimeNames { /// Err(DateTimeError::DuplicateField(_)) /// )); /// assert!(matches!( - /// names - /// .include_month_names(field_symbol, FieldLength::Abbreviated), + /// names.include_month_names(field_symbol, FieldLength::Abbreviated), /// Err(DateTimeError::DuplicateField(_)) /// )); /// ``` @@ -373,7 +374,8 @@ impl TypedDateTimeNames { where P: DataProvider + ?Sized, { - self.inner.load_day_period_names(provider, field_length)?; + self.inner + .load_day_period_names(provider, &self.locale, field_length)?; Ok(self) } @@ -391,20 +393,14 @@ impl TypedDateTimeNames { /// use icu::locid::locale; /// /// let mut names = - /// TypedDateTimeNames::::try_new( - /// &locale!("und").into(), - /// ) - /// .unwrap(); + /// TypedDateTimeNames::::try_new(&locale!("und").into()) + /// .unwrap(); /// /// // First length is successful: - /// names - /// .include_day_period_names(FieldLength::Wide) - /// .unwrap(); + /// names.include_day_period_names(FieldLength::Wide).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::Wide).unwrap(); /// /// // But loading a new length fails: /// assert!(matches!( @@ -436,7 +432,7 @@ impl TypedDateTimeNames { P: DataProvider + ?Sized, { self.inner - .load_weekday_names(provider, field_symbol, field_length)?; + .load_weekday_names(provider, &self.locale, field_symbol, field_length)?; Ok(self) } @@ -454,10 +450,8 @@ impl TypedDateTimeNames { /// use icu::locid::locale; /// /// let mut names = - /// TypedDateTimeNames::::try_new( - /// &locale!("und").into(), - /// ) - /// .unwrap(); + /// TypedDateTimeNames::::try_new(&locale!("und").into()) + /// .unwrap(); /// let field_symbol = icu::datetime::fields::Weekday::Format; /// let alt_field_symbol = icu::datetime::fields::Weekday::StandAlone; /// @@ -477,8 +471,7 @@ impl TypedDateTimeNames { /// Err(DateTimeError::DuplicateField(_)) /// )); /// assert!(matches!( - /// names - /// .include_weekday_names(field_symbol, FieldLength::Abbreviated), + /// names.include_weekday_names(field_symbol, FieldLength::Abbreviated), /// Err(DateTimeError::DuplicateField(_)) /// )); /// ``` @@ -499,31 +492,26 @@ impl TypedDateTimeNames { /// # Examples /// /// ``` + /// use icu::calendar::week::WeekCalculator; /// use icu::calendar::Date; /// use icu::calendar::Gregorian; - /// use icu::calendar::week::WeekCalculator; - /// use icu::datetime::pattern; + /// use icu::datetime::neo_pattern::DateTimePattern; /// use icu::datetime::TypedDateTimeNames; /// use icu::locid::locale; /// use writeable::assert_writeable_eq; /// /// let mut names = - /// TypedDateTimeNames::::try_new( - /// &locale!("en").into(), - /// ) - /// .unwrap(); + /// TypedDateTimeNames::::try_new(&locale!("en").into()) + /// .unwrap(); /// /// // Load the week calculator and set it here: - /// let mut week_calculator = WeekCalculator::try_new( - /// &locale!("en").into() - /// ).unwrap(); + /// let mut week_calculator = + /// WeekCalculator::try_new(&locale!("en").into()).unwrap(); /// names.set_week_calculator(week_calculator); /// /// // Format a pattern needing week data: /// let pattern_str = "'Week' w 'of' Y"; - /// let reference_pattern: pattern::reference::Pattern = - /// pattern_str.parse().unwrap(); - /// let pattern: pattern::runtime::Pattern = (&reference_pattern).into(); + /// let pattern: DateTimePattern = pattern_str.parse().unwrap(); /// let date = Date::try_new_gregorian_date(2023, 12, 5).unwrap(); /// assert_writeable_eq!( /// names.with_pattern(&pattern).format_date(&date), @@ -536,12 +524,35 @@ impl TypedDateTimeNames { self } + // TODO(#4340): Make this fn public when FixedDecimalFormatter is fully optional + #[inline] + fn load_fixed_decimal_formatter

(&mut self, provider: &P) -> Result<&mut Self, Error> + where + P: DataProvider + ?Sized, + { + self.inner + .load_fixed_decimal_formatter(&ExternalLoaderUnstable(provider), &self.locale)?; + Ok(self) + } + + // TODO(#4340): Make this fn public when FixedDecimalFormatter is fully optional + #[cfg(feature = "compiled_data")] + #[inline] + fn include_fixed_decimal_formatter(&mut self) -> Result<&mut Self, Error> { + self.inner + .load_fixed_decimal_formatter(&ExternalLoaderCompiledData, &self.locale)?; + Ok(self) + } + /// Associates this [`TypedDateTimeNames`] with a pattern /// without loading additional data for that pattern. #[inline] - pub fn with_pattern<'l>(&'l self, pattern: &'l Pattern) -> DateTimePatternFormatter<'l, C> { + pub fn with_pattern<'l>( + &'l self, + pattern: &'l DateTimePattern, + ) -> DateTimePatternFormatter<'l, C> { DateTimePatternFormatter { - inner: self.inner.with_pattern(pattern), + inner: self.inner.with_pattern(pattern.as_borrowed()), _calendar: PhantomData, } } @@ -553,28 +564,31 @@ impl TypedDateTimeNames { pub fn load_for_pattern<'l, P>( &'l mut self, provider: &P, - pattern: &'l Pattern, + pattern: &'l DateTimePattern, ) -> Result, Error> where P: DataProvider + DataProvider + DataProvider + DataProvider + + DataProvider + DataProvider + ?Sized, { - let inner = self - .inner + let locale = &self.locale; + self.inner .load_for_pattern::( - provider, - provider, - provider, - provider, - pattern, - |locale| WeekCalculator::try_new_unstable(provider, locale), + Some(provider), + Some(provider), + Some(provider), + Some(provider), + Some(&ExternalLoaderUnstable(provider)), + Some(&ExternalLoaderUnstable(provider)), + locale, + pattern.iter_items(), )?; Ok(DateTimePatternFormatter { - inner, + inner: self.inner.with_pattern(pattern.as_borrowed()), _calendar: PhantomData, }) } @@ -582,41 +596,41 @@ impl TypedDateTimeNames { /// Associates this [`TypedDateTimeNames`] with a pattern /// and includes all data required for that pattern. /// - /// Does not duplicate textual field symbols. See #4337 + /// Does not support duplicate textual field symbols. See #4337 /// /// # Examples /// /// ``` /// use icu::calendar::DateTime; /// use icu::calendar::Gregorian; - /// use icu::datetime::pattern; + /// use icu::datetime::neo_pattern::DateTimePattern; /// use icu::datetime::TypedDateTimeNames; /// use icu::locid::locale; /// use writeable::assert_writeable_eq; /// /// let mut names = - /// TypedDateTimeNames::::try_new( - /// &locale!("en").into(), - /// ) - /// .unwrap(); + /// TypedDateTimeNames::::try_new(&locale!("en").into()) + /// .unwrap(); /// /// // Create a pattern from a pattern string: /// let pattern_str = "EEEE 'on week' w 'of' Y G (MMM d) 'at' h:mm a"; - /// let reference_pattern: pattern::reference::Pattern = - /// pattern_str.parse().unwrap(); - /// let pattern: pattern::runtime::Pattern = (&reference_pattern).into(); + /// let pattern: DateTimePattern = pattern_str.parse().unwrap(); /// /// // Load data for the pattern and format: - /// let datetime = DateTime::try_new_gregorian_datetime(2023, 12, 5, 17, 43, 12).unwrap(); + /// let datetime = + /// DateTime::try_new_gregorian_datetime(2023, 12, 5, 17, 43, 12).unwrap(); /// assert_writeable_eq!( - /// names.include_for_pattern(&pattern).unwrap().format(&datetime), + /// names + /// .include_for_pattern(&pattern) + /// .unwrap() + /// .format(&datetime), /// "Tuesday on week 49 of 2023 AD (Dec 5) at 5:43 PM" /// ); /// ``` #[cfg(feature = "compiled_data")] pub fn include_for_pattern<'l>( &'l mut self, - pattern: &'l Pattern, + pattern: &'l DateTimePattern, ) -> Result, Error> where crate::provider::Baked: DataProvider @@ -624,43 +638,44 @@ impl TypedDateTimeNames { + DataProvider + DataProvider, { - let inner = self - .inner + let locale = &self.locale; + self.inner .load_for_pattern::( - &crate::provider::Baked, - &crate::provider::Baked, - &crate::provider::Baked, - &crate::provider::Baked, - pattern, - WeekCalculator::try_new, + Some(&crate::provider::Baked), + Some(&crate::provider::Baked), + Some(&crate::provider::Baked), + Some(&crate::provider::Baked), + Some(&ExternalLoaderCompiledData), + Some(&ExternalLoaderCompiledData), + locale, + pattern.iter_items(), )?; Ok(DateTimePatternFormatter { - inner, + inner: self.inner.with_pattern(pattern.as_borrowed()), _calendar: PhantomData, }) } } impl RawDateTimeNames { - pub(crate) fn new(locale: DataLocale, fixed_decimal_formatter: FixedDecimalFormatter) -> Self { + pub(crate) fn new_without_fixed_decimal_formatter() -> Self { Self { - locale, year_symbols: OptionalNames::None, month_symbols: OptionalNames::None, weekday_symbols: OptionalNames::None, dayperiod_symbols: OptionalNames::None, - fixed_decimal_formatter, + fixed_decimal_formatter: None, week_calculator: None, } } - fn as_borrowed(&self) -> RawDateTimeNamesBorrowed { + pub(crate) fn as_borrowed(&self) -> RawDateTimeNamesBorrowed { RawDateTimeNamesBorrowed { year_names: self.year_symbols.as_borrowed(), month_names: self.month_symbols.as_borrowed(), weekday_names: self.weekday_symbols.as_borrowed(), dayperiod_names: self.dayperiod_symbols.as_borrowed(), - fixed_decimal_formatter: &self.fixed_decimal_formatter, + fixed_decimal_formatter: self.fixed_decimal_formatter.as_ref(), week_calculator: self.week_calculator.as_ref(), } } @@ -668,6 +683,7 @@ impl RawDateTimeNames { pub(crate) fn load_year_names( &mut self, provider: &P, + locale: &DataLocale, field_length: FieldLength, ) -> Result<(), Error> where @@ -685,7 +701,7 @@ impl RawDateTimeNames { NamePresence::NotLoaded => (), NamePresence::Mismatched => return Err(Error::DuplicateField(field)), }; - let mut locale = self.locale.clone(); + let mut locale = locale.clone(); locale.set_aux(AuxiliaryKeys::from_subtag(aux::symbol_subtag_for( aux::Context::Format, match field_length { @@ -709,6 +725,7 @@ impl RawDateTimeNames { pub(crate) fn load_month_names( &mut self, provider: &P, + locale: &DataLocale, field_symbol: fields::Month, field_length: FieldLength, ) -> Result<(), Error> @@ -729,7 +746,7 @@ impl RawDateTimeNames { NamePresence::NotLoaded => (), NamePresence::Mismatched => return Err(Error::DuplicateField(field)), }; - let mut locale = self.locale.clone(); + let mut locale = locale.clone(); locale.set_aux(AuxiliaryKeys::from_subtag(aux::symbol_subtag_for( match field_symbol { fields::Month::Format => aux::Context::Format, @@ -756,6 +773,7 @@ impl RawDateTimeNames { pub(crate) fn load_day_period_names( &mut self, provider: &P, + locale: &DataLocale, field_length: FieldLength, ) -> Result<(), Error> where @@ -774,7 +792,7 @@ impl RawDateTimeNames { NamePresence::NotLoaded => (), NamePresence::Mismatched => return Err(Error::DuplicateField(field)), }; - let mut locale = self.locale.clone(); + let mut locale = locale.clone(); locale.set_aux(AuxiliaryKeys::from_subtag(aux::symbol_subtag_for( aux::Context::Format, match field_length { @@ -798,6 +816,7 @@ impl RawDateTimeNames { pub(crate) fn load_weekday_names( &mut self, provider: &P, + locale: &DataLocale, field_symbol: fields::Weekday, field_length: FieldLength, ) -> Result<(), Error> @@ -824,7 +843,7 @@ impl RawDateTimeNames { NamePresence::NotLoaded => (), NamePresence::Mismatched => return Err(Error::DuplicateField(field)), }; - let mut locale = self.locale.clone(); + let mut locale = locale.clone(); locale.set_aux(AuxiliaryKeys::from_subtag(aux::symbol_subtag_for( match field_symbol { // UTS 35 says that "e" and "E" have the same non-numeric names @@ -855,12 +874,24 @@ impl RawDateTimeNames { self.week_calculator = Some(week_calculator); } + pub(crate) fn load_fixed_decimal_formatter( + &mut self, + loader: &impl FixedDecimalFormatterLoader, + locale: &DataLocale, + ) -> Result<(), Error> { + let mut options = FixedDecimalFormatterOptions::default(); + options.grouping_strategy = GroupingStrategy::Never; + self.fixed_decimal_formatter = + Some(FixedDecimalFormatterLoader::load(loader, locale, options)?); + Ok(()) + } + /// Associates this [`TypedDateTimeNames`] with a pattern /// without loading additional data for that pattern. #[inline] pub(crate) fn with_pattern<'l>( &'l self, - pattern: &'l Pattern, + pattern: DateTimePatternBorrowed<'l>, ) -> RawDateTimePatternFormatter<'l> { RawDateTimePatternFormatter { pattern, @@ -868,40 +899,53 @@ impl RawDateTimeNames { } } - pub(crate) fn load_for_pattern<'l, YearMarker, MonthMarker>( - &'l mut self, - year_provider: &(impl DataProvider + ?Sized), - month_provider: &(impl DataProvider + ?Sized), - weekday_provider: &(impl DataProvider + ?Sized), - dayperiod_provider: &(impl DataProvider + ?Sized), - pattern: &'l Pattern, - week_calculator_loader: impl FnOnce( - &DataLocale, - ) - -> Result, - ) -> Result, Error> + /// Loads all data required for formatting the given [`PatternItem`]s. + /// + /// This function has a lot of arguments because many of the arguments are generic, + /// and pulling them out to an options struct would be cumbersome. + #[allow(clippy::too_many_arguments)] + pub(crate) fn load_for_pattern( + &mut self, + year_provider: Option<&(impl DataProvider + ?Sized)>, + month_provider: Option<&(impl DataProvider + ?Sized)>, + weekday_provider: Option<&(impl DataProvider + ?Sized)>, + dayperiod_provider: Option<&(impl DataProvider + ?Sized)>, + fixed_decimal_formatter_loader: Option<&impl FixedDecimalFormatterLoader>, + week_calculator_loader: Option<&impl WeekCalculatorLoader>, + locale: &DataLocale, + pattern_items: impl Iterator, + ) -> Result<(), Error> where YearMarker: KeyedDataMarker>, MonthMarker: KeyedDataMarker>, { - let fields = pattern.items.iter().filter_map(|p| match p { + let fields = pattern_items.filter_map(|p| match p { PatternItem::Field(field) => Some(field), _ => None, }); - let mut has_numeric = false; - let mut has_weeks = false; + let mut numeric_field = None; + let mut week_field = None; for field in fields { match field.symbol { ///// Textual symbols ///// FieldSymbol::Era => { - self.load_year_names(year_provider, field.length)?; + self.load_year_names( + year_provider.ok_or(Error::MissingNames(field))?, + locale, + field.length, + )?; } FieldSymbol::Month(symbol) => match field.length { - FieldLength::One => has_numeric = true, - FieldLength::TwoDigit => has_numeric = true, + FieldLength::One => numeric_field = Some(field), + FieldLength::TwoDigit => numeric_field = Some(field), _ => { - self.load_month_names(month_provider, symbol, field.length)?; + self.load_month_names( + month_provider.ok_or(Error::MissingNames(field))?, + locale, + symbol, + field.length, + )?; } }, // 'E' is always text @@ -910,24 +954,33 @@ impl RawDateTimeNames { FieldLength::One | FieldLength::TwoDigit if !matches!(symbol, fields::Weekday::Format) => { - has_numeric = true + numeric_field = Some(field) } _ => { - self.load_weekday_names(weekday_provider, symbol, field.length)?; + self.load_weekday_names( + weekday_provider.ok_or(Error::MissingNames(field))?, + locale, + symbol, + field.length, + )?; } }, FieldSymbol::DayPeriod(_) => { - self.load_day_period_names(dayperiod_provider, field.length)?; + self.load_day_period_names( + dayperiod_provider.ok_or(Error::MissingNames(field))?, + locale, + field.length, + )?; } ///// Numeric symbols ///// - FieldSymbol::Year(fields::Year::WeekOf) => has_weeks = true, - FieldSymbol::Year(_) => has_numeric = true, - FieldSymbol::Week(_) => has_weeks = true, - FieldSymbol::Day(_) => has_numeric = true, - FieldSymbol::Hour(_) => has_numeric = true, - FieldSymbol::Minute => has_numeric = true, - FieldSymbol::Second(_) => has_numeric = true, + FieldSymbol::Year(fields::Year::WeekOf) => week_field = Some(field), + FieldSymbol::Year(_) => numeric_field = Some(field), + FieldSymbol::Week(_) => week_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::TimeZone(_) => { // TODO: Consider whether time zones are supported here. return Err(Error::UnsupportedField(field.symbol)); @@ -935,15 +988,21 @@ impl RawDateTimeNames { }; } - if has_weeks { - self.set_week_calculator(week_calculator_loader(&self.locale)?); + if let Some(field) = week_field { + self.set_week_calculator(WeekCalculatorLoader::load( + week_calculator_loader.ok_or(Error::MissingNames(field))?, + locale, + )?); } - if has_numeric || has_weeks { - // TODO(#4340): Load the FixedDecimalFormatter + if let Some(field) = numeric_field.or(week_field) { + self.load_fixed_decimal_formatter( + fixed_decimal_formatter_loader.ok_or(Error::MissingNames(field))?, + locale, + )?; } - Ok(self.with_pattern(pattern)) + Ok(()) } } @@ -955,7 +1014,7 @@ pub struct DateTimePatternFormatter<'a, C: CldrCalendar> { #[derive(Debug, Copy, Clone)] pub(crate) struct RawDateTimePatternFormatter<'a> { - pattern: &'a Pattern<'a>, + pattern: DateTimePatternBorrowed<'a>, names: RawDateTimeNamesBorrowed<'a>, } @@ -967,9 +1026,6 @@ impl<'a, C: CldrCalendar> DateTimePatternFormatter<'a, C> { where T: DateTimeInput, { - // DISCUSS: Should this return `'a` or a new lifetime `'l: 'a`? - // When returning `'l`, the intermediate type needs to be anchored, - // so for now I made it return `'a`. FormattedDateTimePattern { pattern: self.inner.pattern, datetime: ExtractedDateTimeInput::extract_from(datetime), @@ -986,15 +1042,14 @@ impl<'a, C: CldrCalendar> DateTimePatternFormatter<'a, C> { /// use icu::calendar::Gregorian; /// use icu::datetime::fields; /// use icu::datetime::fields::FieldLength; - /// use icu::datetime::pattern; + /// use icu::datetime::neo_pattern::DateTimePattern; /// use icu::datetime::TypedDateTimeNames; /// use icu::locid::locale; /// use writeable::assert_writeable_eq; /// /// // Create an instance that can format wide month and era names: /// let mut names: TypedDateTimeNames = - /// TypedDateTimeNames::try_new(&locale!("en-GB").into()) - /// .unwrap(); + /// TypedDateTimeNames::try_new(&locale!("en-GB").into()).unwrap(); /// names /// .include_month_names(fields::Month::Format, FieldLength::Wide) /// .unwrap() @@ -1003,9 +1058,7 @@ impl<'a, C: CldrCalendar> DateTimePatternFormatter<'a, C> { /// /// // Create a pattern from a pattern string: /// let pattern_str = "'The date is:' MMMM d, y GGGG"; - /// let reference_pattern: pattern::reference::Pattern = - /// pattern_str.parse().unwrap(); - /// let pattern: pattern::runtime::Pattern = (&reference_pattern).into(); + /// let pattern: DateTimePattern = pattern_str.parse().unwrap(); /// /// // Test it with some different dates: /// // Note: extended year -50 is year 51 BCE @@ -1039,24 +1092,21 @@ impl<'a, C: CldrCalendar> DateTimePatternFormatter<'a, C> { /// use icu::calendar::types::Time; /// use icu::calendar::Gregorian; /// use icu::datetime::fields::FieldLength; - /// use icu::datetime::pattern; + /// use icu::datetime::neo_pattern::DateTimePattern; /// use icu::datetime::TypedDateTimeNames; /// use icu::locid::locale; /// use writeable::assert_writeable_eq; /// /// // Create an instance that can format abbreviated day periods: /// let mut names: TypedDateTimeNames = - /// TypedDateTimeNames::try_new(&locale!("en-US").into()) - /// .unwrap(); + /// TypedDateTimeNames::try_new(&locale!("en-US").into()).unwrap(); /// names /// .include_day_period_names(FieldLength::Abbreviated) /// .unwrap(); /// /// // Create a pattern from a pattern string: /// let pattern_str = "'The time is:' h:mm b"; - /// let reference_pattern: pattern::reference::Pattern = - /// pattern_str.parse().unwrap(); - /// let pattern: pattern::runtime::Pattern = (&reference_pattern).into(); + /// let pattern: DateTimePattern = pattern_str.parse().unwrap(); /// /// // Test it with different times of day: /// let time_am = Time::try_new(11, 4, 14, 0).unwrap(); @@ -1101,22 +1151,28 @@ impl<'a, C: CldrCalendar> DateTimePatternFormatter<'a, C> { /// #1317 ///

#[derive(Debug)] -pub struct FormattedDateTimePattern<'l> { - pattern: &'l Pattern<'l>, +pub struct FormattedDateTimePattern<'a> { + pattern: DateTimePatternBorrowed<'a>, datetime: ExtractedDateTimeInput, - names: RawDateTimeNamesBorrowed<'l>, + names: RawDateTimeNamesBorrowed<'a>, } -impl<'l> Writeable for FormattedDateTimePattern<'l> { +impl<'a> Writeable for FormattedDateTimePattern<'a> { fn write_to(&self, sink: &mut W) -> fmt::Result { let loc_datetime = DateTimeInputWithWeekConfig::new(&self.datetime, self.names.week_calculator); + let Some(fixed_decimal_formatter) = self.names.fixed_decimal_formatter else { + // TODO(#4340): Make the FixedDecimalFormatter optional + icu_provider::_internal::log::warn!("FixedDecimalFormatter not loaded"); + return Err(core::fmt::Error); + }; write_pattern( - self.pattern, + self.pattern.0.items.iter(), + self.pattern.0.metadata, Some(&self.names), Some(&self.names), &loc_datetime, - self.names.fixed_decimal_formatter, + fixed_decimal_formatter, sink, ) .map_err(|_e| { @@ -1128,11 +1184,7 @@ impl<'l> Writeable for FormattedDateTimePattern<'l> { // TODO(#489): Implement writeable_length_hint } -impl<'l> fmt::Display for FormattedDateTimePattern<'l> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.write_to(f) - } -} +writeable::impl_display_with_writeable!(FormattedDateTimePattern<'_>); impl<'data> DateSymbols<'data> for RawDateTimeNamesBorrowed<'data> { fn get_symbol_for_month( @@ -1212,7 +1264,7 @@ impl<'data> DateSymbols<'data> for RawDateTimeNamesBorrowed<'data> { .weekday_names .get_with_length(field_symbol, field_length) .ok_or(Error::MissingNames(field))?; - let day_usize = day as usize; + let day_usize = (day as usize) % 7; weekday_symbols .symbols .get(day_usize) @@ -1276,7 +1328,6 @@ impl<'data> TimeSymbols for RawDateTimeNamesBorrowed<'data> { #[cfg(feature = "compiled_data")] mod tests { use super::*; - use crate::pattern::reference; use icu_calendar::{DateTime, Gregorian}; use icu_locid::locale; use writeable::assert_writeable_eq; @@ -1303,10 +1354,9 @@ mod tests { .unwrap() .load_day_period_names(&crate::provider::Baked, FieldLength::Abbreviated) .unwrap(); - let reference_pattern: reference::Pattern = "'It is' E, MMMM d, y GGGGG 'at' hh:mm a'!'" + let pattern: DateTimePattern = "'It is' E, MMMM d, y GGGGG 'at' hh:mm a'!'" .parse() .unwrap(); - let pattern: Pattern = (&reference_pattern).into(); let datetime = DateTime::try_new_gregorian_datetime(2023, 10, 25, 15, 0, 55).unwrap(); let formatted_pattern = names.with_pattern(&pattern).format(&datetime); @@ -1363,8 +1413,7 @@ mod tests { names .load_year_names(&crate::provider::Baked, field_length) .unwrap(); - let reference_pattern: reference::Pattern = pattern.parse().unwrap(); - let pattern: Pattern = (&reference_pattern).into(); + let pattern: DateTimePattern = pattern.parse().unwrap(); let datetime = DateTime::try_new_gregorian_datetime(2023, 11, 17, 13, 41, 28).unwrap(); let formatted_pattern = names.with_pattern(&pattern).format(&datetime); @@ -1435,8 +1484,7 @@ mod tests { names .load_month_names(&crate::provider::Baked, field_symbol, field_length) .unwrap(); - let reference_pattern: reference::Pattern = pattern.parse().unwrap(); - let pattern: Pattern = (&reference_pattern).into(); + let pattern: DateTimePattern = pattern.parse().unwrap(); let datetime = DateTime::try_new_gregorian_datetime(2023, 11, 17, 13, 41, 28).unwrap(); let formatted_pattern = names.with_pattern(&pattern).format(&datetime); @@ -1554,8 +1602,7 @@ mod tests { names .load_weekday_names(&crate::provider::Baked, field_symbol, field_length) .unwrap(); - let reference_pattern: reference::Pattern = pattern.parse().unwrap(); - let pattern: Pattern = (&reference_pattern).into(); + let pattern: DateTimePattern = pattern.parse().unwrap(); let datetime = DateTime::try_new_gregorian_datetime(2023, 11, 17, 13, 41, 28).unwrap(); let formatted_pattern = names.with_pattern(&pattern).format(&datetime); @@ -1637,8 +1684,7 @@ mod tests { names .load_day_period_names(&crate::provider::Baked, field_length) .unwrap(); - let reference_pattern: reference::Pattern = pattern.parse().unwrap(); - let pattern: Pattern = (&reference_pattern).into(); + let pattern: DateTimePattern = pattern.parse().unwrap(); let datetime = DateTime::try_new_gregorian_datetime(2023, 11, 17, 13, 41, 28).unwrap(); let formatted_pattern = names.with_pattern(&pattern).format(&datetime); diff --git a/components/datetime/src/format/zoned_datetime.rs b/components/datetime/src/format/zoned_datetime.rs index 22a787c4973..beadd87b09e 100644 --- a/components/datetime/src/format/zoned_datetime.rs +++ b/components/datetime/src/format/zoned_datetime.rs @@ -10,7 +10,8 @@ use crate::input::{ DateTimeInput, DateTimeInputWithWeekConfig, ExtractedDateTimeInput, ExtractedTimeZoneInput, LocalizedDateTimeInput, TimeZoneInput, }; -use crate::pattern::{runtime, PatternItem}; +use crate::pattern::runtime::PatternMetadata; +use crate::pattern::PatternItem; use crate::{raw, FormattedTimeZone}; use core::fmt; use writeable::Writeable; @@ -81,7 +82,7 @@ where loop { match iter.next() { Some(PatternItem::Field(field)) => write_field( - pattern, + pattern.metadata, field, iter.peek(), zoned_datetime_format, @@ -97,7 +98,7 @@ where } fn write_field( - pattern: &runtime::Pattern, + pattern_metadata: PatternMetadata, field: fields::Field, next_item: Option<&PatternItem>, zoned_datetime_format: &raw::ZonedDateTimeFormatter, @@ -129,7 +130,7 @@ where } .write_to(w)?, _ => datetime::write_field( - pattern, + pattern_metadata, field, next_item, date_symbols, diff --git a/components/datetime/src/lib.rs b/components/datetime/src/lib.rs index fd7d259436a..55bc1092ff0 100644 --- a/components/datetime/src/lib.rs +++ b/components/datetime/src/lib.rs @@ -134,9 +134,14 @@ mod any; mod calendar; mod datetime; mod error; +mod external_loaders; pub mod fields; mod format; pub mod input; +#[cfg(feature = "experimental")] +pub mod neo; +#[cfg(feature = "experimental")] +pub mod neo_pattern; pub mod options; #[doc(hidden)] pub mod pattern; diff --git a/components/datetime/src/neo.rs b/components/datetime/src/neo.rs new file mode 100644 index 00000000000..773345986d4 --- /dev/null +++ b/components/datetime/src/neo.rs @@ -0,0 +1,817 @@ +// 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 ). + +//! High-level entrypoints for Neo DateTime Formatter + +use crate::external_loaders::*; +use crate::format::neo::*; +use crate::input::ExtractedDateTimeInput; +use crate::input::{DateInput, DateTimeInput, IsoTimeInput}; +use crate::neo_pattern::DateTimePattern; +use crate::options::length; +use crate::provider::neo::*; +use crate::raw::neo::*; +use crate::CldrCalendar; +use crate::Error; +use core::fmt; +use core::marker::PhantomData; +use icu_calendar::provider::WeekDataV2Marker; +use icu_decimal::provider::DecimalSymbolsV1Marker; +use icu_provider::prelude::*; +use writeable::Writeable; + +/// Helper macro for generating any/buffer constructors in this file. +macro_rules! gen_any_buffer_constructors_with_external_loader { + ($compiled_fn:ident, $any_fn:ident, $buffer_fn:ident, $internal_fn:ident, $($arg:ident: $ty:path),+) => { + #[doc = icu_provider::gen_any_buffer_unstable_docs!(ANY, Self::$compiled_fn)] + pub fn $any_fn

( + provider: &P, + locale: &DataLocale, + $($arg: $ty),+ + ) -> Result + where + P: AnyProvider + ?Sized, + { + Self::$internal_fn( + &provider.as_downcasting(), + &ExternalLoaderAny(provider), + locale, + $($arg),+ + ) + } + #[doc = icu_provider::gen_any_buffer_unstable_docs!(BUFFER, Self::$compiled_fn)] + #[cfg(feature = "serde")] + pub fn $buffer_fn

( + provider: &P, + locale: &DataLocale, + $($arg: $ty),+ + ) -> Result + where + P: BufferProvider + ?Sized, + { + Self::$internal_fn( + &provider.as_deserializing(), + &ExternalLoaderBuffer(provider), + locale, + $($arg),+ + ) + } + }; +} + +///

+/// 🚧 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 +/// of the icu meta-crate. Use with caution. +/// #3347 +///
+#[derive(Debug)] +pub struct TypedNeoDateFormatter { + selection: DatePatternSelectionData, + names: RawDateTimeNames, + _calendar: PhantomData, +} + +impl TypedNeoDateFormatter { + /// Creates a [`TypedNeoDateFormatter`] for a date length. + /// + /// # Examples + /// + /// ``` + /// use icu::calendar::Date; + /// use icu::calendar::Gregorian; + /// use icu::datetime::neo::TypedNeoDateFormatter; + /// use icu::datetime::options::length; + /// use icu::locid::locale; + /// use writeable::assert_writeable_eq; + /// + /// let formatter = TypedNeoDateFormatter::::try_new_with_length( + /// &locale!("es-MX").into(), + /// length::Date::Full, + /// ) + /// .unwrap(); + /// + /// assert_writeable_eq!( + /// formatter.format(&Date::try_new_gregorian_date(2023, 12, 20).unwrap()), + /// "miércoles, 20 de diciembre de 2023" + /// ); + /// ``` + #[cfg(feature = "compiled_data")] + pub fn try_new_with_length(locale: &DataLocale, length: length::Date) -> Result + where + crate::provider::Baked: DataProvider + + DataProvider + + DataProvider + + DataProvider, + { + Self::try_new_with_length_internal( + &crate::provider::Baked, + &ExternalLoaderCompiledData, + locale, + length, + ) + } + + gen_any_buffer_constructors_with_external_loader!( + try_new_with_length, + try_new_with_length_with_any_provider, + try_new_with_length_with_buffer_provider, + try_new_with_length_internal, + length: length::Date + ); + + #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_with_length)] + pub fn try_new_with_length_unstable

( + provider: &P, + locale: &DataLocale, + length: length::Date, + ) -> Result + where + P: ?Sized + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider, + { + Self::try_new_with_length_internal( + provider, + &ExternalLoaderUnstable(provider), + locale, + length, + ) + } + + fn try_new_with_length_internal( + provider: &P, + loader: &L, + locale: &DataLocale, + length: length::Date, + ) -> Result + where + P: ?Sized + + DataProvider + + DataProvider + + DataProvider + + DataProvider, + L: FixedDecimalFormatterLoader + WeekCalculatorLoader, + { + let selection = DatePatternSelectionData::try_new_with_length::( + provider, locale, length, + )?; + let mut names = RawDateTimeNames::new_without_fixed_decimal_formatter(); + names.load_for_pattern::( + Some(provider), // year + Some(provider), // month + Some(provider), // weekday + None::<&PhantomProvider>, // day period + Some(loader), // fixed decimal formatter + Some(loader), // week calculator + locale, + selection.pattern_items_for_data_loading(), + )?; + Ok(Self { + selection, + names, + _calendar: PhantomData, + }) + } + + /// Formats a date. + /// + /// For an example, see [`TypedNeoDateFormatter`]. + pub fn format(&self, date: &T) -> FormattedNeoDate + where + T: DateInput, + { + let datetime = ExtractedDateTimeInput::extract_from_date(date); + FormattedNeoDate { + pattern: self.selection.select(&datetime), + datetime, + names: self.names.as_borrowed(), + } + } +} + +///

+/// 🚧 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 +/// of the icu meta-crate. Use with caution. +/// #3347 +///
+#[derive(Debug)] +pub struct FormattedNeoDate<'a> { + pattern: DatePatternDataBorrowed<'a>, + datetime: ExtractedDateTimeInput, + names: RawDateTimeNamesBorrowed<'a>, +} + +impl<'a> Writeable for FormattedNeoDate<'a> { + fn write_to(&self, sink: &mut W) -> fmt::Result { + DateTimeWriter { + datetime: &self.datetime, + names: self.names, + pattern_items: self.pattern.iter_items(), + pattern_metadata: self.pattern.metadata(), + } + .write_to(sink) + } + + // TODO(#489): Implement writeable_length_hint +} + +writeable::impl_display_with_writeable!(FormattedNeoDate<'_>); + +impl<'a> FormattedNeoDate<'a> { + /// Gets the pattern used in this formatted value. + pub fn pattern(&self) -> DateTimePattern { + self.pattern.to_pattern() + } +} + +///
+/// 🚧 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 +/// of the icu meta-crate. Use with caution. +/// #3347 +///
+#[derive(Debug)] +pub struct NeoTimeFormatter { + selection: TimePatternSelectionData, + names: RawDateTimeNames, +} + +impl NeoTimeFormatter { + /// Creates a [`NeoTimeFormatter`] for a time length. + /// + /// # Examples + /// + /// ``` + /// use icu::calendar::types::Time; + /// use icu::datetime::neo::NeoTimeFormatter; + /// use icu::datetime::options::length; + /// use icu::locid::locale; + /// use writeable::assert_writeable_eq; + /// + /// let formatter = NeoTimeFormatter::try_new_with_length( + /// &locale!("es-MX").into(), + /// length::Time::Medium, + /// ) + /// .unwrap(); + /// + /// assert_writeable_eq!( + /// formatter.format(&Time::try_new(14, 48, 58, 0).unwrap()), + /// "2:48:58 p.m." + /// ); + /// ``` + #[cfg(feature = "compiled_data")] + pub fn try_new_with_length(locale: &DataLocale, length: length::Time) -> Result + where + crate::provider::Baked: DataProvider, + { + Self::try_new_with_length_internal( + &crate::provider::Baked, + &ExternalLoaderCompiledData, + locale, + length, + ) + } + + gen_any_buffer_constructors_with_external_loader!( + try_new_with_length, + try_new_with_length_with_any_provider, + try_new_with_length_with_buffer_provider, + try_new_with_length_internal, + length: length::Time + ); + + #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_with_length)] + pub fn try_new_with_length_unstable

( + provider: &P, + locale: &DataLocale, + length: length::Time, + ) -> Result + where + P: ?Sized + + DataProvider + + DataProvider + + DataProvider, + { + Self::try_new_with_length_internal( + provider, + &ExternalLoaderUnstable(provider), + locale, + length, + ) + } + + fn try_new_with_length_internal( + provider: &P, + loader: &L, + locale: &DataLocale, + length: length::Time, + ) -> Result + where + P: ?Sized + DataProvider + DataProvider, + L: FixedDecimalFormatterLoader, + { + let selection = TimePatternSelectionData::try_new_with_length(provider, locale, length)?; + let mut names = RawDateTimeNames::new_without_fixed_decimal_formatter(); + // NOTE: The Gregorian types below are placeholders only. They are not actually linked. + names.load_for_pattern::( + None::<&PhantomProvider>, // year + None::<&PhantomProvider>, // month + None::<&PhantomProvider>, // weekday + Some(provider), // day period + Some(loader), // fixed decimal formatter + None::<&PhantomLoader>, // week calculator + locale, + selection.pattern_items_for_data_loading(), + )?; + Ok(Self { selection, names }) + } + + /// Formats a time of day. + /// + /// For an example, see [`NeoTimeFormatter`]. + pub fn format(&self, time: &T) -> FormattedNeoTime + where + T: IsoTimeInput, + { + let datetime = ExtractedDateTimeInput::extract_from_time(time); + FormattedNeoTime { + pattern: self.selection.select(&datetime), + datetime, + names: self.names.as_borrowed(), + } + } +} + +///

+/// 🚧 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 +/// of the icu meta-crate. Use with caution. +/// #3347 +///
+#[derive(Debug)] +pub struct FormattedNeoTime<'a> { + pattern: TimePatternDataBorrowed<'a>, + datetime: ExtractedDateTimeInput, + names: RawDateTimeNamesBorrowed<'a>, +} + +impl<'a> Writeable for FormattedNeoTime<'a> { + fn write_to(&self, sink: &mut W) -> fmt::Result { + DateTimeWriter { + datetime: &self.datetime, + names: self.names, + pattern_items: self.pattern.iter_items(), + pattern_metadata: self.pattern.metadata(), + } + .write_to(sink) + } + + // TODO(#489): Implement writeable_length_hint +} + +writeable::impl_display_with_writeable!(FormattedNeoTime<'_>); + +impl<'a> FormattedNeoTime<'a> { + /// Gets the pattern used in this formatted value. + pub fn pattern(&self) -> DateTimePattern { + self.pattern.to_pattern() + } +} + +///
+/// 🚧 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 +/// of the icu meta-crate. Use with caution. +/// #3347 +///
+#[derive(Debug)] +pub struct TypedNeoDateTimeFormatter { + selection: DateTimePatternSelectionData, + names: RawDateTimeNames, + _calendar: PhantomData, +} + +impl TypedNeoDateTimeFormatter { + /// Creates a [`TypedNeoDateTimeFormatter`] for a date length. + /// + /// # Examples + /// + /// ``` + /// use icu::calendar::DateTime; + /// use icu::calendar::Gregorian; + /// use icu::datetime::neo::TypedNeoDateTimeFormatter; + /// use icu::datetime::options::length; + /// use icu::locid::locale; + /// use writeable::assert_writeable_eq; + /// + /// let formatter = + /// TypedNeoDateTimeFormatter::::try_new_with_date_length( + /// &locale!("es-MX").into(), + /// length::Date::Full, + /// ) + /// .unwrap(); + /// + /// assert_writeable_eq!( + /// formatter.format( + /// &DateTime::try_new_gregorian_datetime(2023, 12, 20, 14, 48, 58) + /// .unwrap() + /// ), + /// "miércoles, 20 de diciembre de 2023" + /// ); + /// ``` + #[cfg(feature = "compiled_data")] + pub fn try_new_with_date_length( + locale: &DataLocale, + length: length::Date, + ) -> Result + where + crate::provider::Baked: DataProvider + + DataProvider + + DataProvider + + DataProvider, + { + Self::try_new_with_date_length_internal( + &crate::provider::Baked, + &ExternalLoaderCompiledData, + locale, + length, + ) + } + + gen_any_buffer_constructors_with_external_loader!( + try_new_with_date_length, + try_new_with_date_length_with_any_provider, + try_new_with_date_length_with_buffer_provider, + try_new_with_date_length_internal, + length: length::Date + ); + + #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_with_date_length)] + pub fn try_new_with_date_length_unstable

( + provider: &P, + locale: &DataLocale, + length: length::Date, + ) -> Result + where + P: ?Sized + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider, + { + Self::try_new_with_date_length_internal( + provider, + &ExternalLoaderUnstable(provider), + locale, + length, + ) + } + + fn try_new_with_date_length_internal( + provider: &P, + loader: &L, + locale: &DataLocale, + length: length::Date, + ) -> Result + where + P: ?Sized + + DataProvider + + DataProvider + + DataProvider + + DataProvider, + L: FixedDecimalFormatterLoader + WeekCalculatorLoader, + { + let date_formatter = TypedNeoDateFormatter::::try_new_with_length_internal( + provider, loader, locale, length, + )?; + Ok(Self { + selection: DateTimePatternSelectionData::Date(date_formatter.selection), + names: date_formatter.names, + _calendar: PhantomData, + }) + } + + /// Creates a [`TypedNeoDateTimeFormatter`] for a time length. + /// + /// # Examples + /// + /// ``` + /// use icu::calendar::DateTime; + /// use icu::calendar::Gregorian; + /// use icu::datetime::neo::TypedNeoDateTimeFormatter; + /// use icu::datetime::options::length; + /// use icu::locid::locale; + /// use writeable::assert_writeable_eq; + /// + /// let formatter = + /// TypedNeoDateTimeFormatter::::try_new_with_time_length( + /// &locale!("es-MX").into(), + /// length::Time::Medium, + /// ) + /// .unwrap(); + /// + /// assert_writeable_eq!( + /// formatter.format( + /// &DateTime::try_new_gregorian_datetime(2023, 12, 20, 14, 48, 58) + /// .unwrap() + /// ), + /// "2:48:58 p.m." + /// ); + /// ``` + #[cfg(feature = "compiled_data")] + pub fn try_new_with_time_length( + locale: &DataLocale, + length: length::Time, + ) -> Result + where + crate::provider::Baked: + DataProvider + DataProvider, + { + Self::try_new_with_time_length_internal( + &crate::provider::Baked, + &ExternalLoaderCompiledData, + locale, + length, + ) + } + + gen_any_buffer_constructors_with_external_loader!( + try_new_with_time_length, + try_new_with_time_length_with_any_provider, + try_new_with_time_length_with_buffer_provider, + try_new_with_time_length_internal, + length: length::Time + ); + + #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_with_time_length)] + pub fn try_new_with_time_length_unstable

( + provider: &P, + locale: &DataLocale, + length: length::Time, + ) -> Result + where + P: ?Sized + + DataProvider + + DataProvider + + DataProvider, + { + Self::try_new_with_time_length_internal( + provider, + &ExternalLoaderUnstable(provider), + locale, + length, + ) + } + + fn try_new_with_time_length_internal( + provider: &P, + loader: &L, + locale: &DataLocale, + length: length::Time, + ) -> Result + where + P: ?Sized + DataProvider + DataProvider, + L: FixedDecimalFormatterLoader, + { + let time_formatter = + NeoTimeFormatter::try_new_with_length_internal(provider, loader, locale, length)?; + Ok(Self { + selection: DateTimePatternSelectionData::Time(time_formatter.selection), + names: time_formatter.names, + _calendar: PhantomData, + }) + } + + /// Creates a [`TypedNeoDateTimeFormatter`] for date and time lengths. + /// + /// # Examples + /// + /// ``` + /// use icu::calendar::DateTime; + /// use icu::calendar::Gregorian; + /// use icu::datetime::neo::TypedNeoDateTimeFormatter; + /// use icu::datetime::options::length; + /// use icu::locid::locale; + /// use writeable::assert_writeable_eq; + /// + /// let formatter = + /// TypedNeoDateTimeFormatter::::try_new_with_lengths( + /// &locale!("es-MX").into(), + /// length::Date::Full, + /// length::Time::Medium, + /// ) + /// .unwrap(); + /// + /// assert_writeable_eq!( + /// formatter.format( + /// &DateTime::try_new_gregorian_datetime(2023, 12, 20, 14, 48, 58) + /// .unwrap() + /// ), + /// "miércoles, 20 de diciembre de 2023, 2:48:58 p.m." + /// ); + /// ``` + #[cfg(feature = "compiled_data")] + pub fn try_new_with_lengths( + locale: &DataLocale, + date_length: length::Date, + time_length: length::Time, + ) -> Result + where + crate::provider::Baked: DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider, + { + Self::try_new_with_lengths_internal( + &crate::provider::Baked, + &ExternalLoaderCompiledData, + locale, + date_length, + time_length, + ) + } + + gen_any_buffer_constructors_with_external_loader!( + try_new_with_lengths, + try_new_with_lengths_with_any_provider, + try_new_with_lengths_with_buffer_provider, + try_new_with_lengths_internal, + date_length: length::Date, + time_length: length::Time + ); + + #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_with_lengths)] + pub fn try_new_with_lengths_unstable

( + provider: &P, + locale: &DataLocale, + date_length: length::Date, + time_length: length::Time, + ) -> Result + where + P: ?Sized + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider, + { + Self::try_new_with_lengths_internal( + provider, + &ExternalLoaderUnstable(provider), + locale, + date_length, + time_length, + ) + } + + fn try_new_with_lengths_internal( + provider: &P, + loader: &L, + locale: &DataLocale, + date_length: length::Date, + time_length: length::Time, + ) -> Result + where + P: ?Sized + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider, + L: FixedDecimalFormatterLoader + WeekCalculatorLoader, + { + let selection = DateTimeGluePatternSelectionData::try_new_with_lengths::< + C::DatePatternV1Marker, + _, + >(provider, locale, date_length, time_length)?; + let mut names = RawDateTimeNames::new_without_fixed_decimal_formatter(); + names.load_for_pattern::( + Some(provider), // year + Some(provider), // month + Some(provider), // weekday + Some(provider), // day period + Some(loader), // fixed decimal formatter + Some(loader), // week calculator + locale, + selection.pattern_items_for_data_loading(), + )?; + Ok(Self { + selection: DateTimePatternSelectionData::DateTimeGlue(selection), + names, + _calendar: PhantomData, + }) + } + + /// Creates a [`TypedNeoDateTimeFormatter`] from [`DateTimeFormatterOptions`]. + /// + /// Experimental because [`DateTimeFormatterOptions`] might go away or be changed in neo. + /// + ///

+ /// 🚧 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 + /// of the icu meta-crate. Use with caution. + /// #3347 + /// + /// [`DateTimeFormatterOptions`]: crate::DateTimeFormatterOptions + ///
+ #[cfg(all(feature = "compiled_data", feature = "experimental"))] + pub fn try_new( + locale: &DataLocale, + options: crate::DateTimeFormatterOptions, + ) -> Result + where + crate::provider::Baked: DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider + + DataProvider, + { + use crate::DateTimeFormatterOptions; + match options { + DateTimeFormatterOptions::Length(length::Bag { + date: Some(date), + time: Some(time), + }) => Self::try_new_with_lengths(locale, date, time), + DateTimeFormatterOptions::Length(length::Bag { + date: Some(date), + time: None, + }) => Self::try_new_with_date_length(locale, date), + DateTimeFormatterOptions::Length(length::Bag { + date: None, + time: Some(time), + }) => Self::try_new_with_time_length(locale, time), + _ => Err(Error::UnsupportedOptions), + } + } + + /// Formats a date and time of day. + /// + /// For an example, see [`TypedNeoDateTimeFormatter`]. + pub fn format(&self, datetime: &T) -> FormattedNeoDateTime + where + T: DateTimeInput, + { + let datetime = ExtractedDateTimeInput::extract_from(datetime); + FormattedNeoDateTime { + pattern: self.selection.select(&datetime), + datetime, + names: self.names.as_borrowed(), + } + } +} + +///
+/// 🚧 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 +/// of the icu meta-crate. Use with caution. +/// #3347 +///
+#[derive(Debug)] +pub struct FormattedNeoDateTime<'a> { + pattern: DateTimePatternDataBorrowed<'a>, + datetime: ExtractedDateTimeInput, + names: RawDateTimeNamesBorrowed<'a>, +} + +impl<'a> Writeable for FormattedNeoDateTime<'a> { + fn write_to(&self, sink: &mut W) -> fmt::Result { + DateTimeWriter { + datetime: &self.datetime, + names: self.names, + pattern_items: self.pattern.iter_items(), + pattern_metadata: self.pattern.metadata(), + } + .write_to(sink) + } + + // TODO(#489): Implement writeable_length_hint +} + +writeable::impl_display_with_writeable!(FormattedNeoDateTime<'_>); + +impl<'a> FormattedNeoDateTime<'a> { + /// Gets the pattern used in this formatted value. + pub fn pattern(&self) -> DateTimePattern { + self.pattern.to_pattern() + } +} diff --git a/components/datetime/src/neo_pattern.rs b/components/datetime/src/neo_pattern.rs new file mode 100644 index 00000000000..0af447a3fcd --- /dev/null +++ b/components/datetime/src/neo_pattern.rs @@ -0,0 +1,100 @@ +// 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 ). + +//! Temporary module for neo datetime patterns + +use core::str::FromStr; + +use crate::pattern::{runtime, PatternError, PatternItem}; + +/// A pattern for formatting a datetime in a calendar. +/// +/// Most clients should use [`DateTimeFormatter`] instead of directly +/// formatting with patterns. +/// +/// There are two ways to make one of these: +/// +/// 1. From a custom pattern string: [`DateTimePattern::try_from_pattern_str`] +/// 2. From a formatted datetime: [`FormattedNeoDateTime::pattern`] +/// +/// # Examples +/// +/// Create a pattern from a custom string and compare it to one from data: +/// +/// ``` +/// use icu::calendar::DateTime; +/// use icu::calendar::Gregorian; +/// use icu::datetime::neo::TypedNeoDateTimeFormatter; +/// use icu::datetime::neo_pattern::DateTimePattern; +/// use icu::datetime::options::length; +/// use icu::locid::locale; +/// +/// let custom_pattern = +/// DateTimePattern::try_from_pattern_str("d MMM y").unwrap(); +/// +/// let data_pattern = +/// TypedNeoDateTimeFormatter::::try_new_with_date_length( +/// &locale!("es-MX").into(), +/// length::Date::Medium, +/// ) +/// .unwrap() +/// // The pattern can depend on the datetime being formatted. +/// // For this example, we'll choose the local Unix epoch. +/// .format(&DateTime::local_unix_epoch().to_calendar(Gregorian)) +/// .pattern(); +/// +/// assert_eq!(custom_pattern, data_pattern); +/// ``` +/// +/// [`DateTimeFormatter`]: crate::DateTimeFormatter +/// [`FormattedNeoDateTime::pattern`]: crate::neo::FormattedNeoDateTime::pattern +#[derive(Debug)] +pub struct DateTimePattern { + pattern: runtime::Pattern<'static>, +} + +impl DateTimePattern { + /// Creates a [`DateTimePattern`] from a pattern string. + /// + /// For more details on the syntax, see UTS 35: + /// + pub fn try_from_pattern_str(pattern_str: &str) -> Result { + let pattern = runtime::Pattern::from_str(pattern_str)?; + Ok(Self { pattern }) + } + + #[doc(hidden)] // TODO(#4467): Internal API + pub fn from_runtime_pattern(pattern: runtime::Pattern<'static>) -> Self { + Self { pattern } + } + + pub(crate) fn iter_items(&self) -> impl Iterator + '_ { + self.pattern.items.iter() + } + + pub(crate) fn as_borrowed(&self) -> DateTimePatternBorrowed { + DateTimePatternBorrowed(&self.pattern) + } +} + +impl FromStr for DateTimePattern { + type Err = PatternError; + fn from_str(s: &str) -> Result { + Self::try_from_pattern_str(s) + } +} + +// Check equality on the borrowed variant since it flattens the difference +// between the three `Single` pattern variants, which is not public-facing +impl PartialEq for DateTimePattern { + fn eq(&self, other: &Self) -> bool { + self.as_borrowed().eq(&other.as_borrowed()) + } +} + +impl Eq for DateTimePattern {} + +// Not clear if this needs to be public. For now, make it private. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) struct DateTimePatternBorrowed<'a>(pub(crate) &'a runtime::Pattern<'a>); diff --git a/components/datetime/src/pattern/item/ule.rs b/components/datetime/src/pattern/item/ule.rs index 96970705d61..cb1d2a71296 100644 --- a/components/datetime/src/pattern/item/ule.rs +++ b/components/datetime/src/pattern/item/ule.rs @@ -220,7 +220,7 @@ impl GenericPatternItemULE { /// Converts this [`GenericPatternItemULE`] to a [`PatternItemULE`] /// (if a Literal) or returns the placeholder value. - #[allow(dead_code)] // #4415 + #[cfg(feature = "experimental")] #[inline] pub(crate) fn as_pattern_item_ule(&self) -> Result<&PatternItemULE, u8> { if Self::determine_field_from_u8(self.0[0]) { @@ -266,11 +266,9 @@ unsafe impl ULE for GenericPatternItemULE { } } -impl AsULE for GenericPatternItem { - type ULE = GenericPatternItemULE; - +impl GenericPatternItem { #[inline] - fn to_unaligned(self) -> Self::ULE { + pub(crate) const fn to_unaligned_const(self) -> ::ULE { match self { Self::Placeholder(idx) => GenericPatternItemULE([0b1000_0000, 0x00, idx]), Self::Literal(ch) => { @@ -280,6 +278,15 @@ impl AsULE for GenericPatternItem { } } } +} + +impl AsULE for GenericPatternItem { + type ULE = GenericPatternItemULE; + + #[inline] + fn to_unaligned(self) -> Self::ULE { + self.to_unaligned_const() + } #[inline] fn from_unaligned(unaligned: Self::ULE) -> Self { diff --git a/components/datetime/src/pattern/runtime/generic.rs b/components/datetime/src/pattern/runtime/generic.rs index 7a8c683bf0f..74e78eafa25 100644 --- a/components/datetime/src/pattern/runtime/generic.rs +++ b/components/datetime/src/pattern/runtime/generic.rs @@ -23,6 +23,17 @@ pub struct GenericPattern<'data> { pub items: ZeroVec<'data, GenericPatternItem>, } +/// A ZeroSlice containing a 0 and a 1 placeholder +#[cfg(feature = "experimental")] +pub(crate) const ZERO_ONE_SLICE: &zerovec::ZeroSlice = zerovec::zeroslice!( + GenericPatternItem; + GenericPatternItem::to_unaligned_const; + [ + GenericPatternItem::Placeholder(0), + GenericPatternItem::Placeholder(1), + ] +); + impl<'data> GenericPattern<'data> { /// The function allows for creation of new DTF pattern from a generic pattern /// and replacement patterns. diff --git a/components/datetime/src/pattern/runtime/mod.rs b/components/datetime/src/pattern/runtime/mod.rs index 4e5ec915661..b1a1d150469 100644 --- a/components/datetime/src/pattern/runtime/mod.rs +++ b/components/datetime/src/pattern/runtime/mod.rs @@ -15,6 +15,7 @@ mod pattern; mod plural; pub use generic::GenericPattern; -pub use pattern::Pattern; -pub use pattern::PatternMetadata; +#[cfg(feature = "experimental")] +pub(crate) use generic::ZERO_ONE_SLICE; +pub use pattern::{Pattern, PatternMetadata, PatternULE}; pub use plural::{PatternPlurals, PluralPattern}; diff --git a/components/datetime/src/pattern/runtime/pattern.rs b/components/datetime/src/pattern/runtime/pattern.rs index 26278fa0932..860cc7e7f83 100644 --- a/components/datetime/src/pattern/runtime/pattern.rs +++ b/components/datetime/src/pattern/runtime/pattern.rs @@ -2,6 +2,8 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). +#![allow(clippy::exhaustive_structs)] // part of data struct and internal API + use super::super::{reference, PatternError, PatternItem, TimeGranularity}; use alloc::vec::Vec; use core::str::FromStr; @@ -14,7 +16,6 @@ use zerovec::ZeroVec; derive(databake::Bake), databake(path = icu_datetime::pattern::runtime), )] -#[allow(clippy::exhaustive_structs)] // part of data struct #[zerovec::make_varule(PatternULE)] #[zerovec::skip_derive(Ord)] pub struct Pattern<'data> { @@ -41,6 +42,17 @@ impl PatternMetadata { Self::from_time_granularity(time_granularity) } + /// Merges the metadata from a date pattern and a time pattern into one. + #[cfg(feature = "experimental")] + #[inline] + pub(crate) fn merge_date_and_time_metadata( + _date: PatternMetadata, + time: PatternMetadata, + ) -> PatternMetadata { + // Currently we only have time granularity so we ignore the date metadata. + time + } + #[inline] #[doc(hidden)] // databake pub const fn from_time_granularity(time_granularity: TimeGranularity) -> Self { @@ -48,6 +60,7 @@ impl PatternMetadata { } #[cfg(any(feature = "datagen", feature = "experimental"))] + #[inline] pub(crate) fn set_time_granularity(&mut self, time_granularity: TimeGranularity) { self.0 = time_granularity.ordinal(); } diff --git a/components/datetime/src/provider/neo.rs b/components/datetime/src/provider/neo.rs index bc02c110cbf..e77d4379046 100644 --- a/components/datetime/src/provider/neo.rs +++ b/components/datetime/src/provider/neo.rs @@ -461,6 +461,12 @@ pub struct DatePatternV1<'data> { pub pattern: runtime::Pattern<'data>, } +// TODO: We may need to support plural forms here. Something like +// pub enum NeoPatternPlurals<'data> { +// SingleDate(runtime::Pattern<'data>), +// WeekPlurals(ZeroMap<'data, PluralCategory, runtime::PatternULE>), +// } + /// The default per-length patterns associated with times /// /// This uses an auxiliary subtag for length. See [`DatePatternV1`] for more information on the scheme. @@ -528,8 +534,3 @@ pub(crate) struct ErasedDatePatternV1Marker; impl DataMarker for ErasedDatePatternV1Marker { type Yokeable = DatePatternV1<'static>; } - -pub(crate) struct ErasedTimePatternV1Marker; -impl DataMarker for ErasedTimePatternV1Marker { - type Yokeable = TimePatternV1<'static>; -} diff --git a/components/datetime/src/raw/mod.rs b/components/datetime/src/raw/mod.rs index 91cf1e5cc5b..2a140a48004 100644 --- a/components/datetime/src/raw/mod.rs +++ b/components/datetime/src/raw/mod.rs @@ -4,6 +4,8 @@ /// Untyped versions of TypedDateTimeFormatter and TypedZonedDateTimeFormatter mod datetime; +#[cfg(feature = "experimental")] +pub(crate) mod neo; mod zoned_datetime; pub(crate) use datetime::{DateFormatter, DateTimeFormatter, TimeFormatter}; diff --git a/components/datetime/src/raw/neo.rs b/components/datetime/src/raw/neo.rs new file mode 100644 index 00000000000..e6d057a77ad --- /dev/null +++ b/components/datetime/src/raw/neo.rs @@ -0,0 +1,416 @@ +// 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 core::fmt; + +use crate::format::datetime::write_pattern; +use crate::format::neo::*; +use crate::input::{DateTimeInputWithWeekConfig, ExtractedDateTimeInput}; +use crate::neo_pattern::DateTimePattern; +use crate::options::length; +use crate::pattern::runtime::PatternMetadata; +use crate::pattern::{runtime, PatternItem}; +use crate::provider::neo::*; +use crate::Error; +use icu_provider::prelude::*; +use zerovec::ule::AsULE; + +#[derive(Debug)] +pub(crate) enum DatePatternSelectionData { + SingleDate(DataPayload), + #[allow(dead_code)] // TODO(#4478) + OptionalEra { + with_era: DataPayload, + without_era: DataPayload, + }, +} + +#[derive(Debug, Copy, Clone)] +pub(crate) enum DatePatternDataBorrowed<'a> { + Resolved(&'a DatePatternV1<'a>), +} + +#[derive(Debug)] +pub(crate) enum TimePatternSelectionData { + SingleTime(DataPayload), +} + +#[derive(Debug, Copy, Clone)] +pub(crate) enum TimePatternDataBorrowed<'a> { + Resolved(&'a TimePatternV1<'a>), +} + +#[derive(Debug)] +pub(crate) struct DateTimeGluePatternSelectionData { + date: DatePatternSelectionData, + time: TimePatternSelectionData, + glue: DataPayload, +} + +#[derive(Debug)] +pub(crate) enum DateTimePatternSelectionData { + Date(DatePatternSelectionData), + Time(TimePatternSelectionData), + DateTimeGlue(DateTimeGluePatternSelectionData), +} + +#[derive(Debug, Copy, Clone)] +pub(crate) enum DateTimePatternDataBorrowed<'a> { + Date(DatePatternDataBorrowed<'a>), + Time(TimePatternDataBorrowed<'a>), + DateTimeGlue { + date: DatePatternDataBorrowed<'a>, + time: TimePatternDataBorrowed<'a>, + glue: &'a DateTimePatternV1<'a>, + }, +} + +#[derive(Debug, Copy, Clone)] +pub(crate) struct DateTimeWriter<'a, 'b, I> +where + I: Iterator + 'b, + 'a: 'b, +{ + pub(crate) datetime: &'b ExtractedDateTimeInput, + pub(crate) names: RawDateTimeNamesBorrowed<'a>, + pub(crate) pattern_items: I, + pub(crate) pattern_metadata: PatternMetadata, +} + +impl DatePatternSelectionData { + pub(crate) fn try_new_with_length( + provider: &P, + locale: &DataLocale, + length: length::Date, + ) -> Result + where + P: DataProvider + ?Sized, + M: KeyedDataMarker>, + { + let mut locale = locale.clone(); + locale.set_aux(AuxiliaryKeys::from_subtag(aux::pattern_subtag_for( + match length { + length::Date::Full => aux::PatternLength::Full, + length::Date::Long => aux::PatternLength::Long, + length::Date::Medium => aux::PatternLength::Medium, + length::Date::Short => aux::PatternLength::Short, + }, + None, // no hour cycle for date patterns + ))); + let payload = provider + .load(DataRequest { + locale: &locale, + metadata: Default::default(), + })? + .take_payload()? + .cast(); + Ok(Self::SingleDate(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 + '_ { + match self { + DatePatternSelectionData::SingleDate(payload) => payload.get(), + // Assumption: with_era has all the fields of without_era + DatePatternSelectionData::OptionalEra { with_era, .. } => with_era.get(), + } + .pattern + .items + .iter() + } + + /// Borrows a resolved pattern based on the given datetime + pub(crate) fn select(&self, _datetime: &ExtractedDateTimeInput) -> DatePatternDataBorrowed { + match self { + DatePatternSelectionData::SingleDate(payload) => { + DatePatternDataBorrowed::Resolved(payload.get()) + } + DatePatternSelectionData::OptionalEra { .. } => unimplemented!("#4478"), + } + } +} + +impl<'a> DatePatternDataBorrowed<'a> { + #[inline] + pub(crate) fn metadata(self) -> PatternMetadata { + match self { + Self::Resolved(data) => data.pattern.metadata, + } + } + + #[inline] + pub(crate) fn iter_items(self) -> impl Iterator + 'a { + match self { + Self::Resolved(data) => data.pattern.items.iter(), + } + } + + #[inline] + pub(crate) fn to_pattern(self) -> DateTimePattern { + let pattern = match self { + Self::Resolved(data) => &data.pattern, + }; + DateTimePattern::from_runtime_pattern(pattern.clone().into_owned()) + } +} + +impl TimePatternSelectionData { + pub(crate) fn try_new_with_length

( + provider: &P, + locale: &DataLocale, + length: length::Time, + ) -> Result + where + P: DataProvider + ?Sized, + { + let mut locale = locale.clone(); + locale.set_aux(AuxiliaryKeys::from_subtag(aux::pattern_subtag_for( + match length { + length::Time::Full => aux::PatternLength::Full, + length::Time::Long => aux::PatternLength::Long, + length::Time::Medium => aux::PatternLength::Medium, + length::Time::Short => aux::PatternLength::Short, + }, + None, // no hour cycle for date patterns + ))); + let payload = provider + .load(DataRequest { + locale: &locale, + metadata: Default::default(), + })? + .take_payload()? + .cast(); + Ok(Self::SingleTime(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 + '_ { + match self { + TimePatternSelectionData::SingleTime(payload) => payload.get(), + } + .pattern + .items + .iter() + } + + /// Borrows a resolved pattern based on the given datetime + pub(crate) fn select(&self, _datetime: &ExtractedDateTimeInput) -> TimePatternDataBorrowed { + match self { + TimePatternSelectionData::SingleTime(payload) => { + TimePatternDataBorrowed::Resolved(payload.get()) + } + } + } +} + +impl<'a> TimePatternDataBorrowed<'a> { + #[inline] + pub(crate) fn metadata(self) -> PatternMetadata { + match self { + Self::Resolved(data) => data.pattern.metadata, + } + } + + #[inline] + pub(crate) fn iter_items(self) -> impl Iterator + 'a { + match self { + Self::Resolved(data) => data.pattern.items.iter(), + } + } + + #[inline] + pub(crate) fn to_pattern(self) -> DateTimePattern { + let pattern = match self { + Self::Resolved(data) => &data.pattern, + }; + DateTimePattern::from_runtime_pattern(pattern.clone().into_owned()) + } +} + +impl DateTimeGluePatternSelectionData { + pub(crate) fn try_new_with_lengths( + provider: &P, + locale: &DataLocale, + date_length: length::Date, + time_length: length::Time, + ) -> Result + where + P: DataProvider + + DataProvider + + DataProvider + + ?Sized, + M: KeyedDataMarker>, + { + let date = + DatePatternSelectionData::try_new_with_length::(provider, locale, date_length)?; + let time = TimePatternSelectionData::try_new_with_length(provider, locale, time_length)?; + let mut locale = locale.clone(); + locale.set_aux(AuxiliaryKeys::from_subtag(aux::pattern_subtag_for( + // According to UTS 35, use the date length here: use the glue + // pattern "whose type matches the type of the date pattern" + match date_length { + length::Date::Full => aux::PatternLength::Full, + length::Date::Long => aux::PatternLength::Long, + length::Date::Medium => aux::PatternLength::Medium, + length::Date::Short => aux::PatternLength::Short, + }, + None, // no hour cycle for date patterns + ))); + let glue = provider + .load(DataRequest { + locale: &locale, + metadata: Default::default(), + })? + .take_payload()?; + Ok(Self { date, time, glue }) + } + + /// 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 + '_ { + let date_items = self.date.pattern_items_for_data_loading(); + let time_items = self.time.pattern_items_for_data_loading(); + date_items.chain(time_items) + } +} + +impl DateTimePatternSelectionData { + /// Borrows a resolved pattern based on the given datetime + pub(crate) fn select(&self, datetime: &ExtractedDateTimeInput) -> DateTimePatternDataBorrowed { + match self { + DateTimePatternSelectionData::Date(date) => { + DateTimePatternDataBorrowed::Date(date.select(datetime)) + } + DateTimePatternSelectionData::Time(time) => { + DateTimePatternDataBorrowed::Time(time.select(datetime)) + } + DateTimePatternSelectionData::DateTimeGlue(DateTimeGluePatternSelectionData { + date, + time, + glue, + }) => DateTimePatternDataBorrowed::DateTimeGlue { + date: date.select(datetime), + time: time.select(datetime), + glue: glue.get(), + }, + } + } +} + +impl<'a> DateTimePatternDataBorrowed<'a> { + #[inline] + fn date_pattern(self) -> Option> { + match self { + Self::Date(date) => Some(date), + Self::Time(_) => None, + Self::DateTimeGlue { date, .. } => Some(date), + } + } + + #[inline] + fn time_pattern(self) -> Option> { + match self { + Self::Date(_) => None, + Self::Time(time) => Some(time), + Self::DateTimeGlue { time, .. } => Some(time), + } + } + + #[inline] + fn glue_pattern(self) -> Option<&'a DateTimePatternV1<'a>> { + match self { + Self::Date(_) => None, + Self::Time(_) => None, + Self::DateTimeGlue { glue, .. } => Some(glue), + } + } + + #[inline] + pub(crate) fn metadata(self) -> PatternMetadata { + match self { + Self::Date(DatePatternDataBorrowed::Resolved(data)) => data.pattern.metadata, + Self::Time(TimePatternDataBorrowed::Resolved(data)) => data.pattern.metadata, + Self::DateTimeGlue { + date: DatePatternDataBorrowed::Resolved(date), + time: TimePatternDataBorrowed::Resolved(time), + .. + } => PatternMetadata::merge_date_and_time_metadata( + date.pattern.metadata, + time.pattern.metadata, + ), + } + } + + pub(crate) fn iter_items(self) -> impl Iterator + 'a { + let glue_pattern_slice = match self.glue_pattern() { + Some(glue) => glue.pattern.items.as_ule_slice(), + None => runtime::ZERO_ONE_SLICE.as_ule_slice(), + }; + glue_pattern_slice + .iter() + .flat_map( + move |generic_item_ule| match generic_item_ule.as_pattern_item_ule() { + Ok(pattern_item_ule) => core::slice::from_ref(pattern_item_ule), + Err(1) => self + .date_pattern() + .map(|data| match data { + DatePatternDataBorrowed::Resolved(pattern) => { + pattern.pattern.items.as_ule_slice() + } + }) + .unwrap_or(&[]), + Err(0) => self + .time_pattern() + .map(|data| match data { + TimePatternDataBorrowed::Resolved(pattern) => { + pattern.pattern.items.as_ule_slice() + } + }) + .unwrap_or(&[]), + _ => &[], + }, + ) + .map(|unaligned| PatternItem::from_unaligned(*unaligned)) + } + + pub(crate) fn to_pattern(self) -> DateTimePattern { + let pattern = match self { + Self::Date(DatePatternDataBorrowed::Resolved(data)) => &data.pattern, + Self::Time(TimePatternDataBorrowed::Resolved(data)) => &data.pattern, + Self::DateTimeGlue { .. } => todo!(), + }; + DateTimePattern::from_runtime_pattern(pattern.clone().into_owned()) + } +} + +impl<'a, 'b, I> DateTimeWriter<'a, 'b, I> +where + I: Iterator + 'b, + 'a: 'b, +{ + pub(crate) fn write_to(self, sink: &mut W) -> fmt::Result { + let loc_datetime = + DateTimeInputWithWeekConfig::new(self.datetime, self.names.week_calculator); + let Some(fixed_decimal_formatter) = self.names.fixed_decimal_formatter else { + // TODO(#4340): Make the FixedDecimalFormatter optional + icu_provider::_internal::log::warn!("FixedDecimalFormatter not loaded"); + return Err(core::fmt::Error); + }; + write_pattern( + self.pattern_items, + self.pattern_metadata, + Some(&self.names), + Some(&self.names), + &loc_datetime, + fixed_decimal_formatter, + sink, + ) + .map_err(|_e| { + icu_provider::_internal::log::warn!("{_e:?}"); + core::fmt::Error + }) + } +} diff --git a/components/datetime/tests/simple_test.rs b/components/datetime/tests/simple_test.rs new file mode 100644 index 00000000000..f27c3b2d182 --- /dev/null +++ b/components/datetime/tests/simple_test.rs @@ -0,0 +1,170 @@ +// 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 icu_calendar::DateTime; +use icu_datetime::neo::TypedNeoDateTimeFormatter; +use icu_datetime::options::length; +use icu_datetime::{DateTimeFormatterOptions, TypedDateTimeFormatter}; +use icu_locid::langid; +use writeable::assert_writeable_eq; + +const EXPECTED_DATETIME: &[&str] = &[ + "Friday, December 22, 2023, 9:22:53 PM", + "vendredi 22 décembre 2023, 21:22:53", + "2023年12月22日星期五 21:22:53", + "शुक्रवार, 22 दिसंबर 2023, 9:22:53 pm", + "Friday, December 22, 2023, 9:22 PM", + "vendredi 22 décembre 2023, 21:22", + "2023年12月22日星期五 21:22", + "शुक्रवार, 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", + "22 दिसंबर 2023, 9:22:53 pm", + "December 22, 2023, 9:22 PM", + "22 décembre 2023, 21:22", + "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", + "22 दिस॰ 2023, 9:22:53 pm", + "Dec 22, 2023, 9:22 PM", + "22 déc. 2023, 21:22", + "2023年12月22日 21:22", + "22 दिस॰ 2023, 9:22 pm", + "12/22/23, 9:22:53 PM", + "22/12/2023 21:22:53", + "2023/12/22 21:22:53", + "22/12/23, 9:22:53 pm", + "12/22/23, 9:22 PM", + "22/12/2023 21:22", + "2023/12/22 21:22", + "22/12/23, 9:22 pm", +]; + +const EXPECTED_DATE: &[&str] = &[ + "Friday, December 22, 2023", + "vendredi 22 décembre 2023", + "2023年12月22日星期五", + "शुक्रवार, 22 दिसंबर 2023", + "December 22, 2023", + "22 décembre 2023", + "2023年12月22日", + "22 दिसंबर 2023", + "Dec 22, 2023", + "22 déc. 2023", + "2023年12月22日", + "22 दिस॰ 2023", + "12/22/23", + "22/12/2023", + "2023/12/22", + "22/12/23", +]; + +#[test] +fn neo_datetime_lengths() { + let datetime = DateTime::try_new_gregorian_datetime(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 time_length in [length::Time::Medium, length::Time::Short] { + for langid in [langid!("en"), langid!("fr"), langid!("zh"), langid!("hi")] { + let formatter = TypedNeoDateTimeFormatter::try_new_with_lengths( + &(&langid).into(), + date_length, + time_length, + ) + .unwrap(); + let formatted = formatter.format(&datetime); + let expected = expected_iter.next().unwrap(); + assert_writeable_eq!( + formatted, + *expected, + "{date_length:?} {time_length:?} {langid:?}" + ); + } + } + } +} + +#[test] +fn neo_date_lengths() { + let datetime = DateTime::try_new_gregorian_datetime(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 langid in [langid!("en"), langid!("fr"), langid!("zh"), langid!("hi")] { + let formatter = + TypedNeoDateTimeFormatter::try_new_with_date_length(&(&langid).into(), date_length) + .unwrap(); + let formatted = formatter.format(&datetime); + let expected = expected_iter.next().unwrap(); + assert_writeable_eq!(formatted, *expected, "{date_length:?} {langid:?}"); + } + } +} + +#[test] +fn old_datetime_lengths() { + let datetime = DateTime::try_new_gregorian_datetime(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 time_length in [length::Time::Medium, length::Time::Short] { + for langid in [langid!("en"), langid!("fr"), langid!("zh"), langid!("hi")] { + let formatter = TypedDateTimeFormatter::try_new( + &(&langid).into(), + DateTimeFormatterOptions::Length(length::Bag::from_date_time_style( + date_length, + time_length, + )), + ) + .unwrap(); + let formatted = formatter.format(&datetime); + let expected = expected_iter.next().unwrap(); + assert_writeable_eq!( + formatted, + *expected, + "{date_length:?} {time_length:?} {langid:?}" + ); + } + } + } +} + +#[test] +fn old_date_lengths() { + let datetime = DateTime::try_new_gregorian_datetime(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 langid in [langid!("en"), langid!("fr"), langid!("zh"), langid!("hi")] { + let formatter = TypedDateTimeFormatter::try_new( + &(&langid).into(), + DateTimeFormatterOptions::Length(length::Bag::from_date_style(date_length)), + ) + .unwrap(); + let formatted = formatter.format(&datetime); + let expected = expected_iter.next().unwrap(); + assert_writeable_eq!(formatted, *expected, "{date_length:?} {langid:?}"); + } + } +} diff --git a/ffi/capi/tests/missing_apis.txt b/ffi/capi/tests/missing_apis.txt index 02fc141d03b..7103bd77ee8 100644 --- a/ffi/capi/tests/missing_apis.txt +++ b/ffi/capi/tests/missing_apis.txt @@ -31,6 +31,30 @@ icu::datetime::TypedDateTimeNames::load_year_names#FnInStruct icu::datetime::TypedDateTimeNames::set_week_calculator#FnInStruct icu::datetime::TypedDateTimeNames::try_new#FnInStruct icu::datetime::TypedDateTimeNames::with_pattern#FnInStruct +icu::datetime::neo::FormattedNeoDate#Struct +icu::datetime::neo::FormattedNeoDate::pattern#FnInStruct +icu::datetime::neo::FormattedNeoDate::write_to#FnInStruct +icu::datetime::neo::FormattedNeoDateTime#Struct +icu::datetime::neo::FormattedNeoDateTime::pattern#FnInStruct +icu::datetime::neo::FormattedNeoDateTime::write_to#FnInStruct +icu::datetime::neo::FormattedNeoTime#Struct +icu::datetime::neo::FormattedNeoTime::pattern#FnInStruct +icu::datetime::neo::FormattedNeoTime::write_to#FnInStruct +icu::datetime::neo::NeoTimeFormatter#Struct +icu::datetime::neo::NeoTimeFormatter::format#FnInStruct +icu::datetime::neo::NeoTimeFormatter::try_new_with_length#FnInStruct +icu::datetime::neo::TypedNeoDateFormatter#Struct +icu::datetime::neo::TypedNeoDateFormatter::format#FnInStruct +icu::datetime::neo::TypedNeoDateFormatter::try_new_with_length#FnInStruct +icu::datetime::neo::TypedNeoDateTimeFormatter#Struct +icu::datetime::neo::TypedNeoDateTimeFormatter::format#FnInStruct +icu::datetime::neo::TypedNeoDateTimeFormatter::try_new#FnInStruct +icu::datetime::neo::TypedNeoDateTimeFormatter::try_new_with_date_length#FnInStruct +icu::datetime::neo::TypedNeoDateTimeFormatter::try_new_with_lengths#FnInStruct +icu::datetime::neo::TypedNeoDateTimeFormatter::try_new_with_time_length#FnInStruct +icu::datetime::neo_pattern::DateTimePattern#Struct +icu::datetime::neo_pattern::DateTimePattern::from_str#FnInStruct +icu::datetime::neo_pattern::DateTimePattern::try_from_pattern_str#FnInStruct icu::properties::bidi_data::BidiAuxiliaryProperties#Struct icu::properties::bidi_data::BidiAuxiliaryProperties::from_data#FnInStruct icu::properties::bidi_data::BidiAuxiliaryPropertiesBorrowed#Struct