c_gull/
time.rs

1//! Time and date conversion routines.
2//!
3//! This code is highly experimental.
4
5use alloc::ffi::CString;
6use core::cell::{OnceCell, SyncUnsafeCell};
7use core::ptr::{self, null_mut};
8use errno::{set_errno, Errno};
9use libc::{c_char, c_int, c_long, time_t, tm};
10use std::collections::HashSet;
11use std::sync::{Mutex, MutexGuard};
12use tz::timezone::TransitionRule;
13use tz::{DateTime, LocalTimeType, TimeZone};
14
15// These things aren't really thread-safe, but they're implementing C ABIs and
16// the C rule is, it's up to the user to ensure that none of the bad things
17// happen.
18#[repr(transparent)]
19struct SyncTm(tm);
20unsafe impl Sync for SyncTm {}
21
22// `tzname`, `timezone`, and `daylight` are guarded by `TIMEZONE_LOCK`.
23#[repr(transparent)]
24struct SyncTzName([*mut c_char; 2]);
25unsafe impl Sync for SyncTzName {}
26
27// Hold `TIMEZONE_LOCK` when updating `tzname`, `timezone`, and `daylight`.
28static TIMEZONE_LOCK: Mutex<(Option<CString>, Option<CString>)> = Mutex::new((None, None));
29#[no_mangle]
30static mut tzname: SyncTzName = SyncTzName([null_mut(), null_mut()]);
31#[no_mangle]
32static mut timezone: c_long = 0;
33#[no_mangle]
34static mut daylight: c_int = 0;
35
36// Alias `__timezone` to `timezone`.
37core::arch::global_asm!(".globl __timezone", ".set __timezone, timezone");
38
39// Name storage for the `tm_zone` field.
40static TIMEZONE_NAMES: Mutex<OnceCell<HashSet<CString>>> = Mutex::new(OnceCell::new());
41
42#[no_mangle]
43unsafe extern "C" fn gmtime(time: *const time_t) -> *mut tm {
44    libc!(libc::gmtime(time));
45
46    static TM: SyncUnsafeCell<SyncTm> = SyncUnsafeCell::new(SyncTm(blank_tm()));
47    gmtime_r(time, &mut (*TM.get()).0)
48}
49
50#[no_mangle]
51unsafe extern "C" fn gmtime_r(time: *const time_t, result: *mut tm) -> *mut tm {
52    libc!(libc::gmtime_r(time, result));
53
54    let utc_time_type = LocalTimeType::utc();
55
56    let date_time = match DateTime::from_timespec_and_local((*time).into(), 0, utc_time_type) {
57        Ok(date_time) => date_time,
58        Err(_err) => {
59            set_errno(Errno(libc::EIO));
60            return null_mut();
61        }
62    };
63
64    ptr::write(result, date_time_to_tm(date_time));
65
66    result
67}
68
69#[no_mangle]
70unsafe extern "C" fn localtime(time: *const time_t) -> *mut tm {
71    libc!(libc::localtime(time));
72
73    static TM: SyncUnsafeCell<SyncTm> = SyncUnsafeCell::new(SyncTm(blank_tm()));
74    localtime_r(time, &mut (*TM.get()).0)
75}
76
77#[no_mangle]
78unsafe extern "C" fn localtime_r(time: *const time_t, result: *mut tm) -> *mut tm {
79    libc!(libc::localtime_r(time, result));
80
81    let time_zone = match time_zone() {
82        Ok(time_zone) => time_zone,
83        Err(err) => {
84            set_errno(tz_error_to_errno(err));
85            return null_mut();
86        }
87    };
88
89    let local_time_type = match time_zone.find_local_time_type((*time).into()) {
90        Ok(local_time_type) => local_time_type,
91        Err(_err) => {
92            set_errno(Errno(libc::EIO));
93            return null_mut();
94        }
95    };
96
97    let date_time = match DateTime::from_timespec_and_local((*time).into(), 0, *local_time_type) {
98        Ok(date_time) => date_time,
99        Err(_err) => {
100            set_errno(Errno(libc::EIO));
101            return null_mut();
102        }
103    };
104
105    ptr::write(result, date_time_to_tm(date_time));
106
107    result
108}
109
110fn tm_to_date_time(tm: &tm, time_zone: &TimeZone) -> Result<DateTime, Errno> {
111    let tm_year = tm.tm_year;
112    let tm_mon: u8 = match tm.tm_mon.try_into() {
113        Ok(tm_mon) => tm_mon,
114        Err(_err) => return Err(Errno(libc::EOVERFLOW)),
115    };
116    let tm_mday = match tm.tm_mday.try_into() {
117        Ok(tm_mday) => tm_mday,
118        Err(_err) => return Err(Errno(libc::EOVERFLOW)),
119    };
120    let tm_hour = match tm.tm_hour.try_into() {
121        Ok(tm_hour) => tm_hour,
122        Err(_err) => return Err(Errno(libc::EOVERFLOW)),
123    };
124    let tm_min = match tm.tm_min.try_into() {
125        Ok(tm_min) => tm_min,
126        Err(_err) => return Err(Errno(libc::EOVERFLOW)),
127    };
128    let tm_sec = match tm.tm_sec.try_into() {
129        Ok(tm_sec) => tm_sec,
130        Err(_err) => return Err(Errno(libc::EOVERFLOW)),
131    };
132
133    let utc;
134    let (std_time_type, dst_time_type) = if let Some(extra_rule) = time_zone.as_ref().extra_rule() {
135        match extra_rule {
136            TransitionRule::Fixed(local_time_type) => (local_time_type, None),
137            TransitionRule::Alternate(alternate_time_type) => {
138                (alternate_time_type.std(), Some(alternate_time_type.dst()))
139            }
140        }
141    } else {
142        utc = LocalTimeType::utc();
143        (&utc, None)
144    };
145
146    let date_time = match tm.tm_isdst {
147        tm_isdst if tm_isdst < 0 => {
148            match DateTime::find(
149                tm_year + 1900,
150                tm_mon + 1,
151                tm_mday,
152                tm_hour,
153                tm_min,
154                tm_sec,
155                0,
156                time_zone.as_ref(),
157            ) {
158                Ok(found) => {
159                    match found.unique() {
160                        Some(date_time) => date_time,
161                        None => {
162                            // It's not clear what we should do here, so for
163                            // now just be conservative.
164                            return Err(Errno(libc::EIO));
165                        }
166                    }
167                }
168                Err(_err) => return Err(Errno(libc::EIO)),
169            }
170        }
171        tm_isdst => {
172            let time_type = if tm_isdst > 0 {
173                dst_time_type.unwrap_or(std_time_type)
174            } else {
175                std_time_type
176            };
177            match DateTime::new(
178                tm_year + 1900,
179                tm_mon + 1,
180                tm_mday,
181                tm_hour,
182                tm_min,
183                tm_sec,
184                0,
185                *time_type,
186            ) {
187                Ok(date_time) => date_time,
188                Err(_err) => return Err(Errno(libc::EIO)),
189            }
190        }
191    };
192
193    Ok(date_time)
194}
195
196#[no_mangle]
197unsafe extern "C" fn mktime(tm: *mut tm) -> time_t {
198    libc!(libc::mktime(tm));
199
200    let mut lock = TIMEZONE_LOCK.lock().unwrap();
201
202    clear_timezone(&mut lock);
203
204    let time_zone = match time_zone() {
205        Ok(time_zone) => time_zone,
206        Err(err) => {
207            set_errno(tz_error_to_errno(err));
208            return -1;
209        }
210    };
211
212    let utc;
213    let (std_time_type, dst_time_type) = if let Some(extra_rule) = time_zone.as_ref().extra_rule() {
214        match extra_rule {
215            TransitionRule::Fixed(local_time_type) => (local_time_type, None),
216            TransitionRule::Alternate(alternate_time_type) => {
217                (alternate_time_type.std(), Some(alternate_time_type.dst()))
218            }
219        }
220    } else {
221        utc = LocalTimeType::utc();
222        (&utc, None)
223    };
224
225    let date_time = match tm_to_date_time(&*tm, &time_zone) {
226        Ok(date_time) => date_time,
227        Err(errno) => {
228            set_errno(errno);
229            return -1;
230        }
231    };
232
233    let unix_time = match date_time.unix_time().try_into() {
234        Ok(unix_time) => unix_time,
235        Err(_err) => {
236            set_errno(Errno(libc::EOVERFLOW));
237            -1
238        }
239    };
240
241    ptr::write(tm, date_time_to_tm(date_time));
242
243    set_timezone(&mut lock, std_time_type, dst_time_type);
244
245    unix_time
246}
247
248fn date_time_to_tm(date_time: DateTime) -> tm {
249    let local_time_type = date_time.local_time_type();
250
251    let tm_zone = {
252        let mut timezone_names = TIMEZONE_NAMES.lock().unwrap();
253        timezone_names.get_or_init(HashSet::new);
254        let cstr = CString::new(local_time_type.time_zone_designation()).unwrap();
255        // TODO: Use `get_or_insert` when `hash_set_entry` is stabilized.
256        timezone_names.get_mut().unwrap().insert(cstr.clone());
257        timezone_names.get().unwrap().get(&cstr).unwrap().as_ptr()
258    };
259
260    tm {
261        tm_year: date_time.year() - 1900,
262        tm_mon: (date_time.month() - 1).into(),
263        tm_mday: date_time.month_day().into(),
264        tm_hour: date_time.hour().into(),
265        tm_min: date_time.minute().into(),
266        tm_sec: date_time.second().into(),
267        tm_wday: date_time.week_day().into(),
268        tm_yday: date_time.year_day().into(),
269        tm_isdst: if local_time_type.is_dst() { 1 } else { 0 },
270        tm_gmtoff: local_time_type.ut_offset().into(),
271        tm_zone,
272    }
273}
274
275const fn blank_tm() -> tm {
276    tm {
277        tm_year: 0,
278        tm_mon: 0,
279        tm_mday: 0,
280        tm_hour: 0,
281        tm_min: 0,
282        tm_sec: 0,
283        tm_wday: 0,
284        tm_yday: 0,
285        tm_isdst: -1,
286        tm_gmtoff: 0,
287        tm_zone: null_mut(),
288    }
289}
290
291fn tz_error_to_errno(err: tz::Error) -> Errno {
292    // TODO: Are there more meaningful errno values we could infer?
293    let _ = err;
294    Errno(libc::EIO)
295}
296
297#[no_mangle]
298unsafe extern "C" fn timegm(tm: *mut tm) -> time_t {
299    libc!(libc::timegm(tm));
300
301    let time_zone_utc = TimeZone::utc();
302
303    let date_time = match tm_to_date_time(&*tm, &time_zone_utc) {
304        Ok(date_time) => date_time,
305        Err(errno) => {
306            set_errno(errno);
307            return -1;
308        }
309    };
310
311    let unix_time = match date_time.unix_time().try_into() {
312        Ok(unix_time) => unix_time,
313        Err(_err) => {
314            set_errno(Errno(libc::EOVERFLOW));
315            -1
316        }
317    };
318
319    unix_time
320}
321
322#[no_mangle]
323unsafe extern "C" fn timelocal(tm: *mut tm) -> time_t {
324    //libc!(libc::timelocal(tm)); // TODO: upstream
325
326    let time_zone = match time_zone() {
327        Ok(time_zone) => time_zone,
328        Err(err) => {
329            set_errno(tz_error_to_errno(err));
330            return -1;
331        }
332    };
333
334    let date_time = match tm_to_date_time(&*tm, &time_zone) {
335        Ok(date_time) => date_time,
336        Err(errno) => {
337            set_errno(errno);
338            return -1;
339        }
340    };
341
342    let unix_time = match date_time.unix_time().try_into() {
343        Ok(unix_time) => unix_time,
344        Err(_err) => {
345            set_errno(Errno(libc::EOVERFLOW));
346            -1
347        }
348    };
349
350    unix_time
351}
352
353#[no_mangle]
354unsafe extern "C" fn tzset() {
355    //libc!(libc::tzset()); // TODO: upstream
356
357    let mut lock = TIMEZONE_LOCK.lock().unwrap();
358
359    clear_timezone(&mut lock);
360
361    let time_zone = match time_zone() {
362        Ok(time_zone) => time_zone,
363        Err(_) => return,
364    };
365
366    if let Some(extra_rule) = time_zone.as_ref().extra_rule() {
367        match extra_rule {
368            TransitionRule::Fixed(local_time_type) => {
369                set_timezone(&mut lock, local_time_type, None);
370            }
371            TransitionRule::Alternate(alternate_time_type) => {
372                let std = alternate_time_type.std();
373                let dst = alternate_time_type.dst();
374                set_timezone(&mut lock, std, Some(dst));
375            }
376        }
377    }
378}
379
380fn time_zone() -> Result<TimeZone, tz::Error> {
381    match std::env::var("TZ") {
382        Ok(tz) => TimeZone::from_posix_tz(&tz),
383        Err(_) => TimeZone::local(),
384    }
385}
386
387unsafe fn set_timezone(
388    guard: &mut MutexGuard<'_, (Option<CString>, Option<CString>)>,
389    std: &LocalTimeType,
390    dst: Option<&LocalTimeType>,
391) {
392    guard.0 = Some(CString::new(std.time_zone_designation()).unwrap());
393    tzname.0[0] = guard.0.as_ref().unwrap().as_ptr().cast_mut();
394
395    match dst {
396        Some(dst) => {
397            guard.1 = Some(CString::new(dst.time_zone_designation()).unwrap());
398            tzname.0[1] = guard.1.as_ref().unwrap().as_ptr().cast_mut();
399            daylight = 1;
400        }
401        None => {
402            guard.1 = None;
403            tzname.0[1] = guard.0.as_ref().unwrap().as_ptr().cast_mut();
404            daylight = 0;
405        }
406    }
407
408    let ut_offset = std.ut_offset();
409    timezone = -c_long::from(ut_offset);
410}
411
412unsafe fn clear_timezone(guard: &mut MutexGuard<'_, (Option<CString>, Option<CString>)>) {
413    guard.0 = None;
414    guard.1 = None;
415    tzname.0[0] = null_mut();
416    tzname.0[1] = null_mut();
417    timezone = 0;
418    daylight = 0;
419}