Adds internal timezone_from_offset function

It allows to build conversions from chrono without direct access to the C API
This commit is contained in:
Tpt 2023-12-14 16:50:52 +01:00
parent 79a54cfc05
commit dcaed199c7
3 changed files with 56 additions and 19 deletions

View File

@ -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<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()
}
}
@ -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::<Utc>().is_err());
})
}

View File

@ -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();
})
}
}

View File

@ -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;