#![cfg(not(Py_LIMITED_API))] use pyo3::prelude::*; use pyo3::types::{timezone_utc_bound, IntoPyDict, PyDate, PyDateTime, PyTime}; use pyo3_ffi::PyDateTime_IMPORT; fn _get_subclasses<'py>( py: Python<'py>, py_type: &str, args: &str, ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>)> { // Import the class from Python and create some subclasses let datetime = py.import_bound("datetime")?; let locals = [(py_type, datetime.getattr(py_type)?)].into_py_dict_bound(py); let make_subclass_py = format!("class Subklass({}):\n pass", py_type); let make_sub_subclass_py = "class SubSubklass(Subklass):\n pass"; py.run_bound(&make_subclass_py, None, Some(&locals))?; py.run_bound(make_sub_subclass_py, None, Some(&locals))?; // Construct an instance of the base class let obj = py.eval_bound(&format!("{}({})", py_type, args), None, Some(&locals))?; // Construct an instance of the subclass let sub_obj = py.eval_bound(&format!("Subklass({})", args), None, Some(&locals))?; // Construct an instance of the sub-subclass let sub_sub_obj = py.eval_bound(&format!("SubSubklass({})", args), None, Some(&locals))?; Ok((obj, sub_obj, sub_sub_obj)) } macro_rules! assert_check_exact { ($check_func:ident, $check_func_exact:ident, $obj: expr) => { unsafe { use pyo3::ffi::*; assert!($check_func(($obj).as_ptr()) != 0); assert!($check_func_exact(($obj).as_ptr()) != 0); } }; } macro_rules! assert_check_only { ($check_func:ident, $check_func_exact:ident, $obj: expr) => { unsafe { use pyo3::ffi::*; assert!($check_func(($obj).as_ptr()) != 0); assert!($check_func_exact(($obj).as_ptr()) == 0); } }; } #[test] fn test_date_check() { Python::with_gil(|py| { let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "date", "2018, 1, 1").unwrap(); unsafe { PyDateTime_IMPORT() } assert_check_exact!(PyDate_Check, PyDate_CheckExact, obj); assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_obj); assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_sub_obj); assert!(obj.is_instance_of::()); assert!(!obj.is_instance_of::()); assert!(!obj.is_instance_of::()); }); } #[test] fn test_time_check() { Python::with_gil(|py| { let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "time", "12, 30, 15").unwrap(); unsafe { PyDateTime_IMPORT() } assert_check_exact!(PyTime_Check, PyTime_CheckExact, obj); assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_obj); assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_sub_obj); assert!(!obj.is_instance_of::()); assert!(obj.is_instance_of::()); assert!(!obj.is_instance_of::()); }); } #[test] fn test_datetime_check() { Python::with_gil(|py| { let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "datetime", "2018, 1, 1, 13, 30, 15") .map_err(|e| e.display(py)) .unwrap(); unsafe { PyDateTime_IMPORT() } assert_check_only!(PyDate_Check, PyDate_CheckExact, obj); assert_check_exact!(PyDateTime_Check, PyDateTime_CheckExact, obj); assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_obj); assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_sub_obj); assert!(obj.is_instance_of::()); assert!(!obj.is_instance_of::()); assert!(obj.is_instance_of::()); }); } #[test] fn test_delta_check() { Python::with_gil(|py| { let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "timedelta", "1, -3").unwrap(); unsafe { PyDateTime_IMPORT() } assert_check_exact!(PyDelta_Check, PyDelta_CheckExact, obj); assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_obj); assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_sub_obj); }); } #[test] fn test_datetime_utc() { use assert_approx_eq::assert_approx_eq; use pyo3::types::PyDateTime; Python::with_gil(|py| { let utc = timezone_utc_bound(py); let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap(); let locals = [("dt", dt)].into_py_dict_bound(py); let offset: f32 = py .eval_bound("dt.utcoffset().total_seconds()", None, Some(&locals)) .unwrap() .extract() .unwrap(); assert_approx_eq!(offset, 0f32); }); } static INVALID_DATES: &[(i32, u8, u8)] = &[ (-1, 1, 1), (0, 1, 1), (10000, 1, 1), (2 << 30, 1, 1), (2018, 0, 1), (2018, 13, 1), (2018, 1, 0), (2017, 2, 29), (2018, 1, 32), ]; static INVALID_TIMES: &[(u8, u8, u8, u32)] = &[(25, 0, 0, 0), (255, 0, 0, 0), (0, 60, 0, 0), (0, 0, 61, 0)]; #[test] fn test_pydate_out_of_bounds() { use pyo3::types::PyDate; Python::with_gil(|py| { for val in INVALID_DATES { let (year, month, day) = val; let dt = PyDate::new_bound(py, *year, *month, *day); dt.unwrap_err(); } }); } #[test] fn test_pytime_out_of_bounds() { use pyo3::types::PyTime; Python::with_gil(|py| { for val in INVALID_TIMES { let (hour, minute, second, microsecond) = val; let dt = PyTime::new_bound(py, *hour, *minute, *second, *microsecond, None); dt.unwrap_err(); } }); } #[test] fn test_pydatetime_out_of_bounds() { use pyo3::types::PyDateTime; use std::iter; Python::with_gil(|py| { let valid_time = (0, 0, 0, 0); let valid_date = (2018, 1, 1); let invalid_dates = INVALID_DATES.iter().zip(iter::repeat(&valid_time)); let invalid_times = iter::repeat(&valid_date).zip(INVALID_TIMES.iter()); let vals = invalid_dates.chain(invalid_times); for val in vals { let (date, time) = val; let (year, month, day) = date; let (hour, minute, second, microsecond) = time; let dt = PyDateTime::new_bound( py, *year, *month, *day, *hour, *minute, *second, *microsecond, None, ); dt.unwrap_err(); } }); }