diff --git a/components/datetime/src/datetime.rs b/components/datetime/src/datetime.rs index 01af677e04a..3dbb1f65c7b 100644 --- a/components/datetime/src/datetime.rs +++ b/components/datetime/src/datetime.rs @@ -3,10 +3,8 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::{ - fields::FieldSymbol, format::datetime, options::DateTimeFormatOptions, - pattern::PatternItem, provider::{ gregory::{DatePatternsV1Marker, DateSymbolsV1Marker}, helpers::DateTimePatterns, @@ -62,7 +60,7 @@ use crate::{ pub struct DateTimeFormat<'d> { pub(super) locale: Locale, pub(super) pattern: Pattern, - pub(super) symbols: DataPayload<'d, 'd, DateSymbolsV1Marker>, + pub(super) symbols: Option>, } impl<'d> DateTimeFormat<'d> { @@ -99,17 +97,6 @@ impl<'d> DateTimeFormat<'d> { options: &DateTimeFormatOptions, ) -> Result { let locale = locale.into(); - let symbols_data = data_provider - .load_payload(&DataRequest { - resource_path: ResourcePath { - key: provider::key::GREGORY_DATE_SYMBOLS_V1, - options: ResourceOptions { - variant: None, - langid: Some(locale.clone().into()), - }, - }, - })? - .take_payload()?; let patterns_data: icu_provider::DataPayload< '_, @@ -132,18 +119,26 @@ impl<'d> DateTimeFormat<'d> { .get_pattern_for_options(options)? .unwrap_or_default(); - let time_zone_field = pattern - .items() - .iter() - .filter_map(|p| match p { - PatternItem::Field(field) => Some(field), - _ => None, - }) - .find(|field| matches!(field.symbol, FieldSymbol::TimeZone(_))); + let requires_data = datetime::analyze_pattern(&pattern, false) + .map_err(|field| DateTimeFormatError::UnsupportedField(field.symbol))?; - if let Some(field) = time_zone_field { - return Err(DateTimeFormatError::UnsupportedField(field.symbol)); - } + let symbols_data = if requires_data { + Some( + data_provider + .load_payload(&DataRequest { + resource_path: ResourcePath { + key: provider::key::GREGORY_DATE_SYMBOLS_V1, + options: ResourceOptions { + variant: None, + langid: Some(locale.clone().into()), + }, + }, + })? + .take_payload()?, + ) + } else { + None + }; Ok(Self::new(locale, pattern, symbols_data)) } @@ -164,7 +159,7 @@ impl<'d> DateTimeFormat<'d> { pub(super) fn new>( locale: T, pattern: Pattern, - symbols: DataPayload<'d, 'd, DateSymbolsV1Marker>, + symbols: Option>, ) -> Self { let locale = locale.into(); @@ -209,7 +204,7 @@ impl<'d> DateTimeFormat<'d> { { FormattedDateTime { pattern: &self.pattern, - symbols: self.symbols.get(), + symbols: self.symbols.as_ref().map(|s| s.get()), datetime: value, locale: &self.locale, } @@ -246,8 +241,14 @@ impl<'d> DateTimeFormat<'d> { w: &mut impl std::fmt::Write, value: &impl DateTimeInput, ) -> std::fmt::Result { - datetime::write_pattern(&self.pattern, self.symbols.get(), value, &self.locale, w) - .map_err(|_| std::fmt::Error) + datetime::write_pattern( + &self.pattern, + self.symbols.as_ref().map(|s| s.get()), + value, + &self.locale, + w, + ) + .map_err(|_| std::fmt::Error) } /// Takes a [`DateTimeInput`] implementer and returns it formatted as a string. diff --git a/components/datetime/src/format/datetime.rs b/components/datetime/src/format/datetime.rs index 7c1558e787d..860c8df0a8e 100644 --- a/components/datetime/src/format/datetime.rs +++ b/components/datetime/src/format/datetime.rs @@ -5,7 +5,7 @@ use crate::arithmetic; use crate::date::{DateTimeInput, DateTimeInputWithLocale, LocalizedDateTimeInput}; use crate::error::DateTimeFormatError as Error; -use crate::fields::{self, FieldLength, FieldSymbol}; +use crate::fields::{self, Field, FieldLength, FieldSymbol}; use crate::pattern::{Pattern, PatternItem}; use crate::provider; use crate::provider::helpers::DateTimeSymbols; @@ -46,7 +46,7 @@ where T: DateTimeInput, { pub(crate) pattern: &'l Pattern, - pub(crate) symbols: &'l provider::gregory::DateSymbolsV1, + pub(crate) symbols: Option<&'l provider::gregory::DateSymbolsV1>, pub(crate) datetime: &'l T, pub(crate) locale: &'l Locale, } @@ -95,7 +95,7 @@ where pub fn write_pattern( pattern: &crate::pattern::Pattern, - symbols: &provider::gregory::DateSymbolsV1, + symbols: Option<&provider::gregory::DateSymbolsV1>, datetime: &T, locale: &Locale, w: &mut W, @@ -114,10 +114,15 @@ where Ok(()) } +// This function assumes that the correct decision has been +// made regarding availability of symbols in the caller. +// +// When modifying the list of fields using symbols, +// update the matching query in `analyze_pattern` function. pub(super) fn write_field( pattern: &crate::pattern::Pattern, field: &fields::Field, - symbols: &crate::provider::gregory::DateSymbolsV1, + symbols: Option<&crate::provider::gregory::DateSymbolsV1>, datetime: &impl LocalizedDateTimeInput, w: &mut W, ) -> Result<(), Error> @@ -146,16 +151,18 @@ where field.length, )?, length => { - let symbol = symbols.get_symbol_for_month( - month, - length, - datetime - .datetime() - .month() - .ok_or(Error::MissingInputField)? - .number as usize - - 1, - ); + let symbol = symbols + .expect("Expect symbols to be present") + .get_symbol_for_month( + month, + length, + datetime + .datetime() + .month() + .ok_or(Error::MissingInputField)? + .number as usize + - 1, + ); w.write_str(symbol)? } }, @@ -164,7 +171,9 @@ where .datetime() .iso_weekday() .ok_or(Error::MissingInputField)?; - let symbol = symbols.get_symbol_for_weekday(weekday, field.length, dow); + let symbol = symbols + .expect("Expect symbols to be present") + .get_symbol_for_weekday(weekday, field.length, dow); w.write_str(symbol)? } FieldSymbol::Day(..) => format_number( @@ -221,16 +230,18 @@ where field.length, )?, FieldSymbol::DayPeriod(period) => { - let symbol = symbols.get_symbol_for_day_period( - period, - field.length, - datetime.datetime().hour().ok_or(Error::MissingInputField)?, - arithmetic::is_top_of_hour( - pattern, - datetime.datetime().minute().map(u8::from).unwrap_or(0), - datetime.datetime().second().map(u8::from).unwrap_or(0), - ), - ); + let symbol = symbols + .expect("Expect symbols to be present") + .get_symbol_for_day_period( + period, + field.length, + datetime.datetime().hour().ok_or(Error::MissingInputField)?, + arithmetic::is_top_of_hour( + pattern, + datetime.datetime().minute().map(u8::from).unwrap_or(0), + datetime.datetime().second().map(u8::from).unwrap_or(0), + ), + ); w.write_str(symbol)? } field @ FieldSymbol::TimeZone(_) => return Err(Error::UnsupportedField(field)), @@ -238,6 +249,43 @@ where Ok(()) } +// This function determins whether the struct will load symbols data. +// Keep it in sync with the `write_field` use of symbols. +pub fn analyze_pattern(pattern: &Pattern, supports_time_zones: bool) -> Result { + let fields = pattern.items().iter().filter_map(|p| match p { + PatternItem::Field(field) => Some(field), + _ => None, + }); + + let mut requires_symbols = false; + + for field in fields { + if !requires_symbols { + requires_symbols = match field.symbol { + FieldSymbol::Month(_) => { + !matches!(field.length, FieldLength::One | FieldLength::TwoDigit) + } + FieldSymbol::Weekday(_) | FieldSymbol::DayPeriod(_) => true, + _ => false, + } + } + + if supports_time_zones { + if requires_symbols { + // If we require time zones, and symbols, we know all + // we need to return already. + break; + } + } else if matches!(field.symbol, FieldSymbol::TimeZone(_)) { + // If we don't support time zones, and encountered a time zone + // field, error out. + return Err(field); + } + } + + Ok(requires_symbols) +} + #[cfg(test)] mod tests { use super::*; @@ -268,7 +316,7 @@ mod tests { let mut sink = String::new(); write_pattern( &pattern, - &data.get(), + Some(&data.get()), &datetime, &"und".parse().unwrap(), &mut sink, diff --git a/components/datetime/src/format/zoned_datetime.rs b/components/datetime/src/format/zoned_datetime.rs index 5f6a6c281ad..a1a1c859f6a 100644 --- a/components/datetime/src/format/zoned_datetime.rs +++ b/components/datetime/src/format/zoned_datetime.rs @@ -77,7 +77,11 @@ where W: fmt::Write + ?Sized, { let pattern = &zoned_datetime_format.datetime_format.pattern; - let symbols = zoned_datetime_format.datetime_format.symbols.get(); + let symbols = zoned_datetime_format + .datetime_format + .symbols + .as_ref() + .map(|s| s.get()); match field.symbol { FieldSymbol::TimeZone(_time_zone) => time_zone::write_field( diff --git a/components/datetime/src/zoned_datetime.rs b/components/datetime/src/zoned_datetime.rs index 61a1e8b004a..cf292769990 100644 --- a/components/datetime/src/zoned_datetime.rs +++ b/components/datetime/src/zoned_datetime.rs @@ -8,7 +8,10 @@ use icu_provider::{DataProvider, DataRequest, ResourceOptions, ResourcePath}; use crate::{ date::ZonedDateTimeInput, datetime::DateTimeFormat, - format::zoned_datetime::{self, FormattedZonedDateTime}, + format::{ + datetime, + zoned_datetime::{self, FormattedZonedDateTime}, + }, options::DateTimeFormatOptions, provider::{self, helpers::DateTimePatterns}, time_zone::TimeZoneFormat, @@ -124,27 +127,32 @@ impl<'d> ZonedDateTimeFormat<'d> { })? .take_payload()?; - let symbols_data: icu_provider::DataPayload< - '_, - '_, - provider::gregory::DateSymbolsV1Marker, - > = date_provider - .load_payload(&DataRequest { - resource_path: ResourcePath { - key: provider::key::GREGORY_DATE_SYMBOLS_V1, - options: ResourceOptions { - variant: None, - langid: Some(locale.clone().into()), - }, - }, - })? - .take_payload()?; - let pattern = pattern_data .get() .get_pattern_for_options(options)? .unwrap_or_default(); + let requires_data = datetime::analyze_pattern(&pattern, true) + .map_err(|field| DateTimeFormatError::UnsupportedField(field.symbol))?; + + let symbols_data = if requires_data { + Some( + date_provider + .load_payload(&DataRequest { + resource_path: ResourcePath { + key: provider::key::GREGORY_DATE_SYMBOLS_V1, + options: ResourceOptions { + variant: None, + langid: Some(locale.clone().into()), + }, + }, + })? + .take_payload()?, + ) + } else { + None + }; + let datetime_format = DateTimeFormat::new(locale, pattern, symbols_data); let time_zone_format = TimeZoneFormat::try_new( datetime_format.locale.clone(),