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

Add IANA/BCP47 time zone name mappings #3499

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 6 additions & 4 deletions components/datetime/src/time_zone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,23 @@ where
///
/// ```
/// use icu::calendar::DateTime;
/// use icu::timezone::{CustomTimeZone, MetazoneCalculator};
/// use icu::timezone::{CustomTimeZone, MetazoneCalculator, IanaToBcp47Mapper};
/// use icu::datetime::{DateTimeError, time_zone::TimeZoneFormatter};
/// use icu::locid::locale;
/// use tinystr::tinystr;
/// use writeable::assert_writeable_eq;
///
/// // Set up the time zone. Note: the inputs here are
/// // 1. The GMT offset
/// // 2. The BCP-47 time zone ID
/// // 2. The IANA time zone ID
/// // 3. A datetime (for metazone resolution)
/// // 4. Note: we do not need the zone variant because of `load_generic_*()`
///
/// // Set up the Metazone calculator and the DateTime to use in calculation
/// // Set up the Metazone calculator, time zone ID mapper,
/// // and the DateTime to use in calculation
/// let mzc = MetazoneCalculator::try_new_unstable(&icu_testdata::unstable())
/// .unwrap();
/// let mapper = IanaToBcp47Mapper::try_new_unstable(&icu_testdata::unstable()).unwrap();
/// let datetime = DateTime::try_new_iso_datetime(2022, 8, 29, 0, 0, 0)
/// .unwrap();
///
Expand All @@ -104,7 +106,7 @@ where
///
/// // "uschi" - has metazone symbol data for generic_non_location_short
/// let mut time_zone = "-0600".parse::<CustomTimeZone>().unwrap();
/// time_zone.time_zone_id = Some(tinystr!(8, "uschi").into());
/// time_zone.time_zone_id = mapper.as_borrowed().get_strict("America/Chicago");
/// time_zone.maybe_calculate_metazone(&mzc, &datetime);
/// assert_writeable_eq!(
/// tzf.format(&time_zone),
Expand Down
16 changes: 9 additions & 7 deletions components/timezone/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

174 changes: 174 additions & 0 deletions components/timezone/src/iana_ids.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// 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 ).

use crate::error::TimeZoneError;
use crate::provider::names::*;
use crate::TimeZoneBcp47Id;
use icu_provider::prelude::*;

/// A mapper from IANA time zone identifiers to BCP-47 time zone identifiers.
///
/// # Examples
///
/// Demonstration of usage with strict and loose lookup:
///
/// ```
/// use icu::timezone::IanaToBcp47Mapper;
///
/// let mapper = IanaToBcp47Mapper::try_new_unstable(&icu_testdata::unstable()).unwrap();
///
/// // Strict: the IANA identifier is already in case-canonical form, and we find a match
/// let bcp47_id = mapper.as_borrowed().get_strict("America/Chicago");
/// assert_eq!(bcp47_id, Some("uschi".parse().unwrap()));
///
/// // Strict: the IANA identifier is not in the correct case, so we find no match
/// let bcp47_id = mapper.as_borrowed().get_strict("america/chicago");
/// assert_eq!(bcp47_id, None);
///
/// // Loose: we find the IANA identifier even though it is in the wrong case
/// let bcp47_id = mapper.as_borrowed().get_loose("america/chicago");
/// assert_eq!(bcp47_id, Some("uschi".parse().unwrap()));
/// ```
#[derive(Debug)]
pub struct IanaToBcp47Mapper {
data: DataPayload<IanaToBcp47MapV1Marker>,
}

