From d2c4f63a19e3b4a61d6676df184842b0627532ef Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Thu, 23 May 2024 09:56:30 -0700 Subject: [PATCH] Add TimeZoneIdMapper to icu_timezone (#4774) Fixes #4031 Replaces #4548 This new all-in-one type supports all of the time zone ID operations we need, with some operations being asymptotically faster than others. Not sure if we want to deprecate the old ones or keep them around, at least IanaBcp47RoundTripMapper. It's not completely obsolete since it has different performance characteristics. --------- Co-authored-by: Robert Bastian <4706271+robertbastian@users.noreply.github.com> --- CHANGELOG.md | 2 + components/datetime/src/time_zone.rs | 6 +- components/timezone/Cargo.toml | 2 +- components/timezone/README.md | 8 +- components/timezone/src/iana_ids.rs | 6 + components/timezone/src/ids.rs | 694 ++++++++++++++++++ components/timezone/src/lib.rs | 14 +- components/timezone/src/provider.rs | 11 + ffi/capi/bindings/c/ICU4XCustomTimeZone.h | 3 + ffi/capi/bindings/c/ICU4XError.h | 1 + ffi/capi/bindings/c/ICU4XTimeZoneIdMapper.h | 40 + ...TimeZoneIdMapperWithFastCanonicalization.h | 36 + ...apperWithFastCanonicalization_ICU4XError.h | 26 + ...ult_box_ICU4XTimeZoneIdMapper_ICU4XError.h | 26 + ffi/capi/bindings/cpp/ICU4XCustomTimeZone.h | 3 + ffi/capi/bindings/cpp/ICU4XCustomTimeZone.hpp | 20 + ffi/capi/bindings/cpp/ICU4XError.h | 1 + ffi/capi/bindings/cpp/ICU4XError.hpp | 9 + ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapper.h | 40 + .../bindings/cpp/ICU4XTimeZoneIdMapper.hpp | 196 +++++ ...TimeZoneIdMapperWithFastCanonicalization.h | 36 + ...meZoneIdMapperWithFastCanonicalization.hpp | 130 ++++ ...apperWithFastCanonicalization_ICU4XError.h | 26 + ...ult_box_ICU4XTimeZoneIdMapper_ICU4XError.h | 26 + ffi/capi/bindings/dart/CustomTimeZone.g.dart | 22 + ffi/capi/bindings/dart/Error.g.dart | 6 + .../bindings/dart/TimeZoneIdMapper.g.dart | 130 ++++ ...oneIdMapperWithFastCanonicalization.g.dart | 90 +++ ffi/capi/bindings/dart/lib.g.dart | 2 + ffi/capi/bindings/js/ICU4XCustomTimeZone.d.ts | 10 + ffi/capi/bindings/js/ICU4XCustomTimeZone.mjs | 20 + ffi/capi/bindings/js/ICU4XError.d.ts | 7 + ffi/capi/bindings/js/ICU4XError.mjs | 3 + .../bindings/js/ICU4XTimeZoneIdMapper.d.ts | 49 ++ .../bindings/js/ICU4XTimeZoneIdMapper.mjs | 123 ++++ ...eZoneIdMapperWithFastCanonicalization.d.ts | 35 + ...meZoneIdMapperWithFastCanonicalization.mjs | 79 ++ ffi/capi/bindings/js/index.d.ts | 2 + ffi/capi/bindings/js/index.mjs | 2 + ffi/capi/src/errors.rs | 10 +- ffi/capi/src/iana_bcp47_mapper.rs | 2 + ffi/capi/src/lib.rs | 2 + ffi/capi/src/timezone.rs | 27 + ffi/capi/src/timezone_mapper.rs | 205 ++++++ provider/baked/timezone/data/macros.rs | 7 + .../macros/time_zone_iana_to_bcp47_v2.rs.data | 42 ++ .../src/transform/cldr/time_zones/names.rs | 28 + tools/ffi_coverage/src/allowlist.rs | 3 + tutorials/cpp/datetime.cpp | 30 +- utils/zerotrie/src/cursor.rs | 5 +- 50 files changed, 2280 insertions(+), 23 deletions(-) create mode 100644 components/timezone/src/ids.rs create mode 100644 ffi/capi/bindings/c/ICU4XTimeZoneIdMapper.h create mode 100644 ffi/capi/bindings/c/ICU4XTimeZoneIdMapperWithFastCanonicalization.h create mode 100644 ffi/capi/bindings/c/diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError.h create mode 100644 ffi/capi/bindings/c/diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError.h create mode 100644 ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapper.h create mode 100644 ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapper.hpp create mode 100644 ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapperWithFastCanonicalization.h create mode 100644 ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapperWithFastCanonicalization.hpp create mode 100644 ffi/capi/bindings/cpp/diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError.h create mode 100644 ffi/capi/bindings/cpp/diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError.h create mode 100644 ffi/capi/bindings/dart/TimeZoneIdMapper.g.dart create mode 100644 ffi/capi/bindings/dart/TimeZoneIdMapperWithFastCanonicalization.g.dart create mode 100644 ffi/capi/bindings/js/ICU4XTimeZoneIdMapper.d.ts create mode 100644 ffi/capi/bindings/js/ICU4XTimeZoneIdMapper.mjs create mode 100644 ffi/capi/bindings/js/ICU4XTimeZoneIdMapperWithFastCanonicalization.d.ts create mode 100644 ffi/capi/bindings/js/ICU4XTimeZoneIdMapperWithFastCanonicalization.mjs create mode 100644 ffi/capi/src/timezone_mapper.rs create mode 100644 provider/baked/timezone/data/macros/time_zone_iana_to_bcp47_v2.rs.data diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d7ba779ca0..81d90e972b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ - Implement Hangul_Syllable_Type property (https://github.com/unicode-org/icu4x/pull/4885) - `icu_segmenter` - Fix Unicode 15.0 line breaking (https://github.com/unicode-org/icu4x/pull/4389) + - `icu_timezone` + - Added `TimeZoneIdMapper` to replace `IanaToBcp47Mapper` (https://github.com/unicode-org/icu4x/pull/4774) - Data model and providers - `icu_datagen` - Datagen shows elapsed time for keys that are slow to generate (https://github.com/unicode-org/icu4x/pull/4469) diff --git a/components/datetime/src/time_zone.rs b/components/datetime/src/time_zone.rs index ab812fe1f9e..e3d74a65131 100644 --- a/components/datetime/src/time_zone.rs +++ b/components/datetime/src/time_zone.rs @@ -72,7 +72,7 @@ where /// /// ``` /// use icu::calendar::DateTime; -/// use icu::timezone::{CustomTimeZone, MetazoneCalculator, IanaToBcp47Mapper}; +/// use icu::timezone::{CustomTimeZone, MetazoneCalculator, TimeZoneIdMapper}; /// use icu::datetime::{DateTimeError, time_zone::TimeZoneFormatter}; /// use icu::locid::locale; /// use tinystr::tinystr; @@ -87,7 +87,7 @@ where /// // Set up the Metazone calculator, time zone ID mapper, /// // and the DateTime to use in calculation /// let mzc = MetazoneCalculator::new(); -/// let mapper = IanaToBcp47Mapper::new(); +/// let mapper = TimeZoneIdMapper::new(); /// let datetime = DateTime::try_new_iso_datetime(2022, 8, 29, 0, 0, 0) /// .unwrap(); /// @@ -102,7 +102,7 @@ where /// /// // "uschi" - has metazone symbol data for generic_non_location_short /// let mut time_zone = "-0600".parse::().unwrap(); -/// time_zone.time_zone_id = mapper.as_borrowed().get("America/Chicago"); +/// time_zone.time_zone_id = mapper.as_borrowed().iana_to_bcp47("America/Chicago"); /// time_zone.maybe_calculate_metazone(&mzc, &datetime); /// assert_writeable_eq!( /// tzf.format(&time_zone), diff --git a/components/timezone/Cargo.toml b/components/timezone/Cargo.toml index 4b4df8646d8..01119dd62fd 100644 --- a/components/timezone/Cargo.toml +++ b/components/timezone/Cargo.toml @@ -24,7 +24,7 @@ displaydoc = { workspace = true } icu_calendar = { workspace = true } icu_provider = { workspace = true, features = ["macros"] } tinystr = { workspace = true, features = ["alloc", "zerovec"] } -zerotrie = { workspace = true, features = ["yoke", "zerofrom"] } +zerotrie = { workspace = true, features = ["alloc", "yoke", "zerofrom"] } zerovec = { workspace = true, features = ["derive", "yoke"] } databake = { workspace = true, optional = true, features = ["derive"] } diff --git a/components/timezone/README.md b/components/timezone/README.md index e0d85f7c365..b48d71d91f6 100644 --- a/components/timezone/README.md +++ b/components/timezone/README.md @@ -31,7 +31,7 @@ There are two mostly-interchangeable standards for time zone IDs: 2. BCP-47 time zone IDs, like `"uschi"` 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`]. +IANA time zone, use [`TimeZoneIdMapper`]. ### Metazone @@ -87,15 +87,15 @@ the metazone based on a certain local datetime: use icu::calendar::DateTime; use icu::timezone::CustomTimeZone; use icu::timezone::GmtOffset; -use icu::timezone::IanaToBcp47Mapper; +use icu::timezone::TimeZoneIdMapper; use icu::timezone::MetazoneCalculator; use tinystr::{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::().ok(); -let mapper = IanaToBcp47Mapper::new(); -time_zone.time_zone_id = mapper.as_borrowed().get("America/Chicago"); +let mapper = TimeZoneIdMapper::new(); +time_zone.time_zone_id = mapper.as_borrowed().iana_to_bcp47("America/Chicago"); // Alternatively, set it directly from the BCP-47 ID assert_eq!(time_zone.time_zone_id, Some(tinystr!(8, "uschi").into())); diff --git a/components/timezone/src/iana_ids.rs b/components/timezone/src/iana_ids.rs index 66abe9070e2..6c9ad41dc25 100644 --- a/components/timezone/src/iana_ids.rs +++ b/components/timezone/src/iana_ids.rs @@ -2,6 +2,8 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). +#![allow(deprecated)] // all APIS in here are deprecated + use crate::error::TimeZoneError; use crate::provider::names::*; use crate::TimeZoneBcp47Id; @@ -36,6 +38,7 @@ use icu_provider::prelude::*; /// ); /// ``` #[derive(Debug)] +#[deprecated(since = "1.5.0", note = "use `TimeZoneIdMapper` instead")] pub struct IanaToBcp47Mapper { data: DataPayload, } @@ -98,6 +101,7 @@ impl IanaToBcp47Mapper { /// A borrowed wrapper around IANA-to-BCP47 time zone data, returned by /// [`IanaToBcp47Mapper::as_borrowed()`]. More efficient to query. #[derive(Debug)] +#[deprecated(since = "1.5.0", note = "use `TimeZoneIdMapper` instead")] pub struct IanaToBcp47MapperBorrowed<'a> { data: &'a IanaToBcp47MapV1<'a>, } @@ -167,6 +171,7 @@ impl<'a> IanaToBcp47MapperBorrowed<'a> { /// assert_eq!(iana_id, Some("Asia/Kolkata")) /// ``` #[derive(Debug)] +#[deprecated(since = "1.5.0", note = "use `TimeZoneIdMapper` instead")] pub struct IanaBcp47RoundTripMapper { data1: DataPayload, data2: DataPayload, @@ -243,6 +248,7 @@ impl IanaBcp47RoundTripMapper { /// A borrowed wrapper around IANA-BCP47 time zone data, returned by /// [`IanaBcp47RoundTripMapper::as_borrowed()`]. More efficient to query. #[derive(Debug)] +#[deprecated(since = "1.5.0", note = "use `TimeZoneIdMapper` instead")] pub struct IanaBcp47RoundTripMapperBorrowed<'a> { data1: &'a IanaToBcp47MapV1<'a>, data2: &'a Bcp47ToIanaMapV1<'a>, diff --git a/components/timezone/src/ids.rs b/components/timezone/src/ids.rs new file mode 100644 index 00000000000..c5bd6d6b533 --- /dev/null +++ b/components/timezone/src/ids.rs @@ -0,0 +1,694 @@ +// 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 alloc::borrow::Cow; +use alloc::string::String; +use alloc::vec::Vec; +use icu_provider::prelude::*; +use zerotrie::cursor::ZeroAsciiIgnoreCaseTrieCursor; + +use crate::{ + provider::names::{ + Bcp47ToIanaMapV1, Bcp47ToIanaMapV1Marker, IanaToBcp47MapV2, IanaToBcp47MapV2Marker, + }, + TimeZoneBcp47Id, TimeZoneError, +}; + +/// A mapper between IANA time zone identifiers and BCP-47 time zone identifiers. +/// +/// This mapper supports two-way mapping, but it is optimized for the case of IANA to BCP-47. +/// It also supports normalizing and canonicalizing the IANA strings. +/// +/// There are approximately 600 IANA identifiers and 450 BCP-47 identifiers. +/// +/// BCP-47 time zone identifiers are 8 ASCII characters or less and currently +/// average 5.1 characters long. Current IANA time zone identifiers are less than +/// 40 ASCII characters and average 14.2 characters long. +/// +/// These lists grow very slowly; in a typical year, 2-3 new identifiers are added. +/// +/// # Normalization vs Canonicalization +/// +/// Multiple IANA time zone identifiers can refer to the same BCP-47 time zone. For example, the +/// following three IANA identifiers all map to `"usind"`: +/// +/// - "America/Fort_Wayne" +/// - "America/Indiana/Indianapolis" +/// - "America/Indianapolis" +/// - "US/East-Indiana" +/// +/// There is only one canonical identifier, which is "America/Indiana/Indianapolis". The +/// *canonicalization* operation returns the canonical identifier. You should canonicalize if +/// you need to compare time zones for equality. Note that the canonical identifier can change +/// over time. For example, the identifier "Europe/Kiev" was renamed to the newly-added +/// identifier "Europe/Kyiv" in 2022. +/// +/// The *normalization* operation, on the other hand, keeps the input identifier but normalizes +/// the casing. For example, "AMERICA/FORT_WAYNE" normalizes to "America/Fort_Wayne". +/// Normalization is a data-driven operation because there are no algorithmic casing rules that +/// work for all IANA time zone identifiers. +/// +/// Normalization is a cheap operation, but canonicalization might be expensive, since it might +/// require searching over all IANA IDs to find the canonicalization. If you need +/// canonicalization that is reliably fast, use [`TimeZoneIdMapperWithFastCanonicalization`]. +/// +/// # Examples +/// +/// ``` +/// use icu::timezone::TimeZoneIdMapper; +/// +/// let mapper = TimeZoneIdMapper::new(); +/// let mapper = mapper.as_borrowed(); +/// +/// // The IANA zone "Australia/Melbourne" is the BCP-47 zone "aumel": +/// assert_eq!( +/// mapper.iana_to_bcp47("Australia/Melbourne"), +/// Some("aumel".parse().unwrap()) +/// ); +/// +/// // Lookup is ASCII-case-insensitive: +/// assert_eq!( +/// mapper.iana_to_bcp47("australia/melbourne"), +/// Some("aumel".parse().unwrap()) +/// ); +/// +/// // The IANA zone "Australia/Victoria" is an alias: +/// assert_eq!( +/// mapper.iana_to_bcp47("Australia/Victoria"), +/// Some("aumel".parse().unwrap()) +/// ); +/// +/// // We can recover the canonical identifier from the mapper: +/// assert_eq!( +/// mapper +/// .canonicalize_iana("Australia/Victoria") +/// .unwrap() +/// .0, +/// "Australia/Melbourne" +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct TimeZoneIdMapper { + data: DataPayload, +} + +#[cfg(feature = "compiled_data")] +impl Default for TimeZoneIdMapper { + fn default() -> Self { + Self::new() + } +} + +impl TimeZoneIdMapper { + /// Creates a new [`TimeZoneIdMapper`] using compiled data. + /// + /// See [`TimeZoneIdMapper`] for an example. + /// + /// ✨ *Enabled with the `compiled_data` Cargo feature.* + /// + /// [📚 Help choosing a constructor](icu_provider::constructors) + #[cfg(feature = "compiled_data")] + pub fn new() -> Self { + Self { + data: DataPayload::from_static_ref( + crate::provider::Baked::SINGLETON_TIME_ZONE_IANA_TO_BCP47_V2, + ), + } + } + + icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: DataError, + #[cfg(skip)] + functions: [ + new, + try_new_with_any_provider, + try_new_with_buffer_provider, + try_new_unstable, + Self, + ] + ); + + #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)] + pub fn try_new_unstable

(provider: &P) -> Result + where + P: DataProvider + ?Sized, + { + let data = provider.load(Default::default())?.take_payload()?; + Ok(Self { data }) + } + + /// Returns a borrowed version of the mapper that can be queried. + /// + /// This avoids a small potential indirection cost when querying the mapper. + pub fn as_borrowed(&self) -> TimeZoneIdMapperBorrowed { + TimeZoneIdMapperBorrowed { + data: self.data.get(), + } + } +} + +impl AsRef for TimeZoneIdMapper { + #[inline] + fn as_ref(&self) -> &TimeZoneIdMapper { + self + } +} + +/// A borrowed wrapper around the time zone ID mapper, returned by +/// [`TimeZoneIdMapper::as_borrowed()`]. More efficient to query. +#[derive(Debug, Copy, Clone)] +pub struct TimeZoneIdMapperBorrowed<'a> { + data: &'a IanaToBcp47MapV2<'a>, +} + +impl<'a> TimeZoneIdMapperBorrowed<'a> { + /// Gets the BCP-47 time zone ID from an IANA time zone ID + /// with a case-insensitive lookup. + /// + /// Returns `None` if the IANA ID is not found. + /// + /// # Examples + /// + /// ``` + /// use icu_timezone::TimeZoneBcp47Id; + /// use icu_timezone::TimeZoneIdMapper; + /// + /// let mapper = TimeZoneIdMapper::new(); + /// let mapper = mapper.as_borrowed(); + /// + /// let result = mapper.iana_to_bcp47("Asia/CALCUTTA").unwrap(); + /// + /// assert_eq!(*result, "inccu"); + /// + /// // Unknown IANA time zone ID: + /// assert_eq!(mapper.iana_to_bcp47("America/San_Francisco"), None); + /// ``` + pub fn iana_to_bcp47(&self, iana_id: &str) -> Option { + self.iana_lookup_quick(iana_id) + .and_then(|trie_value| self.data.bcp47_ids.get(trie_value.index())) + } + + /// Same as [`Self::iana_to_bcp47()`] but works with potentially ill-formed UTF-8. + pub fn iana_bytes_to_bcp47(&self, iana_id: &[u8]) -> Option { + self.iana_lookup_quick(iana_id) + .and_then(|trie_value| self.data.bcp47_ids.get(trie_value.index())) + } + + /// Normalizes the syntax of an IANA time zone ID. + /// + /// Also returns the BCP-47 time zone ID. + /// + /// Returns `None` if the IANA ID is not found. + /// + /// # Examples + /// + /// ``` + /// use icu_timezone::TimeZoneBcp47Id; + /// use icu_timezone::TimeZoneIdMapper; + /// use std::borrow::Cow; + /// + /// let mapper = TimeZoneIdMapper::new(); + /// let mapper = mapper.as_borrowed(); + /// + /// let result = mapper.normalize_iana("Asia/CALCUTTA").unwrap(); + /// + /// assert_eq!(result.0, "Asia/Calcutta"); + /// assert!(matches!(result.0, Cow::Owned(_))); + /// assert_eq!(*result.1, "inccu"); + /// + /// // Borrows when able: + /// let result = mapper.normalize_iana("America/Chicago").unwrap(); + /// assert_eq!(result.0, "America/Chicago"); + /// assert!(matches!(result.0, Cow::Borrowed(_))); + /// + /// // Unknown IANA time zone ID: + /// assert_eq!(mapper.normalize_iana("America/San_Francisco"), None); + /// ``` + pub fn normalize_iana<'s>(&self, iana_id: &'s str) -> Option<(Cow<'s, str>, TimeZoneBcp47Id)> { + let (trie_value, string) = self.iana_lookup_with_normalization(iana_id, |_| {})?; + let Some(bcp47_id) = self.data.bcp47_ids.get(trie_value.index()) else { + debug_assert!(false, "index should be in range"); + return None; + }; + Some((string, bcp47_id)) + } + + /// Returns the canonical, normalized identifier of the given IANA time zone. + /// + /// Also returns the BCP-47 time zone ID. + /// + /// Returns `None` if the IANA ID is not found. + /// + /// # Examples + /// + /// ``` + /// use icu_timezone::TimeZoneBcp47Id; + /// use icu_timezone::TimeZoneIdMapper; + /// use std::borrow::Cow; + /// + /// let mapper = TimeZoneIdMapper::new(); + /// let mapper = mapper.as_borrowed(); + /// + /// let result = mapper.canonicalize_iana("Asia/CALCUTTA").unwrap(); + /// + /// assert_eq!(result.0, "Asia/Kolkata"); + /// assert!(matches!(result.0, Cow::Owned(_))); + /// assert_eq!(*result.1, "inccu"); + /// + /// // Borrows when able: + /// let result = mapper.canonicalize_iana("America/Chicago").unwrap(); + /// assert_eq!(result.0, "America/Chicago"); + /// assert!(matches!(result.0, Cow::Borrowed(_))); + /// + /// // Unknown IANA time zone ID: + /// assert_eq!(mapper.canonicalize_iana("America/San_Francisco"), None); + /// ``` + pub fn canonicalize_iana<'s>( + &self, + iana_id: &'s str, + ) -> Option<(Cow<'s, str>, TimeZoneBcp47Id)> { + // Note: We collect the cursors into a stack so that we start probing + // nearby the input IANA identifier. This should improve lookup time since + // most renames share the same prefix like "Asia" or "Europe". + let mut stack = Vec::with_capacity(iana_id.len()); + let (trie_value, string) = self.iana_lookup_with_normalization(iana_id, |cursor| { + stack.push((cursor.clone(), 0, 1)); + })?; + let Some(bcp47_id) = self.data.bcp47_ids.get(trie_value.index()) else { + debug_assert!(false, "index should be in range"); + return None; + }; + if trie_value.is_canonical() { + return Some((string, bcp47_id)); + } + // If we get here, we need to walk the trie to find the canonical IANA ID. + let needle = trie_value.to_canonical(); + let Some(string) = self.iana_search(needle, string.into_owned(), stack) else { + debug_assert!(false, "every time zone should have a canonical IANA ID"); + return None; + }; + Some((Cow::Owned(string), bcp47_id)) + } + + /// Returns the canonical, normalized IANA ID of the given BCP-47 ID. + /// + /// This function performs a linear search over all IANA IDs. If this is problematic, consider one of the + /// following functions instead: + /// + /// 1. [`TimeZoneIdMapperBorrowed::canonicalize_iana()`] + /// is faster if you have an IANA ID. + /// 2. [`TimeZoneIdMapperWithFastCanonicalizationBorrowed::canonical_iana_from_bcp47()`] + /// is faster, but it requires loading additional data + /// (see [`TimeZoneIdMapperWithFastCanonicalization`]). + /// + /// Returns `None` if the BCP-47 ID is not found. + /// + /// # Examples + /// + /// ``` + /// use icu_timezone::TimeZoneBcp47Id; + /// use icu_timezone::TimeZoneIdMapper; + /// use std::borrow::Cow; + /// use tinystr::tinystr; + /// + /// let mapper = TimeZoneIdMapper::new(); + /// let mapper = mapper.as_borrowed(); + /// + /// let bcp47_id = TimeZoneBcp47Id(tinystr!(8, "inccu")); + /// let result = mapper.find_canonical_iana_from_bcp47(bcp47_id).unwrap(); + /// + /// assert_eq!(result, "Asia/Kolkata"); + /// + /// // Unknown BCP-47 time zone ID: + /// let bcp47_id = TimeZoneBcp47Id(tinystr!(8, "ussfo")); + /// assert_eq!(mapper.find_canonical_iana_from_bcp47(bcp47_id), None); + /// ``` + pub fn find_canonical_iana_from_bcp47(&self, bcp47_id: TimeZoneBcp47Id) -> Option { + let index = self.data.bcp47_ids.binary_search(&bcp47_id).ok()?; + let stack = alloc::vec![(self.data.map.cursor(), 0, 0)]; + let needle = IanaTrieValue::canonical_for_index(index); + let string = self.iana_search(needle, String::new(), stack)?; + Some(string) + } + + /// Queries the data for `iana_id` without recording the normalized string. + /// This is a fast, no-alloc lookup. + fn iana_lookup_quick(&self, iana_id: impl AsRef<[u8]>) -> Option { + self.data.map.get(iana_id).map(IanaTrieValue) + } + + /// Queries the data for `iana_id` while keeping track of the normalized string. + /// This is a fast lookup, but it may require allocating memory. + fn iana_lookup_with_normalization<'l, 's>( + &'l self, + iana_id: &'s str, + mut cursor_fn: impl FnMut(&ZeroAsciiIgnoreCaseTrieCursor<'l>), + ) -> Option<(IanaTrieValue, Cow<'s, str>)> { + let mut cursor = self.data.map.cursor(); + let mut string = Cow::Borrowed(iana_id); + let mut i = 0; + let trie_value = loop { + cursor_fn(&cursor); + let Some(&input_byte) = string.as_bytes().get(i) else { + break cursor.take_value().map(IanaTrieValue); + }; + let Some(matched_byte) = cursor.step(input_byte) else { + break None; + }; + if matched_byte != input_byte { + // Safety: we write to input_byte farther down after performing safety checks. + let Some(input_byte) = unsafe { string.to_mut().as_bytes_mut() }.get_mut(i) else { + debug_assert!(false, "the same index was just accessed earlier"); + break None; + }; + if !input_byte.is_ascii() { + debug_assert!(false, "non-ASCII input byte: {input_byte}"); + break None; + } + if !matched_byte.is_ascii() { + debug_assert!(false, "non-ASCII matched byte: {matched_byte}"); + break None; + } + // Safety: we just checked that both input_byte and matched_byte are ASCII, + // so the buffer remains UTF-8 when we replace one with the other. + *input_byte = matched_byte; + } + i += 1; + }?; + Some((trie_value, string)) + } + + /// Performs a reverse lookup by walking the trie with an optional start position. + /// This is not a fast operation since it requires a linear search. + fn iana_search( + &self, + needle: IanaTrieValue, + mut string: String, + mut stack: Vec<(ZeroAsciiIgnoreCaseTrieCursor, usize, usize)>, + ) -> Option { + loop { + let Some((mut cursor, index, suffix_len)) = stack.pop() else { + // Nothing left in the trie. + return None; + }; + // Check to see if there is a value at the current node. + if let Some(candidate) = cursor.take_value().map(IanaTrieValue) { + if candidate == needle { + // Success! Found what we were looking for. + return Some(string); + } + } + // Now check for children of the current node. + let mut sub_cursor = cursor.clone(); + if let Some(probe_result) = sub_cursor.probe(index) { + // Found a child. Add the current byte edge to the string. + if !probe_result.byte.is_ascii() { + debug_assert!(false, "non-ASCII probe byte: {}", probe_result.byte); + return None; + } + // Safety: the byte being added is ASCII as guarded above + unsafe { string.as_mut_vec().push(probe_result.byte) }; + // Add the child to the stack, and also add back the current + // node if there are more siblings to visit. + if index + 1 < probe_result.total_siblings as usize { + stack.push((cursor, index + 1, suffix_len)); + stack.push((sub_cursor, 0, 1)); + } else { + stack.push((sub_cursor, 0, suffix_len + 1)); + } + } else { + // No more children. Pop this node's bytes from the string. + for _ in 0..suffix_len { + // Safety: we check that the bytes being removed are ASCII + let removed_byte = unsafe { string.as_mut_vec().pop() }; + if let Some(removed_byte) = removed_byte { + if !removed_byte.is_ascii() { + debug_assert!(false, "non-ASCII removed byte: {removed_byte}"); + // If we get here for some reason, `string` is not in a valid state, + // so to be extra safe, we can clear it. + string.clear(); + return None; + } + } else { + debug_assert!(false, "could not remove another byte"); + return None; + } + } + } + } + } +} + +/// A mapper that supplements [`TimeZoneIdMapper`] with about 8 KB of additional data to +/// improve the performance of canonical IANA ID lookup. +/// +/// The data in [`TimeZoneIdMapper`] is optimized for IANA to BCP-47 lookup; the reverse +/// requires a linear walk over all ~600 IANA identifiers. The data added here allows for +/// constant-time mapping from BCP-47 to IANA. +#[derive(Debug, Clone)] +pub struct TimeZoneIdMapperWithFastCanonicalization { + inner: I, + data: DataPayload, +} + +#[cfg(feature = "compiled_data")] +impl Default for TimeZoneIdMapperWithFastCanonicalization { + fn default() -> Self { + Self::new() + } +} + +impl TimeZoneIdMapperWithFastCanonicalization { + /// Creates a new [`TimeZoneIdMapperWithFastCanonicalization`] using compiled data. + /// + /// See [`TimeZoneIdMapperWithFastCanonicalization`] for an example. + /// + /// ✨ *Enabled with the `compiled_data` Cargo feature.* + /// + /// [📚 Help choosing a constructor](icu_provider::constructors) + #[cfg(feature = "compiled_data")] + pub fn new() -> Self { + const _: () = assert!( + crate::provider::Baked::SINGLETON_TIME_ZONE_IANA_TO_BCP47_V2.bcp47_ids_checksum + == crate::provider::Baked::SINGLETON_TIME_ZONE_BCP47_TO_IANA_V1.bcp47_ids_checksum, + ); + Self { + inner: TimeZoneIdMapper { + data: DataPayload::from_static_ref( + crate::provider::Baked::SINGLETON_TIME_ZONE_IANA_TO_BCP47_V2, + ), + }, + data: DataPayload::from_static_ref( + crate::provider::Baked::SINGLETON_TIME_ZONE_BCP47_TO_IANA_V1, + ), + } + } + + icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: TimeZoneError, + #[cfg(skip)] + functions: [ + new, + try_new_with_any_provider, + try_new_with_buffer_provider, + try_new_unstable, + Self, + ] + ); + + #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)] + pub fn try_new_unstable

(provider: &P) -> Result + where + P: DataProvider + DataProvider + ?Sized, + { + let mapper = TimeZoneIdMapper::try_new_unstable(provider)?; + Self::try_new_with_mapper_unstable(provider, mapper) + } +} + +impl TimeZoneIdMapperWithFastCanonicalization +where + I: AsRef, +{ + /// Creates a new [`TimeZoneIdMapperWithFastCanonicalization`] using compiled data + /// and a pre-existing [`TimeZoneIdMapper`], which can be borrowed. + /// + /// See [`TimeZoneIdMapperWithFastCanonicalization`] for an example. + /// + /// ✨ *Enabled with the `compiled_data` Cargo feature.* + /// + /// [📚 Help choosing a constructor](icu_provider::constructors) + #[cfg(feature = "compiled_data")] + pub fn try_new_with_mapper(mapper: I) -> Result { + Self { + inner: mapper, + data: DataPayload::from_static_ref( + crate::provider::Baked::SINGLETON_TIME_ZONE_BCP47_TO_IANA_V1, + ), + } + .validated() + } + + icu_provider::gen_any_buffer_data_constructors!(locale: skip, mapper: I, error: TimeZoneError, + #[cfg(skip)] + functions: [ + try_new_with_mapper, + try_new_with_mapper_with_any_provider, + try_new_with_mapper_with_buffer_provider, + try_new_with_mapper_unstable, + Self, + ] + ); + + #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)] + pub fn try_new_with_mapper_unstable

(provider: &P, mapper: I) -> Result + where + P: DataProvider + DataProvider + ?Sized, + { + let data = provider.load(Default::default())?.take_payload()?; + Self { + inner: mapper, + data, + } + .validated() + } + + fn validated(self) -> Result { + if self.inner.as_ref().data.get().bcp47_ids_checksum != self.data.get().bcp47_ids_checksum { + return Err(TimeZoneError::MismatchedChecksums); + } + Ok(self) + } + + /// Gets the inner [`TimeZoneIdMapper`] for performing queries. + pub fn inner(&self) -> &TimeZoneIdMapper { + self.inner.as_ref() + } + + /// Returns a borrowed version of the mapper that can be queried. + /// + /// This avoids a small potential indirection cost when querying the mapper. + pub fn as_borrowed(&self) -> TimeZoneIdMapperWithFastCanonicalizationBorrowed { + TimeZoneIdMapperWithFastCanonicalizationBorrowed { + inner: self.inner.as_ref().as_borrowed(), + data: self.data.get(), + } + } +} + +/// A borrowed wrapper around the time zone ID mapper, returned by +/// [`TimeZoneIdMapperWithFastCanonicalization::as_borrowed()`]. More efficient to query. +#[derive(Debug, Copy, Clone)] +pub struct TimeZoneIdMapperWithFastCanonicalizationBorrowed<'a> { + inner: TimeZoneIdMapperBorrowed<'a>, + data: &'a Bcp47ToIanaMapV1<'a>, +} + +impl<'a> TimeZoneIdMapperWithFastCanonicalizationBorrowed<'a> { + /// Gets the inner [`TimeZoneIdMapperBorrowed`] for performing queries. + pub fn inner(&self) -> TimeZoneIdMapperBorrowed<'a> { + self.inner + } + + /// Returns the canonical, normalized identifier of the given IANA time zone. + /// + /// Also returns the BCP-47 time zone ID. + /// + /// This is a faster version of [`TimeZoneIdMapperBorrowed::canonicalize_iana()`] + /// and it always returns borrowed IANA strings, but it requires loading additional data + /// (see [`TimeZoneIdMapperWithFastCanonicalization`]). + /// + /// Returns `None` if the IANA ID is not found. + /// + /// # Examples + /// + /// ``` + /// use icu_timezone::TimeZoneBcp47Id; + /// use icu_timezone::TimeZoneIdMapperWithFastCanonicalization; + /// use std::borrow::Cow; + /// + /// let mapper = TimeZoneIdMapperWithFastCanonicalization::new(); + /// let mapper = mapper.as_borrowed(); + /// + /// let result = mapper.canonicalize_iana("Asia/CALCUTTA").unwrap(); + /// + /// // The Cow is always returned borrowed: + /// assert_eq!(result.0, "Asia/Kolkata"); + /// assert_eq!(*result.1, "inccu"); + /// + /// // Unknown IANA time zone ID: + /// assert_eq!(mapper.canonicalize_iana("America/San_Francisco"), None); + /// ``` + pub fn canonicalize_iana(&self, iana_id: &str) -> Option<(&str, TimeZoneBcp47Id)> { + let trie_value = self.inner.iana_lookup_quick(iana_id)?; + let Some(bcp47_id) = self.inner.data.bcp47_ids.get(trie_value.index()) else { + debug_assert!(false, "index should be in range"); + return None; + }; + let Some(string) = self.data.canonical_iana_ids.get(trie_value.index()) else { + debug_assert!(false, "index should be in range"); + return None; + }; + Some((string, bcp47_id)) + } + + /// Returns the canonical, normalized IANA ID of the given BCP-47 ID. + /// + /// This is a faster version of [`TimeZoneIdMapperBorrowed::find_canonical_iana_from_bcp47()`] + /// and it always returns borrowed IANA strings, but it requires loading additional data + /// (see [`TimeZoneIdMapperWithFastCanonicalization`]). + /// + /// Returns `None` if the BCP-47 ID is not found. + /// + /// # Examples + /// + /// ``` + /// use icu_timezone::TimeZoneBcp47Id; + /// use icu_timezone::TimeZoneIdMapperWithFastCanonicalization; + /// use std::borrow::Cow; + /// use tinystr::tinystr; + /// + /// let mapper = TimeZoneIdMapperWithFastCanonicalization::new(); + /// let mapper = mapper.as_borrowed(); + /// + /// let bcp47_id = TimeZoneBcp47Id(tinystr!(8, "inccu")); + /// let result = mapper.canonical_iana_from_bcp47(bcp47_id).unwrap(); + /// + /// // The Cow is always returned borrowed: + /// assert_eq!(result, "Asia/Kolkata"); + /// + /// // Unknown BCP-47 time zone ID: + /// let bcp47_id = TimeZoneBcp47Id(tinystr!(8, "ussfo")); + /// assert_eq!(mapper.canonical_iana_from_bcp47(bcp47_id), None); + /// ``` + pub fn canonical_iana_from_bcp47(&self, bcp47_id: TimeZoneBcp47Id) -> Option<&str> { + let index = self.inner.data.bcp47_ids.binary_search(&bcp47_id).ok()?; + let Some(string) = self.data.canonical_iana_ids.get(index) else { + debug_assert!(false, "index should be in range"); + return None; + }; + Some(string) + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +struct IanaTrieValue(usize); + +impl IanaTrieValue { + #[inline] + pub(crate) fn to_canonical(self) -> Self { + Self(self.0 | 1) + } + #[inline] + pub(crate) fn canonical_for_index(index: usize) -> Self { + Self(index << 1).to_canonical() + } + #[inline] + pub(crate) fn index(self) -> usize { + self.0 >> 1 + } + #[inline] + pub(crate) fn is_canonical(self) -> bool { + (self.0 & 0x1) != 0 + } +} diff --git a/components/timezone/src/lib.rs b/components/timezone/src/lib.rs index adfa01b4d8d..d635c4cb882 100644 --- a/components/timezone/src/lib.rs +++ b/components/timezone/src/lib.rs @@ -31,7 +31,7 @@ //! 2. BCP-47 time zone IDs, like `"uschi"` //! //! 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`]. +//! IANA time zone, use [`TimeZoneIdMapper`]. //! //! ## Metazone //! @@ -87,15 +87,15 @@ //! use icu::calendar::DateTime; //! use icu::timezone::CustomTimeZone; //! use icu::timezone::GmtOffset; -//! use icu::timezone::IanaToBcp47Mapper; +//! use icu::timezone::TimeZoneIdMapper; //! use icu::timezone::MetazoneCalculator; //! use tinystr::{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::().ok(); -//! let mapper = IanaToBcp47Mapper::new(); -//! time_zone.time_zone_id = mapper.as_borrowed().get("America/Chicago"); +//! let mapper = TimeZoneIdMapper::new(); +//! time_zone.time_zone_id = mapper.as_borrowed().iana_to_bcp47("America/Chicago"); //! //! // Alternatively, set it directly from the BCP-47 ID //! assert_eq!(time_zone.time_zone_id, Some(tinystr!(8, "uschi").into())); @@ -128,16 +128,22 @@ extern crate alloc; mod error; mod iana_ids; +mod ids; mod metazone; pub mod provider; mod time_zone; mod types; pub use error::TimeZoneError; +#[allow(deprecated)] pub use iana_ids::{ IanaBcp47RoundTripMapper, IanaBcp47RoundTripMapperBorrowed, IanaToBcp47Mapper, IanaToBcp47MapperBorrowed, }; +pub use ids::{ + TimeZoneIdMapper, TimeZoneIdMapperBorrowed, TimeZoneIdMapperWithFastCanonicalization, + TimeZoneIdMapperWithFastCanonicalizationBorrowed, +}; pub use metazone::MetazoneCalculator; pub use provider::{MetazoneId, TimeZoneBcp47Id}; pub use time_zone::CustomTimeZone; diff --git a/components/timezone/src/provider.rs b/components/timezone/src/provider.rs index 5c2fb70991c..259510e3ff9 100644 --- a/components/timezone/src/provider.rs +++ b/components/timezone/src/provider.rs @@ -15,6 +15,7 @@ //! //! Read more about data providers: [`icu_provider`] +use core::ops::Deref; use core::str::FromStr; use icu_provider::prelude::*; use tinystr::TinyAsciiStr; @@ -42,6 +43,7 @@ const _: () = { icu_timezone_data::make_provider!(Baked); icu_timezone_data::impl_time_zone_bcp47_to_iana_v1!(Baked); icu_timezone_data::impl_time_zone_iana_to_bcp47_v1!(Baked); + icu_timezone_data::impl_time_zone_iana_to_bcp47_v2!(Baked); icu_timezone_data::impl_time_zone_metazone_period_v1!(Baked); }; @@ -51,6 +53,7 @@ pub const KEYS: &[DataKey] = &[ MetazonePeriodV1Marker::KEY, names::Bcp47ToIanaMapV1Marker::KEY, names::IanaToBcp47MapV1Marker::KEY, + names::IanaToBcp47MapV2Marker::KEY, ]; /// TimeZone ID in BCP47 format @@ -85,6 +88,14 @@ impl From for TinyAsciiStr<8> { } } +impl Deref for TimeZoneBcp47Id { + type Target = TinyAsciiStr<8>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl AsULE for TimeZoneBcp47Id { type ULE = Self; diff --git a/ffi/capi/bindings/c/ICU4XCustomTimeZone.h b/ffi/capi/bindings/c/ICU4XCustomTimeZone.h index 422b7fa3827..c36fbd7d90b 100644 --- a/ffi/capi/bindings/c/ICU4XCustomTimeZone.h +++ b/ffi/capi/bindings/c/ICU4XCustomTimeZone.h @@ -19,6 +19,7 @@ typedef struct ICU4XCustomTimeZone ICU4XCustomTimeZone; #include "diplomat_result_int32_t_ICU4XError.h" #include "diplomat_result_bool_ICU4XError.h" #include "ICU4XIanaToBcp47Mapper.h" +#include "ICU4XTimeZoneIdMapper.h" #include "ICU4XMetazoneCalculator.h" #include "ICU4XIsoDateTime.h" #ifdef __cplusplus @@ -50,6 +51,8 @@ diplomat_result_void_ICU4XError ICU4XCustomTimeZone_try_set_time_zone_id(ICU4XCu diplomat_result_void_ICU4XError ICU4XCustomTimeZone_try_set_iana_time_zone_id(ICU4XCustomTimeZone* self, const ICU4XIanaToBcp47Mapper* mapper, const char* id_data, size_t id_len); +diplomat_result_void_ICU4XError ICU4XCustomTimeZone_try_set_iana_time_zone_id_2(ICU4XCustomTimeZone* self, const ICU4XTimeZoneIdMapper* mapper, const char* id_data, size_t id_len); + void ICU4XCustomTimeZone_clear_time_zone_id(ICU4XCustomTimeZone* self); diplomat_result_void_ICU4XError ICU4XCustomTimeZone_time_zone_id(const ICU4XCustomTimeZone* self, DiplomatWriteable* write); diff --git a/ffi/capi/bindings/c/ICU4XError.h b/ffi/capi/bindings/c/ICU4XError.h index 18320010780..9b6df296cca 100644 --- a/ffi/capi/bindings/c/ICU4XError.h +++ b/ffi/capi/bindings/c/ICU4XError.h @@ -14,6 +14,7 @@ typedef enum ICU4XError { ICU4XError_UnknownError = 0, ICU4XError_WriteableError = 1, ICU4XError_OutOfBoundsError = 2, + ICU4XError_Utf8Error = 3, ICU4XError_DataMissingDataKeyError = 256, ICU4XError_DataMissingVariantError = 257, ICU4XError_DataMissingLocaleError = 258, diff --git a/ffi/capi/bindings/c/ICU4XTimeZoneIdMapper.h b/ffi/capi/bindings/c/ICU4XTimeZoneIdMapper.h new file mode 100644 index 00000000000..134f5b6a9b8 --- /dev/null +++ b/ffi/capi/bindings/c/ICU4XTimeZoneIdMapper.h @@ -0,0 +1,40 @@ +#ifndef ICU4XTimeZoneIdMapper_H +#define ICU4XTimeZoneIdMapper_H +#include +#include +#include +#include +#include "diplomat_runtime.h" + +#ifdef __cplusplus +namespace capi { +#endif + +typedef struct ICU4XTimeZoneIdMapper ICU4XTimeZoneIdMapper; +#ifdef __cplusplus +} // namespace capi +#endif +#include "ICU4XDataProvider.h" +#include "diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError.h" +#include "diplomat_result_void_ICU4XError.h" +#ifdef __cplusplus +namespace capi { +extern "C" { +#endif + +diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError ICU4XTimeZoneIdMapper_create(const ICU4XDataProvider* provider); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapper_iana_to_bcp47(const ICU4XTimeZoneIdMapper* self, const char* value_data, size_t value_len, DiplomatWriteable* write); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapper_normalize_iana(const ICU4XTimeZoneIdMapper* self, const char* value_data, size_t value_len, DiplomatWriteable* write); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapper_canonicalize_iana(const ICU4XTimeZoneIdMapper* self, const char* value_data, size_t value_len, DiplomatWriteable* write); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapper_find_canonical_iana_from_bcp47(const ICU4XTimeZoneIdMapper* self, const char* value_data, size_t value_len, DiplomatWriteable* write); +void ICU4XTimeZoneIdMapper_destroy(ICU4XTimeZoneIdMapper* self); + +#ifdef __cplusplus +} // extern "C" +} // namespace capi +#endif +#endif diff --git a/ffi/capi/bindings/c/ICU4XTimeZoneIdMapperWithFastCanonicalization.h b/ffi/capi/bindings/c/ICU4XTimeZoneIdMapperWithFastCanonicalization.h new file mode 100644 index 00000000000..c8091d4b645 --- /dev/null +++ b/ffi/capi/bindings/c/ICU4XTimeZoneIdMapperWithFastCanonicalization.h @@ -0,0 +1,36 @@ +#ifndef ICU4XTimeZoneIdMapperWithFastCanonicalization_H +#define ICU4XTimeZoneIdMapperWithFastCanonicalization_H +#include +#include +#include +#include +#include "diplomat_runtime.h" + +#ifdef __cplusplus +namespace capi { +#endif + +typedef struct ICU4XTimeZoneIdMapperWithFastCanonicalization ICU4XTimeZoneIdMapperWithFastCanonicalization; +#ifdef __cplusplus +} // namespace capi +#endif +#include "ICU4XDataProvider.h" +#include "diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError.h" +#include "diplomat_result_void_ICU4XError.h" +#ifdef __cplusplus +namespace capi { +extern "C" { +#endif + +diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError ICU4XTimeZoneIdMapperWithFastCanonicalization_create(const ICU4XDataProvider* provider); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapperWithFastCanonicalization_canonicalize_iana(const ICU4XTimeZoneIdMapperWithFastCanonicalization* self, const char* value_data, size_t value_len, DiplomatWriteable* write); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapperWithFastCanonicalization_canonical_iana_from_bcp47(const ICU4XTimeZoneIdMapperWithFastCanonicalization* self, const char* value_data, size_t value_len, DiplomatWriteable* write); +void ICU4XTimeZoneIdMapperWithFastCanonicalization_destroy(ICU4XTimeZoneIdMapperWithFastCanonicalization* self); + +#ifdef __cplusplus +} // extern "C" +} // namespace capi +#endif +#endif diff --git a/ffi/capi/bindings/c/diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError.h b/ffi/capi/bindings/c/diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError.h new file mode 100644 index 00000000000..181c9cceac1 --- /dev/null +++ b/ffi/capi/bindings/c/diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError.h @@ -0,0 +1,26 @@ +#ifndef diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError_H +#define diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError_H +#include +#include +#include +#include +#include "diplomat_runtime.h" + +#include "ICU4XTimeZoneIdMapperWithFastCanonicalization.h" +#include "ICU4XError.h" +#ifdef __cplusplus +namespace capi { +extern "C" { +#endif +typedef struct diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError { + union { + ICU4XTimeZoneIdMapperWithFastCanonicalization* ok; + ICU4XError err; + }; + bool is_ok; +} diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError; +#ifdef __cplusplus +} // extern "C" +} // namespace capi +#endif +#endif diff --git a/ffi/capi/bindings/c/diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError.h b/ffi/capi/bindings/c/diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError.h new file mode 100644 index 00000000000..79641973792 --- /dev/null +++ b/ffi/capi/bindings/c/diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError.h @@ -0,0 +1,26 @@ +#ifndef diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError_H +#define diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError_H +#include +#include +#include +#include +#include "diplomat_runtime.h" + +#include "ICU4XTimeZoneIdMapper.h" +#include "ICU4XError.h" +#ifdef __cplusplus +namespace capi { +extern "C" { +#endif +typedef struct diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError { + union { + ICU4XTimeZoneIdMapper* ok; + ICU4XError err; + }; + bool is_ok; +} diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError; +#ifdef __cplusplus +} // extern "C" +} // namespace capi +#endif +#endif diff --git a/ffi/capi/bindings/cpp/ICU4XCustomTimeZone.h b/ffi/capi/bindings/cpp/ICU4XCustomTimeZone.h index 422b7fa3827..c36fbd7d90b 100644 --- a/ffi/capi/bindings/cpp/ICU4XCustomTimeZone.h +++ b/ffi/capi/bindings/cpp/ICU4XCustomTimeZone.h @@ -19,6 +19,7 @@ typedef struct ICU4XCustomTimeZone ICU4XCustomTimeZone; #include "diplomat_result_int32_t_ICU4XError.h" #include "diplomat_result_bool_ICU4XError.h" #include "ICU4XIanaToBcp47Mapper.h" +#include "ICU4XTimeZoneIdMapper.h" #include "ICU4XMetazoneCalculator.h" #include "ICU4XIsoDateTime.h" #ifdef __cplusplus @@ -50,6 +51,8 @@ diplomat_result_void_ICU4XError ICU4XCustomTimeZone_try_set_time_zone_id(ICU4XCu diplomat_result_void_ICU4XError ICU4XCustomTimeZone_try_set_iana_time_zone_id(ICU4XCustomTimeZone* self, const ICU4XIanaToBcp47Mapper* mapper, const char* id_data, size_t id_len); +diplomat_result_void_ICU4XError ICU4XCustomTimeZone_try_set_iana_time_zone_id_2(ICU4XCustomTimeZone* self, const ICU4XTimeZoneIdMapper* mapper, const char* id_data, size_t id_len); + void ICU4XCustomTimeZone_clear_time_zone_id(ICU4XCustomTimeZone* self); diplomat_result_void_ICU4XError ICU4XCustomTimeZone_time_zone_id(const ICU4XCustomTimeZone* self, DiplomatWriteable* write); diff --git a/ffi/capi/bindings/cpp/ICU4XCustomTimeZone.hpp b/ffi/capi/bindings/cpp/ICU4XCustomTimeZone.hpp index f2491dcfaf7..30a6ea16733 100644 --- a/ffi/capi/bindings/cpp/ICU4XCustomTimeZone.hpp +++ b/ffi/capi/bindings/cpp/ICU4XCustomTimeZone.hpp @@ -14,6 +14,7 @@ class ICU4XCustomTimeZone; #include "ICU4XError.hpp" class ICU4XIanaToBcp47Mapper; +class ICU4XTimeZoneIdMapper; class ICU4XMetazoneCalculator; class ICU4XIsoDateTime; @@ -141,6 +142,14 @@ class ICU4XCustomTimeZone { */ diplomat::result try_set_iana_time_zone_id(const ICU4XIanaToBcp47Mapper& mapper, const std::string_view id); + /** + * Sets the `time_zone_id` field from an IANA string by looking up + * the corresponding BCP-47 string. + * + * Errors if the string is not a valid BCP-47 time zone ID. + */ + diplomat::result try_set_iana_time_zone_id_2(const ICU4XTimeZoneIdMapper& mapper, const std::string_view id); + /** * Clears the `time_zone_id` field. * @@ -317,6 +326,7 @@ class ICU4XCustomTimeZone { }; #include "ICU4XIanaToBcp47Mapper.hpp" +#include "ICU4XTimeZoneIdMapper.hpp" #include "ICU4XMetazoneCalculator.hpp" #include "ICU4XIsoDateTime.hpp" @@ -419,6 +429,16 @@ inline diplomat::result ICU4XCustomTimeZone::try_set } return diplomat_result_out_value; } +inline diplomat::result ICU4XCustomTimeZone::try_set_iana_time_zone_id_2(const ICU4XTimeZoneIdMapper& mapper, const std::string_view id) { + auto diplomat_result_raw_out_value = capi::ICU4XCustomTimeZone_try_set_iana_time_zone_id_2(this->inner.get(), mapper.AsFFI(), id.data(), id.size()); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value; +} inline void ICU4XCustomTimeZone::clear_time_zone_id() { capi::ICU4XCustomTimeZone_clear_time_zone_id(this->inner.get()); } diff --git a/ffi/capi/bindings/cpp/ICU4XError.h b/ffi/capi/bindings/cpp/ICU4XError.h index 18320010780..9b6df296cca 100644 --- a/ffi/capi/bindings/cpp/ICU4XError.h +++ b/ffi/capi/bindings/cpp/ICU4XError.h @@ -14,6 +14,7 @@ typedef enum ICU4XError { ICU4XError_UnknownError = 0, ICU4XError_WriteableError = 1, ICU4XError_OutOfBoundsError = 2, + ICU4XError_Utf8Error = 3, ICU4XError_DataMissingDataKeyError = 256, ICU4XError_DataMissingVariantError = 257, ICU4XError_DataMissingLocaleError = 258, diff --git a/ffi/capi/bindings/cpp/ICU4XError.hpp b/ffi/capi/bindings/cpp/ICU4XError.hpp index dd4bc896f57..83f1b1235d8 100644 --- a/ffi/capi/bindings/cpp/ICU4XError.hpp +++ b/ffi/capi/bindings/cpp/ICU4XError.hpp @@ -34,7 +34,16 @@ enum struct ICU4XError { * Most APIs that return a string may return this error */ WriteableError = 1, + + /** + * Some input was out of bounds + */ OutOfBoundsError = 2, + + /** + * Input expected to be UTF-8 was ill-formed + */ + Utf8Error = 3, DataMissingDataKeyError = 256, DataMissingVariantError = 257, DataMissingLocaleError = 258, diff --git a/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapper.h b/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapper.h new file mode 100644 index 00000000000..134f5b6a9b8 --- /dev/null +++ b/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapper.h @@ -0,0 +1,40 @@ +#ifndef ICU4XTimeZoneIdMapper_H +#define ICU4XTimeZoneIdMapper_H +#include +#include +#include +#include +#include "diplomat_runtime.h" + +#ifdef __cplusplus +namespace capi { +#endif + +typedef struct ICU4XTimeZoneIdMapper ICU4XTimeZoneIdMapper; +#ifdef __cplusplus +} // namespace capi +#endif +#include "ICU4XDataProvider.h" +#include "diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError.h" +#include "diplomat_result_void_ICU4XError.h" +#ifdef __cplusplus +namespace capi { +extern "C" { +#endif + +diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError ICU4XTimeZoneIdMapper_create(const ICU4XDataProvider* provider); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapper_iana_to_bcp47(const ICU4XTimeZoneIdMapper* self, const char* value_data, size_t value_len, DiplomatWriteable* write); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapper_normalize_iana(const ICU4XTimeZoneIdMapper* self, const char* value_data, size_t value_len, DiplomatWriteable* write); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapper_canonicalize_iana(const ICU4XTimeZoneIdMapper* self, const char* value_data, size_t value_len, DiplomatWriteable* write); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapper_find_canonical_iana_from_bcp47(const ICU4XTimeZoneIdMapper* self, const char* value_data, size_t value_len, DiplomatWriteable* write); +void ICU4XTimeZoneIdMapper_destroy(ICU4XTimeZoneIdMapper* self); + +#ifdef __cplusplus +} // extern "C" +} // namespace capi +#endif +#endif diff --git a/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapper.hpp b/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapper.hpp new file mode 100644 index 00000000000..7a43b61340f --- /dev/null +++ b/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapper.hpp @@ -0,0 +1,196 @@ +#ifndef ICU4XTimeZoneIdMapper_HPP +#define ICU4XTimeZoneIdMapper_HPP +#include +#include +#include +#include +#include +#include +#include +#include "diplomat_runtime.hpp" + +#include "ICU4XTimeZoneIdMapper.h" + +class ICU4XDataProvider; +class ICU4XTimeZoneIdMapper; +#include "ICU4XError.hpp" + +/** + * A destruction policy for using ICU4XTimeZoneIdMapper with std::unique_ptr. + */ +struct ICU4XTimeZoneIdMapperDeleter { + void operator()(capi::ICU4XTimeZoneIdMapper* l) const noexcept { + capi::ICU4XTimeZoneIdMapper_destroy(l); + } +}; + +/** + * A mapper between IANA time zone identifiers and BCP-47 time zone identifiers. + * + * This mapper supports two-way mapping, but it is optimized for the case of IANA to BCP-47. + * It also supports normalizing and canonicalizing the IANA strings. + * + * See the [Rust documentation for `TimeZoneIdMapper`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapper.html) for more information. + */ +class ICU4XTimeZoneIdMapper { + public: + + /** + * See the [Rust documentation for `new`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapper.html#method.new) for more information. + */ + static diplomat::result create(const ICU4XDataProvider& provider); + + /** + * See the [Rust documentation for `iana_to_bcp47`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.iana_to_bcp47) for more information. + */ + template diplomat::result iana_to_bcp47_to_writeable(const std::string_view value, W& write) const; + + /** + * See the [Rust documentation for `iana_to_bcp47`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.iana_to_bcp47) for more information. + */ + diplomat::result iana_to_bcp47(const std::string_view value) const; + + /** + * See the [Rust documentation for `normalize_iana`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.normalize_iana) for more information. + */ + template diplomat::result normalize_iana_to_writeable(const std::string_view value, W& write) const; + + /** + * See the [Rust documentation for `normalize_iana`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.normalize_iana) for more information. + */ + diplomat::result normalize_iana(const std::string_view value) const; + + /** + * See the [Rust documentation for `canonicalize_iana`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.canonicalize_iana) for more information. + */ + template diplomat::result canonicalize_iana_to_writeable(const std::string_view value, W& write) const; + + /** + * See the [Rust documentation for `canonicalize_iana`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.canonicalize_iana) for more information. + */ + diplomat::result canonicalize_iana(const std::string_view value) const; + + /** + * See the [Rust documentation for `find_canonical_iana_from_bcp47`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.find_canonical_iana_from_bcp47) for more information. + */ + template diplomat::result find_canonical_iana_from_bcp47_to_writeable(const std::string_view value, W& write) const; + + /** + * See the [Rust documentation for `find_canonical_iana_from_bcp47`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.find_canonical_iana_from_bcp47) for more information. + */ + diplomat::result find_canonical_iana_from_bcp47(const std::string_view value) const; + inline const capi::ICU4XTimeZoneIdMapper* AsFFI() const { return this->inner.get(); } + inline capi::ICU4XTimeZoneIdMapper* AsFFIMut() { return this->inner.get(); } + inline explicit ICU4XTimeZoneIdMapper(capi::ICU4XTimeZoneIdMapper* i) : inner(i) {} + ICU4XTimeZoneIdMapper() = default; + ICU4XTimeZoneIdMapper(ICU4XTimeZoneIdMapper&&) noexcept = default; + ICU4XTimeZoneIdMapper& operator=(ICU4XTimeZoneIdMapper&& other) noexcept = default; + private: + std::unique_ptr inner; +}; + +#include "ICU4XDataProvider.hpp" + +inline diplomat::result ICU4XTimeZoneIdMapper::create(const ICU4XDataProvider& provider) { + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapper_create(provider.AsFFI()); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(ICU4XTimeZoneIdMapper(diplomat_result_raw_out_value.ok)); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value; +} +template inline diplomat::result ICU4XTimeZoneIdMapper::iana_to_bcp47_to_writeable(const std::string_view value, W& write) const { + capi::DiplomatWriteable write_writer = diplomat::WriteableTrait::Construct(write); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapper_iana_to_bcp47(this->inner.get(), value.data(), value.size(), &write_writer); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value; +} +inline diplomat::result ICU4XTimeZoneIdMapper::iana_to_bcp47(const std::string_view value) const { + std::string diplomat_writeable_string; + capi::DiplomatWriteable diplomat_writeable_out = diplomat::WriteableFromString(diplomat_writeable_string); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapper_iana_to_bcp47(this->inner.get(), value.data(), value.size(), &diplomat_writeable_out); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value.replace_ok(std::move(diplomat_writeable_string)); +} +template inline diplomat::result ICU4XTimeZoneIdMapper::normalize_iana_to_writeable(const std::string_view value, W& write) const { + capi::DiplomatWriteable write_writer = diplomat::WriteableTrait::Construct(write); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapper_normalize_iana(this->inner.get(), value.data(), value.size(), &write_writer); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value; +} +inline diplomat::result ICU4XTimeZoneIdMapper::normalize_iana(const std::string_view value) const { + std::string diplomat_writeable_string; + capi::DiplomatWriteable diplomat_writeable_out = diplomat::WriteableFromString(diplomat_writeable_string); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapper_normalize_iana(this->inner.get(), value.data(), value.size(), &diplomat_writeable_out); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value.replace_ok(std::move(diplomat_writeable_string)); +} +template inline diplomat::result ICU4XTimeZoneIdMapper::canonicalize_iana_to_writeable(const std::string_view value, W& write) const { + capi::DiplomatWriteable write_writer = diplomat::WriteableTrait::Construct(write); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapper_canonicalize_iana(this->inner.get(), value.data(), value.size(), &write_writer); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value; +} +inline diplomat::result ICU4XTimeZoneIdMapper::canonicalize_iana(const std::string_view value) const { + std::string diplomat_writeable_string; + capi::DiplomatWriteable diplomat_writeable_out = diplomat::WriteableFromString(diplomat_writeable_string); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapper_canonicalize_iana(this->inner.get(), value.data(), value.size(), &diplomat_writeable_out); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value.replace_ok(std::move(diplomat_writeable_string)); +} +template inline diplomat::result ICU4XTimeZoneIdMapper::find_canonical_iana_from_bcp47_to_writeable(const std::string_view value, W& write) const { + capi::DiplomatWriteable write_writer = diplomat::WriteableTrait::Construct(write); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapper_find_canonical_iana_from_bcp47(this->inner.get(), value.data(), value.size(), &write_writer); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value; +} +inline diplomat::result ICU4XTimeZoneIdMapper::find_canonical_iana_from_bcp47(const std::string_view value) const { + std::string diplomat_writeable_string; + capi::DiplomatWriteable diplomat_writeable_out = diplomat::WriteableFromString(diplomat_writeable_string); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapper_find_canonical_iana_from_bcp47(this->inner.get(), value.data(), value.size(), &diplomat_writeable_out); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value.replace_ok(std::move(diplomat_writeable_string)); +} +#endif diff --git a/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapperWithFastCanonicalization.h b/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapperWithFastCanonicalization.h new file mode 100644 index 00000000000..c8091d4b645 --- /dev/null +++ b/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapperWithFastCanonicalization.h @@ -0,0 +1,36 @@ +#ifndef ICU4XTimeZoneIdMapperWithFastCanonicalization_H +#define ICU4XTimeZoneIdMapperWithFastCanonicalization_H +#include +#include +#include +#include +#include "diplomat_runtime.h" + +#ifdef __cplusplus +namespace capi { +#endif + +typedef struct ICU4XTimeZoneIdMapperWithFastCanonicalization ICU4XTimeZoneIdMapperWithFastCanonicalization; +#ifdef __cplusplus +} // namespace capi +#endif +#include "ICU4XDataProvider.h" +#include "diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError.h" +#include "diplomat_result_void_ICU4XError.h" +#ifdef __cplusplus +namespace capi { +extern "C" { +#endif + +diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError ICU4XTimeZoneIdMapperWithFastCanonicalization_create(const ICU4XDataProvider* provider); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapperWithFastCanonicalization_canonicalize_iana(const ICU4XTimeZoneIdMapperWithFastCanonicalization* self, const char* value_data, size_t value_len, DiplomatWriteable* write); + +diplomat_result_void_ICU4XError ICU4XTimeZoneIdMapperWithFastCanonicalization_canonical_iana_from_bcp47(const ICU4XTimeZoneIdMapperWithFastCanonicalization* self, const char* value_data, size_t value_len, DiplomatWriteable* write); +void ICU4XTimeZoneIdMapperWithFastCanonicalization_destroy(ICU4XTimeZoneIdMapperWithFastCanonicalization* self); + +#ifdef __cplusplus +} // extern "C" +} // namespace capi +#endif +#endif diff --git a/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapperWithFastCanonicalization.hpp b/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapperWithFastCanonicalization.hpp new file mode 100644 index 00000000000..9c39e2bd782 --- /dev/null +++ b/ffi/capi/bindings/cpp/ICU4XTimeZoneIdMapperWithFastCanonicalization.hpp @@ -0,0 +1,130 @@ +#ifndef ICU4XTimeZoneIdMapperWithFastCanonicalization_HPP +#define ICU4XTimeZoneIdMapperWithFastCanonicalization_HPP +#include +#include +#include +#include +#include +#include +#include +#include "diplomat_runtime.hpp" + +#include "ICU4XTimeZoneIdMapperWithFastCanonicalization.h" + +class ICU4XDataProvider; +class ICU4XTimeZoneIdMapperWithFastCanonicalization; +#include "ICU4XError.hpp" + +/** + * A destruction policy for using ICU4XTimeZoneIdMapperWithFastCanonicalization with std::unique_ptr. + */ +struct ICU4XTimeZoneIdMapperWithFastCanonicalizationDeleter { + void operator()(capi::ICU4XTimeZoneIdMapperWithFastCanonicalization* l) const noexcept { + capi::ICU4XTimeZoneIdMapperWithFastCanonicalization_destroy(l); + } +}; + +/** + * A mapper between IANA time zone identifiers and BCP-47 time zone identifiers. + * + * This mapper supports two-way mapping, but it is optimized for the case of IANA to BCP-47. + * It also supports normalizing and canonicalizing the IANA strings. + * + * See the [Rust documentation for `TimeZoneIdMapperWithFastCanonicalization`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalization.html) for more information. + */ +class ICU4XTimeZoneIdMapperWithFastCanonicalization { + public: + + /** + * See the [Rust documentation for `new`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalization.html#method.new) for more information. + */ + static diplomat::result create(const ICU4XDataProvider& provider); + + /** + * See the [Rust documentation for `canonicalize_iana`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalizationBorrowed.html#method.canonicalize_iana) for more information. + */ + template diplomat::result canonicalize_iana_to_writeable(const std::string_view value, W& write) const; + + /** + * See the [Rust documentation for `canonicalize_iana`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalizationBorrowed.html#method.canonicalize_iana) for more information. + */ + diplomat::result canonicalize_iana(const std::string_view value) const; + + /** + * See the [Rust documentation for `canonical_iana_from_bcp47`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalizationBorrowed.html#method.canonical_iana_from_bcp47) for more information. + */ + template diplomat::result canonical_iana_from_bcp47_to_writeable(const std::string_view value, W& write) const; + + /** + * See the [Rust documentation for `canonical_iana_from_bcp47`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalizationBorrowed.html#method.canonical_iana_from_bcp47) for more information. + */ + diplomat::result canonical_iana_from_bcp47(const std::string_view value) const; + inline const capi::ICU4XTimeZoneIdMapperWithFastCanonicalization* AsFFI() const { return this->inner.get(); } + inline capi::ICU4XTimeZoneIdMapperWithFastCanonicalization* AsFFIMut() { return this->inner.get(); } + inline explicit ICU4XTimeZoneIdMapperWithFastCanonicalization(capi::ICU4XTimeZoneIdMapperWithFastCanonicalization* i) : inner(i) {} + ICU4XTimeZoneIdMapperWithFastCanonicalization() = default; + ICU4XTimeZoneIdMapperWithFastCanonicalization(ICU4XTimeZoneIdMapperWithFastCanonicalization&&) noexcept = default; + ICU4XTimeZoneIdMapperWithFastCanonicalization& operator=(ICU4XTimeZoneIdMapperWithFastCanonicalization&& other) noexcept = default; + private: + std::unique_ptr inner; +}; + +#include "ICU4XDataProvider.hpp" + +inline diplomat::result ICU4XTimeZoneIdMapperWithFastCanonicalization::create(const ICU4XDataProvider& provider) { + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapperWithFastCanonicalization_create(provider.AsFFI()); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(ICU4XTimeZoneIdMapperWithFastCanonicalization(diplomat_result_raw_out_value.ok)); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value; +} +template inline diplomat::result ICU4XTimeZoneIdMapperWithFastCanonicalization::canonicalize_iana_to_writeable(const std::string_view value, W& write) const { + capi::DiplomatWriteable write_writer = diplomat::WriteableTrait::Construct(write); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapperWithFastCanonicalization_canonicalize_iana(this->inner.get(), value.data(), value.size(), &write_writer); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value; +} +inline diplomat::result ICU4XTimeZoneIdMapperWithFastCanonicalization::canonicalize_iana(const std::string_view value) const { + std::string diplomat_writeable_string; + capi::DiplomatWriteable diplomat_writeable_out = diplomat::WriteableFromString(diplomat_writeable_string); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapperWithFastCanonicalization_canonicalize_iana(this->inner.get(), value.data(), value.size(), &diplomat_writeable_out); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value.replace_ok(std::move(diplomat_writeable_string)); +} +template inline diplomat::result ICU4XTimeZoneIdMapperWithFastCanonicalization::canonical_iana_from_bcp47_to_writeable(const std::string_view value, W& write) const { + capi::DiplomatWriteable write_writer = diplomat::WriteableTrait::Construct(write); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapperWithFastCanonicalization_canonical_iana_from_bcp47(this->inner.get(), value.data(), value.size(), &write_writer); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value; +} +inline diplomat::result ICU4XTimeZoneIdMapperWithFastCanonicalization::canonical_iana_from_bcp47(const std::string_view value) const { + std::string diplomat_writeable_string; + capi::DiplomatWriteable diplomat_writeable_out = diplomat::WriteableFromString(diplomat_writeable_string); + auto diplomat_result_raw_out_value = capi::ICU4XTimeZoneIdMapperWithFastCanonicalization_canonical_iana_from_bcp47(this->inner.get(), value.data(), value.size(), &diplomat_writeable_out); + diplomat::result diplomat_result_out_value; + if (diplomat_result_raw_out_value.is_ok) { + diplomat_result_out_value = diplomat::Ok(std::monostate()); + } else { + diplomat_result_out_value = diplomat::Err(static_cast(diplomat_result_raw_out_value.err)); + } + return diplomat_result_out_value.replace_ok(std::move(diplomat_writeable_string)); +} +#endif diff --git a/ffi/capi/bindings/cpp/diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError.h b/ffi/capi/bindings/cpp/diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError.h new file mode 100644 index 00000000000..181c9cceac1 --- /dev/null +++ b/ffi/capi/bindings/cpp/diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError.h @@ -0,0 +1,26 @@ +#ifndef diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError_H +#define diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError_H +#include +#include +#include +#include +#include "diplomat_runtime.h" + +#include "ICU4XTimeZoneIdMapperWithFastCanonicalization.h" +#include "ICU4XError.h" +#ifdef __cplusplus +namespace capi { +extern "C" { +#endif +typedef struct diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError { + union { + ICU4XTimeZoneIdMapperWithFastCanonicalization* ok; + ICU4XError err; + }; + bool is_ok; +} diplomat_result_box_ICU4XTimeZoneIdMapperWithFastCanonicalization_ICU4XError; +#ifdef __cplusplus +} // extern "C" +} // namespace capi +#endif +#endif diff --git a/ffi/capi/bindings/cpp/diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError.h b/ffi/capi/bindings/cpp/diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError.h new file mode 100644 index 00000000000..79641973792 --- /dev/null +++ b/ffi/capi/bindings/cpp/diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError.h @@ -0,0 +1,26 @@ +#ifndef diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError_H +#define diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError_H +#include +#include +#include +#include +#include "diplomat_runtime.h" + +#include "ICU4XTimeZoneIdMapper.h" +#include "ICU4XError.h" +#ifdef __cplusplus +namespace capi { +extern "C" { +#endif +typedef struct diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError { + union { + ICU4XTimeZoneIdMapper* ok; + ICU4XError err; + }; + bool is_ok; +} diplomat_result_box_ICU4XTimeZoneIdMapper_ICU4XError; +#ifdef __cplusplus +} // extern "C" +} // namespace capi +#endif +#endif diff --git a/ffi/capi/bindings/dart/CustomTimeZone.g.dart b/ffi/capi/bindings/dart/CustomTimeZone.g.dart index 7ed9a0397f0..d6340102e1e 100644 --- a/ffi/capi/bindings/dart/CustomTimeZone.g.dart +++ b/ffi/capi/bindings/dart/CustomTimeZone.g.dart @@ -196,6 +196,23 @@ final class CustomTimeZone implements ffi.Finalizable { } + /// Sets the `time_zone_id` field from an IANA string by looking up + /// the corresponding BCP-47 string. + /// + /// Errors if the string is not a valid BCP-47 time zone ID. + /// + /// Throws [Error] on failure. + void trySetIanaTimeZoneId2(TimeZoneIdMapper mapper, String id) { + final temp = ffi2.Arena(); + final idView = id.utf8View; + final result = _ICU4XCustomTimeZone_try_set_iana_time_zone_id_2(_ffi, mapper._ffi, idView.allocIn(temp), idView.length); + temp.releaseAll(); + if (!result.isOk) { + throw Error.values.firstWhere((v) => v._ffi == result.union.err); + } + + } + /// Clears the `time_zone_id` field. /// /// See the [Rust documentation for `time_zone_id`](https://docs.rs/icu/latest/icu/timezone/struct.CustomTimeZone.html#structfield.time_zone_id) for more information. @@ -446,6 +463,11 @@ external _ResultVoidInt32 _ICU4XCustomTimeZone_try_set_time_zone_id(ffi.Pointer< // ignore: non_constant_identifier_names external _ResultVoidInt32 _ICU4XCustomTimeZone_try_set_iana_time_zone_id(ffi.Pointer self, ffi.Pointer mapper, ffi.Pointer idData, int idLength); +@meta.ResourceIdentifier('ICU4XCustomTimeZone_try_set_iana_time_zone_id_2') +@ffi.Native<_ResultVoidInt32 Function(ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Size)>(isLeaf: true, symbol: 'ICU4XCustomTimeZone_try_set_iana_time_zone_id_2') +// ignore: non_constant_identifier_names +external _ResultVoidInt32 _ICU4XCustomTimeZone_try_set_iana_time_zone_id_2(ffi.Pointer self, ffi.Pointer mapper, ffi.Pointer idData, int idLength); + @meta.ResourceIdentifier('ICU4XCustomTimeZone_clear_time_zone_id') @ffi.Native)>(isLeaf: true, symbol: 'ICU4XCustomTimeZone_clear_time_zone_id') // ignore: non_constant_identifier_names diff --git a/ffi/capi/bindings/dart/Error.g.dart b/ffi/capi/bindings/dart/Error.g.dart index a2dcc5f4d18..529d9ec846e 100644 --- a/ffi/capi/bindings/dart/Error.g.dart +++ b/ffi/capi/bindings/dart/Error.g.dart @@ -17,8 +17,12 @@ enum Error { /// Most APIs that return a string may return this error writeableError, + /// Some input was out of bounds outOfBoundsError, + /// Input expected to be UTF-8 was ill-formed + utf8Error, + dataMissingDataKeyError, dataMissingVariantError, @@ -136,6 +140,8 @@ enum Error { return 1; case outOfBoundsError: return 2; + case utf8Error: + return 3; case dataMissingDataKeyError: return 256; case dataMissingVariantError: diff --git a/ffi/capi/bindings/dart/TimeZoneIdMapper.g.dart b/ffi/capi/bindings/dart/TimeZoneIdMapper.g.dart new file mode 100644 index 00000000000..057d2ca0a56 --- /dev/null +++ b/ffi/capi/bindings/dart/TimeZoneIdMapper.g.dart @@ -0,0 +1,130 @@ +// generated by diplomat-tool + +part of 'lib.g.dart'; + +/// A mapper between IANA time zone identifiers and BCP-47 time zone identifiers. +/// +/// This mapper supports two-way mapping, but it is optimized for the case of IANA to BCP-47. +/// It also supports normalizing and canonicalizing the IANA strings. +/// +/// See the [Rust documentation for `TimeZoneIdMapper`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapper.html) for more information. +final class TimeZoneIdMapper implements ffi.Finalizable { + final ffi.Pointer _ffi; + + // These are "used" in the sense that they keep dependencies alive + // ignore: unused_field + final core.List _selfEdge; + + // This takes in a list of lifetime edges (including for &self borrows) + // corresponding to data this may borrow from. These should be flat arrays containing + // references to objects, and this object will hold on to them to keep them alive and + // maintain borrow validity. + TimeZoneIdMapper._fromFfi(this._ffi, this._selfEdge) { + if (_selfEdge.isEmpty) { + _finalizer.attach(this, _ffi.cast()); + } + } + + static final _finalizer = ffi.NativeFinalizer(ffi.Native.addressOf(_ICU4XTimeZoneIdMapper_destroy)); + + /// See the [Rust documentation for `new`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapper.html#method.new) for more information. + /// + /// Throws [Error] on failure. + factory TimeZoneIdMapper(DataProvider provider) { + final result = _ICU4XTimeZoneIdMapper_create(provider._ffi); + if (!result.isOk) { + throw Error.values.firstWhere((v) => v._ffi == result.union.err); + } + return TimeZoneIdMapper._fromFfi(result.union.ok, []); + } + + /// See the [Rust documentation for `iana_to_bcp47`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.iana_to_bcp47) for more information. + /// + /// Throws [Error] on failure. + String ianaToBcp47(String value) { + final temp = ffi2.Arena(); + final valueView = value.utf8View; + final writeable = _Writeable(); + final result = _ICU4XTimeZoneIdMapper_iana_to_bcp47(_ffi, valueView.allocIn(temp), valueView.length, writeable._ffi); + temp.releaseAll(); + if (!result.isOk) { + throw Error.values.firstWhere((v) => v._ffi == result.union.err); + } + return writeable.finalize(); + } + + /// See the [Rust documentation for `normalize_iana`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.normalize_iana) for more information. + /// + /// Throws [Error] on failure. + String normalizeIana(String value) { + final temp = ffi2.Arena(); + final valueView = value.utf8View; + final writeable = _Writeable(); + final result = _ICU4XTimeZoneIdMapper_normalize_iana(_ffi, valueView.allocIn(temp), valueView.length, writeable._ffi); + temp.releaseAll(); + if (!result.isOk) { + throw Error.values.firstWhere((v) => v._ffi == result.union.err); + } + return writeable.finalize(); + } + + /// See the [Rust documentation for `canonicalize_iana`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.canonicalize_iana) for more information. + /// + /// Throws [Error] on failure. + String canonicalizeIana(String value) { + final temp = ffi2.Arena(); + final valueView = value.utf8View; + final writeable = _Writeable(); + final result = _ICU4XTimeZoneIdMapper_canonicalize_iana(_ffi, valueView.allocIn(temp), valueView.length, writeable._ffi); + temp.releaseAll(); + if (!result.isOk) { + throw Error.values.firstWhere((v) => v._ffi == result.union.err); + } + return writeable.finalize(); + } + + /// See the [Rust documentation for `find_canonical_iana_from_bcp47`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.find_canonical_iana_from_bcp47) for more information. + /// + /// Throws [Error] on failure. + String findCanonicalIanaFromBcp47(String value) { + final temp = ffi2.Arena(); + final valueView = value.utf8View; + final writeable = _Writeable(); + final result = _ICU4XTimeZoneIdMapper_find_canonical_iana_from_bcp47(_ffi, valueView.allocIn(temp), valueView.length, writeable._ffi); + temp.releaseAll(); + if (!result.isOk) { + throw Error.values.firstWhere((v) => v._ffi == result.union.err); + } + return writeable.finalize(); + } +} + +@meta.ResourceIdentifier('ICU4XTimeZoneIdMapper_destroy') +@ffi.Native)>(isLeaf: true, symbol: 'ICU4XTimeZoneIdMapper_destroy') +// ignore: non_constant_identifier_names +external void _ICU4XTimeZoneIdMapper_destroy(ffi.Pointer self); + +@meta.ResourceIdentifier('ICU4XTimeZoneIdMapper_create') +@ffi.Native<_ResultOpaqueInt32 Function(ffi.Pointer)>(isLeaf: true, symbol: 'ICU4XTimeZoneIdMapper_create') +// ignore: non_constant_identifier_names +external _ResultOpaqueInt32 _ICU4XTimeZoneIdMapper_create(ffi.Pointer provider); + +@meta.ResourceIdentifier('ICU4XTimeZoneIdMapper_iana_to_bcp47') +@ffi.Native<_ResultVoidInt32 Function(ffi.Pointer, ffi.Pointer, ffi.Size, ffi.Pointer)>(isLeaf: true, symbol: 'ICU4XTimeZoneIdMapper_iana_to_bcp47') +// ignore: non_constant_identifier_names +external _ResultVoidInt32 _ICU4XTimeZoneIdMapper_iana_to_bcp47(ffi.Pointer self, ffi.Pointer valueData, int valueLength, ffi.Pointer writeable); + +@meta.ResourceIdentifier('ICU4XTimeZoneIdMapper_normalize_iana') +@ffi.Native<_ResultVoidInt32 Function(ffi.Pointer, ffi.Pointer, ffi.Size, ffi.Pointer)>(isLeaf: true, symbol: 'ICU4XTimeZoneIdMapper_normalize_iana') +// ignore: non_constant_identifier_names +external _ResultVoidInt32 _ICU4XTimeZoneIdMapper_normalize_iana(ffi.Pointer self, ffi.Pointer valueData, int valueLength, ffi.Pointer writeable); + +@meta.ResourceIdentifier('ICU4XTimeZoneIdMapper_canonicalize_iana') +@ffi.Native<_ResultVoidInt32 Function(ffi.Pointer, ffi.Pointer, ffi.Size, ffi.Pointer)>(isLeaf: true, symbol: 'ICU4XTimeZoneIdMapper_canonicalize_iana') +// ignore: non_constant_identifier_names +external _ResultVoidInt32 _ICU4XTimeZoneIdMapper_canonicalize_iana(ffi.Pointer self, ffi.Pointer valueData, int valueLength, ffi.Pointer writeable); + +@meta.ResourceIdentifier('ICU4XTimeZoneIdMapper_find_canonical_iana_from_bcp47') +@ffi.Native<_ResultVoidInt32 Function(ffi.Pointer, ffi.Pointer, ffi.Size, ffi.Pointer)>(isLeaf: true, symbol: 'ICU4XTimeZoneIdMapper_find_canonical_iana_from_bcp47') +// ignore: non_constant_identifier_names +external _ResultVoidInt32 _ICU4XTimeZoneIdMapper_find_canonical_iana_from_bcp47(ffi.Pointer self, ffi.Pointer valueData, int valueLength, ffi.Pointer writeable); diff --git a/ffi/capi/bindings/dart/TimeZoneIdMapperWithFastCanonicalization.g.dart b/ffi/capi/bindings/dart/TimeZoneIdMapperWithFastCanonicalization.g.dart new file mode 100644 index 00000000000..88a01eeffe8 --- /dev/null +++ b/ffi/capi/bindings/dart/TimeZoneIdMapperWithFastCanonicalization.g.dart @@ -0,0 +1,90 @@ +// generated by diplomat-tool + +part of 'lib.g.dart'; + +/// A mapper between IANA time zone identifiers and BCP-47 time zone identifiers. +/// +/// This mapper supports two-way mapping, but it is optimized for the case of IANA to BCP-47. +/// It also supports normalizing and canonicalizing the IANA strings. +/// +/// See the [Rust documentation for `TimeZoneIdMapperWithFastCanonicalization`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalization.html) for more information. +final class TimeZoneIdMapperWithFastCanonicalization implements ffi.Finalizable { + final ffi.Pointer _ffi; + + // These are "used" in the sense that they keep dependencies alive + // ignore: unused_field + final core.List _selfEdge; + + // This takes in a list of lifetime edges (including for &self borrows) + // corresponding to data this may borrow from. These should be flat arrays containing + // references to objects, and this object will hold on to them to keep them alive and + // maintain borrow validity. + TimeZoneIdMapperWithFastCanonicalization._fromFfi(this._ffi, this._selfEdge) { + if (_selfEdge.isEmpty) { + _finalizer.attach(this, _ffi.cast()); + } + } + + static final _finalizer = ffi.NativeFinalizer(ffi.Native.addressOf(_ICU4XTimeZoneIdMapperWithFastCanonicalization_destroy)); + + /// See the [Rust documentation for `new`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalization.html#method.new) for more information. + /// + /// Throws [Error] on failure. + factory TimeZoneIdMapperWithFastCanonicalization(DataProvider provider) { + final result = _ICU4XTimeZoneIdMapperWithFastCanonicalization_create(provider._ffi); + if (!result.isOk) { + throw Error.values.firstWhere((v) => v._ffi == result.union.err); + } + return TimeZoneIdMapperWithFastCanonicalization._fromFfi(result.union.ok, []); + } + + /// See the [Rust documentation for `canonicalize_iana`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalizationBorrowed.html#method.canonicalize_iana) for more information. + /// + /// Throws [Error] on failure. + String canonicalizeIana(String value) { + final temp = ffi2.Arena(); + final valueView = value.utf8View; + final writeable = _Writeable(); + final result = _ICU4XTimeZoneIdMapperWithFastCanonicalization_canonicalize_iana(_ffi, valueView.allocIn(temp), valueView.length, writeable._ffi); + temp.releaseAll(); + if (!result.isOk) { + throw Error.values.firstWhere((v) => v._ffi == result.union.err); + } + return writeable.finalize(); + } + + /// See the [Rust documentation for `canonical_iana_from_bcp47`](https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalizationBorrowed.html#method.canonical_iana_from_bcp47) for more information. + /// + /// Throws [Error] on failure. + String canonicalIanaFromBcp47(String value) { + final temp = ffi2.Arena(); + final valueView = value.utf8View; + final writeable = _Writeable(); + final result = _ICU4XTimeZoneIdMapperWithFastCanonicalization_canonical_iana_from_bcp47(_ffi, valueView.allocIn(temp), valueView.length, writeable._ffi); + temp.releaseAll(); + if (!result.isOk) { + throw Error.values.firstWhere((v) => v._ffi == result.union.err); + } + return writeable.finalize(); + } +} + +@meta.ResourceIdentifier('ICU4XTimeZoneIdMapperWithFastCanonicalization_destroy') +@ffi.Native)>(isLeaf: true, symbol: 'ICU4XTimeZoneIdMapperWithFastCanonicalization_destroy') +// ignore: non_constant_identifier_names +external void _ICU4XTimeZoneIdMapperWithFastCanonicalization_destroy(ffi.Pointer self); + +@meta.ResourceIdentifier('ICU4XTimeZoneIdMapperWithFastCanonicalization_create') +@ffi.Native<_ResultOpaqueInt32 Function(ffi.Pointer)>(isLeaf: true, symbol: 'ICU4XTimeZoneIdMapperWithFastCanonicalization_create') +// ignore: non_constant_identifier_names +external _ResultOpaqueInt32 _ICU4XTimeZoneIdMapperWithFastCanonicalization_create(ffi.Pointer provider); + +@meta.ResourceIdentifier('ICU4XTimeZoneIdMapperWithFastCanonicalization_canonicalize_iana') +@ffi.Native<_ResultVoidInt32 Function(ffi.Pointer, ffi.Pointer, ffi.Size, ffi.Pointer)>(isLeaf: true, symbol: 'ICU4XTimeZoneIdMapperWithFastCanonicalization_canonicalize_iana') +// ignore: non_constant_identifier_names +external _ResultVoidInt32 _ICU4XTimeZoneIdMapperWithFastCanonicalization_canonicalize_iana(ffi.Pointer self, ffi.Pointer valueData, int valueLength, ffi.Pointer writeable); + +@meta.ResourceIdentifier('ICU4XTimeZoneIdMapperWithFastCanonicalization_canonical_iana_from_bcp47') +@ffi.Native<_ResultVoidInt32 Function(ffi.Pointer, ffi.Pointer, ffi.Size, ffi.Pointer)>(isLeaf: true, symbol: 'ICU4XTimeZoneIdMapperWithFastCanonicalization_canonical_iana_from_bcp47') +// ignore: non_constant_identifier_names +external _ResultVoidInt32 _ICU4XTimeZoneIdMapperWithFastCanonicalization_canonical_iana_from_bcp47(ffi.Pointer self, ffi.Pointer valueData, int valueLength, ffi.Pointer writeable); diff --git a/ffi/capi/bindings/dart/lib.g.dart b/ffi/capi/bindings/dart/lib.g.dart index 545d4628357..1c05884a9d0 100644 --- a/ffi/capi/bindings/dart/lib.g.dart +++ b/ffi/capi/bindings/dart/lib.g.dart @@ -118,6 +118,8 @@ part 'Time.g.dart'; part 'TimeFormatter.g.dart'; part 'TimeLength.g.dart'; part 'TimeZoneFormatter.g.dart'; +part 'TimeZoneIdMapper.g.dart'; +part 'TimeZoneIdMapperWithFastCanonicalization.g.dart'; part 'TitlecaseMapper.g.dart'; part 'TitlecaseOptions.g.dart'; part 'TrailingCase.g.dart'; diff --git a/ffi/capi/bindings/js/ICU4XCustomTimeZone.d.ts b/ffi/capi/bindings/js/ICU4XCustomTimeZone.d.ts index 8e37885ccb3..68eabe9b0a5 100644 --- a/ffi/capi/bindings/js/ICU4XCustomTimeZone.d.ts +++ b/ffi/capi/bindings/js/ICU4XCustomTimeZone.d.ts @@ -4,6 +4,7 @@ import { ICU4XError } from "./ICU4XError"; import { ICU4XIanaToBcp47Mapper } from "./ICU4XIanaToBcp47Mapper"; import { ICU4XIsoDateTime } from "./ICU4XIsoDateTime"; import { ICU4XMetazoneCalculator } from "./ICU4XMetazoneCalculator"; +import { ICU4XTimeZoneIdMapper } from "./ICU4XTimeZoneIdMapper"; /** @@ -140,6 +141,15 @@ export class ICU4XCustomTimeZone { */ try_set_iana_time_zone_id(mapper: ICU4XIanaToBcp47Mapper, id: string): void | never; + /** + + * Sets the `time_zone_id` field from an IANA string by looking up the corresponding BCP-47 string. + + * Errors if the string is not a valid BCP-47 time zone ID. + * @throws {@link FFIError}<{@link ICU4XError}> + */ + try_set_iana_time_zone_id_2(mapper: ICU4XTimeZoneIdMapper, id: string): void | never; + /** * Clears the `time_zone_id` field. diff --git a/ffi/capi/bindings/js/ICU4XCustomTimeZone.mjs b/ffi/capi/bindings/js/ICU4XCustomTimeZone.mjs index 154ab95a549..747011e02af 100644 --- a/ffi/capi/bindings/js/ICU4XCustomTimeZone.mjs +++ b/ffi/capi/bindings/js/ICU4XCustomTimeZone.mjs @@ -190,6 +190,26 @@ export class ICU4XCustomTimeZone { return diplomat_out; } + try_set_iana_time_zone_id_2(arg_mapper, arg_id) { + const buf_arg_id = diplomatRuntime.DiplomatBuf.str8(wasm, arg_id); + const diplomat_out = (() => { + const diplomat_receive_buffer = wasm.diplomat_alloc(5, 4); + wasm.ICU4XCustomTimeZone_try_set_iana_time_zone_id_2(diplomat_receive_buffer, this.underlying, arg_mapper.underlying, buf_arg_id.ptr, buf_arg_id.size); + const is_ok = diplomatRuntime.resultFlag(wasm, diplomat_receive_buffer, 4); + if (is_ok) { + const ok_value = {}; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + return ok_value; + } else { + const throw_value = ICU4XError_rust_to_js[diplomatRuntime.enumDiscriminant(wasm, diplomat_receive_buffer)]; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + throw new diplomatRuntime.FFIError(throw_value); + } + })(); + buf_arg_id.free(); + return diplomat_out; + } + clear_time_zone_id() { wasm.ICU4XCustomTimeZone_clear_time_zone_id(this.underlying); } diff --git a/ffi/capi/bindings/js/ICU4XError.d.ts b/ffi/capi/bindings/js/ICU4XError.d.ts index 8926e5f89a2..99111f25ab8 100644 --- a/ffi/capi/bindings/js/ICU4XError.d.ts +++ b/ffi/capi/bindings/js/ICU4XError.d.ts @@ -19,8 +19,15 @@ export enum ICU4XError { */ WriteableError = 'WriteableError', /** + + * Some input was out of bounds */ OutOfBoundsError = 'OutOfBoundsError', + /** + + * Input expected to be UTF-8 was ill-formed + */ + Utf8Error = 'Utf8Error', /** */ DataMissingDataKeyError = 'DataMissingDataKeyError', diff --git a/ffi/capi/bindings/js/ICU4XError.mjs b/ffi/capi/bindings/js/ICU4XError.mjs index 9b7349397aa..873843065e6 100644 --- a/ffi/capi/bindings/js/ICU4XError.mjs +++ b/ffi/capi/bindings/js/ICU4XError.mjs @@ -5,6 +5,7 @@ export const ICU4XError_js_to_rust = { "UnknownError": 0, "WriteableError": 1, "OutOfBoundsError": 2, + "Utf8Error": 3, "DataMissingDataKeyError": 256, "DataMissingVariantError": 257, "DataMissingLocaleError": 258, @@ -64,6 +65,7 @@ export const ICU4XError_rust_to_js = { [0]: "UnknownError", [1]: "WriteableError", [2]: "OutOfBoundsError", + [3]: "Utf8Error", [256]: "DataMissingDataKeyError", [257]: "DataMissingVariantError", [258]: "DataMissingLocaleError", @@ -123,6 +125,7 @@ export const ICU4XError = { "UnknownError": "UnknownError", "WriteableError": "WriteableError", "OutOfBoundsError": "OutOfBoundsError", + "Utf8Error": "Utf8Error", "DataMissingDataKeyError": "DataMissingDataKeyError", "DataMissingVariantError": "DataMissingVariantError", "DataMissingLocaleError": "DataMissingLocaleError", diff --git a/ffi/capi/bindings/js/ICU4XTimeZoneIdMapper.d.ts b/ffi/capi/bindings/js/ICU4XTimeZoneIdMapper.d.ts new file mode 100644 index 00000000000..3424fcd61cc --- /dev/null +++ b/ffi/capi/bindings/js/ICU4XTimeZoneIdMapper.d.ts @@ -0,0 +1,49 @@ +import { FFIError } from "./diplomat-runtime" +import { ICU4XDataProvider } from "./ICU4XDataProvider"; +import { ICU4XError } from "./ICU4XError"; + +/** + + * A mapper between IANA time zone identifiers and BCP-47 time zone identifiers. + + * This mapper supports two-way mapping, but it is optimized for the case of IANA to BCP-47. It also supports normalizing and canonicalizing the IANA strings. + + * See the {@link https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapper.html Rust documentation for `TimeZoneIdMapper`} for more information. + */ +export class ICU4XTimeZoneIdMapper { + + /** + + * See the {@link https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapper.html#method.new Rust documentation for `new`} for more information. + * @throws {@link FFIError}<{@link ICU4XError}> + */ + static create(provider: ICU4XDataProvider): ICU4XTimeZoneIdMapper | never; + + /** + + * See the {@link https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.iana_to_bcp47 Rust documentation for `iana_to_bcp47`} for more information. + * @throws {@link FFIError}<{@link ICU4XError}> + */ + iana_to_bcp47(value: string): string | never; + + /** + + * See the {@link https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.normalize_iana Rust documentation for `normalize_iana`} for more information. + * @throws {@link FFIError}<{@link ICU4XError}> + */ + normalize_iana(value: string): string | never; + + /** + + * See the {@link https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.canonicalize_iana Rust documentation for `canonicalize_iana`} for more information. + * @throws {@link FFIError}<{@link ICU4XError}> + */ + canonicalize_iana(value: string): string | never; + + /** + + * See the {@link https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperBorrowed.html#method.find_canonical_iana_from_bcp47 Rust documentation for `find_canonical_iana_from_bcp47`} for more information. + * @throws {@link FFIError}<{@link ICU4XError}> + */ + find_canonical_iana_from_bcp47(value: string): string | never; +} diff --git a/ffi/capi/bindings/js/ICU4XTimeZoneIdMapper.mjs b/ffi/capi/bindings/js/ICU4XTimeZoneIdMapper.mjs new file mode 100644 index 00000000000..b15584169d8 --- /dev/null +++ b/ffi/capi/bindings/js/ICU4XTimeZoneIdMapper.mjs @@ -0,0 +1,123 @@ +import wasm from "./diplomat-wasm.mjs" +import * as diplomatRuntime from "./diplomat-runtime.mjs" +import { ICU4XError_js_to_rust, ICU4XError_rust_to_js } from "./ICU4XError.mjs" + +const ICU4XTimeZoneIdMapper_box_destroy_registry = new FinalizationRegistry(underlying => { + wasm.ICU4XTimeZoneIdMapper_destroy(underlying); +}); + +export class ICU4XTimeZoneIdMapper { + #lifetimeEdges = []; + constructor(underlying, owned, edges) { + this.underlying = underlying; + this.#lifetimeEdges.push(...edges); + if (owned) { + ICU4XTimeZoneIdMapper_box_destroy_registry.register(this, underlying); + } + } + + static create(arg_provider) { + return (() => { + const diplomat_receive_buffer = wasm.diplomat_alloc(5, 4); + wasm.ICU4XTimeZoneIdMapper_create(diplomat_receive_buffer, arg_provider.underlying); + const is_ok = diplomatRuntime.resultFlag(wasm, diplomat_receive_buffer, 4); + if (is_ok) { + const ok_value = new ICU4XTimeZoneIdMapper(diplomatRuntime.ptrRead(wasm, diplomat_receive_buffer), true, []); + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + return ok_value; + } else { + const throw_value = ICU4XError_rust_to_js[diplomatRuntime.enumDiscriminant(wasm, diplomat_receive_buffer)]; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + throw new diplomatRuntime.FFIError(throw_value); + } + })(); + } + + iana_to_bcp47(arg_value) { + const buf_arg_value = diplomatRuntime.DiplomatBuf.str8(wasm, arg_value); + const diplomat_out = diplomatRuntime.withWriteable(wasm, (writeable) => { + return (() => { + const diplomat_receive_buffer = wasm.diplomat_alloc(5, 4); + wasm.ICU4XTimeZoneIdMapper_iana_to_bcp47(diplomat_receive_buffer, this.underlying, buf_arg_value.ptr, buf_arg_value.size, writeable); + const is_ok = diplomatRuntime.resultFlag(wasm, diplomat_receive_buffer, 4); + if (is_ok) { + const ok_value = {}; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + return ok_value; + } else { + const throw_value = ICU4XError_rust_to_js[diplomatRuntime.enumDiscriminant(wasm, diplomat_receive_buffer)]; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + throw new diplomatRuntime.FFIError(throw_value); + } + })(); + }); + buf_arg_value.free(); + return diplomat_out; + } + + normalize_iana(arg_value) { + const buf_arg_value = diplomatRuntime.DiplomatBuf.str8(wasm, arg_value); + const diplomat_out = diplomatRuntime.withWriteable(wasm, (writeable) => { + return (() => { + const diplomat_receive_buffer = wasm.diplomat_alloc(5, 4); + wasm.ICU4XTimeZoneIdMapper_normalize_iana(diplomat_receive_buffer, this.underlying, buf_arg_value.ptr, buf_arg_value.size, writeable); + const is_ok = diplomatRuntime.resultFlag(wasm, diplomat_receive_buffer, 4); + if (is_ok) { + const ok_value = {}; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + return ok_value; + } else { + const throw_value = ICU4XError_rust_to_js[diplomatRuntime.enumDiscriminant(wasm, diplomat_receive_buffer)]; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + throw new diplomatRuntime.FFIError(throw_value); + } + })(); + }); + buf_arg_value.free(); + return diplomat_out; + } + + canonicalize_iana(arg_value) { + const buf_arg_value = diplomatRuntime.DiplomatBuf.str8(wasm, arg_value); + const diplomat_out = diplomatRuntime.withWriteable(wasm, (writeable) => { + return (() => { + const diplomat_receive_buffer = wasm.diplomat_alloc(5, 4); + wasm.ICU4XTimeZoneIdMapper_canonicalize_iana(diplomat_receive_buffer, this.underlying, buf_arg_value.ptr, buf_arg_value.size, writeable); + const is_ok = diplomatRuntime.resultFlag(wasm, diplomat_receive_buffer, 4); + if (is_ok) { + const ok_value = {}; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + return ok_value; + } else { + const throw_value = ICU4XError_rust_to_js[diplomatRuntime.enumDiscriminant(wasm, diplomat_receive_buffer)]; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + throw new diplomatRuntime.FFIError(throw_value); + } + })(); + }); + buf_arg_value.free(); + return diplomat_out; + } + + find_canonical_iana_from_bcp47(arg_value) { + const buf_arg_value = diplomatRuntime.DiplomatBuf.str8(wasm, arg_value); + const diplomat_out = diplomatRuntime.withWriteable(wasm, (writeable) => { + return (() => { + const diplomat_receive_buffer = wasm.diplomat_alloc(5, 4); + wasm.ICU4XTimeZoneIdMapper_find_canonical_iana_from_bcp47(diplomat_receive_buffer, this.underlying, buf_arg_value.ptr, buf_arg_value.size, writeable); + const is_ok = diplomatRuntime.resultFlag(wasm, diplomat_receive_buffer, 4); + if (is_ok) { + const ok_value = {}; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + return ok_value; + } else { + const throw_value = ICU4XError_rust_to_js[diplomatRuntime.enumDiscriminant(wasm, diplomat_receive_buffer)]; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + throw new diplomatRuntime.FFIError(throw_value); + } + })(); + }); + buf_arg_value.free(); + return diplomat_out; + } +} diff --git a/ffi/capi/bindings/js/ICU4XTimeZoneIdMapperWithFastCanonicalization.d.ts b/ffi/capi/bindings/js/ICU4XTimeZoneIdMapperWithFastCanonicalization.d.ts new file mode 100644 index 00000000000..b6cadd9a0f1 --- /dev/null +++ b/ffi/capi/bindings/js/ICU4XTimeZoneIdMapperWithFastCanonicalization.d.ts @@ -0,0 +1,35 @@ +import { FFIError } from "./diplomat-runtime" +import { ICU4XDataProvider } from "./ICU4XDataProvider"; +import { ICU4XError } from "./ICU4XError"; + +/** + + * A mapper between IANA time zone identifiers and BCP-47 time zone identifiers. + + * This mapper supports two-way mapping, but it is optimized for the case of IANA to BCP-47. It also supports normalizing and canonicalizing the IANA strings. + + * See the {@link https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalization.html Rust documentation for `TimeZoneIdMapperWithFastCanonicalization`} for more information. + */ +export class ICU4XTimeZoneIdMapperWithFastCanonicalization { + + /** + + * See the {@link https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalization.html#method.new Rust documentation for `new`} for more information. + * @throws {@link FFIError}<{@link ICU4XError}> + */ + static create(provider: ICU4XDataProvider): ICU4XTimeZoneIdMapperWithFastCanonicalization | never; + + /** + + * See the {@link https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalizationBorrowed.html#method.canonicalize_iana Rust documentation for `canonicalize_iana`} for more information. + * @throws {@link FFIError}<{@link ICU4XError}> + */ + canonicalize_iana(value: string): string | never; + + /** + + * See the {@link https://docs.rs/icu/latest/icu/timezone/struct.TimeZoneIdMapperWithFastCanonicalizationBorrowed.html#method.canonical_iana_from_bcp47 Rust documentation for `canonical_iana_from_bcp47`} for more information. + * @throws {@link FFIError}<{@link ICU4XError}> + */ + canonical_iana_from_bcp47(value: string): string | never; +} diff --git a/ffi/capi/bindings/js/ICU4XTimeZoneIdMapperWithFastCanonicalization.mjs b/ffi/capi/bindings/js/ICU4XTimeZoneIdMapperWithFastCanonicalization.mjs new file mode 100644 index 00000000000..14ceb79bf88 --- /dev/null +++ b/ffi/capi/bindings/js/ICU4XTimeZoneIdMapperWithFastCanonicalization.mjs @@ -0,0 +1,79 @@ +import wasm from "./diplomat-wasm.mjs" +import * as diplomatRuntime from "./diplomat-runtime.mjs" +import { ICU4XError_js_to_rust, ICU4XError_rust_to_js } from "./ICU4XError.mjs" + +const ICU4XTimeZoneIdMapperWithFastCanonicalization_box_destroy_registry = new FinalizationRegistry(underlying => { + wasm.ICU4XTimeZoneIdMapperWithFastCanonicalization_destroy(underlying); +}); + +export class ICU4XTimeZoneIdMapperWithFastCanonicalization { + #lifetimeEdges = []; + constructor(underlying, owned, edges) { + this.underlying = underlying; + this.#lifetimeEdges.push(...edges); + if (owned) { + ICU4XTimeZoneIdMapperWithFastCanonicalization_box_destroy_registry.register(this, underlying); + } + } + + static create(arg_provider) { + return (() => { + const diplomat_receive_buffer = wasm.diplomat_alloc(5, 4); + wasm.ICU4XTimeZoneIdMapperWithFastCanonicalization_create(diplomat_receive_buffer, arg_provider.underlying); + const is_ok = diplomatRuntime.resultFlag(wasm, diplomat_receive_buffer, 4); + if (is_ok) { + const ok_value = new ICU4XTimeZoneIdMapperWithFastCanonicalization(diplomatRuntime.ptrRead(wasm, diplomat_receive_buffer), true, []); + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + return ok_value; + } else { + const throw_value = ICU4XError_rust_to_js[diplomatRuntime.enumDiscriminant(wasm, diplomat_receive_buffer)]; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + throw new diplomatRuntime.FFIError(throw_value); + } + })(); + } + + canonicalize_iana(arg_value) { + const buf_arg_value = diplomatRuntime.DiplomatBuf.str8(wasm, arg_value); + const diplomat_out = diplomatRuntime.withWriteable(wasm, (writeable) => { + return (() => { + const diplomat_receive_buffer = wasm.diplomat_alloc(5, 4); + wasm.ICU4XTimeZoneIdMapperWithFastCanonicalization_canonicalize_iana(diplomat_receive_buffer, this.underlying, buf_arg_value.ptr, buf_arg_value.size, writeable); + const is_ok = diplomatRuntime.resultFlag(wasm, diplomat_receive_buffer, 4); + if (is_ok) { + const ok_value = {}; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + return ok_value; + } else { + const throw_value = ICU4XError_rust_to_js[diplomatRuntime.enumDiscriminant(wasm, diplomat_receive_buffer)]; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + throw new diplomatRuntime.FFIError(throw_value); + } + })(); + }); + buf_arg_value.free(); + return diplomat_out; + } + + canonical_iana_from_bcp47(arg_value) { + const buf_arg_value = diplomatRuntime.DiplomatBuf.str8(wasm, arg_value); + const diplomat_out = diplomatRuntime.withWriteable(wasm, (writeable) => { + return (() => { + const diplomat_receive_buffer = wasm.diplomat_alloc(5, 4); + wasm.ICU4XTimeZoneIdMapperWithFastCanonicalization_canonical_iana_from_bcp47(diplomat_receive_buffer, this.underlying, buf_arg_value.ptr, buf_arg_value.size, writeable); + const is_ok = diplomatRuntime.resultFlag(wasm, diplomat_receive_buffer, 4); + if (is_ok) { + const ok_value = {}; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + return ok_value; + } else { + const throw_value = ICU4XError_rust_to_js[diplomatRuntime.enumDiscriminant(wasm, diplomat_receive_buffer)]; + wasm.diplomat_free(diplomat_receive_buffer, 5, 4); + throw new diplomatRuntime.FFIError(throw_value); + } + })(); + }); + buf_arg_value.free(); + return diplomat_out; + } +} diff --git a/ffi/capi/bindings/js/index.d.ts b/ffi/capi/bindings/js/index.d.ts index 6e35b280e59..cd20153dd0b 100644 --- a/ffi/capi/bindings/js/index.d.ts +++ b/ffi/capi/bindings/js/index.d.ts @@ -112,6 +112,8 @@ export { ICU4XTime } from './ICU4XTime'; export { ICU4XTimeFormatter } from './ICU4XTimeFormatter'; export { ICU4XTimeLength } from './ICU4XTimeLength'; export { ICU4XTimeZoneFormatter } from './ICU4XTimeZoneFormatter'; +export { ICU4XTimeZoneIdMapper } from './ICU4XTimeZoneIdMapper'; +export { ICU4XTimeZoneIdMapperWithFastCanonicalization } from './ICU4XTimeZoneIdMapperWithFastCanonicalization'; export { ICU4XTitlecaseMapper } from './ICU4XTitlecaseMapper'; export { ICU4XTitlecaseOptionsV1 } from './ICU4XTitlecaseOptionsV1'; export { ICU4XTrailingCase } from './ICU4XTrailingCase'; diff --git a/ffi/capi/bindings/js/index.mjs b/ffi/capi/bindings/js/index.mjs index 7808f70797d..e3519bdd1b6 100644 --- a/ffi/capi/bindings/js/index.mjs +++ b/ffi/capi/bindings/js/index.mjs @@ -112,6 +112,8 @@ export { ICU4XTime } from './ICU4XTime.mjs'; export { ICU4XTimeFormatter } from './ICU4XTimeFormatter.mjs'; export { ICU4XTimeLength } from './ICU4XTimeLength.mjs'; export { ICU4XTimeZoneFormatter } from './ICU4XTimeZoneFormatter.mjs'; +export { ICU4XTimeZoneIdMapper } from './ICU4XTimeZoneIdMapper.mjs'; +export { ICU4XTimeZoneIdMapperWithFastCanonicalization } from './ICU4XTimeZoneIdMapperWithFastCanonicalization.mjs'; export { ICU4XTitlecaseMapper } from './ICU4XTitlecaseMapper.mjs'; export { ICU4XTitlecaseOptionsV1 } from './ICU4XTitlecaseOptionsV1.mjs'; export { ICU4XTrailingCase } from './ICU4XTrailingCase.mjs'; diff --git a/ffi/capi/src/errors.rs b/ffi/capi/src/errors.rs index a460aaad8e3..e47d8f6d05c 100644 --- a/ffi/capi/src/errors.rs +++ b/ffi/capi/src/errors.rs @@ -73,8 +73,10 @@ pub mod ffi { /// Typically found when not enough space is allocated /// Most APIs that return a string may return this error WriteableError = 0x01, - // Some input was out of bounds + /// Some input was out of bounds OutOfBoundsError = 0x02, + /// Input expected to be UTF-8 was ill-formed + Utf8Error = 0x03, // general data errors // See DataError @@ -215,6 +217,12 @@ impl From for ICU4XError { } } +impl From for ICU4XError { + fn from(_: core::str::Utf8Error) -> Self { + ICU4XError::Utf8Error + } +} + #[cfg(feature = "icu_collator")] impl From for ICU4XError { fn from(e: CollatorError) -> Self { diff --git a/ffi/capi/src/iana_bcp47_mapper.rs b/ffi/capi/src/iana_bcp47_mapper.rs index 2d79226e78c..3d685250e04 100644 --- a/ffi/capi/src/iana_bcp47_mapper.rs +++ b/ffi/capi/src/iana_bcp47_mapper.rs @@ -2,6 +2,8 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). +#![allow(deprecated)] // these APIs are deprecated in Rust + #[diplomat::bridge] pub mod ffi { use crate::errors::ffi::ICU4XError; diff --git a/ffi/capi/src/lib.rs b/ffi/capi/src/lib.rs index 20e5c2a2c8d..0f9f3e62313 100644 --- a/ffi/capi/src/lib.rs +++ b/ffi/capi/src/lib.rs @@ -152,6 +152,8 @@ pub mod time; pub mod timezone; #[cfg(feature = "icu_datetime")] pub mod timezone_formatter; +#[cfg(any(feature = "icu_datetime", feature = "icu_timezone"))] +pub mod timezone_mapper; #[cfg(feature = "experimental_components")] pub mod units_converter; #[cfg(feature = "icu_calendar")] diff --git a/ffi/capi/src/timezone.rs b/ffi/capi/src/timezone.rs index 2b83ae7d2da..b61d405fd93 100644 --- a/ffi/capi/src/timezone.rs +++ b/ffi/capi/src/timezone.rs @@ -138,6 +138,12 @@ pub mod ffi { #[diplomat::rust_link(icu::timezone::CustomTimeZone::time_zone_id, StructField)] #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id, Struct, compact)] #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id::from_str, FnInStruct, hidden)] + #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id::deref, FnInStruct, hidden)] + #[diplomat::rust_link( + icu::timezone::TimeZoneBcp47Id::Target, + AssociatedTypeInStruct, + hidden + )] pub fn try_set_time_zone_id(&mut self, id: &DiplomatStr) -> Result<(), ICU4XError> { self.0.time_zone_id = Some(icu_timezone::TimeZoneBcp47Id( tinystr::TinyAsciiStr::from_bytes(id)?, @@ -166,6 +172,27 @@ pub mod ffi { Ok(()) } + // *** TODO: in 2.0 please replace try_set_iana_time_zone_id with try_set_iana_time_zone_id_2 *** + + /// Sets the `time_zone_id` field from an IANA string by looking up + /// the corresponding BCP-47 string. + /// + /// Errors if the string is not a valid BCP-47 time zone ID. + pub fn try_set_iana_time_zone_id_2( + &mut self, + mapper: &crate::timezone_mapper::ffi::ICU4XTimeZoneIdMapper, + id: &DiplomatStr, + ) -> Result<(), ICU4XError> { + self.0.time_zone_id = Some( + mapper + .0 + .as_borrowed() + .iana_bytes_to_bcp47(id) + .ok_or(ICU4XError::TimeZoneInvalidIdError)?, + ); + Ok(()) + } + /// Clears the `time_zone_id` field. #[diplomat::rust_link(icu::timezone::CustomTimeZone::time_zone_id, StructField)] #[diplomat::rust_link(icu::timezone::TimeZoneBcp47Id, Struct, compact)] diff --git a/ffi/capi/src/timezone_mapper.rs b/ffi/capi/src/timezone_mapper.rs new file mode 100644 index 00000000000..122251ef7bb --- /dev/null +++ b/ffi/capi/src/timezone_mapper.rs @@ -0,0 +1,205 @@ +// 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 ). + +#[diplomat::bridge] +pub mod ffi { + use crate::errors::ffi::ICU4XError; + use crate::provider::ffi::ICU4XDataProvider; + use alloc::boxed::Box; + use icu_timezone::{ + TimeZoneBcp47Id, TimeZoneIdMapper, TimeZoneIdMapperWithFastCanonicalization, + }; + use tinystr::TinyAsciiStr; + + /// A mapper between IANA time zone identifiers and BCP-47 time zone identifiers. + /// + /// This mapper supports two-way mapping, but it is optimized for the case of IANA to BCP-47. + /// It also supports normalizing and canonicalizing the IANA strings. + #[diplomat::opaque] + #[diplomat::rust_link(icu::timezone::TimeZoneIdMapper, Struct)] + #[diplomat::rust_link(icu::timezone::TimeZoneIdMapper::as_borrowed, FnInStruct, hidden)] + #[diplomat::rust_link(icu::timezone::TimeZoneIdMapperBorrowed, Struct, hidden)] + #[diplomat::rust_link(icu::timezone::NormalizedIana, Struct, hidden)] + pub struct ICU4XTimeZoneIdMapper(pub TimeZoneIdMapper); + + impl ICU4XTimeZoneIdMapper { + #[diplomat::rust_link(icu::timezone::TimeZoneIdMapper::new, FnInStruct)] + #[diplomat::attr(all(supports = constructors, supports = fallible_constructors), constructor)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result, ICU4XError> { + Ok(Box::new(ICU4XTimeZoneIdMapper(call_constructor!( + TimeZoneIdMapper::new [r => Ok(r)], + TimeZoneIdMapper::try_new_with_any_provider, + TimeZoneIdMapper::try_new_with_buffer_provider, + provider, + )?))) + } + + #[diplomat::rust_link(icu::timezone::TimeZoneIdMapperBorrowed::iana_to_bcp47, FnInStruct)] + #[diplomat::rust_link( + icu::timezone::TimeZoneIdMapperBorrowed::iana_bytes_to_bcp47, + FnInStruct, + hidden + )] + pub fn iana_to_bcp47( + &self, + value: &DiplomatStr, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + use writeable::Writeable; + let handle = self.0.as_borrowed(); + if let Some(s) = handle.iana_bytes_to_bcp47(value) { + Ok(s.0.write_to(write)?) + } else { + Err(ICU4XError::TimeZoneInvalidIdError) + } + } + + #[diplomat::rust_link(icu::timezone::TimeZoneIdMapperBorrowed::normalize_iana, FnInStruct)] + pub fn normalize_iana( + &self, + value: &DiplomatStr, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + use writeable::Writeable; + let handle = self.0.as_borrowed(); + // Validate the UTF-8 here because it gets echoed back to the writeable + let value = core::str::from_utf8(value)?; + if let Some(s) = handle.normalize_iana(value) { + Ok(s.0.write_to(write)?) + } else { + Err(ICU4XError::TimeZoneInvalidIdError) + } + } + + #[diplomat::rust_link( + icu::timezone::TimeZoneIdMapperBorrowed::canonicalize_iana, + FnInStruct + )] + pub fn canonicalize_iana( + &self, + value: &DiplomatStr, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + use writeable::Writeable; + let handle = self.0.as_borrowed(); + // Validate the UTF-8 here because it gets echoed back to the writeable + let value = core::str::from_utf8(value)?; + if let Some(s) = handle.canonicalize_iana(value) { + Ok(s.0.write_to(write)?) + } else { + Err(ICU4XError::TimeZoneInvalidIdError) + } + } + + #[diplomat::rust_link( + icu::timezone::TimeZoneIdMapperBorrowed::find_canonical_iana_from_bcp47, + FnInStruct + )] + pub fn find_canonical_iana_from_bcp47( + &self, + value: &DiplomatStr, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + use writeable::Writeable; + let handle = self.0.as_borrowed(); + let bcp47_id = TimeZoneBcp47Id(TinyAsciiStr::from_bytes(value)?); + if let Some(s) = handle.find_canonical_iana_from_bcp47(bcp47_id) { + Ok(s.write_to(write)?) + } else { + Err(ICU4XError::TimeZoneInvalidIdError) + } + } + } + + /// A mapper between IANA time zone identifiers and BCP-47 time zone identifiers. + /// + /// This mapper supports two-way mapping, but it is optimized for the case of IANA to BCP-47. + /// It also supports normalizing and canonicalizing the IANA strings. + #[diplomat::opaque] + #[diplomat::rust_link(icu::timezone::TimeZoneIdMapperWithFastCanonicalization, Struct)] + #[diplomat::rust_link( + icu::timezone::TimeZoneIdMapperWithFastCanonicalization::as_borrowed, + FnInStruct, + hidden + )] + #[diplomat::rust_link( + icu::timezone::TimeZoneIdMapperWithFastCanonicalization::inner, + FnInStruct, + hidden + )] + #[diplomat::rust_link( + icu::timezone::TimeZoneIdMapperWithFastCanonicalizationBorrowed, + Struct, + hidden + )] + #[diplomat::rust_link( + icu::timezone::TimeZoneIdMapperWithFastCanonicalizationBorrowed::inner, + FnInStruct, + hidden + )] + pub struct ICU4XTimeZoneIdMapperWithFastCanonicalization( + pub TimeZoneIdMapperWithFastCanonicalization, + ); + + impl ICU4XTimeZoneIdMapperWithFastCanonicalization { + #[diplomat::rust_link( + icu::timezone::TimeZoneIdMapperWithFastCanonicalization::new, + FnInStruct + )] + #[diplomat::attr(all(supports = constructors, supports = fallible_constructors), constructor)] + pub fn create( + provider: &ICU4XDataProvider, + ) -> Result, ICU4XError> { + Ok(Box::new(ICU4XTimeZoneIdMapperWithFastCanonicalization( + call_constructor!( + TimeZoneIdMapperWithFastCanonicalization::new [r => Ok(r)], + TimeZoneIdMapperWithFastCanonicalization::try_new_with_any_provider, + TimeZoneIdMapperWithFastCanonicalization::try_new_with_buffer_provider, + provider, + )?, + ))) + } + + #[diplomat::rust_link( + icu::timezone::TimeZoneIdMapperWithFastCanonicalizationBorrowed::canonicalize_iana, + FnInStruct + )] + pub fn canonicalize_iana( + &self, + value: &DiplomatStr, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + use writeable::Writeable; + let handle = self.0.as_borrowed(); + // Validate the UTF-8 here because it gets echoed back to the writeable + let value = core::str::from_utf8(value)?; + if let Some(s) = handle.canonicalize_iana(value) { + Ok(s.0.write_to(write)?) + } else { + Err(ICU4XError::TimeZoneInvalidIdError) + } + } + + #[diplomat::rust_link( + icu::timezone::TimeZoneIdMapperWithFastCanonicalizationBorrowed::canonical_iana_from_bcp47, + FnInStruct + )] + pub fn canonical_iana_from_bcp47( + &self, + value: &DiplomatStr, + write: &mut diplomat_runtime::DiplomatWriteable, + ) -> Result<(), ICU4XError> { + use writeable::Writeable; + let handle = self.0.as_borrowed(); + let bcp47_id = TimeZoneBcp47Id(TinyAsciiStr::from_bytes(value)?); + if let Some(s) = handle.canonical_iana_from_bcp47(bcp47_id) { + Ok(s.write_to(write)?) + } else { + Err(ICU4XError::TimeZoneInvalidIdError) + } + } + } +} diff --git a/provider/baked/timezone/data/macros.rs b/provider/baked/timezone/data/macros.rs index 4bae09934c5..9baef87ea2b 100644 --- a/provider/baked/timezone/data/macros.rs +++ b/provider/baked/timezone/data/macros.rs @@ -40,6 +40,13 @@ pub use __impl_time_zone_iana_to_bcp47_v1 as impl_time_zone_iana_to_bcp47_v1; #[doc(inline)] pub use __impliterable_time_zone_iana_to_bcp47_v1 as impliterable_time_zone_iana_to_bcp47_v1; #[macro_use] +#[path = "macros/time_zone_iana_to_bcp47_v2.rs.data"] +mod time_zone_iana_to_bcp47_v2; +#[doc(inline)] +pub use __impl_time_zone_iana_to_bcp47_v2 as impl_time_zone_iana_to_bcp47_v2; +#[doc(inline)] +pub use __impliterable_time_zone_iana_to_bcp47_v2 as impliterable_time_zone_iana_to_bcp47_v2; +#[macro_use] #[path = "macros/time_zone_metazone_period_v1.rs.data"] mod time_zone_metazone_period_v1; #[doc(inline)] diff --git a/provider/baked/timezone/data/macros/time_zone_iana_to_bcp47_v2.rs.data b/provider/baked/timezone/data/macros/time_zone_iana_to_bcp47_v2.rs.data new file mode 100644 index 00000000000..4fbca2aa6f8 --- /dev/null +++ b/provider/baked/timezone/data/macros/time_zone_iana_to_bcp47_v2.rs.data @@ -0,0 +1,42 @@ +// @generated +/// Implement `DataProvider` on the given struct using the data +/// hardcoded in this file. This allows the struct to be used with +/// `icu`'s `_unstable` constructors. +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_time_zone_iana_to_bcp47_v2 { + ($ provider : ty) => { + #[clippy::msrv = "1.67"] + const _: () = <$provider>::MUST_USE_MAKE_PROVIDER_MACRO; + #[clippy::msrv = "1.67"] + impl $provider { + #[doc(hidden)] + pub const SINGLETON_TIME_ZONE_IANA_TO_BCP47_V2: &'static ::Yokeable = &icu::timezone::provider::names::IanaToBcp47MapV2 { map: zerotrie::ZeroAsciiIgnoreCaseTrie { store: unsafe { zerovec::ZeroVec::from_bytes_unchecked(b"\xE1sABCEGHIJKLMNPRSTUWZ\x0F\x0F\x0F\x13\x13\x13\x13\x13\x13\x14\x14\x14\x16\x16\x16\x16\x16\x16\x03*\xCE\"KZ\xE4\xF3\xFD\x032E\x1F(29\xD6\xDB\xE1gfmnrstu\x02\t\t\t\r\x0E*+\xAC\xBF\x83\trica/\xE1rABCDEFGHJKLMNOPSTW\0\0\0\0\0\0\0\0\0\x01\x01\x01\x01\x01\x01\x01\x015v\x99\xC1\xCA\xD3\xDC\xE3\xF7\x1CP\x85\xAC\xB8\xC3\xCC\xE6\xC5bcdls\x07\x0C\x17\x1Eidjan\x91Mcra\x923dis_Ababa\x92\x11giers\x91{m\xC2ae\x04ra\x92\x07ra\x92\x06\xC5ailru\x15\x1B#.\xC2mn\x05ako\x93S\xC2gj\x04ui\x91Gul\x92?ssau\x92Uantyre\x93oazzaville\x91Ijumbura\x90]\xC3aeo\x11\x16\xC2is\x04ro\x92\x03ablanca\x93Cuta\x92\tnakry\x92C\xC3ajo\x14\x1C\xC2kr\x04ar\x95'_es_Salaam\x95Wibouti\x91suala\x91Wl_Aaiun\x92\x05reetown\x95#aborone\x91\rarare\x96u\xC2ou\x0Channesburg\x96qba\x95-\xC3ahi\x07\x0Fmpala\x95]artoum\x95\x15\xC2gn\x05ali\x95\rshasa\x91E\xC4aiou\x05\x0F\x13gos\x94\x17breville\x92%me\x95?\xC3abs\x05\x0Enda\x90\x01umbashi\x91Caka\x96s\xC3abo\x15\x1C\xC3lps\x05\nabo\x92Kuto\x94\reru\x939abane\x957\xC2gn\x08adishu\x95)rovia\x937\xC4adio\x07\x0F\x15irobi\x93\x07jamena\x95;amey\x94\x13uakchott\x93cuagadougou\x90Worto-Novo\x90_ao_Tome\x95/\xC3iru\x08\x0Fmbuktu\x93Ripoli\x93Anis\x95Kindhoek\x94\x0Ferica/\xE1vABCDEFGHIJKLMNOPRSTVWY\0\x01\x01\x02\x02\x02\x02\x02\x03\x03\x03\x03\x04\x04\x04\x05\x05\x06\x06\x06\x06\xE5D\xE7 Ki\xC4\xE1Zr\xAA\xDF\x85\xDF\xE7Z\xA3@z\x8D\xA3\xC5dnrst\x04\x1D\xC5\xCDak\x95e\xC3cgt\x08\x0Ehorage\x95iuilla\x89igua\x87\xC3agu\x08\x9Eguaina\x90ientina/\xC9BCJLMRSTU\r17@HUmuuenos_Aires\x90\x17\xC2ao\ttamarca\x90\x1B\xC2mr\rodRivadavia\x90\x1Adoba\x90\x19ujuy\x90\x1Fa_Rioja\x90\x1Dendoza\x90#io_Gallegos\x90%a\xC2ln\x04ta\x90'_\xC2JL\x05uan\x90+uis\x90!ucuman\x90)shuaia\x90-ba\x90Kuncion\x94O\xC2ik\x07kokan\x91?a\x95d\xC5aelou\x1A&2I\xC2hr\x0Fia\x91\x05_Banderas\x94\x05bados\x90Ql\xC2ei\x03m\x90kze\x91\x11anc-Sablon\x91/\xC3agi\x08\r_Vista\x90mota\x91]se\x95kenos_Aires\x90\x16\xC6ahioruCTa\x82\x89\xC5mnrty\x19\x1E$,\xC2bp\x0Bridge_Bay\x911o_Grande\x90qcun\x93uacas\x96_amarca\x90\x1A\xC2em\x05nne\x92/an\x93\x1Di\xC2ch\x05ago\x95muahua\x93qudad_Juarez\x93s\xC2rs\x14\xC2ad\x0Bl_Harbour\x91>oba\x90\x18ta_Rica\x91_eston\x91\x13\xC2ir\x05aba\x90oacao\x8F\xC3aeo\x1C+\xC2nw\x0Bmarkshavn\x927son\x913_Creek\x915\xC2nt\x05ver\x95oroit\x95qminica\x91w\xC4diln\x08\x10\x1Bmonton\x91\x15runepe\x90s_Salvador\x951senada\x94\x06ort\xC2_a\x11\xC2NW\x07elson\x91\x17ayne\x95tleza\x90w\xC4loru\t\x1B.ace_Bay\x91\x19\xC2do\x06thab\x928se_Bay\x91\x1B\xC2ae\tnd_Turk\x959nada\x92+\xC2ay\x1C\xC3dty\x08\x0Feloupe\x92Eemala\x92Qaquil\x91\x7Fana\x92W\xC2ae\x0F\xC2lv\x06ifax\x91\x1Dana\x91crmosillo\x93w\xC2nqn\xC2dueiana\xC2/pW\xC7IKMPTVW\r\x12\x1A%/Andianapolis\x95unox\x95{arengo\x95getersburg\x96\x19ell_City\x96\x15\xC2ei\x05vay\x95wncennes\x96\rinamac\x96\x17olis\x95tvik\x919aluit\x91\x1F\xC2au\x07maica\x93\x01\xC2jn\x04uy\x90\x1Eeau\x95y\xC3enr!(ntucky/\xC2LM\x0Bouisville\x95\x7Fonticello\x96\x03ox_IN\x95zalendijk\x90g\xC3aio\x06\n_Paz\x90ema\x94-\xC3suw\n\x13_Angeles\x95}isville\x95~er_Princes\x953\xC4aeio;ks\xC5cnrtz\x05\x11\"*eio\x90{a\xC2gu\x04ua\x94\x19s\x90y\xC2it\x05got\x92Ginique\x93aamoros\x93yatlan\x94\x01\xC4nrtx\x10\x15\x1E\xC2do\x05oza\x90\"minee\x96\x01ida\x93}lakatla\x96\x05ico_City\x93{quelon\x94?n\xC2ct\x05ton\x91!\xC3ers\x0F\x14\xC2rv\x05rey\x93\x7Fideo\x96Ueal\x91(errat\x93e\xC5aeiou\x06\x0E\x15Lssau\x91\tw_York\x96\x0Bpigon\x91(\xC2mr\x03e\x96\x0F\xC2ot\x05nha\x90uh_Dakota/\xC3BCN\x07\x0Eeulah\x96\x1Benter\x96\x07ew_Salem\x96\tuk\x929jinaga\x94\x03\xC4ahou\x1E%R\xC2nr\x11\xC2ag\x04ma\x94+nirtung\x91\x1Eamaribo\x95+oenix\x96\x11rt\xC3-_o\x0B\x15au-Prince\x92aof_Spain\x95Q_\xC2AV\x05cre\x90~elho\x90}\xC2en\nrto_Rico\x94Cta_Arenas\x91S\xC4aeio\x190:\xC2in\nny_River\x91,kin_Inlet\x917\xC3cgs\x05\nife\x91\x01ina\x91%olute\x91#o_Branco\x90\x7Fsario\x90\x18\xC6achitw2>FK\x84\xC2no&t\xC3aio\x10\x15\xC2_r\x08Isabel\x94\x06em\x91\x07ago\x91U_Domingo\x91y_Paulo\x91\x03oresbysund\x92;iprock\x95ntka\x96\x13_\xC6BJKLTV\x0B\x11\x17\x1D$arthelemy\x92Iohns\x91'itts\x93\x15ucia\x931homas\x96cincent\x96]ift_Current\x91=\xC4ehio\x0B\x1C#gucigalpa\x92]u\xC2ln\x03e\x92=der_Bay\x91(juana\x94\x07r\xC2ot\x05nto\x91)ola\x96a\xC2ai\tncouver\x91+rgin\x96b\xC2hi\nitehorse\x91;nnipeg\x91-\xC2ae\x07kutat\x96\x1Dllowknife\x91\x14tarctica/\xC8CDMPRSTV\x06\x1D9@H[aasey\x90\x03\xC2au\x05vis\x90\x05montDUrville\x90\x07\xC2ac\x11\xC2cw\x08quarie\x90Eson\x90\tMurdo\x90\x0Balmer\x90\rothera\x90\x0F\xC2oy\nuth_Pole\x94$owa\x90\x11roll\x90\x13ostok\x90\x15ctic/Longyearbyen\x95\x1Fia/\xE1uABCDFGHIJKMNOPQRSTUVY\0\0\0\0\0\0\x01\x01\x01\x01\x01\x02\x02\x02\x02\x02\x02\x03\x03\x03G\x87\xC1\xEF\xF9\xFE+>\\\xCE\xFB\x1D)Kgx\xBF\tBZ\xC7dlmnqst\x04\n\x0F\x15!3en\x96mmaty\x93#man\x93\x03adyr\x94_t\xC2ao\x03u\x93\x1Fbe\x93!h\xC2gk\x06abat\x95Ihabad\x95Hyrau\x93%\xC4aeir%+2\xC5ghknr\x06\x0C\x0F\x15hdad\x92urain\x90[u\x90Mgkok\x95Anaul\x94[irut\x93/shkek\x93\tunei\x90c\xC3aho\x08-lcutta\x92p\xC3iou\x04\x17ta\x94]\xC2in\x08balsan\x93Wgqing\x91Xngking\x91Xlombo\x935\xC4ahiu\x0F\x14\x18\xC2cm\x04ca\x90Rascus\x955aka\x90Sli\x95G\xC2bs\x03ai\x83hanbe\x95Camagusta\x91iaza\x92'\xC3aeo\x06\x0Crbin\x91Xbron\x92Y\xC3_nv\n\x12Chi_Minh\x96eg_Kong\x92[d\x93Y\xC2rs\x07kutsk\x94ctanbul\x95N\xC2ae\x11\xC2ky\x06arta\x92gapura\x92erusalem\x92}\xC5ahoru2:AL\xC5bmrst\x03\x0B\x11\x17ul\x85chatka\x94wachi\x94;hgar\x91Z\xC2hm\x07mandu\x94\x1Fandu\x94\x1Eandyga\x94glkata\x92qasnoyarsk\x94i\xC3acw\x0B\x11la_Lumpur\x94\x0Bhing\x94\tait\x93\x1B\xC2au#\xC4cgkn\t\x0F\x16a\xC2ou\x02\x93\\\x93]adan\x94aassar\x92iila\x949scat\x94)\xC2io\x07cosia\x91kvo\xC2ks\tuznetsk\x94qibirsk\x94u\xC2mr\x04sk\x94sal\x93+\xC3hoy\n\x13nom_Penh\x93\x0Bntianak\x92kongyang\x93\x17\xC3aoy\x05\rtar\x94Qstanay\x93'zylorda\x93)\xC2ai\x07ngoon\x93Tyadh\x95\x0F\xC5aehir\x1A\x1F'0\xC3ikm\x05\x0Cgon\x96dhalin\x95\x03arkand\x96Woul\x93\x19anghai\x91Yngapore\x95\x19ednekolymsk\x94{\xC5abeho\x10\x17'4\xC2is\x05pei\x95Uhkent\x96Yilisi\x92-\xC2hl\x05ran\x92w_Aviv\x92|im\xC2bp\x03u\x91\nhu\x91\x0B\xC2km\x04yo\x93\x05sk\x94}\xC4jlrs\r#)ung_Pandang\x92ha\xC2an\tnbaatar\x93[_Bator\x93Zumqi\x91[t-Nera\x95\x01\xC2il\tentiane\x93-adivostok\x95\x07\xC2ae\x0F\xC2kn\x06utsk\x95\x0Bgon\x93U\xC2kr\x0Caterinburg\x95\tevan\x8Dlantic/\xC8ABCFJMRS\x07\x0F\"0:BLzores\x94Kermuda\x90aa\xC2np\x05ary\x92\x0Be_Verde\x91ea\xC2er\x05roe\x92 oe\x92!an_Mayen\x95\x1Eadeira\x94Geykjavik\x92y\xC2ot\ruth_Georgia\x92O\xC2_a\x08Helena\x95\x1Bnley\x92\x19stralia/\xD0ABCDEHLMNPQSTVWY\x0F%7>DKeo{\x81\x8C\x9B\xA4\xAD\xB2\xC2Cd\x03T\x90Helaide\x903r\xC2io\x07sbane\x907ken_Hill\x905\xC2au\x08nberra\x90Hrrie\x90Mao\xC2lr\x10\xC2ae\x05ska\x95hutian\x95dizona\x96\x10entral\x95last\xC2-e\tIndiana\x95trn\x96\nawaii\x95rndiana-Starke\x95z\xC2io\x08chigan\x95puntain\x95nacific\x95|-New\x95|amoa\x90.C\x96\x1E-SU\x94nulu\x96\x1E") } }, bcp47_ids: unsafe { zerovec::ZeroVec::from_bytes_unchecked(b"adalv\0\0\0aedxb\0\0\0afkbl\0\0\0aganu\0\0\0aiaxa\0\0\0altia\0\0\0amevn\0\0\0ancur\0\0\0aolad\0\0\0aqcas\0\0\0aqdav\0\0\0aqddu\0\0\0aqmaw\0\0\0aqmcm\0\0\0aqplm\0\0\0aqrot\0\0\0aqsyw\0\0\0aqtrl\0\0\0aqvos\0\0\0arbue\0\0\0arcor\0\0\0arctc\0\0\0arirj\0\0\0arjuj\0\0\0arluq\0\0\0armdz\0\0\0arrgl\0\0\0arsla\0\0\0artuc\0\0\0aruaq\0\0\0arush\0\0\0asppg\0\0\0atvie\0\0\0auadl\0\0\0aubhq\0\0\0aubne\0\0\0audrw\0\0\0aueuc\0\0\0auhba\0\0\0auldc\0\0\0auldh\0\0\0aumel\0\0\0aumqi\0\0\0auper\0\0\0ausyd\0\0\0awaua\0\0\0azbak\0\0\0basjj\0\0\0bbbgi\0\0\0bddac\0\0\0bebru\0\0\0bfoua\0\0\0bgsof\0\0\0bhbah\0\0\0bibjm\0\0\0bjptn\0\0\0bmbda\0\0\0bnbwn\0\0\0bolpb\0\0\0bqkra\0\0\0braux\0\0\0brbel\0\0\0brbvb\0\0\0brcgb\0\0\0brcgr\0\0\0brern\0\0\0brfen\0\0\0brfor\0\0\0brmao\0\0\0brmcz\0\0\0brpvh\0\0\0brrbr\0\0\0brrec\0\0\0brsao\0\0\0brssa\0\0\0brstm\0\0\0bsnas\0\0\0btthi\0\0\0bwgbe\0\0\0bymsq\0\0\0bzbze\0\0\0cacfq\0\0\0caedm\0\0\0cafne\0\0\0caglb\0\0\0cagoo\0\0\0cahal\0\0\0caiql\0\0\0camon\0\0\0careb\0\0\0careg\0\0\0casjf\0\0\0cator\0\0\0cavan\0\0\0cawnp\0\0\0caybx\0\0\0caycb\0\0\0cayda\0\0\0caydq\0\0\0cayek\0\0\0cayev\0\0\0cayxy\0\0\0cayyn\0\0\0cayzs\0\0\0cccck\0\0\0cdfbm\0\0\0cdfih\0\0\0cfbgf\0\0\0cgbzv\0\0\0chzrh\0\0\0ciabj\0\0\0ckrar\0\0\0clipc\0\0\0clpuq\0\0\0clscl\0\0\0cmdla\0\0\0cnsha\0\0\0cnurc\0\0\0cobog\0\0\0crsjo\0\0\0cst6cdt\0cuhav\0\0\0cvrai\0\0\0cxxch\0\0\0cyfmg\0\0\0cynic\0\0\0czprg\0\0\0deber\0\0\0debsngn\0djjib\0\0\0dkcph\0\0\0dmdom\0\0\0dosdq\0\0\0dzalg\0\0\0ecgps\0\0\0ecgye\0\0\0eetll\0\0\0egcai\0\0\0eheai\0\0\0erasm\0\0\0esceu\0\0\0eslpa\0\0\0esmad\0\0\0est5edt\0etadd\0\0\0fihel\0\0\0fimhq\0\0\0fjsuv\0\0\0fkpsy\0\0\0fmksa\0\0\0fmpni\0\0\0fmtkk\0\0\0fotho\0\0\0frpar\0\0\0galbv\0\0\0gazastrpgblon\0\0\0gdgnd\0\0\0getbs\0\0\0gfcay\0\0\0gggci\0\0\0ghacc\0\0\0gigib\0\0\0gldkshvnglgoh\0\0\0globy\0\0\0glthu\0\0\0gmbjl\0\0\0gmt\0\0\0\0\0gncky\0\0\0gpbbr\0\0\0gpmsb\0\0\0gpsbh\0\0\0gqssg\0\0\0grath\0\0\0gsgrv\0\0\0gtgua\0\0\0gugum\0\0\0gwoxb\0\0\0gygeo\0\0\0hebron\0\0hkhkg\0\0\0hntgu\0\0\0hrzag\0\0\0htpap\0\0\0hubud\0\0\0iddjj\0\0\0idjkt\0\0\0idmak\0\0\0idpnk\0\0\0iedub\0\0\0imdgs\0\0\0inccu\0\0\0iodga\0\0\0iqbgw\0\0\0irthr\0\0\0isrey\0\0\0itrom\0\0\0jeruslm\0jesth\0\0\0jmkin\0\0\0joamm\0\0\0jptyo\0\0\0kenbo\0\0\0kgfru\0\0\0khpnh\0\0\0kicxi\0\0\0kipho\0\0\0kitrw\0\0\0kmyva\0\0\0knbas\0\0\0kpfnj\0\0\0krsel\0\0\0kwkwi\0\0\0kygec\0\0\0kzaau\0\0\0kzakx\0\0\0kzala\0\0\0kzguw\0\0\0kzksn\0\0\0kzkzo\0\0\0kzura\0\0\0lavte\0\0\0lbbey\0\0\0lccas\0\0\0livdz\0\0\0lkcmb\0\0\0lrmlw\0\0\0lsmsu\0\0\0ltvno\0\0\0lulux\0\0\0lvrix\0\0\0lytip\0\0\0macas\0\0\0mcmon\0\0\0mdkiv\0\0\0metgd\0\0\0mgtnr\0\0\0mhkwa\0\0\0mhmaj\0\0\0mkskp\0\0\0mlbko\0\0\0mmrgn\0\0\0mncoq\0\0\0mnhvd\0\0\0mnuln\0\0\0momfm\0\0\0mpspn\0\0\0mqfdf\0\0\0mrnkc\0\0\0msmni\0\0\0mst7mdt\0mtmla\0\0\0muplu\0\0\0mvmle\0\0\0mwblz\0\0\0mxchi\0\0\0mxcjs\0\0\0mxcun\0\0\0mxhmo\0\0\0mxmam\0\0\0mxmex\0\0\0mxmid\0\0\0mxmty\0\0\0mxmzt\0\0\0mxoji\0\0\0mxpvr\0\0\0mxtij\0\0\0mykch\0\0\0mykul\0\0\0mzmpm\0\0\0nawdh\0\0\0ncnou\0\0\0nenim\0\0\0nfnlk\0\0\0nglos\0\0\0nimga\0\0\0nlams\0\0\0noosl\0\0\0npktm\0\0\0nrinu\0\0\0nuiue\0\0\0nzakl\0\0\0nzcht\0\0\0ommct\0\0\0papty\0\0\0pelim\0\0\0pfgmr\0\0\0pfnhv\0\0\0pfppt\0\0\0pgpom\0\0\0pgraw\0\0\0phmnl\0\0\0pkkhi\0\0\0plwaw\0\0\0pmmqc\0\0\0pnpcn\0\0\0prsju\0\0\0pst8pdt\0ptfnc\0\0\0ptlis\0\0\0ptpdl\0\0\0pwror\0\0\0pyasu\0\0\0qadoh\0\0\0rereu\0\0\0robuh\0\0\0rsbeg\0\0\0ruasf\0\0\0rubax\0\0\0ruchita\0rudyr\0\0\0rugdx\0\0\0ruikt\0\0\0rukgd\0\0\0rukhndg\0rukra\0\0\0rukuf\0\0\0rukvx\0\0\0rumow\0\0\0runoz\0\0\0ruoms\0\0\0ruovb\0\0\0rupkc\0\0\0rurtw\0\0\0rusred\0\0rutof\0\0\0ruuly\0\0\0ruunera\0ruuus\0\0\0ruvog\0\0\0ruvvo\0\0\0ruyek\0\0\0ruyks\0\0\0rwkgl\0\0\0saruh\0\0\0sbhir\0\0\0scmaw\0\0\0sdkrt\0\0\0sesto\0\0\0sgsin\0\0\0shshn\0\0\0silju\0\0\0sjlyr\0\0\0skbts\0\0\0slfna\0\0\0smsai\0\0\0sndkr\0\0\0somgq\0\0\0srpbm\0\0\0ssjub\0\0\0sttms\0\0\0svsal\0\0\0sxphi\0\0\0sydam\0\0\0szqmn\0\0\0tcgdt\0\0\0tdndj\0\0\0tfpfr\0\0\0tglfw\0\0\0thbkk\0\0\0tjdyu\0\0\0tkfko\0\0\0tldil\0\0\0tmasb\0\0\0tntun\0\0\0totbu\0\0\0trist\0\0\0ttpos\0\0\0tvfun\0\0\0twtpe\0\0\0tzdar\0\0\0uaiev\0\0\0uasip\0\0\0ugkla\0\0\0umawk\0\0\0ummdy\0\0\0unk\0\0\0\0\0usadk\0\0\0usaeg\0\0\0usanc\0\0\0usboi\0\0\0uschi\0\0\0usden\0\0\0usdet\0\0\0ushnl\0\0\0usind\0\0\0usinvev\0usjnu\0\0\0usknx\0\0\0uslax\0\0\0uslui\0\0\0usmnm\0\0\0usmoc\0\0\0usmtm\0\0\0usndcnt\0usndnsl\0usnyc\0\0\0usoea\0\0\0usome\0\0\0usphx\0\0\0ussit\0\0\0ustel\0\0\0uswlz\0\0\0uswsq\0\0\0usxul\0\0\0usyak\0\0\0utc\0\0\0\0\0utce01\0\0utce02\0\0utce03\0\0utce04\0\0utce05\0\0utce06\0\0utce07\0\0utce08\0\0utce09\0\0utce10\0\0utce11\0\0utce12\0\0utce13\0\0utce14\0\0utcw01\0\0utcw02\0\0utcw03\0\0utcw04\0\0utcw05\0\0utcw06\0\0utcw07\0\0utcw08\0\0utcw09\0\0utcw10\0\0utcw11\0\0utcw12\0\0uymvd\0\0\0uzskd\0\0\0uztas\0\0\0vavat\0\0\0vcsvd\0\0\0veccs\0\0\0vgtov\0\0\0vistt\0\0\0vnsgn\0\0\0vuvli\0\0\0wfmau\0\0\0wsapw\0\0\0yeade\0\0\0ytmam\0\0\0zajnb\0\0\0zmlun\0\0\0zwhre\0\0\0") }, bcp47_ids_checksum: 4342095620703458995u64 }; + } + #[clippy::msrv = "1.67"] + impl icu_provider::DataProvider for $provider { + fn load(&self, req: icu_provider::DataRequest) -> Result, icu_provider::DataError> { + if req.locale.is_empty() { + Ok(icu_provider::DataResponse { payload: Some(icu_provider::DataPayload::from_static_ref(Self::SINGLETON_TIME_ZONE_IANA_TO_BCP47_V2)), metadata: Default::default() }) + } else { + Err(icu_provider::DataErrorKind::ExtraneousLocale.with_req(::KEY, req)) + } + } + } + }; +} +/// Implement `IterableDataProvider` on the given struct using the data +/// hardcoded in this file. This allows the struct to be used with +/// `DatagenDriver` for this key. +#[doc(hidden)] +#[macro_export] +macro_rules! __impliterable_time_zone_iana_to_bcp47_v2 { + ($ provider : ty) => { + #[clippy::msrv = "1.67"] + impl icu_provider::datagen::IterableDataProvider for $provider { + fn supported_locales(&self) -> Result, icu_provider::DataError> { + Ok([icu_provider::DataLocale::default()].into()) + } + } + }; +} diff --git a/provider/datagen/src/transform/cldr/time_zones/names.rs b/provider/datagen/src/transform/cldr/time_zones/names.rs index 877f759652b..f22042a9fea 100644 --- a/provider/datagen/src/transform/cldr/time_zones/names.rs +++ b/provider/datagen/src/transform/cldr/time_zones/names.rs @@ -244,3 +244,31 @@ fn test_compute_bcp47_ids_hash() { assert_ne!(checksum4, checksum5); assert_ne!(checksum4, checksum6); } + +/// Tests that all IANA time zone IDs normalize and canonicalize to their correct form. +#[test] +fn test_normalize_canonicalize_iana_coverage() { + let provider = crate::DatagenProvider::new_testing(); + + let resource: &cldr_serde::time_zones::bcp47_tzid::Resource = provider + .cldr() + .unwrap() + .bcp47() + .read_and_parse("timezone.json") + .unwrap(); + let iana2bcp = &compute_bcp47_tzids_btreemap(&resource.keyword.u.time_zones.values); + + let mapper = icu_timezone::TimeZoneIdMapper::try_new_unstable(&provider).unwrap(); + let mapper = mapper.as_borrowed(); + + for iana_id in iana2bcp.keys() { + let normalized = mapper.normalize_iana(iana_id).unwrap().0; + assert_eq!(&normalized, iana_id); + } + + let bcp2iana = compute_canonical_tzids_btreemap(&resource.keyword.u.time_zones.values); + for (iana_id, bcp47_id) in iana2bcp.iter() { + let canonicalized = mapper.canonicalize_iana(iana_id).unwrap().0; + assert_eq!(&canonicalized, bcp2iana.get(bcp47_id).unwrap()); + } +} diff --git a/tools/ffi_coverage/src/allowlist.rs b/tools/ffi_coverage/src/allowlist.rs index cd58c2dae16..512876a1ee5 100644 --- a/tools/ffi_coverage/src/allowlist.rs +++ b/tools/ffi_coverage/src/allowlist.rs @@ -449,6 +449,9 @@ lazy_static::lazy_static! { "icu_provider_adapters::fork::MultiForkByErrorProvider", "icu_provider_adapters::fork::MultiForkByKeyProvider", + // Specialized constructor for separately constructed instances + "icu::timezone::TimeZoneIdMapperWithFastCanonicalization::try_new_with_mapper", + // macros "icu::locid::langid", "icu::locid::locale", diff --git a/tutorials/cpp/datetime.cpp b/tutorials/cpp/datetime.cpp index 62b815ca8a5..9c756e66bc3 100644 --- a/tutorials/cpp/datetime.cpp +++ b/tutorials/cpp/datetime.cpp @@ -9,7 +9,8 @@ #include "ICU4XDataStruct.hpp" #include "ICU4XLogger.hpp" #include "ICU4XCustomTimeZone.hpp" -#include "ICU4XIanaToBcp47Mapper.hpp" +#include "ICU4XTimeZoneIdMapper.hpp" +#include "ICU4XTimeZoneIdMapperWithFastCanonicalization.hpp" #include "ICU4XBcp47ToIanaMapper.hpp" #include "ICU4XGregorianZonedDateTimeFormatter.hpp" #include "ICU4XZonedDateTimeFormatter.hpp" @@ -68,17 +69,32 @@ int main() { return 1; } ICU4XMetazoneCalculator mzcalc = ICU4XMetazoneCalculator::create(dp).ok().value(); - ICU4XIanaToBcp47Mapper mapper = ICU4XIanaToBcp47Mapper::create(dp).ok().value(); - time_zone.try_set_iana_time_zone_id(mapper, "america/chicago").ok().value(); + ICU4XTimeZoneIdMapper mapper = ICU4XTimeZoneIdMapper::create(dp).ok().value(); + time_zone.try_set_iana_time_zone_id_2(mapper, "america/chicago").ok().value(); std::string time_zone_id_return = time_zone.time_zone_id().ok().value(); if (time_zone_id_return != "uschi") { std::cout << "Time zone ID does not roundtrip: " << time_zone_id_return << std::endl; return 1; } - ICU4XBcp47ToIanaMapper reverse_mapper = ICU4XBcp47ToIanaMapper::create(dp).ok().value(); - std::string recovered_iana_id = reverse_mapper.get("uschi").ok().value(); - if (recovered_iana_id != "America/Chicago") { - std::cout << "Time zone ID does not canonicalize to IANA: " << recovered_iana_id << std::endl; + std::string normalized_iana_id = mapper.normalize_iana("America/CHICAGO").ok().value(); + if (normalized_iana_id != "America/Chicago") { + std::cout << "Time zone ID does not normalize: " << normalized_iana_id << std::endl; + return 1; + } + std::string canonicalied_iana_id = mapper.canonicalize_iana("Asia/Calcutta").ok().value(); + if (canonicalied_iana_id != "Asia/Kolkata") { + std::cout << "Time zone ID does not canonicalize: " << canonicalied_iana_id << std::endl; + return 1; + } + std::string slow_recovered_iana_id = mapper.find_canonical_iana_from_bcp47("uschi").ok().value(); + if (slow_recovered_iana_id != "America/Chicago") { + std::cout << "Time zone ID does not roundtrip (slow): " << slow_recovered_iana_id << std::endl; + return 1; + } + ICU4XTimeZoneIdMapperWithFastCanonicalization reverse_mapper = ICU4XTimeZoneIdMapperWithFastCanonicalization::create(dp).ok().value(); + std::string fast_recovered_iana_id = reverse_mapper.canonical_iana_from_bcp47("uschi").ok().value(); + if (fast_recovered_iana_id != "America/Chicago") { + std::cout << "Time zone ID does not roundtrip (fast): " << fast_recovered_iana_id << std::endl; return 1; } ICU4XIsoDateTime local_datetime = ICU4XIsoDateTime::create(2022, 8, 25, 0, 0, 0, 0).ok().value(); diff --git a/utils/zerotrie/src/cursor.rs b/utils/zerotrie/src/cursor.rs index 9a947422d60..71f668fd209 100644 --- a/utils/zerotrie/src/cursor.rs +++ b/utils/zerotrie/src/cursor.rs @@ -365,10 +365,9 @@ impl<'a> ZeroAsciiIgnoreCaseTrieCursor<'a> { /// let mut key_str = Cow::Borrowed("abc".as_bytes()); /// let mut i = 0; /// let value = loop { - /// if i == key_str.len() { + /// let Some(&input_byte) = key_str.get(i) else { /// break cursor.take_value(); - /// } - /// let input_byte = key_str[i]; + /// }; /// let Some(matched_byte) = cursor.step(input_byte) else { /// break None; /// };