From 68217d8bb6a842d6ca9a68ebe4155e67c715eeb2 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 18 Dec 2024 12:04:57 +0100 Subject: [PATCH] Move datetime abi utilities to separate file --- src/conversions/chrono.rs | 54 +---- src/conversions/jiff.rs | 439 +++++++++++++++++++++++++------------- src/types/datetime_abi.rs | 51 +++++ src/types/mod.rs | 2 + 4 files changed, 341 insertions(+), 205 deletions(-) create mode 100644 src/types/datetime_abi.rs diff --git a/src/conversions/chrono.rs b/src/conversions/chrono.rs index 04febb43b78..76fa83fd05c 100644 --- a/src/conversions/chrono.rs +++ b/src/conversions/chrono.rs @@ -44,11 +44,13 @@ use crate::conversion::IntoPyObject; use crate::exceptions::{PyTypeError, PyUserWarning, PyValueError}; #[cfg(Py_LIMITED_API)] -use crate::sync::GILOnceCell; +use crate::intern; use crate::types::any::PyAnyMethods; #[cfg(not(Py_LIMITED_API))] use crate::types::datetime::timezone_from_offset; #[cfg(Py_LIMITED_API)] +use crate::types::datetime_abi::{check_type, timezone_utc, DatetimeTypes}; +#[cfg(Py_LIMITED_API)] use crate::types::IntoPyDict; use crate::types::PyNone; #[cfg(not(Py_LIMITED_API))] @@ -57,8 +59,6 @@ use crate::types::{ PyTzInfo, PyTzInfoAccess, }; use crate::{ffi, Bound, FromPyObject, IntoPyObjectExt, PyAny, PyErr, PyObject, PyResult, Python}; -#[cfg(Py_LIMITED_API)] -use crate::{intern, DowncastError}; #[allow(deprecated)] use crate::{IntoPy, ToPyObject}; use chrono::offset::{FixedOffset, Utc}; @@ -811,54 +811,6 @@ fn py_time_to_naive_time(py_time: &Bound<'_, PyAny>) -> PyResult { .ok_or_else(|| PyValueError::new_err("invalid or out-of-range time")) } -#[cfg(Py_LIMITED_API)] -fn check_type(value: &Bound<'_, PyAny>, t: &PyObject, type_name: &'static str) -> PyResult<()> { - if !value.is_instance(t.bind(value.py()))? { - return Err(DowncastError::new(value, type_name).into()); - } - Ok(()) -} - -#[cfg(Py_LIMITED_API)] -struct DatetimeTypes { - date: PyObject, - datetime: PyObject, - time: PyObject, - timedelta: PyObject, - timezone: PyObject, - timezone_utc: PyObject, - tzinfo: PyObject, -} - -#[cfg(Py_LIMITED_API)] -impl DatetimeTypes { - fn get(py: Python<'_>) -> &Self { - Self::try_get(py).expect("failed to load datetime module") - } - - fn try_get(py: Python<'_>) -> PyResult<&Self> { - static TYPES: GILOnceCell = GILOnceCell::new(); - TYPES.get_or_try_init(py, || { - let datetime = py.import("datetime")?; - let timezone = datetime.getattr("timezone")?; - Ok::<_, PyErr>(Self { - date: datetime.getattr("date")?.into(), - datetime: datetime.getattr("datetime")?.into(), - time: datetime.getattr("time")?.into(), - timedelta: datetime.getattr("timedelta")?.into(), - timezone_utc: timezone.getattr("utc")?.into(), - timezone: timezone.into(), - tzinfo: datetime.getattr("tzinfo")?.into(), - }) - }) - } -} - -#[cfg(Py_LIMITED_API)] -fn timezone_utc(py: Python<'_>) -> Bound<'_, PyAny> { - DatetimeTypes::get(py).timezone_utc.bind(py).clone() -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/conversions/jiff.rs b/src/conversions/jiff.rs index 79315a3a492..7f29d923d0b 100644 --- a/src/conversions/jiff.rs +++ b/src/conversions/jiff.rs @@ -5,6 +5,10 @@ use crate::pybacked::PyBackedStr; use crate::sync::GILOnceCell; #[cfg(not(Py_LIMITED_API))] use crate::types::datetime::timezone_from_offset; +#[cfg(Py_LIMITED_API)] +use crate::types::datetime_abi::{check_type, DatetimeTypes}; +#[cfg(Py_LIMITED_API)] +use crate::types::IntoPyDict; use crate::types::{PyAnyMethods, PyNone, PyType}; #[cfg(not(Py_LIMITED_API))] use crate::types::{ @@ -50,12 +54,35 @@ fn datetime_to_pydatetime<'py>( #[cfg(Py_LIMITED_API)] fn datetime_to_pydatetime<'py>( - _py: Python<'py>, - _datetime: &DateTime, - _fold: bool, - _timezone: Option<&TimeZone>, + py: Python<'py>, + datetime: &DateTime, + fold: bool, + timezone: Option<&TimeZone>, ) -> PyResult> { - todo!() + DatetimeTypes::try_get(py)?.datetime.bind(py).call( + ( + datetime.year(), + datetime.month(), + datetime.day(), + datetime.hour(), + datetime.minute(), + datetime.second(), + datetime.subsec_nanosecond() / 1000, + timezone + .map(|tz| { + if tz.iana_name().is_some() { + tz.into_pyobject(py) + } else { + // TODO after https://github.com/BurntSushi/jiff/pull/170 we can remove this special case + let (offset, _, _) = tz.to_offset(tz.to_timestamp(*datetime)?); + offset.into_pyobject(py) + } + }) + .transpose()? + .as_ref(), + ), + Some(&[("fold", fold as u8)].into_py_dict(py)?), + ) } #[cfg(not(Py_LIMITED_API))] @@ -75,7 +102,7 @@ fn pytime_to_time(time: &Bound<'_, PyAny>) -> PyResult