Skip to content

Commit

Permalink
Adds internal timezone_from_offset function
Browse files Browse the repository at this point in the history
It allows to build conversions from chrono without direct access to the C API
  • Loading branch information
Tpt committed Dec 14, 2023
1 parent 79a54cf commit 6c739d6
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 20 deletions.
1 change: 1 addition & 0 deletions newsfragments/3648.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add internal `timezone_from_offset` function to construct `datetime.timezone` objects for conversions from `chrono`
29 changes: 10 additions & 19 deletions src/conversions/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,14 @@
//! ```
use crate::exceptions::{PyTypeError, PyUserWarning, PyValueError};
use crate::types::{
timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess,
PyTzInfo, PyTzInfoAccess, PyUnicode,
timezone_from_offset, timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess,
PyTime, PyTimeAccess, PyTzInfo, PyTzInfoAccess, PyUnicode,
};
use crate::{FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject};
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 {
Expand Down Expand Up @@ -231,22 +230,14 @@ impl FromPyObject<'_> for DateTime<Utc> {
}
}

// 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()
}
}

Expand Down Expand Up @@ -847,14 +838,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());
})
}
Expand All @@ -863,7 +854,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);
})
Expand All @@ -886,12 +877,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::<Utc>().is_err());
})
}
Expand Down
47 changes: 46 additions & 1 deletion src/types/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
})
}
}
2 changes: 2 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub use self::capsule::PyCapsule;
#[cfg(not(Py_LIMITED_API))]
pub use self::code::PyCode;
pub use self::complex::PyComplex;
#[cfg(all(feature = "chrono", not(Py_LIMITED_API)))]
pub(crate) use self::datetime::timezone_from_offset;
#[cfg(not(Py_LIMITED_API))]
pub use self::datetime::{
timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess,
Expand Down

0 comments on commit 6c739d6

Please sign in to comment.