From dcaed199c7737fa608549ebbd83544f837dcff0e Mon Sep 17 00:00:00 2001 From: Tpt Date: Thu, 14 Dec 2023 16:50:52 +0100 Subject: [PATCH] Adds internal timezone_from_offset function It allows to build conversions from chrono without direct access to the C API --- src/conversions/chrono.rs | 26 ++++++++-------------- src/types/datetime.rs | 47 ++++++++++++++++++++++++++++++++++++++- src/types/mod.rs | 2 +- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/conversions/chrono.rs b/src/conversions/chrono.rs index 9ecbce37ded..55d8e9cfdc9 100644 --- a/src/conversions/chrono.rs +++ b/src/conversions/chrono.rs @@ -41,6 +41,7 @@ //! } //! ``` use crate::exceptions::{PyTypeError, PyUserWarning, PyValueError}; +use crate::types::datetime::timezone_from_offset; use crate::types::{ timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTzInfo, PyTzInfoAccess, PyUnicode, @@ -50,7 +51,6 @@ use chrono::offset::{FixedOffset, Utc}; use chrono::{ DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike, }; -use pyo3_ffi::{PyDateTime_IMPORT, PyTimeZone_FromOffset}; use std::convert::TryInto; impl ToPyObject for Duration { @@ -231,22 +231,14 @@ impl FromPyObject<'_> for DateTime { } } -// Utility function used to convert PyDelta to timezone -fn py_timezone_from_offset<'a>(py: &Python<'a>, td: &PyDelta) -> &'a PyAny { - // Safety: py.from_owned_ptr needs the cast to be valid. - // Since we are forcing a &PyDelta as input, the cast should always be valid. - unsafe { - PyDateTime_IMPORT(); - py.from_owned_ptr(PyTimeZone_FromOffset(td.as_ptr())) - } -} - impl ToPyObject for FixedOffset { fn to_object(&self, py: Python<'_>) -> PyObject { let seconds_offset = self.local_minus_utc(); let td = PyDelta::new(py, 0, seconds_offset, 0, true).expect("failed to construct timedelta"); - py_timezone_from_offset(&py, td).into() + timezone_from_offset(py, td) + .expect("Failed to construct PyTimezone") + .into() } } @@ -847,14 +839,14 @@ mod tests { let offset = FixedOffset::east_opt(3600).unwrap().to_object(py); // Python timezone from timedelta let td = PyDelta::new(py, 0, 3600, 0, true).unwrap(); - let py_timedelta = py_timezone_from_offset(&py, td); + let py_timedelta = timezone_from_offset(py, td).unwrap(); // Should be equal assert!(offset.as_ref(py).eq(py_timedelta).unwrap()); // Same but with negative values let offset = FixedOffset::east_opt(-3600).unwrap().to_object(py); let td = PyDelta::new(py, 0, -3600, 0, true).unwrap(); - let py_timedelta = py_timezone_from_offset(&py, td); + let py_timedelta = timezone_from_offset(py, td).unwrap(); assert!(offset.as_ref(py).eq(py_timedelta).unwrap()); }) } @@ -863,7 +855,7 @@ mod tests { fn test_pyo3_offset_fixed_frompyobject() { Python::with_gil(|py| { let py_timedelta = PyDelta::new(py, 0, 3600, 0, true).unwrap(); - let py_tzinfo = py_timezone_from_offset(&py, py_timedelta); + let py_tzinfo = timezone_from_offset(py, py_timedelta).unwrap(); let offset: FixedOffset = py_tzinfo.extract().unwrap(); assert_eq!(FixedOffset::east_opt(3600).unwrap(), offset); }) @@ -886,12 +878,12 @@ mod tests { assert_eq!(Utc, py_utc); let py_timedelta = PyDelta::new(py, 0, 0, 0, true).unwrap(); - let py_timezone_utc = py_timezone_from_offset(&py, py_timedelta); + let py_timezone_utc = timezone_from_offset(py, py_timedelta).unwrap(); let py_timezone_utc: Utc = py_timezone_utc.extract().unwrap(); assert_eq!(Utc, py_timezone_utc); let py_timedelta = PyDelta::new(py, 0, 3600, 0, true).unwrap(); - let py_timezone = py_timezone_from_offset(&py, py_timedelta); + let py_timezone = timezone_from_offset(py, py_timedelta).unwrap(); assert!(py_timezone.extract::().is_err()); }) } diff --git a/src/types/datetime.rs b/src/types/datetime.rs index a2f9f5cefee..1f9db92a877 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -21,8 +21,10 @@ use crate::ffi::{ }; use crate::instance::PyNativeType; use crate::types::PyTuple; -use crate::{IntoPy, Py, PyAny, Python}; +use crate::{AsPyPointer, IntoPy, Py, PyAny, Python}; use std::os::raw::c_int; +#[cfg(feature = "chrono")] +use std::ptr; fn ensure_datetime_api(_py: Python<'_>) -> &'static PyDateTime_CAPI { unsafe { @@ -487,6 +489,18 @@ pub fn timezone_utc(py: Python<'_>) -> &PyTzInfo { unsafe { &*(ensure_datetime_api(py).TimeZone_UTC as *const PyTzInfo) } } +/// Equivalent to `datetime.timezone` constructor +/// +/// Only used internally +#[cfg(feature = "chrono")] +pub fn timezone_from_offset<'a>(py: Python<'a>, offset: &PyDelta) -> PyResult<&'a PyTzInfo> { + let api = ensure_datetime_api(py); + unsafe { + let ptr = (api.TimeZone_FromTimeZone)(offset.as_ptr(), ptr::null_mut()); + py.from_owned_ptr_or_err(ptr) + } +} + /// Bindings for `datetime.timedelta` #[repr(transparent)] pub struct PyDelta(PyAny); @@ -620,4 +634,35 @@ mod tests { assert!(t.get_tzinfo().is_none()); }); } + + #[test] + #[cfg(all(feature = "macros", feature = "chrono"))] + #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons + fn test_timezone_from_offset() { + Python::with_gil(|py| { + assert!( + timezone_from_offset(py, PyDelta::new(py, 0, -3600, 0, true).unwrap()) + .unwrap() + .call_method1("utcoffset", ((),)) + .unwrap() + .extract::<&PyDelta>() + .unwrap() + .eq(PyDelta::new(py, 0, -3600, 0, true).unwrap()) + .unwrap() + ); + + assert!( + timezone_from_offset(py, PyDelta::new(py, 0, 3600, 0, true).unwrap()) + .unwrap() + .call_method1("utcoffset", ((),)) + .unwrap() + .extract::<&PyDelta>() + .unwrap() + .eq(PyDelta::new(py, 0, 3600, 0, true).unwrap()) + .unwrap() + ); + + timezone_from_offset(py, PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err(); + }) + } } diff --git a/src/types/mod.rs b/src/types/mod.rs index ef2390cc809..fcf8ed0999d 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -278,7 +278,7 @@ mod capsule; mod code; mod complex; #[cfg(not(Py_LIMITED_API))] -mod datetime; +pub(crate) mod datetime; mod dict; mod ellipsis; mod floatob;