1pub use decl::time;
8
9pub(crate) use decl::module_def;
10
11#[cfg(not(target_env = "msvc"))]
12#[cfg(not(target_arch = "wasm32"))]
13unsafe extern "C" {
14 #[cfg(not(target_os = "freebsd"))]
15 #[link_name = "daylight"]
16 static c_daylight: core::ffi::c_int;
17 #[link_name = "timezone"]
19 static c_timezone: core::ffi::c_long;
20 #[link_name = "tzname"]
21 static c_tzname: [*const core::ffi::c_char; 2];
22 #[link_name = "tzset"]
23 fn c_tzset();
24}
25
26#[pymodule(name = "time", with(#[cfg(any(unix, windows))] platform))]
27mod decl {
28 use crate::{
29 AsObject, Py, PyObjectRef, PyResult, VirtualMachine,
30 builtins::{PyStrRef, PyTypeRef},
31 function::{Either, FuncArgs, OptionalArg},
32 types::{PyStructSequence, struct_sequence_new},
33 };
34 #[cfg(any(unix, windows))]
35 use crate::{common::wtf8::Wtf8Buf, convert::ToPyObject};
36 #[cfg(unix)]
37 use alloc::ffi::CString;
38 #[cfg(not(any(unix, windows)))]
39 use chrono::{
40 DateTime, Datelike, TimeZone, Timelike,
41 naive::{NaiveDate, NaiveDateTime, NaiveTime},
42 };
43 use core::time::Duration;
44 #[cfg(target_env = "msvc")]
45 #[cfg(not(target_arch = "wasm32"))]
46 use windows_sys::Win32::System::Time::{GetTimeZoneInformation, TIME_ZONE_INFORMATION};
47
48 #[cfg(windows)]
49 unsafe extern "C" {
50 fn wcsftime(
51 s: *mut libc::wchar_t,
52 max: libc::size_t,
53 format: *const libc::wchar_t,
54 tm: *const libc::tm,
55 ) -> libc::size_t;
56 }
57
58 #[allow(dead_code)]
59 pub(super) const SEC_TO_MS: i64 = 1000;
60 #[allow(dead_code)]
61 pub(super) const MS_TO_US: i64 = 1000;
62 #[allow(dead_code)]
63 pub(super) const SEC_TO_US: i64 = SEC_TO_MS * MS_TO_US;
64 #[allow(dead_code)]
65 pub(super) const US_TO_NS: i64 = 1000;
66 #[allow(dead_code)]
67 pub(super) const MS_TO_NS: i64 = MS_TO_US * US_TO_NS;
68 #[allow(dead_code)]
69 pub(super) const SEC_TO_NS: i64 = SEC_TO_MS * MS_TO_NS;
70 #[allow(dead_code)]
71 pub(super) const NS_TO_MS: i64 = 1000 * 1000;
72 #[allow(dead_code)]
73 pub(super) const NS_TO_US: i64 = 1000;
74
75 fn duration_since_system_now(vm: &VirtualMachine) -> PyResult<Duration> {
76 use std::time::{SystemTime, UNIX_EPOCH};
77
78 SystemTime::now()
79 .duration_since(UNIX_EPOCH)
80 .map_err(|e| vm.new_value_error(format!("Time error: {e:?}")))
81 }
82
83 #[pyattr]
84 pub const _STRUCT_TM_ITEMS: usize = 11;
85
86 #[cfg(not(any(unix, windows)))]
88 fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
89 duration_since_system_now(vm)
90 }
91
92 #[cfg(not(any(unix, windows)))]
94 fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
95 duration_since_system_now(vm)
96 }
97
98 #[pyfunction]
99 fn sleep(seconds: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
100 let seconds_type_name = seconds.clone().class().name().to_owned();
101 let dur = seconds.try_into_value::<Duration>(vm).map_err(|e| {
102 if e.class().is(vm.ctx.exceptions.value_error)
103 && let Some(s) = e.args().first().and_then(|arg| arg.str(vm).ok())
104 && s.as_bytes() == b"negative duration"
105 {
106 return vm.new_value_error("sleep length must be non-negative");
107 }
108 if e.class().is(vm.ctx.exceptions.type_error) {
109 return vm.new_type_error(format!(
110 "'{seconds_type_name}' object cannot be interpreted as an integer or float"
111 ));
112 }
113 e
114 })?;
115
116 #[cfg(unix)]
117 {
118 use std::time::Instant;
122 let deadline = Instant::now() + dur;
123 loop {
124 let remaining = deadline.saturating_duration_since(Instant::now());
125 if remaining.is_zero() {
126 break;
127 }
128 let ts = nix::sys::time::TimeSpec::from(remaining);
129 let (res, err) = vm.allow_threads(|| {
130 let r = unsafe { libc::nanosleep(ts.as_ref(), core::ptr::null_mut()) };
131 (r, nix::Error::last_raw())
132 });
133 if res == 0 {
134 break;
135 }
136 if err != libc::EINTR {
137 return Err(
138 vm.new_os_error(format!("nanosleep: {}", nix::Error::from_raw(err)))
139 );
140 }
141 vm.check_signals()?;
143 }
144 }
145
146 #[cfg(not(unix))]
147 {
148 vm.allow_threads(|| std::thread::sleep(dur));
149 }
150
151 Ok(())
152 }
153
154 #[pyfunction]
155 fn time_ns(vm: &VirtualMachine) -> PyResult<u64> {
156 Ok(duration_since_system_now(vm)?.as_nanos() as u64)
157 }
158
159 #[pyfunction]
160 pub fn time(vm: &VirtualMachine) -> PyResult<f64> {
161 _time(vm)
162 }
163
164 #[cfg(not(all(
165 target_arch = "wasm32",
166 not(any(target_os = "emscripten", target_os = "wasi")),
167 )))]
168 fn _time(vm: &VirtualMachine) -> PyResult<f64> {
169 Ok(duration_since_system_now(vm)?.as_secs_f64())
170 }
171
172 #[cfg(all(
173 target_arch = "wasm32",
174 feature = "wasmbind",
175 not(any(target_os = "emscripten", target_os = "wasi"))
176 ))]
177 fn _time(_vm: &VirtualMachine) -> PyResult<f64> {
178 use wasm_bindgen::prelude::*;
179 #[wasm_bindgen]
180 extern "C" {
181 type Date;
182 #[wasm_bindgen(static_method_of = Date)]
183 fn now() -> f64;
184 }
185 Ok(Date::now() / 1000.0)
187 }
188
189 #[cfg(all(
190 target_arch = "wasm32",
191 not(feature = "wasmbind"),
192 not(any(target_os = "emscripten", target_os = "wasi"))
193 ))]
194 fn _time(vm: &VirtualMachine) -> PyResult<f64> {
195 Err(vm.new_not_implemented_error("time.time"))
196 }
197
198 #[pyfunction]
199 fn monotonic(vm: &VirtualMachine) -> PyResult<f64> {
200 Ok(get_monotonic_time(vm)?.as_secs_f64())
201 }
202
203 #[pyfunction]
204 fn monotonic_ns(vm: &VirtualMachine) -> PyResult<u128> {
205 Ok(get_monotonic_time(vm)?.as_nanos())
206 }
207
208 #[pyfunction]
209 fn perf_counter(vm: &VirtualMachine) -> PyResult<f64> {
210 Ok(get_perf_time(vm)?.as_secs_f64())
211 }
212
213 #[pyfunction]
214 fn perf_counter_ns(vm: &VirtualMachine) -> PyResult<u128> {
215 Ok(get_perf_time(vm)?.as_nanos())
216 }
217
218 #[cfg(target_env = "msvc")]
219 #[cfg(not(target_arch = "wasm32"))]
220 pub(super) fn get_tz_info() -> TIME_ZONE_INFORMATION {
221 let mut info: TIME_ZONE_INFORMATION = unsafe { core::mem::zeroed() };
222 unsafe { GetTimeZoneInformation(&mut info) };
223 info
224 }
225
226 #[cfg(not(target_env = "msvc"))]
232 #[cfg(not(target_arch = "wasm32"))]
233 #[pyattr]
234 fn altzone(_vm: &VirtualMachine) -> core::ffi::c_long {
235 unsafe { super::c_timezone - 3600 }
237 }
238
239 #[cfg(target_env = "msvc")]
240 #[cfg(not(target_arch = "wasm32"))]
241 #[pyattr]
242 fn altzone(_vm: &VirtualMachine) -> i32 {
243 let info = get_tz_info();
244 (info.Bias + info.StandardBias) * 60 - 3600
246 }
247
248 #[cfg(not(target_env = "msvc"))]
249 #[cfg(not(target_arch = "wasm32"))]
250 #[pyattr]
251 fn timezone(_vm: &VirtualMachine) -> core::ffi::c_long {
252 unsafe { super::c_timezone }
253 }
254
255 #[cfg(target_env = "msvc")]
256 #[cfg(not(target_arch = "wasm32"))]
257 #[pyattr]
258 fn timezone(_vm: &VirtualMachine) -> i32 {
259 let info = get_tz_info();
260 (info.Bias + info.StandardBias) * 60
262 }
263
264 #[cfg(not(target_os = "freebsd"))]
265 #[cfg(not(target_env = "msvc"))]
266 #[cfg(not(target_arch = "wasm32"))]
267 #[pyattr]
268 fn daylight(_vm: &VirtualMachine) -> core::ffi::c_int {
269 unsafe { super::c_daylight }
270 }
271
272 #[cfg(target_env = "msvc")]
273 #[cfg(not(target_arch = "wasm32"))]
274 #[pyattr]
275 fn daylight(_vm: &VirtualMachine) -> i32 {
276 let info = get_tz_info();
277 (info.StandardBias != info.DaylightBias) as i32
279 }
280
281 #[cfg(not(target_env = "msvc"))]
282 #[cfg(not(target_arch = "wasm32"))]
283 #[pyattr]
284 fn tzname(vm: &VirtualMachine) -> crate::builtins::PyTupleRef {
285 use crate::builtins::tuple::IntoPyTuple;
286
287 unsafe fn to_str(s: *const core::ffi::c_char) -> String {
288 unsafe { core::ffi::CStr::from_ptr(s) }
289 .to_string_lossy()
290 .into_owned()
291 }
292 unsafe { (to_str(super::c_tzname[0]), to_str(super::c_tzname[1])) }.into_pytuple(vm)
293 }
294
295 #[cfg(target_env = "msvc")]
296 #[cfg(not(target_arch = "wasm32"))]
297 #[pyattr]
298 fn tzname(vm: &VirtualMachine) -> crate::builtins::PyTupleRef {
299 use crate::builtins::tuple::IntoPyTuple;
300 let info = get_tz_info();
301 let standard = widestring::decode_utf16_lossy(info.StandardName)
302 .take_while(|&c| c != '\0')
303 .collect::<String>();
304 let daylight = widestring::decode_utf16_lossy(info.DaylightName)
305 .take_while(|&c| c != '\0')
306 .collect::<String>();
307 let tz_name = (&*standard, &*daylight);
308 tz_name.into_pytuple(vm)
309 }
310
311 #[cfg(not(any(unix, windows)))]
312 fn pyobj_to_date_time(
313 value: Either<f64, i64>,
314 vm: &VirtualMachine,
315 ) -> PyResult<DateTime<chrono::offset::Utc>> {
316 let secs = match value {
317 Either::A(float) => {
318 if !float.is_finite() {
319 return Err(vm.new_value_error("Invalid value for timestamp"));
320 }
321 float.floor() as i64
322 }
323 Either::B(int) => int,
324 };
325 DateTime::<chrono::offset::Utc>::from_timestamp(secs, 0)
326 .ok_or_else(|| vm.new_overflow_error("timestamp out of range for platform time_t"))
327 }
328
329 #[cfg(not(any(unix, windows)))]
330 impl OptionalArg<Option<Either<f64, i64>>> {
331 fn naive_or_local(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
333 Ok(match self {
334 Self::Present(Some(secs)) => pyobj_to_date_time(secs, vm)?
335 .with_timezone(&chrono::Local)
336 .naive_local(),
337 Self::Present(None) | Self::Missing => chrono::offset::Local::now().naive_local(),
338 })
339 }
340 }
341
342 #[cfg(any(unix, windows))]
343 struct CheckedTm {
344 tm: libc::tm,
345 #[cfg(unix)]
346 zone: Option<CString>,
347 }
348
349 #[cfg(any(unix, windows))]
350 fn checked_tm_from_struct_time(
351 t: &StructTimeData,
352 vm: &VirtualMachine,
353 func_name: &'static str,
354 ) -> PyResult<CheckedTm> {
355 let invalid_tuple =
356 || vm.new_type_error(format!("{func_name}(): illegal time tuple argument"));
357
358 let year: i64 = t.tm_year.clone().try_into_value(vm).map_err(|e| {
359 if e.class().is(vm.ctx.exceptions.overflow_error) {
360 vm.new_overflow_error("year out of range")
361 } else {
362 invalid_tuple()
363 }
364 })?;
365 if year < i64::from(i32::MIN) + 1900 || year > i64::from(i32::MAX) {
366 return Err(vm.new_overflow_error("year out of range"));
367 }
368 let year = year as i32;
369 let tm_mon = t
370 .tm_mon
371 .clone()
372 .try_into_value::<i32>(vm)
373 .map_err(|_| invalid_tuple())?
374 - 1;
375 let tm_mday = t
376 .tm_mday
377 .clone()
378 .try_into_value(vm)
379 .map_err(|_| invalid_tuple())?;
380 let tm_hour = t
381 .tm_hour
382 .clone()
383 .try_into_value(vm)
384 .map_err(|_| invalid_tuple())?;
385 let tm_min = t
386 .tm_min
387 .clone()
388 .try_into_value(vm)
389 .map_err(|_| invalid_tuple())?;
390 let tm_sec = t
391 .tm_sec
392 .clone()
393 .try_into_value(vm)
394 .map_err(|_| invalid_tuple())?;
395 let tm_wday = (t
396 .tm_wday
397 .clone()
398 .try_into_value::<i32>(vm)
399 .map_err(|_| invalid_tuple())?
400 + 1)
401 % 7;
402 let tm_yday = t
403 .tm_yday
404 .clone()
405 .try_into_value::<i32>(vm)
406 .map_err(|_| invalid_tuple())?
407 - 1;
408 let tm_isdst = t
409 .tm_isdst
410 .clone()
411 .try_into_value(vm)
412 .map_err(|_| invalid_tuple())?;
413
414 let mut tm: libc::tm = unsafe { core::mem::zeroed() };
415 tm.tm_year = year - 1900;
416 tm.tm_mon = tm_mon;
417 tm.tm_mday = tm_mday;
418 tm.tm_hour = tm_hour;
419 tm.tm_min = tm_min;
420 tm.tm_sec = tm_sec;
421 tm.tm_wday = tm_wday;
422 tm.tm_yday = tm_yday;
423 tm.tm_isdst = tm_isdst;
424
425 if tm.tm_mon == -1 {
426 tm.tm_mon = 0;
427 } else if tm.tm_mon < 0 || tm.tm_mon > 11 {
428 return Err(vm.new_value_error("month out of range"));
429 }
430 if tm.tm_mday == 0 {
431 tm.tm_mday = 1;
432 } else if tm.tm_mday < 0 || tm.tm_mday > 31 {
433 return Err(vm.new_value_error("day of month out of range"));
434 }
435 if tm.tm_hour < 0 || tm.tm_hour > 23 {
436 return Err(vm.new_value_error("hour out of range"));
437 }
438 if tm.tm_min < 0 || tm.tm_min > 59 {
439 return Err(vm.new_value_error("minute out of range"));
440 }
441 if tm.tm_sec < 0 || tm.tm_sec > 61 {
442 return Err(vm.new_value_error("seconds out of range"));
443 }
444 if tm.tm_wday < 0 {
445 return Err(vm.new_value_error("day of week out of range"));
446 }
447 if tm.tm_yday == -1 {
448 tm.tm_yday = 0;
449 } else if tm.tm_yday < 0 || tm.tm_yday > 365 {
450 return Err(vm.new_value_error("day of year out of range"));
451 }
452
453 #[cfg(unix)]
454 {
455 use crate::builtins::PyUtf8StrRef;
456 let zone = if t.tm_zone.is(&vm.ctx.none) {
457 None
458 } else {
459 let zone: PyUtf8StrRef = t
460 .tm_zone
461 .clone()
462 .try_into_value(vm)
463 .map_err(|_| invalid_tuple())?;
464 Some(
465 CString::new(zone.as_str())
466 .map_err(|_| vm.new_value_error("embedded null character"))?,
467 )
468 };
469 if let Some(zone) = &zone {
470 tm.tm_zone = zone.as_ptr().cast_mut();
471 }
472 if !t.tm_gmtoff.is(&vm.ctx.none) {
473 let gmtoff: i64 = t
474 .tm_gmtoff
475 .clone()
476 .try_into_value(vm)
477 .map_err(|_| invalid_tuple())?;
478 tm.tm_gmtoff = gmtoff as _;
479 }
480
481 Ok(CheckedTm { tm, zone })
482 }
483 #[cfg(windows)]
484 {
485 Ok(CheckedTm { tm })
486 }
487 }
488
489 #[cfg(any(unix, windows))]
490 fn asctime_from_tm(tm: &libc::tm) -> String {
491 const WDAY_NAME: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
492 const MON_NAME: [&str; 12] = [
493 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
494 ];
495 format!(
496 "{} {}{:>3} {:02}:{:02}:{:02} {}",
497 WDAY_NAME[tm.tm_wday as usize],
498 MON_NAME[tm.tm_mon as usize],
499 tm.tm_mday,
500 tm.tm_hour,
501 tm.tm_min,
502 tm.tm_sec,
503 tm.tm_year + 1900
504 )
505 }
506
507 #[cfg(not(any(unix, windows)))]
508 impl OptionalArg<StructTimeData> {
509 fn naive_or_local(self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
510 Ok(match self {
511 Self::Present(t) => t.to_date_time(vm)?,
512 Self::Missing => chrono::offset::Local::now().naive_local(),
513 })
514 }
515 }
516
517 #[pyfunction]
519 fn gmtime(
520 secs: OptionalArg<Option<Either<f64, i64>>>,
521 vm: &VirtualMachine,
522 ) -> PyResult<StructTimeData> {
523 #[cfg(any(unix, windows))]
524 {
525 let ts = match secs {
526 OptionalArg::Present(Some(value)) => pyobj_to_time_t(value, vm)?,
527 OptionalArg::Present(None) | OptionalArg::Missing => current_time_t(),
528 };
529 gmtime_from_timestamp(ts, vm)
530 }
531
532 #[cfg(not(any(unix, windows)))]
533 {
534 let instant = match secs {
535 OptionalArg::Present(Some(secs)) => pyobj_to_date_time(secs, vm)?.naive_utc(),
536 OptionalArg::Present(None) | OptionalArg::Missing => {
537 chrono::offset::Utc::now().naive_utc()
538 }
539 };
540 Ok(StructTimeData::new_utc(vm, instant))
541 }
542 }
543
544 #[pyfunction]
545 fn localtime(
546 secs: OptionalArg<Option<Either<f64, i64>>>,
547 vm: &VirtualMachine,
548 ) -> PyResult<StructTimeData> {
549 #[cfg(any(unix, windows))]
550 {
551 let ts = match secs {
552 OptionalArg::Present(Some(value)) => pyobj_to_time_t(value, vm)?,
553 OptionalArg::Present(None) | OptionalArg::Missing => current_time_t(),
554 };
555 localtime_from_timestamp(ts, vm)
556 }
557
558 #[cfg(not(any(unix, windows)))]
559 let instant = secs.naive_or_local(vm)?;
560 #[cfg(not(any(unix, windows)))]
561 {
562 Ok(StructTimeData::new_local(vm, instant, 0))
563 }
564 }
565
566 #[pyfunction]
567 fn mktime(t: StructTimeData, vm: &VirtualMachine) -> PyResult<f64> {
568 #[cfg(unix)]
569 {
570 unix_mktime(&t, vm)
571 }
572
573 #[cfg(windows)]
574 {
575 win_mktime(&t, vm)
576 }
577
578 #[cfg(not(any(unix, windows)))]
579 {
580 let datetime = t.to_date_time(vm)?;
581 let local_dt = chrono::Local
583 .from_local_datetime(&datetime)
584 .single()
585 .ok_or_else(|| vm.new_overflow_error("mktime argument out of range"))?;
586 let seconds_since_epoch = local_dt.timestamp() as f64;
587 Ok(seconds_since_epoch)
588 }
589 }
590
591 #[cfg(not(any(unix, windows)))]
592 const CFMT: &str = "%a %b %e %H:%M:%S %Y";
593
594 #[pyfunction]
595 fn asctime(t: OptionalArg<StructTimeData>, vm: &VirtualMachine) -> PyResult {
596 #[cfg(any(unix, windows))]
597 {
598 let tm = match t {
599 OptionalArg::Present(value) => {
600 checked_tm_from_struct_time(&value, vm, "asctime")?.tm
601 }
602 OptionalArg::Missing => {
603 let now = current_time_t();
604 let local = localtime_from_timestamp(now, vm)?;
605 checked_tm_from_struct_time(&local, vm, "asctime")?.tm
606 }
607 };
608 Ok(vm.ctx.new_str(asctime_from_tm(&tm)).into())
609 }
610
611 #[cfg(not(any(unix, windows)))]
612 {
613 let instant = t.naive_or_local(vm)?;
614 let formatted_time = instant.format(CFMT).to_string();
615 Ok(vm.ctx.new_str(formatted_time).into())
616 }
617 }
618
619 #[pyfunction]
620 fn ctime(secs: OptionalArg<Option<Either<f64, i64>>>, vm: &VirtualMachine) -> PyResult<String> {
621 #[cfg(any(unix, windows))]
622 {
623 let ts = match secs {
624 OptionalArg::Present(Some(value)) => pyobj_to_time_t(value, vm)?,
625 OptionalArg::Present(None) | OptionalArg::Missing => current_time_t(),
626 };
627 let local = localtime_from_timestamp(ts, vm)?;
628 let tm = checked_tm_from_struct_time(&local, vm, "asctime")?.tm;
629 Ok(asctime_from_tm(&tm))
630 }
631
632 #[cfg(not(any(unix, windows)))]
633 {
634 let instant = secs.naive_or_local(vm)?;
635 Ok(instant.format(CFMT).to_string())
636 }
637 }
638
639 #[cfg(any(unix, windows))]
640 fn strftime_crt(format: &PyStrRef, checked_tm: CheckedTm, vm: &VirtualMachine) -> PyResult {
641 #[cfg(unix)]
642 let _keep_zone_alive = &checked_tm.zone;
643 let mut tm = checked_tm.tm;
644 tm.tm_isdst = tm.tm_isdst.clamp(-1, 1);
645
646 #[cfg(windows)]
648 {
649 let year = tm.tm_year + 1900;
650 if !(1..=9999).contains(&year) {
651 return Err(vm.new_value_error("strftime() requires year in [1; 9999]"));
652 }
653 }
654
655 #[cfg(unix)]
656 fn strftime_ascii(fmt: &str, tm: &libc::tm, vm: &VirtualMachine) -> PyResult<String> {
657 let fmt_c =
658 CString::new(fmt).map_err(|_| vm.new_value_error("embedded null character"))?;
659 let mut size = 1024usize;
660 let max_scale = 256usize.saturating_mul(fmt.len().max(1));
661 loop {
662 let mut out = vec![0u8; size];
663 let written = unsafe {
664 libc::strftime(
665 out.as_mut_ptr().cast(),
666 out.len(),
667 fmt_c.as_ptr(),
668 tm as *const libc::tm,
669 )
670 };
671 if written > 0 || size >= max_scale {
672 return Ok(String::from_utf8_lossy(&out[..written]).into_owned());
673 }
674 size = size.saturating_mul(2);
675 }
676 }
677
678 #[cfg(windows)]
679 fn strftime_ascii(fmt: &str, tm: &libc::tm, vm: &VirtualMachine) -> PyResult<String> {
680 if fmt.contains('\0') {
681 return Err(vm.new_value_error("embedded null character"));
682 }
683 let fmt_wide: Vec<u16> = fmt.encode_utf16().chain(core::iter::once(0)).collect();
685 let mut size = 1024usize;
686 let max_scale = 256usize.saturating_mul(fmt.len().max(1));
687 loop {
688 let mut out = vec![0u16; size];
689 let written = unsafe {
690 rustpython_common::suppress_iph!(wcsftime(
691 out.as_mut_ptr(),
692 out.len(),
693 fmt_wide.as_ptr(),
694 tm as *const libc::tm,
695 ))
696 };
697 if written > 0 || size >= max_scale {
698 return Ok(String::from_utf16_lossy(&out[..written]));
699 }
700 size = size.saturating_mul(2);
701 }
702 }
703
704 let mut out = Wtf8Buf::new();
705 let mut ascii = String::new();
706
707 for codepoint in format.as_wtf8().code_points() {
708 if codepoint.to_u32() == 0 {
709 if !ascii.is_empty() {
710 let part = strftime_ascii(&ascii, &tm, vm)?;
711 out.extend(part.chars());
712 ascii.clear();
713 }
714 out.push(codepoint);
715 continue;
716 }
717 if let Some(ch) = codepoint.to_char()
718 && ch.is_ascii()
719 {
720 ascii.push(ch);
721 continue;
722 }
723
724 if !ascii.is_empty() {
725 let part = strftime_ascii(&ascii, &tm, vm)?;
726 out.extend(part.chars());
727 ascii.clear();
728 }
729 out.push(codepoint);
730 }
731 if !ascii.is_empty() {
732 let part = strftime_ascii(&ascii, &tm, vm)?;
733 out.extend(part.chars());
734 }
735 Ok(out.to_pyobject(vm))
736 }
737
738 #[pyfunction]
739 fn strftime(format: PyStrRef, t: OptionalArg<StructTimeData>, vm: &VirtualMachine) -> PyResult {
740 #[cfg(any(unix, windows))]
741 {
742 let checked_tm = match t {
743 OptionalArg::Present(value) => checked_tm_from_struct_time(&value, vm, "strftime")?,
744 OptionalArg::Missing => {
745 let now = current_time_t();
746 let local = localtime_from_timestamp(now, vm)?;
747 checked_tm_from_struct_time(&local, vm, "strftime")?
748 }
749 };
750 strftime_crt(&format, checked_tm, vm)
751 }
752
753 #[cfg(not(any(unix, windows)))]
754 {
755 use core::fmt::Write;
756
757 let fmt_lossy = format.to_string_lossy();
758
759 let instant = match t.naive_or_local(vm) {
763 Ok(dt) => dt,
764 Err(_) => return Ok(vm.ctx.new_str(fmt_lossy.into_owned()).into()),
765 };
766
767 let mut formatted_time = String::new();
768 write!(&mut formatted_time, "{}", instant.format(&fmt_lossy))
769 .unwrap_or_else(|_| formatted_time = format.to_string());
770 Ok(vm.ctx.new_str(formatted_time).into())
771 }
772 }
773
774 #[pyfunction]
775 fn strptime(string: PyStrRef, format: OptionalArg<PyStrRef>, vm: &VirtualMachine) -> PyResult {
776 let strptime_module = vm.import("_strptime", 0)?;
778 let strptime_func = strptime_module.get_attr("_strptime_time", vm)?;
779
780 match format.into_option() {
782 Some(fmt) => strptime_func.call((string, fmt), vm),
783 None => strptime_func.call((string,), vm),
784 }
785 }
786
787 #[cfg(not(any(
788 windows,
789 target_vendor = "apple",
790 target_os = "android",
791 target_os = "dragonfly",
792 target_os = "freebsd",
793 target_os = "linux",
794 target_os = "fuchsia",
795 target_os = "emscripten",
796 )))]
797 fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
798 Err(vm.new_not_implemented_error("thread time unsupported in this system"))
799 }
800
801 #[pyfunction]
802 fn thread_time(vm: &VirtualMachine) -> PyResult<f64> {
803 Ok(get_thread_time(vm)?.as_secs_f64())
804 }
805
806 #[pyfunction]
807 fn thread_time_ns(vm: &VirtualMachine) -> PyResult<u64> {
808 Ok(get_thread_time(vm)?.as_nanos() as u64)
809 }
810
811 #[cfg(any(windows, all(target_arch = "wasm32", target_os = "emscripten")))]
812 pub(super) fn time_muldiv(ticks: i64, mul: i64, div: i64) -> u64 {
813 let int_part = ticks / div;
814 let ticks = ticks % div;
815 let remaining = (ticks * mul) / div;
816 (int_part * mul + remaining) as u64
817 }
818
819 #[cfg(all(target_arch = "wasm32", target_os = "emscripten"))]
820 fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
821 let t: libc::tms = unsafe {
822 let mut t = core::mem::MaybeUninit::uninit();
823 if libc::times(t.as_mut_ptr()) == -1 {
824 return Err(vm.new_os_error("Failed to get clock time".to_owned()));
825 }
826 t.assume_init()
827 };
828 let freq = unsafe { libc::sysconf(libc::_SC_CLK_TCK) };
829
830 Ok(Duration::from_nanos(
831 time_muldiv(t.tms_utime, SEC_TO_NS, freq) + time_muldiv(t.tms_stime, SEC_TO_NS, freq),
832 ))
833 }
834
835 #[cfg(not(any(
836 windows,
837 target_os = "macos",
838 target_os = "android",
839 target_os = "dragonfly",
840 target_os = "freebsd",
841 target_os = "linux",
842 target_os = "illumos",
843 target_os = "netbsd",
844 target_os = "solaris",
845 target_os = "openbsd",
846 target_os = "redox",
847 all(target_arch = "wasm32", target_os = "emscripten")
848 )))]
849 fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
850 Err(vm.new_not_implemented_error("process time unsupported in this system"))
851 }
852
853 #[pyfunction]
854 fn process_time(vm: &VirtualMachine) -> PyResult<f64> {
855 Ok(get_process_time(vm)?.as_secs_f64())
856 }
857
858 #[pyfunction]
859 fn process_time_ns(vm: &VirtualMachine) -> PyResult<u64> {
860 Ok(get_process_time(vm)?.as_nanos() as u64)
861 }
862
863 #[pystruct_sequence_data(try_from_object)]
865 pub struct StructTimeData {
866 pub tm_year: PyObjectRef,
867 pub tm_mon: PyObjectRef,
868 pub tm_mday: PyObjectRef,
869 pub tm_hour: PyObjectRef,
870 pub tm_min: PyObjectRef,
871 pub tm_sec: PyObjectRef,
872 pub tm_wday: PyObjectRef,
873 pub tm_yday: PyObjectRef,
874 pub tm_isdst: PyObjectRef,
875 #[pystruct_sequence(skip)]
876 pub tm_zone: PyObjectRef,
877 #[pystruct_sequence(skip)]
878 pub tm_gmtoff: PyObjectRef,
879 }
880
881 impl core::fmt::Debug for StructTimeData {
882 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
883 write!(f, "struct_time()")
884 }
885 }
886
887 impl StructTimeData {
888 #[cfg(not(any(unix, windows)))]
889 fn new_inner(
890 vm: &VirtualMachine,
891 tm: NaiveDateTime,
892 isdst: i32,
893 gmtoff: i32,
894 zone: &str,
895 ) -> Self {
896 Self {
897 tm_year: vm.ctx.new_int(tm.year()).into(),
898 tm_mon: vm.ctx.new_int(tm.month()).into(),
899 tm_mday: vm.ctx.new_int(tm.day()).into(),
900 tm_hour: vm.ctx.new_int(tm.hour()).into(),
901 tm_min: vm.ctx.new_int(tm.minute()).into(),
902 tm_sec: vm.ctx.new_int(tm.second()).into(),
903 tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()).into(),
904 tm_yday: vm.ctx.new_int(tm.ordinal()).into(),
905 tm_isdst: vm.ctx.new_int(isdst).into(),
906 tm_zone: vm.ctx.new_str(zone).into(),
907 tm_gmtoff: vm.ctx.new_int(gmtoff).into(),
908 }
909 }
910
911 #[cfg(not(any(unix, windows)))]
913 fn new_utc(vm: &VirtualMachine, tm: NaiveDateTime) -> Self {
914 Self::new_inner(vm, tm, 0, 0, "UTC")
915 }
916
917 #[cfg(not(any(unix, windows)))]
919 fn new_local(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self {
920 let local_time = chrono::Local.from_local_datetime(&tm).unwrap();
921 let offset_seconds = local_time.offset().local_minus_utc();
922 let tz_abbr = local_time.format("%Z").to_string();
923 Self::new_inner(vm, tm, isdst, offset_seconds, &tz_abbr)
924 }
925
926 #[cfg(not(any(unix, windows)))]
927 fn to_date_time(&self, vm: &VirtualMachine) -> PyResult<NaiveDateTime> {
928 let invalid_overflow = || vm.new_overflow_error("mktime argument out of range");
929 let invalid_value = || vm.new_value_error("invalid struct_time parameter");
930
931 macro_rules! field {
932 ($field:ident) => {
933 self.$field.clone().try_into_value(vm)?
934 };
935 }
936 let dt = NaiveDateTime::new(
937 NaiveDate::from_ymd_opt(field!(tm_year), field!(tm_mon), field!(tm_mday))
938 .ok_or_else(invalid_value)?,
939 NaiveTime::from_hms_opt(field!(tm_hour), field!(tm_min), field!(tm_sec))
940 .ok_or_else(invalid_overflow)?,
941 );
942 Ok(dt)
943 }
944 }
945
946 #[pyattr]
947 #[pystruct_sequence(name = "struct_time", module = "time", data = "StructTimeData")]
948 pub struct PyStructTime;
949
950 #[pyclass(with(PyStructSequence))]
951 impl PyStructTime {
952 #[pyslot]
953 fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
954 let (seq, _dict): (PyObjectRef, OptionalArg<PyObjectRef>) = args.bind(vm)?;
955 struct_sequence_new(cls, seq, vm)
956 }
957 }
958
959 #[cfg(any(unix, windows))]
961 pub(super) fn tm_from_struct_time(
962 t: &StructTimeData,
963 vm: &VirtualMachine,
964 ) -> PyResult<libc::tm> {
965 let invalid_tuple = || vm.new_type_error("mktime(): illegal time tuple argument");
966 let year: i32 = t
967 .tm_year
968 .clone()
969 .try_into_value(vm)
970 .map_err(|_| invalid_tuple())?;
971 if year < i32::MIN + 1900 {
972 return Err(vm.new_overflow_error("year out of range"));
973 }
974
975 let mut tm: libc::tm = unsafe { core::mem::zeroed() };
976 tm.tm_sec = t
977 .tm_sec
978 .clone()
979 .try_into_value(vm)
980 .map_err(|_| invalid_tuple())?;
981 tm.tm_min = t
982 .tm_min
983 .clone()
984 .try_into_value(vm)
985 .map_err(|_| invalid_tuple())?;
986 tm.tm_hour = t
987 .tm_hour
988 .clone()
989 .try_into_value(vm)
990 .map_err(|_| invalid_tuple())?;
991 tm.tm_mday = t
992 .tm_mday
993 .clone()
994 .try_into_value(vm)
995 .map_err(|_| invalid_tuple())?;
996 tm.tm_mon = t
997 .tm_mon
998 .clone()
999 .try_into_value::<i32>(vm)
1000 .map_err(|_| invalid_tuple())?
1001 - 1;
1002 tm.tm_year = year - 1900;
1003 tm.tm_wday = -1;
1004 tm.tm_yday = t
1005 .tm_yday
1006 .clone()
1007 .try_into_value::<i32>(vm)
1008 .map_err(|_| invalid_tuple())?
1009 - 1;
1010 tm.tm_isdst = t
1011 .tm_isdst
1012 .clone()
1013 .try_into_value(vm)
1014 .map_err(|_| invalid_tuple())?;
1015 Ok(tm)
1016 }
1017
1018 #[cfg(any(unix, windows))]
1019 #[cfg_attr(target_env = "musl", allow(deprecated))]
1020 fn pyobj_to_time_t(value: Either<f64, i64>, vm: &VirtualMachine) -> PyResult<libc::time_t> {
1021 match value {
1022 Either::A(float) => {
1023 if !float.is_finite() {
1024 return Err(vm.new_value_error("Invalid value for timestamp"));
1025 }
1026 let secs = float.floor();
1027 #[cfg_attr(target_env = "musl", allow(deprecated))]
1028 if secs < libc::time_t::MIN as f64 || secs > libc::time_t::MAX as f64 {
1029 return Err(vm.new_overflow_error("timestamp out of range for platform time_t"));
1030 }
1031 #[cfg_attr(target_env = "musl", allow(deprecated))]
1032 Ok(secs as libc::time_t)
1033 }
1034 Either::B(int) => {
1035 #[allow(clippy::useless_conversion)]
1037 #[cfg_attr(target_env = "musl", allow(deprecated))]
1038 let ts: libc::time_t = int.try_into().map_err(|_| {
1039 vm.new_overflow_error("timestamp out of range for platform time_t")
1040 })?;
1041 Ok(ts)
1042 }
1043 }
1044 }
1045
1046 #[cfg(any(unix, windows))]
1047 #[allow(unused_imports)]
1048 use super::platform::*;
1049
1050 pub(crate) fn module_exec(
1051 vm: &VirtualMachine,
1052 module: &Py<crate::builtins::PyModule>,
1053 ) -> PyResult<()> {
1054 #[cfg(not(target_env = "msvc"))]
1055 #[cfg(not(target_arch = "wasm32"))]
1056 unsafe {
1057 super::c_tzset()
1058 };
1059
1060 __module_exec(vm, module);
1061 Ok(())
1062 }
1063}
1064
1065#[cfg(unix)]
1066#[pymodule(sub)]
1067mod platform {
1068 #[allow(unused_imports)]
1069 use super::decl::{SEC_TO_NS, StructTimeData, US_TO_NS};
1070 #[cfg_attr(target_os = "macos", allow(unused_imports))]
1071 use crate::{
1072 PyObject, PyRef, PyResult, TryFromBorrowedObject, VirtualMachine,
1073 builtins::{PyNamespace, PyUtf8StrRef},
1074 convert::IntoPyException,
1075 };
1076 use core::time::Duration;
1077 #[cfg_attr(target_env = "musl", allow(deprecated))]
1078 use libc::time_t;
1079 use nix::{sys::time::TimeSpec, time::ClockId};
1080
1081 #[cfg(target_os = "solaris")]
1082 #[pyattr]
1083 use libc::CLOCK_HIGHRES;
1084 #[cfg(not(any(
1085 target_os = "illumos",
1086 target_os = "netbsd",
1087 target_os = "solaris",
1088 target_os = "openbsd",
1089 target_os = "wasi",
1090 )))]
1091 #[pyattr]
1092 use libc::CLOCK_PROCESS_CPUTIME_ID;
1093 #[cfg(not(any(
1094 target_os = "illumos",
1095 target_os = "netbsd",
1096 target_os = "solaris",
1097 target_os = "openbsd",
1098 target_os = "redox",
1099 )))]
1100 #[pyattr]
1101 use libc::CLOCK_THREAD_CPUTIME_ID;
1102 #[cfg(target_os = "linux")]
1103 #[pyattr]
1104 use libc::{CLOCK_BOOTTIME, CLOCK_MONOTONIC_RAW, CLOCK_TAI};
1105 #[pyattr]
1106 use libc::{CLOCK_MONOTONIC, CLOCK_REALTIME};
1107 #[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly"))]
1108 #[pyattr]
1109 use libc::{CLOCK_PROF, CLOCK_UPTIME};
1110
1111 impl<'a> TryFromBorrowedObject<'a> for ClockId {
1112 fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult<Self> {
1113 obj.try_to_value(vm).map(Self::from_raw)
1114 }
1115 }
1116
1117 fn struct_time_from_tm(vm: &VirtualMachine, tm: libc::tm) -> StructTimeData {
1118 let zone = unsafe {
1119 if tm.tm_zone.is_null() {
1120 String::new()
1121 } else {
1122 core::ffi::CStr::from_ptr(tm.tm_zone)
1123 .to_string_lossy()
1124 .into_owned()
1125 }
1126 };
1127 StructTimeData {
1128 tm_year: vm.ctx.new_int(tm.tm_year + 1900).into(),
1129 tm_mon: vm.ctx.new_int(tm.tm_mon + 1).into(),
1130 tm_mday: vm.ctx.new_int(tm.tm_mday).into(),
1131 tm_hour: vm.ctx.new_int(tm.tm_hour).into(),
1132 tm_min: vm.ctx.new_int(tm.tm_min).into(),
1133 tm_sec: vm.ctx.new_int(tm.tm_sec).into(),
1134 tm_wday: vm.ctx.new_int((tm.tm_wday + 6) % 7).into(),
1135 tm_yday: vm.ctx.new_int(tm.tm_yday + 1).into(),
1136 tm_isdst: vm.ctx.new_int(tm.tm_isdst).into(),
1137 tm_zone: vm.ctx.new_str(zone).into(),
1138 tm_gmtoff: vm.ctx.new_int(tm.tm_gmtoff).into(),
1139 }
1140 }
1141
1142 #[cfg_attr(target_env = "musl", allow(deprecated))]
1143 pub(super) fn current_time_t() -> time_t {
1144 unsafe { libc::time(core::ptr::null_mut()) }
1145 }
1146
1147 #[cfg_attr(target_env = "musl", allow(deprecated))]
1148 pub(super) fn gmtime_from_timestamp(
1149 when: time_t,
1150 vm: &VirtualMachine,
1151 ) -> PyResult<StructTimeData> {
1152 let mut out = core::mem::MaybeUninit::<libc::tm>::uninit();
1153 let ret = unsafe { libc::gmtime_r(&when, out.as_mut_ptr()) };
1154 if ret.is_null() {
1155 return Err(vm.new_overflow_error("timestamp out of range for platform time_t"));
1156 }
1157 Ok(struct_time_from_tm(vm, unsafe { out.assume_init() }))
1158 }
1159
1160 #[cfg_attr(target_env = "musl", allow(deprecated))]
1161 pub(super) fn localtime_from_timestamp(
1162 when: time_t,
1163 vm: &VirtualMachine,
1164 ) -> PyResult<StructTimeData> {
1165 let mut out = core::mem::MaybeUninit::<libc::tm>::uninit();
1166 let ret = unsafe { libc::localtime_r(&when, out.as_mut_ptr()) };
1167 if ret.is_null() {
1168 return Err(vm.new_overflow_error("timestamp out of range for platform time_t"));
1169 }
1170 Ok(struct_time_from_tm(vm, unsafe { out.assume_init() }))
1171 }
1172
1173 pub(super) fn unix_mktime(t: &StructTimeData, vm: &VirtualMachine) -> PyResult<f64> {
1174 let mut tm = super::decl::tm_from_struct_time(t, vm)?;
1175 let timestamp = unsafe { libc::mktime(&mut tm) };
1176 if timestamp == -1 && tm.tm_wday == -1 {
1177 return Err(vm.new_overflow_error("mktime argument out of range"));
1178 }
1179 Ok(timestamp as f64)
1180 }
1181
1182 fn get_clock_time(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<Duration> {
1183 let ts = nix::time::clock_gettime(clk_id).map_err(|e| e.into_pyexception(vm))?;
1184 Ok(ts.into())
1185 }
1186
1187 #[pyfunction]
1188 fn clock_gettime(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<f64> {
1189 get_clock_time(clk_id, vm).map(|d| d.as_secs_f64())
1190 }
1191
1192 #[pyfunction]
1193 fn clock_gettime_ns(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<u128> {
1194 get_clock_time(clk_id, vm).map(|d| d.as_nanos())
1195 }
1196
1197 #[cfg(not(target_os = "redox"))]
1198 #[pyfunction]
1199 fn clock_getres(clk_id: ClockId, vm: &VirtualMachine) -> PyResult<f64> {
1200 let ts = nix::time::clock_getres(clk_id).map_err(|e| e.into_pyexception(vm))?;
1201 Ok(Duration::from(ts).as_secs_f64())
1202 }
1203
1204 #[cfg(not(target_os = "redox"))]
1205 #[cfg(not(target_vendor = "apple"))]
1206 fn set_clock_time(clk_id: ClockId, timespec: TimeSpec, vm: &VirtualMachine) -> PyResult<()> {
1207 nix::time::clock_settime(clk_id, timespec).map_err(|e| e.into_pyexception(vm))
1208 }
1209
1210 #[cfg(not(target_os = "redox"))]
1211 #[cfg(target_os = "macos")]
1212 fn set_clock_time(clk_id: ClockId, timespec: TimeSpec, vm: &VirtualMachine) -> PyResult<()> {
1213 let ret = unsafe { libc::clock_settime(clk_id.as_raw(), timespec.as_ref()) };
1215 nix::Error::result(ret)
1216 .map(drop)
1217 .map_err(|e| e.into_pyexception(vm))
1218 }
1219
1220 #[cfg(not(target_os = "redox"))]
1221 #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))]
1222 #[pyfunction]
1223 fn clock_settime(clk_id: ClockId, time: Duration, vm: &VirtualMachine) -> PyResult<()> {
1224 set_clock_time(clk_id, time.into(), vm)
1225 }
1226
1227 #[cfg(not(target_os = "redox"))]
1228 #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))]
1229 #[cfg_attr(target_env = "musl", allow(deprecated))]
1230 #[pyfunction]
1231 fn clock_settime_ns(clk_id: ClockId, time: libc::time_t, vm: &VirtualMachine) -> PyResult<()> {
1232 let ts = Duration::from_nanos(time as _).into();
1233 set_clock_time(clk_id, ts, vm)
1234 }
1235
1236 #[cfg(any(
1238 target_os = "macos",
1239 target_os = "android",
1240 target_os = "dragonfly",
1241 target_os = "freebsd",
1242 target_os = "fuchsia",
1243 target_os = "emscripten",
1244 target_os = "linux",
1245 ))]
1246 #[pyfunction]
1247 fn get_clock_info(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<PyRef<PyNamespace>> {
1248 let (adj, imp, mono, res) = match name.as_str() {
1249 "monotonic" | "perf_counter" => (
1250 false,
1251 "time.clock_gettime(CLOCK_MONOTONIC)",
1252 true,
1253 clock_getres(ClockId::CLOCK_MONOTONIC, vm)?,
1254 ),
1255 "process_time" => (
1256 false,
1257 "time.clock_gettime(CLOCK_PROCESS_CPUTIME_ID)",
1258 true,
1259 clock_getres(ClockId::CLOCK_PROCESS_CPUTIME_ID, vm)?,
1260 ),
1261 "thread_time" => (
1262 false,
1263 "time.clock_gettime(CLOCK_THREAD_CPUTIME_ID)",
1264 true,
1265 clock_getres(ClockId::CLOCK_THREAD_CPUTIME_ID, vm)?,
1266 ),
1267 "time" => (
1268 true,
1269 "time.clock_gettime(CLOCK_REALTIME)",
1270 false,
1271 clock_getres(ClockId::CLOCK_REALTIME, vm)?,
1272 ),
1273 _ => return Err(vm.new_value_error("unknown clock")),
1274 };
1275
1276 Ok(py_namespace!(vm, {
1277 "implementation" => vm.new_pyobj(imp),
1278 "monotonic" => vm.ctx.new_bool(mono),
1279 "adjustable" => vm.ctx.new_bool(adj),
1280 "resolution" => vm.ctx.new_float(res),
1281 }))
1282 }
1283
1284 #[cfg(not(any(
1285 target_os = "macos",
1286 target_os = "android",
1287 target_os = "dragonfly",
1288 target_os = "freebsd",
1289 target_os = "fuchsia",
1290 target_os = "emscripten",
1291 target_os = "linux",
1292 )))]
1293 #[pyfunction]
1294 fn get_clock_info(_name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<PyRef<PyNamespace>> {
1295 Err(vm.new_not_implemented_error("get_clock_info unsupported on this system"))
1296 }
1297
1298 pub(super) fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
1299 get_clock_time(ClockId::CLOCK_MONOTONIC, vm)
1300 }
1301
1302 pub(super) fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
1303 get_clock_time(ClockId::CLOCK_MONOTONIC, vm)
1304 }
1305
1306 #[cfg(not(any(
1307 target_os = "illumos",
1308 target_os = "netbsd",
1309 target_os = "openbsd",
1310 target_os = "redox"
1311 )))]
1312 pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
1313 get_clock_time(ClockId::CLOCK_THREAD_CPUTIME_ID, vm)
1314 }
1315
1316 #[cfg(target_os = "solaris")]
1317 pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
1318 Ok(Duration::from_nanos(unsafe { libc::gethrvtime() }))
1319 }
1320
1321 #[cfg(not(any(
1322 target_os = "illumos",
1323 target_os = "netbsd",
1324 target_os = "solaris",
1325 target_os = "openbsd",
1326 )))]
1327 pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
1328 get_clock_time(ClockId::CLOCK_PROCESS_CPUTIME_ID, vm)
1329 }
1330
1331 #[cfg(any(
1332 target_os = "illumos",
1333 target_os = "netbsd",
1334 target_os = "solaris",
1335 target_os = "openbsd",
1336 ))]
1337 pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
1338 use nix::sys::resource::{UsageWho, getrusage};
1339 fn from_timeval(tv: libc::timeval, vm: &VirtualMachine) -> PyResult<i64> {
1340 (|tv: libc::timeval| {
1341 let t = tv.tv_sec.checked_mul(SEC_TO_NS)?;
1342 let u = (tv.tv_usec as i64).checked_mul(US_TO_NS)?;
1343 t.checked_add(u)
1344 })(tv)
1345 .ok_or_else(|| vm.new_overflow_error("timestamp too large to convert to i64"))
1346 }
1347 let ru = getrusage(UsageWho::RUSAGE_SELF).map_err(|e| e.into_pyexception(vm))?;
1348 let utime = from_timeval(ru.user_time().into(), vm)?;
1349 let stime = from_timeval(ru.system_time().into(), vm)?;
1350
1351 Ok(Duration::from_nanos((utime + stime) as u64))
1352 }
1353}
1354
1355#[cfg(windows)]
1356#[pymodule(sub)]
1357mod platform {
1358 use super::decl::{MS_TO_NS, SEC_TO_NS, StructTimeData, get_tz_info, time_muldiv};
1359 use crate::{
1360 PyRef, PyResult, VirtualMachine,
1361 builtins::{PyNamespace, PyUtf8StrRef},
1362 };
1363 use core::time::Duration;
1364 use windows_sys::Win32::{
1365 Foundation::FILETIME,
1366 System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency},
1367 System::SystemInformation::{GetSystemTimeAdjustment, GetTickCount64},
1368 System::Threading::{GetCurrentProcess, GetCurrentThread, GetProcessTimes, GetThreadTimes},
1369 };
1370
1371 unsafe extern "C" {
1372 fn _gmtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int;
1373 fn _localtime64_s(tm: *mut libc::tm, time: *const libc::time_t) -> libc::c_int;
1374 #[link_name = "_mktime64"]
1375 fn c_mktime(tm: *mut libc::tm) -> libc::time_t;
1376 }
1377
1378 fn struct_time_from_tm(
1379 vm: &VirtualMachine,
1380 tm: libc::tm,
1381 zone: &str,
1382 gmtoff: i32,
1383 ) -> StructTimeData {
1384 StructTimeData {
1385 tm_year: vm.ctx.new_int(tm.tm_year + 1900).into(),
1386 tm_mon: vm.ctx.new_int(tm.tm_mon + 1).into(),
1387 tm_mday: vm.ctx.new_int(tm.tm_mday).into(),
1388 tm_hour: vm.ctx.new_int(tm.tm_hour).into(),
1389 tm_min: vm.ctx.new_int(tm.tm_min).into(),
1390 tm_sec: vm.ctx.new_int(tm.tm_sec).into(),
1391 tm_wday: vm.ctx.new_int((tm.tm_wday + 6) % 7).into(),
1392 tm_yday: vm.ctx.new_int(tm.tm_yday + 1).into(),
1393 tm_isdst: vm.ctx.new_int(tm.tm_isdst).into(),
1394 tm_zone: vm.ctx.new_str(zone).into(),
1395 tm_gmtoff: vm.ctx.new_int(gmtoff).into(),
1396 }
1397 }
1398
1399 #[cfg_attr(target_env = "musl", allow(deprecated))]
1400 pub(super) fn current_time_t() -> libc::time_t {
1401 unsafe { libc::time(core::ptr::null_mut()) }
1402 }
1403
1404 #[cfg_attr(target_env = "musl", allow(deprecated))]
1405 pub(super) fn gmtime_from_timestamp(
1406 when: libc::time_t,
1407 vm: &VirtualMachine,
1408 ) -> PyResult<StructTimeData> {
1409 let mut out = core::mem::MaybeUninit::<libc::tm>::uninit();
1410 let err = unsafe { _gmtime64_s(out.as_mut_ptr(), &when) };
1411 if err != 0 {
1412 return Err(vm.new_overflow_error("timestamp out of range for platform time_t"));
1413 }
1414 Ok(struct_time_from_tm(
1415 vm,
1416 unsafe { out.assume_init() },
1417 "UTC",
1418 0,
1419 ))
1420 }
1421
1422 #[cfg_attr(target_env = "musl", allow(deprecated))]
1423 pub(super) fn localtime_from_timestamp(
1424 when: libc::time_t,
1425 vm: &VirtualMachine,
1426 ) -> PyResult<StructTimeData> {
1427 let mut out = core::mem::MaybeUninit::<libc::tm>::uninit();
1428 let err = unsafe { _localtime64_s(out.as_mut_ptr(), &when) };
1429 if err != 0 {
1430 return Err(vm.new_overflow_error("timestamp out of range for platform time_t"));
1431 }
1432 let tm = unsafe { out.assume_init() };
1433
1434 let info = get_tz_info();
1436 let (bias, name) = if tm.tm_isdst > 0 {
1437 (info.DaylightBias, &info.DaylightName)
1438 } else {
1439 (info.StandardBias, &info.StandardName)
1440 };
1441 let zone = widestring::decode_utf16_lossy(name.iter().copied())
1442 .take_while(|&c| c != '\0')
1443 .collect::<String>();
1444 let gmtoff = -((info.Bias + bias) as i32) * 60;
1445
1446 Ok(struct_time_from_tm(vm, tm, &zone, gmtoff))
1447 }
1448
1449 pub(super) fn win_mktime(t: &StructTimeData, vm: &VirtualMachine) -> PyResult<f64> {
1450 let mut tm = super::decl::tm_from_struct_time(t, vm)?;
1451 let timestamp = unsafe { rustpython_common::suppress_iph!(c_mktime(&mut tm)) };
1452 if timestamp == -1 && tm.tm_wday == -1 {
1453 return Err(vm.new_overflow_error("mktime argument out of range"));
1454 }
1455 Ok(timestamp as f64)
1456 }
1457
1458 fn u64_from_filetime(time: FILETIME) -> u64 {
1459 let large: [u32; 2] = [time.dwLowDateTime, time.dwHighDateTime];
1460 unsafe { core::mem::transmute(large) }
1461 }
1462
1463 fn win_perf_counter_frequency(vm: &VirtualMachine) -> PyResult<i64> {
1464 let frequency = unsafe {
1465 let mut freq = core::mem::MaybeUninit::uninit();
1466 if QueryPerformanceFrequency(freq.as_mut_ptr()) == 0 {
1467 return Err(vm.new_last_os_error());
1468 }
1469 freq.assume_init()
1470 };
1471
1472 if frequency < 1 {
1473 Err(vm.new_runtime_error("invalid QueryPerformanceFrequency"))
1474 } else if frequency > i64::MAX / SEC_TO_NS {
1475 Err(vm.new_overflow_error("QueryPerformanceFrequency is too large"))
1476 } else {
1477 Ok(frequency)
1478 }
1479 }
1480
1481 fn global_frequency(vm: &VirtualMachine) -> PyResult<i64> {
1482 rustpython_common::static_cell! {
1483 static FREQUENCY: PyResult<i64>;
1484 };
1485 FREQUENCY
1486 .get_or_init(|| win_perf_counter_frequency(vm))
1487 .clone()
1488 }
1489
1490 pub(super) fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
1491 let ticks = unsafe {
1492 let mut performance_count = core::mem::MaybeUninit::uninit();
1493 QueryPerformanceCounter(performance_count.as_mut_ptr());
1494 performance_count.assume_init()
1495 };
1496
1497 Ok(Duration::from_nanos(time_muldiv(
1498 ticks,
1499 SEC_TO_NS,
1500 global_frequency(vm)?,
1501 )))
1502 }
1503
1504 fn get_system_time_adjustment(vm: &VirtualMachine) -> PyResult<u32> {
1505 let mut _time_adjustment = core::mem::MaybeUninit::uninit();
1506 let mut time_increment = core::mem::MaybeUninit::uninit();
1507 let mut _is_time_adjustment_disabled = core::mem::MaybeUninit::uninit();
1508 let time_increment = unsafe {
1509 if GetSystemTimeAdjustment(
1510 _time_adjustment.as_mut_ptr(),
1511 time_increment.as_mut_ptr(),
1512 _is_time_adjustment_disabled.as_mut_ptr(),
1513 ) == 0
1514 {
1515 return Err(vm.new_last_os_error());
1516 }
1517 time_increment.assume_init()
1518 };
1519 Ok(time_increment)
1520 }
1521
1522 pub(super) fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
1523 let ticks = unsafe { GetTickCount64() };
1524
1525 Ok(Duration::from_nanos(
1526 (ticks as i64)
1527 .checked_mul(MS_TO_NS)
1528 .ok_or_else(|| vm.new_overflow_error("timestamp too large to convert to i64"))?
1529 as u64,
1530 ))
1531 }
1532
1533 #[pyfunction]
1534 fn get_clock_info(name: PyUtf8StrRef, vm: &VirtualMachine) -> PyResult<PyRef<PyNamespace>> {
1535 let (adj, imp, mono, res) = match name.as_str() {
1536 "monotonic" => (
1537 false,
1538 "GetTickCount64()",
1539 true,
1540 get_system_time_adjustment(vm)? as f64 * 1e-7,
1541 ),
1542 "perf_counter" => (
1543 false,
1544 "QueryPerformanceCounter()",
1545 true,
1546 1.0 / (global_frequency(vm)? as f64),
1547 ),
1548 "process_time" => (false, "GetProcessTimes()", true, 1e-7),
1549 "thread_time" => (false, "GetThreadTimes()", true, 1e-7),
1550 "time" => (
1551 true,
1552 "GetSystemTimeAsFileTime()",
1553 false,
1554 get_system_time_adjustment(vm)? as f64 * 1e-7,
1555 ),
1556 _ => return Err(vm.new_value_error("unknown clock")),
1557 };
1558
1559 Ok(py_namespace!(vm, {
1560 "implementation" => vm.new_pyobj(imp),
1561 "monotonic" => vm.ctx.new_bool(mono),
1562 "adjustable" => vm.ctx.new_bool(adj),
1563 "resolution" => vm.ctx.new_float(res),
1564 }))
1565 }
1566
1567 pub(super) fn get_thread_time(vm: &VirtualMachine) -> PyResult<Duration> {
1568 let (kernel_time, user_time) = unsafe {
1569 let mut _creation_time = core::mem::MaybeUninit::uninit();
1570 let mut _exit_time = core::mem::MaybeUninit::uninit();
1571 let mut kernel_time = core::mem::MaybeUninit::uninit();
1572 let mut user_time = core::mem::MaybeUninit::uninit();
1573
1574 let thread = GetCurrentThread();
1575 if GetThreadTimes(
1576 thread,
1577 _creation_time.as_mut_ptr(),
1578 _exit_time.as_mut_ptr(),
1579 kernel_time.as_mut_ptr(),
1580 user_time.as_mut_ptr(),
1581 ) == 0
1582 {
1583 return Err(vm.new_os_error("Failed to get clock time".to_owned()));
1584 }
1585 (kernel_time.assume_init(), user_time.assume_init())
1586 };
1587 let k_time = u64_from_filetime(kernel_time);
1588 let u_time = u64_from_filetime(user_time);
1589 Ok(Duration::from_nanos((k_time + u_time) * 100))
1590 }
1591
1592 pub(super) fn get_process_time(vm: &VirtualMachine) -> PyResult<Duration> {
1593 let (kernel_time, user_time) = unsafe {
1594 let mut _creation_time = core::mem::MaybeUninit::uninit();
1595 let mut _exit_time = core::mem::MaybeUninit::uninit();
1596 let mut kernel_time = core::mem::MaybeUninit::uninit();
1597 let mut user_time = core::mem::MaybeUninit::uninit();
1598
1599 let process = GetCurrentProcess();
1600 if GetProcessTimes(
1601 process,
1602 _creation_time.as_mut_ptr(),
1603 _exit_time.as_mut_ptr(),
1604 kernel_time.as_mut_ptr(),
1605 user_time.as_mut_ptr(),
1606 ) == 0
1607 {
1608 return Err(vm.new_os_error("Failed to get clock time".to_owned()));
1609 }
1610 (kernel_time.assume_init(), user_time.assume_init())
1611 };
1612 let k_time = u64_from_filetime(kernel_time);
1613 let u_time = u64_from_filetime(user_time);
1614 Ok(Duration::from_nanos((k_time + u_time) * 100))
1615 }
1616}