blob: 69b422314d2a8f30388a557c9739c638b42275c3 [file] [log] [blame]
//! Get the system's UTC offset on Windows.
use core::mem::MaybeUninit;
use crate::{OffsetDateTime, UtcOffset};
// ffi: WINAPI FILETIME struct
#[repr(C)]
#[allow(non_snake_case, clippy::missing_docs_in_private_items)]
struct FileTime {
dwLowDateTime: u32,
dwHighDateTime: u32,
}
// ffi: WINAPI SYSTEMTIME struct
#[repr(C)]
#[allow(non_snake_case, clippy::missing_docs_in_private_items)]
struct SystemTime {
wYear: u16,
wMonth: u16,
wDayOfWeek: u16,
wDay: u16,
wHour: u16,
wMinute: u16,
wSecond: u16,
wMilliseconds: u16,
}
#[link(name = "kernel32")]
extern "system" {
// https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetofiletime
fn SystemTimeToFileTime(lpSystemTime: *const SystemTime, lpFileTime: *mut FileTime) -> i32;
// https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-systemtimetotzspecificlocaltime
fn SystemTimeToTzSpecificLocalTime(
lpTimeZoneInformation: *const core::ffi::c_void, // We only pass `nullptr` here
lpUniversalTime: *const SystemTime,
lpLocalTime: *mut SystemTime,
) -> i32;
}
/// Convert a `SYSTEMTIME` to a `FILETIME`. Returns `None` if any error occurred.
fn systemtime_to_filetime(systime: &SystemTime) -> Option<FileTime> {
let mut ft = MaybeUninit::uninit();
// Safety: `SystemTimeToFileTime` is thread-safe.
if 0 == unsafe { SystemTimeToFileTime(systime, ft.as_mut_ptr()) } {
// failed
None
} else {
// Safety: The call succeeded.
Some(unsafe { ft.assume_init() })
}
}
/// Convert a `FILETIME` to an `i64`, representing a number of seconds.
fn filetime_to_secs(filetime: &FileTime) -> i64 {
/// FILETIME represents 100-nanosecond intervals
const FT_TO_SECS: i64 = 10_000_000;
((filetime.dwHighDateTime as i64) << 32 | filetime.dwLowDateTime as i64) / FT_TO_SECS
}
/// Convert an [`OffsetDateTime`] to a `SYSTEMTIME`.
fn offset_to_systemtime(datetime: OffsetDateTime) -> SystemTime {
let (_, month, day_of_month) = datetime.to_offset(UtcOffset::UTC).date().to_calendar_date();
SystemTime {
wYear: datetime.year() as _,
wMonth: month as _,
wDay: day_of_month as _,
wDayOfWeek: 0, // ignored
wHour: datetime.hour() as _,
wMinute: datetime.minute() as _,
wSecond: datetime.second() as _,
wMilliseconds: datetime.millisecond(),
}
}
/// Obtain the system's UTC offset.
pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
// This function falls back to UTC if any system call fails.
let systime_utc = offset_to_systemtime(datetime.to_offset(UtcOffset::UTC));
// Safety: `local_time` is only read if it is properly initialized, and
// `SystemTimeToTzSpecificLocalTime` is thread-safe.
let systime_local = unsafe {
let mut local_time = MaybeUninit::uninit();
if 0 == SystemTimeToTzSpecificLocalTime(
core::ptr::null(), // use system's current timezone
&systime_utc,
local_time.as_mut_ptr(),
) {
// call failed
return None;
} else {
local_time.assume_init()
}
};
// Convert SYSTEMTIMEs to FILETIMEs so we can perform arithmetic on them.
let ft_system = systemtime_to_filetime(&systime_utc)?;
let ft_local = systemtime_to_filetime(&systime_local)?;
let diff_secs = (filetime_to_secs(&ft_local) - filetime_to_secs(&ft_system))
.try_into()
.ok()?;
UtcOffset::from_whole_seconds(diff_secs).ok()
}