rustpython_vm/stdlib/
time.rs

1//! The python `time` module.
2
3// See also:
4// https://docs.python.org/3/library/time.html
5use crate::{builtins::PyModule, PyRef, VirtualMachine};
6
7pub use decl::time;
8
9pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
10    #[cfg(not(target_env = "msvc"))]
11    #[cfg(not(target_arch = "wasm32"))]
12    unsafe {
13        c_tzset()
14    };
15    decl::make_module(vm)
16}
17
18#[cfg(not(target_env = "msvc"))]
19#[cfg(not(target_arch = "wasm32"))]
20extern "C" {
21    #[link_name = "daylight"]
22    static c_daylight: std::ffi::c_int;
23    // pub static dstbias: std::ffi::c_int;
24    #[link_name = "timezone"]
25    static c_timezone: std::ffi::c_long;
26    #[link_name = "tzname"]
27    static c_tzname: [*const std::ffi::c_char; 2];
28    #[link_name = "tzset"]
29    fn c_tzset();
30}
31
32#[pymodule(name = "time", with(platform))]
33mod decl {
34    use crate::{
35        builtins::{PyStrRef, PyTypeRef},
36        function::{Either, FuncArgs, OptionalArg},
37        types::PyStructSequence,
38        PyObjectRef, PyResult, TryFromObject, VirtualMachine,
39    };
40    use chrono::{
41        naive::{NaiveDate, NaiveDateTime, NaiveTime},
42        DateTime, Datelike, Timelike,
43    };
44    use std::time::Duration;
45
46    #[allow(dead_code)]
47    pub(super) const SEC_TO_MS: i64 = 1000;
48    #[allow(dead_code)]
49    pub(super) const MS_TO_US: i64 = 1000;
50    #[allow(dead_code)]
51    pub(super) const SEC_TO_US: i64 = SEC_TO_MS * MS_TO_US;
52    #[allow(dead_code)]
53    pub(super) const US_TO_NS: i64 = 1000;
54    #[allow(dead_code)]
55    pub(super) const MS_TO_NS: i64 = MS_TO_US * US_TO_NS;
56    #[allow(dead_code)]
57    pub(super) const SEC_TO_NS: i64 = SEC_TO_MS * MS_TO_NS;
58    #[allow(dead_code)]
59    pub(super) const NS_TO_MS: i64 = 1000 * 1000;
60    #[allow(dead_code)]
61    pub(super) const NS_TO_US: i64 = 1000;
62
63    fn duration_since_system_now(vm: &VirtualMachine) -> PyResult<Duration> {
64        use std::time::{SystemTime, UNIX_EPOCH};
65
66        SystemTime::now()
67            .duration_since(UNIX_EPOCH)
68            .map_err(|e| vm.new_value_error(format!("Time error: {e:?}")))
69    }
70
71    // TODO: implement proper monotonic time for wasm/wasi.
72    #[cfg(not(any(unix, windows)))]
73    fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
74        duration_since_system_now(vm)
75    }
76
77    // TODO: implement proper perf time for wasm/wasi.
78    #[cfg(not(any(unix, windows)))]
79    fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
80        duration_since_system_now(vm)
81    }
82
83    #[cfg(not(unix))]
84    #[pyfunction]
85    fn sleep(dur: Duration) {
86        std::thread::sleep(dur);
87    }
88
89    #[cfg(not(target_os = "wasi"))]
90    #[pyfunction]
91    fn time_ns(vm: &VirtualMachine) -> PyResult<u64> {
92        Ok(duration_since_system_now(vm)?.as_nanos() as u64)
93    }
94
95    #[pyfunction]
96    pub fn time(vm: &VirtualMachine) -> PyResult<f64> {
97        _time(vm)
98    }
99
100    #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
101    fn _time(vm: &VirtualMachine) -> PyResult<f64> {
102        Ok(duration_since_system_now(vm)?.as_secs_f64())
103    }
104
105    #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
106    fn _time(_vm: &VirtualMachine) -> PyResult<f64> {
107        use wasm_bindgen::prelude::*;
108        #[wasm_bindgen]
109        extern "C" {
110            type Date;
111            #[wasm_bindgen(static_method_of = Date)]
112            fn now() -> f64;
113        }
114        // Date.now returns unix time in milliseconds, we want it in seconds
115        Ok(Date::now() / 1000.0)
116    }
117
118    #[pyfunction]
119    fn monotonic(vm: &VirtualMachine) -> PyResult<f64> {
120        Ok(get_monotonic_time(vm)?.as_secs_f64())
121    }
122
123    #[pyfunction]
124    fn monotonic_ns(vm: &VirtualMachine) -> PyResult<u128> {
125        Ok(get_monotonic_time(vm)?.as_nanos())
126    }
127
128    #[pyfunction]
129    fn perf_counter(vm: &VirtualMachine) -> PyResult<f64> {
130        Ok(get_perf_time(vm)?.as_secs_f64())
131    }
132
133    #[pyfunction]
134    fn perf_counter_ns(vm: &VirtualMachine) -> PyResult<u128> {
135        Ok(get_perf_time(vm)?.as_nanos())
136    }
137
138    // #[pyfunction]
139    // fn tzset() {
140    //     unsafe { super::_tzset() };
141    // }
142
143    #[cfg(not(target_env = "msvc"))]
144    #[cfg(not(target_arch = "wasm32"))]
145    #[pyattr]
146    fn timezone(_vm: &VirtualMachine) -> std::ffi::c_long {
147        unsafe { super::c_timezone }
148    }
149
150    #[cfg(not(target_env = "msvc"))]
151    #[cfg(not(target_arch = "wasm32"))]
152    #[pyattr]
153    fn daylight(_vm: &VirtualMachine) -> std::ffi::c_int {
154        unsafe { super::c_daylight }
155    }
156
157    #[cfg(not(target_env = "msvc"))]
158    #[cfg(not(target_arch = "wasm32"))]
159    #[pyattr]
160    fn tzname(vm: &VirtualMachine) -> crate::builtins::PyTupleRef {
161        use crate::builtins::tuple::IntoPyTuple;
162
163        unsafe fn to_str(s: *const std::ffi::c_char) -> String {
164            std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned()
165        }
166        unsafe { (to_str(super::c_tzname[0]), to_str(super::c_tzname[1])) }.into_pytuple(vm)
167    }
168
169    fn pyobj_to_date_time(
170        value: Either<f64, i64>,
171        vm: &VirtualMachine,
172    ) -> PyResult<DateTime<chrono::offset::Utc>> {
173        let timestamp = match value {
174            Either::A(float) => {
175                let secs = float.trunc() as i64;
176                let nsecs = (float.fract() * 1e9) as u32;
177                DateTime::<chrono::offset::Utc>::from_timestamp(secs, nsecs)
178            }
179            Either::B(int) => DateTime::<chrono::offset::Utc>::from_timestamp(int, 0),
180        };
181        timestamp.ok_or_else(|| {
182            vm.new_overflow_error("timestamp out of range for platform time_t".to_owned())
183        })
184    }
185
186    impl OptionalArg<Either<f64, i64>> {
187        /// Construct a localtime from the optional seconds, or get the current local time.
188        fn naive_or_local(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
189            Ok(match self {
190                OptionalArg::Present(secs) => pyobj_to_date_time(secs, vm)?.naive_utc(),
191                OptionalArg::Missing => chrono::offset::Local::now().naive_local(),
192            })
193        }
194
195        fn naive_or_utc(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
196            Ok(match self {
197                OptionalArg::Present(secs) => pyobj_to_date_time(secs, vm)?.naive_utc(),
198                OptionalArg::Missing => chrono::offset::Utc::now().naive_utc(),
199            })
200        }
201    }
202
203    impl OptionalArg<PyStructTime> {
204        fn naive_or_local(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
205            Ok(match self {
206                OptionalArg::Present(t) => t.to_date_time(vm)?,
207                OptionalArg::Missing => chrono::offset::Local::now().naive_local(),
208            })
209        }
210    }
211
212    /// https://docs.python.org/3/library/time.html?highlight=gmtime#time.gmtime
213    #[pyfunction]
214    fn gmtime(secs: OptionalArg<Either<f64, i64>>, vm: &VirtualMachine) -> PyResult<PyStructTime> {
215        let instant = secs.naive_or_utc(vm)?;
216        Ok(PyStructTime::new(vm, instant, 0))
217    }
218
219    #[pyfunction]
220    fn localtime(
221        secs: OptionalArg<Either<f64, i64>>,
222        vm: &VirtualMachine,
223    ) -> PyResult<PyStructTime> {
224        let instant = secs.naive_or_local(vm)?;
225        // TODO: isdst flag must be valid value here
226        // https://docs.python.org/3/library/time.html#time.localtime
227        Ok(PyStructTime::new(vm, instant, -1))
228    }
229
230    #[pyfunction]
231    fn mktime(t: PyStructTime, vm: &VirtualMachine) -> PyResult<f64> {
232        let datetime = t.to_date_time(vm)?;
233        let seconds_since_epoch = datetime.and_utc().timestamp() as f64;
234        Ok(seconds_since_epoch)
235    }
236
237    const CFMT: &str = "%a %b %e %H:%M:%S %Y";
238
239    #[pyfunction]
240    fn asctime(t: OptionalArg<PyStructTime>, vm: &VirtualMachine) -> PyResult {
241        let instant = t.naive_or_local(vm)?;
242        let formatted_time = instant.format(CFMT).to_string();
243        Ok(vm.ctx.new_str(formatted_time).into())
244    }
245
246    #[pyfunction]
247    fn ctime(secs: OptionalArg<Either<f64, i64>>, vm: &VirtualMachine) -> PyResult<String> {
248        let instant = secs.naive_or_local(vm)?;
249        Ok(instant.format(CFMT).to_string())
250    }
251
252    #[pyfunction]
253    fn strftime(format: PyStrRef, t: OptionalArg<PyStructTime>, vm: &VirtualMachine) -> PyResult {
254        use std::fmt::Write;
255
256        let instant = t.naive_or_local(vm)?;
257        let mut formatted_time = String::new();
258
259        /*
260         * chrono doesn't support all formats and it
261         * raises an error if unsupported format is supplied.
262         * If error happens, we set result as input arg.
263         */
264        write!(&mut formatted_time, "{}", instant.format(format.as_str()))
265            .unwrap_or_else(|_| formatted_time = format.to_string());
266        Ok(vm.ctx.new_str(formatted_time).into())
267    }
268
269    #[pyfunction]
270    fn strptime(
271        string: PyStrRef,
272        format: OptionalArg<PyStrRef>,
273        vm: &VirtualMachine,
274    ) -> PyResult<PyStructTime> {
275        let format = format.as_ref().map_or("%a %b %H:%M:%S %Y", |s| s.as_str());
276        let instant = NaiveDateTime::parse_from_str(string.as_str(), format)
277            .map_err(|e| vm.new_value_error(format!("Parse error: {e:?}")))?;
278        Ok(PyStructTime::new(vm, instant, -1))
279    }
280
281    #[cfg(not(any(
282        windows,
283        target_vendor = "apple",
284        target_os = "android",
285        target_os = "dragonfly",
286        target_os = "freebsd",
287        target_os = "linux",
288        target_os = "fuchsia",
289        target_os = "emscripten",
290    )))]
291    fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
292        Err(vm.new_not_implemented_error("thread time unsupported in this system".to_owned()))
293    }
294
295    #[pyfunction]
296    fn thread_time(vm: &VirtualMachine) -> PyResult<f64> {
297        Ok(get_thread_time(vm)?.as_secs_f64())
298    }
299
300    #[pyfunction]
301    fn thread_time_ns(vm: &VirtualMachine) -> PyResult<u64> {
302        Ok(get_thread_time(vm)?.as_nanos() as u64)
303    }
304
305    #[cfg(any(windows, all(target_arch = "wasm32", target_os = "emscripten")))]
306    pub(super) fn time_muldiv(ticks: i64, mul: i64, div: i64) -> u64 {
307        let intpart = ticks / div;
308        let ticks = ticks % div;
309        let remaining = (ticks * mul) / div;
310        (intpart * mul + remaining) as u64
311    }
312
313    #[cfg(all(target_arch = "wasm32", target_os = "emscripten"))]
314    fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
315        let t: libc::tms = unsafe {
316            let mut t = std::mem::MaybeUninit::uninit();
317            if libc::times(t.as_mut_ptr()) == -1 {
318                return Err(vm.new_os_error("Failed to get clock time".to_owned()));
319            }
320            t.assume_init()
321        };
322        let freq = unsafe { libc::sysconf(libc::_SC_CLK_TCK) };
323
324        Ok(Duration::from_nanos(
325            time_muldiv(t.tms_utime, SEC_TO_NS, freq) + time_muldiv(t.tms_stime, SEC_TO_NS, freq),
326        ))
327    }
328
329    // same as the get_process_time impl for most unixes
330    #[cfg(all(target_arch = "wasm32", target_os = "wasi"))]
331    pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
332        let time: libc::timespec = unsafe {
333            let mut time = std::mem::MaybeUninit::uninit();
334            if libc::clock_gettime(libc::CLOCK_PROCESS_CPUTIME_ID, time.as_mut_ptr()) == -1 {
335                return Err(vm.new_os_error("Failed to get clock time".to_owned()));
336            }
337            time.assume_init()
338        };
339        Ok(Duration::new(time.tv_sec as u64, time.tv_nsec as u32))
340    }
341
342    #[cfg(not(any(
343        windows,
344        target_os = "macos",
345        target_os = "android",
346        target_os = "dragonfly",
347        target_os = "freebsd",
348        target_os = "linux",
349        target_os = "illumos",
350        target_os = "netbsd",
351        target_os = "solaris",
352        target_os = "openbsd",
353        target_os = "redox",
354        all(target_arch = "wasm32", not(target_os = "unknown"))
355    )))]
356    fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
357        Err(vm.new_not_implemented_error("process time unsupported in this system".to_owned()))
358    }
359
360    #[pyfunction]
361    fn process_time(vm: &VirtualMachine) -> PyResult<f64> {
362        Ok(get_process_time(vm)?.as_secs_f64())
363    }
364
365    #[pyfunction]
366    fn process_time_ns(vm: &VirtualMachine) -> PyResult<u64> {
367        Ok(get_process_time(vm)?.as_nanos() as u64)
368    }
369
370    #[pyattr]
371    #[pyclass(name = "struct_time")]
372    #[derive(PyStructSequence, TryIntoPyStructSequence)]
373    #[allow(dead_code)]
374    struct PyStructTime {
375        tm_year: PyObjectRef,
376        tm_mon: PyObjectRef,
377        tm_mday: PyObjectRef,
378        tm_hour: PyObjectRef,
379        tm_min: PyObjectRef,
380        tm_sec: PyObjectRef,
381        tm_wday: PyObjectRef,
382        tm_yday: PyObjectRef,
383        tm_isdst: PyObjectRef,
384    }
385
386    impl std::fmt::Debug for PyStructTime {
387        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
388            write!(f, "struct_time()")
389        }
390    }
391
392    #[pyclass(with(PyStructSequence))]
393    impl PyStructTime {
394        fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
395            PyStructTime {
396                tm_year: vm.ctx.new_int(tm.year()).into(),
397                tm_mon: vm.ctx.new_int(tm.month()).into(),
398                tm_mday: vm.ctx.new_int(tm.day()).into(),
399                tm_hour: vm.ctx.new_int(tm.hour()).into(),
400                tm_min: vm.ctx.new_int(tm.minute()).into(),
401                tm_sec: vm.ctx.new_int(tm.second()).into(),
402                tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()).into(),
403                tm_yday: vm.ctx.new_int(tm.ordinal()).into(),
404                tm_isdst: vm.ctx.new_int(isdst).into(),
405            }
406        }
407
408        fn to_date_time(&self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
409            let invalid_overflow =
410                || vm.new_overflow_error("mktime argument out of range".to_owned());
411            let invalid_value = || vm.new_value_error("invalid struct_time parameter".to_owned());
412
413            macro_rules! field {
414                ($field:ident) => {
415                    self.$field.clone().try_into_value(vm)?
416                };
417            }
418            let dt = NaiveDateTime::new(
419                NaiveDate::from_ymd_opt(field!(tm_year), field!(tm_mon), field!(tm_mday))
420                    .ok_or_else(invalid_value)?,
421                NaiveTime::from_hms_opt(field!(tm_hour), field!(tm_min), field!(tm_sec))
422                    .ok_or_else(invalid_overflow)?,
423            );
424            Ok(dt)
425        }
426
427        #[pyslot]
428        fn slot_new(_cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
429            // cls is ignorable because this is not a basetype
430            let seq = args.bind(vm)?;
431            Ok(vm.new_pyobj(Self::try_from_object(vm, seq)?))
432        }
433    }
434
435    #[allow(unused_imports)]
436    use super::platform::*;
437}
438
439#[cfg(unix)]
440#[pymodule(sub)]
441mod platform {
442    #[allow(unused_imports)]
443    use super::decl::{SEC_TO_NS, US_TO_NS};
444    #[cfg_attr(target_os = "macos", allow(unused_imports))]
445    use crate::{
446        builtins::{PyNamespace, PyStrRef},
447        convert::IntoPyException,
448        PyObject, PyRef, PyResult, TryFromBorrowedObject, VirtualMachine,
449    };
450    use nix::{sys::time::TimeSpec, time::ClockId};
451    use std::time::Duration;
452
453    #[cfg(target_os = "solaris")]
454    #[pyattr]
455    use libc::CLOCK_HIGHRES;
456    #[cfg(not(any(
457        target_os = "illumos",
458        target_os = "netbsd",
459        target_os = "solaris",
460        target_os = "openbsd",
461    )))]
462    #[pyattr]
463    use libc::CLOCK_PROCESS_CPUTIME_ID;
464    #[cfg(not(any(
465        target_os = "illumos",
466        target_os = "netbsd",
467        target_os = "solaris",
468        target_os = "openbsd",
469        target_os = "redox",
470    )))]
471    #[pyattr]
472    use libc::CLOCK_THREAD_CPUTIME_ID;
473    #[cfg(target_os = "linux")]
474    #[pyattr]
475    use libc::{CLOCK_BOOTTIME, CLOCK_MONOTONIC_RAW, CLOCK_TAI};
476    #[pyattr]
477    use libc::{CLOCK_MONOTONIC, CLOCK_REALTIME};
478    #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly"))]
479    #[pyattr]
480    use libc::{CLOCK_PROF, CLOCK_UPTIME};
481
482    impl<'a> TryFromBorrowedObject<'a> for ClockId {
483        fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
484            obj.try_to_value(vm).map(ClockId::from_raw)
485        }
486    }
487
488    fn get_clock_time(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<Duration> {
489        let ts = nix::time::clock_gettime(clk_id).map_err(|e| e.into_pyexception(vm))?;
490        Ok(ts.into())
491    }
492
493    #[pyfunction]
494    fn clock_gettime(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<f64> {
495        get_clock_time(clk_id, vm).map(|d| d.as_secs_f64())
496    }
497
498    #[pyfunction]
499    fn clock_gettime_ns(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<u128> {
500        get_clock_time(clk_id, vm).map(|d| d.as_nanos())
501    }
502
503    #[cfg(not(target_os = "redox"))]
504    #[pyfunction]
505    fn clock_getres(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<f64> {
506        let ts = nix::time::clock_getres(clk_id).map_err(|e| e.into_pyexception(vm))?;
507        Ok(Duration::from(ts).as_secs_f64())
508    }
509
510    #[cfg(not(target_os = "redox"))]
511    #[cfg(not(target_vendor = "apple"))]
512    fn set_clock_time(clk_id: ClockId, timespec: TimeSpec, vm: &VirtualMachine) -> PyResult<()> {
513        nix::time::clock_settime(clk_id, timespec).map_err(|e| e.into_pyexception(vm))
514    }
515
516    #[cfg(not(target_os = "redox"))]
517    #[cfg(target_os = "macos")]
518    fn set_clock_time(clk_id: ClockId, timespec: TimeSpec, vm: &VirtualMachine) -> PyResult<()> {
519        // idk why nix disables clock_settime on macos
520        let ret = unsafe { libc::clock_settime(clk_id.as_raw(), timespec.as_ref()) };
521        nix::Error::result(ret)
522            .map(drop)
523            .map_err(|e| e.into_pyexception(vm))
524    }
525
526    #[cfg(not(target_os = "redox"))]
527    #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))]
528    #[pyfunction]
529    fn clock_settime(clk_id: ClockId, time: Duration, vm: &VirtualMachine) -> PyResult<()> {
530        set_clock_time(clk_id, time.into(), vm)
531    }
532
533    #[cfg(not(target_os = "redox"))]
534    #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))]
535    #[pyfunction]
536    fn clock_settime_ns(clk_id: ClockId, time: libc::time_t, vm: &VirtualMachine) -> PyResult<()> {
537        let ts = Duration::from_nanos(time as _).into();
538        set_clock_time(clk_id, ts, vm)
539    }
540
541    // Requires all CLOCK constants available and clock_getres
542    #[cfg(any(
543        target_os = "macos",
544        target_os = "android",
545        target_os = "dragonfly",
546        target_os = "freebsd",
547        target_os = "fuchsia",
548        target_os = "emscripten",
549        target_os = "linux",
550    ))]
551    #[pyfunction]
552    fn get_clock_info(name: PyStrRef, vm: &VirtualMachine) -> PyResult<PyRef<PyNamespace>> {
553        let (adj, imp, mono, res) = match name.as_ref() {
554            "monotonic" | "perf_counter" => (
555                false,
556                "time.clock_gettime(CLOCK_MONOTONIC)",
557                true,
558                clock_getres(ClockId::CLOCK_MONOTONIC, vm)?,
559            ),
560            "process_time" => (
561                false,
562                "time.clock_gettime(CLOCK_PROCESS_CPUTIME_ID)",
563                true,
564                clock_getres(ClockId::CLOCK_PROCESS_CPUTIME_ID, vm)?,
565            ),
566            "thread_time" => (
567                false,
568                "time.clock_gettime(CLOCK_THREAD_CPUTIME_ID)",
569                true,
570                clock_getres(ClockId::CLOCK_THREAD_CPUTIME_ID, vm)?,
571            ),
572            "time" => (
573                true,
574                "time.clock_gettime(CLOCK_REALTIME)",
575                false,
576                clock_getres(ClockId::CLOCK_REALTIME, vm)?,
577            ),
578            _ => return Err(vm.new_value_error("unknown clock".to_owned())),
579        };
580
581        Ok(py_namespace!(vm, {
582            "implementation" => vm.new_pyobj(imp),
583            "monotonic" => vm.ctx.new_bool(mono),
584            "adjustable" => vm.ctx.new_bool(adj),
585            "resolution" => vm.ctx.new_float(res),
586        }))
587    }
588
589    #[cfg(not(any(
590        target_os = "macos",
591        target_os = "android",
592        target_os = "dragonfly",
593        target_os = "freebsd",
594        target_os = "fuchsia",
595        target_os = "emscripten",
596        target_os = "linux",
597    )))]
598    #[pyfunction]
599    fn get_clock_info(_name: PyStrRef, vm: &VirtualMachine) -> PyResult<PyRef<PyNamespace>> {
600        Err(vm.new_not_implemented_error("get_clock_info unsupported on this system".to_owned()))
601    }
602
603    pub(super) fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
604        get_clock_time(ClockId::CLOCK_MONOTONIC, vm)
605    }
606
607    pub(super) fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
608        get_clock_time(ClockId::CLOCK_MONOTONIC, vm)
609    }
610
611    #[pyfunction]
612    fn sleep(dur: Duration, vm: &VirtualMachine) -> PyResult<()> {
613        // this is basically std::thread::sleep, but that catches interrupts and we don't want to;
614
615        let ts = TimeSpec::from(dur);
616        let res = unsafe { libc::nanosleep(ts.as_ref(), std::ptr::null_mut()) };
617        let interrupted = res == -1 && nix::errno::errno() == libc::EINTR;
618
619        if interrupted {
620            vm.check_signals()?;
621        }
622
623        Ok(())
624    }
625
626    #[cfg(not(any(
627        target_os = "illumos",
628        target_os = "netbsd",
629        target_os = "openbsd",
630        target_os = "redox"
631    )))]
632    pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
633        get_clock_time(ClockId::CLOCK_THREAD_CPUTIME_ID, vm)
634    }
635
636    #[cfg(target_os = "solaris")]
637    pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
638        Ok(Duration::from_nanos(unsafe { libc::gethrvtime() }))
639    }
640
641    #[cfg(not(any(
642        target_os = "illumos",
643        target_os = "netbsd",
644        target_os = "solaris",
645        target_os = "openbsd",
646    )))]
647    pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
648        get_clock_time(ClockId::CLOCK_PROCESS_CPUTIME_ID, vm)
649    }
650
651    #[cfg(any(
652        target_os = "illumos",
653        target_os = "netbsd",
654        target_os = "solaris",
655        target_os = "openbsd",
656    ))]
657    pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
658        use nix::sys::resource::{getrusage, UsageWho};
659        fn from_timeval(tv: libc::timeval, vm: &VirtualMachine) -> PyResult<i64> {
660            (|tv: libc::timeval| {
661                let t = tv.tv_sec.checked_mul(SEC_TO_NS)?;
662                let u = (tv.tv_usec as i64).checked_mul(US_TO_NS)?;
663                t.checked_add(u)
664            })(tv)
665            .ok_or_else(|| {
666                vm.new_overflow_error("timestamp too large to convert to i64".to_owned())
667            })
668        }
669        let ru = getrusage(UsageWho::RUSAGE_SELF).map_err(|e| e.into_pyexception(vm))?;
670        let utime = from_timeval(ru.user_time().into(), vm)?;
671        let stime = from_timeval(ru.system_time().into(), vm)?;
672
673        Ok(Duration::from_nanos((utime + stime) as u64))
674    }
675}
676
677#[cfg(windows)]
678#[pymodule]
679mod platform {
680    use super::decl::{time_muldiv, MS_TO_NS, SEC_TO_NS};
681    use crate::{
682        builtins::{PyNamespace, PyStrRef},
683        stdlib::os::errno_err,
684        PyRef, PyResult, VirtualMachine,
685    };
686    use std::time::Duration;
687    use windows_sys::Win32::{
688        Foundation::FILETIME,
689        System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency},
690        System::SystemInformation::{GetSystemTimeAdjustment, GetTickCount64},
691        System::Threading::{GetCurrentProcess, GetCurrentThread, GetProcessTimes, GetThreadTimes},
692    };
693
694    fn u64_from_filetime(time: FILETIME) -> u64 {
695        let large: [u32; 2] = [time.dwLowDateTime, time.dwHighDateTime];
696        unsafe { std::mem::transmute(large) }
697    }
698
699    fn win_perf_counter_frequency(vm: &VirtualMachine) -> PyResult<i64> {
700        let frequency = unsafe {
701            let mut freq = std::mem::MaybeUninit::uninit();
702            if QueryPerformanceFrequency(freq.as_mut_ptr()) == 0 {
703                return Err(errno_err(vm));
704            }
705            freq.assume_init()
706        };
707
708        if frequency < 1 {
709            Err(vm.new_runtime_error("invalid QueryPerformanceFrequency".to_owned()))
710        } else if frequency > i64::MAX / SEC_TO_NS {
711            Err(vm.new_overflow_error("QueryPerformanceFrequency is too large".to_owned()))
712        } else {
713            Ok(frequency)
714        }
715    }
716
717    fn global_frequency(vm: &VirtualMachine) -> PyResult<i64> {
718        rustpython_common::static_cell! {
719            static FREQUENCY: PyResult<i64>;
720        };
721        FREQUENCY
722            .get_or_init(|| win_perf_counter_frequency(vm))
723            .clone()
724    }
725
726    pub(super) fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
727        let ticks = unsafe {
728            let mut performance_count = std::mem::MaybeUninit::uninit();
729            QueryPerformanceCounter(performance_count.as_mut_ptr());
730            performance_count.assume_init()
731        };
732
733        Ok(Duration::from_nanos(time_muldiv(
734            ticks,
735            SEC_TO_NS,
736            global_frequency(vm)?,
737        )))
738    }
739
740    fn get_system_time_adjustment(vm: &VirtualMachine) -> PyResult<u32> {
741        let mut _time_adjustment = std::mem::MaybeUninit::uninit();
742        let mut time_increment = std::mem::MaybeUninit::uninit();
743        let mut _is_time_adjustment_disabled = std::mem::MaybeUninit::uninit();
744        let time_increment = unsafe {
745            if GetSystemTimeAdjustment(
746                _time_adjustment.as_mut_ptr(),
747                time_increment.as_mut_ptr(),
748                _is_time_adjustment_disabled.as_mut_ptr(),
749            ) == 0
750            {
751                return Err(errno_err(vm));
752            }
753            time_increment.assume_init()
754        };
755        Ok(time_increment)
756    }
757
758    pub(super) fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
759        let ticks = unsafe { GetTickCount64() };
760
761        Ok(Duration::from_nanos(
762            (ticks as i64).checked_mul(MS_TO_NS).ok_or_else(|| {
763                vm.new_overflow_error("timestamp too large to convert to i64".to_owned())
764            })? as u64,
765        ))
766    }
767
768    #[pyfunction]
769    fn get_clock_info(name: PyStrRef, vm: &VirtualMachine) -> PyResult<PyRef<PyNamespace>> {
770        let (adj, imp, mono, res) = match name.as_ref() {
771            "monotonic" => (
772                false,
773                "GetTickCount64()",
774                true,
775                get_system_time_adjustment(vm)? as f64 * 1e-7,
776            ),
777            "perf_counter" => (
778                false,
779                "QueryPerformanceCounter()",
780                true,
781                1.0 / (global_frequency(vm)? as f64),
782            ),
783            "process_time" => (false, "GetProcessTimes()", true, 1e-7),
784            "thread_time" => (false, "GetThreadTimes()", true, 1e-7),
785            "time" => (
786                true,
787                "GetSystemTimeAsFileTime()",
788                false,
789                get_system_time_adjustment(vm)? as f64 * 1e-7,
790            ),
791            _ => return Err(vm.new_value_error("unknown clock".to_owned())),
792        };
793
794        Ok(py_namespace!(vm, {
795            "implementation" => vm.new_pyobj(imp),
796            "monotonic" => vm.ctx.new_bool(mono),
797            "adjustable" => vm.ctx.new_bool(adj),
798            "resolution" => vm.ctx.new_float(res),
799        }))
800    }
801
802    pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
803        let (kernel_time, user_time) = unsafe {
804            let mut _creation_time = std::mem::MaybeUninit::uninit();
805            let mut _exit_time = std::mem::MaybeUninit::uninit();
806            let mut kernel_time = std::mem::MaybeUninit::uninit();
807            let mut user_time = std::mem::MaybeUninit::uninit();
808
809            let thread = GetCurrentThread();
810            if GetThreadTimes(
811                thread,
812                _creation_time.as_mut_ptr(),
813                _exit_time.as_mut_ptr(),
814                kernel_time.as_mut_ptr(),
815                user_time.as_mut_ptr(),
816            ) == 0
817            {
818                return Err(vm.new_os_error("Failed to get clock time".to_owned()));
819            }
820            (kernel_time.assume_init(), user_time.assume_init())
821        };
822        let k_time = u64_from_filetime(kernel_time);
823        let u_time = u64_from_filetime(user_time);
824        Ok(Duration::from_nanos((k_time + u_time) * 100))
825    }
826
827    pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
828        let (kernel_time, user_time) = unsafe {
829            let mut _creation_time = std::mem::MaybeUninit::uninit();
830            let mut _exit_time = std::mem::MaybeUninit::uninit();
831            let mut kernel_time = std::mem::MaybeUninit::uninit();
832            let mut user_time = std::mem::MaybeUninit::uninit();
833
834            let process = GetCurrentProcess();
835            if GetProcessTimes(
836                process,
837                _creation_time.as_mut_ptr(),
838                _exit_time.as_mut_ptr(),
839                kernel_time.as_mut_ptr(),
840                user_time.as_mut_ptr(),
841            ) == 0
842            {
843                return Err(vm.new_os_error("Failed to get clock time".to_owned()));
844            }
845            (kernel_time.assume_init(), user_time.assume_init())
846        };
847        let k_time = u64_from_filetime(kernel_time);
848        let u_time = u64_from_filetime(user_time);
849        Ok(Duration::from_nanos((k_time + u_time) * 100))
850    }
851}
852
853// mostly for wasm32
854#[cfg(not(any(unix, windows)))]
855#[pymodule(sub)]
856mod platform {}