icu_time/zone/windows.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 Windows timezone IDs.
6
7use core::fmt::Write;
8use icu_locale_core::subtags::{region, Region};
9use icu_provider::{DataError, DataPayload, DataProvider};
10
11use crate::{
12 provider::windows::{TimezoneIdentifiersWindowsV1, WindowsZonesToBcp47Map},
13 TimeZone,
14};
15
16/// A mapper between Windows time zone identifier and a BCP-47 ID.
17///
18/// This mapper currently only supports mapping from windows time zone identifiers
19/// to BCP-47 identifiers.
20///
21/// A windows time zone may vary depending on an associated territory/region. This is represented
22/// by the internal data mapping by delimiting the windows time zone and territory/region
23/// code with a "/".
24///
25/// For instance, Central Standard Time can vary depending on the provided regions listed below:
26///
27/// - Central Standard Time/001
28/// - Central Standard Time/US
29/// - Central Standard Time/CA
30/// - Central Standard Time/MX
31/// - Central Standard Time/ZZ
32///
33/// As such, a [`Region`] may be provided to further specify a desired territory/region when
34/// querying a BCP-47 identifier. If no region is provided or the specificity is not required,
35/// then the territory will default to the M.49 World Code, `001`.
36#[derive(Debug)]
37pub struct WindowsParser {
38 data: DataPayload<TimezoneIdentifiersWindowsV1>,
39}
40
41impl WindowsParser {
42 /// Creates a new static [`WindowsParserBorrowed`].
43 #[allow(clippy::new_ret_no_self)]
44 #[cfg(feature = "compiled_data")]
45 pub fn new() -> WindowsParserBorrowed<'static> {
46 WindowsParserBorrowed::new()
47 }
48
49 icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
50 functions: [
51 new: skip,
52 try_new_with_buffer_provider,
53 try_new_unstable,
54 Self,
55 ]
56 );
57
58 #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
59 pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
60 where
61 P: DataProvider<TimezoneIdentifiersWindowsV1> + ?Sized,
62 {
63 let data = provider.load(Default::default())?.payload;
64 Ok(Self { data })
65 }
66
67 /// Returns the borrowed version of the mapper that can be queried from
68 /// the owned mapper.
69 ///
70 /// Using the borrowed version allows one to avoid a small potential
71 /// indirection cost when querying the mapper from the owned version.
72 pub fn as_borrowed(&self) -> WindowsParserBorrowed {
73 WindowsParserBorrowed {
74 data: self.data.get(),
75 }
76 }
77}
78
79/// A borrowed wrapper around the windows time zone mapper data.
80#[derive(Debug, Copy, Clone)]
81pub struct WindowsParserBorrowed<'a> {
82 data: &'a WindowsZonesToBcp47Map<'a>,
83}
84
85impl WindowsParserBorrowed<'static> {
86 /// Cheaply converts a [`WindowsParserBorrowed<'static>`] into a [`WindowsParser`].
87 ///
88 /// Note: Due to branching and indirection, using [`WindowsParser`] might inhibit some
89 /// compile-time optimizations that are possible with [`WindowsParserBorrowed`].
90 pub fn static_to_owned(&self) -> WindowsParser {
91 WindowsParser {
92 data: DataPayload::from_static_ref(self.data),
93 }
94 }
95}
96
97#[cfg(feature = "compiled_data")]
98impl Default for WindowsParserBorrowed<'_> {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104impl WindowsParserBorrowed<'_> {
105 /// Creates a new static [`WindowsParserBorrowed`].
106 #[cfg(feature = "compiled_data")]
107 pub fn new() -> Self {
108 WindowsParserBorrowed {
109 data: crate::provider::Baked::SINGLETON_TIMEZONE_IDENTIFIERS_WINDOWS_V1,
110 }
111 }
112
113 /// Returns the BCP-47 ID for a provided Windows time zone and [`Region`] with a case sensitive query.
114 ///
115 /// If no region is provided or the specificity is not required,
116 /// then the territory will default to the M.49 World Code, `001`.
117 ///
118 /// ```rust
119 /// use icu::locale::subtags::{region, subtag};
120 /// use icu::time::{zone::WindowsParser, TimeZone};
121 ///
122 /// let win_tz_mapper = WindowsParser::new();
123 ///
124 /// let bcp47_id = win_tz_mapper.parse("Central Standard Time", None);
125 /// assert_eq!(bcp47_id, Some(TimeZone(subtag!("uschi"))));
126 ///
127 /// let bcp47_id =
128 /// win_tz_mapper.parse("Central Standard Time", Some(region!("US")));
129 /// assert_eq!(bcp47_id, Some(TimeZone(subtag!("uschi"))));
130 ///
131 /// let bcp47_id =
132 /// win_tz_mapper.parse("Central Standard Time", Some(region!("CA")));
133 /// assert_eq!(bcp47_id, Some(TimeZone(subtag!("cawnp"))));
134 /// ```
135 pub fn parse(self, windows_tz: &str, region: Option<Region>) -> Option<TimeZone> {
136 self.parse_from_utf8(windows_tz.as_bytes(), region)
137 }
138
139 /// See [`Self::parse`].
140 pub fn parse_from_utf8(self, windows_tz: &[u8], region: Option<Region>) -> Option<TimeZone> {
141 let mut cursor = self.data.map.cursor();
142 // Returns None if input is non-ASCII
143 for &byte in windows_tz {
144 cursor.step(byte);
145 }
146 cursor.step(b'/');
147 cursor
148 .write_str(region.unwrap_or(region!("001")).as_str())
149 // region is valid ASCII, but we can do this instead of unwrap
150 .ok()?;
151 self.data.bcp47_ids.get(cursor.take_value()?)
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use icu::locale::subtags::subtag;
159
160 #[test]
161 fn basic_windows_tz_lookup() {
162 let win_map = WindowsParser::new();
163
164 let result = win_map.parse("Central Standard Time", None);
165 assert_eq!(result, Some(TimeZone(subtag!("uschi"))));
166
167 let result = win_map.parse("Eastern Standard Time", None);
168 assert_eq!(result, Some(TimeZone(subtag!("usnyc"))));
169
170 let result = win_map.parse("Eastern Standard Time", Some(region!("CA")));
171 assert_eq!(result, Some(TimeZone(subtag!("cator"))));
172
173 let result = win_map.parse("GMT Standard Time", None);
174 assert_eq!(result, Some(TimeZone(subtag!("gblon"))));
175 }
176}