From 5d5689f95bbe2332301cc33a83f3c75f0ac8b838 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Tue, 21 Aug 2018 13:43:32 -0400 Subject: [PATCH] Switch Py{Date}{Time} constructor parameters to i32 While the valid ranges for the constructor parameters is the same when expressed as either u32 or i32, since the Python API uses i32 in their public interface, we won't have to make any changes to the signatures if the Python behavior changes (e.g. supporting negative years) without their API changing. --- examples/rustapi_module/src/lib.rs | 36 +++++----- src/objects/datetime.rs | 102 ++++++++++++++--------------- tests/test_datetime.rs | 70 ++++++++++++-------- 3 files changed, 112 insertions(+), 96 deletions(-) diff --git a/examples/rustapi_module/src/lib.rs b/examples/rustapi_module/src/lib.rs index 2182ddc5..054289bb 100644 --- a/examples/rustapi_module/src/lib.rs +++ b/examples/rustapi_module/src/lib.rs @@ -14,7 +14,7 @@ use pyo3::{ObjectProtocol, ToPyObject}; use pyo3::{Py, PyResult, Python}; #[pyfunction] -fn make_date(py: Python, year: u32, month: u32, day: u32) -> PyResult> { +fn make_date(py: Python, year: i32, month: i32, day: i32) -> PyResult> { PyDate::new(py, year, month, day) } @@ -33,10 +33,10 @@ fn date_from_timestamp(py: Python, ts: i64) -> PyResult> { #[pyfunction] fn make_time( py: Python, - hour: u32, - minute: u32, - second: u32, - microsecond: u32, + hour: i32, + minute: i32, + second: i32, + microsecond: i32, tzinfo: Option<&PyTzInfo>, ) -> PyResult> { PyTime::new( @@ -53,10 +53,10 @@ fn make_time( #[pyfunction] fn time_with_fold( py: Python, - hour: u32, - minute: u32, - second: u32, - microsecond: u32, + hour: i32, + minute: i32, + second: i32, + microsecond: i32, tzinfo: Option<&PyTzInfo>, fold: bool, ) -> PyResult> { @@ -94,7 +94,7 @@ fn get_time_tuple_fold(py: Python, dt: &PyTime) -> Py { dt.get_minute(), dt.get_second(), dt.get_microsecond(), - dt.get_fold() as u32, + dt.get_fold() as i32, ], ) } @@ -119,13 +119,13 @@ fn get_delta_tuple(py: Python, delta: &PyDelta) -> Py { #[pyfunction] fn make_datetime( py: Python, - year: u32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, - microsecond: u32, + year: i32, + month: i32, + day: i32, + hour: i32, + minute: i32, + second: i32, + microsecond: i32, tzinfo: Option<&PyTzInfo>, ) -> PyResult> { PyDateTime::new( @@ -170,7 +170,7 @@ fn get_datetime_tuple_fold(py: Python, dt: &PyDateTime) -> Py { dt.get_minute(), dt.get_second(), dt.get_microsecond(), - dt.get_fold() as u32, + dt.get_fold() as i32, ], ) } diff --git a/src/objects/datetime.rs b/src/objects/datetime.rs index 17e48f28..1539b748 100644 --- a/src/objects/datetime.rs +++ b/src/objects/datetime.rs @@ -28,9 +28,9 @@ use python::{Python, ToPyPointer}; // Traits pub trait PyDateAccess { - fn get_year(&self) -> u32; - fn get_month(&self) -> u32; - fn get_day(&self) -> u32; + fn get_year(&self) -> i32; + fn get_month(&self) -> i32; + fn get_day(&self) -> i32; } pub trait PyDeltaAccess { @@ -40,10 +40,10 @@ pub trait PyDeltaAccess { } pub trait PyTimeAccess { - fn get_hour(&self) -> u32; - fn get_minute(&self) -> u32; - fn get_second(&self) -> u32; - fn get_microsecond(&self) -> u32; + fn get_hour(&self) -> i32; + fn get_minute(&self) -> i32; + fn get_second(&self) -> i32; + fn get_microsecond(&self) -> i32; #[cfg(Py_3_6)] fn get_fold(&self) -> u8; } @@ -53,7 +53,7 @@ pub struct PyDate(PyObject); pyobject_native_type!(PyDate, PyDateTime_DateType, PyDate_Check); impl PyDate { - pub fn new(py: Python, year: u32, month: u32, day: u32) -> PyResult> { + pub fn new(py: Python, year: i32, month: i32, day: i32) -> PyResult> { unsafe { let ptr = (PyDateTimeAPI.Date_FromDate)( year as c_int, @@ -74,16 +74,16 @@ impl PyDate { } impl PyDateAccess for PyDate { - fn get_year(&self) -> u32 { - unsafe { PyDateTime_GET_YEAR(self.as_ptr()) as u32 } + fn get_year(&self) -> i32 { + unsafe { PyDateTime_GET_YEAR(self.as_ptr()) as i32 } } - fn get_month(&self) -> u32 { - unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u32 } + fn get_month(&self) -> i32 { + unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as i32 } } - fn get_day(&self) -> u32 { - unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u32 } + fn get_day(&self) -> i32 { + unsafe { PyDateTime_GET_DAY(self.as_ptr()) as i32 } } } @@ -94,13 +94,13 @@ pyobject_native_type!(PyDateTime, PyDateTime_DateTimeType, PyDateTime_Check); impl PyDateTime { pub fn new( py: Python, - year: u32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, - microsecond: u32, + year: i32, + month: i32, + day: i32, + hour: i32, + minute: i32, + second: i32, + microsecond: i32, tzinfo: Option<&PyObject>, ) -> PyResult> { unsafe { @@ -139,34 +139,34 @@ impl PyDateTime { } impl PyDateAccess for PyDateTime { - fn get_year(&self) -> u32 { - unsafe { PyDateTime_GET_YEAR(self.as_ptr()) as u32 } + fn get_year(&self) -> i32 { + unsafe { PyDateTime_GET_YEAR(self.as_ptr()) as i32 } } - fn get_month(&self) -> u32 { - unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u32 } + fn get_month(&self) -> i32 { + unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as i32 } } - fn get_day(&self) -> u32 { - unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u32 } + fn get_day(&self) -> i32 { + unsafe { PyDateTime_GET_DAY(self.as_ptr()) as i32 } } } impl PyTimeAccess for PyDateTime { - fn get_hour(&self) -> u32 { - unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u32 } + fn get_hour(&self) -> i32 { + unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as i32 } } - fn get_minute(&self) -> u32 { - unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u32 } + fn get_minute(&self) -> i32 { + unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as i32 } } - fn get_second(&self) -> u32 { - unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u32 } + fn get_second(&self) -> i32 { + unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as i32 } } - fn get_microsecond(&self) -> u32 { - unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 } + fn get_microsecond(&self) -> i32 { + unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as i32 } } #[cfg(Py_3_6)] @@ -182,10 +182,10 @@ pyobject_native_type!(PyTime, PyDateTime_TimeType, PyTime_Check); impl PyTime { pub fn new( py: Python, - hour: u32, - minute: u32, - second: u32, - microsecond: u32, + hour: i32, + minute: i32, + second: i32, + microsecond: i32, tzinfo: Option<&PyObject>, ) -> PyResult> { unsafe { @@ -207,10 +207,10 @@ impl PyTime { #[cfg(Py_3_6)] pub fn new_with_fold( py: Python, - hour: u32, - minute: u32, - second: u32, - microsecond: u32, + hour: i32, + minute: i32, + second: i32, + microsecond: i32, tzinfo: Option<&PyObject>, fold: bool, ) -> PyResult> { @@ -233,20 +233,20 @@ impl PyTime { } impl PyTimeAccess for PyTime { - fn get_hour(&self) -> u32 { - unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u32 } + fn get_hour(&self) -> i32 { + unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as i32 } } - fn get_minute(&self) -> u32 { - unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u32 } + fn get_minute(&self) -> i32 { + unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as i32 } } - fn get_second(&self) -> u32 { - unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u32 } + fn get_second(&self) -> i32 { + unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as i32 } } - fn get_microsecond(&self) -> u32 { - unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 } + fn get_microsecond(&self) -> i32 { + unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as i32 } } #[cfg(Py_3_6)] diff --git a/tests/test_datetime.rs b/tests/test_datetime.rs index 5f3f0071..dff2ba2b 100644 --- a/tests/test_datetime.rs +++ b/tests/test_datetime.rs @@ -2,8 +2,9 @@ extern crate pyo3; -use pyo3::prelude::*; +use std::iter; +use pyo3::prelude::*; use pyo3::ffi::*; fn _get_subclasses<'p>( @@ -123,19 +124,41 @@ fn test_datetime_utc() { assert_eq!(offset, 0f32); } +static INVALID_DATES : &'static [(i32, i32, i32)] = &[ + (-1, 1, 1), + (0, 1, 1), + (10000, 1, 1), + (2<<30, 1, 1), + (2018, 2<<30, 1), + (2018, 0, 1), + (2018, -1, 1), + (2018, 13, 1), + (2018, 1, 0), + (2017, 2, 29), + (2018, 1, -1), + (2018, 1, 32), +]; + +static INVALID_TIMES: &'static [(i32, i32, i32, i32)] = &[ + (-1, 0, 0, 0), + (25, 0, 0, 0), + (2<<30, 0, 0, 0), + (0, -1, 0, 0), + (0, 60, 0, 0), + (0, 2<<30, 0, 0), + (0, 0, -1, 0), + (0, 0, 61, 0), + (0, 0, 2<<30, 0), +]; + + #[cfg(Py_3_6)] #[test] fn test_pydate_out_of_bounds() { // This test is an XFAIL on Python < 3.6 until bounds checking is implemented let gil = Python::acquire_gil(); let py = gil.python(); - let vals = [ - (2147484672u32, 1, 1), - (2018, 2147484672u32, 1), - (2018, 1, 2147484672u32), - ]; - - for val in vals.into_iter() { + for val in INVALID_DATES.into_iter() { let (year, month, day) = val; let dt = PyDate::new(py, *year, *month, *day); let msg = format!("Should have raised an error: {:#?}", val); @@ -152,14 +175,7 @@ fn test_pytime_out_of_bounds() { // This test is an XFAIL on Python < 3.6 until bounds checking is implemented let gil = Python::acquire_gil(); let py = gil.python(); - let vals = [ - (2147484672u32, 0, 0, 0), - (0, 2147484672u32, 0, 0), - (0, 0, 2147484672u32, 0), - (0, 0, 0, 2147484672u32), - ]; - - for val in vals.into_iter() { + for val in INVALID_TIMES.into_iter() { let (hour, minute, second, microsecond) = val; let dt = PyTime::new(py, *hour, *minute, *second, *microsecond, None); let msg = format!("Should have raised an error: {:#?}", val); @@ -176,18 +192,18 @@ fn test_pydatetime_out_of_bounds() { // This test is an XFAIL on Python < 3.6 until bounds checking is implemented let gil = Python::acquire_gil(); let py = gil.python(); - let vals = [ - (2147484672u32, 1, 1, 0, 0, 0, 0), - (2018, 2147484672u32, 1, 0, 0, 0, 0), - (2018, 1, 2147484672u32, 0, 0, 0, 0), - (2018, 1, 1, 2147484672u32, 0, 0, 0), - (2018, 1, 1, 0, 2147484672u32, 0, 0), - (2018, 1, 1, 0, 0, 2147484672u32, 0), - (2018, 1, 1, 0, 0, 0, 2147484672u32), - ]; + let valid_time = (0, 0, 0, 0); + let valid_date = (2018, 1, 1); - for val in vals.into_iter() { - let (year, month, day, hour, minute, second, microsecond) = val; + let invalid_dates = INVALID_DATES.into_iter().zip(iter::repeat(&valid_time)); + let invalid_times = iter::repeat(&valid_date).zip(INVALID_TIMES.into_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( py, *year,