impl IanaToBcp47Mapper {
/// Creates a new [`IanaToBcp47Mapper`].
///
/// See [`IanaToBcp47Mapper`] for an example.
///
/// [📚 Help choosing a constructor](crate::constructors)
/// <div class="stab unstable">
/// ⚠️ The bounds on this function may change over time, including in SemVer minor releases.
/// </div>
pub fn try_new_unstable<P>(provider: &P) -> Result<Self, TimeZoneError>
where
P: DataProvider<IanaToBcp47MapV1Marker> + ?Sized,
{
let data = provider.load(Default::default())?.take_payload()?;
Ok(Self { data })
}

icu_provider::gen_any_buffer_constructors!(locale: skip, options: skip, error: TimeZoneError);

/// Returns a borrowed version of the mapper that can be queried.
///
/// This avoids a small potential cost of reading the data pointer.
pub fn as_borrowed(&self) -> IanaToBcp47MapperBorrowed {
IanaToBcp47MapperBorrowed {
data: self.data.get(),
}
}
}

/// A borrowed wrapper around IANA-to-BCP47 time zone data, returned by
/// [`IanaToBcp47Mapper::as_borrowed()`]. More efficient to query.
#[derive(Debug)]
pub struct IanaToBcp47MapperBorrowed<'a> {
data: &'a IanaToBcp47MapV1<'a>,
}

impl<'a> IanaToBcp47MapperBorrowed<'a> {
/// Looks up a BCP-47 time zone identifier based on an exact match for the given IANA
/// time zone identifier.
///
/// See examples in [`IanaToBcp47Mapper`].
pub fn get_strict(&self, iana_id: &str) -> Option<TimeZoneBcp47Id> {
self.data
.map
.get_copied(NormalizedTimeZoneIdStr::from_str(iana_id))
}

/// Looks up a BCP-47 time zone identifier based on an ASCII-case-insensitive match for
/// the given IANA time zone identifier.
///
/// This is the type of match specified in [ECMAScript Temporal].
///
/// See examples in [`IanaToBcp47Mapper`].
///
/// [ECMAScript Temporal]: https://tc39.es/proposal-temporal/#sec-isavailabletimezonename
pub fn get_loose(&self, iana_id: &str) -> Option<TimeZoneBcp47Id> {
self.data
.map
.get_copied_by(|probe| probe.cmp_loose(NormalizedTimeZoneIdStr::from_str(iana_id)))
}
}

/// A mapper from BCP-47 time zone identifiers to canonical IANA time zone identifiers.
///
/// This is mainly useful if the IANA identifier needs to be recovered. However, the ID returned
/// from this function might not be the same as the one sourced from [`IanaToBcp47Mapper`].
///
/// # Examples
///
/// Demonstration of canonicalization of the time zone identifier:
///
/// ```
/// use icu::timezone::IanaToBcp47Mapper;
/// use icu::timezone::Bcp47ToIanaMapper;
///
/// let mapper1 = IanaToBcp47Mapper::try_new_unstable(&icu_testdata::unstable()).unwrap();
/// let mapper2 = Bcp47ToIanaMapper::try_new_unstable(&icu_testdata::unstable()).unwrap();
///
/// // Look up the time zone ID for "Asia/Calcutta"
/// let bcp47_id = mapper1.as_borrowed().get_loose("asia/calcutta");
/// assert_eq!(bcp47_id, Some("inccu".parse().unwrap()));
///
/// // Get it back as the canonical form "Asia/Kolkata"
/// let mapper2_borrowed = mapper2.as_borrowed();
/// let iana_id = mapper2_borrowed.get(bcp47_id.unwrap());
/// assert_eq!(iana_id, Some("Asia/Kolkata"))
/// ```
#[derive(Debug)]
pub struct Bcp47ToIanaMapper {
data: DataPayload<Bcp47ToIanaMapV1Marker>,
}

impl Bcp47ToIanaMapper {
/// Creates a new [`Bcp47ToIanaMapper`].
///
/// See [`Bcp47ToIanaMapper`] for an example.
///
/// [📚 Help choosing a constructor](crate::constructors)
/// <div class="stab unstable">
/// ⚠️ The bounds on this function may change over time, including in SemVer minor releases.
/// </div>
pub fn try_new_unstable<P>(provider: &P) -> Result<Self, TimeZoneError>
where
P: DataProvider<Bcp47ToIanaMapV1Marker> + ?Sized,
{
let data = provider.load(Default::default())?.take_payload()?;
Ok(Self { data })
}

icu_provider::gen_any_buffer_constructors!(locale: skip, options: skip, error: TimeZoneError);

/// Returns a borrowed version of the mapper that can be queried.
///
/// This avoids a small potential cost of reading the data pointer.
pub fn as_borrowed(&self) -> Bcp47ToIanaMapperBorrowed {
Bcp47ToIanaMapperBorrowed {
data: self.data.get(),
}
}
}

