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