Skip to main content

rustpython_host_env/
time.rs

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/// Access to the C runtime's `tzset` / `timezone` / `altzone` / `daylight` / `tzname`
19/// globals used by Python's `time` module.
20///
21/// Not available under MSVC (which exposes these only via the
22/// `_get_tzname`-style helpers) or on `wasm32` (no libc tz state).
23#[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        // Set by `build.rs` when `time.h` exposes `altzone` (CPython `HAVE_ALTZONE`).
32        #[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    /// DST offset west of UTC in seconds, matching CPython's `time.altzone`.
51    ///
52    /// Uses the C `altzone` global when available; otherwise falls back to
53    /// `timezone - 3600` (same as CPython without `HAVE_ALTZONE`).
54    #[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    /// Snapshot of `tzname[0]` / `tzname[1]` as owned `String`s.
73    /// Reads the C globals once and copies the bytes out so callers don't
74    /// have to handle the raw pointers themselves.
75    #[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// WASI libc represents clockid_t as an opaque pointer type without Eq or PartialEq.
233#[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}