Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds internal timezone_from_offset function #3648

Merged
merged 1 commit into from Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 9 additions & 17 deletions src/conversions/chrono.rs
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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()
}
}

Expand Down Expand Up @@ -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());
})
}
Expand All @@ -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);
})
Expand All @@ -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());
})
}
Expand Down
47 changes: 46 additions & 1 deletion src/types/datetime.rs
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> {
adamreichold marked this conversation as resolved.
Show resolved Hide resolved
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: 1 addition & 1 deletion src/types/mod.rs
Expand Up @@ -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;
Expand Down