Skip to content

Commit

Permalink
Add ability to convert DateTimeFormatter to FieldSetBuilder (#6251)
Browse files Browse the repository at this point in the history
  • Loading branch information
sffc authored Mar 7, 2025
1 parent 1fc4f03 commit a3bd57d
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 19 deletions.
9 changes: 8 additions & 1 deletion components/datetime/src/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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)),+,]) => {
Expand Down
6 changes: 5 additions & 1 deletion components/datetime/src/fieldsets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,)?
Expand Down Expand Up @@ -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)?),
Expand Down Expand Up @@ -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,)?
);
Expand Down Expand Up @@ -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,
Expand Down
109 changes: 109 additions & 0 deletions components/datetime/src/neo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -771,6 +772,60 @@ impl<C: CldrCalendar, FSet: DateTimeMarkers> FixedCalendarDateTimeFormatter<C, F
_calendar: PhantomData,
}
}

/// 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::FixedCalendarDateTimeFormatter;
/// use icu::locale::locale;
/// use writeable::assert_writeable_eq;
///
/// // Create a simple YMDT formatter:
/// let formatter = FixedCalendarDateTimeFormatter::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 = FixedCalendarDateTimeFormatter::try_new(
/// locale!("und").into(),
/// builder.build_composite_datetime().unwrap(),
/// )
/// .unwrap();
/// let datetime = DateTime {
/// date: Date::try_new_gregorian(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()
}
}

impl<FSet: DateTimeMarkers> DateTimeFormatter<FSet> {
Expand Down Expand Up @@ -903,6 +958,60 @@ impl<FSet: DateTimeMarkers> DateTimeFormatter<FSet> {
pub fn calendar(&self) -> icu_calendar::Ref<AnyCalendar> {
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.
Expand Down
60 changes: 43 additions & 17 deletions components/datetime/src/raw/neo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -22,12 +23,23 @@ use zerovec::ZeroSlice;

#[derive(Debug, Copy, Clone)]
pub(crate) struct RawOptions {
pub(crate) length: Length,
pub(crate) length: Option<Length>,
pub(crate) date_fields: Option<builder::DateFields>,
pub(crate) alignment: Option<Alignment>,
pub(crate) year_style: Option<YearStyle>,
pub(crate) time_precision: Option<TimePrecision>,
}

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<fields::Hour>,
Expand Down Expand Up @@ -73,7 +85,7 @@ pub(crate) enum TimePatternDataBorrowed<'a> {

#[derive(Debug, Clone)]
pub(crate) enum ZonePatternSelectionData {
SinglePatternItem(TimeZone, FieldLength, <PatternItem as AsULE>::ULE),
SinglePatternItem(ZoneFieldSet, <PatternItem as AsULE>::ULE),
}

#[derive(Debug, Copy, Clone)]
Expand Down Expand Up @@ -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(),
)
Expand Down Expand Up @@ -188,7 +200,7 @@ impl DatePatternSelectionData {
}
};
Some(DatePatternDataBorrowed::Resolved(
payload.get(options.length, variant),
payload.get(options.length(), variant),
options.alignment,
))
}
Expand Down Expand Up @@ -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(),
)
Expand All @@ -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,
Expand All @@ -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<Item = PatternItem> + '_ {
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)
}
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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> {
Expand Down
2 changes: 2 additions & 0 deletions ffi/capi/tests/missing_apis.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit a3bd57d

Please sign in to comment.