use core::mem::MaybeUninit;
use alloc::string::String;
use windows_sys::Win32::System::Time::{
GetDynamicTimeZoneInformation, DYNAMIC_TIME_ZONE_INFORMATION,
TIME_ZONE_ID_INVALID,
};
use crate::{
error::{tz::system::Error as E, Error, ErrorContext},
tz::{TimeZone, TimeZoneDatabase},
util::utf8,
};
use self::windows_zones::WINDOWS_TO_IANA;
#[allow(dead_code)] mod windows_zones;
pub(super) fn get(db: &TimeZoneDatabase) -> Option<TimeZone> {
let tz_key_name = match get_tz_key_name() {
Ok(tz_key_name) => tz_key_name,
Err(_err) => {
warn!(
"failed to discover current time zone via \
winapi GetDynamicTimeZoneInformation: {_err}",
);
return None;
}
};
let iana_name = match windows_to_iana(&tz_key_name) {
Ok(iana_name) => iana_name,
Err(_err) => {
warn!("could not find IANA time zone name: {_err}");
return None;
}
};
let tz = match db.get(iana_name) {
Ok(tz) => tz,
Err(_err) => {
warn!(
"could not find mapped IANA time zone {iana_name} \
in zoneinfo database {db:?}: {_err}",
);
return None;
}
};
Some(tz)
}
pub(super) fn read(_db: &TimeZoneDatabase, path: &str) -> Option<TimeZone> {
match super::read_unnamed_tzif_file(path) {
Ok(tz) => Some(tz),
Err(_err) => {
trace!("failed to read {path} as unnamed time zone: {_err}");
None
}
}
}
fn windows_to_iana(tz_key_name: &str) -> Result<&'static str, Error> {
let result = WINDOWS_TO_IANA.binary_search_by(|(win_name, _)| {
utf8::cmp_ignore_ascii_case(win_name, &tz_key_name)
});
let Ok(index) = result else {
return Err(Error::from(E::WindowsMissingIanaMapping));
};
let iana_name = WINDOWS_TO_IANA[index].1;
trace!(
"found Windows time zone name `{tz_key_name}`, and \
successfully mapped it to IANA time zone `{iana_name}`",
);
Ok(iana_name)
}
fn get_tz_key_name() -> Result<String, Error> {
let mut info: MaybeUninit<DYNAMIC_TIME_ZONE_INFORMATION> =
MaybeUninit::uninit();
let rc = unsafe { GetDynamicTimeZoneInformation(info.as_mut_ptr()) };
if rc == TIME_ZONE_ID_INVALID {
return Err(Error::io(std::io::Error::last_os_error()));
}
let info = unsafe { info.assume_init() };
let tz_key_name = nul_terminated_utf16_to_string(&info.TimeZoneKeyName)
.context(E::WindowsTimeZoneKeyName)?;
Ok(tz_key_name)
}
fn nul_terminated_utf16_to_string(
code_units: &[u16],
) -> Result<String, Error> {
let nul = code_units
.iter()
.position(|&cu| cu == 0)
.ok_or(E::WindowsUtf16DecodeNul)?;
let string = String::from_utf16(&code_units[..nul])
.map_err(|_| E::WindowsUtf16DecodeInvalid)?;
Ok(string)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_time_zone_name_windows_dynamic_time_zone() {
let _ = crate::logging::Logger::init();
let db = crate::tz::db();
if crate::tz::db().is_definitively_empty() {
return;
}
let path = std::path::Path::new("/etc/localtime");
if !path.exists() {
return;
}
assert!(get(db).is_some());
}
}