Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

datetime: support timezone bindings #1588

Merged
merged 1 commit into from Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add `timezone_utc()`. [#1588](https://github.com/PyO3/pyo3/pull/1588)
- Implement `ToPyObject` for `[T; N]`. [#2313](https://github.com/PyO3/pyo3/pull/2313)
- Added the internal `IntoPyResult` trait to give better error messages when function return types do not implement `IntoPy`. [#2326](https://github.com/PyO3/pyo3/pull/2326)
- Add `PyDictKeys`, `PyDictValues` and `PyDictItems` Rust types to represent `dict_keys`, `dict_values` and `dict_items` types. [#2358](https://github.com/PyO3/pyo3/pull/2358)
Expand All @@ -26,10 +27,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `CompareOp::matches` to easily implement `__richcmp__` as the result of a
Rust `std::cmp::Ordering` comparison. [#2460](https://github.com/PyO3/pyo3/pull/2460)
- Supprt `#[pyo3(name)]` on enum variants [#2457](https://github.com/PyO3/pyo3/pull/2457)
- Add `PySuper` object [#2049](https://github.com/PyO3/pyo3/issues/2049)
- Add `PySuper` object [#2049](https://github.com/PyO3/pyo3/issues/2049)

### Changed

- Change datetime constructors taking a `tzinfo` to take `Option<&PyTzInfo>` instead of `Option<&PyObject>`: `PyDateTime::new()`, `PyDateTime::new_with_fold()`, `PyTime::new()`, and `PyTime::new_with_fold()`. [#1588](https://github.com/PyO3/pyo3/pull/1588)
- Several methods of `Py` and `PyAny` now accept `impl IntoPy<Py<PyString>>` rather than just `&str` to allow use of the `intern!` macro. [#2312](https://github.com/PyO3/pyo3/pull/2312)
- Move `PyTypeObject::type_object` method to `PyTypeInfo` trait, and deprecate `PyTypeObject` trait. [#2287](https://github.com/PyO3/pyo3/pull/2287)
- The deprecated `pyproto` feature is now disabled by default. [#2322](https://github.com/PyO3/pyo3/pull/2322)
Expand Down
13 changes: 11 additions & 2 deletions pyo3-ffi/src/datetime.rs
Expand Up @@ -574,8 +574,17 @@ pub unsafe fn PyTZInfo_CheckExact(op: *mut PyObject) -> c_int {
// skipped non-limited PyTime_FromTime
// skipped non-limited PyTime_FromTimeAndFold
// skipped non-limited PyDelta_FromDSU
// skipped non-limited PyTimeZone_FromOffset
// skipped non-limited PyTimeZone_FromOffsetAndName

pub unsafe fn PyTimeZone_FromOffset(offset: *mut PyObject) -> *mut PyObject {
((*PyDateTimeAPI()).TimeZone_FromTimeZone)(offset, std::ptr::null_mut())
}

pub unsafe fn PyTimeZone_FromOffsetAndName(
offset: *mut PyObject,
name: *mut PyObject,
) -> *mut PyObject {
((*PyDateTimeAPI()).TimeZone_FromTimeZone)(offset, name)
}

#[cfg(not(PyPy))]
pub unsafe fn PyDateTime_FromTimestamp(args: *mut PyObject) -> *mut PyObject {
Expand Down
21 changes: 3 additions & 18 deletions pytests/src/datetime.rs
Expand Up @@ -33,14 +33,7 @@ fn make_time<'p>(
microsecond: u32,
tzinfo: Option<&PyTzInfo>,
) -> PyResult<&'p PyTime> {
PyTime::new(
py,
hour,
minute,
second,
microsecond,
tzinfo.map(|o| o.to_object(py)).as_ref(),
)
PyTime::new(py, hour, minute, second, microsecond, tzinfo)
}

#[pyfunction]
Expand All @@ -53,15 +46,7 @@ fn time_with_fold<'p>(
tzinfo: Option<&PyTzInfo>,
fold: bool,
) -> PyResult<&'p PyTime> {
PyTime::new_with_fold(
py,
hour,
minute,
second,
microsecond,
tzinfo.map(|o| o.to_object(py)).as_ref(),
fold,
)
PyTime::new_with_fold(py, hour, minute, second, microsecond, tzinfo, fold)
}

#[pyfunction]
Expand Down Expand Up @@ -130,7 +115,7 @@ fn make_datetime<'p>(
minute,
second,
microsecond,
tzinfo.map(|o| (o.to_object(py))).as_ref(),
tzinfo,
)
}

Expand Down
77 changes: 61 additions & 16 deletions src/ffi/tests.rs
Expand Up @@ -11,8 +11,10 @@ use libc::wchar_t;
fn test_datetime_fromtimestamp() {
Python::with_gil(|py| {
let args: Py<PyAny> = (100,).into_py(py);
unsafe { PyDateTime_IMPORT() };
let dt: &PyAny = unsafe { py.from_owned_ptr(PyDateTime_FromTimestamp(args.as_ptr())) };
let dt: &PyAny = unsafe {
PyDateTime_IMPORT();
py.from_owned_ptr(PyDateTime_FromTimestamp(args.as_ptr()))
};
let locals = PyDict::new(py);
locals.set_item("dt", dt).unwrap();
py.run(
Expand All @@ -29,8 +31,10 @@ fn test_datetime_fromtimestamp() {
fn test_date_fromtimestamp() {
Python::with_gil(|py| {
let args: Py<PyAny> = (100,).into_py(py);
unsafe { PyDateTime_IMPORT() };
let dt: &PyAny = unsafe { py.from_owned_ptr(PyDate_FromTimestamp(args.as_ptr())) };
let dt: &PyAny = unsafe {
PyDateTime_IMPORT();
py.from_owned_ptr(PyDate_FromTimestamp(args.as_ptr()))
};
let locals = PyDict::new(py);
locals.set_item("dt", dt).unwrap();
py.run(
Expand All @@ -46,12 +50,10 @@ fn test_date_fromtimestamp() {
#[test]
fn test_utc_timezone() {
Python::with_gil(|py| {
let utc_timezone = unsafe {
let utc_timezone: &PyAny = unsafe {
PyDateTime_IMPORT();
PyDateTime_TimeZone_UTC()
py.from_borrowed_ptr(PyDateTime_TimeZone_UTC())
};
let utc_timezone =
unsafe { &*((&utc_timezone) as *const *mut PyObject as *const Py<PyAny>) };
let locals = PyDict::new(py);
locals.set_item("utc_timezone", utc_timezone).unwrap();
py.run(
Expand All @@ -63,6 +65,49 @@ fn test_utc_timezone() {
})
}

#[test]
#[cfg(feature = "macros")]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
fn test_timezone_from_offset() {
use crate::types::PyDelta;

Python::with_gil(|py| {
let tz: &PyAny = unsafe {
PyDateTime_IMPORT();
py.from_borrowed_ptr(PyTimeZone_FromOffset(
PyDelta::new(py, 0, 100, 0, false).unwrap().as_ptr(),
))
};
crate::py_run!(
py,
tz,
"import datetime; assert tz == datetime.timezone(datetime.timedelta(seconds=100))"
);
})
}

#[test]
#[cfg(feature = "macros")]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
fn test_timezone_from_offset_and_name() {
use crate::types::PyDelta;

Python::with_gil(|py| {
let tz: &PyAny = unsafe {
PyDateTime_IMPORT();
py.from_borrowed_ptr(PyTimeZone_FromOffsetAndName(
PyDelta::new(py, 0, 100, 0, false).unwrap().as_ptr(),
PyString::new(py, "testtz").as_ptr(),
))
};
crate::py_run!(
py,
tz,
"import datetime; assert tz == datetime.timezone(datetime.timedelta(seconds=100), 'testtz')"
);
})
}

#[cfg(target_endian = "little")]
#[test]
fn ascii_object_bitfield() {
Expand Down Expand Up @@ -193,19 +238,19 @@ fn ucs4() {
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
#[cfg(not(PyPy))]
fn test_get_tzinfo() {
use crate::types::timezone_utc;

crate::Python::with_gil(|py| {
use crate::types::{PyDateTime, PyTime};
use crate::{AsPyPointer, PyAny, ToPyObject};
use crate::{AsPyPointer, PyAny};

let datetime = py.import("datetime").map_err(|e| e.print(py)).unwrap();
let timezone = datetime.getattr("timezone").unwrap();
let utc = timezone.getattr("utc").unwrap().to_object(py);
let utc = timezone_utc(py);

let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(utc)).unwrap();

assert!(
unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) }
.is(&utc)
.is(utc)
);

let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
Expand All @@ -215,11 +260,11 @@ fn test_get_tzinfo() {
.is_none()
);

let t = PyTime::new(py, 0, 0, 0, 0, Some(&utc)).unwrap();
let t = PyTime::new(py, 0, 0, 0, 0, Some(utc)).unwrap();

assert!(
unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_TIME_GET_TZINFO(t.as_ptr())) }
.is(&utc)
.is(utc)
);

let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
Expand Down