From 245f9d09972342a51e7bf208fd4d0a2246881451 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 29 Feb 2024 09:39:03 -0500 Subject: [PATCH] Render times in local and UTC --- l10n/src/lib.rs | 155 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 122 insertions(+), 33 deletions(-) diff --git a/l10n/src/lib.rs b/l10n/src/lib.rs index bbdc712..4b94982 100644 --- a/l10n/src/lib.rs +++ b/l10n/src/lib.rs @@ -1,28 +1,39 @@ use std::ops::Deref; -use chrono::{Datelike, FixedOffset, NaiveDate, Timelike}; -use chrono_tz::Tz; -use icu::datetime::options::length; -use icu_locid::locale; +use chrono::{Datelike, FixedOffset, NaiveDate, TimeZone, Timelike}; +use chrono_tz::{OffsetName, Tz}; +use icu::{ + datetime::options::length, + timezone::{CustomTimeZone, GmtOffset}, +}; +use icu_locid::{locale, Locale}; use icu_provider::DataLocale; -use sys_locale::get_locales; +use std::str::FromStr; +use sys_locale::get_locale; -#[derive(Clone, Copy, Debug)] -pub struct L10N {} +#[derive(Clone, Debug)] +pub struct L10N { + locale: Locale, + zone: chrono_tz::Tz, +} impl Default for L10N { fn default() -> Self { - Self {} + let lc = get_locale().unwrap(); + let locale = Locale::try_from_bytes(lc.as_bytes()).unwrap(); + let zone = chrono_tz::UTC; + Self { locale, zone } } } impl L10N { fn set_locale(&mut self, locale: String) { - unimplemented!() + let locale = Locale::try_from_bytes(locale.as_bytes()).unwrap(); + self.locale = locale; } fn set_timezone(&mut self, zone: Tz) { - unimplemented!() + self.zone = zone; } // Need to take a message and turn it into a string in the current language. Except I don't @@ -39,7 +50,15 @@ impl L10N { date_style: length::Date, time_style: length::Time, ) -> String { - unimplemented!() + let time: DateTime = time.with_timezone(&chrono_tz::UTC).into(); + let options = length::Bag::from_date_time_style(date_style, time_style); + let formatter = icu::datetime::DateTimeFormatter::try_new( + &DataLocale::from(&self.locale), + options.into(), + ) + .unwrap(); + let icu_time: icu::calendar::DateTime = time.into(); + formatter.format_to_string(&icu_time.to_any()).unwrap() } fn format_date_time_local( @@ -48,27 +67,60 @@ impl L10N { date_style: length::Date, time_style: length::Time, ) -> String { - unimplemented!() - } - - fn format_date_time_tz( - &self, - time: DateTime, - date_style: length::Date, - time_style: length::Time, - ) -> String { + let time: DateTime = time.with_timezone(&self.zone).into(); let options = length::Bag::from_date_time_style(date_style, time_style); let formatter = icu::datetime::DateTimeFormatter::try_new( - &DataLocale::from(locale!("en-US")), + &DataLocale::from(&self.locale), options.into(), ) .unwrap(); let icu_time: icu::calendar::DateTime = time.into(); - let any = icu_time.to_any(); - - formatter.format_to_string(&any).unwrap() + formatter.format_to_string(&icu_time.to_any()).unwrap() } + /* + * I have been unable to get from a chrono_tz::Tz to an ICU timezone. I have tried a variety of + * parsers on the CustomTimeZone object. I have not researched the data provider to see what is + * available there. The ZoneID for the reference date is US/Mountain, and the abbreviation is + * MST. I'll want to get to a CustomTimeZone so that the formatter can render MST or Mountain + * Standard Time or something similar. + fn format_date_time_tz( + &self, + time: DateTime, + date_style: length::Date, + time_style: length::Time, + ) -> String { + let options = length::Bag::from_date_time_style(date_style, time_style); + let formatter = icu::datetime::ZonedDateTimeFormatter::try_new( + &DataLocale::from(&self.locale), + options.into(), + Default::default(), + ) + .unwrap(); + let icu_time: icu::calendar::DateTime = time.into(); + let any = icu_time.to_any(); + + println!("{:?}", time.offset()); + + let zone_id: String = time.offset().abbreviation().to_owned(); + println!("{:?}", zone_id); + let zone_id = icu::timezone::TimeZoneBcp47Id::from_str(&zone_id).unwrap(); + + let zone: CustomTimeZone = CustomTimeZone { + gmt_offset: None, + time_zone_id: Some(zone_id), + /* + icu::timezone::TimeZoneBcp47Id::from_str(time.offset().tz_id().parse().unwrap()) + .unwrap(), + */ + metazone_id: None, + zone_variant: None, + }; + + formatter.format_to_string(&any, &zone).unwrap() + } + */ + fn format_date( &self, time: NaiveDate, @@ -83,6 +135,7 @@ impl L10N { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] struct DateTime(chrono::DateTime); impl Deref for DateTime { @@ -119,23 +172,59 @@ impl From for icu::calendar::DateTime { mod tests { use super::*; - #[test] - fn it_formats_a_time_according_to_locale() { - let l10n = L10N::default(); - - let now = NaiveDate::from_ymd_opt(2006, 1, 2) + fn reftime() -> DateTime { + NaiveDate::from_ymd_opt(2006, 1, 2) .unwrap() .and_hms_opt(3, 4, 5) .unwrap() - .and_local_timezone(Tz::UTC) + .and_local_timezone(Tz::US__Mountain) .unwrap() - .into(); + .into() + } + + #[test] + fn it_formats_a_time_in_utc() { + let mut l10n = L10N::default(); + // Make sure we know the locale before the test begins. Some systems, such as my own, are + // not actually in English. + l10n.set_locale("en-US".to_owned()); + l10n.set_timezone(chrono_tz::US::Eastern); + let now = reftime(); // 202f is the code-point for a narrow non-breaking space. Presumably this is used in // particular to ensure that the am/pm marker doesn't get split off from the time assert_eq!( - l10n.format_date_time_tz(now, length::Date::Long, length::Time::Medium), - "January 2, 2006, 3:04:05\u{202f}AM" + l10n.format_date_time_utc(now.clone(), length::Date::Long, length::Time::Medium), + "January 2, 2006, 10:04:05\u{202f}AM" + ); + + l10n.set_locale("eo-EO".to_owned()); + assert_eq!( + l10n.format_date_time_utc(now.clone(), length::Date::Long, length::Time::Medium), + "2006-Januaro-02 10:04:05" + ); + } + + #[test] + fn it_formats_a_time_in_the_current_zone() { + let mut l10n = L10N::default(); + // Make sure we know the locale before the test begins. Some systems, such as my own, are + // not actually in English. + l10n.set_locale("en-US".to_owned()); + l10n.set_timezone(chrono_tz::US::Eastern); + let now = reftime(); + + // 202f is the code-point for a narrow non-breaking space. Presumably this is used in + // particular to ensure that the am/pm marker doesn't get split off from the time + assert_eq!( + l10n.format_date_time_local(now.clone(), length::Date::Long, length::Time::Medium), + "January 2, 2006, 5:04:05\u{202f}AM" + ); + + l10n.set_locale("eo-EO".to_owned()); + assert_eq!( + l10n.format_date_time_local(now.clone(), length::Date::Long, length::Time::Medium), + "2006-Januaro-02 05:04:05" ); }