feat: add dependency
This commit is contained in:
49
javascript-engine/external/iana-time-zone/src/ffi_utils.rs
vendored
Normal file
49
javascript-engine/external/iana-time-zone/src/ffi_utils.rs
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
//! Cross platform FFI helpers.
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
// The system property named 'persist.sys.timezone' contains the name of the
|
||||
// current timezone.
|
||||
//
|
||||
// From https://android.googlesource.com/platform/bionic/+/gingerbread-release/libc/docs/OVERVIEW.TXT#79:
|
||||
//
|
||||
// > The name of the current timezone is taken from the TZ environment variable,
|
||||
// > if defined. Otherwise, the system property named 'persist.sys.timezone' is
|
||||
// > checked instead.
|
||||
const ANDROID_TIMEZONE_PROPERTY_NAME: &[u8] = b"persist.sys.timezone\0";
|
||||
|
||||
/// Return a [`CStr`] to access the timezone from an Android system properties
|
||||
/// environment.
|
||||
pub(crate) fn android_timezone_property_name() -> &'static CStr {
|
||||
// In tests or debug mode, opt into extra runtime checks.
|
||||
if cfg!(any(test, debug_assertions)) {
|
||||
return CStr::from_bytes_with_nul(ANDROID_TIMEZONE_PROPERTY_NAME).unwrap();
|
||||
}
|
||||
|
||||
// SAFETY: the key is NUL-terminated and there are no other NULs, this
|
||||
// invariant is checked in tests.
|
||||
unsafe { CStr::from_bytes_with_nul_unchecked(ANDROID_TIMEZONE_PROPERTY_NAME) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::CStr;
|
||||
|
||||
use super::{android_timezone_property_name, ANDROID_TIMEZONE_PROPERTY_NAME};
|
||||
|
||||
#[test]
|
||||
fn test_android_timezone_property_name_is_valid_cstr() {
|
||||
CStr::from_bytes_with_nul(ANDROID_TIMEZONE_PROPERTY_NAME).unwrap();
|
||||
|
||||
let mut invalid_property_name = ANDROID_TIMEZONE_PROPERTY_NAME.to_owned();
|
||||
invalid_property_name.push(b'\0');
|
||||
CStr::from_bytes_with_nul(&invalid_property_name).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_android_timezone_property_name_getter() {
|
||||
let key = android_timezone_property_name().to_bytes_with_nul();
|
||||
assert_eq!(key, ANDROID_TIMEZONE_PROPERTY_NAME);
|
||||
std::str::from_utf8(key).unwrap();
|
||||
}
|
||||
}
|
||||
113
javascript-engine/external/iana-time-zone/src/lib.rs
vendored
Normal file
113
javascript-engine/external/iana-time-zone/src/lib.rs
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
#![warn(clippy::all)]
|
||||
#![warn(clippy::cargo)]
|
||||
#![warn(clippy::undocumented_unsafe_blocks)]
|
||||
#![allow(unknown_lints)]
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![warn(trivial_casts, trivial_numeric_casts)]
|
||||
#![warn(unused_qualifications)]
|
||||
#![warn(variant_size_differences)]
|
||||
|
||||
//! get the IANA time zone for the current system
|
||||
//!
|
||||
//! This small utility crate provides the
|
||||
//! [`get_timezone()`](fn.get_timezone.html) function.
|
||||
//!
|
||||
//! ```rust
|
||||
//! // Get the current time zone as a string.
|
||||
//! let tz_str = iana_time_zone::get_timezone()?;
|
||||
//! println!("The current time zone is: {}", tz_str);
|
||||
//! # Ok::<(), iana_time_zone::GetTimezoneError>(())
|
||||
//! ```
|
||||
//!
|
||||
//! The resulting string can be parsed to a
|
||||
//! [`chrono-tz::Tz`](https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)
|
||||
//! variant like this:
|
||||
//! ```ignore
|
||||
//! let tz_str = iana_time_zone::get_timezone()?;
|
||||
//! let tz: chrono_tz::Tz = tz_str.parse()?;
|
||||
//! ```
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod ffi_utils;
|
||||
|
||||
#[cfg_attr(target_os = "linux", path = "tz_linux.rs")]
|
||||
#[cfg_attr(target_os = "windows", path = "tz_windows.rs")]
|
||||
#[cfg_attr(any(target_os = "macos", target_os = "ios"), path = "tz_macos.rs")]
|
||||
#[cfg_attr(
|
||||
all(target_arch = "wasm32", not(target_os = "wasi")),
|
||||
path = "tz_wasm32.rs"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
any(target_os = "freebsd", target_os = "dragonfly"),
|
||||
path = "tz_freebsd.rs"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
any(target_os = "netbsd", target_os = "openbsd"),
|
||||
path = "tz_netbsd.rs"
|
||||
)]
|
||||
#[cfg_attr(
|
||||
any(target_os = "illumos", target_os = "solaris"),
|
||||
path = "tz_illumos.rs"
|
||||
)]
|
||||
#[cfg_attr(target_os = "android", path = "tz_android.rs")]
|
||||
#[cfg_attr(target_os = "haiku", path = "tz_haiku.rs")]
|
||||
mod platform;
|
||||
|
||||
/// Error types
|
||||
#[derive(Debug)]
|
||||
pub enum GetTimezoneError {
|
||||
/// Failed to parse
|
||||
FailedParsingString,
|
||||
/// Wrapped IO error
|
||||
IoError(std::io::Error),
|
||||
/// Platform-specific error from the operating system
|
||||
OsError,
|
||||
}
|
||||
|
||||
impl std::error::Error for GetTimezoneError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
GetTimezoneError::FailedParsingString => None,
|
||||
GetTimezoneError::IoError(err) => Some(err),
|
||||
GetTimezoneError::OsError => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GetTimezoneError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.write_str(match self {
|
||||
GetTimezoneError::FailedParsingString => "GetTimezoneError::FailedParsingString",
|
||||
GetTimezoneError::IoError(err) => return err.fmt(f),
|
||||
GetTimezoneError::OsError => "OsError",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for GetTimezoneError {
|
||||
fn from(orig: std::io::Error) -> Self {
|
||||
GetTimezoneError::IoError(orig)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current IANA time zone as a string.
|
||||
///
|
||||
/// See the module-level documentatation for a usage example and more details
|
||||
/// about this function.
|
||||
#[inline]
|
||||
pub fn get_timezone() -> Result<String, GetTimezoneError> {
|
||||
platform::get_timezone_inner()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_current() {
|
||||
println!("current: {}", get_timezone().unwrap());
|
||||
}
|
||||
}
|
||||
9
javascript-engine/external/iana-time-zone/src/platform.rs
vendored
Normal file
9
javascript-engine/external/iana-time-zone/src/platform.rs
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
pub fn get_timezone_inner() -> std::result::Result<String, crate::GetTimezoneError> {
|
||||
Err(crate::GetTimezoneError::OsError)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "fallback"))]
|
||||
compile_error!(
|
||||
"iana-time-zone is currently implemented for Linux, Window, MacOS, FreeBSD, NetBSD, \
|
||||
OpenBSD, Dragonfly, WebAssembly (browser), iOS, Illumos, Android, Solaris and Haiku.",
|
||||
);
|
||||
27
javascript-engine/external/iana-time-zone/src/tz_android.rs
vendored
Normal file
27
javascript-engine/external/iana-time-zone/src/tz_android.rs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::sync::Once;
|
||||
|
||||
use android_system_properties::AndroidSystemProperties;
|
||||
|
||||
use crate::ffi_utils::android_timezone_property_name;
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
let key = android_timezone_property_name();
|
||||
|
||||
get_properties()
|
||||
.and_then(|properties| properties.get_from_cstr(key))
|
||||
.ok_or(crate::GetTimezoneError::OsError)
|
||||
}
|
||||
|
||||
fn get_properties() -> Option<&'static AndroidSystemProperties> {
|
||||
static INITIALIZED: Once = Once::new();
|
||||
static mut PROPERTIES: Option<AndroidSystemProperties> = None;
|
||||
|
||||
INITIALIZED.call_once(|| {
|
||||
let properties = AndroidSystemProperties::new();
|
||||
// SAFETY: `INITIALIZED` is synchronizing. The variable is only assigned to once.
|
||||
unsafe { PROPERTIES = Some(properties) };
|
||||
});
|
||||
|
||||
// SAFETY: `INITIALIZED` is synchronizing. The variable is only assigned to once.
|
||||
unsafe { PROPERTIES.as_ref() }
|
||||
}
|
||||
7
javascript-engine/external/iana-time-zone/src/tz_freebsd.rs
vendored
Normal file
7
javascript-engine/external/iana-time-zone/src/tz_freebsd.rs
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
// see https://gitlab.gnome.org/GNOME/evolution-data-server/-/issues/19
|
||||
let mut contents = std::fs::read_to_string("/var/db/zoneinfo")?;
|
||||
// Trim to the correct length without allocating.
|
||||
contents.truncate(contents.trim_end().len());
|
||||
Ok(contents)
|
||||
}
|
||||
3
javascript-engine/external/iana-time-zone/src/tz_haiku.rs
vendored
Normal file
3
javascript-engine/external/iana-time-zone/src/tz_haiku.rs
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
iana_time_zone_haiku::get_timezone().ok_or(crate::GetTimezoneError::OsError)
|
||||
}
|
||||
22
javascript-engine/external/iana-time-zone/src/tz_illumos.rs
vendored
Normal file
22
javascript-engine/external/iana-time-zone/src/tz_illumos.rs
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
// https://illumos.org/man/5/TIMEZONE
|
||||
// https://docs.oracle.com/cd/E23824_01/html/821-1473/uc-timezone-4.html
|
||||
|
||||
let file = OpenOptions::new().read(true).open("/etc/default/init")?;
|
||||
let mut reader = BufReader::with_capacity(1536, file);
|
||||
let mut line = String::with_capacity(80);
|
||||
loop {
|
||||
line.clear();
|
||||
let count = reader.read_line(&mut line)?;
|
||||
if count == 0 {
|
||||
return Err(crate::GetTimezoneError::FailedParsingString);
|
||||
} else if line.starts_with("TZ=") {
|
||||
line.truncate(line.trim_end().len());
|
||||
line.replace_range(..3, "");
|
||||
return Ok(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
javascript-engine/external/iana-time-zone/src/tz_linux.rs
vendored
Normal file
45
javascript-engine/external/iana-time-zone/src/tz_linux.rs
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::fs::{read_link, read_to_string};
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
etc_localtime().or_else(|_| etc_timezone())
|
||||
}
|
||||
|
||||
fn etc_timezone() -> Result<String, crate::GetTimezoneError> {
|
||||
// see https://stackoverflow.com/a/12523283
|
||||
let mut contents = read_to_string("/etc/timezone")?;
|
||||
// Trim to the correct length without allocating.
|
||||
contents.truncate(contents.trim_end().len());
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
fn etc_localtime() -> Result<String, crate::GetTimezoneError> {
|
||||
// Per <https://www.man7.org/linux/man-pages/man5/localtime.5.html>:
|
||||
// “ The /etc/localtime file configures the system-wide timezone of the local system that is
|
||||
// used by applications for presentation to the user. It should be an absolute or relative
|
||||
// symbolic link pointing to /usr/share/zoneinfo/, followed by a timezone identifier such as
|
||||
// "Europe/Berlin" or "Etc/UTC". The resulting link should lead to the corresponding binary
|
||||
// tzfile(5) timezone data for the configured timezone. ”
|
||||
|
||||
// Systemd does not canonicalize the link, but only checks if it is prefixed by
|
||||
// "/usr/share/zoneinfo/" or "../usr/share/zoneinfo/". So we do the same.
|
||||
// <https://github.com/systemd/systemd/blob/9102c625a673a3246d7e73d8737f3494446bad4e/src/basic/time-util.c#L1493>
|
||||
|
||||
const PREFIXES: &[&str] = &[
|
||||
"/usr/share/zoneinfo/", // absolute path
|
||||
"../usr/share/zoneinfo/", // relative path
|
||||
"/etc/zoneinfo/", // absolute path for NixOS
|
||||
"../etc/zoneinfo/", // relative path for NixOS
|
||||
];
|
||||
let mut s = read_link("/etc/localtime")?
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.map_err(|_| crate::GetTimezoneError::FailedParsingString)?;
|
||||
for &prefix in PREFIXES {
|
||||
if s.starts_with(prefix) {
|
||||
// Trim to the correct length without allocating.
|
||||
s.replace_range(..prefix.len(), "");
|
||||
return Ok(s);
|
||||
}
|
||||
}
|
||||
Err(crate::GetTimezoneError::FailedParsingString)
|
||||
}
|
||||
144
javascript-engine/external/iana-time-zone/src/tz_macos.rs
vendored
Normal file
144
javascript-engine/external/iana-time-zone/src/tz_macos.rs
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
get_timezone().ok_or(crate::GetTimezoneError::OsError)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_timezone() -> Option<String> {
|
||||
// The longest name in the IANA time zone database is 25 ASCII characters long.
|
||||
const MAX_LEN: usize = 32;
|
||||
let mut buf = [0; MAX_LEN];
|
||||
|
||||
// Get system time zone, and borrow its name.
|
||||
let tz = system_time_zone::SystemTimeZone::new()?;
|
||||
let name = tz.name()?;
|
||||
|
||||
// If the name is encoded in UTF-8, copy it directly.
|
||||
let name = if let Some(name) = name.as_utf8() {
|
||||
name
|
||||
} else {
|
||||
// Otherwise convert the name to UTF-8.
|
||||
name.to_utf8(&mut buf)?
|
||||
};
|
||||
|
||||
if name.is_empty() || name.len() >= MAX_LEN {
|
||||
// The name should not be empty, or excessively long.
|
||||
None
|
||||
} else {
|
||||
Some(name.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
mod system_time_zone {
|
||||
//! create a safe wrapper around `CFTimeZoneRef`
|
||||
|
||||
use core_foundation_sys::base::{CFRelease, CFTypeRef};
|
||||
use core_foundation_sys::timezone::{CFTimeZoneCopySystem, CFTimeZoneGetName, CFTimeZoneRef};
|
||||
|
||||
pub(crate) struct SystemTimeZone(CFTimeZoneRef);
|
||||
|
||||
impl Drop for SystemTimeZone {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`.
|
||||
unsafe { CFRelease(self.0 as CFTypeRef) };
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemTimeZone {
|
||||
pub(crate) fn new() -> Option<Self> {
|
||||
// SAFETY: No invariants to uphold. We'll release the pointer when we don't need it anymore.
|
||||
let v: CFTimeZoneRef = unsafe { CFTimeZoneCopySystem() };
|
||||
if v.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(SystemTimeZone(v))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the time zone name as a [super::string_ref::StringRef].
|
||||
///
|
||||
/// The lifetime of the `StringRef` is bound to our lifetime. Mutable
|
||||
/// access is also prevented by taking a reference to `self`.
|
||||
pub(crate) fn name(&self) -> Option<super::string_ref::StringRef<'_, Self>> {
|
||||
// SAFETY: `SystemTimeZone` is only ever created with a valid `CFTimeZoneRef`.
|
||||
let string = unsafe { CFTimeZoneGetName(self.0) };
|
||||
if string.is_null() {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: here we ensure that `string` is a valid pointer.
|
||||
Some(unsafe { super::string_ref::StringRef::new(string, self) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod string_ref {
|
||||
//! create safe wrapper around `CFStringRef`
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use core_foundation_sys::base::{Boolean, CFRange};
|
||||
use core_foundation_sys::string::{
|
||||
kCFStringEncodingUTF8, CFStringGetBytes, CFStringGetCStringPtr, CFStringGetLength,
|
||||
CFStringRef,
|
||||
};
|
||||
|
||||
pub(crate) struct StringRef<'a, T> {
|
||||
string: CFStringRef,
|
||||
// We exclude mutable access to the parent by taking a reference to the
|
||||
// parent (rather than, for example, just using a marker to enforce the
|
||||
// parent's lifetime).
|
||||
_parent: &'a T,
|
||||
}
|
||||
|
||||
impl<'a, T> StringRef<'a, T> {
|
||||
// SAFETY: `StringRef` must be valid pointer
|
||||
pub(crate) unsafe fn new(string: CFStringRef, _parent: &'a T) -> Self {
|
||||
Self { string, _parent }
|
||||
}
|
||||
|
||||
pub(crate) fn as_utf8(&self) -> Option<&'a str> {
|
||||
// SAFETY: `StringRef` is only ever created with a valid `CFStringRef`.
|
||||
let v = unsafe { CFStringGetCStringPtr(self.string, kCFStringEncodingUTF8) };
|
||||
if !v.is_null() {
|
||||
// SAFETY: `CFStringGetCStringPtr()` returns NUL-terminated strings.
|
||||
let v = unsafe { std::ffi::CStr::from_ptr(v) };
|
||||
if let Ok(v) = v.to_str() {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn to_utf8<'b>(&self, buf: &'b mut [u8]) -> Option<&'b str> {
|
||||
// SAFETY: `StringRef` is only ever created with a valid `CFStringRef`.
|
||||
let length = unsafe { CFStringGetLength(self.string) };
|
||||
|
||||
let mut buf_bytes = 0;
|
||||
let range = CFRange {
|
||||
location: 0,
|
||||
length,
|
||||
};
|
||||
|
||||
let converted_bytes = unsafe {
|
||||
// SAFETY: `StringRef` is only ever created with a valid `CFStringRef`.
|
||||
CFStringGetBytes(
|
||||
self.string,
|
||||
range,
|
||||
kCFStringEncodingUTF8,
|
||||
b'\0',
|
||||
false as Boolean,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as isize,
|
||||
&mut buf_bytes,
|
||||
)
|
||||
};
|
||||
if converted_bytes != length {
|
||||
return None;
|
||||
}
|
||||
|
||||
let len = buf_bytes.try_into().ok()?;
|
||||
let s = buf.get(..len)?;
|
||||
std::str::from_utf8(s).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
25
javascript-engine/external/iana-time-zone/src/tz_netbsd.rs
vendored
Normal file
25
javascript-engine/external/iana-time-zone/src/tz_netbsd.rs
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
use std::fs::read_link;
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
// see https://www.cyberciti.biz/faq/openbsd-time-zone-howto/
|
||||
|
||||
// This is a backport of the Linux implementation.
|
||||
// NetBSDs is less than thorough how the softlink should be set up.
|
||||
|
||||
const PREFIXES: &[&str] = &[
|
||||
"/usr/share/zoneinfo/", // absolute path
|
||||
"../usr/share/zoneinfo/", // relative path
|
||||
];
|
||||
let mut s = read_link("/etc/localtime")?
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.map_err(|_| crate::GetTimezoneError::FailedParsingString)?;
|
||||
for &prefix in PREFIXES {
|
||||
if s.starts_with(prefix) {
|
||||
// Trim to the correct length without allocating.
|
||||
s.replace_range(..prefix.len(), "");
|
||||
return Ok(s);
|
||||
}
|
||||
}
|
||||
Err(crate::GetTimezoneError::FailedParsingString)
|
||||
}
|
||||
21
javascript-engine/external/iana-time-zone/src/tz_wasm32.rs
vendored
Normal file
21
javascript-engine/external/iana-time-zone/src/tz_wasm32.rs
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
use js_sys::{Array, Intl, Object, Reflect};
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
let intl = Intl::DateTimeFormat::new(&Array::new(), &Object::new()).resolved_options();
|
||||
Reflect::get(&intl, &JsValue::from_str("timeZone"))
|
||||
.ok()
|
||||
.and_then(|tz| tz.as_string())
|
||||
.ok_or(crate::GetTimezoneError::OsError)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
let tz = super::get_timezone_inner().unwrap();
|
||||
console_log!("tz={:?}", tz);
|
||||
}
|
||||
}
|
||||
312
javascript-engine/external/iana-time-zone/src/tz_windows.rs
vendored
Normal file
312
javascript-engine/external/iana-time-zone/src/tz_windows.rs
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
use std::mem::zeroed;
|
||||
|
||||
use windows_sys::core::{GUID, HRESULT};
|
||||
use windows_sys::Win32::System::Com::CoIncrementMTAUsage;
|
||||
use windows_sys::Win32::System::WinRT::HSTRING_HEADER;
|
||||
|
||||
use self::hstring::HString;
|
||||
use self::instance::Instance;
|
||||
use self::tz_on_calendar::TzOnCalendar;
|
||||
|
||||
macro_rules! wstring {
|
||||
($($letters:tt)+) => {
|
||||
[ $($letters as _,)+ ]
|
||||
};
|
||||
}
|
||||
|
||||
const WINDOWS_GLOBALIZATION_CALENDAR: &[u16] = &wstring!(
|
||||
'W' 'i' 'n' 'd' 'o' 'w' 's' '.'
|
||||
'G' 'l' 'o' 'b' 'a' 'l' 'i' 'z' 'a' 't' 'i' 'o' 'n' '.'
|
||||
'C' 'a' 'l' 'e' 'n' 'd' 'a' 'r'
|
||||
0
|
||||
);
|
||||
|
||||
const TIMEZONE_ON_CALENDAR_GUID: GUID = GUID {
|
||||
data1: 0xbb3c25e5,
|
||||
data2: 0x46cf,
|
||||
data3: 0x4317,
|
||||
data4: [0xa3, 0xf5, 0x02, 0x62, 0x1a, 0xd5, 0x44, 0x78],
|
||||
};
|
||||
|
||||
const CO_E_NOTINITIALIZED: HRESULT = -2147221008;
|
||||
|
||||
impl From<HRESULT> for crate::GetTimezoneError {
|
||||
fn from(orig: HRESULT) -> Self {
|
||||
std::io::Error::from_raw_os_error(orig).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_timezone_inner() -> Result<String, crate::GetTimezoneError> {
|
||||
// Get HSTRING for "Windows.Globalization.Calendar".
|
||||
// SAFETY: An `HSTRING_HEADER` actually does not need initialization when used with
|
||||
// `WindowsCreateStringReference()`, but zeroing it is as good a initial value as any.
|
||||
let mut string_header: HSTRING_HEADER = unsafe { zeroed() };
|
||||
let class_name = HString::create(WINDOWS_GLOBALIZATION_CALENDAR, &mut string_header)?;
|
||||
|
||||
// Create new "Windows.Globalization.Calendar" instance.
|
||||
let calendar = Instance::activate(&class_name).or_else(|result| {
|
||||
// Some other library could have called CoIncrementMTAUsage() or CoInitializeEx(), so we
|
||||
// only call CoIncrementMTAUsage() if RoActivateInstance() tells us that multithreading
|
||||
// was not initialized, yet.
|
||||
|
||||
// No need to check the error. The only conceivable error code this function returns is
|
||||
// E_OUTOFMEMORY, and the program is about to get OOM killed anyway in this case.
|
||||
// Windows-rs does not check the result, either.
|
||||
|
||||
if result != CO_E_NOTINITIALIZED {
|
||||
return Err(result);
|
||||
}
|
||||
let mut cookie = 0;
|
||||
// SAFETY: "Don't call CoIncrementMTAUsage during process shutdown or inside dllmain."
|
||||
// Using the function is `fn main()` is totally fine. If you go really low level
|
||||
// and implement an "fn wWinMain()" somehow, then all bets are off anyway.
|
||||
let _ = unsafe { CoIncrementMTAUsage(&mut cookie) };
|
||||
|
||||
Instance::activate(&class_name)
|
||||
})?;
|
||||
|
||||
// Query ITimeZoneOnCalendar of the calendar instance.
|
||||
let tz = TzOnCalendar::query(&calendar)?;
|
||||
|
||||
// Get the name of the time zone.
|
||||
let name = HString::from_tz_on_calendar(&tz)?;
|
||||
|
||||
// Convert to Rust String
|
||||
Ok(name.to_string())
|
||||
}
|
||||
|
||||
mod hstring {
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use windows_sys::core::{HRESULT, HSTRING};
|
||||
use windows_sys::Win32::System::WinRT::WindowsDeleteString;
|
||||
use windows_sys::Win32::System::WinRT::{
|
||||
WindowsCreateStringReference, WindowsGetStringRawBuffer, HSTRING_HEADER,
|
||||
};
|
||||
|
||||
use super::tz_on_calendar::TzOnCalendar;
|
||||
|
||||
pub struct HString<'a> {
|
||||
string: HSTRING,
|
||||
_header: Option<&'a mut HSTRING_HEADER>,
|
||||
}
|
||||
|
||||
impl<'a> HString<'a> {
|
||||
// `source` must be null-terminated. Windows tests if the the terminator is missing and
|
||||
// returns an error if it is absent.
|
||||
pub fn create(source: &'a [u16], header: &'a mut HSTRING_HEADER) -> Result<Self, HRESULT> {
|
||||
let mut string = null_mut();
|
||||
// SAFETY: `source` is a valid reference. If its contents are not a valid wide string,
|
||||
// then the call will return an error code. We keep a reference to the `source`
|
||||
// and `header`, so they stay valid until the `HSTRING` is released.
|
||||
let result = unsafe {
|
||||
WindowsCreateStringReference(
|
||||
source.as_ptr(),
|
||||
(source.len().saturating_sub(1)) as u32,
|
||||
header,
|
||||
&mut string,
|
||||
)
|
||||
};
|
||||
if result < 0 || string.is_null() {
|
||||
Err(result)
|
||||
} else {
|
||||
Ok(Self {
|
||||
string,
|
||||
_header: Some(header),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_tz_on_calendar(instance: &'a TzOnCalendar) -> Result<Self, HRESULT> {
|
||||
let mut string = null_mut();
|
||||
// SAFETY: A `TzOnCalendar` is only ever created with a valid instance.
|
||||
let result = unsafe {
|
||||
let instance = instance.as_ptr();
|
||||
((**instance).GetTimeZone)(instance, &mut string)
|
||||
};
|
||||
if result < 0 || string.is_null() {
|
||||
Err(result)
|
||||
} else {
|
||||
Ok(Self {
|
||||
string,
|
||||
_header: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: You are not allowed to release the returned pointer.
|
||||
pub unsafe fn as_ptr(&self) -> HSTRING {
|
||||
self.string
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for HString<'_> {
|
||||
fn to_string(&self) -> String {
|
||||
let mut len = 0;
|
||||
// SAFETY: An `HString` is only ever created with a valid `HSTRING`.
|
||||
// It keeps a reference to `HSTRING_HEADER` if needed.
|
||||
let buf = unsafe { WindowsGetStringRawBuffer(self.string, &mut len) };
|
||||
if len == 0 || buf.is_null() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// SAFETY: `WindowsGetStringRawBuffer` returns a valid pointer to a wide string.
|
||||
let slice = unsafe { std::slice::from_raw_parts(buf, len as usize) };
|
||||
String::from_utf16_lossy(slice)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HString<'_> {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: An `HString` is only ever created with a valid `HSTRING`.
|
||||
unsafe { WindowsDeleteString(self.string) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod instance {
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use windows_sys::core::HRESULT;
|
||||
use windows_sys::Win32::System::WinRT::RoActivateInstance;
|
||||
|
||||
use super::hstring::HString;
|
||||
use super::interfaces::IUnknown;
|
||||
|
||||
pub struct Instance(IUnknown);
|
||||
|
||||
impl Instance {
|
||||
pub fn activate(class_id: &HString<'_>) -> Result<Self, HRESULT> {
|
||||
let mut instance = null_mut();
|
||||
// SAFETY: An `HString` is only ever crated with a valid `HSTRING`.
|
||||
let result = unsafe { RoActivateInstance(class_id.as_ptr(), &mut instance) };
|
||||
if result < 0 || instance.is_null() {
|
||||
Err(result)
|
||||
} else {
|
||||
Ok(Self(instance.cast()))
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: You are not allowed to release the returned pointer.
|
||||
pub unsafe fn as_ptr(&self) -> IUnknown {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Instance {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: An `Instance` is only ever created with a valid `IUnknown`.
|
||||
unsafe { ((**self.0).Release)(self.0) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod tz_on_calendar {
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use windows_sys::core::HRESULT;
|
||||
|
||||
use super::instance::Instance;
|
||||
use super::interfaces::{ITimeZoneOnCalendar, IUnknown};
|
||||
use super::TIMEZONE_ON_CALENDAR_GUID;
|
||||
|
||||
pub struct TzOnCalendar(ITimeZoneOnCalendar);
|
||||
|
||||
impl TzOnCalendar {
|
||||
pub fn query(source: &Instance) -> Result<Self, HRESULT> {
|
||||
let mut tz = null_mut();
|
||||
// SAFETY: An `Instance` is only ever created with a valid `IUnknown`.
|
||||
let result = unsafe {
|
||||
let source = source.as_ptr();
|
||||
((**source).QueryInterface)(source, &TIMEZONE_ON_CALENDAR_GUID, &mut tz)
|
||||
};
|
||||
if result < 0 || tz.is_null() {
|
||||
Err(result)
|
||||
} else {
|
||||
Ok(Self(tz.cast()))
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: You are not allowed to release the returned pointer.
|
||||
pub unsafe fn as_ptr(&self) -> ITimeZoneOnCalendar {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TzOnCalendar {
|
||||
fn drop(&mut self) {
|
||||
let v: IUnknown = self.0.cast();
|
||||
// SAFETY: `TzOnCalendar` is only ever created with a valid `ITimeZoneOnCalendar`.
|
||||
unsafe { ((**v).Release)(v) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_camel_case_types)]
|
||||
mod interfaces {
|
||||
use std::ops::Deref;
|
||||
|
||||
use windows_sys::core::{GUID, HRESULT, HSTRING};
|
||||
|
||||
pub type IUnknown = *mut *const IUnknown_Vtbl;
|
||||
pub type IInspectable = *mut *const IInspectable_Vtbl;
|
||||
pub type ITimeZoneOnCalendar = *mut *const ITimeZoneOnCalendar_Vtbl;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IUnknown_Vtbl {
|
||||
pub QueryInterface: unsafe extern "system" fn(
|
||||
this: IUnknown,
|
||||
iid: &GUID,
|
||||
interface: &mut IUnknown,
|
||||
) -> HRESULT,
|
||||
pub AddRef: unsafe extern "system" fn(this: IUnknown) -> u32,
|
||||
pub Release: unsafe extern "system" fn(this: IUnknown) -> u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IInspectable_Vtbl {
|
||||
pub base: IUnknown_Vtbl,
|
||||
pub GetIids: unsafe extern "system" fn(
|
||||
this: IInspectable,
|
||||
count: &mut u32,
|
||||
values: &mut &mut GUID,
|
||||
) -> HRESULT,
|
||||
pub GetRuntimeClassName:
|
||||
unsafe extern "system" fn(this: IInspectable, value: &mut HSTRING) -> HRESULT,
|
||||
pub GetTrustLevel:
|
||||
unsafe extern "system" fn(this: IInspectable, value: &mut i32) -> HRESULT,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ITimeZoneOnCalendar_Vtbl {
|
||||
pub base: IInspectable_Vtbl,
|
||||
pub GetTimeZone:
|
||||
unsafe extern "system" fn(this: ITimeZoneOnCalendar, result: &mut HSTRING) -> HRESULT,
|
||||
pub ChangeTimeZone:
|
||||
unsafe extern "system" fn(this: ITimeZoneOnCalendar, timezoneid: HSTRING) -> HRESULT,
|
||||
pub TimeZoneAsFullString:
|
||||
unsafe extern "system" fn(this: ITimeZoneOnCalendar, result: *mut HSTRING) -> HRESULT,
|
||||
pub TimeZoneAsString: unsafe extern "system" fn(
|
||||
this: ITimeZoneOnCalendar,
|
||||
ideallength: i32,
|
||||
result: &mut HSTRING,
|
||||
) -> HRESULT,
|
||||
}
|
||||
|
||||
impl Deref for IInspectable_Vtbl {
|
||||
type Target = IUnknown_Vtbl;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ITimeZoneOnCalendar_Vtbl {
|
||||
type Target = IInspectable_Vtbl;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.base
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user