/// A borrowed wrapper around IANA-to-BCP47 time zone data, returned by
/// [`Bcp47ToIanaMapper::as_borrowed()`]. More efficient to query.
#[derive(Debug)]
pub struct Bcp47ToIanaMapperBorrowed<'a> {
data: &'a Bcp47ToIanaMapV1<'a>,
}

impl<'a> Bcp47ToIanaMapperBorrowed<'a> {
/// Looks up a BCP-47 time zone identifier based on an exact match for the given IANA
/// time zone identifier.
///
/// See examples in [`Bcp47ToIanaMapper`].
pub fn get(&self, bcp47_id: TimeZoneBcp47Id) -> Option<&str> {
self.data.map.get(&bcp47_id.0.to_unvalidated())
}
}
18 changes: 11 additions & 7 deletions components/timezone/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
//! 1. IANA time zone IDs, like `"America/Chicago"`
//! 2. BCP-47 time zone IDs, like `"uschi"`
//!
//! ICU4X uses BCP-47 time zone IDs for all of its APIs.
//! ICU4X uses BCP-47 time zone IDs for all of its APIs. To get a BCP-47 time zone from an
//! IANA time zone, use [`IanaToBcp47Mapper`].
//!
//! ## Metazone
//!
Expand Down Expand Up @@ -81,17 +82,18 @@
//! the metazone based on a certain local datetime:
//!
//! ```
//! use icu_calendar::DateTime;
//! use icu_timezone::CustomTimeZone;
//! use icu_timezone::GmtOffset;
//! use icu_timezone::MetazoneCalculator;
//! use icu::calendar::DateTime;
//! use icu::timezone::CustomTimeZone;
//! use icu::timezone::GmtOffset;
//! use icu::timezone::MetazoneCalculator;
//! use icu::timezone::IanaToBcp47Mapper;
//! use tinystr::TinyAsciiStr;
//!
//! // Create a time zone for America/Chicago at GMT-6:
//! let mut time_zone = CustomTimeZone::new_empty();
//! time_zone.gmt_offset = "-0600".parse::<GmtOffset>().ok();
//! time_zone.time_zone_id =
//! "uschi".parse::<TinyAsciiStr<8>>().ok().map(Into::into);
//! let mapper = IanaToBcp47Mapper::try_new_unstable(&icu_testdata::unstable()).unwrap();
//! time_zone.time_zone_id = mapper.as_borrowed().get_strict("America/Chicago");
//!
//! // Compute the metazone at January 1, 2022:
//! let mzc = MetazoneCalculator::try_new_unstable(&icu_testdata::unstable())
Expand Down Expand Up @@ -121,12 +123,14 @@
extern crate alloc;

mod error;
mod iana_ids;
mod metazone;
pub mod provider;
mod time_zone;
mod types;

pub use error::TimeZoneError;
pub use iana_ids::{Bcp47ToIanaMapper, IanaToBcp47Mapper};
pub use metazone::MetazoneCalculator;
pub use provider::{MetazoneId, TimeZoneBcp47Id};
pub use time_zone::CustomTimeZone;
Expand Down
5 changes: 5 additions & 0 deletions components/timezone/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ use tinystr::TinyAsciiStr;
use zerovec::ule::{AsULE, ULE};
use zerovec::{ZeroMap2d, ZeroSlice, ZeroVec};

pub mod names;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: not sure if we can say this fixes #2909 since this is just the data model, there's no fetcher struct yet

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pushed the API


pub use names::Bcp47ToIanaMapV1Marker;
pub use names::IanaToBcp47MapV1Marker;

/// TimeZone ID in BCP47 format
///
/// <div class="stab unstable">
Expand Down
Loading