From 96c629ac4d69eda20cd1d30ac9d5f72a0ecaf80f Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers <7943856+bschoenmaeckers@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:20:13 +0100 Subject: [PATCH] fix `chrono::DateTime` intoPyObject conversion (#4790) * fix `chrono::DateTime` intoPyObject conversion * Add test --- newsfragments/4790.fixed.md | 1 + src/conversions/chrono.rs | 61 +++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 newsfragments/4790.fixed.md diff --git a/newsfragments/4790.fixed.md b/newsfragments/4790.fixed.md new file mode 100644 index 00000000000..9b5e1bf60f1 --- /dev/null +++ b/newsfragments/4790.fixed.md @@ -0,0 +1 @@ +fix chrono::DateTime intoPyObject conversion when `Tz` is `chrono_tz::Tz` diff --git a/src/conversions/chrono.rs b/src/conversions/chrono.rs index bdee4934409..02bc65e59c5 100644 --- a/src/conversions/chrono.rs +++ b/src/conversions/chrono.rs @@ -54,7 +54,7 @@ use crate::types::{ timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTzInfo, PyTzInfoAccess, }; -use crate::{ffi, Bound, FromPyObject, PyAny, PyErr, PyObject, PyResult, Python}; +use crate::{ffi, Bound, FromPyObject, IntoPyObjectExt, PyAny, PyErr, PyObject, PyResult, Python}; #[cfg(Py_LIMITED_API)] use crate::{intern, DowncastError}; #[allow(deprecated)] @@ -418,11 +418,14 @@ impl ToPyObject for DateTime { #[allow(deprecated)] impl IntoPy for DateTime { fn into_py(self, py: Python<'_>) -> PyObject { - self.into_pyobject(py).unwrap().into_any().unbind() + self.to_object(py) } } -impl<'py, Tz: TimeZone> IntoPyObject<'py> for DateTime { +impl<'py, Tz: TimeZone> IntoPyObject<'py> for DateTime +where + Tz: IntoPyObject<'py>, +{ #[cfg(Py_LIMITED_API)] type Target = PyAny; #[cfg(not(Py_LIMITED_API))] @@ -436,7 +439,10 @@ impl<'py, Tz: TimeZone> IntoPyObject<'py> for DateTime { } } -impl<'py, Tz: TimeZone> IntoPyObject<'py> for &DateTime { +impl<'py, Tz: TimeZone> IntoPyObject<'py> for &DateTime +where + Tz: IntoPyObject<'py>, +{ #[cfg(Py_LIMITED_API)] type Target = PyAny; #[cfg(not(Py_LIMITED_API))] @@ -445,7 +451,11 @@ impl<'py, Tz: TimeZone> IntoPyObject<'py> for &DateTime { type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> Result { - let tz = self.offset().fix().into_pyobject(py)?; + let tz = self.timezone().into_bound_py_any(py)?; + + #[cfg(not(Py_LIMITED_API))] + let tz = tz.downcast()?; + let DateArgs { year, month, day } = (&self.naive_local().date()).into(); let TimeArgs { hour, @@ -456,7 +466,7 @@ impl<'py, Tz: TimeZone> IntoPyObject<'py> for &DateTime { } = (&self.naive_local().time()).into(); #[cfg(not(Py_LIMITED_API))] - let datetime = PyDateTime::new(py, year, month, day, hour, min, sec, micro, Some(&tz))?; + let datetime = PyDateTime::new(py, year, month, day, hour, min, sec, micro, Some(tz))?; #[cfg(Py_LIMITED_API)] let datetime = DatetimeTypes::try_get(py).and_then(|dt| { @@ -1174,6 +1184,35 @@ mod tests { }) } + #[test] + #[cfg(all(Py_3_9, feature = "chrono-tz", not(windows)))] + fn test_pyo3_datetime_into_pyobject_tz() { + Python::with_gil(|py| { + let datetime = NaiveDate::from_ymd_opt(2024, 12, 11) + .unwrap() + .and_hms_opt(23, 3, 13) + .unwrap() + .and_local_timezone(chrono_tz::Tz::Europe__London) + .unwrap(); + let datetime = datetime.into_pyobject(py).unwrap(); + let py_datetime = new_py_datetime_ob( + py, + "datetime", + ( + 2024, + 12, + 11, + 23, + 3, + 13, + 0, + python_zoneinfo(py, "Europe/London"), + ), + ); + assert_eq!(datetime.compare(&py_datetime).unwrap(), Ordering::Equal); + }) + } + #[test] fn test_pyo3_datetime_frompyobject_utc() { Python::with_gil(|py| { @@ -1377,6 +1416,16 @@ mod tests { .unwrap() } + #[cfg(all(Py_3_9, feature = "chrono-tz", not(windows)))] + fn python_zoneinfo<'py>(py: Python<'py>, timezone: &str) -> Bound<'py, PyAny> { + py.import("zoneinfo") + .unwrap() + .getattr("ZoneInfo") + .unwrap() + .call1((timezone,)) + .unwrap() + } + #[cfg(not(any(target_arch = "wasm32", Py_GIL_DISABLED)))] mod proptests { use super::*;