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}