use crate::{
TzError,
sys::{
DST_IN_EFFECT, DST_NOT_IN_EFFECT, DST_UNKNOWN, localtime_rz, mktime_z, timezone_t, tzalloc,
tzfree,
},
};
use chrono::{Datelike, FixedOffset, MappedLocalTime, NaiveDateTime, Timelike};
use libc::time_t;
use std::{
ffi::{CStr, CString},
io,
ptr::null,
};
#[derive(Debug)]
pub struct TzInner {
timezone: timezone_t,
}
impl TzInner {
pub fn new(olson_id: &str) -> Result<Self, TzError> {
let id = CString::new(olson_id)?;
let timezone = unsafe { tzalloc(id.as_ptr()) };
if timezone.is_null() {
Err(TzError::Io(io::Error::last_os_error()))
} else {
Ok(Self { timezone })
}
}
pub fn local() -> Result<Self, TzError> {
let timezone = unsafe { tzalloc(null()) };
if timezone.is_null() {
Err(TzError::Io(io::Error::last_os_error()))
} else {
Ok(Self { timezone })
}
}
pub fn name_at_utc_timestamp(&self, time: time_t) -> &str {
let result = self.localtime_rz(time).expect("localtime_rz failed");
let tm_zone = unsafe { CStr::from_ptr(result.tm_zone) };
tm_zone.to_str().unwrap()
}
pub fn offset_at_utc_timestamp(&self, time: time_t) -> FixedOffset {
let result = self.localtime_rz(time).expect("localtime_rz failed");
FixedOffset::east_opt(
result
.tm_gmtoff
.try_into()
.expect("localtime_r returned invalid UTC offset"),
)
.expect("localtime_r returned invalid UTC offset")
}
pub fn timestamp_from_local_datetime(&self, local: &NaiveDateTime) -> MappedLocalTime<time_t> {
let (utc_timestamp, _isdst) = self.mktime_with_dst(local, DST_UNKNOWN);
let (utc_timestamp_without_dst, isdst0) = self.mktime_with_dst(local, DST_NOT_IN_EFFECT);
let (utc_timestamp_with_dst, isdst1) = self.mktime_with_dst(local, DST_IN_EFFECT);
if utc_timestamp == -1 {
MappedLocalTime::None
} else if isdst0 == isdst1 {
MappedLocalTime::Single(utc_timestamp)
} else {
MappedLocalTime::Ambiguous(utc_timestamp_without_dst, utc_timestamp_with_dst)
}
}
fn localtime_rz(&self, time: time_t) -> Result<libc::tm, io::Error> {
let mut result = libc::tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: null(),
};
if unsafe { localtime_rz(self.timezone, &time, &mut result) }.is_null() {
Err(io::Error::last_os_error())
} else {
Ok(result)
}
}
fn mktime_with_dst(&self, local: &NaiveDateTime, isdst: i32) -> (time_t, i32) {
let mut tm = libc::tm {
tm_sec: local.second() as i32,
tm_min: local.minute() as i32,
tm_hour: local.hour() as i32,
tm_mday: local.day() as i32,
tm_mon: local.month0() as i32,
tm_year: local.year() - 1900,
tm_wday: 0,
tm_yday: 0,
tm_isdst: isdst,
tm_gmtoff: 0,
tm_zone: null(),
};
let timestamp = unsafe { mktime_z(self.timezone, &mut tm) };
(timestamp, tm.tm_isdst)
}
}
impl Drop for TzInner {
fn drop(&mut self) {
unsafe { tzfree(self.timezone) }
}
}
unsafe impl Send for TzInner {}
unsafe impl Sync for TzInner {}