add `_bound` constructors for datetime types (#3778)

* add `_bound` constructors for datetime types

* review: Icxolu feedback

* update uses of deprecated timezone_utc
This commit is contained in:
David Hewitt 2024-02-12 20:49:58 +00:00 committed by GitHub
parent 1279467d27
commit 5b9b76fe58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 387 additions and 145 deletions

View File

@ -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]
@ -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<'py, 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<'py, 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]
@ -75,14 +75,19 @@ 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>(delta: &Bound<'py, PyDelta>) -> Bound<'py, PyTuple> {
PyTuple::new_bound(
py,
delta.py(),
[
delta.get_days(),
delta.get_seconds(),
@ -93,8 +98,8 @@ 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,
@ -102,9 +107,9 @@ fn make_datetime<'p>(
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&PyTzInfo>,
) -> PyResult<&'p PyDateTime> {
PyDateTime::new(
tzinfo: Option<&Bound<'py, PyTzInfo>>,
) -> PyResult<Bound<'py, PyDateTime>> {
PyDateTime::new_bound(
py,
year,
month,
@ -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<'py, PyDateTime>) -> Bound<'py, PyTuple> {
PyTuple::new_bound(
py,
[
@ -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<'py, PyDateTime>,
) -> Bound<'py, PyTuple> {
PyTuple::new_bound(
py,
[
@ -151,21 +159,21 @@ 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<'py, PyTzInfo>>,
) -> PyResult<Bound<'py, PyDateTime>> {
PyDateTime::from_timestamp_bound(py, ts, tz)
}
#[pyfunction]
fn get_datetime_tzinfo(dt: &PyDateTime) -> Option<Bound<'_, PyTzInfo>> {
fn get_datetime_tzinfo<'py>(dt: &Bound<'py, PyDateTime>) -> Option<Bound<'py, PyTzInfo>> {
dt.get_tzinfo_bound()
}
#[pyfunction]
fn get_time_tzinfo(dt: &PyTime) -> Option<Bound<'_, PyTzInfo>> {
fn get_time_tzinfo<'py>(dt: &Bound<'py, PyTime>) -> Option<Bound<'py, PyTzInfo>> {
dt.get_tzinfo_bound()
}
@ -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<'py, 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<'py>(&self, _dt: &Bound<'py, PyDateTime>) -> Option<Bound<'py, PyDelta>> {
None
}
}

View File

@ -47,14 +47,12 @@ use crate::types::any::PyAnyMethods;
use crate::types::datetime::timezone_from_offset;
#[cfg(not(Py_LIMITED_API))]
use crate::types::{
timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess,
PyTzInfo, PyTzInfoAccess,
timezone_utc_bound, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime,
PyTimeAccess, PyTzInfo, PyTzInfoAccess,
};
#[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,
@ -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(),
@ -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()
}
@ -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()
}
@ -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))
}
}
@ -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()
}
@ -366,7 +365,7 @@ impl FromPyObject<'_> for FixedOffset {
impl ToPyObject for Utc {
fn to_object(&self, py: Python<'_>) -> PyObject {
timezone_utc(py).into()
timezone_utc_bound(py).into()
}
}
@ -378,7 +377,7 @@ impl IntoPy<PyObject> for Utc {
impl FromPyObject<'_> for Utc {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Utc> {
let py_utc = timezone_utc(ob.py());
let py_utc = timezone_utc_bound(ob.py());
if ob.eq(py_utc)? {
Ok(Utc)
} else {
@ -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 {
@ -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,
@ -558,8 +557,8 @@ impl DatetimeTypes {
}
#[cfg(Py_LIMITED_API)]
fn timezone_utc(py: Python<'_>) -> &PyAny {
DatetimeTypes::get(py).timezone_utc.as_ref(py)
fn timezone_utc_bound(py: Python<'_>) -> Bound<'_, PyAny> {
DatetimeTypes::get(py).timezone_utc.bind(py).clone()
}
#[cfg(test)]
@ -913,7 +912,7 @@ mod tests {
let minute = 8;
let second = 9;
let micro = 999_999;
let tz_utc = timezone_utc(py);
let tz_utc = timezone_utc_bound(py);
let py_datetime = new_py_datetime_ob(
py,
"datetime",

View File

@ -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::{
@ -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"),
@ -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)]

View File

@ -77,11 +77,11 @@ fn test_utc_timezone() {
#[cfg(feature = "macros")]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
fn test_timezone_from_offset() {
use crate::types::PyDelta;
use crate::{ffi_ptr_ext::FfiPtrExt, types::PyDelta};
Python::with_gil(|py| {
let delta = PyDelta::new(py, 0, 100, 0, false).unwrap();
let tz: &PyAny = unsafe { py.from_borrowed_ptr(PyTimeZone_FromOffset(delta.as_ptr())) };
let delta = PyDelta::new_bound(py, 0, 100, 0, false).unwrap();
let tz = unsafe { PyTimeZone_FromOffset(delta.as_ptr()).assume_owned(py) };
crate::py_run!(
py,
tz,
@ -95,16 +95,13 @@ fn test_timezone_from_offset() {
#[cfg(feature = "macros")]
#[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
fn test_timezone_from_offset_and_name() {
use crate::types::PyDelta;
use crate::{ffi_ptr_ext::FfiPtrExt, 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(
delta.as_ptr(),
tzname.as_ptr(),
))
let tz = unsafe {
PyTimeZone_FromOffsetAndName(delta.as_ptr(), tzname.as_ptr()).assume_owned(py)
};
crate::py_run!(
py,
@ -253,36 +250,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())) }

View File

@ -21,6 +21,7 @@ use crate::ffi::{
};
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::instance::PyNativeType;
use crate::py_result_ext::PyResultExt;
use crate::types::any::PyAnyMethods;
use crate::types::PyTuple;
use crate::{Bound, IntoPy, Py, PyAny, PyErr, Python};
@ -201,27 +202,53 @@ pyobject_native_type!(
);
impl PyDate {
/// Creates a new `datetime.date`.
/// Deprecated form of [`PyDate::new_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyDate::new` will be replaced by `PyDate::new_bound` in a future PyO3 version"
)
)]
pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate> {
Self::new_bound(py, year, month, day).map(Bound::into_gil_ref)
}
/// Creates a new `datetime.date`.
pub fn new_bound(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
let api = ensure_datetime_api(py)?;
unsafe {
let ptr = (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType);
py.from_owned_ptr_or_err(ptr)
(api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
/// Deprecated form of [`PyDate::from_timestamp_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyDate::from_timestamp` will be replaced by `PyDate::from_timestamp_bound` in a future PyO3 version"
)
)]
pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> {
Self::from_timestamp_bound(py, timestamp).map(Bound::into_gil_ref)
}
/// Construct a `datetime.date` from a POSIX timestamp
///
/// This is equivalent to `datetime.date.fromtimestamp`
pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> {
pub fn from_timestamp_bound(py: Python<'_>, timestamp: i64) -> PyResult<Bound<'_, PyDate>> {
let time_tuple = PyTuple::new_bound(py, [timestamp]);
// safety ensure that the API is loaded
let _api = ensure_datetime_api(py)?;
unsafe {
let ptr = PyDate_FromTimestamp(time_tuple.as_ptr());
py.from_owned_ptr_or_err(ptr)
PyDate_FromTimestamp(time_tuple.as_ptr())
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
}
@ -266,10 +293,17 @@ pyobject_native_type!(
);
impl PyDateTime {
/// Creates a new `datetime.datetime` object.
/// Deprecated form of [`PyDateTime::new_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyDateTime::new` will be replaced by `PyDateTime::new_bound` in a future PyO3 version"
)
)]
#[allow(clippy::too_many_arguments)]
pub fn new<'p>(
py: Python<'p>,
pub fn new<'py>(
py: Python<'py>,
year: i32,
month: u8,
day: u8,
@ -277,11 +311,38 @@ impl PyDateTime {
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&PyTzInfo>,
) -> PyResult<&'p PyDateTime> {
tzinfo: Option<&'py PyTzInfo>,
) -> PyResult<&'py PyDateTime> {
Self::new_bound(
py,
year,
month,
day,
hour,
minute,
second,
microsecond,
tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
)
.map(Bound::into_gil_ref)
}
/// Creates a new `datetime.datetime` object.
#[allow(clippy::too_many_arguments)]
pub fn new_bound<'py>(
py: Python<'py>,
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&Bound<'py, PyTzInfo>>,
) -> PyResult<Bound<'py, PyDateTime>> {
let api = ensure_datetime_api(py)?;
unsafe {
let ptr = (api.DateTime_FromDateAndTime)(
(api.DateTime_FromDateAndTime)(
year,
c_int::from(month),
c_int::from(day),
@ -291,11 +352,48 @@ impl PyDateTime {
microsecond as c_int,
opt_to_pyobj(tzinfo),
api.DateTimeType,
);
py.from_owned_ptr_or_err(ptr)
)
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
/// Deprecated form of [`PyDateTime::new_bound_with_fold`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyDateTime::new_with_fold` will be replaced by `PyDateTime::new_bound_with_fold` in a future PyO3 version"
)
)]
#[allow(clippy::too_many_arguments)]
pub fn new_with_fold<'py>(
py: Python<'py>,
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&'py PyTzInfo>,
fold: bool,
) -> PyResult<&'py PyDateTime> {
Self::new_bound_with_fold(
py,
year,
month,
day,
hour,
minute,
second,
microsecond,
tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
fold,
)
.map(Bound::into_gil_ref)
}
/// Alternate constructor that takes a `fold` parameter. A `true` value for this parameter
/// signifies this this datetime is the later of two moments with the same representation,
/// during a repeated interval.
@ -304,8 +402,8 @@ impl PyDateTime {
/// represented time is ambiguous.
/// See [PEP 495](https://www.python.org/dev/peps/pep-0495/) for more detail.
#[allow(clippy::too_many_arguments)]
pub fn new_with_fold<'p>(
py: Python<'p>,
pub fn new_bound_with_fold<'py>(
py: Python<'py>,
year: i32,
month: u8,
day: u8,
@ -313,12 +411,12 @@ impl PyDateTime {
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&PyTzInfo>,
tzinfo: Option<&Bound<'py, PyTzInfo>>,
fold: bool,
) -> PyResult<&'p PyDateTime> {
) -> PyResult<Bound<'py, PyDateTime>> {
let api = ensure_datetime_api(py)?;
unsafe {
let ptr = (api.DateTime_FromDateAndTimeAndFold)(
(api.DateTime_FromDateAndTimeAndFold)(
year,
c_int::from(month),
c_int::from(day),
@ -329,27 +427,46 @@ impl PyDateTime {
opt_to_pyobj(tzinfo),
c_int::from(fold),
api.DateTimeType,
);
py.from_owned_ptr_or_err(ptr)
)
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
/// Deprecated form of [`PyDateTime::from_timestamp_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyDateTime::from_timestamp` will be replaced by `PyDateTime::from_timestamp_bound` in a future PyO3 version"
)
)]
pub fn from_timestamp<'py>(
py: Python<'py>,
timestamp: f64,
tzinfo: Option<&'py PyTzInfo>,
) -> PyResult<&'py PyDateTime> {
Self::from_timestamp_bound(py, timestamp, tzinfo.map(PyTzInfo::as_borrowed).as_deref())
.map(Bound::into_gil_ref)
}
/// Construct a `datetime` object from a POSIX timestamp
///
/// This is equivalent to `datetime.datetime.fromtimestamp`
pub fn from_timestamp<'p>(
py: Python<'p>,
pub fn from_timestamp_bound<'py>(
py: Python<'py>,
timestamp: f64,
tzinfo: Option<&PyTzInfo>,
) -> PyResult<&'p PyDateTime> {
let args: Py<PyTuple> = (timestamp, tzinfo).into_py(py);
tzinfo: Option<&Bound<'py, PyTzInfo>>,
) -> PyResult<Bound<'py, PyDateTime>> {
let args = IntoPy::<Py<PyTuple>>::into_py((timestamp, tzinfo), py).into_bound(py);
// safety ensure API is loaded
let _api = ensure_datetime_api(py)?;
unsafe {
let ptr = PyDateTime_FromTimestamp(args.as_ptr());
py.from_owned_ptr_or_err(ptr)
PyDateTime_FromTimestamp(args.as_ptr())
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
}
@ -463,42 +580,99 @@ pyobject_native_type!(
);
impl PyTime {
/// Creates a new `datetime.time` object.
pub fn new<'p>(
py: Python<'p>,
/// Deprecated form of [`PyTime::new_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyTime::new` will be replaced by `PyTime::new_bound` in a future PyO3 version"
)
)]
pub fn new<'py>(
py: Python<'py>,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&PyTzInfo>,
) -> PyResult<&'p PyTime> {
tzinfo: Option<&'py PyTzInfo>,
) -> PyResult<&'py PyTime> {
Self::new_bound(
py,
hour,
minute,
second,
microsecond,
tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
)
.map(Bound::into_gil_ref)
}
/// Creates a new `datetime.time` object.
pub fn new_bound<'py>(
py: Python<'py>,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&Bound<'py, PyTzInfo>>,
) -> PyResult<Bound<'py, PyTime>> {
let api = ensure_datetime_api(py)?;
unsafe {
let ptr = (api.Time_FromTime)(
(api.Time_FromTime)(
c_int::from(hour),
c_int::from(minute),
c_int::from(second),
microsecond as c_int,
opt_to_pyobj(tzinfo),
api.TimeType,
);
py.from_owned_ptr_or_err(ptr)
)
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
/// Alternate constructor that takes a `fold` argument. See [`PyDateTime::new_with_fold`].
pub fn new_with_fold<'p>(
py: Python<'p>,
/// Deprecated form of [`PyTime::new_bound_with_fold`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyTime::new_with_fold` will be replaced by `PyTime::new_bound_with_fold` in a future PyO3 version"
)
)]
pub fn new_with_fold<'py>(
py: Python<'py>,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&PyTzInfo>,
tzinfo: Option<&'py PyTzInfo>,
fold: bool,
) -> PyResult<&'p PyTime> {
) -> PyResult<&'py PyTime> {
Self::new_bound_with_fold(
py,
hour,
minute,
second,
microsecond,
tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
fold,
)
.map(Bound::into_gil_ref)
}
/// Alternate constructor that takes a `fold` argument. See [`PyDateTime::new_with_fold`].
pub fn new_bound_with_fold<'py>(
py: Python<'py>,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&Bound<'py, PyTzInfo>>,
fold: bool,
) -> PyResult<Bound<'py, PyTime>> {
let api = ensure_datetime_api(py)?;
unsafe {
let ptr = (api.Time_FromTimeAndFold)(
(api.Time_FromTimeAndFold)(
c_int::from(hour),
c_int::from(minute),
c_int::from(second),
@ -506,8 +680,9 @@ impl PyTime {
opt_to_pyobj(tzinfo),
fold as c_int,
api.TimeType,
);
py.from_owned_ptr_or_err(ptr)
)
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
}
@ -596,20 +771,45 @@ pyobject_native_type!(
#checkfunction=PyTZInfo_Check
);
/// Equivalent to `datetime.timezone.utc`
/// Deprecated form of [`timezone_utc_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`timezone_utc` will be replaced by `timezone_utc_bound` in a future PyO3 version"
)
)]
pub fn timezone_utc(py: Python<'_>) -> &PyTzInfo {
unsafe { &*(expect_datetime_api(py).TimeZone_UTC as *const PyTzInfo) }
timezone_utc_bound(py).into_gil_ref()
}
/// Equivalent to `datetime.timezone.utc`
pub fn timezone_utc_bound(py: Python<'_>) -> Bound<'_, PyTzInfo> {
// TODO: this _could_ have a borrowed form `timezone_utc_borrowed`, but that seems
// like an edge case optimization and we'd prefer in PyO3 0.21 to use `Bound` as
// much as possible
unsafe {
expect_datetime_api(py)
.TimeZone_UTC
.assume_borrowed(py)
.to_owned()
.downcast_into_unchecked()
}
}
/// 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> {
pub(crate) fn timezone_from_offset<'py>(
offset: &Bound<'py, PyDelta>,
) -> PyResult<Bound<'py, PyTzInfo>> {
let py = offset.py();
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)
(api.TimeZone_FromTimeZone)(offset.as_ptr(), ptr::null_mut())
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
@ -625,7 +825,14 @@ pyobject_native_type!(
);
impl PyDelta {
/// Creates a new `timedelta`.
/// Deprecated form of [`PyDelta::new_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyDelta::new` will be replaced by `PyDelta::new_bound` in a future PyO3 version"
)
)]
pub fn new(
py: Python<'_>,
days: i32,
@ -633,16 +840,28 @@ impl PyDelta {
microseconds: i32,
normalize: bool,
) -> PyResult<&PyDelta> {
Self::new_bound(py, days, seconds, microseconds, normalize).map(Bound::into_gil_ref)
}
/// Creates a new `timedelta`.
pub fn new_bound(
py: Python<'_>,
days: i32,
seconds: i32,
microseconds: i32,
normalize: bool,
) -> PyResult<Bound<'_, PyDelta>> {
let api = ensure_datetime_api(py)?;
unsafe {
let ptr = (api.Delta_FromDelta)(
(api.Delta_FromDelta)(
days as c_int,
seconds as c_int,
microseconds as c_int,
normalize as c_int,
api.DeltaType,
);
py.from_owned_ptr_or_err(ptr)
)
.assume_owned_or_err(py)
.downcast_into_unchecked()
}
}
}
@ -677,7 +896,7 @@ impl PyDeltaAccess for Bound<'_, PyDelta> {
// Utility function which returns a borrowed reference to either
// the underlying tzinfo or None.
fn opt_to_pyobj(opt: Option<&PyTzInfo>) -> *mut ffi::PyObject {
fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
match opt {
Some(tzi) => tzi.as_ptr(),
None => unsafe { ffi::Py_None() },
@ -685,6 +904,7 @@ fn opt_to_pyobj(opt: Option<&PyTzInfo>) -> *mut ffi::PyObject {
}
#[cfg(test)]
#[cfg_attr(not(feature = "gil-refs"), allow(deprecated))]
mod tests {
use super::*;
#[cfg(feature = "macros")]
@ -768,7 +988,7 @@ mod tests {
fn test_timezone_from_offset() {
Python::with_gil(|py| {
assert!(
timezone_from_offset(py, PyDelta::new(py, 0, -3600, 0, true).unwrap())
timezone_from_offset(&PyDelta::new_bound(py, 0, -3600, 0, true).unwrap())
.unwrap()
.call_method1("utcoffset", ((),))
.unwrap()
@ -779,7 +999,7 @@ mod tests {
);
assert!(
timezone_from_offset(py, PyDelta::new(py, 0, 3600, 0, true).unwrap())
timezone_from_offset(&PyDelta::new_bound(py, 0, 3600, 0, true).unwrap())
.unwrap()
.call_method1("utcoffset", ((),))
.unwrap()
@ -789,7 +1009,7 @@ mod tests {
.unwrap()
);
timezone_from_offset(py, PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err();
timezone_from_offset(&PyDelta::new_bound(py, 1, 0, 0, true).unwrap()).unwrap_err();
})
}
}

View File

@ -8,10 +8,13 @@ pub use self::capsule::PyCapsule;
#[cfg(not(Py_LIMITED_API))]
pub use self::code::PyCode;
pub use self::complex::PyComplex;
#[allow(deprecated)]
#[cfg(not(Py_LIMITED_API))]
pub use self::datetime::timezone_utc;
#[cfg(not(Py_LIMITED_API))]
pub use self::datetime::{
timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess,
PyTzInfo, PyTzInfoAccess,
timezone_utc_bound, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime,
PyTimeAccess, PyTzInfo, PyTzInfoAccess,
};
pub use self::dict::{IntoPyDict, PyDict};
#[cfg(not(PyPy))]

View File

@ -1,7 +1,7 @@
#![cfg(not(Py_LIMITED_API))]
use pyo3::prelude::*;
use pyo3::types::{timezone_utc, IntoPyDict, PyDate, PyDateTime, PyTime};
use pyo3::types::{timezone_utc_bound, IntoPyDict, PyDate, PyDateTime, PyTime};
use pyo3_ffi::PyDateTime_IMPORT;
fn _get_subclasses<'py>(
@ -118,9 +118,9 @@ fn test_datetime_utc() {
use pyo3::types::PyDateTime;
Python::with_gil(|py| {
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();
let locals = [("dt", dt)].into_py_dict_bound(py);
@ -155,7 +155,7 @@ fn test_pydate_out_of_bounds() {
Python::with_gil(|py| {
for val in INVALID_DATES {
let (year, month, day) = val;
let dt = PyDate::new(py, *year, *month, *day);
let dt = PyDate::new_bound(py, *year, *month, *day);
dt.unwrap_err();
}
});
@ -168,7 +168,7 @@ fn test_pytime_out_of_bounds() {
Python::with_gil(|py| {
for val in INVALID_TIMES {
let (hour, minute, second, microsecond) = val;
let dt = PyTime::new(py, *hour, *minute, *second, *microsecond, None);
let dt = PyTime::new_bound(py, *hour, *minute, *second, *microsecond, None);
dt.unwrap_err();
}
});
@ -192,7 +192,7 @@ fn test_pydatetime_out_of_bounds() {
let (date, time) = val;
let (year, month, day) = date;
let (hour, minute, second, microsecond) = time;
let dt = PyDateTime::new(
let dt = PyDateTime::new_bound(
py,
*year,
*month,

View File

@ -21,6 +21,6 @@ fn test_bad_datetime_module_panic() {
.unwrap();
// This should panic because the "datetime" module is empty
PyDate::new(py, 2018, 1, 1).unwrap();
PyDate::new_bound(py, 2018, 1, 1).unwrap();
});
}