Skip to content

Commit

Permalink
Only load symbols data if pattern requires them. (#791)
Browse files Browse the repository at this point in the history
  • Loading branch information
zbraniecki authored Jun 18, 2021
1 parent 7636296 commit c5135af
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 73 deletions.
59 changes: 30 additions & 29 deletions components/datetime/src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<DataPayload<'d, 'd, DateSymbolsV1Marker>>,
}

impl<'d> DateTimeFormat<'d> {
Expand Down Expand Up @@ -99,17 +97,6 @@ impl<'d> DateTimeFormat<'d> {
options: &DateTimeFormatOptions,
) -> Result<Self, DateTimeFormatError> {
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<
'_,
Expand All @@ -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))
}
Expand All @@ -164,7 +159,7 @@ impl<'d> DateTimeFormat<'d> {
pub(super) fn new<T: Into<Locale>>(
locale: T,
pattern: Pattern,
symbols: DataPayload<'d, 'd, DateSymbolsV1Marker>,
symbols: Option<DataPayload<'d, 'd, DateSymbolsV1Marker>>,
) -> Self {
let locale = locale.into();

Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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.
Expand Down
100 changes: 74 additions & 26 deletions components/datetime/src/format/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -95,7 +95,7 @@ where

pub fn write_pattern<T, W>(
pattern: &crate::pattern::Pattern,
symbols: &provider::gregory::DateSymbolsV1,
symbols: Option<&provider::gregory::DateSymbolsV1>,
datetime: &T,
locale: &Locale,
w: &mut W,
Expand All @@ -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<T, W>(
pattern: &crate::pattern::Pattern,
field: &fields::Field,
symbols: &crate::provider::gregory::DateSymbolsV1,
symbols: Option<&crate::provider::gregory::DateSymbolsV1>,
datetime: &impl LocalizedDateTimeInput<T>,
w: &mut W,
) -> Result<(), Error>
Expand Down Expand Up @@ -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)?
}
},
Expand All @@ -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(
Expand Down Expand Up @@ -221,23 +230,62 @@ 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)),
};
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<bool, &Field> {
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::*;
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion components/datetime/src/format/zoned_datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
42 changes: 25 additions & 17 deletions components/datetime/src/zoned_datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down

0 comments on commit c5135af

Please sign in to comment.