1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
// Thin Rust FFI layer around libtz
//
// Copyright © 2023 David Caldwell <david@porkrind.org>
// License: MIT (see LICENSE.md file)
#![doc = include_str!("README.md")]
use std::cell::RefCell;
use std::ffi::{CString,CStr};
use std::os::unix::ffi::OsStringExt;
use std::os::raw::{c_void,c_char,c_int,c_long};
thread_local!(static ENV_STORAGE: RefCell<CString> = RefCell::new(CString::default()));
#[doc(hidden)]
#[no_mangle]
pub extern "C" fn rust_getenv(name: *const c_char) -> *const c_char {
rust_getenv_internal(name).unwrap_or(std::ptr::null())
}
fn rust_getenv_internal(name: *const c_char) -> Option<*const c_char> {
let name: &str = unsafe { CStr::from_ptr(name).to_str().ok()? };
let value_cstr = CString::new(std::env::var_os(name)?.into_vec()).ok()?;
ENV_STORAGE.with(|storage_cell| {
let mut storage = storage_cell.borrow_mut();
*storage = value_cstr;
Some(storage.as_ptr())
})
}
/// System time. On unix, the number of seconds since 00:00:00 UTC on 1 January 1970.
///
/// Note: This is libtz's `time_t`, which currently hardcoded to i64 regardless
/// of the system's `time_t`.
pub type TimeT = i64; // See `-Dtime_tz` in build.rs
/// A broken down time representation, logically equivalent to `struct tm` in unix.
///
/// Note: This is libtz's `struct tm` and doesn't necessarily match the system's
/// in terms of member ordering.
///
/// Reference: <https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html>
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct Tm {
/** Seconds [0, 60] */ pub tm_sec : c_int,
/** Minutes [0, 59] */ pub tm_min : c_int,
/** Hour [0, 23] */ pub tm_hour : c_int,
/** Day of the month [1, 31] */ pub tm_mday : c_int,
/** Month [0, 11] (January = 0) */ pub tm_mon : c_int,
/** Year minus 1900 */ pub tm_year : c_int,
/** Day of the week [0, 6] (Sunday = 0) */ pub tm_wday : c_int,
/** Day of the year [0, 365] (Jan/01 = 0) */ pub tm_yday : c_int,
/** Daylight savings flag */ pub tm_isdst : c_int,
/** Seconds East of UTC */ pub tm_gmtoff : c_long,
/** Timezone abbreviation */ pub tm_zone: *const c_char,
}
/// Opaque pointer for timezone data. Defined in the C code as:
/// ```C
/// typedef struct state *timezone_t;
/// ```
///
/// This is returned by [`tzalloc`]. It should be explicitly freed with [`tzfree`].
pub type TimezoneT = *const c_void;
extern "C" {
/// Convert system time to a UTC [`Tm`].
///
/// Rust interface for the C function:
/// ```C
/// struct tm *gmtime_r(time_t const *restrict, struct tm *restrict);
/// ```
///
/// The `gmtime_r` function converts to Coordinated Universal Time, returning a pointer to [`Tm`]
/// structures, described below. The storage for the returned [`Tm`] should be passed in as the 2nd
/// argument. If it encounters an error, it will return a `NULL` and set `errno` (see
/// [`std::io::Error::last_os_error`]).
#[link_name = "tz_gmtime_r"]
pub fn gmtime_r(timep: *const TimeT, tmp: *mut Tm) -> *mut Tm;
/// Convert system time to a local time [`Tm`] using globally configured time zone.
///
/// Rust interface for the C function:
/// ```C
/// struct tm *localtime_r(time_t const *restrict, struct tm *restrict);
/// ```
///
/// The `localtime_r` function corrects for the time zone and any time zone adjustments (such as Daylight
/// Saving Time in the United States). The storage for the returned [`Tm`] should be passed in as the 2nd
/// argument. If it encounters an error, it will return a `NULL` and set `errno` (see
/// [`std::io::Error::last_os_error`]). The pointer returned in [`Tm::tm_zone`] will only be valid until
/// the next [`tzet()`][tzset] call.
#[link_name = "tz_localtime_r"]
pub fn localtime_r(timep: *const TimeT, tmp: *mut Tm) -> *mut Tm;
/// Convert UTC [`Tm`] to system time.
///
/// Rust interface for the C function:
/// ```C
/// time_t timegm(struct tm *);
/// ```
///
/// This function is like [`mktime`] except that it treats the `tmp` as UTC (ignoring the `tm_idst` and
/// `tm_zone` members).
#[link_name = "tz_timegm"]
pub fn timegm(tmp: *const Tm) -> TimeT;
/// Convert local time [`Tm`] to system time using globally configured time zone.
///
/// Rust interface for the C function:
/// ```C
/// time_t mktime(struct tm *);
/// ```
///
/// The `mktime` function converts the broken-down time, expressed as local time, in the structure pointed
/// to by `tm` into a calendar time value with the same encoding as that of the values returned by the
/// `time` function. The original values of the `tm_wday` and `tm_yday` components of the structure are
/// ignored, and the original values of the other components are not restricted to their normal ranges.
/// (A positive or zero value for `tm_isdst` causes `mktime` to presume initially that daylight saving
/// time respectively, is or is not in effect for the specified time.
///
/// A negative value for `tm_isdst` causes the `mktime` function to attempt to divine whether daylight
/// saving time is in effect for the specified time; in this case it does not use a consistent rule and
/// may give a different answer when later presented with the same argument.) On successful completion,
/// the values of the `tm_wday` and `tm_yday` components of the structure are set appropriately, and the
/// other components are set to represent the specified calendar time, but with their values forced to
/// their normal ranges; the final value of `tm_mday` is not set until `tm_mon` and `tm_year` are
/// determined. The `mktime` function returns the specified calendar time; If the calendar time cannot be
/// represented, it returns -1.
#[link_name = "tz_mktime"]
pub fn mktime(tmp: *const Tm) -> TimeT;
/// Re-read `TZ` environment variable and configure global time zone.
///
/// Rust interface for the C function:
/// ```C
/// void tzset(void);
/// ```
///
/// The `tzset` function acts like `tzalloc(getenv("TZ"))`, except it saves any resulting timezone object
/// into internal storage that is accessed by `localtime_r`, and `mktime`. The anonymous shared timezone
/// object is freed by the next call to `tzset`. If the implied call to `tzalloc` fails, `tzset` falls
/// back on Universal Time (UT).
#[link_name = "tz_tzset"]
pub fn tzset();
// -DNETBSD_INSPIRED
/// Allocate a configured time zone.
///
/// Rust interface for the C function:
/// ```C
/// timezone_t tzalloc(char const *);
/// ```
///
/// The `tzalloc` function allocates and returns a timezone object described
/// by `TZ`. If `TZ` is not a valid timezone description, or if the object
/// cannot be allocated, `tzalloc` returns a null pointer and sets `errno`.
///
#[doc = include_str!("tzalloc.md")]
#[link_name = "tz_tzalloc"]
pub fn tzalloc(zone: *const c_char) -> TimezoneT;
/// Free allocated time zone.
///
/// Rust interface for the C function:
/// ```C
/// void tzfree(timezone_t);
/// ```
///
/// The `tzfree` function frees a timezone object `tz`, which should have been successfully allocated by
/// `tzalloc`. This invalidates any `tm_zone` pointers that `tz` was used to set.
#[link_name = "tz_tzfree"]
pub fn tzfree(tz: TimezoneT);
/// Convert system time to local time using passed-in time zone.
///
/// Rust interface for the C function:
/// ```C
/// struct tm *localtime_rz(timezone_t restrict, time_t const *restrict, struct tm *restrict);
/// ```
///
/// This acts like [`localtime_r`] except it uses the passed in TimezoneT instead of the shared global
/// configuration. The pointer returned in [`Tm::tm_zone`] will be valid until the [`TimezoneT`] is freed
/// with [`tzfree()`][tzfree].
#[link_name = "tz_localtime_rz"]
pub fn localtime_rz(tz: TimezoneT, timep: *const TimeT, tmp: *mut Tm) -> *mut Tm;
/// Convert local time `Tm` to system time using passed-in time zone.
///
/// Rust interface for the C function:
/// ```C
/// time_t mktime_z(timezone_t restrict, struct tm *restrict);
/// ```
///
/// This acts like [`mktime`] except it uses the passed in TimezoneT instead of the shared global
/// configuration.
#[link_name = "tz_mktime_z"]
pub fn mktime_z(tz: TimezoneT, tmp: *const Tm) -> TimeT;
/// Convert from leap-second to POSIX `time_t`s.
///
/// Rust interface for the C function:
/// ```C
/// time_t posix2time_z(timezone_t, time_t);
/// ```
///
#[doc = include_str!("time2posix.md")]
#[link_name = "tz_posix2time_z"]
pub fn posix2time_z(tz: TimezoneT, t: TimeT) -> TimeT;
/// Convert from POSIX to leap-second `time_t`s.
///
/// Rust interface for the C function:
/// ```C
/// time_t time2posix_z(timezone_t, time_t);
/// ```
///
#[doc = include_str!("time2posix.md")]
#[link_name = "tz_time2posix_z"]
pub fn time2posix_z(tz: TimezoneT, t: TimeT) -> TimeT;
}
#[cfg(test)]
mod tests {
use std::ffi::{CString,CStr};
use std::mem::MaybeUninit;
use super::*;
#[test]
fn basic() {
let time: TimeT = 127810800;
std::env::set_var("TZ", "America/Los_Angeles");
unsafe { tzset() };
let mut tm = MaybeUninit::<Tm>::uninit();
let tmp = tm.as_mut_ptr();
let ret = unsafe { localtime_r(&time, tmp) };
assert_ne!(ret, std::ptr::null_mut());
assert_eq!(ret, tmp);
let tm = unsafe { tm.assume_init() };
let zone: &str = unsafe { CStr::from_ptr(tm.tm_zone).to_str().expect("correct utf8") };
assert_eq!((tm.tm_sec, tm.tm_min, tm.tm_hour, tm.tm_mday, tm.tm_mon, tm.tm_year, tm.tm_wday, tm.tm_yday, tm.tm_isdst, tm.tm_gmtoff, zone),
(0, 0, 0, 19, 0, 74, 6, 18, 1, -25200, "PDT"));
assert_eq!(unsafe { mktime(&tm) }, time); // Round trip
let time: TimeT = time + tm.tm_gmtoff;
assert_eq!(unsafe { timegm(&tm) }, time);
let mut tm = MaybeUninit::<Tm>::uninit();
let tmp = tm.as_mut_ptr();
let ret = unsafe { gmtime_r(&time, tmp) };
assert_ne!(ret, std::ptr::null_mut());
assert_eq!(ret, tmp);
let tm = unsafe { tm.assume_init() };
let zone: &str = unsafe { CStr::from_ptr(tm.tm_zone).to_str().expect("correct utf8") };
assert_eq!((tm.tm_sec, tm.tm_min, tm.tm_hour, tm.tm_mday, tm.tm_mon, tm.tm_year, tm.tm_wday, tm.tm_yday, tm.tm_isdst, tm.tm_gmtoff, zone),
(0, 0, 0, 19, 0, 74, 6, 18, 0, 0, "UTC"));
}
#[test]
fn localtime_rz_test() {
let tzname = CString::new("America/New_York").unwrap();
let tz = unsafe { tzalloc(tzname.as_ptr()) };
assert_ne!(tz, std::ptr::null_mut());
let time: TimeT = 127810800;
let mut tm = MaybeUninit::<Tm>::uninit();
let ret = unsafe { localtime_rz(tz, &time, tm.as_mut_ptr()) };
assert_ne!(ret, std::ptr::null_mut());
let tm = unsafe { tm.assume_init() };
let zone: &str = unsafe { CStr::from_ptr(tm.tm_zone).to_str().expect("correct utf8") };
assert_eq!((tm.tm_sec, tm.tm_min, tm.tm_hour, tm.tm_mday, tm.tm_mon, tm.tm_year, tm.tm_wday, tm.tm_yday, tm.tm_isdst, tm.tm_gmtoff, zone),
(0, 0, 3, 19, 0, 74, 6, 18, 1, -14400, "EDT"));
assert_eq!(unsafe { mktime_z(tz, &tm) }, time); // Round trip
unsafe { tzfree(tz) };
}
#[test]
fn posix_conversions() {
let posixtime: TimeT = 536457599;
let tzname = CString::new("UTC").unwrap();
let tz = unsafe { tzalloc(tzname.as_ptr()) };
assert_ne!(tz, std::ptr::null_mut());
let time = unsafe { posix2time_z(tz, posixtime) };
let mut tm = MaybeUninit::<Tm>::uninit();
let ret = unsafe { localtime_rz(tz, &time, tm.as_mut_ptr()) };
assert_ne!(ret, std::ptr::null_mut());
let tm = unsafe { tm.assume_init() };
let zone: &str = unsafe { CStr::from_ptr(tm.tm_zone).to_str().expect("correct utf8") };
assert_eq!((tm.tm_sec, tm.tm_min, tm.tm_hour, tm.tm_mday, tm.tm_mon, tm.tm_year, tm.tm_wday, tm.tm_yday, tm.tm_isdst, tm.tm_gmtoff, zone),
(59, 59, 23, 31, 11, 86, 3, 364, 0, 0, "UTC"));
assert_eq!(unsafe { time2posix_z(tz, time) }, posixtime); // Round Trip
unsafe { tzfree(tz) };
}
#[test]
fn test_readme_deps() {
version_sync::assert_markdown_deps_updated!("README.md");
}
}