From 7cba53b5f7c4d947fb0767bdc55b73ad7ccfd012 Mon Sep 17 00:00:00 2001 From: Bryan Malyn Date: Sat, 26 Aug 2023 14:35:52 -0500 Subject: [PATCH 1/3] Explore using icu4 Relates to #854, but doesn't replace datetime code as a mentioned possible approach. Uses icu4 for: * short_months * long_months * short_weekdays * long_weekdays * am_pm * decimal_point Still uses pure_rust_locales for (I couldn't figure out how to get this from icu4 yet...): * d_fmt * d_t_fmt * t_fmt * t_fmt_ampm --- Cargo.toml | 29 +++- build.rs | 38 ++++++ src/datetime/tests.rs | 8 +- src/format/formatting.rs | 277 ++++++++++++++++++++------------------- src/format/locales.rs | 244 +++++++++++++++++++++++++++++++--- src/format/strftime.rs | 34 ++--- 6 files changed, 451 insertions(+), 179 deletions(-) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index ac55f10aab..e694455b4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,18 @@ std = [] clock = ["std", "winapi", "iana-time-zone", "android-tzdata"] oldtime = ["time"] wasmbind = ["wasm-bindgen", "js-sys"] -unstable-locales = ["pure-rust-locales", "alloc"] +unstable-locales = [ + "alloc", + "icu_calendar", + "icu_datagen", + "icu_datetime", + "icu_decimal", + "icu_locid_transform", + "icu_locid", + "icu_provider", + "pure-rust-locales", + "zerovec", +] __internal_bench = ["criterion"] __doctest = [] @@ -33,7 +44,14 @@ time = { version = "0.1.43", optional = true } num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } +icu_calendar = {version = "1.2.0", optional = true} +icu_datetime = { version = "1.2.0", optional = true } +icu_decimal = { version = "1.2.0", optional = true } +icu_locid = { version = "1.2.0", optional = true } +icu_locid_transform = { version = "1.2.0", optional = true } +icu_provider = { version = "1.2.0", optional = true } pure-rust-locales = { version = "0.6", optional = true } +zerovec = { version = "0.9", optional = true } criterion = { version = "0.4.0", optional = true } rkyv = { version = "0.7", optional = true } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } @@ -61,6 +79,15 @@ doc-comment = { version = "0.3" } [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] wasm-bindgen-test = "0.3" +[build-dependencies] +icu_calendar = {version = "1.2.0", optional = true} +icu_datetime = { version = "1.2.0", optional = true } +icu_decimal = { version = "1.2.0", optional = true } +icu_locid = { version = "1.2.0", optional = true } +icu_locid_transform = { version = "1.2.0", optional = true } +icu_provider = { version = "1.2.0", optional = true } +icu_datagen = { version = "1.2.0", optional = true } + [package.metadata.docs.rs] features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000..f4f635e3d4 --- /dev/null +++ b/build.rs @@ -0,0 +1,38 @@ +// 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 ). + +#[cfg(feature = "unstable-locales")] +fn main() { + use icu_datagen::prelude::*; + use std::path::PathBuf; + println!("cargo:rerun-if-changed=build.rs"); + + let out_dir = std::env::var_os("OUT_DIR").unwrap(); + let mod_directory = PathBuf::from(out_dir).join("baked_data"); + + let mut options = BakedOptions::default(); + // Whether to overwrite existing data. By default, errors if it is present. + options.overwrite = true; + // Whether to use separate crates to name types instead of the `icu` metacrate + options.use_separate_crates = true; + + icu_datagen::datagen( + None, + // Note: These are the keys required by `PluralRules::try_new_cardinal_unstable` + &[ + icu_decimal::provider::DecimalSymbolsV1Marker::KEY, + icu_datetime::provider::calendar::TimeSymbolsV1Marker::KEY, + icu_datetime::provider::calendar::GregorianDateSymbolsV1Marker::KEY, + icu_locid_transform::provider::LikelySubtagsForLanguageV1Marker::KEY, + icu_locid_transform::provider::LikelySubtagsForScriptRegionV1Marker::KEY, + ], + &SourceData::default() + .with_cldr_for_tag(SourceData::LATEST_TESTED_CLDR_TAG, CldrLocaleSubset::Modern) + .expect("Infallible"), + vec![icu_datagen::Out::Baked { mod_directory, options }], + ) + .expect("Datagen should be successful"); +} +#[cfg(not(feature = "unstable-locales"))] +fn main() {} diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 7686105275..8a842059cf 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1477,8 +1477,8 @@ fn locale_decimal_point() { assert_eq!(dt.format_localized("%T%.6f", nl_NL).to_string(), "18:58:00,123456"); assert_eq!(dt.format_localized("%T%.9f", nl_NL).to_string(), "18:58:00,123456780"); - assert_eq!(dt.format_localized("%T%.f", ar_SY).to_string(), "18:58:00.123456780"); - assert_eq!(dt.format_localized("%T%.3f", ar_SY).to_string(), "18:58:00.123"); - assert_eq!(dt.format_localized("%T%.6f", ar_SY).to_string(), "18:58:00.123456"); - assert_eq!(dt.format_localized("%T%.9f", ar_SY).to_string(), "18:58:00.123456780"); + assert_eq!(dt.format_localized("%T%.f", ar_SY).to_string(), "18:58:00٫123456780"); + assert_eq!(dt.format_localized("%T%.3f", ar_SY).to_string(), "18:58:00٫123"); + assert_eq!(dt.format_localized("%T%.6f", ar_SY).to_string(), "18:58:00٫123456"); + assert_eq!(dt.format_localized("%T%.9f", ar_SY).to_string(), "18:58:00٫123456780"); } diff --git a/src/format/formatting.rs b/src/format/formatting.rs index aa2608820d..6b5ae4bc35 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -309,154 +309,155 @@ fn format_inner( Item::Fixed(ref spec) => { use self::Fixed::*; - let ret = match *spec { - ShortMonthName => date.map(|d| { - w.write_str(short_months(locale)[d.month0() as usize])?; - Ok(()) - }), - LongMonthName => date.map(|d| { - w.write_str(long_months(locale)[d.month0() as usize])?; - Ok(()) - }), - ShortWeekdayName => date.map(|d| { - w.write_str( - short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize], - )?; - Ok(()) - }), - LongWeekdayName => date.map(|d| { - w.write_str( - long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize], - )?; - Ok(()) - }), - LowerAmPm => time.map(|t| { - let ampm = if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] }; - for c in ampm.chars().flat_map(|c| c.to_lowercase()) { - w.write_char(c)? - } - Ok(()) - }), - UpperAmPm => time.map(|t| { - w.write_str(if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] })?; - Ok(()) - }), - Nanosecond => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - if nano == 0 { + let ret = + match *spec { + ShortMonthName => date.map(|d| { + w.write_str(short_months(locale)[d.month0() as usize].as_ref())?; Ok(()) - } else { - w.write_str(decimal_point(locale))?; - if nano % 1_000_000 == 0 { - write!(w, "{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(w, "{:06}", nano / 1_000) + }), + LongMonthName => date.map(|d| { + w.write_str(long_months(locale)[d.month0() as usize].as_ref())?; + Ok(()) + }), + ShortWeekdayName => date.map(|d| { + w.write_str( + short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize] + .as_ref(), + )?; + Ok(()) + }), + LongWeekdayName => date.map(|d| { + w.write_str( + long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize] + .as_ref(), + )?; + Ok(()) + }), + LowerAmPm => time.map(|t| { + let ampm = am_pm(locale); + let cur_ampm = &m[if t.hour12().0 { 1 } else { 0 }]; + w.write_str(&cur_ampm.to_lowercase())?; + Ok(()) + }), + UpperAmPm => time.map(|t| { + let ampm = am_pm(locale); + let cur_ampm = &m[if t.hour12().0 { 1 } else { 0 }]; + w.write_str(cur_ampm)?; + Ok(()) + }), + Nanosecond => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + if nano == 0 { + Ok(()) } else { - write!(w, "{:09}", nano) + w.write_str(decimal_point(locale).as_ref())?; + if nano % 1_000_000 == 0 { + write!(w, "{:03}", nano / 1_000_000) + } else if nano % 1_000 == 0 { + write!(w, "{:06}", nano / 1_000) + } else { + write!(w, "{:09}", nano) + } } - } - }), - Nanosecond3 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:03}", nano / 1_000_000) - }), - Nanosecond6 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:06}", nano / 1_000) - }), - Nanosecond9 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { - time.map(|t| { + }), + Nanosecond3 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(locale).as_ref())?; write!(w, "{:03}", nano / 1_000_000) - }) - } - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { - time.map(|t| { + }), + Nanosecond6 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(locale).as_ref())?; write!(w, "{:06}", nano / 1_000) - }) - } - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { - time.map(|t| { + }), + Nanosecond9 => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(locale).as_ref())?; write!(w, "{:09}", nano) - }) - } - TimezoneName => off.map(|(name, _)| { - w.write_str(name)?; - Ok(()) - }), - TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Maybe, - allow_zulu: *spec == TimezoneOffsetZ, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Colon, - allow_zulu: *spec == TimezoneOffsetColonZ, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetDoubleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Seconds, - colons: Colons::Colon, - allow_zulu: false, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetTripleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Hours, - colons: Colons::None, - allow_zulu: false, - padding: Pad::Zero, + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time + .map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:03}", nano / 1_000_000) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time + .map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:06}", nano / 1_000) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time + .map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:09}", nano) + }), + TimezoneName => off.map(|(name, _)| { + w.write_str(name)?; + Ok(()) + }), + TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Maybe, + allow_zulu: *spec == TimezoneOffsetZ, + padding: Pad::Zero, + } + .format(w, off) + }), + TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: *spec == TimezoneOffsetColonZ, + padding: Pad::Zero, + } + .format(w, off) + }), + TimezoneOffsetDoubleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Seconds, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, + } + .format(w, off) + }), + TimezoneOffsetTripleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Hours, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(w, off) + }), + Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { + return Err(fmt::Error); } - .format(w, off) - }), - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - return Err(fmt::Error); - } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822_inner(w, *d, *t, off, locale)) - } else { - None + RFC2822 => + // same as `%a, %d %b %Y %H:%M:%S %z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc2822_inner(w, *d, *t, off, locale)) + } else { + None + } } - } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339( - w, - crate::NaiveDateTime::new(*d, *t), - off.fix(), - SecondsFormat::AutoSi, - false, - )) - } else { - None + RFC3339 => + // same as `%Y-%m-%dT%H:%M:%S%.f%:z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc3339( + w, + crate::NaiveDateTime::new(*d, *t), + off.fix(), + SecondsFormat::AutoSi, + false, + )) + } else { + None + } } - } - }; + }; ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format } @@ -635,11 +636,11 @@ fn write_rfc2822_inner( return Err(fmt::Error); } - w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?; + w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize].as_ref())?; w.write_str(", ")?; write_hundreds(w, d.day() as u8)?; w.write_char(' ')?; - w.write_str(short_months(locale)[d.month0() as usize])?; + w.write_str(short_months(locale)[d.month0() as usize].as_ref())?; w.write_char(' ')?; write_hundreds(w, (year / 100) as u8)?; write_hundreds(w, (year % 100) as u8)?; diff --git a/src/format/locales.rs b/src/format/locales.rs index 4cf4d4149a..42e4d7cc68 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -1,48 +1,254 @@ #[cfg(feature = "unstable-locales")] mod localized { - use pure_rust_locales::{locale_match, Locale}; + use icu_calendar::types::MonthCode; + use icu_datetime::provider::calendar::{GregorianDateSymbolsV1Marker, TimeSymbolsV1Marker}; + use icu_decimal::provider::DecimalSymbolsV1Marker; + use icu_locid_transform::LocaleExpander; + use icu_provider::{ + DataError, DataLocale, DataPayload, DataProvider, DataRequest, DataResponse, + KeyedDataMarker, + }; + use pure_rust_locales::locale_match; - pub(crate) const fn default_locale() -> Locale { - Locale::POSIX + struct BakedProvider; + #[allow(unreachable_pub)] + mod baked { + include!(concat!(env!("OUT_DIR"), "/baked_data/mod.rs")); + impl_data_provider!(super::BakedProvider); + } + + pub(crate) fn default_locale() -> pure_rust_locales::Locale { + pure_rust_locales::Locale::POSIX + } + + fn get_payload(locale: pure_rust_locales::Locale) -> Result, DataError> + where + M: KeyedDataMarker, + BakedProvider: icu_provider::DataProvider, + { + // Convert pure_rust_locales::Locale -> String + // POSIX doesn't exist in icu4, falling back to English + let locale_string = match locale { + pure_rust_locales::Locale::POSIX => "en".to_string(), + _ => format!("{:?}", locale), + }; + + // Continue converting the locale_string in to a icu_locid::Locale + let mut locale = icu_locid::Locale::try_from_bytes(locale_string.as_bytes()) + .expect("Could not create Locale"); + + // Minimize icu_locid::Locale to make the locale as generic as it can be without losing anything + // Ex. en-US -> en, but en-CA stays en-CA + let lc = LocaleExpander::try_new_unstable(&BakedProvider) + .expect("Failed to create LocaleExpander"); + lc.minimize(&mut locale); + + BakedProvider + .load(DataRequest { locale: &DataLocale::from(locale), metadata: Default::default() }) + .or_else(|_| BakedProvider.load(DataRequest::default())) + .and_then(|response: DataResponse| response.take_payload()) } - pub(crate) const fn short_months(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::ABMON) + pub(crate) fn short_months(locale: pure_rust_locales::Locale) -> [String; 12] { + get_payload::(locale).map_or( + [ + "Jan".to_string(), + "Feb".to_string(), + "Mar".to_string(), + "Apr".to_string(), + "May".to_string(), + "Jun".to_string(), + "Jul".to_string(), + "Aug".to_string(), + "Sep".to_string(), + "Oct".to_string(), + "Nov".to_string(), + "Dec".to_string(), + ], + |payload| { + let format_abbreviated = &payload.get().months.format.abbreviated; + [ + format_abbreviated + .get(MonthCode("M01".parse().unwrap())) + .map_or("Jan".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M02".parse().unwrap())) + .map_or("Feb".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M03".parse().unwrap())) + .map_or("Mar".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M04".parse().unwrap())) + .map_or("Apr".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M05".parse().unwrap())) + .map_or("May".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M06".parse().unwrap())) + .map_or("Jun".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M07".parse().unwrap())) + .map_or("Jul".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M08".parse().unwrap())) + .map_or("Aug".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M09".parse().unwrap())) + .map_or("Sep".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M10".parse().unwrap())) + .map_or("Oct".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M11".parse().unwrap())) + .map_or("Nov".to_string(), ToString::to_string), + format_abbreviated + .get(MonthCode("M12".parse().unwrap())) + .map_or("Dec".to_string(), ToString::to_string), + ] + }, + ) } - pub(crate) const fn long_months(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::MON) + pub(crate) fn long_months(locale: pure_rust_locales::Locale) -> [String; 12] { + get_payload::(locale).map_or( + [ + "January".to_string(), + "February".to_string(), + "March".to_string(), + "April".to_string(), + "May".to_string(), + "June".to_string(), + "July".to_string(), + "August".to_string(), + "September".to_string(), + "October".to_string(), + "November".to_string(), + "December".to_string(), + ], + |payload| { + let format_wide = &payload.get().months.format.wide; + [ + format_wide + .get(MonthCode("M01".parse().unwrap())) + .map_or("January".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M02".parse().unwrap())) + .map_or("February".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M03".parse().unwrap())) + .map_or("March".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M04".parse().unwrap())) + .map_or("April".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M05".parse().unwrap())) + .map_or("May".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M06".parse().unwrap())) + .map_or("June".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M07".parse().unwrap())) + .map_or("July".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M08".parse().unwrap())) + .map_or("August".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M09".parse().unwrap())) + .map_or("September".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M10".parse().unwrap())) + .map_or("October".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M11".parse().unwrap())) + .map_or("November".to_string(), ToString::to_string), + format_wide + .get(MonthCode("M12".parse().unwrap())) + .map_or("December".to_string(), ToString::to_string), + ] + }, + ) } - pub(crate) const fn short_weekdays(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::ABDAY) + pub(crate) fn short_weekdays(locale: pure_rust_locales::Locale) -> [String; 7] { + get_payload::(locale).map_or( + [ + "Sun".to_string(), + "Mon".to_string(), + "Tue".to_string(), + "Wed".to_string(), + "Thu".to_string(), + "Fri".to_string(), + "Sat".to_string(), + ], + |payload| { + let format_abbreviated = &payload.get().weekdays.format.abbreviated.0; + [ + format_abbreviated[0].to_string(), + format_abbreviated[1].to_string(), + format_abbreviated[2].to_string(), + format_abbreviated[3].to_string(), + format_abbreviated[4].to_string(), + format_abbreviated[5].to_string(), + format_abbreviated[6].to_string(), + ] + }, + ) } - pub(crate) const fn long_weekdays(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::DAY) + pub(crate) fn long_weekdays(locale: pure_rust_locales::Locale) -> [String; 7] { + get_payload::(locale).map_or( + [ + "Sunday".to_string(), + "Monday".to_string(), + "Tuesday".to_string(), + "Wednesday".to_string(), + "Thursday".to_string(), + "Friday".to_string(), + "Saturday".to_string(), + ], + |payload| { + let format_wide = &payload.get().weekdays.format.wide.0; + [ + format_wide[0].to_string(), + format_wide[1].to_string(), + format_wide[2].to_string(), + format_wide[3].to_string(), + format_wide[4].to_string(), + format_wide[5].to_string(), + format_wide[6].to_string(), + ] + }, + ) } - pub(crate) const fn am_pm(locale: Locale) -> &'static [&'static str] { - locale_match!(locale => LC_TIME::AM_PM) + pub(crate) fn am_pm(locale: pure_rust_locales::Locale) -> [String; 2] { + get_payload::(locale).map_or( + ["AM".to_string(), "PM".to_string()], + |payload| { + let format_wide = &payload.get().day_periods.format.wide; + [format_wide.am.to_string(), format_wide.pm.to_string()] + }, + ) } - pub(crate) const fn decimal_point(locale: Locale) -> &'static str { - locale_match!(locale => LC_NUMERIC::DECIMAL_POINT) + pub(crate) fn decimal_point(locale: pure_rust_locales::Locale) -> String { + get_payload::(locale) + .map_or(".".to_string(), |payload| payload.get().decimal_separator.to_string()) } - pub(crate) const fn d_fmt(locale: Locale) -> &'static str { + pub(crate) fn d_fmt(locale: pure_rust_locales::Locale) -> &'static str { locale_match!(locale => LC_TIME::D_FMT) } - pub(crate) const fn d_t_fmt(locale: Locale) -> &'static str { + pub(crate) fn d_t_fmt(locale: pure_rust_locales::Locale) -> &'static str { locale_match!(locale => LC_TIME::D_T_FMT) } - pub(crate) const fn t_fmt(locale: Locale) -> &'static str { + pub(crate) fn t_fmt(locale: pure_rust_locales::Locale) -> &'static str { locale_match!(locale => LC_TIME::T_FMT) } - pub(crate) const fn t_fmt_ampm(locale: Locale) -> &'static str { + pub(crate) fn t_fmt_ampm(locale: pure_rust_locales::Locale) -> &'static str { locale_match!(locale => LC_TIME::T_FMT_AMPM) } } diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 50d4ac7cc3..9c3f39d6aa 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -784,19 +784,19 @@ mod tests { .unwrap(); // date specifiers - assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui"); + assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "juil."); assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet"); - assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui"); - assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim"); + assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "juil."); + assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim."); assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche"); assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01"); assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01"); assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08"); - assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001"); + assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-juil.-2001"); // time specifiers - assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), ""); - assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), ""); + assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "am"); + assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "AM"); assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34"); assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60"); assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60"); @@ -805,21 +805,21 @@ mod tests { // date & time specifiers assert_eq!( dt.format_localized("%c", Locale::fr_BE).to_string(), - "dim 08 jui 2001 00:34:60 +09:30" + "dim. 08 juil. 2001 00:34:60 +09:30" ); let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap(); // date specifiers - assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul"); + assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Juli"); assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli"); - assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul"); - assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So"); + assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Juli"); + assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So."); assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag"); assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01"); assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001"); assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08"); - assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001"); + assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Juli-2001"); } /// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does @@ -858,15 +858,15 @@ mod tests { .unwrap(); // date specifiers - assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7월"); + assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), "7월"); assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7월"); - assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7월"); + assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), "7월"); assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "일"); assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "일요일"); assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01"); assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001년 07월 08일"); assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08"); - assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7월-2001"); + assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8-7월-2001"); assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "오전 12시 34분 60초"); // date & time specifiers @@ -887,15 +887,15 @@ mod tests { .unwrap(); // date specifiers - assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7月"); + assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), "7月"); assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7月"); - assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7月"); + assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), "7月"); assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "日"); assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "日曜日"); assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01"); assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001年07月08日"); assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08"); - assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7月-2001"); + assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8-7月-2001"); assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "午前12時34分60秒"); // date & time specifiers From df542e7daec292f8b87fc684c040a0a69d321190 Mon Sep 17 00:00:00 2001 From: Bryan Malyn Date: Sat, 26 Aug 2023 18:08:27 -0500 Subject: [PATCH 2/3] icu prefer stand_alone vs format --- src/format/locales.rs | 46 ++++++++++++++++++++++++++++++++++++++---- src/format/strftime.rs | 8 ++++---- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/format/locales.rs b/src/format/locales.rs index 42e4d7cc68..bd9a7b8554 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -1,5 +1,9 @@ #[cfg(feature = "unstable-locales")] mod localized { + use alloc::{ + format, + string::{String, ToString}, + }; use icu_calendar::types::MonthCode; use icu_datetime::provider::calendar::{GregorianDateSymbolsV1Marker, TimeSymbolsV1Marker}; use icu_decimal::provider::DecimalSymbolsV1Marker; @@ -66,7 +70,15 @@ mod localized { "Dec".to_string(), ], |payload| { - let format_abbreviated = &payload.get().months.format.abbreviated; + //Prefer stand_alone, but fall back to format + let format_abbreviated = payload + .get() + .clone() + .months + .stand_alone + .and_then(|x| x.abbreviated) + .unwrap_or_else(|| payload.get().months.format.abbreviated.clone()); + [ format_abbreviated .get(MonthCode("M01".parse().unwrap())) @@ -126,7 +138,15 @@ mod localized { "December".to_string(), ], |payload| { - let format_wide = &payload.get().months.format.wide; + //Prefer stand_alone, but fall back to format + let format_wide = payload + .get() + .clone() + .months + .stand_alone + .and_then(|x| x.wide) + .unwrap_or_else(|| payload.get().months.format.wide.clone()); + [ format_wide .get(MonthCode("M01".parse().unwrap())) @@ -181,7 +201,16 @@ mod localized { "Sat".to_string(), ], |payload| { - let format_abbreviated = &payload.get().weekdays.format.abbreviated.0; + //Prefer stand_alone, but fall back to format + let format_abbreviated = payload + .get() + .clone() + .weekdays + .stand_alone + .and_then(|x| x.abbreviated) + .unwrap_or_else(|| payload.get().weekdays.format.abbreviated.clone()) + .0; + [ format_abbreviated[0].to_string(), format_abbreviated[1].to_string(), @@ -207,7 +236,16 @@ mod localized { "Saturday".to_string(), ], |payload| { - let format_wide = &payload.get().weekdays.format.wide.0; + //Prefer stand_alone, but fall back to format + let format_wide = payload + .get() + .clone() + .weekdays + .stand_alone + .and_then(|x| x.wide) + .unwrap_or_else(|| payload.get().weekdays.format.wide.clone()) + .0; + [ format_wide[0].to_string(), format_wide[1].to_string(), diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 9c3f39d6aa..d7e36587bc 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -811,15 +811,15 @@ mod tests { let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap(); // date specifiers - assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Juli"); + assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul"); assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli"); - assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Juli"); - assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So."); + assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul"); + assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So"); assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag"); assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01"); assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001"); assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08"); - assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Juli-2001"); + assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001"); } /// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does From c83af79193371919fa51aee11628aa3e74512fe5 Mon Sep 17 00:00:00 2001 From: Bryan Malyn Date: Sat, 26 Aug 2023 18:28:15 -0500 Subject: [PATCH 3/3] fallback to pure_rust_locales if icu fails --- src/format/locales.rs | 293 ++++++++++++++++++++++++------------------ 1 file changed, 167 insertions(+), 126 deletions(-) diff --git a/src/format/locales.rs b/src/format/locales.rs index bd9a7b8554..b136ce3c2b 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -54,21 +54,24 @@ mod localized { } pub(crate) fn short_months(locale: pure_rust_locales::Locale) -> [String; 12] { - get_payload::(locale).map_or( - [ - "Jan".to_string(), - "Feb".to_string(), - "Mar".to_string(), - "Apr".to_string(), - "May".to_string(), - "Jun".to_string(), - "Jul".to_string(), - "Aug".to_string(), - "Sep".to_string(), - "Oct".to_string(), - "Nov".to_string(), - "Dec".to_string(), - ], + get_payload::(locale).map_or_else( + |_| { + let months = locale_match!(locale => LC_TIME::ABMON); + [ + months[0].to_string(), + months[1].to_string(), + months[2].to_string(), + months[3].to_string(), + months[4].to_string(), + months[5].to_string(), + months[6].to_string(), + months[7].to_string(), + months[8].to_string(), + months[9].to_string(), + months[10].to_string(), + months[11].to_string(), + ] + }, |payload| { //Prefer stand_alone, but fall back to format let format_abbreviated = payload @@ -80,63 +83,78 @@ mod localized { .unwrap_or_else(|| payload.get().months.format.abbreviated.clone()); [ - format_abbreviated - .get(MonthCode("M01".parse().unwrap())) - .map_or("Jan".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M02".parse().unwrap())) - .map_or("Feb".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M03".parse().unwrap())) - .map_or("Mar".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M04".parse().unwrap())) - .map_or("Apr".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M05".parse().unwrap())) - .map_or("May".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M06".parse().unwrap())) - .map_or("Jun".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M07".parse().unwrap())) - .map_or("Jul".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M08".parse().unwrap())) - .map_or("Aug".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M09".parse().unwrap())) - .map_or("Sep".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M10".parse().unwrap())) - .map_or("Oct".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M11".parse().unwrap())) - .map_or("Nov".to_string(), ToString::to_string), - format_abbreviated - .get(MonthCode("M12".parse().unwrap())) - .map_or("Dec".to_string(), ToString::to_string), + format_abbreviated.get(MonthCode("M01".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[0].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M02".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[1].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M03".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[2].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M04".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[3].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M05".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[4].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M06".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[5].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M07".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[6].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M08".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[7].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M09".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[8].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M10".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[9].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M11".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[10].to_string(), + ToString::to_string, + ), + format_abbreviated.get(MonthCode("M12".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::ABMON)[11].to_string(), + ToString::to_string, + ), ] }, ) } pub(crate) fn long_months(locale: pure_rust_locales::Locale) -> [String; 12] { - get_payload::(locale).map_or( - [ - "January".to_string(), - "February".to_string(), - "March".to_string(), - "April".to_string(), - "May".to_string(), - "June".to_string(), - "July".to_string(), - "August".to_string(), - "September".to_string(), - "October".to_string(), - "November".to_string(), - "December".to_string(), - ], + get_payload::(locale).map_or_else( + |_| { + let months = locale_match!(locale => LC_TIME::MON); + [ + months[0].to_string(), + months[1].to_string(), + months[2].to_string(), + months[3].to_string(), + months[4].to_string(), + months[5].to_string(), + months[6].to_string(), + months[7].to_string(), + months[8].to_string(), + months[9].to_string(), + months[10].to_string(), + months[11].to_string(), + ] + }, |payload| { //Prefer stand_alone, but fall back to format let format_wide = payload @@ -148,58 +166,73 @@ mod localized { .unwrap_or_else(|| payload.get().months.format.wide.clone()); [ - format_wide - .get(MonthCode("M01".parse().unwrap())) - .map_or("January".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M02".parse().unwrap())) - .map_or("February".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M03".parse().unwrap())) - .map_or("March".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M04".parse().unwrap())) - .map_or("April".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M05".parse().unwrap())) - .map_or("May".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M06".parse().unwrap())) - .map_or("June".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M07".parse().unwrap())) - .map_or("July".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M08".parse().unwrap())) - .map_or("August".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M09".parse().unwrap())) - .map_or("September".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M10".parse().unwrap())) - .map_or("October".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M11".parse().unwrap())) - .map_or("November".to_string(), ToString::to_string), - format_wide - .get(MonthCode("M12".parse().unwrap())) - .map_or("December".to_string(), ToString::to_string), + format_wide.get(MonthCode("M01".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[0].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M02".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[1].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M03".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[2].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M04".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[3].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M05".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[4].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M06".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[5].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M07".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[6].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M08".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[7].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M09".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[8].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M10".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[9].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M11".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[10].to_string(), + ToString::to_string, + ), + format_wide.get(MonthCode("M12".parse().unwrap())).map_or_else( + || locale_match!(locale => LC_TIME::MON)[11].to_string(), + ToString::to_string, + ), ] }, ) } pub(crate) fn short_weekdays(locale: pure_rust_locales::Locale) -> [String; 7] { - get_payload::(locale).map_or( - [ - "Sun".to_string(), - "Mon".to_string(), - "Tue".to_string(), - "Wed".to_string(), - "Thu".to_string(), - "Fri".to_string(), - "Sat".to_string(), - ], + get_payload::(locale).map_or_else( + |_| { + let weekdays = locale_match!(locale => LC_TIME::ABDAY); + [ + weekdays[0].to_string(), + weekdays[1].to_string(), + weekdays[2].to_string(), + weekdays[3].to_string(), + weekdays[4].to_string(), + weekdays[5].to_string(), + weekdays[6].to_string(), + ] + }, |payload| { //Prefer stand_alone, but fall back to format let format_abbreviated = payload @@ -225,16 +258,19 @@ mod localized { } pub(crate) fn long_weekdays(locale: pure_rust_locales::Locale) -> [String; 7] { - get_payload::(locale).map_or( - [ - "Sunday".to_string(), - "Monday".to_string(), - "Tuesday".to_string(), - "Wednesday".to_string(), - "Thursday".to_string(), - "Friday".to_string(), - "Saturday".to_string(), - ], + get_payload::(locale).map_or_else( + |_| { + let weekdays = locale_match!(locale => LC_TIME::DAY); + [ + weekdays[0].to_string(), + weekdays[1].to_string(), + weekdays[2].to_string(), + weekdays[3].to_string(), + weekdays[4].to_string(), + weekdays[5].to_string(), + weekdays[6].to_string(), + ] + }, |payload| { //Prefer stand_alone, but fall back to format let format_wide = payload @@ -260,8 +296,11 @@ mod localized { } pub(crate) fn am_pm(locale: pure_rust_locales::Locale) -> [String; 2] { - get_payload::(locale).map_or( - ["AM".to_string(), "PM".to_string()], + get_payload::(locale).map_or_else( + |_| { + let am_pm = locale_match!(locale => LC_TIME::AM_PM); + [am_pm[0].to_string(), am_pm[1].to_string()] + }, |payload| { let format_wide = &payload.get().day_periods.format.wide; [format_wide.am.to_string(), format_wide.pm.to_string()] @@ -270,8 +309,10 @@ mod localized { } pub(crate) fn decimal_point(locale: pure_rust_locales::Locale) -> String { - get_payload::(locale) - .map_or(".".to_string(), |payload| payload.get().decimal_separator.to_string()) + get_payload::(locale).map_or_else( + |_| locale_match!(locale => LC_NUMERIC::DECIMAL_POINT).to_string(), + |payload| payload.get().decimal_separator.to_string(), + ) } pub(crate) fn d_fmt(locale: pure_rust_locales::Locale) -> &'static str {