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}