diff --git a/components/datetime/src/dynamic.rs b/components/datetime/src/dynamic.rs index ee520208818..4d71d66b8d5 100644 --- a/components/datetime/src/dynamic.rs +++ b/components/datetime/src/dynamic.rs @@ -66,7 +66,7 @@ //! assert_eq!(results, ["Jan 15, 4:00 PM", "Jan 15"]) //! ``` -use crate::fieldsets::Combo; +use crate::fieldsets::{builder, Combo}; use crate::raw::neo::RawOptions; use crate::scaffold::GetField; use crate::{fieldsets, provider}; @@ -386,6 +386,13 @@ macro_rules! impl_attrs { )+ } } + pub(crate) fn to_zone_style(self) -> builder::ZoneStyle { + match self { + $( + Self::$variant(_) => builder::ZoneStyle::$variant, + )+ + } + } } }; (@datetime, $type:path, [$(($d_variant:ident, $variant:ident)),+,]) => { diff --git a/components/datetime/src/fieldsets.rs b/components/datetime/src/fieldsets.rs index 0b2e767063a..f14247270be 100644 --- a/components/datetime/src/fieldsets.rs +++ b/components/datetime/src/fieldsets.rs @@ -172,6 +172,7 @@ macro_rules! impl_marker_with_options { $(#[$attr:meta])* $type:ident, $(sample_length: $sample_length:ident,)? + $(date_fields: $date_fields:expr,)? $(alignment: $alignment_yes:ident,)? $(year_style: $yearstyle_yes:ident,)? $(time_precision: $timeprecision_yes:ident,)? @@ -209,7 +210,8 @@ macro_rules! impl_marker_with_options { impl $type { pub(crate) fn to_raw_options(self) -> RawOptions { RawOptions { - length: yes_or!(self.length, $(Length::$length_override)?), + length: yes_or!(Some(self.length), $(Some(Length::$length_override))?), + date_fields: yes_or!(None, $($date_fields)?), alignment: ternary!(self.alignment, None, $($alignment_yes)?), year_style: ternary!(self.year_style, None, $($yearstyle_yes)?), time_precision: ternary!(self.time_precision, None, $($timeprecision_yes)?), @@ -392,6 +394,7 @@ macro_rules! impl_date_or_calendar_period_marker { $(#[$attr])* $type, sample_length: $sample_length, + date_fields: Some(builder::DateFields::$type), $(alignment: $option_alignment_yes,)? $(year_style: $year_yes,)? ); @@ -548,6 +551,7 @@ macro_rules! impl_date_marker { $(#[$attr])* $type_time, sample_length: $sample_length, + date_fields: Some(builder::DateFields::$type), alignment: yes, $(year_style: $year_yes,)? time_precision: yes, diff --git a/components/datetime/src/neo.rs b/components/datetime/src/neo.rs index b957e23bbc6..f471ec06175 100644 --- a/components/datetime/src/neo.rs +++ b/components/datetime/src/neo.rs @@ -6,6 +6,7 @@ use crate::error::DateTimeFormatterLoadError; use crate::external_loaders::*; +use crate::fieldsets::builder::FieldSetBuilder; use crate::fieldsets::enums::CompositeFieldSet; use crate::format::datetime::try_write_pattern_items; use crate::format::ExtractedInput; @@ -771,6 +772,60 @@ impl FixedCalendarDateTimeFormatter FieldSetBuilder { + self.selection.to_builder() + } } impl DateTimeFormatter { @@ -903,6 +958,60 @@ impl DateTimeFormatter { pub fn calendar(&self) -> icu_calendar::Ref { icu_calendar::Ref(&self.calendar) } + + /// Gets a [`FieldSetBuilder`] corresponding to the fields and options configured in this + /// formatter. The builder can be used to recreate the formatter. + /// + /// # Examples + /// + /// ``` + /// use icu::datetime::fieldsets::builder::*; + /// use icu::datetime::fieldsets::YMDT; + /// use icu::datetime::input::*; + /// use icu::datetime::options::*; + /// use icu::datetime::DateTimeFormatter; + /// use icu::locale::locale; + /// use writeable::assert_writeable_eq; + /// + /// // Create a simple YMDT formatter: + /// let formatter = DateTimeFormatter::try_new( + /// locale!("und").into(), + /// YMDT::long().hm().with_alignment(Alignment::Column) + /// ) + /// .unwrap(); + /// + /// // Get the builder corresponding to it: + /// let builder = formatter.to_field_set_builder(); + /// + /// // Check that the builder is what we expect: + /// let mut equivalent_builder = FieldSetBuilder::default(); + /// equivalent_builder.length = Some(Length::Long); + /// equivalent_builder.date_fields = Some(DateFields::YMD); + /// equivalent_builder.time_precision = Some(TimePrecision::Minute); + /// equivalent_builder.alignment = Some(Alignment::Column); + /// assert_eq!( + /// builder, + /// equivalent_builder, + /// ); + /// + /// // Check that it creates a formatter with equivalent behavior: + /// let built_formatter = DateTimeFormatter::try_new( + /// locale!("und").into(), + /// builder.build_composite_datetime().unwrap(), + /// ) + /// .unwrap(); + /// let datetime = DateTime { + /// date: Date::try_new_iso(2025, 3, 6).unwrap(), + /// time: Time::try_new(16, 41, 0, 0).unwrap(), + /// }; + /// assert_eq!( + /// formatter.format(&datetime).to_string(), + /// built_formatter.format(&datetime).to_string(), + /// ); + /// ``` + pub fn to_field_set_builder(&self) -> FieldSetBuilder { + self.selection.to_builder() + } } /// A formatter optimized for time and time zone formatting, when a calendar is not needed. diff --git a/components/datetime/src/raw/neo.rs b/components/datetime/src/raw/neo.rs index 325b50e9758..7266e774e58 100644 --- a/components/datetime/src/raw/neo.rs +++ b/components/datetime/src/raw/neo.rs @@ -2,11 +2,12 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). +use crate::fieldsets::builder; use crate::fieldsets::enums::{CompositeFieldSet, TimeFieldSet, ZoneFieldSet}; use crate::format::ExtractedInput; use crate::options::*; use crate::pattern::DateTimePattern; -use crate::provider::fields::{self, Field, FieldLength, FieldSymbol, TimeZone}; +use crate::provider::fields::{self, Field, FieldLength, FieldSymbol}; use crate::provider::pattern::{ runtime::{self, PatternMetadata}, GenericPatternItem, PatternItem, @@ -22,12 +23,23 @@ use zerovec::ZeroSlice; #[derive(Debug, Copy, Clone)] pub(crate) struct RawOptions { - pub(crate) length: Length, + pub(crate) length: Option, + pub(crate) date_fields: Option, pub(crate) alignment: Option, pub(crate) year_style: Option, pub(crate) time_precision: Option, } +impl RawOptions { + #[inline] + pub(crate) fn length(self) -> Length { + self.length.unwrap_or_else(|| { + debug_assert!(false, "length not set in a formatter that needs it"); + Default::default() + }) + } +} + #[derive(Debug, Copy, Clone, Default)] pub(crate) struct RawPreferences { pub(crate) hour_cycle: Option, @@ -73,7 +85,7 @@ pub(crate) enum TimePatternDataBorrowed<'a> { #[derive(Debug, Clone)] pub(crate) enum ZonePatternSelectionData { - SinglePatternItem(TimeZone, FieldLength, ::ULE), + SinglePatternItem(ZoneFieldSet, ::ULE), } #[derive(Debug, Copy, Clone)] @@ -156,7 +168,7 @@ impl DatePatternSelectionData { let payload = self.payload.get_option()?; Some( payload - .get(options.length, PackedSkeletonVariant::Variant1) + .get(options.length(), PackedSkeletonVariant::Variant1) .items .iter(), ) @@ -188,7 +200,7 @@ impl DatePatternSelectionData { } }; Some(DatePatternDataBorrowed::Resolved( - payload.get(options.length, variant), + payload.get(options.length(), variant), options.alignment, )) } @@ -313,7 +325,7 @@ impl TimePatternSelectionData { let payload = self.payload.get_option()?; Some( payload - .get(options.length, PackedSkeletonVariant::Variant1) + .get(options.length(), PackedSkeletonVariant::Variant1) .items .iter(), ) @@ -330,7 +342,7 @@ impl TimePatternSelectionData { let time_precision = options.time_precision.unwrap_or_default(); let (variant, subsecond_digits) = input.resolve_time_precision(time_precision); Some(TimePatternDataBorrowed::Resolved( - payload.get(options.length, variant), + payload.get(options.length(), variant), options.alignment, prefs.hour_cycle, subsecond_digits, @@ -357,23 +369,20 @@ impl ZonePatternSelectionData { symbol: FieldSymbol::TimeZone(symbol), length, }); - Self::SinglePatternItem(symbol, length, pattern_item.to_unaligned()) + Self::SinglePatternItem(field_set, pattern_item.to_unaligned()) } /// Borrows a pattern containing all of the fields that need to be loaded. #[inline] pub(crate) fn pattern_items_for_data_loading(&self) -> impl Iterator + '_ { - let Self::SinglePatternItem(symbol, length, _) = self; - [PatternItem::Field(Field { - symbol: FieldSymbol::TimeZone(*symbol), - length: *length, - })] - .into_iter() + let Self::SinglePatternItem(_, pattern_item) = self; + let pattern_item = PatternItem::from_unaligned(*pattern_item); + [pattern_item].into_iter() } /// Borrows a resolved pattern based on the given datetime pub(crate) fn select(&self, _input: &ExtractedInput) -> ZonePatternDataBorrowed { - let Self::SinglePatternItem(_, _, pattern_item) = self; + let Self::SinglePatternItem(_, pattern_item) = self; ZonePatternDataBorrowed::SinglePatternItem(pattern_item) } } @@ -451,7 +460,8 @@ impl DateTimeZonePatternSelectionData { let selection = ZonePatternSelectionData::new_with_skeleton(field_set); Ok(Self { options: RawOptions { - length: Length::Medium, // not used + length: None, + date_fields: None, year_style: None, alignment: None, time_precision: None, @@ -589,7 +599,7 @@ impl DateTimeZonePatternSelectionData { marker_attrs::pattern_marker_attr_for_glue( // According to UTS 35, use the date length here: use the glue // pattern "whose type matches the type of the date pattern" - match options.length { + match options.length() { Length::Long => marker_attrs::PatternLength::Long, Length::Medium => marker_attrs::PatternLength::Medium, Length::Short => marker_attrs::PatternLength::Short, @@ -630,6 +640,22 @@ impl DateTimeZonePatternSelectionData { glue: self.glue.as_ref().map(|glue| glue.get()), } } + + /// Converts one of these into a corresponding [`builder::FieldSetBuilder`] + pub(crate) fn to_builder(&self) -> builder::FieldSetBuilder { + let zone_style = self.zone.as_ref().map(|zone| { + let ZonePatternSelectionData::SinglePatternItem(field_set, _) = zone; + field_set.to_zone_style() + }); + builder::FieldSetBuilder { + length: self.options.length, + date_fields: self.options.date_fields, + time_precision: self.options.time_precision, + zone_style, + alignment: self.options.alignment, + year_style: self.options.year_style, + } + } } impl<'a> DateTimeZonePatternDataBorrowed<'a> { diff --git a/ffi/capi/tests/missing_apis.txt b/ffi/capi/tests/missing_apis.txt index 9d236dd14b5..1a4beedfe5f 100644 --- a/ffi/capi/tests/missing_apis.txt +++ b/ffi/capi/tests/missing_apis.txt @@ -14,6 +14,8 @@ # Please check in with @Manishearth, @robertbastian, or @sffc if you have questions +icu::datetime::DateTimeFormatter::to_field_set_builder#FnInStruct +icu::datetime::FixedCalendarDateTimeFormatter::to_field_set_builder#FnInStruct icu::datetime::fieldsets::Combo#Struct icu::datetime::fieldsets::Combo::into_enums#FnInStruct icu::datetime::fieldsets::D#Struct