icu_datetime/provider/
time_zones.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Data provider structs for time zones.
6
7use alloc::borrow::Cow;
8use icu_pattern::{DoublePlaceholderPattern, SinglePlaceholderPattern};
9use icu_provider::prelude::*;
10use icu_time::{zone::TimeZoneVariant, TimeZone};
11use zerovec::{ZeroMap, ZeroVec};
12
13pub use icu_time::provider::MetazoneId;
14
15/// Time zone type aliases for cleaner code
16pub(crate) mod tz {
17    pub(crate) use super::ExemplarCities;
18    pub(crate) use super::Locations;
19    pub(crate) use super::MetazoneGenericNames as MzGeneric;
20    pub(crate) use super::MetazoneSpecificNames as MzSpecific;
21    pub(crate) use super::TimeZoneEssentials as Essentials;
22    pub(crate) use super::TimezoneNamesCitiesOverrideV1 as CitiesOverrideV1;
23    pub(crate) use super::TimezoneNamesCitiesRootV1 as CitiesRootV1;
24    pub(crate) use super::TimezoneNamesEssentialsV1 as EssentialsV1;
25    pub(crate) use super::TimezoneNamesGenericLongV1 as MzGenericLongV1;
26    pub(crate) use super::TimezoneNamesGenericShortV1 as MzGenericShortV1;
27    pub(crate) use super::TimezoneNamesLocationsOverrideV1 as LocationsOverrideV1;
28    pub(crate) use super::TimezoneNamesLocationsRootV1 as LocationsRootV1;
29    pub(crate) use super::TimezoneNamesSpecificLongV1 as MzSpecificLongV1;
30    pub(crate) use super::TimezoneNamesSpecificShortV1 as MzSpecificShortV1;
31    pub(crate) use super::TimezoneNamesStandardLongV1 as MzStandardLongV1;
32    pub(crate) use icu_time::provider::TimezonePeriods as MzPeriod;
33    pub(crate) use icu_time::provider::TimezonePeriodsV1 as MzPeriodV1;
34}
35
36icu_provider::data_marker!(
37    /// `TimezoneNamesEssentialsV1`
38    TimezoneNamesEssentialsV1,
39    TimeZoneEssentials<'static>
40);
41icu_provider::data_marker!(
42    /// `TimezoneNamesLocationsOverrideV1`
43    TimezoneNamesLocationsOverrideV1,
44    Locations<'static>
45);
46icu_provider::data_marker!(
47    /// `TimezoneNamesLocationsRootV1`
48    TimezoneNamesLocationsRootV1,
49    Locations<'static>
50);
51icu_provider::data_marker!(
52    /// `TimezoneNamesCitiesOverrideV1`
53    TimezoneNamesCitiesOverrideV1,
54    ExemplarCities<'static>
55);
56icu_provider::data_marker!(
57    /// `TimezoneNamesCitiesRootV1`
58    TimezoneNamesCitiesRootV1,
59    ExemplarCities<'static>
60);
61
62icu_provider::data_marker!(
63    /// `TimezoneNamesGenericLongV1`
64    ///
65    /// Checksumed to ensure consistency with [`TimezoneMetazonePeriodsV1`].
66    TimezoneNamesGenericLongV1,
67    MetazoneGenericNames<'static>,
68    has_checksum = true
69);
70icu_provider::data_marker!(
71    /// `TimezoneNamesGenericShortV1`
72    ///
73    /// Checksumed to ensure consistency with [`TimezoneMetazonePeriodsV1`].
74    TimezoneNamesGenericShortV1,
75    MetazoneGenericNames<'static>,
76    has_checksum = true
77);
78icu_provider::data_marker!(
79    /// `TimezoneNamesStandardLongV1`
80    ///
81    /// Checksumed to ensure consistency with [`TimezoneMetazonePeriodsV1`].
82    TimezoneNamesStandardLongV1,
83    MetazoneGenericNames<'static>,
84    has_checksum = true
85);
86icu_provider::data_marker!(
87    /// `TimezoneNamesSpecificLongV1`
88    ///
89    /// Checksumed to ensure consistency with [`TimezoneMetazonePeriodsV1`].
90    TimezoneNamesSpecificLongV1,
91    MetazoneSpecificNames<'static>,
92    has_checksum = true
93);
94icu_provider::data_marker!(
95    /// `TimezoneNamesSpecificShortV1`
96    ///
97    /// Checksumed to ensure consistency with [`TimezoneMetazonePeriodsV1`].
98    TimezoneNamesSpecificShortV1,
99    MetazoneSpecificNames<'static>,
100    has_checksum = true,
101);
102
103/// An ICU4X mapping to the CLDR timeZoneNames format strings.
104/// See CLDR-JSON timeZoneNames.json and <https://cldr.unicode.org/translation/time-zones-and-city-names>
105/// for more context.
106///
107/// <div class="stab unstable">
108/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
109/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
110/// to be stable, their Rust representation might not be. Use with caution.
111/// </div>
112#[derive(PartialEq, Debug, Clone, Default, yoke::Yokeable, zerofrom::ZeroFrom)]
113#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
114#[cfg_attr(feature = "datagen", databake(path = icu_datetime::provider::time_zones))]
115#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
116#[yoke(prove_covariance_manually)]
117pub struct TimeZoneEssentials<'data> {
118    /// The separator sign
119    #[cfg_attr(feature = "serde", serde(borrow,))]
120    pub offset_separator: Cow<'data, str>,
121    /// The localized offset format.
122    #[cfg_attr(
123        feature = "serde",
124        serde(
125            borrow,
126            deserialize_with = "icu_pattern::deserialize_borrowed_cow::<icu_pattern::SinglePlaceholder, _>"
127        )
128    )]
129    pub offset_pattern: Cow<'data, SinglePlaceholderPattern>,
130    /// The localized zero-offset format.
131    #[cfg_attr(feature = "serde", serde(borrow))]
132    pub offset_zero: Cow<'data, str>,
133    /// The localized unknown-offset format.
134    #[cfg_attr(feature = "serde", serde(borrow))]
135    pub offset_unknown: Cow<'data, str>,
136}
137
138icu_provider::data_struct!(
139    TimeZoneEssentials<'_>,
140    #[cfg(feature = "datagen")]
141);
142
143/// An ICU4X mapping to the CLDR timeZoneNames exemplar cities.
144/// See CLDR-JSON timeZoneNames.json for more context.
145///
146/// <div class="stab unstable">
147/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
148/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
149/// to be stable, their Rust representation might not be. Use with caution.
150/// </div>
151#[derive(PartialEq, Debug, Clone, Default, yoke::Yokeable, zerofrom::ZeroFrom)]
152#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
153#[cfg_attr(feature = "datagen", databake(path = icu_datetime::provider::time_zones))]
154#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
155#[yoke(prove_covariance_manually)]
156pub struct Locations<'data> {
157    /// Per-zone location display name
158    #[cfg_attr(feature = "serde", serde(borrow))]
159    pub locations: ZeroMap<'data, TimeZone, str>,
160    /// The format string for a region's generic time.
161    #[cfg_attr(
162        feature = "serde",
163        serde(
164            borrow,
165            deserialize_with = "icu_pattern::deserialize_borrowed_cow::<icu_pattern::SinglePlaceholder, _>"
166        )
167    )]
168    pub pattern_generic: Cow<'data, SinglePlaceholderPattern>,
169    /// The format string for a region's standard time.
170    #[cfg_attr(
171        feature = "serde",
172        serde(
173            borrow,
174            deserialize_with = "icu_pattern::deserialize_borrowed_cow::<icu_pattern::SinglePlaceholder, _>"
175        )
176    )]
177    pub pattern_standard: Cow<'data, SinglePlaceholderPattern>,
178    /// The format string for a region's daylight time.
179    #[cfg_attr(
180        feature = "serde",
181        serde(
182            borrow,
183            deserialize_with = "icu_pattern::deserialize_borrowed_cow::<icu_pattern::SinglePlaceholder, _>"
184        )
185    )]
186    pub pattern_daylight: Cow<'data, SinglePlaceholderPattern>,
187    /// Metazone Name with Location Pattern.
188    #[cfg_attr(
189        feature = "serde",
190        serde(
191            borrow,
192            deserialize_with = "icu_pattern::deserialize_borrowed_cow::<icu_pattern::DoublePlaceholder, _>"
193        )
194    )]
195    pub pattern_partial_location: Cow<'data, DoublePlaceholderPattern>,
196}
197
198icu_provider::data_struct!(
199    Locations<'_>,
200    #[cfg(feature = "datagen")]
201);
202
203/// An ICU4X mapping to the CLDR timeZoneNames exemplar cities.
204/// See CLDR-JSON timeZoneNames.json for more context.
205///
206/// <div class="stab unstable">
207/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
208/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
209/// to be stable, their Rust representation might not be. Use with caution.
210/// </div>
211#[derive(PartialEq, Debug, Clone, Default, yoke::Yokeable, zerofrom::ZeroFrom)]
212#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
213#[cfg_attr(feature = "datagen", databake(path = icu_datetime::provider::time_zones))]
214#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
215#[yoke(prove_covariance_manually)]
216pub struct ExemplarCities<'data> {
217    /// Per-zone exemplar city name. This is deduplicated against `Locations.locations`, so it
218    /// only contains time zones that don't use the exemplar city in the location format.
219    #[cfg_attr(feature = "serde", serde(borrow))]
220    pub exemplars: ZeroMap<'data, TimeZone, str>,
221}
222
223icu_provider::data_struct!(
224    ExemplarCities<'_>,
225    #[cfg(feature = "datagen")]
226);
227
228/// An ICU4X mapping to generic metazone names.
229/// See CLDR-JSON timeZoneNames.json for more context.
230///
231/// <div class="stab unstable">
232/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
233/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
234/// to be stable, their Rust representation might not be. Use with caution.
235/// </div>
236#[derive(PartialEq, Debug, Clone, Default, yoke::Yokeable, zerofrom::ZeroFrom)]
237#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
238#[cfg_attr(feature = "datagen", databake(path = icu_datetime::provider::time_zones))]
239#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
240#[yoke(prove_covariance_manually)]
241pub struct MetazoneGenericNames<'data> {
242    /// The default mapping between metazone id and localized metazone name.
243    #[cfg_attr(feature = "serde", serde(borrow))]
244    pub defaults: ZeroMap<'data, MetazoneId, str>,
245    /// The override mapping between timezone id and localized metazone name.
246    #[cfg_attr(feature = "serde", serde(borrow))]
247    pub overrides: ZeroMap<'data, TimeZone, str>,
248}
249
250icu_provider::data_struct!(
251    MetazoneGenericNames<'_>,
252    #[cfg(feature = "datagen")]
253);
254
255/// An ICU4X mapping to specific metazone names.
256/// Specific names include time variants such as "daylight."
257/// See CLDR-JSON timeZoneNames.json for more context.
258///
259/// These markers use a checksum to ensure consistency with [`icu_time::provider::TimezonePeriodsV1`].
260///
261/// <div class="stab unstable">
262/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
263/// including in SemVer minor releases. While the serde representation of data structs is guaranteed
264/// to be stable, their Rust representation might not be. Use with caution.
265/// </div>
266#[derive(PartialEq, Debug, Clone, Default, yoke::Yokeable, zerofrom::ZeroFrom)]
267#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
268#[cfg_attr(feature = "datagen", databake(path = icu_datetime::provider::time_zones))]
269#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
270#[yoke(prove_covariance_manually)]
271pub struct MetazoneSpecificNames<'data> {
272    /// The default mapping between metazone id and localized metazone name.
273    #[cfg_attr(feature = "serde", serde(borrow))]
274    pub defaults: ZeroMap<'data, (MetazoneId, TimeZoneVariant), str>,
275    /// The override mapping between timezone id and localized metazone name.
276    #[cfg_attr(feature = "serde", serde(borrow))]
277    pub overrides: ZeroMap<'data, (TimeZone, TimeZoneVariant), str>,
278    /// The metazones for which the standard name is in `MetazoneGenericStandardNames*V1`
279    #[cfg_attr(feature = "serde", serde(borrow))]
280    pub use_standard: ZeroVec<'data, MetazoneId>,
281}
282
283icu_provider::data_struct!(
284    MetazoneSpecificNames<'_>,
285    #[cfg(feature = "datagen")]
286);
287
288#[cfg(feature = "serde")]
289pub(crate) mod legacy {
290    use super::*;
291    use icu_time::zone::ZoneNameTimestamp;
292    use zerovec::ule::NichedOption;
293    use zerovec::ZeroMap2d;
294
295    icu_provider::data_marker!(
296        /// `TimezoneMetazonePeriodsV1`
297        TimezoneMetazonePeriodsV1,
298        MetazonePeriod<'static>,
299        is_singleton = true,
300        has_checksum = true
301    );
302
303    /// An ICU4X mapping to the metazones at a given period.
304    /// See CLDR-JSON metaZones.json for more context.
305    ///
306    /// <div class="stab unstable">
307    /// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
308    /// including in SemVer minor releases. While the serde representation of data structs is guaranteed
309    /// to be stable, their Rust representation might not be. Use with caution.
310    /// </div>
311    #[derive(PartialEq, Debug, Clone, Default, yoke::Yokeable, zerofrom::ZeroFrom)]
312    #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
313    #[yoke(prove_covariance_manually)]
314    #[allow(dead_code, reason = "We construct this struct through serde impls")]
315    pub struct MetazonePeriod<'data> {
316        /// The default mapping between period and offsets. The second level key is a wall-clock time encoded as
317        /// [`ZoneNameTimestamp`]. It represents when the metazone started to be used.
318        #[cfg_attr(feature = "serde", serde(borrow))]
319        pub list: ZeroMap2d<'data, TimeZone, ZoneNameTimestamp, NichedOption<MetazoneId, 1>>,
320    }
321
322    icu_provider::data_struct!(
323        MetazonePeriod<'_>,
324        #[cfg(feature = "datagen")]
325    );
326
327    #[inline(never)] // keep this compat code self-contained and not duplicated
328    pub(crate) fn metazone_timezone_compat(
329        provider: &impl BufferProvider,
330        req: DataRequest<'_>,
331    ) -> Result<DataResponse<icu_time::provider::TimezonePeriodsV1>, DataError> {
332        use alloc::vec::Vec;
333        use icu_time::provider::Timestamp24;
334        use icu_time::provider::TimezonePeriods;
335        use zerotrie::ZeroTrieSimpleAscii;
336        use zerovec::ule::vartuple::VarTuple;
337        use zerovec::ule::AsULE;
338        use zerovec::vecs::VarZeroVecOwned;
339
340        let DataResponse::<TimezoneMetazonePeriodsV1> {
341            payload: old_payload,
342            metadata,
343        } = provider.as_deserializing().load(req)?;
344
345        let index = ZeroTrieSimpleAscii::<Vec<u8>>::from_iter(
346            old_payload
347                .get()
348                .list
349                .iter0()
350                .enumerate()
351                .map(|(i, v)| (v.key0().as_str(), i)),
352        )
353        .convert_store();
354
355        let mut list = VarZeroVecOwned::new();
356
357        for ps in old_payload.get().list.iter0() {
358            let mut cursor = ps.into_iter1_copied();
359            let Some((_, mz)) = cursor.next() else {
360                continue; // unreachable
361            };
362
363            let rest = cursor
364                .map(move |(&t, mz)| (Timestamp24(ZoneNameTimestamp::from_unaligned(t)), 0, mz))
365                .collect::<ZeroVec<_>>();
366
367            let rest = VarTuple {
368                sized: (0, mz),
369                variable: rest.as_slice(),
370            };
371
372            list.push(&rest);
373        }
374
375        Ok(DataResponse {
376            payload: DataPayload::from_owned(TimezonePeriods {
377                index,
378                list: list.into(),
379                offsets: ZeroVec::from(alloc::vec![Default::default()]),
380            }),
381            metadata,
382        })
383    }
384
385    #[test]
386    fn test_metazone_timezone_compat() {
387        use icu_locale::subtags::subtag;
388        use icu_time::ZonedDateTime;
389
390        let converted = metazone_timezone_compat(
391            &icu_provider_blob::BlobDataProvider::try_new_from_static_blob(
392                // icu4x-datagen --markers TimezoneMetazonePeriodsV1 --format blob
393                include_bytes!("../../tests/data/metazone_periods_old.postcard"),
394            )
395            .unwrap(),
396            Default::default(),
397        )
398        .unwrap()
399        .payload;
400
401        let tz = TimeZone(subtag!("aqcas"));
402        for timestamp in [
403            "1970-01-01 00:00Z",
404            "2009-10-17 18:00Z",
405            "2010-03-04 15:00Z",
406            "2011-10-27 18:00Z",
407            "2012-02-21 17:00Z",
408            "2016-10-21 16:00Z",
409            "2018-03-10 17:00Z",
410            "2018-10-06 20:00Z",
411            "2019-03-16 16:00Z",
412            "2019-10-03 19:00Z",
413            "2020-03-07 16:00Z",
414            "2021-03-13 13:00Z",
415            "2022-03-12 13:00Z",
416            "2023-03-08 16:00Z",
417        ] {
418            let t = ZoneNameTimestamp::from_zoned_date_time_iso(
419                ZonedDateTime::try_offset_only_from_str(timestamp, icu_calendar::Iso).unwrap(),
420            );
421
422            assert_eq!(
423                converted
424                    .get()
425                    .get(tz, t)
426                    .unwrap()
427                    .1
428                    .map(|mz| match mz.id.get() {
429                        // the ID list changed with CLDR 48
430                        22 => 21,
431                        31 => 30,
432                        _ => unreachable!(),
433                    }),
434                icu_time::provider::Baked::SINGLETON_TIMEZONE_PERIODS_V1
435                    .get(tz, t)
436                    .unwrap()
437                    .1
438                    .map(|mz| mz.id.get()),
439                "{timestamp:?}",
440            );
441        }
442    }
443}