diff --git a/Cargo.lock b/Cargo.lock index fffdaf504a0..d5a4a8c019b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1311,6 +1311,7 @@ dependencies = [ "icu_properties", "icu_segmenter", "icu_testdata", + "icu_timezone", "icu_uniset", ] @@ -1513,6 +1514,7 @@ dependencies = [ "icu_provider_fs", "icu_segmenter", "icu_testdata", + "icu_timezone", "icu_uniset", "itertools", "lazy_static", @@ -1553,6 +1555,7 @@ dependencies = [ "icu_provider", "icu_provider_adapters", "icu_testdata", + "icu_timezone", "litemap", "serde", "serde-tuple-vec-map", @@ -1848,6 +1851,7 @@ dependencies = [ "icu_provider_blob", "icu_provider_fs", "icu_segmenter", + "icu_timezone", "icu_uniset", "litemap", "log", @@ -1862,6 +1866,22 @@ dependencies = [ "zip 0.6.2", ] +[[package]] +name = "icu_timezone" +version = "0.6.0" +dependencies = [ + "databake", + "displaydoc", + "icu", + "icu_calendar", + "icu_locid", + "icu_provider", + "icu_testdata", + "serde", + "tinystr 0.6.0", + "zerovec", +] + [[package]] name = "icu_uniset" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index 4155b5965e1..151c24b05ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "components/normalizer", "components/plurals", "components/properties", + "components/timezone", "experimental/bies", "experimental/casemapping", "experimental/char16trie", diff --git a/components/calendar/src/error.rs b/components/calendar/src/error.rs index 1545ff1c9e0..1ff1242185c 100644 --- a/components/calendar/src/error.rs +++ b/components/calendar/src/error.rs @@ -32,9 +32,6 @@ pub enum DateTimeError { /// The minimum value min: isize, }, - /// The time zone offset was invalid. - #[displaydoc("Failed to parse time-zone offset")] - InvalidTimeZoneOffset, /// Out of range // TODO(Manishearth) turn this into a proper variant OutOfRange, diff --git a/components/calendar/src/types.rs b/components/calendar/src/types.rs index 0936ca2552c..977ffc2bde7 100644 --- a/components/calendar/src/types.rs +++ b/components/calendar/src/types.rs @@ -433,123 +433,6 @@ impl Time { } } -/// The GMT offset in seconds for a mock time zone -#[derive(Copy, Clone, Debug, Default)] -pub struct GmtOffset(i32); - -impl GmtOffset { - /// Attempt to create a [`GmtOffset`] from a seconds input. It returns an error when the seconds - /// overflows or underflows. - pub fn try_new(seconds: i32) -> Result { - // Valid range is from GMT-12 to GMT+14 in seconds. - if seconds < -(12 * 60 * 60) { - Err(DateTimeError::Underflow { - field: "GmtOffset", - min: -(12 * 60 * 60), - }) - } else if seconds > (14 * 60 * 60) { - Err(DateTimeError::Overflow { - field: "GmtOffset", - max: (14 * 60 * 60), - }) - } else { - Ok(Self(seconds)) - } - } - - /// Returns the raw offset value in seconds. - pub fn raw_offset_seconds(&self) -> i32 { - self.0 - } - - /// Returns `true` if the [`GmtOffset`] is positive, otherwise `false`. - pub fn is_positive(&self) -> bool { - self.0 >= 0 - } - - /// Returns `true` if the [`GmtOffset`] is zero, otherwise `false`. - pub fn is_zero(&self) -> bool { - self.0 == 0 - } - - /// Returns `true` if the [`GmtOffset`] has non-zero minutes, otherwise `false`. - pub fn has_minutes(&self) -> bool { - self.0 % 3600 / 60 > 0 - } - - /// Returns `true` if the [`GmtOffset`] has non-zero seconds, otherwise `false`. - pub fn has_seconds(&self) -> bool { - self.0 % 3600 % 60 > 0 - } -} - -impl FromStr for GmtOffset { - type Err = DateTimeError; - - /// Parse a [`GmtOffset`] from a string. - /// - /// The offset must range from GMT-12 to GMT+14. - /// The string must be an ISO 8601 time zone designator: - /// e.g. Z - /// e.g. +05 - /// e.g. +0500 - /// e.g. +05:00 - /// - /// # Examples - /// - /// ``` - /// use icu::datetime::date::GmtOffset; - /// - /// let offset0: GmtOffset = "Z".parse().expect("Failed to parse a GMT offset."); - /// let offset1: GmtOffset = "-09".parse().expect("Failed to parse a GMT offset."); - /// let offset2: GmtOffset = "-0930".parse().expect("Failed to parse a GMT offset."); - /// let offset3: GmtOffset = "-09:30".parse().expect("Failed to parse a GMT offset."); - /// ``` - fn from_str(input: &str) -> Result { - let offset_sign = match input.chars().next() { - Some('+') => 1, - /* ASCII */ Some('-') => -1, - /* U+2212 */ Some('−') => -1, - Some('Z') => return Ok(Self(0)), - _ => return Err(DateTimeError::InvalidTimeZoneOffset), - }; - - let seconds = match input.chars().count() { - /* ±hh */ - 3 => { - #[allow(clippy::indexing_slicing)] - // TODO(#1668) Clippy exceptions need docs or fixing. - let hour: u8 = input[1..3].parse()?; - offset_sign * (hour as i32 * 60 * 60) - } - /* ±hhmm */ - 5 => { - #[allow(clippy::indexing_slicing)] - // TODO(#1668) Clippy exceptions need docs or fixing. - let hour: u8 = input[1..3].parse()?; - #[allow(clippy::indexing_slicing)] - // TODO(#1668) Clippy exceptions need docs or fixing. - let minute: u8 = input[3..5].parse()?; - offset_sign * (hour as i32 * 60 * 60 + minute as i32 * 60) - } - /* ±hh:mm */ - 6 => { - #[allow(clippy::indexing_slicing)] - // TODO(#1668) Clippy exceptions need docs or fixing. - let hour: u8 = input[1..3].parse()?; - #[allow(clippy::indexing_slicing)] - // TODO(#1668) Clippy exceptions need docs or fixing. - let minute: u8 = input[4..6].parse()?; - offset_sign * (hour as i32 * 60 * 60 + minute as i32 * 60) - } - #[allow(clippy::panic)] // TODO(#1668) Clippy exceptions need docs or fixing. - _ => panic!("Invalid time-zone designator"), - }; - - Self::try_new(seconds) - } -} - /// A weekday in a 7-day week, according to ISO-8601. /// /// The discriminant values correspond to ISO-8601 weekday numbers (Monday = 1, Sunday = 7). diff --git a/components/datetime/Cargo.toml b/components/datetime/Cargo.toml index bdf3275634f..f504226848b 100644 --- a/components/datetime/Cargo.toml +++ b/components/datetime/Cargo.toml @@ -37,6 +37,7 @@ icu_locid = { version = "0.6", path = "../locid" } icu_plurals = { version = "0.6", path = "../plurals" } icu_provider = { version = "0.6", path = "../../provider/core", features = ["macros"] } icu_calendar = { version = "0.6", path = "../calendar" } +icu_timezone = { version = "0.6", path = "../timezone" } writeable = { version = "0.4", path = "../../utils/writeable" } litemap = { version = "0.4.0", path = "../../utils/litemap" } tinystr = { path = "../../utils/tinystr", version = "0.6.0", features = ["alloc", "zerovec"], default-features = false } @@ -51,7 +52,7 @@ fixed_decimal = { version = "0.3", path = "../../utils/fixed_decimal" } [dev-dependencies] criterion = "0.3" -icu = { path = "../icu", default-features = false } +icu = { path = "../icu", default-features = false, features = ["experimental"] } icu_benchmark_macros = { version = "0.6", path = "../../tools/benchmark/macros" } icu_provider = { version = "0.6", path = "../../provider/core" } icu_provider_adapters = { path = "../../provider/adapters" } @@ -71,8 +72,8 @@ bench = false # This option is required for Benchmark CI std = ["icu_provider/std", "icu_locid/std", "icu_calendar/std"] default = [] bench = ["serde"] -serde = ["dep:serde", "litemap/serde", "zerovec/serde", "tinystr/serde", "smallvec/serde", "icu_calendar/serde", "icu_decimal/serde", "icu_provider/serde", "icu_plurals/serde"] -datagen = ["serde", "icu_calendar/datagen", "icu_provider/datagen", "std", "databake"] +serde = ["dep:serde", "litemap/serde", "zerovec/serde", "tinystr/serde", "smallvec/serde", "icu_calendar/serde", "icu_decimal/serde", "icu_provider/serde", "icu_plurals/serde", "icu_timezone/serde"] +datagen = ["serde", "icu_calendar/datagen", "icu_timezone/datagen", "icu_provider/datagen", "std", "databake"] [[bench]] name = "datetime" diff --git a/components/datetime/benches/datetime.rs b/components/datetime/benches/datetime.rs index 2b6b56a0808..67e881ebcb5 100644 --- a/components/datetime/benches/datetime.rs +++ b/components/datetime/benches/datetime.rs @@ -10,11 +10,12 @@ use std::fmt::Write; use icu_calendar::{DateTime, Gregorian}; use icu_datetime::DateTimeFormatter; use icu_datetime::{ - mock::{parse_gregorian_from_str, parse_zoned_gregorian_from_str, time_zone::MockTimeZone}, + mock::{parse_gregorian_from_str, parse_zoned_gregorian_from_str}, time_zone::TimeZoneFormatterOptions, ZonedDateTimeFormatter, }; use icu_locid::Locale; +use icu_timezone::CustomTimeZone; fn datetime_benches(c: &mut Criterion) { let provider = icu_testdata::get_provider(); @@ -63,7 +64,7 @@ fn datetime_benches(c: &mut Criterion) { group.bench_function("zoned_datetime_overview", |b| { b.iter(|| { for fx in &fxs.0 { - let datetimes: Vec<(DateTime, MockTimeZone)> = fx + let datetimes: Vec<(DateTime, CustomTimeZone)> = fx .values .iter() .map(|value| parse_zoned_gregorian_from_str(value).unwrap()) @@ -221,7 +222,7 @@ fn datetime_benches(c: &mut Criterion) { group.bench_function("ZonedDateTimeFormatter/format_to_write", |b| { b.iter(|| { for fx in &fxs.0 { - let datetimes: Vec<(DateTime, MockTimeZone)> = fx + let datetimes: Vec<(DateTime, CustomTimeZone)> = fx .values .iter() .map(|value| parse_zoned_gregorian_from_str(value).unwrap()) @@ -255,7 +256,7 @@ fn datetime_benches(c: &mut Criterion) { group.bench_function("ZonedDateTimeFormatter/format_to_string", |b| { b.iter(|| { for fx in &fxs.0 { - let datetimes: Vec<(DateTime, MockTimeZone)> = fx + let datetimes: Vec<(DateTime, CustomTimeZone)> = fx .values .iter() .map(|value| parse_zoned_gregorian_from_str(value).unwrap()) @@ -286,7 +287,7 @@ fn datetime_benches(c: &mut Criterion) { group.bench_function("FormattedZonedDateTime/format", |b| { b.iter(|| { for fx in &fxs.0 { - let datetimes: Vec<(DateTime, MockTimeZone)> = fx + let datetimes: Vec<(DateTime, CustomTimeZone)> = fx .values .iter() .map(|value| parse_zoned_gregorian_from_str(value).unwrap()) @@ -321,7 +322,7 @@ fn datetime_benches(c: &mut Criterion) { group.bench_function("FormattedZonedDateTime/to_string", |b| { b.iter(|| { for fx in &fxs.0 { - let datetimes: Vec<(DateTime, MockTimeZone)> = fx + let datetimes: Vec<(DateTime, CustomTimeZone)> = fx .values .iter() .map(|value| parse_zoned_gregorian_from_str(value).unwrap()) diff --git a/components/datetime/src/any/zoned_datetime.rs b/components/datetime/src/any/zoned_datetime.rs index 1cbdeb8f11a..f083012e011 100644 --- a/components/datetime/src/any/zoned_datetime.rs +++ b/components/datetime/src/any/zoned_datetime.rs @@ -47,7 +47,7 @@ use icu_plurals::provider::OrdinalV1Marker; /// /// ``` /// use icu::calendar::{DateTime, Gregorian}; -/// use icu::datetime::mock::time_zone::MockTimeZone; +/// use icu::timezone::CustomTimeZone; /// use icu::datetime::{options::length, any::ZonedAnyDateTimeFormatter}; /// use icu::locid::locale; /// use icu_datetime::TimeZoneFormatterOptions; @@ -67,7 +67,7 @@ use icu_plurals::provider::OrdinalV1Marker; /// .expect("Failed to construct DateTime."); /// let any_datetime = datetime.to_any(); /// -/// let time_zone: MockTimeZone = "+05:00".parse().expect("Timezone should parse"); +/// let time_zone: CustomTimeZone = "+05:00".parse().expect("Timezone should parse"); /// /// let value = zdtf.format_to_string(&any_datetime, &time_zone).expect("calendars should match"); /// diff --git a/components/datetime/src/date.rs b/components/datetime/src/date.rs index 1340878b5d7..3c2e21ddf11 100644 --- a/components/datetime/src/date.rs +++ b/components/datetime/src/date.rs @@ -10,6 +10,7 @@ use icu_calendar::any_calendar::AnyCalendarKind; use icu_calendar::Calendar; use icu_calendar::{arithmetic::week_of, AsCalendar, Date, DateTime, Iso}; use icu_provider::DataLocale; +use icu_timezone::{CustomTimeZone, GmtOffset}; use tinystr::TinyStr8; // TODO (Manishearth) fix up imports to directly import from icu_calendar @@ -465,3 +466,21 @@ impl IsoTimeInput for DateTime { Some(self.time.nanosecond) } } + +impl TimeZoneInput for CustomTimeZone { + fn gmt_offset(&self) -> Option { + self.gmt_offset + } + + fn time_zone_id(&self) -> Option { + self.time_zone_id + } + + fn metazone_id(&self) -> Option { + self.metazone_id + } + + fn time_variant(&self) -> Option { + self.time_variant + } +} diff --git a/components/datetime/src/lib.rs b/components/datetime/src/lib.rs index 9f77bbdbe05..581ecef2951 100644 --- a/components/datetime/src/lib.rs +++ b/components/datetime/src/lib.rs @@ -89,8 +89,6 @@ pub mod datetime; mod error; pub mod fields; mod format; -/// A module for metazone id calculation. -pub mod metazone; pub mod mock; pub mod options; #[doc(hidden)] diff --git a/components/datetime/src/mock/mod.rs b/components/datetime/src/mock.rs similarity index 91% rename from components/datetime/src/mock/mod.rs rename to components/datetime/src/mock.rs index 6aa3748c4b0..e6d65d0497c 100644 --- a/components/datetime/src/mock/mod.rs +++ b/components/datetime/src/mock.rs @@ -6,11 +6,9 @@ //! and examples. use core::str::FromStr; +use either::Either; use icu_calendar::{DateTime, DateTimeError, Gregorian}; -use time_zone::MockTimeZone; - -/// Temporary time zone input utilities. -pub mod time_zone; +use icu_timezone::{CustomTimeZone, TimeZoneError}; /// Temporary function for parsing a `DateTime` /// @@ -68,7 +66,7 @@ pub fn parse_gregorian_from_str(input: &str) -> Result, Date Ok(datetime) } -/// Parse a [`DateTime`] and [`MockTimeZone`] from a string. +/// Parse a [`DateTime`] and [`CustomTimeZone`] from a string. /// /// This utility is for easily creating dates, not a complete robust solution. The /// string must take a specific form of the ISO 8601 format: @@ -87,14 +85,14 @@ pub fn parse_gregorian_from_str(input: &str) -> Result, Date /// ``` pub fn parse_zoned_gregorian_from_str( input: &str, -) -> Result<(DateTime, MockTimeZone), DateTimeError> { - let datetime = parse_gregorian_from_str(input)?; +) -> Result<(DateTime, CustomTimeZone), Either> { + let datetime = parse_gregorian_from_str(input).map_err(Either::Left)?; let time_zone = match input .rfind(|c| c == '+' || /* ASCII */ c == '-' || /* U+2212 */ c == '−' || c == 'Z') { #[allow(clippy::indexing_slicing)] // TODO(#1668) Clippy exceptions need docs or fixing. - Some(index) => FromStr::from_str(&input[index..])?, - None => return Err(DateTimeError::InvalidTimeZoneOffset), + Some(index) => FromStr::from_str(&input[index..]).map_err(Either::Right)?, + None => return Err(Either::Right(TimeZoneError::InvalidOffset)), }; Ok((datetime, time_zone)) diff --git a/components/datetime/src/provider/time_zones.rs b/components/datetime/src/provider/time_zones.rs index cbdca3c612b..de46c4861a9 100644 --- a/components/datetime/src/provider/time_zones.rs +++ b/components/datetime/src/provider/time_zones.rs @@ -4,9 +4,10 @@ use alloc::borrow::Cow; use icu_provider::{yoke, zerofrom}; -use tinystr::{TinyAsciiStr, TinyStr8}; -use zerovec::ule::{AsULE, ULE}; -use zerovec::{ZeroMap, ZeroMap2d, ZeroSlice, ZeroVec}; +use tinystr::TinyStr8; +use zerovec::{ZeroMap, ZeroMap2d}; + +pub use icu_timezone::provider::{MetaZoneId, TimeZoneBcp47Id}; /// An ICU4X mapping to the CLDR timeZoneNames format strings. /// See CLDR-JSON timeZoneNames.json for more context. @@ -48,62 +49,6 @@ pub struct TimeZoneFormatsV1<'data> { pub gmt_offset_fallback: Cow<'data, str>, } -/// TimeZone ID in BCP47 format -#[repr(transparent)] -#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd, yoke::Yokeable, ULE, Hash)] -#[cfg_attr(feature = "datagen", derive(serde::Serialize))] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -pub struct TimeZoneBcp47Id(pub TinyAsciiStr<8>); - -impl AsULE for TimeZoneBcp47Id { - type ULE = Self; - - #[inline] - fn to_unaligned(self) -> Self::ULE { - self - } - - #[inline] - fn from_unaligned(unaligned: Self::ULE) -> Self { - unaligned - } -} - -impl<'a> zerovec::maps::ZeroMapKV<'a> for TimeZoneBcp47Id { - type Container = ZeroVec<'a, TimeZoneBcp47Id>; - type Slice = ZeroSlice; - type GetType = TimeZoneBcp47Id; - type OwnedType = TimeZoneBcp47Id; -} - -/// MetaZone ID in a compact format -#[repr(transparent)] -#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd, yoke::Yokeable, ULE, Hash)] -#[cfg_attr(feature = "datagen", derive(serde::Serialize))] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -pub struct MetaZoneId(pub TinyAsciiStr<4>); - -impl AsULE for MetaZoneId { - type ULE = Self; - - #[inline] - fn to_unaligned(self) -> Self::ULE { - self - } - - #[inline] - fn from_unaligned(unaligned: Self::ULE) -> Self { - unaligned - } -} - -impl<'a> zerovec::maps::ZeroMapKV<'a> for MetaZoneId { - type Container = ZeroVec<'a, MetaZoneId>; - type Slice = ZeroSlice; - type GetType = MetaZoneId; - type OwnedType = MetaZoneId; -} - /// An ICU4X mapping to the CLDR timeZoneNames exemplar cities. /// See CLDR-JSON timeZoneNames.json for more context. #[icu_provider::data_struct(ExemplarCitiesV1Marker = "time_zone/exemplar_cities@1")] @@ -200,20 +145,3 @@ pub struct MetaZoneSpecificNamesShortV1<'data> { #[cfg_attr(feature = "serde", serde(borrow))] pub overrides: ZeroMap2d<'data, TimeZoneBcp47Id, TinyStr8, str>, } - -/// An ICU4X mapping to the metazones at a given period. -/// See CLDR-JSON metaZones.json for more context. -#[icu_provider::data_struct(MetaZonePeriodV1Marker = "time_zone/metazone_period@1")] -#[derive(PartialEq, Debug, Clone, Default)] -#[cfg_attr( - feature = "datagen", - derive(serde::Serialize, databake::Bake), - databake(path = icu_datetime::provider::time_zones), -)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -#[yoke(prove_covariance_manually)] -pub struct MetaZonePeriodV1<'data>( - /// The default mapping between period and metazone id. The second level key is a wall-clock time represented as the number of minutes since the local unix epoch. It represents when the metazone started to be used. - #[cfg_attr(feature = "serde", serde(borrow))] - pub ZeroMap2d<'data, TimeZoneBcp47Id, i32, Option>, -); diff --git a/components/datetime/src/time_zone.rs b/components/datetime/src/time_zone.rs index 159753dab21..08afa8ccba6 100644 --- a/components/datetime/src/time_zone.rs +++ b/components/datetime/src/time_zone.rs @@ -59,8 +59,7 @@ where /// # Examples /// /// ``` -/// use icu_datetime::date::GmtOffset; -/// use icu_datetime::mock::time_zone::MockTimeZone; +/// use icu::timezone::{GmtOffset, CustomTimeZone}; /// use icu_datetime::{TimeZoneFormatter, TimeZoneFormatterConfig, TimeZoneFormatterOptions}; /// use icu_locid::locale; /// @@ -74,7 +73,7 @@ where /// ) /// .expect("Failed to create TimeZoneFormatter"); /// -/// let time_zone = MockTimeZone::new(Some(GmtOffset::default()), None, None, None); +/// let time_zone = CustomTimeZone::new(Some(GmtOffset::default()), None, None, None); /// /// let value = tzf.format_to_string(&time_zone); /// ``` @@ -87,7 +86,7 @@ pub struct TimeZoneFormatter { pub(super) fallback_unit: TimeZoneFormatterUnit, } -/// A container contains all data payloads for TimeZone. +/// A container contains all data payloads for CustomTimeZone. pub struct TimeZoneDataPayloads { /// The data that contains meta information about how to display content. pub(super) zone_formats: DataPayload, @@ -345,7 +344,7 @@ impl TimeZoneFormatter { /// # Examples /// /// ``` - /// use icu_datetime::mock::time_zone::MockTimeZone; + /// use icu::timezone::CustomTimeZone; /// use icu_datetime::{TimeZoneFormatter, TimeZoneFormatterConfig, TimeZoneFormatterOptions}; /// use icu_locid::locale; /// @@ -603,8 +602,7 @@ impl TimeZoneFormatter { /// # Examples /// /// ``` - /// use icu_datetime::date::GmtOffset; - /// use icu_datetime::mock::time_zone::MockTimeZone; + /// use icu::timezone::{GmtOffset, CustomTimeZone}; /// use icu_datetime::{TimeZoneFormatter, TimeZoneFormatterConfig, TimeZoneFormatterOptions}; /// use icu_locid::locale; /// @@ -618,7 +616,7 @@ impl TimeZoneFormatter { /// ) /// .expect("Failed to create TimeZoneFormatter"); /// - /// let time_zone = MockTimeZone::new(Some(GmtOffset::default()), None, None, None); + /// let time_zone = CustomTimeZone::new(Some(GmtOffset::default()), None, None, None); /// /// let _ = tzf.format(&time_zone); /// ``` @@ -638,8 +636,7 @@ impl TimeZoneFormatter { /// # Examples /// /// ``` - /// use icu_datetime::date::GmtOffset; - /// use icu_datetime::mock::time_zone::MockTimeZone; + /// use icu::timezone::{GmtOffset, CustomTimeZone}; /// use icu_datetime::{TimeZoneFormatter, TimeZoneFormatterConfig, TimeZoneFormatterOptions}; /// use icu_locid::locale; /// @@ -653,7 +650,7 @@ impl TimeZoneFormatter { /// ) /// .expect("Failed to create TimeZoneFormatter"); /// - /// let time_zone = MockTimeZone::new(Some(GmtOffset::default()), None, None, None); + /// let time_zone = CustomTimeZone::new(Some(GmtOffset::default()), None, None, None); /// /// let mut buffer = String::new(); /// tzf.format_to_write(&mut buffer, &time_zone) @@ -674,8 +671,7 @@ impl TimeZoneFormatter { /// # Examples /// /// ``` - /// use icu_datetime::date::GmtOffset; - /// use icu_datetime::mock::time_zone::MockTimeZone; + /// use icu::timezone::{GmtOffset, CustomTimeZone}; /// use icu_datetime::{TimeZoneFormatter, TimeZoneFormatterConfig, TimeZoneFormatterOptions}; /// use icu_locid::locale; /// @@ -689,7 +685,7 @@ impl TimeZoneFormatter { /// ) /// .expect("Failed to create TimeZoneFormatter"); /// - /// let time_zone = MockTimeZone::new(Some(GmtOffset::default()), None, None, None); + /// let time_zone = CustomTimeZone::new(Some(GmtOffset::default()), None, None, None); /// /// let _ = tzf.format_to_string(&time_zone); /// ``` @@ -761,7 +757,7 @@ impl TimeZoneFormatter { } } -/// Determines which ISO-8601 format should be used to format a [`GmtOffset`](crate::date::GmtOffset). +/// Determines which ISO-8601 format should be used to format a [`GmtOffset`](icu_timezone::GmtOffset). #[derive(Debug, Clone, Copy, PartialEq)] #[allow(clippy::exhaustive_enums)] // this type is stable pub enum IsoFormat { diff --git a/components/datetime/tests/datetime.rs b/components/datetime/tests/datetime.rs index e8f282b018c..25867b21455 100644 --- a/components/datetime/tests/datetime.rs +++ b/components/datetime/tests/datetime.rs @@ -17,7 +17,6 @@ use icu_calendar::{ provider::JapaneseErasV1Marker, AsCalendar, DateTime, Gregorian, Iso, }; -use icu_datetime::mock::time_zone::MockTimeZone; use icu_datetime::provider::time_zones::{MetaZoneId, TimeZoneBcp47Id}; use icu_datetime::time_zone::TimeZoneFormatterConfig; use icu_datetime::{ @@ -43,6 +42,7 @@ use icu_plurals::provider::OrdinalV1Marker; use icu_provider::prelude::*; use icu_provider_adapters::any_payload::AnyPayloadProvider; use icu_provider_adapters::fork::MultiForkByKeyProvider; +use icu_timezone::CustomTimeZone; use patterns::{ get_dayperiod_tests, get_time_zone_tests, structs::{ @@ -523,7 +523,7 @@ fn test_time_zone_format_configs() { fn test_time_zone_format_gmt_offset_not_set_debug_assert_panic() { let zone_provider = icu_testdata::get_provider(); let langid: LanguageIdentifier = "en".parse().unwrap(); - let time_zone = MockTimeZone::new( + let time_zone = CustomTimeZone::new( None, Some(TimeZoneBcp47Id(tinystr!(8, "uslax"))), Some(MetaZoneId(tinystr!(4, "ampa"))), diff --git a/components/icu/Cargo.toml b/components/icu/Cargo.toml index 5e37425edae..d9355c20959 100644 --- a/components/icu/Cargo.toml +++ b/components/icu/Cargo.toml @@ -37,6 +37,7 @@ icu_locid = { version = "0.6", path = "../locid", default-features = false } icu_normalizer = { version = "0.6", path = "../normalizer", default-features = false } icu_plurals = { version = "0.6", path = "../plurals", default-features = false } icu_properties = { version = "0.6", path = "../properties", default-features = false } +icu_timezone = { version = "0.6", path = "../timezone", default-features = false, optional = true } icu_segmenter = { version = "0.6", path = "../../experimental/segmenter", default-features = false, optional = true } @@ -69,7 +70,7 @@ serde = [ serde_human = [ "icu_list/serde_human" ] -experimental = ["icu_segmenter"] +experimental = ["icu_segmenter", "icu_timezone"] [[example]] name = "tui" diff --git a/components/icu/src/lib.rs b/components/icu/src/lib.rs index 65bae61657f..118ac300bbb 100644 --- a/components/icu/src/lib.rs +++ b/components/icu/src/lib.rs @@ -136,3 +136,7 @@ pub use icu_properties as properties; #[cfg(feature = "experimental")] #[doc(inline)] pub use icu_segmenter as segmenter; + +#[cfg(feature = "experimental")] +#[doc(inline)] +pub use icu_timezone as timezone; diff --git a/components/timezone/Cargo.toml b/components/timezone/Cargo.toml new file mode 100644 index 00000000000..10933e77c22 --- /dev/null +++ b/components/timezone/Cargo.toml @@ -0,0 +1,52 @@ +# 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 ). + +[package] +name = "icu_timezone" +description = "API for resolving and manipulating time zone information" +version = "0.6.0" +authors = ["The ICU4X Project Developers"] +edition = "2018" +readme = "README.md" +repository = "https://github.com/unicode-org/icu4x" +license-file = "LICENSE" +categories = ["internationalization"] +# Keep this in sync with other crates unless there are exceptions +include = [ + "src/**/*", + "examples/**/*", + "benches/**/*", + "tests/**/*", + "Cargo.toml", + "LICENSE", + "README.md" +] + +[features] +std = [] +bench = [] +serde = ["dep:serde", "zerovec/serde", "tinystr/serde", "icu_provider/serde"] +datagen = ["serde", "databake", "zerovec/databake", "tinystr/databake"] + +[package.metadata.docs.rs] +all-features = true + +[package.metadata.cargo-all-features] +skip_optional_dependencies = true +# Bench feature gets tested separately and is only relevant for CI +denylist = ["bench"] + +[dependencies] +displaydoc = { version = "0.2.3", default-features = false } +tinystr = { path = "../../utils/tinystr", version = "0.6.0", features = ["alloc", "zerovec"], default-features = false } +icu_provider = { version = "0.6", path = "../../provider/core", features = ["macros"] } +icu_locid = { version = "0.6", path = "../../components/locid" } +icu_calendar = { version = "0.6", path = "../../components/calendar" } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"], optional = true } +zerovec = { version = "0.7", path = "../../utils/zerovec", default-features = false, features = ["derive", "yoke"] } +databake = { version = "0.1.0", path = "../../utils/databake", optional = true, features = ["derive"] } + +[dev-dependencies] +icu = { path = "../../components/icu", default-features = false, features = ["experimental"] } +icu_testdata = { version = "0.6", path = "../../provider/testdata" } diff --git a/components/timezone/LICENSE b/components/timezone/LICENSE new file mode 100644 index 00000000000..5ab1f57507b --- /dev/null +++ b/components/timezone/LICENSE @@ -0,0 +1,331 @@ +Except as otherwise noted below, ICU4X is licensed under the Apache +License, Version 2.0 (included below) or the MIT license (included +below), at your option. Unless importing data or code in the manner +stated below, any contribution intentionally submitted for inclusion +in ICU4X by you, as defined in the Apache-2.0 license, shall be dual +licensed in the foregoing manner, without any additional terms or +conditions. + +As exceptions to the above: +* Portions of ICU4X that have been adapted from ICU4C and/or ICU4J are +under the Unicode license (included below) and/or the ICU license +(included below) as indicated by source code comments. +* Unicode data incorporated in ICU4X is under the Unicode license +(included below). +* Your contributions may import code from ICU4C and/or ICU4J and +Unicode data under these licenses. Indicate the license and the ICU4C +or ICU4J origin in source code comments. + +- - - - + +Apache License, version 2.0 + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +- - - - + +MIT License + +Copyright The ICU4X Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +- - - - + +Unicode License + +COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later) + +Copyright © 1991-2020 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in https://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. + +- - - - + +ICU License - ICU 1.8.1 to ICU 57.1 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1995-2016 International Business Machines Corporation and others +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, and/or sell copies of the Software, and to permit persons +to whom the Software is furnished to do so, provided that the above +copyright notice(s) and this permission notice appear in all copies of +the Software and that both the above copyright notice(s) and this +permission notice appear in supporting documentation. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY +SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, use +or other dealings in this Software without prior written authorization +of the copyright holder. + +All trademarks and registered trademarks mentioned herein are the +property of their respective owners. + +- - - - diff --git a/components/timezone/README.md b/components/timezone/README.md new file mode 100644 index 00000000000..66750211edc --- /dev/null +++ b/components/timezone/README.md @@ -0,0 +1,7 @@ +# icu_timezone [![crates.io](https://img.shields.io/crates/v/icu_timezone)](https://crates.io/crates/icu_timezone) + +Types for resolving and manipulating timezones + +## More Information + +For more information on development, authorship, contributing etc. please visit [`ICU4X home page`](https://github.com/unicode-org/icu4x). diff --git a/components/timezone/src/error.rs b/components/timezone/src/error.rs new file mode 100644 index 00000000000..5563d64b0a0 --- /dev/null +++ b/components/timezone/src/error.rs @@ -0,0 +1,31 @@ +// 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 displaydoc::Display; +use icu_provider::prelude::DataError; + +#[cfg(feature = "std")] +impl std::error::Error for TimeZoneError {} + +/// A list of possible error outcomes for working with types in this crate +/// and operations. +#[derive(Display, Debug, Copy, Clone, PartialEq)] +#[non_exhaustive] +pub enum TimeZoneError { + /// An input overflowed its range. + #[displaydoc("GmtOffset must be between (-12 × 60 × 60) - (14 × 60 × 60)")] + OffsetOutOfBounds, + /// The time zone offset was invalid. + #[displaydoc("Failed to parse time-zone offset")] + InvalidOffset, + /// An error originating inside of the [data provider](icu_provider). + #[displaydoc("{0}")] + DataProvider(DataError), +} + +impl From for TimeZoneError { + fn from(e: DataError) -> Self { + TimeZoneError::DataProvider(e) + } +} diff --git a/components/timezone/src/lib.rs b/components/timezone/src/lib.rs new file mode 100644 index 00000000000..52bd7f1f85e --- /dev/null +++ b/components/timezone/src/lib.rs @@ -0,0 +1,32 @@ +// 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 ). + +//! Types for resolving and manipulating timezones + +// https://github.com/unicode-org/icu4x/blob/main/docs/process/boilerplate.md#library-annotations +#![cfg_attr(not(any(test, feature = "std")), no_std)] +#![cfg_attr( + not(test), + deny( + clippy::indexing_slicing, + clippy::unwrap_used, + clippy::expect_used, + clippy::panic, + clippy::exhaustive_structs, + clippy::exhaustive_enums + ) +)] + +extern crate alloc; + +mod error; +mod metazone; +pub mod provider; +mod time_zone; +mod types; + +pub use error::TimeZoneError; +pub use metazone::MetaZoneCalculator; +pub use time_zone::CustomTimeZone; +pub use types::GmtOffset; diff --git a/components/datetime/src/metazone.rs b/components/timezone/src/metazone.rs similarity index 88% rename from components/datetime/src/metazone.rs rename to components/timezone/src/metazone.rs index b9d6bb30f08..d5a084c6750 100644 --- a/components/datetime/src/metazone.rs +++ b/components/timezone/src/metazone.rs @@ -2,10 +2,10 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -use crate::provider::time_zones::{MetaZoneId, TimeZoneBcp47Id}; +use crate::provider::{MetaZoneId, TimeZoneBcp47Id}; -use crate::error::DateTimeFormatterError; -use crate::provider::time_zones::MetaZonePeriodV1Marker; +use crate::error::TimeZoneError; +use crate::provider::MetaZonePeriodV1Marker; use icu_calendar::DateTime; use icu_calendar::Iso; use icu_provider::prelude::*; @@ -25,14 +25,14 @@ impl MetaZoneCalculator { /// /// ``` /// use icu_locid::locale; - /// use icu::datetime::metazone::MetaZoneCalculator; + /// use icu::timezone::MetaZoneCalculator; /// /// let provider = icu_testdata::get_provider(); /// let mzc = MetaZoneCalculator::try_new(&provider); /// /// assert!(mzc.is_ok()); /// ``` - pub fn try_new

(zone_provider: &P) -> Result + pub fn try_new

(zone_provider: &P) -> Result where P: DataProvider + ?Sized, { @@ -50,9 +50,9 @@ impl MetaZoneCalculator { /// # Examples /// /// ``` - /// use icu::datetime::metazone::MetaZoneCalculator; + /// use icu::timezone::MetaZoneCalculator; /// use icu_calendar::DateTime; - /// use icu_datetime::provider::time_zones::{MetaZoneId, TimeZoneBcp47Id}; + /// use icu::timezone::provider::{MetaZoneId, TimeZoneBcp47Id}; /// use icu_locid::locale; /// use tinystr::tinystr; /// diff --git a/components/timezone/src/provider.rs b/components/timezone/src/provider.rs new file mode 100644 index 00000000000..8ff9be29f44 --- /dev/null +++ b/components/timezone/src/provider.rs @@ -0,0 +1,88 @@ +// 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 ). + +// Provider structs must be stable +#![allow(clippy::exhaustive_structs)] + +//! Data provider struct definitions for this ICU4X component. +//! +//! Read more about data providers: [`icu_provider`] + +use icu_provider::{yoke, zerofrom}; +use tinystr::TinyAsciiStr; +use zerovec::ule::{AsULE, ULE}; +use zerovec::{ZeroMap2d, ZeroSlice, ZeroVec}; + +/// TimeZone ID in BCP47 format +#[repr(transparent)] +#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd, yoke::Yokeable, ULE, Hash)] +#[cfg_attr(feature = "datagen", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +pub struct TimeZoneBcp47Id(pub TinyAsciiStr<8>); + +impl AsULE for TimeZoneBcp47Id { + type ULE = Self; + + #[inline] + fn to_unaligned(self) -> Self::ULE { + self + } + + #[inline] + fn from_unaligned(unaligned: Self::ULE) -> Self { + unaligned + } +} + +impl<'a> zerovec::maps::ZeroMapKV<'a> for TimeZoneBcp47Id { + type Container = ZeroVec<'a, TimeZoneBcp47Id>; + type Slice = ZeroSlice; + type GetType = TimeZoneBcp47Id; + type OwnedType = TimeZoneBcp47Id; +} + +/// MetaZone ID in a compact format +#[repr(transparent)] +#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd, yoke::Yokeable, ULE, Hash)] +#[cfg_attr(feature = "datagen", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +pub struct MetaZoneId(pub TinyAsciiStr<4>); + +impl AsULE for MetaZoneId { + type ULE = Self; + + #[inline] + fn to_unaligned(self) -> Self::ULE { + self + } + + #[inline] + fn from_unaligned(unaligned: Self::ULE) -> Self { + unaligned + } +} + +impl<'a> zerovec::maps::ZeroMapKV<'a> for MetaZoneId { + type Container = ZeroVec<'a, MetaZoneId>; + type Slice = ZeroSlice; + type GetType = MetaZoneId; + type OwnedType = MetaZoneId; +} + +/// An ICU4X mapping to the metazones at a given period. +/// See CLDR-JSON metaZones.json for more context. +#[icu_provider::data_struct(MetaZonePeriodV1Marker = "time_zone/metazone_period@1")] +#[derive(PartialEq, Debug, Clone, Default)] +#[cfg_attr( + feature = "datagen", + derive(serde::Serialize, databake::Bake), + databake(path = icu_timezone::provider), +)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[yoke(prove_covariance_manually)] +pub struct MetaZonePeriodV1<'data>( + /// The default mapping between period and metazone id. The second level key is a wall-clock time represented as the number of minutes since the local unix epoch. It represents when the metazone started to be used. + #[cfg_attr(feature = "serde", serde(borrow))] + pub ZeroMap2d<'data, TimeZoneBcp47Id, i32, Option>, +); diff --git a/components/datetime/src/mock/time_zone.rs b/components/timezone/src/time_zone.rs similarity index 61% rename from components/datetime/src/mock/time_zone.rs rename to components/timezone/src/time_zone.rs index fa60c68e416..4c8d077e815 100644 --- a/components/datetime/src/mock/time_zone.rs +++ b/components/timezone/src/time_zone.rs @@ -2,41 +2,33 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). -use crate::provider::time_zones::{MetaZoneId, TimeZoneBcp47Id}; +use crate::provider::{MetaZoneId, TimeZoneBcp47Id}; use tinystr::TinyStr8; -use crate::date::*; use crate::metazone::MetaZoneCalculator; +use crate::{GmtOffset, TimeZoneError}; use core::str::FromStr; use icu_calendar::{DateTime, Iso}; -/// A temporary struct that implements [`TimeZoneInput`] -/// and is used in tests, benchmarks and examples of this component. -/// -/// *Notice:* Rust at the moment does not have a canonical way to represent time zones. We are introducing -/// [`MockTimeZone`](crate::mock::time_zone::MockTimeZone) as an example of the data necessary for -/// ICU [`TimeZoneFormatter`](crate::TimeZoneFormatter) to work, and [we hope to work with the community]( -/// https://github.com/unicode-org/icu4x/blob/main/docs/research/datetime.md) to develop core date and time -/// APIs that will work as an input for this component. +/// A utility type that can hold time zone information /// /// # Examples /// /// ``` -/// use icu::datetime::date::GmtOffset; -/// use icu::datetime::mock::time_zone::MockTimeZone; +/// use icu::timezone::{GmtOffset, CustomTimeZone}; /// -/// let tz1 = MockTimeZone::new( +/// let tz1 = CustomTimeZone::new( /// Some(GmtOffset::default()), /// /* time_zone_id */ None, /// /* metazone_id */ None, /// /* time_variaint */ None, /// ); /// -/// let tz2: MockTimeZone = "+05:00".parse().expect("Failed to parse a time zone."); +/// let tz2: CustomTimeZone = "+05:00".parse().expect("Failed to parse a time zone."); /// ``` #[derive(Debug, Default)] #[allow(clippy::exhaustive_structs)] // this type will not add fields (it is largely an example type) -pub struct MockTimeZone { +pub struct CustomTimeZone { /// The GMT offset in seconds. pub gmt_offset: Option, /// The IANA time-zone identifier @@ -47,8 +39,8 @@ pub struct MockTimeZone { pub time_variant: Option, } -impl MockTimeZone { - /// Creates a new [`MockTimeZone`]. +impl CustomTimeZone { + /// Creates a new [`CustomTimeZone`]. /// A GMT offset is required, as it is used as a final fallback for formatting. /// The other arguments optionally allow access to more robust formats. pub fn new( @@ -70,17 +62,17 @@ impl MockTimeZone { /// # Examples /// /// ``` - /// use icu::datetime::date::GmtOffset; - /// use icu::datetime::metazone::MetaZoneCalculator; - /// use icu::datetime::mock::time_zone::MockTimeZone; + /// use icu::timezone::GmtOffset; + /// use icu::timezone::MetaZoneCalculator; + /// use icu::timezone::CustomTimeZone; + /// use icu::timezone::provider::{MetaZoneId, TimeZoneBcp47Id}; /// use icu_calendar::DateTime; - /// use icu_datetime::provider::time_zones::{MetaZoneId, TimeZoneBcp47Id}; /// use icu_locid::locale; /// use tinystr::tinystr; /// /// let provider = icu_testdata::get_provider(); /// let mzc = MetaZoneCalculator::try_new(&provider).expect("data exists"); - /// let mut tz = MockTimeZone::new( + /// let mut tz = CustomTimeZone::new( /// /* gmt_offset */ Some("+11".parse().expect("Failed to parse a GMT offset.")), /// /* time_zone_id */ Some(TimeZoneBcp47Id(tinystr!(8, "gugum"))), /// /* metazone_id */ None, @@ -105,10 +97,10 @@ impl MockTimeZone { } } -impl FromStr for MockTimeZone { - type Err = DateTimeError; +impl FromStr for CustomTimeZone { + type Err = TimeZoneError; - /// Parse a [`MockTimeZone`] from a string. + /// Parse a [`CustomTimeZone`] from a string. /// /// This utility is for easily creating time zones, not a complete robust solution. /// @@ -122,12 +114,12 @@ impl FromStr for MockTimeZone { /// # Examples /// /// ``` - /// use icu::datetime::mock::time_zone::MockTimeZone; + /// use icu::timezone::CustomTimeZone; /// - /// let tz0: MockTimeZone = "Z".parse().expect("Failed to parse a time zone."); - /// let tz1: MockTimeZone = "+02".parse().expect("Failed to parse a time zone."); - /// let tz2: MockTimeZone = "-0230".parse().expect("Failed to parse a time zone."); - /// let tz3: MockTimeZone = "+02:30".parse().expect("Failed to parse a time zone."); + /// let tz0: CustomTimeZone = "Z".parse().expect("Failed to parse a time zone."); + /// let tz1: CustomTimeZone = "+02".parse().expect("Failed to parse a time zone."); + /// let tz2: CustomTimeZone = "-0230".parse().expect("Failed to parse a time zone."); + /// let tz3: CustomTimeZone = "+02:30".parse().expect("Failed to parse a time zone."); /// ``` fn from_str(input: &str) -> Result { let gmt_offset = input.parse::().ok(); @@ -139,21 +131,3 @@ impl FromStr for MockTimeZone { }) } } - -impl TimeZoneInput for MockTimeZone { - fn gmt_offset(&self) -> Option { - self.gmt_offset - } - - fn time_zone_id(&self) -> Option { - self.time_zone_id - } - - fn metazone_id(&self) -> Option { - self.metazone_id - } - - fn time_variant(&self) -> Option { - self.time_variant - } -} diff --git a/components/timezone/src/types.rs b/components/timezone/src/types.rs new file mode 100644 index 00000000000..b7d0982013e --- /dev/null +++ b/components/timezone/src/types.rs @@ -0,0 +1,125 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use crate::TimeZoneError; +use core::str::FromStr; + +/// The GMT offset in seconds for a timezone +#[derive(Copy, Clone, Debug, Default)] +pub struct GmtOffset(i32); + +impl GmtOffset { + /// Attempt to create a [`GmtOffset`] from a seconds input. It returns an error when the seconds + /// overflows or underflows. + pub fn try_new(seconds: i32) -> Result { + // Valid range is from GMT-12 to GMT+14 in seconds. + if seconds < -(12 * 60 * 60) || seconds > (14 * 60 * 60) { + Err(TimeZoneError::OffsetOutOfBounds) + } else { + Ok(Self(seconds)) + } + } + + /// Returns the raw offset value in seconds. + pub fn raw_offset_seconds(&self) -> i32 { + self.0 + } + + /// Returns `true` if the [`GmtOffset`] is positive, otherwise `false`. + pub fn is_positive(&self) -> bool { + self.0 >= 0 + } + + /// Returns `true` if the [`GmtOffset`] is zero, otherwise `false`. + pub fn is_zero(&self) -> bool { + self.0 == 0 + } + + /// Returns `true` if the [`GmtOffset`] has non-zero minutes, otherwise `false`. + pub fn has_minutes(&self) -> bool { + self.0 % 3600 / 60 > 0 + } + + /// Returns `true` if the [`GmtOffset`] has non-zero seconds, otherwise `false`. + pub fn has_seconds(&self) -> bool { + self.0 % 3600 % 60 > 0 + } +} + +impl FromStr for GmtOffset { + type Err = TimeZoneError; + + /// Parse a [`GmtOffset`] from a string. + /// + /// The offset must range from GMT-12 to GMT+14. + /// The string must be an ISO 8601 time zone designator: + /// e.g. Z + /// e.g. +05 + /// e.g. +0500 + /// e.g. +05:00 + /// + /// # Examples + /// + /// ``` + /// use icu_timezone::GmtOffset; + /// + /// let offset0: GmtOffset = "Z".parse().expect("Failed to parse a GMT offset."); + /// let offset1: GmtOffset = "-09".parse().expect("Failed to parse a GMT offset."); + /// let offset2: GmtOffset = "-0930".parse().expect("Failed to parse a GMT offset."); + /// let offset3: GmtOffset = "-09:30".parse().expect("Failed to parse a GMT offset."); + /// ``` + fn from_str(input: &str) -> Result { + let offset_sign = match input.chars().next() { + Some('+') => 1, + /* ASCII */ Some('-') => -1, + /* U+2212 */ Some('−') => -1, + Some('Z') => return Ok(Self(0)), + _ => return Err(TimeZoneError::InvalidOffset), + }; + + let seconds = match input.chars().count() { + /* ±hh */ + 3 => { + #[allow(clippy::indexing_slicing)] + // TODO(#1668) Clippy exceptions need docs or fixing. + let hour: u8 = input[1..3] + .parse() + .map_err(|_| TimeZoneError::InvalidOffset)?; + offset_sign * (hour as i32 * 60 * 60) + } + /* ±hhmm */ + 5 => { + #[allow(clippy::indexing_slicing)] + // TODO(#1668) Clippy exceptions need docs or fixing. + let hour: u8 = input[1..3] + .parse() + .map_err(|_| TimeZoneError::InvalidOffset)?; + #[allow(clippy::indexing_slicing)] + // TODO(#1668) Clippy exceptions need docs or fixing. + let minute: u8 = input[3..5] + .parse() + .map_err(|_| TimeZoneError::InvalidOffset)?; + offset_sign * (hour as i32 * 60 * 60 + minute as i32 * 60) + } + /* ±hh:mm */ + 6 => { + #[allow(clippy::indexing_slicing)] + // TODO(#1668) Clippy exceptions need docs or fixing. + let hour: u8 = input[1..3] + .parse() + .map_err(|_| TimeZoneError::InvalidOffset)?; + #[allow(clippy::indexing_slicing)] + // TODO(#1668) Clippy exceptions need docs or fixing. + let minute: u8 = input[4..6] + .parse() + .map_err(|_| TimeZoneError::InvalidOffset)?; + offset_sign * (hour as i32 * 60 * 60 + minute as i32 * 60) + } + #[allow(clippy::panic)] // TODO(#1668) Clippy exceptions need docs or fixing. + _ => panic!("Invalid time-zone designator"), + }; + + Self::try_new(seconds) + } +} diff --git a/ffi/diplomat/c/include/ICU4XError.h b/ffi/diplomat/c/include/ICU4XError.h index 9bbd3b65715..72b241b3a0e 100644 --- a/ffi/diplomat/c/include/ICU4XError.h +++ b/ffi/diplomat/c/include/ICU4XError.h @@ -38,9 +38,8 @@ typedef enum ICU4XError { ICU4XError_DateTimeParseError = 1792, ICU4XError_DateTimeOverflowError = 1793, ICU4XError_DateTimeUnderflowError = 1794, - ICU4XError_DateTimeInvalidTimeZoneOffsetError = 1795, - ICU4XError_DateTimeOutOfRangeError = 1796, - ICU4XError_DateTimeMissingInputError = 1797, + ICU4XError_DateTimeOutOfRangeError = 1795, + ICU4XError_DateTimeMissingInputError = 1796, ICU4XError_DateTimeFormatPatternError = 2048, ICU4XError_DateTimeFormatMissingInputFieldError = 2049, ICU4XError_DateTimeFormatSkeletonError = 2050, diff --git a/ffi/diplomat/cpp/docs/source/errors_ffi.rst b/ffi/diplomat/cpp/docs/source/errors_ffi.rst index 9a2ad9c6136..e11dd6683bd 100644 --- a/ffi/diplomat/cpp/docs/source/errors_ffi.rst +++ b/ffi/diplomat/cpp/docs/source/errors_ffi.rst @@ -77,8 +77,6 @@ .. cpp:enumerator:: DateTimeUnderflowError - .. cpp:enumerator:: DateTimeInvalidTimeZoneOffsetError - .. cpp:enumerator:: DateTimeOutOfRangeError .. cpp:enumerator:: DateTimeMissingInputError diff --git a/ffi/diplomat/cpp/include/ICU4XError.h b/ffi/diplomat/cpp/include/ICU4XError.h index 9bbd3b65715..72b241b3a0e 100644 --- a/ffi/diplomat/cpp/include/ICU4XError.h +++ b/ffi/diplomat/cpp/include/ICU4XError.h @@ -38,9 +38,8 @@ typedef enum ICU4XError { ICU4XError_DateTimeParseError = 1792, ICU4XError_DateTimeOverflowError = 1793, ICU4XError_DateTimeUnderflowError = 1794, - ICU4XError_DateTimeInvalidTimeZoneOffsetError = 1795, - ICU4XError_DateTimeOutOfRangeError = 1796, - ICU4XError_DateTimeMissingInputError = 1797, + ICU4XError_DateTimeOutOfRangeError = 1795, + ICU4XError_DateTimeMissingInputError = 1796, ICU4XError_DateTimeFormatPatternError = 2048, ICU4XError_DateTimeFormatMissingInputFieldError = 2049, ICU4XError_DateTimeFormatSkeletonError = 2050, diff --git a/ffi/diplomat/cpp/include/ICU4XError.hpp b/ffi/diplomat/cpp/include/ICU4XError.hpp index 72d659b16f4..791921b8717 100644 --- a/ffi/diplomat/cpp/include/ICU4XError.hpp +++ b/ffi/diplomat/cpp/include/ICU4XError.hpp @@ -69,9 +69,8 @@ enum struct ICU4XError { DateTimeParseError = 1792, DateTimeOverflowError = 1793, DateTimeUnderflowError = 1794, - DateTimeInvalidTimeZoneOffsetError = 1795, - DateTimeOutOfRangeError = 1796, - DateTimeMissingInputError = 1797, + DateTimeOutOfRangeError = 1795, + DateTimeMissingInputError = 1796, DateTimeFormatPatternError = 2048, DateTimeFormatMissingInputFieldError = 2049, DateTimeFormatSkeletonError = 2050, diff --git a/ffi/diplomat/src/errors.rs b/ffi/diplomat/src/errors.rs index 5a125888a5d..9ae9d3bffec 100644 --- a/ffi/diplomat/src/errors.rs +++ b/ffi/diplomat/src/errors.rs @@ -74,9 +74,8 @@ pub mod ffi { DateTimeParseError = 0x7_00, DateTimeOverflowError = 0x7_01, DateTimeUnderflowError = 0x7_02, - DateTimeInvalidTimeZoneOffsetError = 0x7_03, - DateTimeOutOfRangeError = 0x7_04, - DateTimeMissingInputError = 0x7_05, + DateTimeOutOfRangeError = 0x7_03, + DateTimeMissingInputError = 0x7_04, // datetime format errors DateTimeFormatPatternError = 0x8_00, @@ -144,7 +143,6 @@ impl From for ICU4XError { DateTimeError::Parse => ICU4XError::DateTimeParseError, DateTimeError::Overflow { field: _, max: _ } => ICU4XError::DateTimeOverflowError, DateTimeError::Underflow { field: _, min: _ } => ICU4XError::DateTimeUnderflowError, - DateTimeError::InvalidTimeZoneOffset => ICU4XError::DateTimeInvalidTimeZoneOffsetError, DateTimeError::OutOfRange => ICU4XError::DateTimeOutOfRangeError, DateTimeError::MissingInput(_) => ICU4XError::DateTimeMissingInputError, _ => ICU4XError::UnknownError, diff --git a/ffi/diplomat/wasm/lib/ICU4XError.d.ts b/ffi/diplomat/wasm/lib/ICU4XError.d.ts index 86a7325ba9b..f81521ec64f 100644 --- a/ffi/diplomat/wasm/lib/ICU4XError.d.ts +++ b/ffi/diplomat/wasm/lib/ICU4XError.d.ts @@ -97,9 +97,6 @@ export enum ICU4XError { /** */ DateTimeUnderflowError = 'DateTimeUnderflowError', - /** - */ - DateTimeInvalidTimeZoneOffsetError = 'DateTimeInvalidTimeZoneOffsetError', /** */ DateTimeOutOfRangeError = 'DateTimeOutOfRangeError', diff --git a/ffi/diplomat/wasm/lib/ICU4XError.js b/ffi/diplomat/wasm/lib/ICU4XError.js index 5bcf0a39843..23c71500c71 100644 --- a/ffi/diplomat/wasm/lib/ICU4XError.js +++ b/ffi/diplomat/wasm/lib/ICU4XError.js @@ -29,9 +29,8 @@ export const ICU4XError_js_to_rust = { "DateTimeParseError": 1792, "DateTimeOverflowError": 1793, "DateTimeUnderflowError": 1794, - "DateTimeInvalidTimeZoneOffsetError": 1795, - "DateTimeOutOfRangeError": 1796, - "DateTimeMissingInputError": 1797, + "DateTimeOutOfRangeError": 1795, + "DateTimeMissingInputError": 1796, "DateTimeFormatPatternError": 2048, "DateTimeFormatMissingInputFieldError": 2049, "DateTimeFormatSkeletonError": 2050, @@ -72,9 +71,8 @@ export const ICU4XError_rust_to_js = { 1792: "DateTimeParseError", 1793: "DateTimeOverflowError", 1794: "DateTimeUnderflowError", - 1795: "DateTimeInvalidTimeZoneOffsetError", - 1796: "DateTimeOutOfRangeError", - 1797: "DateTimeMissingInputError", + 1795: "DateTimeOutOfRangeError", + 1796: "DateTimeMissingInputError", 2048: "DateTimeFormatPatternError", 2049: "DateTimeFormatMissingInputFieldError", 2050: "DateTimeFormatSkeletonError", @@ -115,7 +113,6 @@ export const ICU4XError = { "DateTimeParseError": "DateTimeParseError", "DateTimeOverflowError": "DateTimeOverflowError", "DateTimeUnderflowError": "DateTimeUnderflowError", - "DateTimeInvalidTimeZoneOffsetError": "DateTimeInvalidTimeZoneOffsetError", "DateTimeOutOfRangeError": "DateTimeOutOfRangeError", "DateTimeMissingInputError": "DateTimeMissingInputError", "DateTimeFormatPatternError": "DateTimeFormatPatternError", diff --git a/provider/datagen/Cargo.toml b/provider/datagen/Cargo.toml index 5bed9fdbaae..97b88e05e3e 100644 --- a/provider/datagen/Cargo.toml +++ b/provider/datagen/Cargo.toml @@ -43,6 +43,8 @@ icu_locale_canonicalizer = { version = "0.6", path = "../../components/locale_ca icu_normalizer = { version = "0.6", path = "../../components/normalizer", features = ["datagen"] } icu_plurals = { version = "0.6", path = "../../components/plurals", features = ["datagen"] } icu_properties = { version = "0.6", path = "../../components/properties", features = ["datagen"]} +icu_timezone = { version = "0.6", path = "../../components/timezone", features = ["datagen"] } + # (experimental) icu_casemapping = { version = "0.1", path = "../../experimental/casemapping", features = ["datagen"], optional = true } icu_segmenter = { version = "0.6", path = "../../experimental/segmenter", features = ["datagen", "lstm"], optional = true } diff --git a/provider/datagen/src/registry.rs b/provider/datagen/src/registry.rs index ff2c16f35ce..1bea30a9686 100644 --- a/provider/datagen/src/registry.rs +++ b/provider/datagen/src/registry.rs @@ -17,6 +17,7 @@ use icu_plurals::provider::*; use icu_properties::provider::*; use icu_provider::hello_world::HelloWorldV1Marker; use icu_provider_adapters::fallback::provider::*; +use icu_timezone::provider::*; #[cfg(feature = "experimental")] use icu_casemapping::provider::*; @@ -135,9 +136,9 @@ registry!( MathV1Marker, MetaZoneGenericNamesLongV1Marker, MetaZoneGenericNamesShortV1Marker, - MetaZonePeriodV1Marker, MetaZoneSpecificNamesLongV1Marker, MetaZoneSpecificNamesShortV1Marker, + MetaZonePeriodV1Marker, NfcInertV1Marker, NfdInertV1Marker, NfkcInertV1Marker, diff --git a/provider/datagen/src/transform/cldr/time_zones/convert.rs b/provider/datagen/src/transform/cldr/time_zones/convert.rs index 0d5ea874829..0aa9c88c70f 100644 --- a/provider/datagen/src/transform/cldr/time_zones/convert.rs +++ b/provider/datagen/src/transform/cldr/time_zones/convert.rs @@ -13,9 +13,9 @@ use cldr_serde::time_zones::time_zone_names::*; use icu_calendar::DateTime; use icu_datetime::provider::time_zones::{ ExemplarCitiesV1, MetaZoneGenericNamesLongV1, MetaZoneGenericNamesShortV1, MetaZoneId, - MetaZonePeriodV1, MetaZoneSpecificNamesLongV1, MetaZoneSpecificNamesShortV1, TimeZoneBcp47Id, - TimeZoneFormatsV1, + MetaZoneSpecificNamesLongV1, MetaZoneSpecificNamesShortV1, TimeZoneBcp47Id, TimeZoneFormatsV1, }; +use icu_timezone::provider::MetaZonePeriodV1; use std::borrow::Cow; use std::collections::HashMap; use tinystr::TinyStr8; diff --git a/provider/datagen/src/transform/cldr/time_zones/mod.rs b/provider/datagen/src/transform/cldr/time_zones/mod.rs index e1c50288795..be16c06bc77 100644 --- a/provider/datagen/src/transform/cldr/time_zones/mod.rs +++ b/provider/datagen/src/transform/cldr/time_zones/mod.rs @@ -11,6 +11,7 @@ use icu_datetime::provider::time_zones::*; use icu_datetime::provider::time_zones::{MetaZoneId, TimeZoneBcp47Id}; use icu_provider::datagen::IterableDataProvider; use icu_provider::prelude::*; +use icu_timezone::provider::*; use std::collections::HashMap; mod convert; @@ -82,14 +83,15 @@ macro_rules! impl_data_provider { // MetaZonePeriodV1 does not require localized time zone data Ok(vec![Default::default()]) } else { - Ok(self - .source - .cldr()? - .dates("gregorian") - .list_langs()? - .map(DataLocale::from) - .collect()) - } + + Ok(self + .source + .cldr()? + .dates("gregorian") + .list_langs()? + .map(DataLocale::from) + .collect()) +} } } )+ diff --git a/provider/testdata/Cargo.toml b/provider/testdata/Cargo.toml index c02ca87d67d..3ee89204961 100644 --- a/provider/testdata/Cargo.toml +++ b/provider/testdata/Cargo.toml @@ -313,6 +313,7 @@ icu_normalizer = { version = "0.6", path = "../../components/normalizer", option icu_plurals = { version = "0.6", path = "../../components/plurals", optional = true } icu_properties = { version = "0.6", path = "../../components/properties", optional = true } icu_segmenter = { version = "0.6", path = "../../experimental/segmenter", optional = true, features = ["lstm"] } +icu_timezone = { version = "0.6", path = "../../components/timezone", optional = true } icu_char16trie = { version = "0.1", path = "../../experimental/char16trie", optional = true } icu_codepointtrie = { version = "0.4", path = "../../utils/codepointtrie", optional = true } icu_uniset = { version = "0.5", path = "../../utils/uniset", optional = true } @@ -356,6 +357,7 @@ baked = [ "icu_properties", "icu_provider_adapters", "icu_segmenter", + "icu_timezone", "icu_char16trie", "icu_codepointtrie", "icu_uniset", diff --git a/provider/testdata/data/baked/any.rs b/provider/testdata/data/baked/any.rs index 13890c25b5c..1e4a1f61a0c 100644 --- a/provider/testdata/data/baked/any.rs +++ b/provider/testdata/data/baked/any.rs @@ -33,8 +33,6 @@ impl AnyProvider for BakedDataProvider { ::icu_datetime::provider::time_zones::MetaZoneGenericNamesLongV1Marker::KEY.get_hash(); const METAZONEGENERICNAMESSHORTV1MARKER: ::icu_provider::DataKeyHash = ::icu_datetime::provider::time_zones::MetaZoneGenericNamesShortV1Marker::KEY.get_hash(); - const METAZONEPERIODV1MARKER: ::icu_provider::DataKeyHash = - ::icu_datetime::provider::time_zones::MetaZonePeriodV1Marker::KEY.get_hash(); const METAZONESPECIFICNAMESLONGV1MARKER: ::icu_provider::DataKeyHash = ::icu_datetime::provider::time_zones::MetaZoneSpecificNamesLongV1Marker::KEY.get_hash(); const METAZONESPECIFICNAMESSHORTV1MARKER: ::icu_provider::DataKeyHash = @@ -219,6 +217,8 @@ impl AnyProvider for BakedDataProvider { ::icu_segmenter::provider::UCharDictionaryBreakDataV1Marker::KEY.get_hash(); const WORDBREAKDATAV1MARKER: ::icu_provider::DataKeyHash = ::icu_segmenter::provider::WordBreakDataV1Marker::KEY.get_hash(); + const METAZONEPERIODV1MARKER: ::icu_provider::DataKeyHash = + ::icu_timezone::provider::MetaZonePeriodV1Marker::KEY.get_hash(); Ok(AnyResponse { payload: Some(match key.get_hash() { JAPANESEERASV1MARKER => calendar::japanese_v1_u_ca::DATA @@ -275,9 +275,6 @@ impl AnyProvider for BakedDataProvider { METAZONEGENERICNAMESSHORTV1MARKER => time_zone::generic_short_v1::DATA .get_by(|k| req.locale.strict_cmp(k.as_bytes()).reverse()) .map(AnyPayload::from_static_ref), - METAZONEPERIODV1MARKER => time_zone::metazone_period_v1::DATA - .get_by(|k| req.locale.strict_cmp(k.as_bytes()).reverse()) - .map(AnyPayload::from_static_ref), METAZONESPECIFICNAMESLONGV1MARKER => time_zone::specific_long_v1::DATA .get_by(|k| req.locale.strict_cmp(k.as_bytes()).reverse()) .map(AnyPayload::from_static_ref), @@ -548,6 +545,9 @@ impl AnyProvider for BakedDataProvider { WORDBREAKDATAV1MARKER => segmenter::word_v1::DATA .get_by(|k| req.locale.strict_cmp(k.as_bytes()).reverse()) .map(AnyPayload::from_static_ref), + METAZONEPERIODV1MARKER => time_zone::metazone_period_v1::DATA + .get_by(|k| req.locale.strict_cmp(k.as_bytes()).reverse()) + .map(AnyPayload::from_static_ref), _ => return Err(DataErrorKind::MissingDataKey.with_req(key, req)), }) .ok_or_else(|| DataErrorKind::MissingLocale.with_req(key, req))?, diff --git a/provider/testdata/data/baked/mod.rs b/provider/testdata/data/baked/mod.rs index ee2c1b695af..7ef78a05bd6 100644 --- a/provider/testdata/data/baked/mod.rs +++ b/provider/testdata/data/baked/mod.rs @@ -215,18 +215,6 @@ impl DataProvider<::icu_datetime::provider::time_zones::MetaZoneGenericNamesShor }) } } -impl DataProvider<::icu_datetime::provider::time_zones::MetaZonePeriodV1Marker> for BakedDataProvider { - fn load(&self, req: DataRequest) -> Result, DataError> { - Ok(DataResponse { - metadata: Default::default(), - payload: Some(DataPayload::from_owned(zerofrom::ZeroFrom::zero_from( - *time_zone::metazone_period_v1::DATA - .get_by(|k| req.locale.strict_cmp(k.as_bytes()).reverse()) - .ok_or_else(|| DataErrorKind::MissingLocale.with_req(::icu_datetime::provider::time_zones::MetaZonePeriodV1Marker::KEY, req))?, - ))), - }) - } -} impl DataProvider<::icu_datetime::provider::time_zones::MetaZoneSpecificNamesLongV1Marker> for BakedDataProvider { fn load(&self, req: DataRequest) -> Result, DataError> { Ok(DataResponse { @@ -1337,3 +1325,15 @@ impl DataProvider<::icu_segmenter::provider::WordBreakDataV1Marker> for BakedDat }) } } +impl DataProvider<::icu_timezone::provider::MetaZonePeriodV1Marker> for BakedDataProvider { + fn load(&self, req: DataRequest) -> Result, DataError> { + Ok(DataResponse { + metadata: Default::default(), + payload: Some(DataPayload::from_owned(zerofrom::ZeroFrom::zero_from( + *time_zone::metazone_period_v1::DATA + .get_by(|k| req.locale.strict_cmp(k.as_bytes()).reverse()) + .ok_or_else(|| DataErrorKind::MissingLocale.with_req(::icu_timezone::provider::MetaZonePeriodV1Marker::KEY, req))?, + ))), + }) + } +} diff --git a/provider/testdata/data/baked/time_zone/metazone_period_v1.rs b/provider/testdata/data/baked/time_zone/metazone_period_v1.rs index 739b46a59ae..2ae9d49e9d3 100644 --- a/provider/testdata/data/baked/time_zone/metazone_period_v1.rs +++ b/provider/testdata/data/baked/time_zone/metazone_period_v1.rs @@ -1,8 +1,9 @@ // @generated -type DataStruct = < :: icu_datetime :: provider :: time_zones :: MetaZonePeriodV1Marker as :: icu_provider :: DataMarker > :: Yokeable ; +type DataStruct = + <::icu_timezone::provider::MetaZonePeriodV1Marker as ::icu_provider::DataMarker>::Yokeable; pub static DATA: litemap::LiteMap<&str, &DataStruct, &[(&str, &DataStruct)]> = litemap::LiteMap::from_sorted_slice_unchecked(&[("und", UND)]); -static UND: &DataStruct = &::icu_datetime::provider::time_zones::MetaZonePeriodV1(unsafe { +static UND: &DataStruct = &::icu_timezone::provider::MetaZonePeriodV1(unsafe { #[allow(unused_unsafe)] ::zerovec::ZeroMap2d::from_parts_unchecked( unsafe {