diff options
Diffstat (limited to 'vendor/time/src/sys/local_offset_at/windows.rs')
-rw-r--r-- | vendor/time/src/sys/local_offset_at/windows.rs | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/vendor/time/src/sys/local_offset_at/windows.rs b/vendor/time/src/sys/local_offset_at/windows.rs new file mode 100644 index 000000000..fed01bf3a --- /dev/null +++ b/vendor/time/src/sys/local_offset_at/windows.rs @@ -0,0 +1,114 @@ +//! 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: i32 = (filetime_to_secs(&ft_local) - filetime_to_secs(&ft_system)) + .try_into() + .ok()?; + + UtcOffset::from_hms( + (diff_secs / 3_600) as _, + ((diff_secs / 60) % 60) as _, + (diff_secs % 60) as _, + ) + .ok() +} |