Skip to content

Commit

Permalink
add _bound constructors for datetime types
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Feb 5, 2024
1 parent c9c6f92 commit ad80c94
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 108 deletions.
78 changes: 45 additions & 33 deletions pytests/src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use pyo3::types::{
};

#[pyfunction]
fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate> {
PyDate::new(py, year, month, day)
fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
PyDate::new_bound(py, year, month, day)
}

#[pyfunction]
Expand All @@ -17,34 +17,34 @@ fn get_date_tuple<'p>(py: Python<'p>, d: &PyDate) -> Bound<'p, PyTuple> {
}

#[pyfunction]
fn date_from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> {
PyDate::from_timestamp(py, timestamp)
fn date_from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<Bound<'_, PyDate>> {
PyDate::from_timestamp_bound(py, timestamp)
}

#[pyfunction]
fn make_time<'p>(
py: Python<'p>,
fn make_time<'py>(
py: Python<'py>,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&PyTzInfo>,
) -> PyResult<&'p PyTime> {
PyTime::new(py, hour, minute, second, microsecond, tzinfo)
tzinfo: Option<&Bound<'_, PyTzInfo>>,
) -> PyResult<Bound<'py, PyTime>> {
PyTime::new_bound(py, hour, minute, second, microsecond, tzinfo)
}

#[pyfunction]
#[pyo3(signature = (hour, minute, second, microsecond, tzinfo, fold))]
fn time_with_fold<'p>(
py: Python<'p>,
fn time_with_fold<'py>(
py: Python<'py>,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&PyTzInfo>,
tzinfo: Option<&Bound<'_, PyTzInfo>>,
fold: bool,
) -> PyResult<&'p PyTime> {
PyTime::new_with_fold(py, hour, minute, second, microsecond, tzinfo, fold)
) -> PyResult<Bound<'py, PyTime>> {
PyTime::new_bound_with_fold(py, hour, minute, second, microsecond, tzinfo, fold)
}

#[pyfunction]
Expand Down Expand Up @@ -75,12 +75,17 @@ fn get_time_tuple_fold<'p>(py: Python<'p>, dt: &PyTime) -> Bound<'p, PyTuple> {
}

#[pyfunction]
fn make_delta(py: Python<'_>, days: i32, seconds: i32, microseconds: i32) -> PyResult<&PyDelta> {
PyDelta::new(py, days, seconds, microseconds, true)
fn make_delta(
py: Python<'_>,
days: i32,
seconds: i32,
microseconds: i32,
) -> PyResult<Bound<'_, PyDelta>> {
PyDelta::new_bound(py, days, seconds, microseconds, true)
}

#[pyfunction]
fn get_delta_tuple<'p>(py: Python<'p>, delta: &PyDelta) -> Bound<'p, PyTuple> {
fn get_delta_tuple<'py>(py: Python<'py>, delta: &Bound<'_, PyDelta>) -> Bound<'py, PyTuple> {
PyTuple::new_bound(
py,
[
Expand All @@ -93,18 +98,18 @@ fn get_delta_tuple<'p>(py: Python<'p>, delta: &PyDelta) -> Bound<'p, PyTuple> {

#[allow(clippy::too_many_arguments)]
#[pyfunction]
fn make_datetime<'p>(
py: Python<'p>,
fn make_datetime<'py>(
py: Python<'py>,
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&PyTzInfo>,
) -> PyResult<&'p PyDateTime> {
PyDateTime::new(
tzinfo: Option<&Bound<'_, PyTzInfo>>,
) -> PyResult<Bound<'py, PyDateTime>> {
PyDateTime::new_bound(
py,
year,
month,
Expand All @@ -118,7 +123,7 @@ fn make_datetime<'p>(
}

#[pyfunction]
fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> {
fn get_datetime_tuple<'py>(py: Python<'py>, dt: &Bound<'_, PyDateTime>) -> Bound<'py, PyTuple> {
PyTuple::new_bound(
py,
[
Expand All @@ -134,7 +139,10 @@ fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple>
}

#[pyfunction]
fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> {
fn get_datetime_tuple_fold<'py>(
py: Python<'py>,
dt: &Bound<'_, PyDateTime>,
) -> Bound<'py, PyTuple> {
PyTuple::new_bound(
py,
[
Expand All @@ -151,12 +159,12 @@ fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyT
}

#[pyfunction]
fn datetime_from_timestamp<'p>(
py: Python<'p>,
fn datetime_from_timestamp<'py>(
py: Python<'py>,
ts: f64,
tz: Option<&PyTzInfo>,
) -> PyResult<&'p PyDateTime> {
PyDateTime::from_timestamp(py, ts, tz)
tz: Option<&Bound<'_, PyTzInfo>>,
) -> PyResult<Bound<'py, PyDateTime>> {
PyDateTime::from_timestamp_bound(py, ts, tz)
}

#[pyfunction]
Expand All @@ -179,15 +187,19 @@ impl TzClass {
TzClass {}
}

fn utcoffset<'p>(&self, py: Python<'p>, _dt: &PyDateTime) -> PyResult<&'p PyDelta> {
PyDelta::new(py, 0, 3600, 0, true)
fn utcoffset<'py>(
&self,
py: Python<'py>,
_dt: &Bound<'_, PyDateTime>,
) -> PyResult<Bound<'py, PyDelta>> {
PyDelta::new_bound(py, 0, 3600, 0, true)
}

