icu_time/zone/
iana.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//! Tools for parsing IANA time zone IDs.
6
7use icu_provider::prelude::*;
8use zerovec::vecs::{VarZeroSliceIter, ZeroSliceIter};
9
10use crate::{
11    provider::iana::{
12        IanaNames, IanaToBcp47Map, TimezoneIdentifiersIanaCoreV1,
13        TimezoneIdentifiersIanaExtendedV1, NON_REGION_CITY_PREFIX,
14    },
15    TimeZone,
16};
17
18/// A parser for parsing an IANA time zone ID to a [`TimeZone`] type.
19///
20/// There are approximately 600 IANA identifiers and 450 [`TimeZone`] identifiers.
21/// These lists grow very slowly; in a typical year, 2-3 new identifiers are added.
22///
23/// This means that multiple IANA identifiers map to the same [`TimeZone`]. For example, the
24/// following four IANA identifiers all map to the same [`TimeZone`]:
25///
26/// - `America/Fort_Wayne`
27/// - `America/Indiana/Indianapolis`
28/// - `America/Indianapolis`
29/// - `US/East-Indiana`
30///
31/// For each [`TimeZone`], there is one "canonical" IANA time zone ID (for the above example, it is
32/// `America/Indiana/Indianapolis`). Note that the canonical identifier can change over time.
33/// For example, the identifier `Europe/Kiev` was renamed to the newly-added identifier `Europe/Kyiv` in 2022.
34///
35/// # Examples
36///
37/// ```
38/// use icu::locale::subtags::subtag;
39/// use icu::time::zone::IanaParser;
40/// use icu::time::TimeZone;
41///
42/// let parser = IanaParser::new();
43///
44/// // The IANA zone "Australia/Melbourne" is the BCP-47 zone "aumel":
45/// assert_eq!(
46///     parser.parse("Australia/Melbourne"),
47///     TimeZone(subtag!("aumel"))
48/// );
49///
50/// // Parsing is ASCII-case-insensitive:
51/// assert_eq!(
52///     parser.parse("australia/melbourne"),
53///     TimeZone(subtag!("aumel"))
54/// );
55///
56/// // The IANA zone "Australia/Victoria" is an alias:
57/// assert_eq!(
58///     parser.parse("Australia/Victoria"),
59///     TimeZone(subtag!("aumel"))
60/// );
61///
62/// // The IANA zone "Australia/Boing_Boing" does not exist
63/// // (maybe not *yet*), so it produces the special unknown
64/// // time zone in order for this operation to be infallible:
65/// assert_eq!(parser.parse("Australia/Boing_Boing"), TimeZone::UNKNOWN);
66/// ```
67#[derive(Debug, Clone)]
68pub struct IanaParser {
69    data: DataPayload<TimezoneIdentifiersIanaCoreV1>,
70    checksum: u64,
71}
72
73impl IanaParser {
74    /// Creates a new [`IanaParser`] using compiled data.
75    ///
76    /// See [`IanaParser`] for an example.
77    ///
78    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
79    ///
80    /// [📚 Help choosing a constructor](icu_provider::constructors)
81    #[cfg(feature = "compiled_data")]
82    #[allow(clippy::new_ret_no_self)]
83    pub fn new() -> IanaParserBorrowed<'static> {
84        IanaParserBorrowed::new()
85    }
86
87    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
88        functions: [
89            new: skip,
90            try_new_with_buffer_provider,
91            try_new_unstable,
92            Self,
93        ]
94    );
95
96    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
97    pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
98    where
99        P: DataProvider<TimezoneIdentifiersIanaCoreV1> + ?Sized,
100    {
101        let response = provider.load(Default::default())?;
102        Ok(Self {
103            data: response.payload,
104            checksum: response.metadata.checksum.ok_or_else(|| {
105                DataError::custom("Missing checksum")
106                    .with_req(TimezoneIdentifiersIanaCoreV1::INFO, Default::default())
107            })?,
108        })
109    }
110
111    /// Returns a borrowed version of the parser that can be queried.
112    ///
113    /// This avoids a small potential indirection cost when querying the parser.
114    pub fn as_borrowed(&self) -> IanaParserBorrowed {
115        IanaParserBorrowed {
116            data: self.data.get(),
117            checksum: self.checksum,
118        }
119    }
120}
121
122impl AsRef<IanaParser> for IanaParser {
123    #[inline]
124    fn as_ref(&self) -> &IanaParser {
125        self
126    }
127}
128
129/// A borrowed wrapper around the time zone ID parser, returned by
130/// [`IanaParser::as_borrowed()`]. More efficient to query.
131#[derive(Debug, Copy, Clone)]
132pub struct IanaParserBorrowed<'a> {
133    data: &'a IanaToBcp47Map<'a>,
134    checksum: u64,
135}
136
137#[cfg(feature = "compiled_data")]
138impl Default for IanaParserBorrowed<'static> {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144impl IanaParserBorrowed<'static> {
145    /// Creates a new [`IanaParserBorrowed`] using compiled data.
146    ///
147    /// See [`IanaParserBorrowed`] for an example.
148    ///
149    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
150    ///
151    /// [📚 Help choosing a constructor](icu_provider::constructors)
152    #[cfg(feature = "compiled_data")]
153    pub fn new() -> Self {
154        Self {
155            data: crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_CORE_V1,
156            checksum: crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_CORE_V1_CHECKSUM,
157        }
158    }
159
160    /// Cheaply converts a [`IanaParserBorrowed<'static>`] into a [`IanaParser`].
161    ///
162    /// Note: Due to branching and indirection, using [`IanaParser`] might inhibit some
163    /// compile-time optimizations that are possible with [`IanaParserBorrowed`].
164    pub fn static_to_owned(&self) -> IanaParser {
165        IanaParser {
166            data: DataPayload::from_static_ref(self.data),
167            checksum: self.checksum,
168        }
169    }
170}
171
172impl<'a> IanaParserBorrowed<'a> {
173    /// Gets the [`TimeZone`] from a case-insensitive IANA time zone ID.
174    ///
175    /// Returns [`TimeZone::UNKNOWN`] if the IANA ID is not found.
176    ///
177    /// # Examples
178    ///
179    /// ```
180    /// use icu_time::zone::iana::IanaParser;
181    /// use icu_time::TimeZone;
182    ///
183    /// let parser = IanaParser::new();
184    ///
185    /// let result = parser.parse("Asia/CALCUTTA");
186    ///
187    /// assert_eq!(result.as_str(), "inccu");
188    ///
189    /// // Unknown IANA time zone ID:
190    /// assert_eq!(parser.parse("America/San_Francisco"), TimeZone::UNKNOWN);
191    /// ```
192    pub fn parse(&self, iana_id: &str) -> TimeZone {
193        self.parse_from_utf8(iana_id.as_bytes())
194    }
195
196    /// Same as [`Self::parse()`] but works with potentially ill-formed UTF-8.
197    pub fn parse_from_utf8(&self, iana_id: &[u8]) -> TimeZone {
198        let Some(trie_value) = self.trie_value(iana_id) else {
199            return TimeZone::UNKNOWN;
200        };
201        let Some(tz) = self.data.bcp47_ids.get(trie_value.index()) else {
202            debug_assert!(false, "index should be in range");
203            return TimeZone::UNKNOWN;
204        };
205        tz
206    }
207
208    fn trie_value(&self, iana_id: &[u8]) -> Option<IanaTrieValue> {
209        let mut cursor = self.data.map.cursor();
210        if !iana_id.contains(&b'/') {
211            cursor.step(NON_REGION_CITY_PREFIX);
212        }
213        for &b in iana_id {
214            cursor.step(b);
215        }
216        cursor.take_value().map(IanaTrieValue)
217    }
218
219    /// Returns an iterator over all known time zones.
220    ///
221    /// # Examples
222    ///
223    /// ```
224    /// use icu::locale::subtags::subtag;
225    /// use icu::time::zone::IanaParser;
226    /// use icu::time::zone::TimeZone;
227    /// use std::collections::BTreeSet;
228    ///
229    /// let parser = IanaParser::new();
230    ///
231    /// let ids = parser.iter().collect::<BTreeSet<_>>();
232    ///
233    /// assert!(ids.contains(&TimeZone(subtag!("uaiev"))));
234    /// ```
235    pub fn iter(&self) -> TimeZoneIter<'a> {
236        TimeZoneIter {
237            inner: self.data.bcp47_ids.iter(),
238        }
239    }
240}
241
242/// Returned by [`IanaParserBorrowed::iter()`]
243#[derive(Debug)]
244pub struct TimeZoneIter<'a> {
245    inner: ZeroSliceIter<'a, TimeZone>,
246}
247
248impl Iterator for TimeZoneIter<'_> {
249    type Item = TimeZone;
250
251    fn next(&mut self) -> Option<Self::Item> {
252        self.inner.next()
253    }
254}
255
256/// A parser that supplements [`IanaParser`] with about 10kB of additional data to support
257/// returning canonical and case-normalized IANA time zone IDs.
258#[derive(Debug, Clone)]
259pub struct IanaParserExtended<I> {
260    inner: I,
261    data: DataPayload<TimezoneIdentifiersIanaExtendedV1>,
262}
263
264impl IanaParserExtended<IanaParser> {
265    /// Creates a new [`IanaParserExtended`] using compiled data.
266    ///
267    /// See [`IanaParserExtended`] for an example.
268    ///
269    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
270    ///
271    /// [📚 Help choosing a constructor](icu_provider::constructors)
272    #[cfg(feature = "compiled_data")]
273    #[allow(clippy::new_ret_no_self)]
274    pub fn new() -> IanaParserExtendedBorrowed<'static> {
275        IanaParserExtendedBorrowed::new()
276    }
277
278    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
279        functions: [
280            new: skip,
281            try_new_with_buffer_provider,
282            try_new_unstable,
283            Self,
284        ]
285    );
286
287    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
288    pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
289    where
290        P: DataProvider<TimezoneIdentifiersIanaCoreV1>
291            + DataProvider<TimezoneIdentifiersIanaExtendedV1>
292            + ?Sized,
293    {
294        let parser = IanaParser::try_new_unstable(provider)?;
295        Self::try_new_with_parser_unstable(provider, parser)
296    }
297}
298
299impl<I> IanaParserExtended<I>
300where
301    I: AsRef<IanaParser>,
302{
303    /// Creates a new [`IanaParserExtended`] using compiled data
304    /// and a pre-existing [`IanaParser`], which can be borrowed.
305    ///
306    /// See [`IanaParserExtended`] for an example.
307    ///
308    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
309    ///
310    /// [📚 Help choosing a constructor](icu_provider::constructors)
311    #[cfg(feature = "compiled_data")]
312    pub fn try_new_with_parser(parser: I) -> Result<Self, DataError> {
313        if parser.as_ref().checksum
314            != crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_EXTENDED_V1_CHECKSUM
315        {
316            return Err(
317                DataErrorKind::InconsistentData(TimezoneIdentifiersIanaCoreV1::INFO)
318                    .with_marker(TimezoneIdentifiersIanaExtendedV1::INFO),
319            );
320        }
321        Ok(Self {
322            inner: parser,
323            data: DataPayload::from_static_ref(
324                crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_EXTENDED_V1,
325            ),
326        })
327    }
328
329    icu_provider::gen_buffer_data_constructors!((parser: I) -> error: DataError,
330        functions: [
331            try_new_with_parser: skip,
332            try_new_with_parser_with_buffer_provider,
333            try_new_with_parser_unstable,
334            Self,
335        ]
336    );
337
338    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
339    pub fn try_new_with_parser_unstable<P>(provider: &P, parser: I) -> Result<Self, DataError>
340    where
341        P: DataProvider<TimezoneIdentifiersIanaCoreV1>
342            + DataProvider<TimezoneIdentifiersIanaExtendedV1>
343            + ?Sized,
344    {
345        let response = provider.load(Default::default())?;
346        if Some(parser.as_ref().checksum) != response.metadata.checksum {
347            return Err(
348                DataErrorKind::InconsistentData(TimezoneIdentifiersIanaCoreV1::INFO)
349                    .with_marker(TimezoneIdentifiersIanaExtendedV1::INFO),
350            );
351        }
352        Ok(Self {
353            inner: parser,
354            data: response.payload,
355        })
356    }
357
358    /// Returns a borrowed version of the parser that can be queried.
359    ///
360    /// This avoids a small potential indirection cost when querying the parser.
361    pub fn as_borrowed(&self) -> IanaParserExtendedBorrowed {
362        IanaParserExtendedBorrowed {
363            inner: self.inner.as_ref().as_borrowed(),
364            data: self.data.get(),
365        }
366    }
367}
368
369/// A borrowed wrapper around the time zone ID parser, returned by
370/// [`IanaParserExtended::as_borrowed()`]. More efficient to query.
371#[derive(Debug, Copy, Clone)]
372pub struct IanaParserExtendedBorrowed<'a> {
373    inner: IanaParserBorrowed<'a>,
374    data: &'a IanaNames<'a>,
375}
376
377#[cfg(feature = "compiled_data")]
378impl Default for IanaParserExtendedBorrowed<'static> {
379    fn default() -> Self {
380        Self::new()
381    }
382}
383
384impl IanaParserExtendedBorrowed<'static> {
385    /// Creates a new [`IanaParserExtendedBorrowed`] using compiled data.
386    ///
387    /// See [`IanaParserExtendedBorrowed`] for an example.
388    ///
389    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
390    ///
391    /// [📚 Help choosing a constructor](icu_provider::constructors)
392    #[cfg(feature = "compiled_data")]
393    pub fn new() -> Self {
394        const _: () = assert!(
395            crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_CORE_V1_CHECKSUM
396                == crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_EXTENDED_V1_CHECKSUM,
397        );
398        Self {
399            inner: IanaParserBorrowed::new(),
400            data: crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_IANA_EXTENDED_V1,
401        }
402    }
403
404    /// Cheaply converts a [`IanaParserExtendedBorrowed<'static>`] into a [`IanaParserExtended`].
405    ///
406    /// Note: Due to branching and indirection, using [`IanaParserExtended`] might inhibit some
407    /// compile-time optimizations that are possible with [`IanaParserExtendedBorrowed`].
408    pub fn static_to_owned(&self) -> IanaParserExtended<IanaParser> {
409        IanaParserExtended {
410            inner: self.inner.static_to_owned(),
411            data: DataPayload::from_static_ref(self.data),
412        }
413    }
414}
415
416impl<'a> IanaParserExtendedBorrowed<'a> {
417    /// Gets the [`TimeZone`], the canonical IANA ID, and the case-normalized IANA ID from a case-insensitive IANA time zone ID.
418    ///
419    /// Returns `TimeZone::UNKNOWN` / `"Etc/Unknown"` if the IANA ID is not found.
420    ///
421    /// # Examples
422    ///
423    /// ```
424    /// use icu_time::zone::iana::IanaParserExtended;
425    /// use icu_time::TimeZone;
426    ///
427    /// let parser = IanaParserExtended::new();
428    ///
429    /// let r = parser.parse("Asia/CALCUTTA");
430    ///
431    /// assert_eq!(r.time_zone.as_str(), "inccu");
432    /// assert_eq!(r.canonical, "Asia/Kolkata");
433    /// assert_eq!(r.normalized, "Asia/Calcutta");
434    ///
435    /// // Unknown IANA time zone ID:
436    /// let r = parser.parse("America/San_Francisco");
437    ///
438    /// assert_eq!(r.time_zone, TimeZone::UNKNOWN);
439    /// assert_eq!(r.canonical, "Etc/Unknown");
440    /// assert_eq!(r.normalized, "Etc/Unknown");
441    /// ```
442    pub fn parse(&self, iana_id: &str) -> TimeZoneAndCanonicalAndNormalized<'a> {
443        self.parse_from_utf8(iana_id.as_bytes())
444    }
445
446    /// Same as [`Self::parse()`] but works with potentially ill-formed UTF-8.
447    pub fn parse_from_utf8(&self, iana_id: &[u8]) -> TimeZoneAndCanonicalAndNormalized<'a> {
448        let Some(trie_value) = self.inner.trie_value(iana_id) else {
449            return TimeZoneAndCanonicalAndNormalized::UKNONWN;
450        };
451        let Some(time_zone) = self.inner.data.bcp47_ids.get(trie_value.index()) else {
452            debug_assert!(false, "index should be in range");
453            return TimeZoneAndCanonicalAndNormalized::UKNONWN;
454        };
455        let Some(canonical) = self.data.normalized_iana_ids.get(trie_value.index()) else {
456            debug_assert!(false, "index should be in range");
457            return TimeZoneAndCanonicalAndNormalized::UKNONWN;
458        };
459        let normalized = if trie_value.is_canonical() {
460            canonical
461        } else {
462            let Some(Ok(index)) = self.data.normalized_iana_ids.binary_search_in_range_by(
463                |a| {
464                    a.as_bytes()
465                        .iter()
466                        .map(u8::to_ascii_lowercase)
467                        .cmp(iana_id.iter().map(u8::to_ascii_lowercase))
468                },
469                self.inner.data.bcp47_ids.len()..self.data.normalized_iana_ids.len(),
470            ) else {
471                debug_assert!(
472                    false,
473                    "binary search should succeed if trie lookup succeeds"
474                );
475                return TimeZoneAndCanonicalAndNormalized::UKNONWN;
476            };
477            let Some(normalized) = self
478                .data
479                .normalized_iana_ids
480                .get(self.inner.data.bcp47_ids.len() + index)
481            else {
482                debug_assert!(false, "binary search returns valid index");
483                return TimeZoneAndCanonicalAndNormalized::UKNONWN;
484            };
485            normalized
486        };
487        TimeZoneAndCanonicalAndNormalized {
488            time_zone,
489            canonical,
490            normalized,
491        }
492    }
493
494    /// Returns an iterator over all time zones and their canonical IANA identifiers.
495    ///
496    /// The iterator is sorted by the canonical IANA identifiers.
497    ///
498    /// # Examples
499    ///
500    /// ```
501    /// use icu::locale::subtags::subtag;
502    /// use icu::time::zone::iana::IanaParserExtended;
503    /// use icu::time::zone::TimeZone;
504    /// use std::collections::BTreeSet;
505    ///
506    /// let parser = IanaParserExtended::new();
507    ///
508    /// let ids = parser
509    ///     .iter()
510    ///     .map(|t| (t.time_zone, t.canonical))
511    ///     .collect::<BTreeSet<_>>();
512    ///
513    /// assert!(ids.contains(&(TimeZone(subtag!("uaiev")), "Europe/Kyiv")));
514    /// assert!(parser.iter().count() >= 445);
515    /// ```
516    pub fn iter(&self) -> TimeZoneAndCanonicalIter<'a> {
517        TimeZoneAndCanonicalIter(
518            self.inner
519                .data
520                .bcp47_ids
521                .iter()
522                .zip(self.data.normalized_iana_ids.iter()),
523        )
524    }
525
526    /// Returns an iterator equivalent to calling [`Self::parse`] on all IANA time zone identifiers.
527    ///
528    /// The only guarantee w.r.t iteration order is that for a given time zone, the canonical IANA
529    /// identifier will come first, and the following non-canonical IANA identifiers will be sorted.
530    /// However, the output is not grouped by time zone.
531    ///
532    /// The current implementation returns all sorted canonical IANA identifiers first, followed by all
533    /// sorted non-canonical identifiers, however this is subject to change.
534    ///
535    /// # Examples
536    ///
537    /// ```
538    /// use icu::time::zone::iana::IanaParserExtended;
539    /// use icu::time::zone::TimeZone;
540    /// use std::collections::BTreeMap;
541    /// use icu::locale::subtags::subtag;
542    ///
543    /// let parser = IanaParserExtended::new();
544    ///
545    /// let ids = parser.iter_all().enumerate().map(|(a, b)| ((b.time_zone, b.canonical, b.normalized), a)).collect::<std::collections::BTreeMap<_, _>>();
546    ///
547    /// let kyiv_idx = ids[&(TimeZone(subtag!("uaiev")), "Europe/Kyiv", "Europe/Kyiv")];
548    /// let kiev_idx = ids[&(TimeZone(subtag!("uaiev")), "Europe/Kyiv", "Europe/Kiev")];
549    /// let uzgh_idx = ids[&(TimeZone(subtag!("uaiev")), "Europe/Kyiv", "Europe/Uzhgorod")];
550    /// let zapo_idx = ids[&(TimeZone(subtag!("uaiev")), "Europe/Kyiv", "Europe/Zaporozhye")];
551    ///
552    /// // The order for a particular time zone is guaranteed
553    /// assert!(kyiv_idx < kiev_idx && kiev_idx < uzgh_idx && uzgh_idx < zapo_idx);
554    /// // It is not guaranteed that the entries for a particular time zone are consecutive
555    /// assert!(kyiv_idx + 1 != kiev_idx);
556    ///
557    /// assert!(parser.iter_all().count() >= 598);
558    /// ```
559    pub fn iter_all(&self) -> TimeZoneAndCanonicalAndNormalizedIter<'a> {
560        TimeZoneAndCanonicalAndNormalizedIter(0, *self)
561    }
562}
563
564/// Return value of [`IanaParserBorrowed::iter`].
565#[derive(Debug, Copy, Clone, PartialEq, Eq)]
566#[non_exhaustive]
567pub struct TimeZoneAndCanonical<'a> {
568    /// The parsed [`TimeZone`]
569    pub time_zone: TimeZone,
570    /// The canonical IANA ID
571    pub canonical: &'a str,
572}
573
574/// Return value of [`IanaParserExtendedBorrowed::parse`], [`IanaParserExtendedBorrowed::iter`].
575#[derive(Debug, Copy, Clone, PartialEq, Eq)]
576#[non_exhaustive]
577pub struct TimeZoneAndCanonicalAndNormalized<'a> {
578    /// The parsed [`TimeZone`]
579    pub time_zone: TimeZone,
580    /// The canonical IANA ID
581    pub canonical: &'a str,
582    /// The normalized IANA ID
583    pub normalized: &'a str,
584}
585
586impl TimeZoneAndCanonicalAndNormalized<'static> {
587    const UKNONWN: Self = TimeZoneAndCanonicalAndNormalized {
588        time_zone: TimeZone::UNKNOWN,
589        canonical: "Etc/Unknown",
590        normalized: "Etc/Unknown",
591    };
592}
593
594/// The iterator returned by [`IanaParserExtendedBorrowed::iter()`]
595#[derive(Debug)]
596pub struct TimeZoneAndCanonicalIter<'a>(
597    core::iter::Zip<ZeroSliceIter<'a, TimeZone>, VarZeroSliceIter<'a, str>>,
598);
599
600impl<'a> Iterator for TimeZoneAndCanonicalIter<'a> {
601    type Item = TimeZoneAndCanonical<'a>;
602
603    fn next(&mut self) -> Option<Self::Item> {
604        let (time_zone, canonical) = self.0.next()?;
605        Some(TimeZoneAndCanonical {
606            time_zone,
607            canonical,
608        })
609    }
610}
611
612/// The iterator returned by [`IanaParserExtendedBorrowed::iter_all()`]
613#[derive(Debug)]
614pub struct TimeZoneAndCanonicalAndNormalizedIter<'a>(usize, IanaParserExtendedBorrowed<'a>);
615
616impl<'a> Iterator for TimeZoneAndCanonicalAndNormalizedIter<'a> {
617    type Item = TimeZoneAndCanonicalAndNormalized<'a>;
618
619    fn next(&mut self) -> Option<Self::Item> {
620        if let (Some(time_zone), Some(canonical)) = (
621            self.1.inner.data.bcp47_ids.get(self.0),
622            self.1.data.normalized_iana_ids.get(self.0),
623        ) {
624            self.0 += 1;
625            Some(TimeZoneAndCanonicalAndNormalized {
626                time_zone,
627                canonical,
628                normalized: canonical,
629            })
630        } else if let Some(normalized) = self.1.data.normalized_iana_ids.get(self.0) {
631            let Some(trie_value) = self.1.inner.trie_value(normalized.as_bytes()) else {
632                debug_assert!(false, "normalized value should be in trie");
633                return None;
634            };
635            let (Some(time_zone), Some(canonical)) = (
636                self.1.inner.data.bcp47_ids.get(trie_value.index()),
637                self.1.data.normalized_iana_ids.get(trie_value.index()),
638            ) else {
639                debug_assert!(false, "index should be in range");
640                return None;
641            };
642            self.0 += 1;
643            Some(TimeZoneAndCanonicalAndNormalized {
644                time_zone,
645                canonical,
646                normalized,
647            })
648        } else {
649            None
650        }
651    }
652}
653
654#[derive(Copy, Clone, PartialEq, Eq)]
655#[repr(transparent)]
656struct IanaTrieValue(usize);
657
658impl IanaTrieValue {
659    #[inline]
660    pub(crate) fn index(self) -> usize {
661        self.0 >> 1
662    }
663    #[inline]
664    pub(crate) fn is_canonical(self) -> bool {
665        (self.0 & 0x1) != 0
666    }
667}