1use 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#[repr(transparent)]
19struct SyncTm(tm);
20unsafe impl Sync for SyncTm {}
21
22#[repr(transparent)]
24struct SyncTzName([*mut c_char; 2]);
25unsafe impl Sync for SyncTzName {}
26
27static 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
36core::arch::global_asm!(".globl __timezone", ".set __timezone, timezone");
38
39static 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 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 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 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 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 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}