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
#![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.
///
/// FIXME: This is libtz's `time_t`, which currently mirrors the system's `time_t`. This makes hardcoding it
/// as i64 incorrect! Should we try to mirror the system `time_t`? Or should we force libtz to compile with a
/// 64 bit `time_t` (which appears to be possible)?
pub type TimeT = i64; // FIXME: This isn't in Rust yet.
#[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 UTC.
///
/// 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`]).
pub fn gmtime_r(timep: *const TimeT, tmp: *mut Tm) -> *mut Tm;
/// Convert system time to local time 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`]).
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).
///
/// ```text
/// bad_rust_code();
/// ```
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.
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).
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")]
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.
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.
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.
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")]
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")]
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: super::TimeT = 127810800;
std::env::set_var("TZ", "America/Los_Angeles");
unsafe { super::tzset() };
let mut tm = MaybeUninit::<super::Tm>::uninit();
let tmp = tm.as_mut_ptr();
let ret = unsafe { super::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 { super::mktime(&tm) }, time); // Round trip
let time: TimeT = time + tm.tm_gmtoff;
assert_eq!(unsafe { super::timegm(&tm) }, time);
let mut tm = MaybeUninit::<super::Tm>::uninit();
let tmp = tm.as_mut_ptr();
let ret = unsafe { super::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 { super::tzalloc(tzname.as_ptr()) };
assert_ne!(tz, std::ptr::null_mut());
let time: super::TimeT = 127810800;
let mut tm = MaybeUninit::<super::Tm>::uninit();
let ret = unsafe { super::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 { super::mktime_z(tz, &tm) }, time); // Round trip
unsafe { super::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 { super::tzfree(tz) };
}
#[test]
fn test_readme_deps() {
version_sync::assert_markdown_deps_updated!("README.md");
}
}