1#[cfg(unix)]
2use alloc::ffi::CString;
3use core::time::Duration;
4use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH};
5
6#[cfg(target_env = "msvc")]
7use alloc::string::String;
8
9pub const SEC_TO_MS: i64 = 1000;
10pub const MS_TO_US: i64 = 1000;
11pub const SEC_TO_US: i64 = SEC_TO_MS * MS_TO_US;
12pub const US_TO_NS: i64 = 1000;
13pub const MS_TO_NS: i64 = MS_TO_US * US_TO_NS;
14pub const SEC_TO_NS: i64 = SEC_TO_MS * MS_TO_NS;
15pub const NS_TO_MS: i64 = 1000 * 1000;
16pub const NS_TO_US: i64 = 1000;
17
18#[cfg(all(not(target_env = "msvc"), not(target_arch = "wasm32")))]
24pub mod tz {
25 unsafe extern "C" {
26 #[cfg(not(target_os = "freebsd"))]
27 #[link_name = "daylight"]
28 static c_daylight: core::ffi::c_int;
29 #[link_name = "timezone"]
30 static c_timezone: core::ffi::c_long;
31 #[cfg(has_altzone)]
33 #[link_name = "altzone"]
34 static c_altzone: core::ffi::c_long;
35 #[link_name = "tzname"]
36 static c_tzname: [*const core::ffi::c_char; 2];
37 #[link_name = "tzset"]
38 fn c_tzset();
39 }
40
41 pub fn tzset() {
42 unsafe { c_tzset() }
43 }
44
45 #[must_use]
46 pub fn timezone() -> core::ffi::c_long {
47 unsafe { c_timezone }
48 }
49
50 #[must_use]
55 pub fn altzone() -> core::ffi::c_long {
56 #[cfg(has_altzone)]
57 {
58 unsafe { c_altzone }
59 }
60 #[cfg(not(has_altzone))]
61 {
62 timezone() - 3600
63 }
64 }
65
66 #[cfg(not(target_os = "freebsd"))]
67 #[must_use]
68 pub fn daylight() -> core::ffi::c_int {
69 unsafe { c_daylight }
70 }
71
72 #[must_use]
76 pub fn tzname_strings() -> (String, String) {
77 unsafe fn to_str(s: *const core::ffi::c_char) -> String {
78 unsafe { core::ffi::CStr::from_ptr(s) }
79 .to_string_lossy()
80 .into_owned()
81 }
82 unsafe { (to_str(c_tzname[0]), to_str(c_tzname[1])) }
83 }
84}
85
86pub fn duration_since_system_now() -> Result<Duration, SystemTimeError> {
87 SystemTime::now().duration_since(UNIX_EPOCH)
88}
89
90#[cfg(unix)]
91pub type TimeT = libc::time_t;
92
93#[cfg(windows)]
94pub type TimeT = libc::time_t;
95
96#[cfg(unix)]
97#[derive(Clone, Copy, Debug)]
98pub struct ProcessTimes {
99 pub user: f64,
100 pub system: f64,
101 pub children_user: f64,
102 pub children_system: f64,
103 pub elapsed: f64,
104}
105
106#[cfg(unix)]
107#[cfg_attr(target_env = "musl", allow(deprecated))]
108pub fn current_time_t() -> TimeT {
109 unsafe { libc::time(core::ptr::null_mut()) }
110}
111
112#[cfg(unix)]
113#[cfg_attr(target_env = "musl", allow(deprecated))]
114pub fn gmtime_from_timestamp(when: TimeT) -> Option<libc::tm> {
115 let mut out = core::mem::MaybeUninit::<libc::tm>::uninit();
116 let ret = unsafe { libc::gmtime_r(&when, out.as_mut_ptr()) };
117 (!ret.is_null()).then(|| unsafe { out.assume_init() })
118}
119
120#[cfg(unix)]
121#[cfg_attr(target_env = "musl", allow(deprecated))]
122pub fn localtime_from_timestamp(when: TimeT) -> Option<libc::tm> {
123 let mut out = core::mem::MaybeUninit::<libc::tm>::uninit();
124 let ret = unsafe { libc::localtime_r(&when, out.as_mut_ptr()) };
125 (!ret.is_null()).then(|| unsafe { out.assume_init() })
126}
127
128#[cfg(unix)]
129pub fn mktime(tm: &mut libc::tm) -> TimeT {
130 unsafe { libc::mktime(tm) }
131}
132
133#[cfg(windows)]
134unsafe extern "C" {
135 fn _gmtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int;
136 fn _localtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int;
137 #[link_name = "_mktime64"]
138 fn c_mktime(tm: *mut libc::tm) -> libc::time_t;
139}
140
141#[cfg(windows)]
142#[cfg_attr(target_env = "musl", allow(deprecated))]
143pub fn current_time_t() -> TimeT {
144 unsafe { libc::time(core::ptr::null_mut()) }
145}
146
147#[cfg(windows)]
148#[cfg_attr(target_env = "musl", allow(deprecated))]
149pub fn gmtime_from_timestamp(when: TimeT) -> Option<libc::tm> {
150 let mut out = core::mem::MaybeUninit::<libc::tm>::uninit();
151 let err = unsafe { _gmtime64_s(out.as_mut_ptr(), &when) };
152 (err == 0).then(|| unsafe { out.assume_init() })
153}
154
155#[cfg(windows)]
156#[cfg_attr(target_env = "musl", allow(deprecated))]
157pub fn localtime_from_timestamp(when: TimeT) -> Option<libc::tm> {
158 let mut out = core::mem::MaybeUninit::<libc::tm>::uninit();
159 let err = unsafe { _localtime64_s(out.as_mut_ptr(), &when) };
160 (err == 0).then(|| unsafe { out.assume_init() })
161}
162
163#[cfg(windows)]
164pub fn mktime(tm: &mut libc::tm) -> TimeT {
165 unsafe { crate::suppress_iph!(c_mktime(tm)) }
166}
167
168#[cfg(any(unix, windows, target_os = "wasi"))]
169pub fn strerror(errno: i32) -> String {
170 unsafe { core::ffi::CStr::from_ptr(libc::strerror(errno)) }
171 .to_string_lossy()
172 .into_owned()
173}
174
175#[cfg(unix)]
176pub fn nix_errno_display(errno: i32) -> String {
177 nix::errno::Errno::from_raw(errno).to_string()
178}
179
180#[cfg(all(unix, not(any(target_os = "redox", target_os = "android"))))]
181pub fn getloadavg() -> std::io::Result<[f64; 3]> {
182 let mut loadavg = [0f64; 3];
183 let ok = unsafe { libc::getloadavg(loadavg.as_mut_ptr(), 3) };
184 if ok != 3 {
185 Err(std::io::Error::last_os_error())
186 } else {
187 Ok(loadavg)
188 }
189}
190
191#[cfg(unix)]
192pub fn waitstatus_to_exitcode(status: libc::c_int) -> Option<i32> {
193 if libc::WIFEXITED(status) {
194 return Some(libc::WEXITSTATUS(status));
195 }
196 if libc::WIFSIGNALED(status) {
197 return Some(-libc::WTERMSIG(status));
198 }
199 None
200}
201
202#[cfg(any(unix, all(target_arch = "wasm32", target_os = "emscripten")))]
203pub fn process_times() -> std::io::Result<ProcessTimes> {
204 let mut t = libc::tms {
205 tms_utime: 0,
206 tms_stime: 0,
207 tms_cutime: 0,
208 tms_cstime: 0,
209 };
210
211 let tick_for_second = unsafe { libc::sysconf(libc::_SC_CLK_TCK) };
212 if tick_for_second <= 0 {
213 return Err(std::io::Error::last_os_error());
214 }
215 let tick_for_second = tick_for_second as f64;
216 let c = unsafe { libc::times(&mut t as *mut _) };
217 if c == (-1i8) as libc::clock_t {
218 return Err(std::io::Error::last_os_error());
219 }
220
221 Ok(ProcessTimes {
222 user: t.tms_utime as f64 / tick_for_second,
223 system: t.tms_stime as f64 / tick_for_second,
224 children_user: t.tms_cutime as f64 / tick_for_second,
225 children_system: t.tms_cstime as f64 / tick_for_second,
226 elapsed: c as f64 / tick_for_second,
227 })
228}
229
230#[cfg(any(unix, target_os = "wasi"))]
231#[derive(Copy, Clone, Debug)]
232#[cfg_attr(unix, derive(Eq, PartialEq))]
234pub struct ClockId(libc::clockid_t);
235
236#[cfg(any(unix, target_os = "wasi"))]
237impl ClockId {
238 pub const fn from_raw(raw: libc::clockid_t) -> Self {
239 Self(raw)
240 }
241
242 pub const fn as_raw(self) -> libc::clockid_t {
243 self.0
244 }
245
246 pub const CLOCK_MONOTONIC: Self = Self(libc::CLOCK_MONOTONIC);
247 pub const CLOCK_REALTIME: Self = Self(libc::CLOCK_REALTIME);
248
249 #[cfg(not(any(
250 target_os = "illumos",
251 target_os = "netbsd",
252 target_os = "solaris",
253 target_os = "openbsd",
254 target_os = "wasi",
255 )))]
256 pub const CLOCK_PROCESS_CPUTIME_ID: Self = Self(libc::CLOCK_PROCESS_CPUTIME_ID);
257
258 #[cfg(not(any(
259 target_os = "illumos",
260 target_os = "netbsd",
261 target_os = "solaris",
262 target_os = "openbsd",
263 target_os = "redox",
264 target_os = "wasi",
265 )))]
266 pub const CLOCK_THREAD_CPUTIME_ID: Self = Self(libc::CLOCK_THREAD_CPUTIME_ID);
267
268 #[cfg(target_vendor = "apple")]
269 pub const CLOCK_UPTIME_RAW: Self = Self(libc::CLOCK_UPTIME_RAW);
270}
271
272#[cfg(unix)]
273pub use libc::{CLOCK_MONOTONIC, CLOCK_REALTIME};
274
275#[cfg(target_os = "solaris")]
276pub use libc::CLOCK_HIGHRES;
277
278#[cfg(any(target_os = "linux", target_vendor = "apple"))]
279pub use libc::CLOCK_MONOTONIC_RAW;
280
281#[cfg(all(
282 unix,
283 not(any(
284 target_os = "illumos",
285 target_os = "netbsd",
286 target_os = "solaris",
287 target_os = "openbsd",
288 ))
289))]
290pub use libc::CLOCK_PROCESS_CPUTIME_ID;
291
292#[cfg(all(
293 unix,
294 not(any(
295 target_os = "illumos",
296 target_os = "netbsd",
297 target_os = "solaris",
298 target_os = "openbsd",
299 target_os = "redox",
300 ))
301))]
302pub use libc::CLOCK_THREAD_CPUTIME_ID;
303
304#[cfg(target_os = "linux")]
305pub use libc::{CLOCK_BOOTTIME, CLOCK_TAI};
306
307#[cfg(target_vendor = "apple")]
308pub use libc::{CLOCK_MONOTONIC_RAW_APPROX, CLOCK_UPTIME_RAW, CLOCK_UPTIME_RAW_APPROX};
309
310#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly"))]
311pub use libc::{CLOCK_PROF, CLOCK_UPTIME};
312
313#[cfg(unix)]
314fn nix_clock_id(id: ClockId) -> nix::time::ClockId {
315 nix::time::ClockId::from_raw(id.as_raw())
316}
317
318#[cfg(unix)]
319pub fn clock_gettime(id: ClockId) -> std::io::Result<Duration> {
320 nix::time::clock_gettime(nix_clock_id(id))
321 .map(Duration::from)
322 .map_err(std::io::Error::from)
323}
324
325#[cfg(target_os = "wasi")]
326pub fn clock_gettime(id: ClockId) -> std::io::Result<Duration> {
327 let mut ts = core::mem::MaybeUninit::<libc::timespec>::uninit();
328
329 let ret = unsafe { libc::clock_gettime(id.as_raw(), ts.as_mut_ptr()) };
330 if ret != 0 {
331 return Err(std::io::Error::last_os_error());
332 }
333
334 let ts = unsafe { ts.assume_init() };
335
336 Ok(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32))
337}
338
339#[cfg(all(unix, not(target_os = "redox")))]
340pub fn clock_getres(id: ClockId) -> std::io::Result<Duration> {
341 nix::time::clock_getres(nix_clock_id(id))
342 .map(Duration::from)
343 .map_err(std::io::Error::from)
344}
345
346#[cfg(target_os = "wasi")]
347pub fn clock_getres(id: ClockId) -> std::io::Result<Duration> {
348 let mut ts = core::mem::MaybeUninit::<libc::timespec>::uninit();
349 let ret = unsafe { libc::clock_getres(id.as_raw(), ts.as_mut_ptr()) };
350 if ret != 0 {
351 return Err(std::io::Error::last_os_error());
352 }
353 let ts = unsafe { ts.assume_init() };
354 Ok(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32))
355}
356
357#[cfg(all(unix, not(target_os = "redox"), not(target_vendor = "apple")))]
358pub fn clock_settime(id: ClockId, time: Duration) -> std::io::Result<()> {
359 let ts = nix::sys::time::TimeSpec::from(time);
360 nix::time::clock_settime(nix_clock_id(id), ts)
361 .map(drop)
362 .map_err(std::io::Error::from)
363}
364
365#[cfg(all(unix, not(target_os = "redox"), target_os = "macos"))]
366pub fn clock_settime(id: ClockId, time: Duration) -> std::io::Result<()> {
367 let ts = nix::sys::time::TimeSpec::from(time);
368 let ret = unsafe { libc::clock_settime(id.as_raw(), ts.as_ref()) };
369 if ret != 0 {
370 Err(std::io::Error::last_os_error())
371 } else {
372 Ok(())
373 }
374}
375
376#[cfg(unix)]
377pub fn nanosleep(duration: Duration) -> std::io::Result<()> {
378 let ts = nix::sys::time::TimeSpec::from(duration);
379 let ret = unsafe { libc::nanosleep(ts.as_ref(), core::ptr::null_mut()) };
380 if ret != 0 {
381 Err(std::io::Error::last_os_error())
382 } else {
383 Ok(())
384 }
385}
386
387#[cfg(target_os = "solaris")]
388pub fn gethrvtime_duration() -> Duration {
389 Duration::from_nanos(unsafe { libc::gethrvtime() })
390}
391
392#[cfg(windows)]
393#[cfg(not(target_arch = "wasm32"))]
394#[derive(Clone, Debug)]
395pub struct WindowsTimeZoneInfo {
396 pub bias: i32,
397 pub standard_bias: i32,
398 pub daylight_bias: i32,
399 pub standard_name: String,
400 pub daylight_name: String,
401}
402
403#[cfg(windows)]
404#[cfg(not(target_arch = "wasm32"))]
405fn decode_tz_name(name: &[u16]) -> String {
406 widestring::decode_utf16_lossy(name.iter().copied())
407 .take_while(|&c| c != '\0')
408 .collect()
409}
410
411#[cfg(windows)]
412#[cfg(not(target_arch = "wasm32"))]
413#[must_use]
414pub fn get_tz_info() -> WindowsTimeZoneInfo {
415 let mut info = unsafe { core::mem::zeroed() };
416 unsafe { windows_sys::Win32::System::Time::GetTimeZoneInformation(&mut info) };
417 WindowsTimeZoneInfo {
418 bias: info.Bias as i32,
419 standard_bias: info.StandardBias as i32,
420 daylight_bias: info.DaylightBias as i32,
421 standard_name: decode_tz_name(&info.StandardName),
422 daylight_name: decode_tz_name(&info.DaylightName),
423 }
424}
425
426#[cfg(windows)]
427fn u64_from_filetime(time: windows_sys::Win32::Foundation::FILETIME) -> u64 {
428 u64::from(time.dwLowDateTime) | (u64::from(time.dwHighDateTime) << 32)
429}
430
431#[cfg(windows)]
432#[derive(Clone, Copy, Debug)]
433pub struct ProcessTimes100ns {
434 pub user: u64,
435 pub system: u64,
436}
437
438#[cfg(windows)]
439pub fn query_performance_frequency() -> Option<i64> {
440 let mut freq = core::mem::MaybeUninit::uninit();
441 (unsafe {
442 windows_sys::Win32::System::Performance::QueryPerformanceFrequency(freq.as_mut_ptr())
443 } != 0)
444 .then(|| unsafe { freq.assume_init() })
445}
446
447#[cfg(windows)]
448pub fn query_performance_counter() -> i64 {
449 let mut counter = core::mem::MaybeUninit::uninit();
450 unsafe {
451 windows_sys::Win32::System::Performance::QueryPerformanceCounter(counter.as_mut_ptr());
452 counter.assume_init()
453 }
454}
455
456#[cfg(windows)]
457pub fn get_system_time_adjustment() -> Option<u32> {
458 let mut time_adjustment = core::mem::MaybeUninit::uninit();
459 let mut time_increment = core::mem::MaybeUninit::uninit();
460 let mut is_time_adjustment_disabled = core::mem::MaybeUninit::uninit();
461 (unsafe {
462 windows_sys::Win32::System::SystemInformation::GetSystemTimeAdjustment(
463 time_adjustment.as_mut_ptr(),
464 time_increment.as_mut_ptr(),
465 is_time_adjustment_disabled.as_mut_ptr(),
466 )
467 } != 0)
468 .then(|| unsafe { time_increment.assume_init() })
469}
470
471#[cfg(windows)]
472pub fn tick_count64() -> u64 {
473 unsafe { windows_sys::Win32::System::SystemInformation::GetTickCount64() }
474}
475
476#[cfg(windows)]
477pub fn get_thread_time_100ns() -> Option<u64> {
478 let mut creation_time = core::mem::MaybeUninit::uninit();
479 let mut exit_time = core::mem::MaybeUninit::uninit();
480 let mut kernel_time = core::mem::MaybeUninit::uninit();
481 let mut user_time = core::mem::MaybeUninit::uninit();
482 (unsafe {
483 windows_sys::Win32::System::Threading::GetThreadTimes(
484 windows_sys::Win32::System::Threading::GetCurrentThread(),
485 creation_time.as_mut_ptr(),
486 exit_time.as_mut_ptr(),
487 kernel_time.as_mut_ptr(),
488 user_time.as_mut_ptr(),
489 )
490 } != 0)
491 .then(|| unsafe {
492 u64_from_filetime(kernel_time.assume_init())
493 + u64_from_filetime(user_time.assume_init())
494 })
495}
496
497#[cfg(windows)]
498pub fn get_process_time_100ns() -> Option<u64> {
499 get_process_times_100ns().map(|times| times.user + times.system)
500}
501
502#[cfg(windows)]
503pub fn get_process_times_100ns() -> Option<ProcessTimes100ns> {
504 let mut creation_time = core::mem::MaybeUninit::uninit();
505 let mut exit_time = core::mem::MaybeUninit::uninit();
506 let mut kernel_time = core::mem::MaybeUninit::uninit();
507 let mut user_time = core::mem::MaybeUninit::uninit();
508 (unsafe {
509 windows_sys::Win32::System::Threading::GetProcessTimes(
510 windows_sys::Win32::System::Threading::GetCurrentProcess(),
511 creation_time.as_mut_ptr(),
512 exit_time.as_mut_ptr(),
513 kernel_time.as_mut_ptr(),
514 user_time.as_mut_ptr(),
515 )
516 } != 0)
517 .then(|| unsafe {
518 ProcessTimes100ns {
519 user: u64_from_filetime(user_time.assume_init()),
520 system: u64_from_filetime(kernel_time.assume_init()),
521 }
522 })
523}
524
525#[cfg(any(unix, windows))]
526pub fn asctime_from_tm(tm: &libc::tm) -> String {
527 const WDAY_NAME: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
528 const MON_NAME: [&str; 12] = [
529 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
530 ];
531 format!(
532 "{} {}{:>3} {:02}:{:02}:{:02} {}",
533 WDAY_NAME[tm.tm_wday as usize],
534 MON_NAME[tm.tm_mon as usize],
535 tm.tm_mday,
536 tm.tm_hour,
537 tm.tm_min,
538 tm.tm_sec,
539 tm.tm_year + 1900
540 )
541}
542
543#[cfg(any(unix, windows))]
544#[derive(Clone, Debug)]
545pub struct CheckedTm {
546 pub tm: libc::tm,
547 #[cfg(unix)]
548 pub zone: Option<CString>,
549}
550
551#[cfg(any(unix, windows))]
552#[derive(Clone, Debug)]
553pub struct CheckedTmParts {
554 pub year: i64,
555 pub tm_mon: i32,
556 pub tm_mday: i32,
557 pub tm_hour: i32,
558 pub tm_min: i32,
559 pub tm_sec: i32,
560 pub tm_wday: i32,
561 pub tm_yday: i32,
562 pub tm_isdst: i32,
563 #[cfg(unix)]
564 pub zone: Option<String>,
565 #[cfg(unix)]
566 pub gmtoff: Option<i64>,
567}
568
569#[cfg(any(unix, windows))]
570#[derive(Clone, Copy, Debug)]
571pub struct MktimeTmParts {
572 pub year: i32,
573 pub tm_sec: i32,
574 pub tm_min: i32,
575 pub tm_hour: i32,
576 pub tm_mday: i32,
577 pub tm_mon: i32,
578 pub tm_yday: i32,
579 pub tm_isdst: i32,
580}
581
582#[cfg(any(unix, windows))]
583#[derive(Clone, Copy, Debug, Eq, PartialEq)]
584pub enum CheckedTmError {
585 YearOutOfRange,
586 MonthOutOfRange,
587 DayOfMonthOutOfRange,
588 HourOutOfRange,
589 MinuteOutOfRange,
590 SecondsOutOfRange,
591 DayOfWeekOutOfRange,
592 DayOfYearOutOfRange,
593 EmbeddedNul,
594}
595
596#[cfg(any(unix, windows))]
597pub fn checked_tm_from_parts(parts: CheckedTmParts) -> Result<CheckedTm, CheckedTmError> {
598 if parts.year < i64::from(i32::MIN) + 1900 || parts.year > i64::from(i32::MAX) {
599 return Err(CheckedTmError::YearOutOfRange);
600 }
601
602 let mut tm: libc::tm = unsafe { core::mem::zeroed() };
603 tm.tm_year = parts.year as i32 - 1900;
604 tm.tm_mon = parts.tm_mon;
605 tm.tm_mday = parts.tm_mday;
606 tm.tm_hour = parts.tm_hour;
607 tm.tm_min = parts.tm_min;
608 tm.tm_sec = parts.tm_sec;
609 tm.tm_wday = parts.tm_wday;
610 tm.tm_yday = parts.tm_yday;
611 tm.tm_isdst = parts.tm_isdst;
612
613 if tm.tm_mon == -1 {
614 tm.tm_mon = 0;
615 } else if !(0..=11).contains(&tm.tm_mon) {
616 return Err(CheckedTmError::MonthOutOfRange);
617 }
618 if tm.tm_mday == 0 {
619 tm.tm_mday = 1;
620 } else if !(0..=31).contains(&tm.tm_mday) {
621 return Err(CheckedTmError::DayOfMonthOutOfRange);
622 }
623 if !(0..=23).contains(&tm.tm_hour) {
624 return Err(CheckedTmError::HourOutOfRange);
625 }
626 if !(0..=59).contains(&tm.tm_min) {
627 return Err(CheckedTmError::MinuteOutOfRange);
628 }
629 if !(0..=61).contains(&tm.tm_sec) {
630 return Err(CheckedTmError::SecondsOutOfRange);
631 }
632 if tm.tm_wday < 0 {
633 return Err(CheckedTmError::DayOfWeekOutOfRange);
634 }
635 if tm.tm_yday == -1 {
636 tm.tm_yday = 0;
637 } else if !(0..=365).contains(&tm.tm_yday) {
638 return Err(CheckedTmError::DayOfYearOutOfRange);
639 }
640
641 #[cfg(unix)]
642 {
643 let zone = match parts.zone {
644 Some(zone) => Some(CString::new(zone).map_err(|_| CheckedTmError::EmbeddedNul)?),
645 None => None,
646 };
647 if let Some(zone) = &zone {
648 tm.tm_zone = zone.as_ptr().cast_mut();
649 }
650 if let Some(gmtoff) = parts.gmtoff {
651 tm.tm_gmtoff = gmtoff as _;
652 }
653 Ok(CheckedTm { tm, zone })
654 }
655 #[cfg(windows)]
656 {
657 Ok(CheckedTm { tm })
658 }
659}
660
661#[cfg(any(unix, windows))]
662pub fn mktime_tm_from_parts(parts: MktimeTmParts) -> Result<libc::tm, CheckedTmError> {
663 if parts.year < i32::MIN + 1900 {
664 return Err(CheckedTmError::YearOutOfRange);
665 }
666 let mut tm: libc::tm = unsafe { core::mem::zeroed() };
667 tm.tm_sec = parts.tm_sec;
668 tm.tm_min = parts.tm_min;
669 tm.tm_hour = parts.tm_hour;
670 tm.tm_mday = parts.tm_mday;
671 tm.tm_mon = parts.tm_mon - 1;
672 tm.tm_year = parts.year - 1900;
673 tm.tm_wday = -1;
674 tm.tm_yday = parts.tm_yday - 1;
675 tm.tm_isdst = parts.tm_isdst;
676 Ok(tm)
677}
678
679#[cfg(unix)]
680pub fn strftime_ascii(fmt: &str, tm: &libc::tm) -> Result<String, CheckedTmError> {
681 let fmt_c = CString::new(fmt).map_err(|_| CheckedTmError::EmbeddedNul)?;
682 let mut size = 1024usize;
683 let max_scale = 256usize.saturating_mul(fmt.len().max(1));
684 loop {
685 let mut out = vec![0u8; size];
686 let written = unsafe {
687 libc::strftime(
688 out.as_mut_ptr().cast(),
689 out.len(),
690 fmt_c.as_ptr(),
691 tm as *const libc::tm,
692 )
693 };
694 if written > 0 || size >= max_scale {
695 return Ok(String::from_utf8_lossy(&out[..written]).into_owned());
696 }
697 size = size.saturating_mul(2);
698 }
699}
700
701#[cfg(windows)]
702unsafe extern "C" {
703 fn wcsftime(
704 s: *mut libc::wchar_t,
705 max: libc::size_t,
706 format: *const libc::wchar_t,
707 tm: *const libc::tm,
708 ) -> libc::size_t;
709}
710
711#[cfg(windows)]
712pub fn strftime_ascii(fmt: &str, tm: &libc::tm) -> Result<String, CheckedTmError> {
713 let fmt_wide = widestring::WideCString::from_str(fmt)
714 .map_err(|_| CheckedTmError::EmbeddedNul)?
715 .into_vec_with_nul();
716 let mut size = 1024usize;
717 let max_scale = 256usize.saturating_mul(fmt.len().max(1));
718 loop {
719 let mut out = vec![0u16; size];
720 let written = unsafe {
721 crate::suppress_iph!(wcsftime(
722 out.as_mut_ptr(),
723 out.len(),
724 fmt_wide.as_ptr(),
725 tm as *const libc::tm,
726 ))
727 };
728 if written > 0 || size >= max_scale {
729 return Ok(String::from_utf16_lossy(&out[..written]));
730 }
731 size = size.saturating_mul(2);
732 }
733}