Skip to content

Commit

Permalink
Implement and test FormattedNeoDateTime::pattern(), refactoring cod…
Browse files Browse the repository at this point in the history
…e to enable it (#5461)
  • Loading branch information
sffc authored Aug 28, 2024
1 parent cc0199a commit b8d9847
Show file tree
Hide file tree
Showing 23 changed files with 257 additions and 160 deletions.
1 change: 0 additions & 1 deletion components/datetime/src/fields/length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ impl FieldLength {
}

#[inline]
#[cfg(feature = "datagen")]
pub(crate) fn to_len(self) -> usize {
match self {
FieldLength::One => 1,
Expand Down
47 changes: 6 additions & 41 deletions components/datetime/src/format/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use crate::fields::{self, Field, FieldLength, FieldSymbol, Second, Week, Year};
use crate::input::{DateInput, ExtractedDateTimeInput, ExtractedTimeZoneInput, IsoTimeInput};
use crate::options::preferences::HourCycle;
use crate::pattern::runtime::{PatternBorrowed, PatternMetadata};
use crate::pattern::{
runtime::{Pattern, PatternPlurals},
Expand All @@ -27,7 +26,6 @@ use crate::time_zone::{
};
use crate::time_zone::{IsoFormat, IsoMinutes, IsoSeconds, ResolvedNeoTimeZoneSkeleton};

use super::FormattingOptions;
use core::fmt::{self, Write};
use fixed_decimal::FixedDecimal;
use icu_calendar::types::{
Expand Down Expand Up @@ -134,7 +132,6 @@ impl<'l> Writeable for FormattedDateTime<'l> {

r = r.and(try_write_pattern(
pattern.as_borrowed(),
Default::default(),
&self.datetime,
self.date_symbols,
self.time_symbols,
Expand Down Expand Up @@ -199,7 +196,6 @@ where
#[allow(clippy::too_many_arguments)]
pub(crate) fn try_write_pattern<'data, W, DS, TS, ZS>(
pattern: PatternBorrowed<'data>,
formatting_options: FormattingOptions,
datetime: &ExtractedDateTimeInput,
date_symbols: Option<&DS>,
time_symbols: Option<&TS>,
Expand All @@ -217,7 +213,6 @@ where
try_write_pattern_items(
pattern.metadata,
pattern.items.iter(),
formatting_options,
datetime,
date_symbols,
time_symbols,
Expand All @@ -232,7 +227,6 @@ where
pub(crate) fn try_write_pattern_items<'data, W, DS, TS, ZS>(
pattern_metadata: PatternMetadata,
pattern_items: impl Iterator<Item = PatternItem>,
formatting_options: FormattingOptions,
datetime: &ExtractedDateTimeInput,
date_symbols: Option<&DS>,
time_symbols: Option<&TS>,
Expand Down Expand Up @@ -268,7 +262,6 @@ where
r = r.and(try_write_field(
field,
pattern_metadata,
formatting_options,
datetime,
date_symbols,
time_symbols,
Expand Down Expand Up @@ -351,7 +344,6 @@ pub enum DateTimeWriteError {
pub(crate) fn try_write_field<'data, W, DS, TS>(
field: fields::Field,
pattern_metadata: PatternMetadata,
formatting_options: FormattingOptions,
datetime: &ExtractedDateTimeInput,
date_symbols: Option<&DS>,
time_symbols: Option<&TS>,
Expand All @@ -376,33 +368,7 @@ where
})
}

#[cfg_attr(not(feature = "experimental"), allow(unused_mut))]
let mut field_symbol = field.symbol;
#[cfg(feature = "experimental")]
if let Some(fractional_second_digits) = formatting_options.fractional_second_digits {
if matches!(
field_symbol,
FieldSymbol::Second(fields::Second::Second) | FieldSymbol::DecimalSecond(_)
) {
field_symbol = FieldSymbol::from_fractional_second_digits(fractional_second_digits);
}
}

let mut field_length = field.length;
if formatting_options.force_2_digit_month_day_week_hour
&& field_length == FieldLength::One
&& matches!(
field_symbol,
FieldSymbol::Month(_)
| FieldSymbol::Day(_)
| FieldSymbol::Week(_)
| FieldSymbol::Hour(_)
)
{
field_length = FieldLength::TwoDigit;
}

Ok(match (field_symbol, field_length) {
Ok(match (field.symbol, field.length) {
(FieldSymbol::Era, l) => match datetime.year() {
None => {
write_value_missing(w, field)?;
Expand Down Expand Up @@ -663,18 +629,18 @@ where
}
Some(h) => {
let h = usize::from(h) as isize;
let h = match (hour, formatting_options.hour_cycle) {
(fields::Hour::H11, None) | (_, Some(HourCycle::H11)) => h % 12,
(fields::Hour::H12, None) | (_, Some(HourCycle::H12)) => {
let h = match hour {
fields::Hour::H11 => h % 12,
fields::Hour::H12 => {
let v = h % 12;
if v == 0 {
12
} else {
v
}
}
(fields::Hour::H23, None) | (_, Some(HourCycle::H23)) => h,
(fields::Hour::H24, None) | (_, Some(HourCycle::H24)) => {
fields::Hour::H23 => h,
fields::Hour::H24 => {
if h == 0 {
24
} else {
Expand Down Expand Up @@ -1192,7 +1158,6 @@ mod tests {
let mut sink = String::new();
try_write_pattern(
pattern.as_borrowed(),
Default::default(),
&ExtractedDateTimeInput::extract_from(&datetime),
Some(date_data.payload.get()),
Some(time_data.payload.get()),
Expand Down
13 changes: 0 additions & 13 deletions components/datetime/src/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,8 @@
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

#[cfg(feature = "experimental")]
use crate::neo_skeleton::FractionalSecondDigits;
use crate::options::preferences::HourCycle;

pub mod datetime;
#[cfg(feature = "experimental")]
pub mod neo;
pub mod time_zone;
pub mod zoned_datetime;

/// Internal non-pattern runtime options passed to the formatter
#[derive(Debug, Copy, Clone, Default)]
pub(crate) struct FormattingOptions {
pub(crate) hour_cycle: Option<HourCycle>,
pub(crate) force_2_digit_month_day_week_hour: bool,
#[cfg(feature = "experimental")]
pub(crate) fractional_second_digits: Option<FractionalSecondDigits>,
}
1 change: 0 additions & 1 deletion components/datetime/src/format/neo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2294,7 +2294,6 @@ impl<'a> TryWriteable for FormattedDateTimePattern<'a> {
) -> Result<Result<(), Self::Error>, fmt::Error> {
try_write_pattern(
self.pattern.0.as_borrowed(),
Default::default(),
&self.datetime,
Some(&self.names),
Some(&self.names),
Expand Down
1 change: 0 additions & 1 deletion components/datetime/src/format/zoned_datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ where
r = r.and(datetime::try_write_field(
field,
pattern.metadata,
Default::default(),
datetime,
date_symbols,
time_symbols,
Expand Down
1 change: 0 additions & 1 deletion components/datetime/src/neo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,7 +1469,6 @@ impl<'a> TryWriteable for FormattedNeoDateTime<'a> {
try_write_pattern_items(
self.pattern.metadata(),
self.pattern.iter_items(),
self.pattern.formatting_options(),
&self.datetime,
Some(&self.names),
Some(&self.names),
Expand Down
23 changes: 22 additions & 1 deletion components/datetime/src/neo_pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use core::str::FromStr;

use writeable::{impl_display_with_writeable, Writeable};

use crate::helpers::size_test;
use crate::pattern::{runtime, PatternError, PatternItem};

Expand All @@ -20,6 +22,12 @@ size_test!(DateTimePattern, date_time_pattern_size, 32);
///
/// 1. From a custom pattern string: [`DateTimePattern::try_from_pattern_str`]
/// 2. From a formatted datetime: [`FormattedNeoDateTime::pattern`]
///
/// There are two things you can do with one of these:
///
/// 1. Use it to directly format a datetime via [`TypedDateTimeNames`]
/// 2. Convert it to a string pattern via [`Writeable`]
///
#[doc = date_time_pattern_size!()]
///
/// # Examples
Expand All @@ -34,9 +42,12 @@ size_test!(DateTimePattern, date_time_pattern_size, 32);
/// use icu::datetime::neo_pattern::DateTimePattern;
/// use icu::datetime::neo_skeleton::NeoSkeletonLength;
/// use icu::locale::locale;
/// use writeable::assert_writeable_eq;
///
/// let pattern_str = "d MMM y";
/// let custom_pattern =
/// DateTimePattern::try_from_pattern_str("d MMM y").unwrap();
/// DateTimePattern::try_from_pattern_str(pattern_str).unwrap();
/// assert_writeable_eq!(custom_pattern, pattern_str);
///
/// let data_pattern =
/// TypedNeoFormatter::<Gregorian, NeoYearMonthDayMarker>::try_new(
Expand All @@ -49,11 +60,13 @@ size_test!(DateTimePattern, date_time_pattern_size, 32);
/// .format(&DateTime::local_unix_epoch().to_calendar(Gregorian))
/// .pattern();
///
/// assert_writeable_eq!(data_pattern, pattern_str);
/// assert_eq!(custom_pattern, data_pattern);
/// ```
///
/// [`DateTimeFormatter`]: crate::DateTimeFormatter
/// [`FormattedNeoDateTime::pattern`]: crate::neo::FormattedNeoDateTime::pattern
/// [`TypedDateTimeNames`]: crate::TypedDateTimeNames
#[derive(Debug)]
pub struct DateTimePattern {
pattern: runtime::Pattern<'static>,
Expand Down Expand Up @@ -100,6 +113,14 @@ impl PartialEq for DateTimePattern {

impl Eq for DateTimePattern {}

impl Writeable for DateTimePattern {
fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
self.pattern.write_to(sink)
}
}

impl_display_with_writeable!(DateTimePattern);

// Not clear if this needs to be public. For now, make it private.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) struct DateTimePatternBorrowed<'a>(pub(crate) &'a runtime::Pattern<'a>);
2 changes: 1 addition & 1 deletion components/datetime/src/pattern/common/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ mod reference {
S: ser::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_string())
serializer.serialize_str(&self.to_runtime_pattern().to_string())
} else {
let pfs = PatternForSerde::from(self);
pfs.serialize(serializer)
Expand Down
4 changes: 2 additions & 2 deletions components/datetime/src/pattern/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ impl TimeGranularity {
}
}

impl From<&PatternItem> for TimeGranularity {
impl From<PatternItem> for TimeGranularity {
/// Retrieves the granularity of time represented by a [`PatternItem`].
/// If the [`PatternItem`] is not time-related, returns [`None`].
fn from(item: &PatternItem) -> Self {
fn from(item: PatternItem) -> Self {
match item {
PatternItem::Field(field) => match field.symbol {
fields::FieldSymbol::Hour(_) => Self::Hours,
Expand Down
2 changes: 1 addition & 1 deletion components/datetime/src/pattern/reference/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ mod test {
let pattern = pattern
.combined(vec![date, time])
.expect("Failed to combine date and time.");
assert_eq!(pattern.to_string(), "Y/m/d 'at' HH:mm");
assert_eq!(pattern.to_runtime_pattern().to_string(), "Y/m/d 'at' HH:mm");
}
}
2 changes: 1 addition & 1 deletion components/datetime/src/pattern/reference/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//! for parsing/inspecting/modifying and serialization.
//!
//! The runtime `Pattern` uses parsing/serialization from this module.
mod display;

mod generic;
mod parser;
pub(crate) mod pattern;
Expand Down
13 changes: 12 additions & 1 deletion components/datetime/src/pattern/reference/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::pattern::runtime;

use super::{
super::{PatternError, PatternItem, TimeGranularity},
Parser,
Expand All @@ -23,12 +25,21 @@ impl Pattern {
pub fn items_mut(&mut self) -> &mut [PatternItem] {
&mut self.items
}

pub fn to_runtime_pattern(&self) -> runtime::Pattern<'static> {
runtime::Pattern::from(self)
}
}

impl From<Vec<PatternItem>> for Pattern {
fn from(items: Vec<PatternItem>) -> Self {
Self {
time_granularity: items.iter().map(Into::into).max().unwrap_or_default(),
time_granularity: items
.iter()
.copied()
.map(Into::into)
.max()
.unwrap_or_default(),
items,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

#![cfg(feature = "datagen")]

//! Set of `Display` implementations for reference and runtime `Pattern`.

use super::{
Expand All @@ -13,11 +11,12 @@ use super::{
use crate::fields::FieldSymbol;
use alloc::string::String;
use core::fmt::{self, Write};
use writeable::{impl_display_with_writeable, Writeable};

/// A helper function optimized to dump string buffers into `Pattern`
/// serialization wrapping minimal chunks of the buffer in escaping `'`
/// literals to produce valid UTF35 pattern string.
fn dump_buffer_into_formatter(literal: &str, formatter: &mut fmt::Formatter) -> fmt::Result {
fn dump_buffer_into_formatter<W: Write + ?Sized>(literal: &str, formatter: &mut W) -> fmt::Result {
if literal.is_empty() {
return Ok(());
}
Expand Down Expand Up @@ -69,12 +68,9 @@ fn dump_buffer_into_formatter(literal: &str, formatter: &mut fmt::Formatter) ->
}

/// This trait is implemented in order to provide the machinery to convert a [`Pattern`] to a UTS 35
/// pattern string. It could also be implemented as the Writeable trait, but at the time of writing
/// this was not done, as this code would need to implement the [`writeable_length_hint()`] method,
/// which would need to duplicate the branching logic of the [`fmt`](std::fmt) method here. This code
/// is used in generating the data providers and is not as performance sensitive.
impl fmt::Display for Pattern {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
/// pattern string.
impl Writeable for Pattern<'_> {
fn write_to<W: Write + ?Sized>(&self, formatter: &mut W) -> fmt::Result {
let mut buffer = String::new();
for pattern_item in self.items.iter() {
match pattern_item {
Expand All @@ -93,7 +89,7 @@ impl fmt::Display for Pattern {
}
}
PatternItem::Literal(ch) => {
buffer.push(*ch);
buffer.push(ch);
}
}
}
Expand All @@ -103,8 +99,10 @@ impl fmt::Display for Pattern {
}
}

impl fmt::Display for GenericPattern {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
impl_display_with_writeable!(Pattern<'_>);

impl Writeable for GenericPattern<'_> {
fn write_to<W: Write + ?Sized>(&self, formatter: &mut W) -> fmt::Result {
let mut buffer = alloc::string::String::new();
for pattern_item in self.items.iter() {
match pattern_item {
Expand All @@ -114,7 +112,7 @@ impl fmt::Display for GenericPattern {
write!(formatter, "{{{idx}}}")?;
}
GenericPatternItem::Literal(ch) => {
buffer.push(*ch);
buffer.push(ch);
}
}
}
Expand All @@ -123,3 +121,5 @@ impl fmt::Display for GenericPattern {
Ok(())
}
}

impl_display_with_writeable!(GenericPattern<'_>);
Loading

0 comments on commit b8d9847

Please sign in to comment.