diff --git a/components/datetime/src/neo.rs b/components/datetime/src/neo.rs index 9b349a708e4..be544017645 100644 --- a/components/datetime/src/neo.rs +++ b/components/datetime/src/neo.rs @@ -140,7 +140,7 @@ size_test!(FixedCalendarDateTimeFormatter { selection: DateTimeZonePatternSelectionData, - names: RawDateTimeNames, + pub(crate) names: RawDateTimeNames, _calendar: PhantomData, } @@ -356,8 +356,8 @@ size_test!( #[derive(Debug)] pub struct DateTimeFormatter { selection: DateTimeZonePatternSelectionData, - names: RawDateTimeNames, - calendar: AnyCalendar, + pub(crate) names: RawDateTimeNames, + pub(crate) calendar: AnyCalendar, } impl DateTimeFormatter @@ -469,37 +469,68 @@ where { let calendar = AnyCalendarLoader::load(loader, (&prefs).into()) .map_err(DateTimeFormatterLoadError::Data)?; + let names = RawDateTimeNames::new_without_number_formatting(); + Self::try_new_internal_with_calendar_and_names( + provider, provider, loader, prefs, field_set, calendar, names, + ) + .map_err(|e| e.0) + } + + pub(crate) fn try_new_internal_with_calendar_and_names( + provider_p: &P0, + provider: &P1, + loader: &L, + prefs: DateTimeFormatterPreferences, + field_set: CompositeFieldSet, + calendar: AnyCalendar, + mut names: RawDateTimeNames, + ) -> Result< + Self, + ( + DateTimeFormatterLoadError, + (AnyCalendar, RawDateTimeNames), + ), + > + where + P0: ?Sized + AllAnyCalendarPatternDataMarkers, + P1: ?Sized + AllAnyCalendarFormattingDataMarkers, + L: DecimalFormatterLoader, + { let kind = calendar.kind(); let selection = DateTimeZonePatternSelectionData::try_new_with_skeleton( - &AnyCalendarProvider::<::Skel, _>::new(provider, kind), - &::TimeSkeletonPatternsV1::bind(provider), - &FSet::GluePatternV1::bind(provider), + &AnyCalendarProvider::<::Skel, _>::new(provider_p, kind), + &::TimeSkeletonPatternsV1::bind(provider_p), + &FSet::GluePatternV1::bind(provider_p), prefs, field_set, - ) - .map_err(DateTimeFormatterLoadError::Data)?; - let mut names = RawDateTimeNames::new_without_number_formatting(); - names - .load_for_pattern( - &AnyCalendarProvider::<::Year, _>::new(provider, kind), - &AnyCalendarProvider::<::Month, _>::new(provider, kind), - &::WeekdayNamesV1::bind(provider), - &::DayPeriodNamesV1::bind(provider), - &::EssentialsV1::bind(provider), - &::LocationsV1::bind(provider), - &::LocationsRootV1::bind(provider), - &::ExemplarCitiesRootV1::bind(provider), - &::ExemplarCitiesV1::bind(provider), - &::GenericLongV1::bind(provider), - &::GenericShortV1::bind(provider), - &::SpecificLongV1::bind(provider), - &::SpecificShortV1::bind(provider), - &::MetazonePeriodV1::bind(provider), - loader, // fixed decimal formatter - prefs, - selection.pattern_items_for_data_loading(), - ) - .map_err(DateTimeFormatterLoadError::Names)?; + ); + let selection = match selection { + Ok(selection) => selection, + Err(e) => return Err((DateTimeFormatterLoadError::Data(e), (calendar, names))), + }; + let result = names.load_for_pattern( + &AnyCalendarProvider::<::Year, _>::new(provider, kind), + &AnyCalendarProvider::<::Month, _>::new(provider, kind), + &::WeekdayNamesV1::bind(provider), + &::DayPeriodNamesV1::bind(provider), + &::EssentialsV1::bind(provider), + &::LocationsV1::bind(provider), + &::LocationsRootV1::bind(provider), + &::ExemplarCitiesRootV1::bind(provider), + &::ExemplarCitiesV1::bind(provider), + &::GenericLongV1::bind(provider), + &::GenericShortV1::bind(provider), + &::SpecificLongV1::bind(provider), + &::SpecificShortV1::bind(provider), + &::MetazonePeriodV1::bind(provider), + loader, // fixed decimal formatter + prefs, + selection.pattern_items_for_data_loading(), + ); + match result { + Ok(()) => (), + Err(e) => return Err((DateTimeFormatterLoadError::Names(e), (calendar, names))), + }; Ok(Self { selection, names, diff --git a/components/datetime/src/pattern/mod.rs b/components/datetime/src/pattern/mod.rs index 7d92512209f..7d8b14bfc66 100644 --- a/components/datetime/src/pattern/mod.rs +++ b/components/datetime/src/pattern/mod.rs @@ -19,6 +19,7 @@ use crate::error::ErrorField; pub use formatter::DateTimePatternFormatter; pub use formatter::FormattedDateTimePattern; use icu_pattern::SinglePlaceholderPattern; +pub use names::DateTimeNames; pub use names::DayPeriodNameLength; pub use names::MonthNameLength; pub(crate) use names::RawDateTimeNames; diff --git a/components/datetime/src/pattern/names.rs b/components/datetime/src/pattern/names.rs index 2cd29cf3692..c38bcec8889 100644 --- a/components/datetime/src/pattern/names.rs +++ b/components/datetime/src/pattern/names.rs @@ -8,20 +8,21 @@ use super::{ PatternLoadError, }; use crate::error::ErrorField; -use crate::fieldsets::enums::CompositeDateTimeFieldSet; -use crate::input; +use crate::fieldsets::enums::{CompositeDateTimeFieldSet, CompositeFieldSet}; use crate::provider::fields::{self, FieldLength, FieldSymbol}; use crate::provider::neo::{marker_attrs, *}; use crate::provider::pattern::PatternItem; use crate::provider::time_zones::tz; -use crate::scaffold::*; use crate::size_test_macro::size_test; use crate::{external_loaders::*, DateTimeFormatterPreferences}; +use crate::{input, FixedCalendarDateTimeFormatter}; +use crate::{scaffold::*, DateTimeFormatter, DateTimeFormatterLoadError}; use core::fmt; use core::marker::PhantomData; use core::num::NonZeroU8; use icu_calendar::types::FormattingEra; use icu_calendar::types::MonthCode; +use icu_calendar::AnyCalendar; use icu_decimal::options::DecimalFormatterOptions; use icu_decimal::options::GroupingStrategy; use icu_decimal::provider::{DecimalDigitsV1, DecimalSymbolsV2}; @@ -545,15 +546,24 @@ size_test!( /// ); /// ``` #[derive(Debug)] -pub struct TypedDateTimeNames< - C: CldrCalendar, - FSet: DateTimeNamesMarker = CompositeDateTimeFieldSet, -> { +pub struct TypedDateTimeNames { prefs: DateTimeFormatterPreferences, inner: RawDateTimeNames, _calendar: PhantomData, } +/// A low-level type that formats datetime patterns with localized names. +/// The calendar is chosen in the constructor at runtime. +/// +/// Currently this only supports loading of non-calendar-specific names, but +/// additional functions may be added in the future. If you need this, see +/// +#[derive(Debug)] +pub struct DateTimeNames { + inner: TypedDateTimeNames<(), FSet>, + calendar: AnyCalendar, +} + pub(crate) struct RawDateTimeNames { year_names: >::Container, month_names: >::Container, @@ -641,7 +651,7 @@ pub(crate) struct RawDateTimeNamesBorrowed<'l> { pub(crate) decimal_formatter: Option<&'l DecimalFormatter>, } -impl TypedDateTimeNames { +impl TypedDateTimeNames { /// Constructor that takes a selected locale and creates an empty instance. /// /// For an example, see [`TypedDateTimeNames`]. @@ -730,7 +740,191 @@ impl TypedDateTimeNames { _calendar: PhantomData, } } +} + +impl TypedDateTimeNames { + /// Creates an instance with the names loaded in a [`FixedCalendarDateTimeFormatter`]. + pub fn from_fixed_calendar_formatter( + prefs: DateTimeFormatterPreferences, + formatter: FixedCalendarDateTimeFormatter, + ) -> Self { + Self { + prefs, + inner: formatter.names, + _calendar: PhantomData, + } + } + + #[cfg(feature = "compiled_data")] + pub fn try_into_fixed_calendar_formatter( + self, + field_set: FSet, + ) -> Result, DateTimeFormatterLoadError> { + todo!() + } +} + +impl DateTimeNames { + /// Creates a completely empty instance, not even with number formatting. + pub fn new_without_number_formatting( + prefs: DateTimeFormatterPreferences, + calendar: AnyCalendar, + ) -> Self { + Self { + inner: TypedDateTimeNames::new_without_number_formatting(prefs), + calendar, + } + } + + /// Creates an instance with the names and calendar loaded in a [`DateTimeFormatter`]. + /// + /// This function requires passing in the [`DateTimeFormatterPreferences`] because it is not + /// retained in the formatter. Pass the same value or else unexpected behavior may occur. + /// + /// # Examples + /// + /// ``` + /// use icu::calendar::Date; + /// use icu::timezone::{DateTime, Time}; + /// use icu::datetime::DateTimeFormatter; + /// use icu::datetime::fieldsets::{YMD, YMDT}; + /// use icu::datetime::pattern::{DateTimeNames, DayPeriodNameLength}; + /// use icu::locale::locale; + /// use writeable::assert_writeable_eq; + /// + /// let prefs = locale!("es-MX").into(); + /// + /// let formatter = + /// DateTimeFormatter::try_new( + /// prefs, + /// YMD::long(), + /// ) + /// .unwrap(); + /// + /// assert_writeable_eq!( + /// formatter.format(&Date::try_new_iso(2025, 2, 13).unwrap()), + /// "13 de febrero de 2025" + /// ); + /// + /// // Change the YMD formatter to a YMDT formatter, after loading day period names. + /// // This assumes that the locale uses Abbreviated names for the given semantic skeleton! + /// let mut names = DateTimeNames::from_formatter(prefs, formatter).with_fset::(); + /// names.as_mut().include_day_period_names(DayPeriodNameLength::Abbreviated).unwrap(); + /// let formatter = names.try_into_formatter(YMDT::long().hm()).unwrap(); + /// + /// assert_writeable_eq!( + /// formatter.format(&DateTime { + /// date: Date::try_new_iso(2025, 2, 13).unwrap(), + /// time: Time::midnight(), + /// }), + /// "13 de febrero de 2025, 12:00 a.m." + /// ); + /// ``` + pub fn from_formatter( + prefs: DateTimeFormatterPreferences, + formatter: DateTimeFormatter, + ) -> Self { + Self { + inner: TypedDateTimeNames { + prefs, + inner: formatter.names, + _calendar: PhantomData, + }, + calendar: formatter.calendar, + } + } +} + +impl DateTimeNames +where + FSet::D: DateDataMarkers, + FSet::T: TimeMarkers, + FSet::Z: ZoneMarkers, + FSet: GetField, +{ + /// Loads a pattern for the given field set and returns a [`DateTimeFormatter`]. + /// + /// The names in the current [`DateTimeNames`] _must_ be sufficient for the field set. + /// If not, the input object will be returned with an error. + /// + /// # Examples + /// + /// ``` + /// use icu::calendar::AnyCalendar; + /// use icu::timezone::Time; + /// use icu::datetime::fieldsets::T; + /// use icu::datetime::pattern::{DateTimeNames, DayPeriodNameLength}; + /// use icu::locale::locale; + /// use writeable::assert_writeable_eq; + /// + /// let names = DateTimeNames::new_without_number_formatting( + /// locale!("es-MX").into(), + /// AnyCalendar::try_new(locale!("es-MX").into()).unwrap(), + /// ); + /// + /// let field_set = T::long().hm(); + /// + /// // Cannot convert yet: no names are loaded + /// let mut names = names.try_into_formatter(field_set).unwrap_err().1; + /// + /// // Load the data we need: + /// names.as_mut().include_day_period_names(DayPeriodNameLength::Abbreviated).unwrap(); + /// names.as_mut().include_decimal_formatter().unwrap(); + /// + /// // Now the conversion is successful: + /// let formatter = names.try_into_formatter(field_set).unwrap(); + /// + /// assert_writeable_eq!( + /// formatter.format(&Time::midnight()), + /// "12:00 a.m." + /// ); + /// ``` + #[cfg(feature = "compiled_data")] + pub fn try_into_formatter( + self, + field_set: FSet, + ) -> Result, (DateTimeFormatterLoadError, Self)> + where + crate::provider::Baked: AllAnyCalendarPatternDataMarkers, + { + DateTimeFormatter::try_new_internal_with_calendar_and_names( + &crate::provider::Baked, + &EmptyDataProvider, + &ExternalLoaderCompiledData, + self.inner.prefs, + field_set.get_field(), + self.calendar, + self.inner.inner, + ) + .map_err(|e| { + ( + e.0, + Self { + inner: TypedDateTimeNames { + prefs: self.inner.prefs, + inner: e.1 .1, + _calendar: PhantomData, + }, + calendar: e.1 .0, + }, + ) + }) + } +} + +impl AsRef> for DateTimeNames { + fn as_ref(&self) -> &TypedDateTimeNames<(), FSet> { + &self.inner + } +} + +impl AsMut> for DateTimeNames { + fn as_mut(&mut self) -> &mut TypedDateTimeNames<(), FSet> { + &mut self.inner + } +} +impl TypedDateTimeNames { /// Loads year (era or cycle) names for the specified length. /// /// Does not support multiple field symbols or lengths. See #4337 @@ -858,7 +1052,9 @@ impl TypedDateTimeNames { { self.load_month_names(&crate::provider::Baked, length) } +} +impl TypedDateTimeNames { /// Loads day period names for the specified length. /// /// Does not support multiple field symbols or lengths. See #4337 @@ -1560,7 +1756,9 @@ impl TypedDateTimeNames { .load_decimal_formatter(&ExternalLoaderCompiledData, self.prefs)?; Ok(self) } +} +impl TypedDateTimeNames { /// Associates this [`TypedDateTimeNames`] with a pattern /// without checking that all necessary data is loaded. #[inline] @@ -1693,7 +1891,9 @@ impl TypedDateTimeNames { self.inner.as_borrowed(), )) } +} +impl TypedDateTimeNames { /// Maps a [`TypedDateTimeNames`] of a specific `FSet` to a more general `FSet`. /// /// For example, this can transform a formatter for [`DateFieldSet`] to one for @@ -1756,6 +1956,22 @@ impl TypedDateTimeNames { } } +impl DateTimeNames { + /// Maps a [`TypedDateTimeNames`] of a specific `FSet` to a more general `FSet`. + /// + /// For example, this can transform a formatter for [`DateFieldSet`] to one for + /// [`CompositeDateTimeFieldSet`]. + /// + /// [`DateFieldSet`]: crate::fieldsets::enums::DateFieldSet + /// [`CompositeDateTimeFieldSet`]: crate::fieldsets::enums::CompositeDateTimeFieldSet + pub fn with_fset>(self) -> DateTimeNames { + DateTimeNames { + inner: self.inner.with_fset(), + calendar: self.calendar, + } + } +} + impl RawDateTimeNames { pub(crate) fn new_without_number_formatting() -> Self { Self { diff --git a/components/datetime/src/scaffold/fieldset_traits.rs b/components/datetime/src/scaffold/fieldset_traits.rs index cf496d38ef6..fcce0ec04d6 100644 --- a/components/datetime/src/scaffold/fieldset_traits.rs +++ b/components/datetime/src/scaffold/fieldset_traits.rs @@ -295,12 +295,77 @@ where { } +/// Trait to consolidate data provider markers defined by this crate +/// for datetime skeleton patterns with any calendar. +/// +/// This trait is implemented on all providers that support datetime skeleton patterns, +/// including [`crate::provider::Baked`]. +// This trait is implicitly sealed due to sealed supertraits +#[rustfmt::skip] +pub trait AllAnyCalendarPatternDataMarkers: + DataProvider<<::Skel as CalMarkers>::Buddhist> + + DataProvider<<::Skel as CalMarkers>::Chinese> + + DataProvider<<::Skel as CalMarkers>::Coptic> + + DataProvider<<::Skel as CalMarkers>::Dangi> + + DataProvider<<::Skel as CalMarkers>::Ethiopian> + + DataProvider<<::Skel as CalMarkers>::EthiopianAmeteAlem> + + DataProvider<<::Skel as CalMarkers>::Gregorian> + + DataProvider<<::Skel as CalMarkers>::Hebrew> + + DataProvider<<::Skel as CalMarkers>::Indian> + + DataProvider<<::Skel as CalMarkers>::IslamicCivil> + + DataProvider<<::Skel as CalMarkers>::IslamicObservational> + + DataProvider<<::Skel as CalMarkers>::IslamicTabular> + + DataProvider<<::Skel as CalMarkers>::IslamicUmmAlQura> + + DataProvider<<::Skel as CalMarkers>::Japanese> + + DataProvider<<::Skel as CalMarkers>::JapaneseExtended> + + DataProvider<<::Skel as CalMarkers>::Persian> + + DataProvider<<::Skel as CalMarkers>::Roc> + + DataProvider<::TimeSkeletonPatternsV1> + + DataProvider +where + FSet::D: DateDataMarkers, + FSet::T: TimeMarkers, + FSet::Z: ZoneMarkers, +{ +} + +#[rustfmt::skip] +impl AllAnyCalendarPatternDataMarkers for T +where + FSet: DateTimeMarkers, + FSet::D: DateDataMarkers, + FSet::T: TimeMarkers, + FSet::Z: ZoneMarkers, + T: ?Sized + + DataProvider<<::Skel as CalMarkers>::Buddhist> + + DataProvider<<::Skel as CalMarkers>::Chinese> + + DataProvider<<::Skel as CalMarkers>::Coptic> + + DataProvider<<::Skel as CalMarkers>::Dangi> + + DataProvider<<::Skel as CalMarkers>::Ethiopian> + + DataProvider<<::Skel as CalMarkers>::EthiopianAmeteAlem> + + DataProvider<<::Skel as CalMarkers>::Gregorian> + + DataProvider<<::Skel as CalMarkers>::Hebrew> + + DataProvider<<::Skel as CalMarkers>::Indian> + + DataProvider<<::Skel as CalMarkers>::IslamicCivil> + + DataProvider<<::Skel as CalMarkers>::IslamicObservational> + + DataProvider<<::Skel as CalMarkers>::IslamicTabular> + + DataProvider<<::Skel as CalMarkers>::IslamicUmmAlQura> + + DataProvider<<::Skel as CalMarkers>::Japanese> + + DataProvider<<::Skel as CalMarkers>::JapaneseExtended> + + DataProvider<<::Skel as CalMarkers>::Persian> + + DataProvider<<::Skel as CalMarkers>::Roc> + + DataProvider<::TimeSkeletonPatternsV1> + + DataProvider +{ +} + /// Trait to consolidate data provider markers defined by this crate /// for datetime formatting with any calendar. /// /// This trait is implemented on all providers that support datetime formatting, /// including [`crate::provider::Baked`]. // This trait is implicitly sealed due to sealed supertraits +#[rustfmt::skip] pub trait AllAnyCalendarFormattingDataMarkers: DataProvider<<::Year as CalMarkers>::Buddhist> + DataProvider<<::Year as CalMarkers>::Chinese> @@ -336,26 +401,8 @@ pub trait AllAnyCalendarFormattingDataMarkers: + DataProvider<<::Month as CalMarkers>::JapaneseExtended> + DataProvider<<::Month as CalMarkers>::Persian> + DataProvider<<::Month as CalMarkers>::Roc> - + DataProvider<<::Skel as CalMarkers>::Buddhist> - + DataProvider<<::Skel as CalMarkers>::Chinese> - + DataProvider<<::Skel as CalMarkers>::Coptic> - + DataProvider<<::Skel as CalMarkers>::Dangi> - + DataProvider<<::Skel as CalMarkers>::Ethiopian> - + DataProvider<<::Skel as CalMarkers>::EthiopianAmeteAlem> - + DataProvider<<::Skel as CalMarkers>::Gregorian> - + DataProvider<<::Skel as CalMarkers>::Hebrew> - + DataProvider<<::Skel as CalMarkers>::Indian> - + DataProvider<<::Skel as CalMarkers>::IslamicCivil> - + DataProvider<<::Skel as CalMarkers>::IslamicObservational> - + DataProvider<<::Skel as CalMarkers>::IslamicTabular> - + DataProvider<<::Skel as CalMarkers>::IslamicUmmAlQura> - + DataProvider<<::Skel as CalMarkers>::Japanese> - + DataProvider<<::Skel as CalMarkers>::JapaneseExtended> - + DataProvider<<::Skel as CalMarkers>::Persian> - + DataProvider<<::Skel as CalMarkers>::Roc> + DataProvider<::WeekdayNamesV1> + DataProvider<::DayPeriodNamesV1> - + DataProvider<::TimeSkeletonPatternsV1> + DataProvider<::EssentialsV1> + DataProvider<::LocationsV1> + DataProvider<::LocationsRootV1> @@ -366,7 +413,7 @@ pub trait AllAnyCalendarFormattingDataMarkers: + DataProvider<::SpecificLongV1> + DataProvider<::SpecificShortV1> + DataProvider<::MetazonePeriodV1> - + DataProvider + + AllAnyCalendarPatternDataMarkers where FSet::D: DateDataMarkers, FSet::T: TimeMarkers, @@ -374,6 +421,7 @@ where { } +#[rustfmt::skip] impl AllAnyCalendarFormattingDataMarkers for T where FSet: DateTimeMarkers, @@ -415,26 +463,8 @@ where + DataProvider<<::Month as CalMarkers>::JapaneseExtended> + DataProvider<<::Month as CalMarkers>::Persian> + DataProvider<<::Month as CalMarkers>::Roc> - + DataProvider<<::Skel as CalMarkers>::Buddhist> - + DataProvider<<::Skel as CalMarkers>::Chinese> - + DataProvider<<::Skel as CalMarkers>::Coptic> - + DataProvider<<::Skel as CalMarkers>::Dangi> - + DataProvider<<::Skel as CalMarkers>::Ethiopian> - + DataProvider<<::Skel as CalMarkers>::EthiopianAmeteAlem> - + DataProvider<<::Skel as CalMarkers>::Gregorian> - + DataProvider<<::Skel as CalMarkers>::Hebrew> - + DataProvider<<::Skel as CalMarkers>::Indian> - + DataProvider<<::Skel as CalMarkers>::IslamicCivil> - + DataProvider<<::Skel as CalMarkers>::IslamicObservational> - + DataProvider<<::Skel as CalMarkers>::IslamicTabular> - + DataProvider<<::Skel as CalMarkers>::IslamicUmmAlQura> - + DataProvider<<::Skel as CalMarkers>::Japanese> - + DataProvider<<::Skel as CalMarkers>::JapaneseExtended> - + DataProvider<<::Skel as CalMarkers>::Persian> - + DataProvider<<::Skel as CalMarkers>::Roc> + DataProvider<::WeekdayNamesV1> + DataProvider<::DayPeriodNamesV1> - + DataProvider<::TimeSkeletonPatternsV1> + DataProvider<::EssentialsV1> + DataProvider<::LocationsV1> + DataProvider<::LocationsRootV1> @@ -445,7 +475,7 @@ where + DataProvider<::SpecificLongV1> + DataProvider<::SpecificShortV1> + DataProvider<::MetazonePeriodV1> - + DataProvider + + AllAnyCalendarPatternDataMarkers, { } diff --git a/components/datetime/src/scaffold/mod.rs b/components/datetime/src/scaffold/mod.rs index 1885bf2ca82..90a28a7f247 100644 --- a/components/datetime/src/scaffold/mod.rs +++ b/components/datetime/src/scaffold/mod.rs @@ -25,6 +25,7 @@ pub use calendar::NoDataCalMarkers; pub(crate) use fieldset_traits::datetime_marker_helper; pub use fieldset_traits::AllAnyCalendarExternalDataMarkers; pub use fieldset_traits::AllAnyCalendarFormattingDataMarkers; +pub use fieldset_traits::AllAnyCalendarPatternDataMarkers; pub use fieldset_traits::AllFixedCalendarExternalDataMarkers; pub use fieldset_traits::AllFixedCalendarFormattingDataMarkers; pub use fieldset_traits::AllInputMarkers;