fn tzname(&self, _py: Python<'_>, _dt: &PyDateTime) -> String {
fn tzname(&self, _dt: &Bound<'_, PyDateTime>) -> String {
String::from("+01:00")
}

fn dst(&self, _py: Python<'_>, _dt: &PyDateTime) -> Option<&PyDelta> {
fn dst(&self, _dt: &Bound<'_, PyDateTime>) -> Option<&PyDelta> {
None
}
}
Expand Down
33 changes: 16 additions & 17 deletions src/conversions/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ use crate::types::{
};
#[cfg(Py_LIMITED_API)]
use crate::{intern, DowncastError};
use crate::{
Bound, FromPyObject, IntoPy, PyAny, PyErr, PyNativeType, PyObject, PyResult, Python, ToPyObject,
};
use crate::{Bound, FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject};
use chrono::offset::{FixedOffset, Utc};
use chrono::{
DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike,
Expand All @@ -81,7 +79,7 @@ impl ToPyObject for Duration {
// We pass true as the `normalize` parameter since we'd need to do several checks here to
// avoid that, and it shouldn't have a big performance impact.
// The seconds and microseconds cast should never overflow since it's at most the number of seconds per day
PyDelta::new(
PyDelta::new_bound(
py,
days.try_into().unwrap_or(i32::MAX),
secs.try_into().unwrap(),
Expand Down Expand Up @@ -144,7 +142,7 @@ impl ToPyObject for NaiveDate {
let DateArgs { year, month, day } = self.into();
#[cfg(not(Py_LIMITED_API))]
{
PyDate::new(py, year, month, day)
PyDate::new_bound(py, year, month, day)
.expect("failed to construct date")
.into()
}
Expand Down Expand Up @@ -189,15 +187,16 @@ impl ToPyObject for NaiveTime {
truncated_leap_second,
} = self.into();
#[cfg(not(Py_LIMITED_API))]
let time = PyTime::new(py, hour, min, sec, micro, None).expect("Failed to construct time");
let time =
PyTime::new_bound(py, hour, min, sec, micro, None).expect("Failed to construct time");
#[cfg(Py_LIMITED_API)]
let time = DatetimeTypes::get(py)
.time
.as_ref(py)
.bind(py)
.call1((hour, min, sec, micro))
.expect("failed to construct datetime.time");
if truncated_leap_second {
warn_truncated_leap_second(time);
warn_truncated_leap_second(&time);
}
time.into()
}
Expand Down Expand Up @@ -264,7 +263,7 @@ impl<Tz: TimeZone> ToPyObject for DateTime<Tz> {
// FIXME: convert to better timezone representation here than just convert to fixed offset
// See https://github.com/PyO3/pyo3/issues/3266
let tz = self.offset().fix().to_object(py);
let tz = tz.downcast(py).unwrap();
let tz = tz.bind(py).downcast().unwrap();
naive_datetime_to_py_datetime(py, &self.naive_local(), Some(tz))
}
}
Expand Down Expand Up @@ -310,9 +309,9 @@ impl ToPyObject for FixedOffset {

#[cfg(not(Py_LIMITED_API))]
{
let td = PyDelta::new(py, 0, seconds_offset, 0, true)
let td = PyDelta::new_bound(py, 0, seconds_offset, 0, true)
.expect("failed to construct timedelta");
timezone_from_offset(py, td)
timezone_from_offset(&td)
.expect("Failed to construct PyTimezone")
.into()
}
Expand Down Expand Up @@ -430,8 +429,8 @@ impl From<&NaiveTime> for TimeArgs {
fn naive_datetime_to_py_datetime(
py: Python<'_>,
naive_datetime: &NaiveDateTime,
#[cfg(not(Py_LIMITED_API))] tzinfo: Option<&PyTzInfo>,
#[cfg(Py_LIMITED_API)] tzinfo: Option<&PyAny>,
#[cfg(not(Py_LIMITED_API))] tzinfo: Option<&Bound<'_, PyTzInfo>>,
#[cfg(Py_LIMITED_API)] tzinfo: Option<&Bound<'_, PyAny>>,
) -> PyObject {
let DateArgs { year, month, day } = (&naive_datetime.date()).into();
let TimeArgs {
Expand All @@ -442,21 +441,21 @@ fn naive_datetime_to_py_datetime(
truncated_leap_second,
} = (&naive_datetime.time()).into();
#[cfg(not(Py_LIMITED_API))]
let datetime = PyDateTime::new(py, year, month, day, hour, min, sec, micro, tzinfo)
let datetime = PyDateTime::new_bound(py, year, month, day, hour, min, sec, micro, tzinfo)
.expect("failed to construct datetime");
#[cfg(Py_LIMITED_API)]
let datetime = DatetimeTypes::get(py)
.datetime
.as_ref(py)
.bind(py)
.call1((year, month, day, hour, min, sec, micro, tzinfo))
.expect("failed to construct datetime.datetime");
if truncated_leap_second {
warn_truncated_leap_second(datetime);
warn_truncated_leap_second(&datetime);
}
datetime.into()
}

fn warn_truncated_leap_second(obj: &PyAny) {
fn warn_truncated_leap_second(obj: &Bound<'_, PyAny>) {
let py = obj.py();
if let Err(e) = PyErr::warn(
py,
Expand Down
17 changes: 14 additions & 3 deletions src/conversions/std/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::types::any::PyAnyMethods;
#[cfg(Py_LIMITED_API)]
use crate::types::PyType;
#[cfg(not(Py_LIMITED_API))]
use crate::types::{timezone_utc, PyDateTime, PyDelta, PyDeltaAccess};
use crate::types::{timezone_utc_bound, PyDateTime, PyDelta, PyDeltaAccess};
#[cfg(Py_LIMITED_API)]
use crate::Py;
use crate::{
Expand Down Expand Up @@ -59,7 +59,7 @@ impl ToPyObject for Duration {

#[cfg(not(Py_LIMITED_API))]
{
PyDelta::new(
PyDelta::new_bound(
py,
days.try_into()
.expect("Too large Rust duration for timedelta"),
Expand Down Expand Up @@ -130,7 +130,18 @@ fn unix_epoch_py(py: Python<'_>) -> &PyObject {
#[cfg(not(Py_LIMITED_API))]
{
Ok::<_, PyErr>(
PyDateTime::new(py, 1970, 1, 1, 0, 0, 0, 0, Some(timezone_utc(py)))?.into(),
PyDateTime::new_bound(
py,
1970,
1,
1,
0,
0,
0,
0,
Some(&timezone_utc_bound(py)),
)?
.into(),
)
}
#[cfg(Py_LIMITED_API)]
Expand Down
16 changes: 8 additions & 8 deletions src/ffi/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fn test_timezone_from_offset() {
use crate::types::PyDelta;

Python::with_gil(|py| {
let delta = PyDelta::new(py, 0, 100, 0, false).unwrap();
let delta = PyDelta::new_bound(py, 0, 100, 0, false).unwrap();
let tz: &PyAny = unsafe { py.from_borrowed_ptr(PyTimeZone_FromOffset(delta.as_ptr())) };
crate::py_run!(
py,
Expand All @@ -97,7 +97,7 @@ fn test_timezone_from_offset_and_name() {
use crate::types::PyDelta;

Python::with_gil(|py| {
let delta = PyDelta::new(py, 0, 100, 0, false).unwrap();
let delta = PyDelta::new_bound(py, 0, 100, 0, false).unwrap();
let tzname = PyString::new_bound(py, "testtz");
let tz: &PyAny = unsafe {
py.from_borrowed_ptr(PyTimeZone_FromOffsetAndName(
Expand Down Expand Up @@ -252,36 +252,36 @@ fn ucs4() {
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
#[cfg(not(PyPy))]
fn test_get_tzinfo() {
use crate::types::timezone_utc;
use crate::types::timezone_utc_bound;

crate::Python::with_gil(|py| {
use crate::types::{PyDateTime, PyTime};
use crate::PyAny;

let utc = timezone_utc(py);
let utc = &timezone_utc_bound(py);

let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(utc)).unwrap();
let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, Some(utc)).unwrap();

assert!(
unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) }
.is(utc)
);

let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();

assert!(
unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_DATE_GET_TZINFO(dt.as_ptr())) }
.is_none()
);

let t = PyTime::new(py, 0, 0, 0, 0, Some(utc)).unwrap();
let t = PyTime::new_bound(py, 0, 0, 0, 0, Some(utc)).unwrap();

assert!(
unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_TIME_GET_TZINFO(t.as_ptr())) }
.is(utc)
);

let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
let t = PyTime::new_bound(py, 0, 0, 0, 0, None).unwrap();

assert!(
unsafe { py.from_borrowed_ptr::<PyAny>(PyDateTime_TIME_GET_TZINFO(t.as_ptr())) }
Expand Down
Loading

0 comments on commit ad80c94

Please sign in to comment.