Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Teach length::Bag how to switch hour cycles #840

Merged
merged 21 commits into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cf29e4f
Teach length::Bag how to switch hour cycles
gregtatum Jun 30, 2021
4e891be
De-duplicate the hour cycle in the length data
gregtatum Jul 1, 2021
d6d0575
Rename flexible to coarse, and restructure provider data
gregtatum Jul 2, 2021
4529f36
Break out hour cycle into its own module
gregtatum Jul 2, 2021
4c6f00c
Add a feature flag provider_transform_utils, and apply it to the hour…
gregtatum Jul 2, 2021
dc73213
Correct H23 as the stored symbol for skeletons
gregtatum Jul 2, 2021
b80883a
Fix CI by running `cargo make generate-readmes`
gregtatum Jul 2, 2021
e752509
Change the default CoarseHourCycle
gregtatum Jul 6, 2021
1bedace
Rename provider_transform_utils to provider_transform_internals
gregtatum Jul 6, 2021
96b09ee
Refactor Skeleton::from(pattern)
gregtatum Jul 6, 2021
468c200
Rewrite all unwrap() to expect()
gregtatum Jul 6, 2021
2701006
Refactor get_pattern_for_time_length
gregtatum Jul 6, 2021
69fe327
Fix clippy issue with nested match
gregtatum Jul 6, 2021
36fd956
Remove extraneous return
gregtatum Jul 14, 2021
023e612
Merge branch 'main' into hour-cycle
gregtatum Jul 14, 2021
1f127d1
Merge branch 'main' into hour-cycle
gregtatum Jul 21, 2021
e7ee7e3
Work around width expansion issue
gregtatum Jul 22, 2021
43ee215
Merge in main
gregtatum Jul 23, 2021
816746c
Update comment to be more general
gregtatum Jul 23, 2021
b978ed3
Merge branch 'main' into hour-cycle
gregtatum Jul 28, 2021
8e56e10
Add std as a feature requirement for provider_transform_internals
gregtatum Jul 28, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/datetime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ bench = false # This option is required for Benchmark CI
default = ["provider_serde"]
bench = []
provider_serde = ["serde", "litemap/serde"]
provider_transform_internals = []

[[bench]]
name = "datetime"
Expand Down
1 change: 0 additions & 1 deletion components/datetime/src/options/length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ use serde::{Deserialize, Serialize};
pub struct Bag {
pub date: Option<Date>,
pub time: Option<Time>,
#[cfg_attr(feature = "serde", serde(skip_serializing, skip_deserializing))]
pub preferences: Option<preferences::Bag>,
}

