time_tz/
system.rs

1// Copyright (c) 2022, Yuri6037
2//
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without modification,
6// are permitted provided that the following conditions are met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13// * Neither the name of time-tz nor the names of its contributors
14// may be used to endorse or promote products derived from this software
15// without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29//! Support for getting time zone information from the target system.
30//! 
31//! Currently only supported for Windows, Unix, and WASM targets.
32
33use crate::timezones::get_by_name;
34use crate::Tz;
35use thiserror::Error;
36
37#[cfg(target_family = "wasm")]
38use js_sys::{ Intl, Reflect, Array, Object };
39#[cfg(target_family = "wasm")]
40use wasm_bindgen::JsValue;
41
42#[derive(Debug, Error)]
43pub enum Error {
44    /// An IO error has occurred.
45    #[error("io error: {0}")]
46    Io(std::io::Error),
47
48    /// An OS level error has occurred (can only happen on Windows).
49    #[error("low-level os error")]
50    Os,
51
52    /// The timezone is undetermined (means the timezone is not defined or that the system
53    /// itself doesn't know the its timezone).
54    #[error("undefined timezone")]
55    Undetermined,
56
57    /// Somehow the read timezone name contains non unicode...
58    #[error("timezone name is not unicode")]
59    Unicode,
60
61    /// The timezone doesn't exist in the crate's database.
62    #[error("unknown timezone name")]
63    Unknown,
64    
65    /// The target platform is not supported. Windows, Unix, and WASM targets are the only supported for the system feature at this moment.
66    #[error("unsupported platform")]
67    Unsupported,
68}
69
70/// Gets the current timezone from the system.
71/// 
72/// Currently only supported for Windows, Unix, and WASM targets.
73/// 
74/// # Errors
75/// Returns an [Error](enum@Error) if the timezone cannot be determined.
76pub fn get_timezone() -> Result<&'static Tz, Error> {
77    cfg_if::cfg_if! {
78        if #[cfg(unix)] {
79            use std::path::Path;
80            let path = Path::new("/etc/localtime");
81            let realpath = std::fs::read_link(path).map_err(Error::Io)?;
82            // The part of the path we're interested in cannot contain non unicode characters.
83            if let Some(iana) = realpath.to_str().ok_or(Error::Unicode)?.split("/zoneinfo/").last() {
84                let tz = get_by_name(iana).ok_or(Error::Unknown)?;
85                Ok(tz)
86            } else {
87                Err(Error::Undetermined)
88            }
89        } else if #[cfg(windows)] {
90            unsafe {
91                use windows_sys::Win32::System::Time::GetDynamicTimeZoneInformation;
92                use windows_sys::Win32::System::Time::DYNAMIC_TIME_ZONE_INFORMATION;
93                let mut data: DYNAMIC_TIME_ZONE_INFORMATION = std::mem::zeroed();
94                let res = GetDynamicTimeZoneInformation(&mut data as _);
95                if res > 2 {
96                    return Err(Error::Os);
97                } else {
98                    let win_name_utf16 = &data.TimeZoneKeyName;
99                    let mut len: usize = 0;
100                    while win_name_utf16[len] != 0x0 {
101                        len += 1;
102                    }
103                    if len == 0 {
104                        return Err(Error::Undetermined);
105                    }
106                    let win_tz = String::from_utf16(&win_name_utf16[..len]).map_err(|_| Error::Unicode)?;
107                    let tz = get_by_name(&win_tz).ok_or(Error::Unknown)?;
108                    Ok(tz)
109                }
110            }
111        } else if #[cfg(target_family = "wasm")] {
112            let options = Intl::DateTimeFormat::new(&Array::new(), &Object::new())
113                .resolved_options();
114
115            let tz = Reflect::get(&options, &JsValue::from("timeZone"))
116                .map_err(|_| Error::Undetermined)?
117                .as_string()
118                .ok_or(Error::Unicode)?;
119
120            let tz = get_by_name(&tz).ok_or(Error::Unknown)?;
121            Ok(tz)
122        } else {
123            Err(Error::Unsupported)
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    #[test]
131    fn get_timezone() {
132        let tz = super::get_timezone();
133        assert!(tz.is_ok());
134    }
135}