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}