Skip to content

Commit

Permalink
Add Fractional Second Digits to neo datetime (#5322)
Browse files Browse the repository at this point in the history
  • Loading branch information
sffc authored Aug 5, 2024
1 parent cb02882 commit adde60a
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 72 deletions.
95 changes: 50 additions & 45 deletions components/datetime/src/format/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::time_zone::{
TimeZoneFormatterUnit,
};

use super::FormattingOptions;
use core::fmt::{self, Write};
use core::iter::Peekable;
use fixed_decimal::FixedDecimal;
Expand Down Expand Up @@ -133,6 +134,7 @@ 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 @@ -203,6 +205,7 @@ 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 @@ -220,6 +223,7 @@ where
try_write_pattern_items(
pattern.metadata,
pattern.items.iter(),
formatting_options,
datetime,
date_symbols,
time_symbols,
Expand All @@ -234,6 +238,7 @@ 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 @@ -271,6 +276,7 @@ where
field,
&mut iter,
pattern_metadata,
formatting_options,
datetime,
date_symbols,
time_symbols,
Expand Down Expand Up @@ -354,6 +360,7 @@ pub(crate) fn try_write_field<'data, W, DS, TS>(
field: fields::Field,
iter: &mut Peekable<impl Iterator<Item = PatternItem>>,
pattern_metadata: PatternMetadata,
formatting_options: FormattingOptions,
datetime: &ExtractedDateTimeInput,
date_symbols: Option<&DS>,
time_symbols: Option<&TS>,
Expand Down Expand Up @@ -668,69 +675,66 @@ where
}
Some(iso_minute) => try_write_number(w, fdf, usize::from(iso_minute).into(), l)?,
},
(FieldSymbol::Second(Second::Second), l) => match (datetime.second(), iter.peek()) {
(
None,
Some(&PatternItem::Field(
next_field @ Field {
symbol: FieldSymbol::Second(Second::FractionalSecond),
..
},
)),
) => {
iter.next(); // Advance over nanosecond symbol
write_value_missing(w, field)?;
// Write error value for nanos even if we have them
write_value_missing(w, next_field)?;
Err(DateTimeWriteError::MissingInputField("second"))
}
(None, _) => {
(FieldSymbol::Second(Second::Second), l) => match datetime.second() {
None => {
write_value_missing(w, field)?;
Err(DateTimeWriteError::MissingInputField("second"))
}
(
Some(second),
Some(&PatternItem::Field(
next_field @ Field {
symbol: FieldSymbol::Second(Second::FractionalSecond),
length,
},
)),
) => {
iter.next(); // Advance over nanosecond symbol
let r = datetime
.nanosecond()
.ok_or(DateTimeWriteError::MissingInputField("nanosecond"))
.and_then(|ns| {
// We only support fixed field length for fractional seconds.
let FieldLength::Fixed(p) = length else {
return Err(DateTimeWriteError::UnsupportedField(next_field));
};
Ok((ns, p))
});
match r {
Err(e) => {
Some(second) => {
match match (iter.peek(), formatting_options.fractional_second_digits) {
(
Some(&PatternItem::Field(
next_field @ Field {
symbol: FieldSymbol::Second(Second::FractionalSecond),
length: FieldLength::Fixed(p),
},
)),
_,
) => {
// Fractional second digits via field symbol
iter.next(); // Advance over nanosecond symbol
Some((-(p as i16), Some(next_field), datetime.nanosecond()))
}
(_, Some(p)) => {
// Fractional second digits via semantic option
Some((-(p as i16), None, datetime.nanosecond()))
}
_ => None,
} {
Some((_, maybe_next_field, None)) => {
// Request to format nanoseconds but we don't have nanoseconds
let seconds_result =
try_write_number(w, fdf, usize::from(second).into(), l)?;
write_value_missing(w, next_field)?;
if let Some(next_field) = maybe_next_field {
write_value_missing(w, next_field)?;
}
// Return the earlier error
seconds_result.and(Err(e))
seconds_result.and(Err(DateTimeWriteError::MissingInputField("nanosecond")))
}
Ok((ns, p)) => {
Some((position, maybe_next_field, Some(ns))) => {
// Formatting with fractional seconds
let mut s = FixedDecimal::from(usize::from(second));
let _infallible = s.concatenate_end(
FixedDecimal::from(usize::from(ns)).multiplied_pow10(-9),
);
debug_assert!(_infallible.is_ok());
s.pad_end(-(p as i16));
s.pad_end(position);
if maybe_next_field.is_none() {
// Truncate on semantic option but not "S" field
// TODO: Does this make sense?
s.trunc(position);
}
try_write_number(w, fdf, s, l)?
}
None => {
// Normal seconds formatting with no fractional second digits
try_write_number(w, fdf, usize::from(second).into(), l)?
}
}
}
(Some(second), _) => try_write_number(w, fdf, usize::from(second).into(), l)?,
},
(FieldSymbol::Second(Second::FractionalSecond), _) => {
// Fractional second not following second
// Fractional second not following second or with invalid length
write_value_missing(w, field)?;
Err(DateTimeWriteError::UnsupportedField(field))
}
Expand Down Expand Up @@ -1114,6 +1118,7 @@ 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
12 changes: 12 additions & 0 deletions components/datetime/src/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,20 @@
// 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;

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 {
#[cfg(feature = "experimental")]
pub(crate) fractional_second_digits: Option<FractionalSecondDigits>,
#[cfg(not(feature = "experimental"))]
pub(crate) fractional_second_digits: Option<u8>,
}
1 change: 1 addition & 0 deletions components/datetime/src/format/neo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2286,6 +2286,7 @@ 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: 1 addition & 0 deletions components/datetime/src/format/zoned_datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ where
field,
&mut iter,
pattern.metadata,
Default::default(),
datetime,
date_symbols,
time_symbols,
Expand Down
12 changes: 12 additions & 0 deletions components/datetime/src/neo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,19 +162,26 @@ pub struct NeoOptions<R: DateTimeMarkers> {
///
/// See [`EraDisplay`](crate::neo_skeleton::EraDisplay).
pub era_display: R::EraDisplayOption,
/// How many fractional seconds to display,
/// if seconds are included in the field set.
///
/// See [`FractionalSecondDigits`](crate::neo_skeleton::FractionalSecondDigits).
pub fractional_second_digits: R::FractionalSecondDigitsOption,
}

impl<R> From<NeoSkeletonLength> for NeoOptions<R>
where
R: DateTimeMarkers,
R::LengthOption: From<NeoSkeletonLength>,
R::EraDisplayOption: Default,
R::FractionalSecondDigitsOption: Default,
{
#[inline]
fn from(value: NeoSkeletonLength) -> Self {
NeoOptions {
length: value.into(),
era_display: Default::default(),
fractional_second_digits: Default::default(),
}
}
}
Expand All @@ -186,12 +193,14 @@ where
R: DateTimeMarkers,
R::LengthOption: Default,
R::EraDisplayOption: Default,
R::FractionalSecondDigitsOption: Default,
{
#[inline]
fn default() -> Self {
NeoOptions {
length: Default::default(),
era_display: Default::default(),
fractional_second_digits: Default::default(),
}
}
}
Expand Down Expand Up @@ -541,6 +550,7 @@ where
options.length.into(),
components,
options.era_display.into(),
options.fractional_second_digits.into(),
hour_cycle,
)
.map_err(LoadError::Data)?;
Expand Down Expand Up @@ -1247,6 +1257,7 @@ where
options.length.into(),
components,
options.era_display.into(),
options.fractional_second_digits.into(),
hour_cycle,
)
.map_err(LoadError::Data)?;
Expand Down Expand Up @@ -1447,6 +1458,7 @@ 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
Loading

0 comments on commit adde60a

Please sign in to comment.