use libtz_sys::{TimezoneT, TimeT, tzalloc, tzfree, localtime_rz, mktime_z, posix2time_z, time2posix_z};
use std::ffi::CString;
use std::mem::MaybeUninit;
use crate::Tm;
pub struct Timezone {
tz: TimezoneT,
}
impl Timezone {
pub fn new(name: &str) -> Result<Timezone, String> {
let tzname = CString::new(name).map_err(|_| "name has internal null byte".to_string())?;
let tz = unsafe { tzalloc(tzname.as_ptr()) };
if tz == std::ptr::null_mut() {
return Err("tzalloc failed".to_string());
}
Ok(Timezone{
tz: tz
})
}
pub fn default() -> Result<Timezone, String> {
use std::os::unix::ffi::OsStringExt;
let zone_cstr;
let zone = match std::env::var_os("TZ") {
Some(zone) => { zone_cstr = CString::new(zone.into_vec()).map_err(|_| "name has internal null byte".to_string())?;
zone_cstr.as_ptr() },
None => std::ptr::null_mut(),
};
let tz = unsafe { tzalloc(zone) };
if tz == std::ptr::null_mut() {
return Err("tzalloc failed".to_string());
}
Ok(Timezone{
tz: tz
})
}
pub fn localtime(&self, time: TimeT) -> Result<Tm, String> {
let mut tztm = MaybeUninit::<libtz_sys::Tm>::uninit();
let ret = unsafe { localtime_rz(self.tz, &time, tztm.as_mut_ptr()) };
if ret == std::ptr::null_mut() {
return Err(format!("errno={}", std::io::Error::last_os_error()));
}
let tztm = unsafe { tztm.assume_init() };
Tm::try_from(&tztm)
}
pub fn mktime(&self, tm: &Tm) -> Result<TimeT, String> {
match unsafe { mktime_z(self.tz, &tm.into()) } {
-1 => Err(format!("Invalid date specified")),
time => Ok(time),
}
}
pub fn time2posix(&self, time: TimeT) -> TimeT {
unsafe { time2posix_z(self.tz, time) }
}
pub fn posix2time(&self, time: TimeT) -> TimeT {
unsafe { posix2time_z(self.tz, time) }
}
}
impl Drop for Timezone {
fn drop(&mut self) {
unsafe { tzfree(self.tz) };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basics() {
let tz = Timezone::new("US/Pacific").expect("timezone alloc");
let time = 946713600; let tm = tz.localtime(time).expect("localtime");
assert_eq!(tm, Tm{tm_sec :0,
tm_min :0,
tm_hour :0,
tm_mday :1,
tm_mon :0,
tm_year :100,
tm_wday :6,
tm_yday :0,
tm_isdst :0,
tm_gmtoff :-28800,
tm_zone :"PST".to_string()});
assert_eq!(tz.mktime(&tm).expect("unix time"), time); }
#[test]
fn default() {
std::env::set_var("TZ", "Europe/Paris");
let tz = Timezone::default().expect("load from TZ");
let time = 915177600; let tm;
assert_eq!(tz.mktime({tm=tz.localtime(time).expect("localtime"); &tm}).expect("mktime"), time);
assert_eq!(tm.tm_zone, "CET");
std::env::remove_var("TZ");
let tz = Timezone::default().expect("load from /etc/localtime");
let time = 915177600; assert_eq!(tz.mktime(&tz.localtime(time).expect("localtime")).expect("mktime"), time);
}
#[test]
fn posix_conversions() {
let tz = Timezone::new("UTC").expect("timezone alloc");
let posixtime: TimeT = 536457599;
let time = tz.posix2time(posixtime);
let tm = tz.localtime(time).expect("localtime");
assert_eq!(tm, Tm{tm_sec :59,
tm_min :59,
tm_hour :23,
tm_mday :31,
tm_mon :11,
tm_year :86,
tm_wday :3,
tm_yday :364,
tm_isdst :0,
tm_gmtoff :0,
tm_zone :"UTC".to_string()});
assert_eq!(tz.time2posix(time), posixtime); }
}