Render times in local and UTC

This commit is contained in:
Savanni D'Gerinel 2024-02-29 09:39:03 -05:00
parent 22b772a8c7
commit 245f9d0997
1 changed files with 122 additions and 33 deletions

View File

@ -1,28 +1,39 @@
use std::ops::Deref; use std::ops::Deref;
use chrono::{Datelike, FixedOffset, NaiveDate, Timelike}; use chrono::{Datelike, FixedOffset, NaiveDate, TimeZone, Timelike};
use chrono_tz::Tz; use chrono_tz::{OffsetName, Tz};
use icu::datetime::options::length; use icu::{
use icu_locid::locale; datetime::options::length,
timezone::{CustomTimeZone, GmtOffset},
};
use icu_locid::{locale, Locale};
use icu_provider::DataLocale; use icu_provider::DataLocale;
use sys_locale::get_locales; use std::str::FromStr;
use sys_locale::get_locale;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Debug)]
pub struct L10N {} pub struct L10N {
locale: Locale,
zone: chrono_tz::Tz,
}
impl Default for L10N { impl Default for L10N {
fn default() -> Self { 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 { impl L10N {
fn set_locale(&mut self, locale: String) { 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) { 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 // 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, date_style: length::Date,
time_style: length::Time, time_style: length::Time,
) -> String { ) -> 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<icu::calendar::Gregorian> = time.into();
formatter.format_to_string(&icu_time.to_any()).unwrap()
} }
fn format_date_time_local( fn format_date_time_local(
@ -48,9 +67,23 @@ impl L10N {
date_style: length::Date, date_style: length::Date,
time_style: length::Time, time_style: length::Time,
) -> String { ) -> String {
unimplemented!() 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(&self.locale),
options.into(),
)
.unwrap();
let icu_time: icu::calendar::DateTime<icu::calendar::Gregorian> = time.into();
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( fn format_date_time_tz(
&self, &self,
time: DateTime, time: DateTime,
@ -58,16 +91,35 @@ impl L10N {
time_style: length::Time, time_style: length::Time,
) -> String { ) -> String {
let options = length::Bag::from_date_time_style(date_style, time_style); let options = length::Bag::from_date_time_style(date_style, time_style);
let formatter = icu::datetime::DateTimeFormatter::try_new( let formatter = icu::datetime::ZonedDateTimeFormatter::try_new(
&DataLocale::from(locale!("en-US")), &DataLocale::from(&self.locale),
options.into(), options.into(),
Default::default(),
) )
.unwrap(); .unwrap();
let icu_time: icu::calendar::DateTime<icu::calendar::Gregorian> = time.into(); let icu_time: icu::calendar::DateTime<icu::calendar::Gregorian> = time.into();
let any = icu_time.to_any(); let any = icu_time.to_any();
formatter.format_to_string(&any).unwrap() 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( fn format_date(
&self, &self,
@ -83,6 +135,7 @@ impl L10N {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct DateTime(chrono::DateTime<Tz>); struct DateTime(chrono::DateTime<Tz>);
impl Deref for DateTime { impl Deref for DateTime {
@ -119,23 +172,59 @@ impl From<DateTime> for icu::calendar::DateTime<icu::calendar::Gregorian> {
mod tests { mod tests {
use super::*; use super::*;
#[test] fn reftime() -> DateTime {
fn it_formats_a_time_according_to_locale() { NaiveDate::from_ymd_opt(2006, 1, 2)
let l10n = L10N::default();
let now = NaiveDate::from_ymd_opt(2006, 1, 2)
.unwrap() .unwrap()
.and_hms_opt(3, 4, 5) .and_hms_opt(3, 4, 5)
.unwrap() .unwrap()
.and_local_timezone(Tz::UTC) .and_local_timezone(Tz::US__Mountain)
.unwrap() .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 // 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 // particular to ensure that the am/pm marker doesn't get split off from the time
assert_eq!( assert_eq!(
l10n.format_date_time_tz(now, length::Date::Long, length::Time::Medium), l10n.format_date_time_utc(now.clone(), length::Date::Long, length::Time::Medium),
"January 2, 2006, 3:04:05\u{202f}AM" "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"
); );
} }