From f45362943de0d1b40490af71c8742e261858d9ee Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 8 Apr 2018 10:28:20 -0400 Subject: [PATCH] Add datetime.time --- src/ffi3/datetime.rs | 5 ++ src/objects/datetime.rs | 23 +++++++++ src/objects/mod.rs | 2 +- tests/rustapi_module/src/lib.rs | 13 ++++- tests/rustapi_module/tests/test_datetime.py | 56 ++++++++++++++++++++- 5 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/ffi3/datetime.rs b/src/ffi3/datetime.rs index 6d85c8c1..2d4e2150 100644 --- a/src/ffi3/datetime.rs +++ b/src/ffi3/datetime.rs @@ -130,3 +130,8 @@ pub unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int { pub unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int { PyObject_TypeCheck(op, PyDateTimeAPI.TZInfoType) as c_int } + +#[inline(always)] +pub unsafe fn PyTime_Check(op: *mut PyObject) -> c_int { + PyObject_TypeCheck(op, PyDateTimeAPI.TimeType) as c_int +} diff --git a/src/objects/datetime.rs b/src/objects/datetime.rs index 724c796c..c740914b 100644 --- a/src/objects/datetime.rs +++ b/src/objects/datetime.rs @@ -54,6 +54,29 @@ impl PyDateTime { } } +// datetime.time +pub struct PyTime(PyObject); +pyobject_convert!(PyTime); +pyobject_nativetype!(PyTime, PyDateTime_TimeType, PyTime_Check); + +impl PyTime { + pub fn new(py: Python, hour: u32, minute: u32, second: u32, + microsecond: u32, tzinfo: &PyObject) -> PyResult> { + let h = hour as c_int; + let m = minute as c_int; + let s = second as c_int; + let u = microsecond as c_int; + let tzi = tzinfo.as_ptr(); + + unsafe { + let ptr = PyDateTimeAPI.Time_FromTime.unwrap()( + h, m, s, u, tzi, PyDateTimeAPI.TimeType + ); + Py::from_owned_ptr_or_err(py, ptr) + } + } +} + // datetime.tzinfo bindings pub struct PyTzInfo(PyObject); pyobject_convert!(PyTzInfo); diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 36c6ff1d..8520880f 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -5,7 +5,7 @@ mod exc_impl; pub use self::boolobject::PyBool; pub use self::bytearray::PyByteArray; -pub use self::datetime::{PyDate,PyDateTime,PyTzInfo}; +pub use self::datetime::{PyDate,PyTime,PyDateTime,PyTzInfo}; pub use self::dict::PyDict; pub use self::floatob::PyFloat; pub use self::iterator::PyIterator; diff --git a/tests/rustapi_module/src/lib.rs b/tests/rustapi_module/src/lib.rs index 862ab598..2a8994cf 100644 --- a/tests/rustapi_module/src/lib.rs +++ b/tests/rustapi_module/src/lib.rs @@ -4,7 +4,7 @@ extern crate pyo3; use pyo3::{py, Py, Python, PyModule, PyResult}; use pyo3::{ToPyObject}; use pyo3::prelude::{PyObject}; -use pyo3::prelude::{PyDate, PyDateTime, PyTzInfo}; +use pyo3::prelude::{PyDate, PyTime, PyDateTime, PyTzInfo}; #[py::modinit(datetime)] fn init_mod(py: Python, m: &PyModule) -> PyResult<()> { @@ -13,6 +13,17 @@ fn init_mod(py: Python, m: &PyModule) -> PyResult<()> { PyDate::new(py, year, month, day) } + #[pyfn(m, "make_time")] + fn make_time(py: Python, hour: u32, minute: u32, second: u32, + microsecond: u32, tzinfo: Option<&PyTzInfo>) -> PyResult> { + let tzi: PyObject = match tzinfo { + Some(t) => t.to_object(py), + None => py.None(), + }; + + PyTime::new(py, hour, minute, second, microsecond, &tzi) + } + #[pyfn(m, "make_datetime")] fn make_datetime(py: Python, year: u32, month: u32, day: u32, hour: u32, minute: u32, second: u32, microsecond: u32, diff --git a/tests/rustapi_module/tests/test_datetime.py b/tests/rustapi_module/tests/test_datetime.py index cc01331c..5b922679 100644 --- a/tests/rustapi_module/tests/test_datetime.py +++ b/tests/rustapi_module/tests/test_datetime.py @@ -4,6 +4,8 @@ import datetime as pdt import pytest +UTC = pdt.timezone.utc + def test_date(): assert rdt.make_date(2017, 9, 1) == pdt.date(2017, 9, 1) @@ -14,15 +16,67 @@ def test_invalid_date_fails(): rdt.make_date(2017, 2, 30) +@pytest.mark.parametrize('args, kwargs', [ + ((0, 0, 0, 0, None), {}), + ((1, 12, 14, 124731), {}), + ((1, 12, 14, 124731), {'tzinfo': UTC}), +]) +def test_time(args, kwargs): + act = rdt.make_time(*args, **kwargs) + exp = pdt.time(*args, **kwargs) + + assert act == exp + assert act.tzinfo is exp.tzinfo + + +@pytest.mark.xfail +@pytest.mark.parametrize('args', [ + (-1, 0, 0, 0), + (0, -1, 0, 0), + (0, 0, -1, 0), + (0, 0, 0, -1), +]) +def test_invalid_time_fails_xfail(args): + with pytest.raises(ValueError): + rdt.make_time(*args) + + +@pytest.mark.parametrize('args', [ + (24, 0, 0, 0), + (25, 0, 0, 0), + (0, 60, 0, 0), + (0, 61, 0, 0), + (0, 0, 60, 0), + (0, 0, 61, 0), + (0, 0, 0, 1000000) +]) +def test_invalid_time_fails(args): + with pytest.raises(ValueError): + rdt.make_time(*args) + + +@pytest.mark.parametrize('args', [ + ('0', 0, 0, 0), + (0, '0', 0, 0), + (0, 0, '0', 0), + (0, 0, 0, '0'), + (0, 0, 0, 0, 'UTC') +]) +def test_time_typeerror(args): + with pytest.raises(TypeError): + rdt.make_time(*args) + + @pytest.mark.parametrize('args, kwargs', [ ((2017, 9, 1, 12, 45, 30, 0), {}), - ((2017, 9, 1, 12, 45, 30, 0), {'tzinfo': pdt.timezone.utc}), + ((2017, 9, 1, 12, 45, 30, 0), {'tzinfo': UTC}), ]) def test_datetime(args, kwargs): act = rdt.make_datetime(*args, **kwargs) exp = pdt.datetime(*args, **kwargs) assert act == exp + assert act.tzinfo is exp.tzinfo def test_invalid_datetime_fails():