diff --git a/executors/rust/2.0-beta1/src/datetimefmt.rs b/executors/rust/2.0-beta1/src/datetimefmt.rs index fbe4661d..6fad6857 100644 --- a/executors/rust/2.0-beta1/src/datetimefmt.rs +++ b/executors/rust/2.0-beta1/src/datetimefmt.rs @@ -2,24 +2,16 @@ // https://docs.rs/icu/1.3.2/icu/datetime/input/trait.TimeZoneInput.html // https://docs.rs/ixdtf/latest/ixdtf/ -use icu::calendar::DateTime; -use icu::datetime::{ - options::components, options::length, options::DateTimeFormatterOptions, pattern::reference, - pattern::runtime, ZonedDateTimeFormatter, -}; +use icu::datetime::fieldsets; +use icu::datetime::fieldsets::enums::*; +use icu::datetime::DateTimeFormatter; +use icu::datetime::DateTimeFormatterPreferences; +use icu::locale::extensions::unicode; +use icu::locale::preferences::extensions::unicode::keywords::CalendarAlgorithm; use icu::locale::Locale; -// https://docs.rs/icu/latest/icu/timezone/struct.CustomTimeZone.html#method.maybe_calculate_metazone -use icu::timezone::CustomTimeZone; -use icu::timezone::GmtOffset; -use icu::timezone::MetazoneCalculator; - -use icu_provider::DataLocale; - -use icu::timezone::IanaToBcp47Mapper; - -use ixdtf::parsers::IxdtfParser; +use icu::timezone::IxdtfParser; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -54,143 +46,93 @@ pub fn run_datetimeformat_test(json_obj: &Value) -> Result { let option_struct: DateTimeFormatOptions = serde_json::from_str(&options.to_string()).unwrap(); - // Get calendar. If present, add to locale string - // as "-u-ca-" + calendar name. - let calendar_str = &option_struct.calendar; - - let locale_json_str: &str = json_obj["locale"].as_str().unwrap(); - let mut locale_str: String = locale_json_str.to_string(); - // ??? Is calendar necessary with the u-ca option in ISO string? - if calendar_str.is_some() { - locale_str = locale_json_str.to_string() + "-u-ca-" + &calendar_str.as_ref().unwrap(); - } - - let lang_id = if let Ok(lc) = locale_str.parse::() { - lc - } else { - return Ok(json!({ - "label": label, - "error_detail": {"option": locale_str}, - "error_type": "locale problem", - })); - }; - let data_locale = DataLocale::from(lang_id); - - let mut _unsupported_options: Vec<&str> = Vec::new(); - - let option_struct: DateTimeFormatOptions = serde_json::from_str(&options.to_string()).unwrap(); - - let date_style_str = &option_struct.date_style; - let date_style = match date_style_str.as_deref() { - Some("full") => Some(length::Date::Full), - Some("long") => Some(length::Date::Long), - Some("medium") => Some(length::Date::Medium), - Some("short") => Some(length::Date::Short), - _ => None, - }; - - // TimeStyle long or full requires that you use ZonedDateTimeFormatter. - // long known issue that is documented and has been filed several times. - // Is fixed in 2.0 - let time_style_str = &option_struct.time_style; - let time_style = match time_style_str.as_deref() { - Some("full") => Some(length::Time::Full), - Some("long") => Some(length::Time::Long), - Some("medium") => Some(length::Time::Medium), - Some("short") => Some(length::Time::Short), - _ => None, - }; + let calendar_algorithm = option_struct.calendar.as_ref().map(|calendar_str| { + CalendarAlgorithm::try_from(&unicode::Value::try_from_str(calendar_str).unwrap()).unwrap() + }); - // Set up DT option if either is set - let mut dt_length_options = length::Bag::empty(); - dt_length_options.date = date_style; - dt_length_options.time = time_style; - - let dt_options = if dt_length_options != length::Bag::empty() { - DateTimeFormatterOptions::Length(dt_length_options) - } else { - // For versions 1.X, but not in 2.X. - // This is using an interal feature. - let skeleton_str = &json_obj["skeleton"].as_str().unwrap(); - let parsed_skeleton = skeleton_str.parse::().unwrap(); - let mut components_bag = components::Bag::from(&runtime::PatternPlurals::SinglePattern( - runtime::Pattern::from(&parsed_skeleton), - )); - - let option_struct: DateTimeFormatOptions = - serde_json::from_str(&options.to_string()).unwrap(); - - components_bag.hour = match option_struct.hour.as_deref() { - Some("numeric") => Some(components::Numeric::Numeric), - Some("2-digit") => Some(components::Numeric::TwoDigit), - _ => None, - }; - DateTimeFormatterOptions::Components(components_bag) - }; - - // Get ISO instant in UTC time zone - let input_iso = &json_obj["input_string"].as_str().unwrap(); - - let dt_iso = IxdtfParser::new(input_iso).parse().unwrap(); - let date = dt_iso.date.unwrap(); - let time = dt_iso.time.unwrap(); - - // Compute the seconds for the timezone's offset - let offset_seconds: i32 = json_obj["tz_offset_secs"] - .as_i64() + let locale = json_obj["locale"] + .as_str() .unwrap() - .try_into() + .parse::() .unwrap(); - - let gmt_offset_seconds = GmtOffset::try_from_offset_seconds(offset_seconds).ok(); - - let mut datetime_iso = DateTime::try_new_iso_datetime( - date.year, - date.month, - date.day, - time.hour, - time.minute, - 0, // Seconds added below. - ) - .expect("Failed to initialize ISO DateTime instance."); - let mut dt_integer_minutes = datetime_iso.minutes_since_local_unix_epoch(); - dt_integer_minutes += offset_seconds / 60; - - datetime_iso = DateTime::from_minutes_since_local_unix_epoch(dt_integer_minutes); - datetime_iso.time.second = time.second.try_into().unwrap(); - - let any_datetime = datetime_iso.to_any(); - - // Testing with a default timezone - let timezone_str = &option_struct.time_zone; - - // https://docs.rs/icu/latest/icu/timezone/struct.IanaToBcp47Mapper.html - let mapper = IanaToBcp47Mapper::new(); - let mapper_borrowed = mapper.as_borrowed(); - - let mapped_tz = mapper_borrowed.get(timezone_str.as_ref().unwrap()); - let mzc = MetazoneCalculator::new(); - let my_metazone_id = mzc.compute_metazone_from_time_zone(mapped_tz.unwrap(), &datetime_iso); - - let time_zone = if timezone_str.is_some() { - CustomTimeZone { - gmt_offset: gmt_offset_seconds, - time_zone_id: mapped_tz, - metazone_id: my_metazone_id, - zone_variant: None, + let mut preferences = DateTimeFormatterPreferences::from(&locale); + preferences.calendar_algorithm = calendar_algorithm; + + let field_set = match ( + option_struct.date_style.as_deref(), + option_struct.time_style.as_deref(), + ) { + (Some(date_style), Some(time_style)) => { + use fieldsets::*; + use CompositeFieldSet::DateTime; + use CompositeFieldSet::DateTimeZone; + use DateAndTimeFieldSet as Enum; + match (date_style, time_style) { + ("full", "full") => DateTimeZone(Enum::YMDET(YMDET::long()), ZoneStyle::Z), + ("full", "long") => DateTimeZone(Enum::YMDET(YMDET::long()), ZoneStyle::Z), + ("full", "medium") => DateTime(Enum::YMDET(YMDET::long())), + ("full", "short") => DateTime(Enum::YMDET(YMDET::long().hm())), + ("long", "full") => DateTimeZone(Enum::YMDT(YMDT::long()), ZoneStyle::Z), + ("long", "long") => DateTimeZone(Enum::YMDT(YMDT::long()), ZoneStyle::Z), + ("long", "medium") => DateTime(Enum::YMDT(YMDT::long())), + ("long", "short") => DateTime(Enum::YMDT(YMDT::long().hm())), + ("medium", "full") => DateTimeZone(Enum::YMDT(YMDT::medium()), ZoneStyle::Z), + ("medium", "long") => DateTimeZone(Enum::YMDT(YMDT::medium()), ZoneStyle::Z), + ("medium", "medium") => DateTime(Enum::YMDT(YMDT::medium())), + ("medium", "short") => DateTime(Enum::YMDT(YMDT::medium().hm())), + ("short", "full") => DateTimeZone(Enum::YMDT(YMDT::short()), ZoneStyle::Z), + ("short", "long") => DateTimeZone(Enum::YMDT(YMDT::short()), ZoneStyle::Z), + ("short", "medium") => DateTime(Enum::YMDT(YMDT::short())), + ("short", "short") => DateTime(Enum::YMDT(YMDT::short().hm())), + (date_style, time_style) => { + panic!("unknown date/time style: {date_style}, {time_style}") + } + } + } + (Some(date_style), None) => { + use CompositeFieldSet as Comp; + use DateFieldSet as Enum; + match date_style { + "full" => Comp::Date(Enum::YMDE(fieldsets::YMDE::long())), + "long" => Comp::Date(Enum::YMD(fieldsets::YMD::long())), + "medium" => Comp::Date(Enum::YMD(fieldsets::YMD::medium())), + "short" => Comp::Date(Enum::YMD(fieldsets::YMD::short())), + time_style => panic!("unknown time style: {time_style}"), + } + } + (None, Some(time_style)) => { + use CompositeFieldSet as Comp; + use TimeFieldSet as Enum; + match time_style { + "full" => Comp::TimeZone(Enum::T(fieldsets::T::long()), ZoneStyle::Z), + "long" => Comp::TimeZone(Enum::T(fieldsets::T::long()), ZoneStyle::Z), + "medium" => Comp::Time(Enum::T(fieldsets::T::medium())), + "short" => Comp::Time(Enum::T(fieldsets::T::short())), + date_style => panic!("unknown date style: {date_style}"), + } + } + (None, None) => { + // Components bag. + // The test cases only use semantic skeletons, so we can match on them here. + match json_obj["skeleton"].as_str().unwrap() { + other => panic!("unknown skeleton: {other}"), + } } - } else { - // Defaults to UTC - CustomTimeZone::utc() }; + // Get ISO instant in UTC time zone + let input_iso = &json_obj["original_input"].as_str().unwrap(); + + // Extract all the information we need from the string + let input_zoned_date_time = crate::try_or_return_error!(label, locale, { + IxdtfParser::new() + .try_from_str(&input_iso) + .map_err(|e| format!("{e:?}")) + }); + // The constructor is called with the given options // The default parameter is time zone formatter options. Not used yet. - let dtf_result = ZonedDateTimeFormatter::try_new_experimental( - &data_locale, - dt_options.clone(), - Default::default(), - ); + let dtf_result = DateTimeFormatter::try_new(preferences, field_set); let datetime_formatter = match dtf_result { Ok(dtf) => dtf, @@ -203,15 +145,13 @@ pub fn run_datetimeformat_test(json_obj: &Value) -> Result { } }; - let formatted_dt = datetime_formatter - .format(&any_datetime, &time_zone) - .expect("should work"); + let formatted_dt = datetime_formatter.format_any_calendar(&input_zoned_date_time); let result_string = formatted_dt.to_string(); Ok(json!({ "label": label, "result": result_string, "actual_options": - format!("{dt_options:?}, {time_zone:?}"), + format!("{field_set:?}, {input_zoned_date_time:?}"), })) } diff --git a/executors/rust/2.0-beta1/src/main.rs b/executors/rust/2.0-beta1/src/main.rs index bb2b08c4..90e179e6 100644 --- a/executors/rust/2.0-beta1/src/main.rs +++ b/executors/rust/2.0-beta1/src/main.rs @@ -6,7 +6,7 @@ use std::io; mod collator; -// mod datetimefmt; +mod datetimefmt; mod decimalfmt; mod displaynames; mod likelysubtags; @@ -20,6 +20,7 @@ mod relativedatetime_fmt; #[macro_use] mod try_or_return_error; +#[allow(unused)] pub fn return_error(json_obj: &serde_json::Value) -> Result { let label = &json_obj["label"].as_str().unwrap(); return Ok(serde_json::json!({ @@ -35,8 +36,7 @@ mod run_all_tests; fn main() -> io::Result<()> { let executor_fns = run_all_tests::ExecutorFns { run_collation_test: collator::run_collation_test, - run_datetimeformat_test: return_error, - // run_datetimeformat_test: datetimefmt::run_datetimeformat_test, + run_datetimeformat_test: datetimefmt::run_datetimeformat_test, run_likelysubtags_test: likelysubtags::run_likelysubtags_test, run_list_fmt_test: listfmt::run_list_fmt_test, run_locale_name_test: localenames::run_locale_name_test,