//! The python `time` module. /// See also: /// https://docs.python.org/3/library/time.html use std::fmt; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use chrono::{Datelike, Timelike}; use crate::builtins::pystr::PyStrRef; use crate::builtins::pytype::PyTypeRef; use crate::function::OptionalArg; use crate::utils::Either; use crate::vm::VirtualMachine; use crate::{PyClassImpl, PyObjectRef, PyResult, PyStructSequence, TryFromObject}; #[cfg(unix)] fn time_sleep(dur: Duration, vm: &VirtualMachine) -> PyResult<()> { // this is basically std::thread::sleep, but that catches interrupts and we don't want to; let mut ts = libc::timespec { tv_sec: std::cmp::min(libc::time_t::max_value() as u64, dur.as_secs()) as libc::time_t, tv_nsec: dur.subsec_nanos() as _, }; let res = unsafe { libc::nanosleep(&ts, &mut ts) }; let interrupted = res == -1 && nix::errno::errno() == libc::EINTR; if interrupted { vm.check_signals()?; } Ok(()) } #[cfg(not(unix))] fn time_sleep(dur: Duration) { std::thread::sleep(dur); } #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] pub fn get_time() -> f64 { match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(v) => v.as_secs_f64(), Err(err) => panic!("Time error: {:?}", err), } } #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] pub fn get_time() -> f64 { use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { type Date; #[wasm_bindgen(static_method_of = Date)] fn now() -> f64; } // Date.now returns unix time in milliseconds, we want it in seconds Date::now() / 1000.0 } fn time_time(_vm: &VirtualMachine) -> f64 { get_time() } fn time_monotonic(_vm: &VirtualMachine) -> f64 { // TODO: implement proper monotonic time! match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(v) => v.as_secs_f64(), Err(err) => panic!("Time error: {:?}", err), } } fn pyobj_to_naive_date_time( value: Either, vm: &VirtualMachine, ) -> PyResult { let timestamp = match value { Either::A(float) => { let secs = float.trunc() as i64; let nsecs = (float.fract() * 1e9) as u32; NaiveDateTime::from_timestamp_opt(secs, nsecs) } Either::B(int) => NaiveDateTime::from_timestamp_opt(int, 0), }; timestamp.ok_or_else(|| { vm.new_overflow_error("timestamp out of range for platform time_t".to_owned()) }) } /// https://docs.python.org/3/library/time.html?highlight=gmtime#time.gmtime fn time_gmtime(secs: OptionalArg>, vm: &VirtualMachine) -> PyResult { let default = chrono::offset::Utc::now().naive_utc(); let instant = match secs { OptionalArg::Present(secs) => pyobj_to_naive_date_time(secs, vm)?, OptionalArg::Missing => default, }; Ok(PyStructTime::new(vm, instant, 0)) } fn time_localtime( secs: OptionalArg>, vm: &VirtualMachine, ) -> PyResult { let instant = optional_or_localtime(secs, vm)?; // TODO: isdst flag must be valid value here // https://docs.python.org/3/library/time.html#time.localtime Ok(PyStructTime::new(vm, instant, -1)) } fn time_mktime(t: PyStructTime, vm: &VirtualMachine) -> PyResult { let datetime = t.to_date_time(vm)?; let seconds_since_epoch = datetime.timestamp() as f64; Ok(vm.ctx.new_float(seconds_since_epoch)) } /// Construct a localtime from the optional seconds, or get the current local time. fn optional_or_localtime( secs: OptionalArg>, vm: &VirtualMachine, ) -> PyResult { let default = chrono::offset::Local::now().naive_local(); Ok(match secs { OptionalArg::Present(secs) => pyobj_to_naive_date_time(secs, vm)?, OptionalArg::Missing => default, }) } const CFMT: &str = "%a %b %e %H:%M:%S %Y"; fn time_asctime(t: OptionalArg, vm: &VirtualMachine) -> PyResult { let default = chrono::offset::Local::now().naive_local(); let instant = match t { OptionalArg::Present(t) => t.to_date_time(vm)?, OptionalArg::Missing => default, }; let formatted_time = instant.format(&CFMT).to_string(); Ok(vm.ctx.new_str(formatted_time)) } fn time_ctime(secs: OptionalArg>, vm: &VirtualMachine) -> PyResult { let instant = optional_or_localtime(secs, vm)?; Ok(instant.format(&CFMT).to_string()) } fn time_strftime(format: PyStrRef, t: OptionalArg, vm: &VirtualMachine) -> PyResult { let default = chrono::offset::Local::now().naive_local(); let instant = match t { OptionalArg::Present(t) => t.to_date_time(vm)?, OptionalArg::Missing => default, }; let formatted_time = instant.format(format.as_str()).to_string(); Ok(vm.ctx.new_str(formatted_time)) } fn time_strptime( string: PyStrRef, format: OptionalArg, vm: &VirtualMachine, ) -> PyResult { let format = match format { OptionalArg::Present(ref format) => format.as_str(), OptionalArg::Missing => "%a %b %H:%M:%S %Y", }; let instant = NaiveDateTime::parse_from_str(string.as_str(), format) .map_err(|e| vm.new_value_error(format!("Parse error: {:?}", e)))?; Ok(PyStructTime::new(vm, instant, -1)) } #[pyclass(module = "time", name = "struct_time")] #[derive(PyStructSequence)] #[allow(dead_code)] struct PyStructTime { tm_year: PyObjectRef, tm_mon: PyObjectRef, tm_mday: PyObjectRef, tm_hour: PyObjectRef, tm_min: PyObjectRef, tm_sec: PyObjectRef, tm_wday: PyObjectRef, tm_yday: PyObjectRef, tm_isdst: PyObjectRef, } impl fmt::Debug for PyStructTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "struct_time()") } } #[pyimpl(with(PyStructSequence))] impl PyStructTime { fn new(vm: &VirtualMachine, tm: NaiveDateTime, isdst: i32) -> Self { PyStructTime { tm_year: vm.ctx.new_int(tm.year()), tm_mon: vm.ctx.new_int(tm.month()), tm_mday: vm.ctx.new_int(tm.day()), tm_hour: vm.ctx.new_int(tm.hour()), tm_min: vm.ctx.new_int(tm.minute()), tm_sec: vm.ctx.new_int(tm.second()), tm_wday: vm.ctx.new_int(tm.weekday().num_days_from_monday()), tm_yday: vm.ctx.new_int(tm.ordinal()), tm_isdst: vm.ctx.new_int(isdst), } } fn to_date_time(&self, vm: &VirtualMachine) -> PyResult { let invalid = || vm.new_value_error("invalid struct_time parameter".to_owned()); macro_rules! field { ($field:ident) => { TryFromObject::try_from_object(vm, self.$field.clone())? }; } let dt = NaiveDateTime::new( NaiveDate::from_ymd_opt(field!(tm_year), field!(tm_mon), field!(tm_mday)) .ok_or_else(invalid)?, NaiveTime::from_hms_opt(field!(tm_hour), field!(tm_min), field!(tm_sec)) .ok_or_else(invalid)?, ); Ok(dt) } #[pyslot] fn tp_new(_cls: PyTypeRef, seq: PyObjectRef, vm: &VirtualMachine) -> PyResult { // cls is ignorable because this is not a basetype Self::try_from_object(vm, seq) } } impl TryFromObject for PyStructTime { fn try_from_object(vm: &VirtualMachine, seq: PyObjectRef) -> PyResult { let seq = vm.extract_elements::(&seq)?; if seq.len() != 9 { return Err( vm.new_type_error("time.struct_time() takes a sequence of length 9".to_owned()) ); } let mut i = seq.into_iter(); Ok(PyStructTime { tm_year: i.next().unwrap(), tm_mon: i.next().unwrap(), tm_mday: i.next().unwrap(), tm_hour: i.next().unwrap(), tm_min: i.next().unwrap(), tm_sec: i.next().unwrap(), tm_wday: i.next().unwrap(), tm_yday: i.next().unwrap(), tm_isdst: i.next().unwrap(), }) } } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let struct_time_type = PyStructTime::make_class(ctx); py_module!(vm, "time", { "asctime" => named_function!(ctx, time, asctime), "ctime" => named_function!(ctx, time, ctime), "gmtime" => named_function!(ctx, time, gmtime), "mktime" => named_function!(ctx, time, mktime), "localtime" => named_function!(ctx, time, localtime), "monotonic" => named_function!(ctx, time, monotonic), "strftime" => named_function!(ctx, time, strftime), "strptime" => named_function!(ctx, time, strptime), "sleep" => named_function!(ctx, time, sleep), "struct_time" => struct_time_type, "time" => named_function!(ctx, time, time), "perf_counter" => named_function!(ctx, time, time), // TODO: fix }) }