Skip to content

Commit

Permalink
fix chrono::DateTime<Tz> intoPyObject conversion (#4790)
Browse files Browse the repository at this point in the history
* fix `chrono::DateTime<Tz>` intoPyObject conversion

* Add test
  • Loading branch information
bschoenmaeckers authored Dec 12, 2024
1 parent 926f5cf commit 0777c6e
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 6 deletions.
1 change: 1 addition & 0 deletions newsfragments/4790.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fix chrono::DateTime<Tz> intoPyObject conversion when `Tz` is `chrono_tz::Tz`
61 changes: 55 additions & 6 deletions src/conversions/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -418,11 +418,14 @@ impl<Tz: TimeZone> ToPyObject for DateTime<Tz> {
#[allow(deprecated)]
impl<Tz: TimeZone> IntoPy<PyObject> for DateTime<Tz> {
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<Tz> {
impl<'py, Tz: TimeZone> IntoPyObject<'py> for DateTime<Tz>
where
Tz: IntoPyObject<'py>,
{
#[cfg(Py_LIMITED_API)]
type Target = PyAny;
#[cfg(not(Py_LIMITED_API))]
Expand All @@ -436,7 +439,10 @@ impl<'py, Tz: TimeZone> IntoPyObject<'py> for DateTime<Tz> {
}
}

impl<'py, Tz: TimeZone> IntoPyObject<'py> for &DateTime<Tz> {
impl<'py, Tz: TimeZone> IntoPyObject<'py> for &DateTime<Tz>
where
Tz: IntoPyObject<'py>,
{
#[cfg(Py_LIMITED_API)]
type Target = PyAny;
#[cfg(not(Py_LIMITED_API))]
Expand All @@ -445,7 +451,11 @@ impl<'py, Tz: TimeZone> IntoPyObject<'py> for &DateTime<Tz> {
type Error = PyErr;

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
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,
Expand All @@ -456,7 +466,7 @@ impl<'py, Tz: TimeZone> IntoPyObject<'py> for &DateTime<Tz> {
} = (&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| {
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -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::*;
Expand Down

0 comments on commit 0777c6e

Please sign in to comment.