-
-
Notifications
You must be signed in to change notification settings - Fork 261
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move local offset function to proper sys directory
- Loading branch information
Showing
7 changed files
with
259 additions
and
233 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
//! A fallback for any OS not covered. | ||
|
||
use crate::{OffsetDateTime, UtcOffset}; | ||
|
||
pub(super) fn local_offset_at(_datetime: OffsetDateTime) -> Option<UtcOffset> { | ||
None | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
//! A method to obtain the local offset from UTC. | ||
|
||
#[cfg_attr(target_family = "windows", path = "windows.rs")] | ||
#[cfg_attr(target_family = "unix", path = "unix.rs")] | ||
mod imp; | ||
|
||
use crate::{OffsetDateTime, UtcOffset}; | ||
|
||
/// Attempt to obtain the system's UTC offset. If the offset cannot be determined, `None` is | ||
/// returned. | ||
pub(crate) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> { | ||
imp::local_offset_at(datetime) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
//! Get the system's UTC offset on Unix. | ||
|
||
#[cfg(unsound_local_offset)] | ||
use core::convert::TryInto; | ||
#[cfg(unsound_local_offset)] | ||
use core::mem::MaybeUninit; | ||
|
||
use crate::{OffsetDateTime, UtcOffset}; | ||
|
||
/// Obtain the system's UTC offset. | ||
// See #293 for details. | ||
#[cfg(not(unsound_local_offset))] | ||
#[allow(clippy::missing_const_for_fn)] | ||
pub(super) fn local_offset_at(_datetime: OffsetDateTime) -> Option<UtcOffset> { | ||
None | ||
} | ||
|
||
/// Convert the given Unix timestamp to a `libc::tm`. Returns `None` on any error. | ||
#[cfg(unsound_local_offset)] | ||
fn timestamp_to_tm(timestamp: i64) -> Option<libc::tm> { | ||
extern "C" { | ||
#[cfg_attr(target_os = "netbsd", link_name = "__tzset50")] | ||
fn tzset(); | ||
} | ||
|
||
// The exact type of `timestamp` beforehand can vary, so this conversion is necessary. | ||
#[allow(clippy::useless_conversion)] | ||
let timestamp = timestamp.try_into().ok()?; | ||
|
||
let mut tm = MaybeUninit::uninit(); | ||
|
||
// Update timezone information from system. `localtime_r` does not do this for us. | ||
// | ||
// Safety: tzset is thread-safe. | ||
#[allow(unsafe_code)] | ||
unsafe { | ||
tzset(); | ||
} | ||
|
||
// Safety: We are calling a system API, which mutates the `tm` variable. If a null | ||
// pointer is returned, an error occurred. | ||
#[allow(unsafe_code)] | ||
let tm_ptr = unsafe { libc::localtime_r(×tamp, tm.as_mut_ptr()) }; | ||
|
||
if tm_ptr.is_null() { | ||
None | ||
} else { | ||
// Safety: The value was initialized, as we no longer have a null pointer. | ||
#[allow(unsafe_code)] | ||
{ | ||
Some(unsafe { tm.assume_init() }) | ||
} | ||
} | ||
} | ||
|
||
/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error. | ||
// `tm_gmtoff` extension | ||
#[cfg(unsound_local_offset)] | ||
#[cfg(not(any(target_os = "solaris", target_os = "illumos")))] | ||
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> { | ||
let seconds: i32 = tm.tm_gmtoff.try_into().ok()?; | ||
UtcOffset::from_hms( | ||
(seconds / 3_600) as _, | ||
((seconds / 60) % 60) as _, | ||
(seconds % 60) as _, | ||
) | ||
.ok() | ||
} | ||
|
||
/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error. | ||
#[cfg(unsound_local_offset)] | ||
#[cfg(any(target_os = "solaris", target_os = "illumos"))] | ||
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> { | ||
use core::convert::TryFrom; | ||
|
||
use crate::Date; | ||
|
||
let mut tm = tm; | ||
if tm.tm_sec == 60 { | ||
// Leap seconds are not currently supported. | ||
tm.tm_sec = 59; | ||
} | ||
|
||
let local_timestamp = | ||
Date::from_ordinal_date(1900 + tm.tm_year, u16::try_from(tm.tm_yday).ok()? + 1) | ||
.ok()? | ||
.with_hms( | ||
tm.tm_hour.try_into().ok()?, | ||
tm.tm_min.try_into().ok()?, | ||
tm.tm_sec.try_into().ok()?, | ||
) | ||
.ok()? | ||
.assume_utc() | ||
.unix_timestamp(); | ||
|
||
let diff_secs: i32 = (local_timestamp - datetime.unix_timestamp()) | ||
.try_into() | ||
.ok()?; | ||
|
||
UtcOffset::from_hms( | ||
(diff_secs / 3_600) as _, | ||
((diff_secs / 60) % 60) as _, | ||
(diff_secs % 60) as _, | ||
) | ||
.ok() | ||
} | ||
|
||
/// Obtain the system's UTC offset. | ||
#[cfg(unsound_local_offset)] | ||
pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> { | ||
tm_to_offset(timestamp_to_tm(datetime.unix_timestamp())?) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
//! Get the system's UTC offset on Windows. | ||
|
||
use core::convert::TryInto; | ||
use core::mem::MaybeUninit; | ||
|
||
// ffi: WINAPI FILETIME struct | ||
#[repr(C)] | ||
#[allow(non_snake_case)] | ||
struct FileTime { | ||
dwLowDateTime: u32, | ||
dwHighDateTime: u32, | ||
} | ||
|
||
// ffi: WINAPI SYSTEMTIME struct | ||
#[repr(C)] | ||
#[allow(non_snake_case)] | ||
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 std::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. We are only assuming initialization if | ||
// the call succeeded. | ||
#[allow(unsafe_code)] | ||
{ | ||
if 0 == unsafe { SystemTimeToFileTime(systime, ft.as_mut_ptr()) } { | ||
// failed | ||
None | ||
} else { | ||
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. | ||
#[allow(unsafe_code)] | ||
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
//! Functions with a common interface that rely on system calls. | ||
|
||
#[cfg(feature = "local-offset")] | ||
mod local_offset_at; | ||
|
||
#[cfg(feature = "local-offset")] | ||
pub(crate) use local_offset_at::local_offset_at; |
Oops, something went wrong.