use core::mem::MaybeUninit;
use std::io::Error;
use std::ptr;
use std::result::Result;
use super::win_bindings::{
SystemTimeToFileTime, SystemTimeToTzSpecificLocalTime, TzSpecificLocalTimeToSystemTime,
FILETIME, SYSTEMTIME,
};
use super::FixedOffset;
use crate::{Datelike, LocalResult, NaiveDateTime, Timelike};
macro_rules! windows_sys_call {
($name:ident($($arg:expr),*), $error_id:expr) => {
if $name($($arg),*) == $error_id {
return Err(Error::last_os_error());
}
}
}
const HECTONANOSECS_IN_SEC: i64 = 10_000_000;
const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC;
pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult<FixedOffset> {
offset(utc, false)
}
pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult<FixedOffset> {
offset(local, true)
}
pub(super) fn offset(d: &NaiveDateTime, local: bool) -> LocalResult<FixedOffset> {
let naive_sys_time = system_time_from_naive_date_time(d);
let local_sys_time = match local {
false => from_utc_time(naive_sys_time),
true => from_local_time(naive_sys_time),
};
if let Ok(offset) = local_sys_time {
return LocalResult::Single(offset);
}
LocalResult::None
}
fn from_utc_time(utc_time: SYSTEMTIME) -> Result<FixedOffset, Error> {
let local_time = utc_to_local_time(&utc_time)?;
let utc_secs = system_time_as_unix_seconds(&utc_time)?;
let local_secs = system_time_as_unix_seconds(&local_time)?;
let offset = (local_secs - utc_secs) as i32;
Ok(FixedOffset::east_opt(offset).unwrap())
}
fn from_local_time(local_time: SYSTEMTIME) -> Result<FixedOffset, Error> {
let utc_time = local_to_utc_time(&local_time)?;
let utc_secs = system_time_as_unix_seconds(&utc_time)?;
let local_secs = system_time_as_unix_seconds(&local_time)?;
let offset = (local_secs - utc_secs) as i32;
Ok(FixedOffset::east_opt(offset).unwrap())
}
fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME {
SYSTEMTIME {
wYear: dt.year() as u16,
wMonth: dt.month() as u16,
wDayOfWeek: dt.weekday() as u16,
wDay: dt.day() as u16,
wHour: dt.hour() as u16,
wMinute: dt.minute() as u16,
wSecond: dt.second() as u16,
wMilliseconds: 0,
}
}
pub(crate) fn local_to_utc_time(local: &SYSTEMTIME) -> Result<SYSTEMTIME, Error> {
let mut sys_time = MaybeUninit::<SYSTEMTIME>::uninit();
unsafe {
windows_sys_call!(
TzSpecificLocalTimeToSystemTime(ptr::null(), local, sys_time.as_mut_ptr()),
0
)
};
Ok(unsafe { sys_time.assume_init() })
}
pub(crate) fn utc_to_local_time(utc_time: &SYSTEMTIME) -> Result<SYSTEMTIME, Error> {
let mut local = MaybeUninit::<SYSTEMTIME>::uninit();
unsafe {
windows_sys_call!(
SystemTimeToTzSpecificLocalTime(ptr::null(), utc_time, local.as_mut_ptr()),
0
)
};
Ok(unsafe { local.assume_init() })
}
pub(crate) fn system_time_as_unix_seconds(st: &SYSTEMTIME) -> Result<i64, Error> {
let mut init = MaybeUninit::<FILETIME>::uninit();
unsafe { windows_sys_call!(SystemTimeToFileTime(st, init.as_mut_ptr()), 0) }
let filetime = unsafe { init.assume_init() };
let bit_shift = ((filetime.dwHighDateTime as u64) << 32) | (filetime.dwLowDateTime as u64);
let unix_secs = (bit_shift as i64 - HECTONANOSEC_TO_UNIX_EPOCH) / HECTONANOSECS_IN_SEC;
Ok(unix_secs)
}