pub fn utc_offset_secs() -> i32 {
platform_offset().unwrap_or(0)
}
#[cfg(unix)]
fn platform_offset() -> Option<i32> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.ok()?
.as_secs() as libc::time_t;
let mut out: libc::tm = unsafe { std::mem::zeroed() };
let filled = unsafe { libc::localtime_r(&now, &mut out) };
if filled.is_null() {
return None;
}
Some(out.tm_gmtoff as i32)
}
#[cfg(windows)]
#[repr(C)]
#[allow(non_snake_case)]
struct TimeZoneInformation {
Bias: i32,
StandardName: [u16; 32],
StandardDate: [u16; 8],
StandardBias: i32,
DaylightName: [u16; 32],
DaylightDate: [u16; 8],
DaylightBias: i32,
}
#[cfg(windows)]
unsafe extern "system" {
fn GetTimeZoneInformation(info: *mut TimeZoneInformation) -> u32;
}
#[cfg(windows)]
fn platform_offset() -> Option<i32> {
const INVALID: u32 = 0xFFFF_FFFF;
const DAYLIGHT: u32 = 2;
let mut info: TimeZoneInformation = unsafe { std::mem::zeroed() };
let which = unsafe { GetTimeZoneInformation(&mut info) };
if which == INVALID {
return None;
}
let bias = info.Bias
+ if which == DAYLIGHT {
info.DaylightBias
} else {
info.StandardBias
};
Some(-bias * 60)
}
#[cfg(not(any(unix, windows)))]
fn platform_offset() -> Option<i32> {
None
}
pub fn local_stamp(at: i64) -> String {
let offset = utc_offset_secs();
let (y, mo, d, h, mi, s) = civil_from_unix(at + offset as i64);
let sign = if offset < 0 { '-' } else { '+' };
let off = offset.abs();
format!(
"{y:04}-{mo:02}-{d:02} {h:02}:{mi:02}:{s:02} {sign}{:02}:{:02}",
off / 3600,
(off % 3600) / 60
)
}
pub fn local_clock(at: i64) -> String {
let offset = utc_offset_secs();
let (_, _, _, h, mi, s) = civil_from_unix(at + offset as i64);
let sign = if offset < 0 { '-' } else { '+' };
let off = offset.abs();
format!(
"{h:02}:{mi:02}:{s:02} {sign}{:02}:{:02}",
off / 3600,
(off % 3600) / 60
)
}
fn civil_from_unix(at: i64) -> (i64, u32, u32, u32, u32, u32) {
let days = at.div_euclid(86_400);
let rest = at.rem_euclid(86_400);
let z = days + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
let y = if m <= 2 { y + 1 } else { y };
(
y,
m,
d,
(rest / 3600) as u32,
((rest % 3600) / 60) as u32,
(rest % 60) as u32,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_epoch_and_a_known_instant_convert() {
assert_eq!(civil_from_unix(0), (1970, 1, 1, 0, 0, 0));
assert_eq!(civil_from_unix(1_789_653_120), (2026, 9, 17, 13, 52, 0));
assert_eq!(civil_from_unix(-1), (1969, 12, 31, 23, 59, 59));
}
#[test]
fn a_stamp_carries_its_offset() {
let stamp = local_stamp(1_789_653_120);
let offset = &stamp[stamp.len() - 6..];
assert!(
offset.starts_with('+') || offset.starts_with('-'),
"a timestamp with no offset is the bug this exists to fix: {stamp}"
);
assert_eq!(&offset[3..4], ":", "the offset is written +HH:MM: {stamp}");
assert!(local_stamp(0).ends_with(&local_clock(0)));
}
}