Expand Down
8 changes: 4 additions & 4 deletions components/datetime/src/options/preferences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub struct Bag {
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum HourCycle {
/// Hour is formatted to be in range 1-24
/// Hour is formatted to be in range 1-24 where midnight is 24:00.
///
/// # Examples
///
Expand All @@ -63,7 +63,7 @@ pub enum HourCycle {
/// ```
#[cfg_attr(feature = "serde", serde(rename = "h24"))]
H24,
/// Hour is formatted to be in range 0-23
/// Hour is formatted to be in range 0-23 where midnight is 00:00.
///
/// # Examples
///
Expand All @@ -75,7 +75,7 @@ pub enum HourCycle {
/// ```
#[cfg_attr(feature = "serde", serde(rename = "h23"))]
H23,
/// Hour is formatted to be in range 1-12
/// Hour is formatted to be in range 1-12 where midnight is 12:00.
///
/// # Examples
///
Expand All @@ -87,7 +87,7 @@ pub enum HourCycle {
/// ```
#[cfg_attr(feature = "serde", serde(rename = "h12"))]
H12,
/// Hour is formatted to be in range 0-11
/// Hour is formatted to be in range 0-11 where midnight is 00:00.
///
/// # Examples
///
Expand Down
27 changes: 27 additions & 0 deletions components/datetime/src/pattern/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

mod error;
mod parser;
pub mod transform_hour_cycle;

use crate::fields::{self, Field, FieldLength, FieldSymbol};
pub use error::Error;
Expand Down Expand Up @@ -99,6 +100,10 @@ impl Pattern {
&self.items
}

pub fn items_mut(&mut self) -> &mut [PatternItem] {
&mut self.items
}

pub fn from_bytes(input: &str) -> Result<Self, Error> {
Parser::new(input).parse().map(Self::from)
}
Expand Down Expand Up @@ -281,3 +286,25 @@ impl Serialize for Pattern {
}
}
}

/// Used to represent either H11/H12, or H23/H24. Skeletons only store these
/// hour cycles as H12 or H23.
#[derive(Debug, PartialEq, Clone, Copy)]
#[cfg_attr(
feature = "provider_serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub enum CoarseHourCycle {
/// Can either be fields::Hour::H11 or fields::Hour::H12
H11H12,
/// Can either be fields::Hour::H23 or fields::Hour::H24
H23H24,
}

/// Default is required for serialization. H23H24 is the more locale-agnostic choice, as it's
/// less likely to have a day period in it.
impl Default for CoarseHourCycle {
fn default() -> Self {
CoarseHourCycle::H23H24
}
}
87 changes: 87 additions & 0 deletions components/datetime/src/pattern/transform_hour_cycle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

//! This hour cycle module represents various utilities for working with hour cycles in order
//! to apply a user preference.

#![doc(hidden)]
#![cfg(feature = "provider_transform_internals")]

use crate::fields;
use crate::pattern::{CoarseHourCycle, Pattern, PatternItem};
use crate::provider;
use crate::skeleton;

/// Figure out the coarse hour cycle given a pattern, which is useful for generating the provider
/// patterns for `length::Bag`.
pub fn determine_coarse_hour_cycle(pattern: &Pattern) -> Option<CoarseHourCycle> {
for item in pattern.items() {
if let PatternItem::Field(fields::Field {
symbol: fields::FieldSymbol::Hour(pattern_hour),
length: _,
}) = item
{
return Some(match pattern_hour {
fields::Hour::H11 | fields::Hour::H12 => CoarseHourCycle::H11H12,
fields::Hour::H23 | fields::Hour::H24 => CoarseHourCycle::H23H24,
});
}
}

None
}

/// Invoke the pattern matching machinery to transform the hour cycle of a pattern. This provides
/// a safe mapping from a h11/h12 to h23/h24 for transforms.
#[doc(hidden)]
#[cfg(feature = "provider_transform_internals")]
pub fn apply_coarse_hour_cycle(
datetime: &provider::gregory::patterns::DateTimeFormatsV1,
pattern_str: &str,
mut pattern: Pattern,
coarse_hour_cycle: CoarseHourCycle,
) -> Option<String> {
for item in pattern.items_mut() {
if let PatternItem::Field(fields::Field { symbol, length: _ }) = item {
if let fields::FieldSymbol::Hour(pattern_hour) = symbol {
if match coarse_hour_cycle {
CoarseHourCycle::H11H12 => match pattern_hour {
fields::Hour::H11 | fields::Hour::H12 => true,
fields::Hour::H23 | fields::Hour::H24 => false,
},
CoarseHourCycle::H23H24 => match pattern_hour {
fields::Hour::H11 | fields::Hour::H12 => false,
fields::Hour::H23 | fields::Hour::H24 => true,
},
} {
// The preference hour cycle matches the pattern, bail out early and
// return the current pattern.
return Some(pattern_str.into());
} else {
// Mutate the pattern with the new symbol, so that it can be matched against.
*symbol = fields::FieldSymbol::Hour(match coarse_hour_cycle {
CoarseHourCycle::H11H12 => fields::Hour::H12,
CoarseHourCycle::H23H24 => fields::Hour::H23,
});
break;
}
}
}
}

let skeleton = skeleton::Skeleton::from(&pattern);

match skeleton::create_best_pattern_for_fields(
&datetime.skeletons,
&datetime.length_patterns,
&skeleton.as_slice(),
// Prefer using the matched pattern directly, rather than mutating it to match the
// requested fields.
true,
) {
skeleton::BestSkeleton::AllFieldsMatch(pattern)
| skeleton::BestSkeleton::MissingOrExtraFields(pattern) => Some(format!("{}", pattern)),
skeleton::BestSkeleton::NoMatch => None,
}
}
14 changes: 13 additions & 1 deletion components/datetime/src/provider/gregory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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;
use icu_provider::yoke::{self, *};
use std::borrow::Cow;

Expand Down Expand Up @@ -30,7 +31,18 @@ pub struct DateSymbolsV1 {
pub struct DatePatternsV1 {
pub date: patterns::LengthPatternsV1,

pub time: patterns::LengthPatternsV1,
/// These patterns are common uses of time formatting, broken down by the length of the
/// pattern. Users can override the hour cycle with a preference, so there are two
/// pattern groups stored here. Note that the pattern will contain either h11 or h12.
pub time_h11_h12: patterns::LengthPatternsV1,

/// These patterns are common uses of time formatting, broken down by the length of the
/// pattern. Users can override the hour cycle with a preference, so there are two
/// pattern groups stored here. Note that the pattern will contain either h23 or h24.
pub time_h23_h24: patterns::LengthPatternsV1,

/// By default a locale will prefer one hour cycle type over another.
pub preferred_hour_cycle: pattern::CoarseHourCycle,

pub datetime: patterns::DateTimeFormatsV1,
}
Expand Down
66 changes: 56 additions & 10 deletions components/datetime/src/provider/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
use crate::date;
use crate::error::DateTimeFormatError;
use crate::fields;
use crate::options::{components, length, DateTimeFormatOptions};
use crate::pattern::Pattern;
use crate::options::{components, length, preferences, DateTimeFormatOptions};
use crate::pattern::{Pattern, PatternItem};
use crate::provider;
use crate::skeleton;
use std::borrow::Cow;
Expand All @@ -21,7 +21,11 @@ pub trait DateTimePatterns {
) -> Result<Option<Pattern>>;
fn get_pattern_for_length_bag(&self, length: &length::Bag) -> Result<Option<Pattern>>;
fn get_pattern_for_date_length(&self, length: length::Date) -> Result<Pattern>;
fn get_pattern_for_time_length(&self, length: length::Time) -> Result<Pattern>;
fn get_pattern_for_time_length(
&self,
length: length::Time,
preferences: &Option<preferences::Bag>,
) -> Result<Pattern>;
fn get_pattern_for_datetime_length(
&self,
length: length::Date,
Expand Down Expand Up @@ -71,6 +75,7 @@ impl DateTimePatterns for provider::gregory::DatePatternsV1 {
&self.datetime.skeletons,
&self.datetime.length_patterns,
&requested_fields,
false, // Prefer the requested fields over the matched pattern.
) {
skeleton::BestSkeleton::AllFieldsMatch(pattern)
| skeleton::BestSkeleton::MissingOrExtraFields(pattern) => Some(pattern),
Expand All @@ -82,10 +87,12 @@ impl DateTimePatterns for provider::gregory::DatePatternsV1 {
fn get_pattern_for_length_bag(&self, length: &length::Bag) -> Result<Option<Pattern>> {
match (length.date, length.time) {
(None, None) => Ok(None),
(None, Some(time_length)) => self.get_pattern_for_time_length(time_length).map(Some),
(None, Some(time_length)) => self
.get_pattern_for_time_length(time_length, &length.preferences)
.map(Some),
(Some(date_length), None) => self.get_pattern_for_date_length(date_length).map(Some),
(Some(date_length), Some(time_length)) => {
let time = self.get_pattern_for_time_length(time_length)?;
let time = self.get_pattern_for_time_length(time_length, &length.preferences)?;
let date = self.get_pattern_for_date_length(date_length)?;

self.get_pattern_for_datetime_length(date_length, date, time)
Expand Down Expand Up @@ -121,15 +128,54 @@ impl DateTimePatterns for provider::gregory::DatePatternsV1 {
Ok(Pattern::from_bytes_combination(s, date, time)?)
}

fn get_pattern_for_time_length(&self, length: length::Time) -> Result<Pattern> {
let time = &self.time;
let s = match length {
/// Look up the proper pre-computed pattern for a given length. If a preference for an hour
/// cycle is set, it will look look up a pattern in the time_h11_12 or time_h23_h24 provider
/// data, and then manually modify the symbol in the pattern if needed.
fn get_pattern_for_time_length(
&self,
length: length::Time,
preferences: &Option<preferences::Bag>,
) -> Result<Pattern> {
// Determine the coarse hour cycle patterns to use from either the preference bag,
// or the preferred hour cycle for the locale.
let time = if let Some(preferences::Bag {
hour_cycle: Some(hour_cycle_pref),
}) = preferences
{
match hour_cycle_pref {
preferences::HourCycle::H11 | preferences::HourCycle::H12 => &self.time_h11_h12,
preferences::HourCycle::H23 | preferences::HourCycle::H24 => &self.time_h23_h24,
}
} else {
match self.preferred_hour_cycle {
crate::pattern::CoarseHourCycle::H11H12 => &self.time_h11_h12,
crate::pattern::CoarseHourCycle::H23H24 => &self.time_h23_h24,
}
};

let mut pattern = Pattern::from_bytes(match length {
length::Time::Full => &time.full,
length::Time::Long => &time.long,
length::Time::Medium => &time.medium,
length::Time::Short => &time.short,
};
Ok(Pattern::from_bytes(s)?)
})?;

if let Some(preferences::Bag {
hour_cycle: Some(hour_cycle_pref),
}) = preferences
{
// Apply the preference::Bag override and change the pattern from a coarse hour cycle
// to the specific hour cycle.
for item in pattern.items_mut() {
if let PatternItem::Field(fields::Field { symbol, length: _ }) = item {
if let fields::FieldSymbol::Hour(_) = symbol {
*symbol = fields::FieldSymbol::Hour(hour_cycle_pref.field());
}
}
}
}

Ok(pattern)
}
}
zbraniecki marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
Loading