1//! Get the system's UTC offset on Unix.
23use core::mem::MaybeUninit;
45use crate::{OffsetDateTime, UtcOffset};
67/// Convert the given Unix timestamp to a `libc::tm`. Returns `None` on any error.
8fn timestamp_to_tm(timestamp: i64) -> Option<libc::tm> {
9// The exact type of `timestamp` beforehand can vary, so this conversion is necessary.
10#[allow(clippy::useless_conversion)]
11let timestamp = timestamp.try_into().ok()?;
1213let mut tm = MaybeUninit::uninit();
1415// Safety: We are calling a system API, which mutates the `tm` variable. If a null
16 // pointer is returned, an error occurred.
17let tm_ptr = unsafe { libc::localtime_r(×tamp, tm.as_mut_ptr()) };
1819if tm_ptr.is_null() {
20None
21} else {
22// Safety: The value was initialized, as we no longer have a null pointer.
23Some(unsafe { tm.assume_init() })
24 }
25}
2627/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
28// This is available to any target known to have the `tm_gmtoff` extension.
29#[cfg(any(
30 target_os = "redox",
31 target_os = "linux",
32 target_os = "l4re",
33 target_os = "android",
34 target_os = "emscripten",
35 target_os = "macos",
36 target_os = "ios",
37 target_os = "watchos",
38 target_os = "freebsd",
39 target_os = "dragonfly",
40 target_os = "openbsd",
41 target_os = "netbsd",
42 target_os = "haiku",
43))]
44fn tm_to_offset(_unix_timestamp: i64, tm: libc::tm) -> Option<UtcOffset> {
45let seconds = tm.tm_gmtoff.try_into().ok()?;
46 UtcOffset::from_whole_seconds(seconds).ok()
47}
4849/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
50///
51/// This method can return an incorrect value, as it only approximates the `tm_gmtoff` field. The
52/// reason for this is that daylight saving time does not start on the same date every year, nor are
53/// the rules for daylight saving time the same for every year. This implementation assumes 1970 is
54/// equivalent to every other year, which is not always the case.
55#[cfg(not(any(
56 target_os = "redox",
57 target_os = "linux",
58 target_os = "l4re",
59 target_os = "android",
60 target_os = "emscripten",
61 target_os = "macos",
62 target_os = "ios",
63 target_os = "watchos",
64 target_os = "freebsd",
65 target_os = "dragonfly",
66 target_os = "openbsd",
67 target_os = "netbsd",
68 target_os = "haiku",
69)))]
70fn tm_to_offset(unix_timestamp: i64, tm: libc::tm) -> Option<UtcOffset> {
71use crate::Date;
7273let mut tm = tm;
74if tm.tm_sec == 60 {
75// Leap seconds are not currently supported.
76tm.tm_sec = 59;
77 }
7879let local_timestamp =
80 Date::from_ordinal_date(1900 + tm.tm_year, u16::try_from(tm.tm_yday).ok()? + 1)
81 .ok()?
82.with_hms(
83 tm.tm_hour.try_into().ok()?,
84 tm.tm_min.try_into().ok()?,
85 tm.tm_sec.try_into().ok()?,
86 )
87 .ok()?
88.assume_utc()
89 .unix_timestamp();
9091let diff_secs = (local_timestamp - unix_timestamp).try_into().ok()?;
9293 UtcOffset::from_whole_seconds(diff_secs).ok()
94}
9596/// Obtain the system's UTC offset.
97pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
98let unix_timestamp = datetime.unix_timestamp();
99let tm = timestamp_to_tm(unix_timestamp)?;
100 tm_to_offset(unix_timestamp, tm)
101}