#[cfg(not(Py_LIMITED_API))]
use crate::err::PyErr;
use crate::err::PyResult;
#[cfg(not(Py_3_9))]
use crate::exceptions::PyImportError;
#[cfg(not(Py_LIMITED_API))]
use crate::ffi::{
self, PyDateTime_CAPI, PyDateTime_DATE_GET_FOLD, PyDateTime_DATE_GET_HOUR,
PyDateTime_DATE_GET_MICROSECOND, PyDateTime_DATE_GET_MINUTE, PyDateTime_DATE_GET_SECOND,
PyDateTime_DELTA_GET_DAYS, PyDateTime_DELTA_GET_MICROSECONDS, PyDateTime_DELTA_GET_SECONDS,
PyDateTime_FromTimestamp, PyDateTime_GET_DAY, PyDateTime_GET_MONTH, PyDateTime_GET_YEAR,
PyDateTime_IMPORT, PyDateTime_TIME_GET_FOLD, PyDateTime_TIME_GET_HOUR,
PyDateTime_TIME_GET_MICROSECOND, PyDateTime_TIME_GET_MINUTE, PyDateTime_TIME_GET_SECOND,
PyDate_FromTimestamp,
};
#[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
use crate::ffi::{PyDateTime_DATE_GET_TZINFO, PyDateTime_TIME_GET_TZINFO, Py_IsNone};
#[cfg(Py_LIMITED_API)]
use crate::type_object::PyTypeInfo;
#[cfg(Py_LIMITED_API)]
use crate::types::typeobject::PyTypeMethods;
#[cfg(Py_LIMITED_API)]
use crate::types::IntoPyDict;
use crate::types::{any::PyAnyMethods, PyString, PyType};
#[cfg(not(Py_LIMITED_API))]
use crate::{ffi_ptr_ext::FfiPtrExt, py_result_ext::PyResultExt, types::PyTuple, BoundObject};
use crate::{sync::PyOnceLock, Py};
use crate::{Borrowed, Bound, IntoPyObject, PyAny, Python};
#[cfg(not(Py_LIMITED_API))]
use core::ffi::c_int;
#[cfg(not(Py_LIMITED_API))]
fn ensure_datetime_api(py: Python<'_>) -> PyResult<&'static PyDateTime_CAPI> {
if let Some(api) = unsafe { pyo3_ffi::PyDateTimeAPI().as_ref() } {
Ok(api)
} else {
unsafe {
PyDateTime_IMPORT();
pyo3_ffi::PyDateTimeAPI().as_ref()
}
.ok_or_else(|| PyErr::fetch(py))
}
}
#[cfg(not(Py_LIMITED_API))]
fn expect_datetime_api(py: Python<'_>) -> &'static PyDateTime_CAPI {
ensure_datetime_api(py).expect("failed to import `datetime` C API")
}
#[cfg(not(Py_LIMITED_API))]
macro_rules! ffi_fun_with_autoinit {
($(#[$outer:meta] unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => {
$(
#[$outer]
#[allow(non_snake_case)]
unsafe fn $name($arg: *mut crate::ffi::PyObject) -> $ret {
let _ = ensure_datetime_api(unsafe { Python::assume_attached() });
unsafe { crate::ffi::$name($arg) }
}
)*
};
}
#[cfg(not(Py_LIMITED_API))]
ffi_fun_with_autoinit! {
unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
}
#[cfg(not(Py_LIMITED_API))]
pub trait PyDateAccess {
fn get_year(&self) -> i32;
fn get_month(&self) -> u8;
fn get_day(&self) -> u8;
}
#[cfg(not(Py_LIMITED_API))]
pub trait PyDeltaAccess {
fn get_days(&self) -> i32;
fn get_seconds(&self) -> i32;
fn get_microseconds(&self) -> i32;
}
#[cfg(not(Py_LIMITED_API))]
pub trait PyTimeAccess {
fn get_hour(&self) -> u8;
fn get_minute(&self) -> u8;
fn get_second(&self) -> u8;
fn get_microsecond(&self) -> u32;
fn get_fold(&self) -> bool;
}
pub trait PyTzInfoAccess<'py> {
fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>>;
}
#[repr(transparent)]
pub struct PyDate(PyAny);
#[cfg(not(Py_LIMITED_API))]
pyobject_native_type!(
PyDate,
crate::ffi::PyDateTime_Date,
|py| expect_datetime_api(py).DateType,
"datetime",
"date",
#module=Some("datetime"),
#checkfunction=PyDate_Check
);
pyobject_subclassable_native_type!(PyDate, crate::ffi::PyDateTime_Date);
#[cfg(Py_LIMITED_API)]
pyobject_native_type_core!(
PyDate,
|py| {
static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
TYPE.import(py, "datetime", "date").unwrap().as_type_ptr()
},
"datetime",
"date",
#module=Some("datetime")
);
impl PyDate {
pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
#[cfg(not(Py_LIMITED_API))]
{
let api = ensure_datetime_api(py)?;
unsafe {
(api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
#[cfg(Py_LIMITED_API)]
Ok(Self::type_object(py)
.call((year, month, day), None)?
.cast_into()?)
}
pub fn from_timestamp(py: Python<'_>, timestamp: f64) -> PyResult<Bound<'_, PyDate>> {
#[cfg(not(Py_LIMITED_API))]
{
let time_tuple = PyTuple::new(py, [timestamp])?;
let _api = ensure_datetime_api(py)?;
unsafe {
PyDate_FromTimestamp(time_tuple.as_ptr())
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
#[cfg(Py_LIMITED_API)]
Ok(Self::type_object(py)
.call_method1("fromtimestamp", (timestamp,))?
.cast_into()?)
}
}
#[cfg(not(Py_LIMITED_API))]
impl PyDateAccess for Bound<'_, PyDate> {
fn get_year(&self) -> i32 {
unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
}
fn get_month(&self) -> u8 {
unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
}
fn get_day(&self) -> u8 {
unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
}
}
#[repr(transparent)]
pub struct PyDateTime(PyAny);
#[cfg(not(Py_LIMITED_API))]
pyobject_native_type!(
PyDateTime,
crate::ffi::PyDateTime_DateTime,
|py| expect_datetime_api(py).DateTimeType,
"datetime",
"datetime",
#module=Some("datetime"),
#checkfunction=PyDateTime_Check
);
pyobject_subclassable_native_type!(PyDateTime, crate::ffi::PyDateTime_DateTime);
#[cfg(Py_LIMITED_API)]
pyobject_native_type_core!(
PyDateTime,
|py| {
static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
TYPE.import(py, "datetime", "datetime")
.unwrap()
.as_type_ptr()
},
"datetime",
"datetime",
#module=Some("datetime")
);
impl PyDateTime {
#[allow(clippy::too_many_arguments)]
pub fn new<'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>> {
#[cfg(not(Py_LIMITED_API))]
{
let api = ensure_datetime_api(py)?;
unsafe {
(api.DateTime_FromDateAndTime)(
year,
c_int::from(month),
c_int::from(day),
c_int::from(hour),
c_int::from(minute),
c_int::from(second),
microsecond as c_int,
opt_to_pyobj(tzinfo),
api.DateTimeType,
)
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
#[cfg(Py_LIMITED_API)]
Ok(Self::type_object(py)
.call(
(year, month, day, hour, minute, second, microsecond, tzinfo),
None,
)?
.cast_into()?)
}
#[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<&Bound<'py, PyTzInfo>>,
fold: bool,
) -> PyResult<Bound<'py, PyDateTime>> {
#[cfg(not(Py_LIMITED_API))]
{
let api = ensure_datetime_api(py)?;
unsafe {
(api.DateTime_FromDateAndTimeAndFold)(
year,
c_int::from(month),
c_int::from(day),
c_int::from(hour),
c_int::from(minute),
c_int::from(second),
microsecond as c_int,
opt_to_pyobj(tzinfo),
c_int::from(fold),
api.DateTimeType,
)
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
#[cfg(Py_LIMITED_API)]
Ok(Self::type_object(py)
.call(
(year, month, day, hour, minute, second, microsecond, tzinfo),
Some(&[("fold", fold)].into_py_dict(py)?),
)?
.cast_into()?)
}
pub fn from_timestamp<'py>(
py: Python<'py>,
timestamp: f64,
tzinfo: Option<&Bound<'py, PyTzInfo>>,
) -> PyResult<Bound<'py, PyDateTime>> {
#[cfg(not(Py_LIMITED_API))]
{
let args = (timestamp, tzinfo).into_pyobject(py)?;
let _api = ensure_datetime_api(py)?;
unsafe {
PyDateTime_FromTimestamp(args.as_ptr())
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
#[cfg(Py_LIMITED_API)]
Ok(Self::type_object(py)
.call_method1("fromtimestamp", (timestamp, tzinfo))?
.cast_into()?)
}
}
#[cfg(not(Py_LIMITED_API))]
impl PyDateAccess for Bound<'_, PyDateTime> {
fn get_year(&self) -> i32 {
unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
}
fn get_month(&self) -> u8 {
unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
}
fn get_day(&self) -> u8 {
unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
}
}
#[cfg(not(Py_LIMITED_API))]
impl PyTimeAccess for Bound<'_, PyDateTime> {
fn get_hour(&self) -> u8 {
unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u8 }
}
fn get_minute(&self) -> u8 {
unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u8 }
}
fn get_second(&self) -> u8 {
unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u8 }
}
fn get_microsecond(&self) -> u32 {
unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 }
}
fn get_fold(&self) -> bool {
unsafe { PyDateTime_DATE_GET_FOLD(self.as_ptr()) > 0 }
}
}
impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyDateTime> {
fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
#[cfg(all(not(Py_3_10), not(Py_LIMITED_API)))]
unsafe {
let ptr = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
if (*ptr).hastzinfo != 0 {
Some(
(*ptr)
.tzinfo
.assume_borrowed(self.py())
.to_owned()
.cast_into_unchecked(),
)
} else {
None
}
}
#[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
unsafe {
let res = PyDateTime_DATE_GET_TZINFO(self.as_ptr());
if Py_IsNone(res) == 1 {
None
} else {
Some(
res.assume_borrowed(self.py())
.to_owned()
.cast_into_unchecked(),
)
}
}
#[cfg(Py_LIMITED_API)]
unsafe {
let tzinfo = self.getattr(intern!(self.py(), "tzinfo")).ok()?;
if tzinfo.is_none() {
None
} else {
Some(tzinfo.cast_into_unchecked())
}
}
}
}
#[repr(transparent)]
pub struct PyTime(PyAny);
#[cfg(not(Py_LIMITED_API))]
pyobject_native_type!(
PyTime,
crate::ffi::PyDateTime_Time,
|py| expect_datetime_api(py).TimeType,
"datetime",
"time",
#module=Some("datetime"),
#checkfunction=PyTime_Check
);
pyobject_subclassable_native_type!(PyTime, crate::ffi::PyDateTime_Time);
#[cfg(Py_LIMITED_API)]
pyobject_native_type_core!(
PyTime,
|py| {
static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
TYPE.import(py, "datetime", "time").unwrap().as_type_ptr()
},
"datetime",
"time",
#module=Some("datetime")
);
impl PyTime {
pub fn new<'py>(
py: Python<'py>,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
tzinfo: Option<&Bound<'py, PyTzInfo>>,
) -> PyResult<Bound<'py, PyTime>> {
#[cfg(not(Py_LIMITED_API))]
{
let api = ensure_datetime_api(py)?;
unsafe {
(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,
)
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
#[cfg(Py_LIMITED_API)]
Ok(Self::type_object(py)
.call((hour, minute, second, microsecond, tzinfo), None)?
.cast_into()?)
}
pub fn new_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>> {
#[cfg(not(Py_LIMITED_API))]
{
let api = ensure_datetime_api(py)?;
unsafe {
(api.Time_FromTimeAndFold)(
c_int::from(hour),
c_int::from(minute),
c_int::from(second),
microsecond as c_int,
opt_to_pyobj(tzinfo),
fold as c_int,
api.TimeType,
)
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
#[cfg(Py_LIMITED_API)]
Ok(Self::type_object(py)
.call(
(hour, minute, second, microsecond, tzinfo),
Some(&[("fold", fold)].into_py_dict(py)?),
)?
.cast_into()?)
}
}
#[cfg(not(Py_LIMITED_API))]
impl PyTimeAccess for Bound<'_, PyTime> {
fn get_hour(&self) -> u8 {
unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 }
}
fn get_minute(&self) -> u8 {
unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 }
}
fn get_second(&self) -> u8 {
unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 }
}
fn get_microsecond(&self) -> u32 {
unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 }
}
fn get_fold(&self) -> bool {
unsafe { PyDateTime_TIME_GET_FOLD(self.as_ptr()) != 0 }
}
}
impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyTime> {
fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
#[cfg(all(not(Py_3_10), not(Py_LIMITED_API)))]
unsafe {
let ptr = self.as_ptr() as *mut ffi::PyDateTime_Time;
if (*ptr).hastzinfo != 0 {
Some(
(*ptr)
.tzinfo
.assume_borrowed(self.py())
.to_owned()
.cast_into_unchecked(),
)
} else {
None
}
}
#[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
unsafe {
let res = PyDateTime_TIME_GET_TZINFO(self.as_ptr());
if Py_IsNone(res) == 1 {
None
} else {
Some(
res.assume_borrowed(self.py())
.to_owned()
.cast_into_unchecked(),
)
}
}
#[cfg(Py_LIMITED_API)]
unsafe {
let tzinfo = self.getattr(intern!(self.py(), "tzinfo")).ok()?;
if tzinfo.is_none() {
None
} else {
Some(tzinfo.cast_into_unchecked())
}
}
}
}
#[repr(transparent)]
pub struct PyTzInfo(PyAny);
#[cfg(not(Py_LIMITED_API))]
pyobject_native_type!(
PyTzInfo,
crate::ffi::PyObject,
|py| expect_datetime_api(py).TZInfoType,
"datetime",
"tzinfo",
#module=Some("datetime"),
#checkfunction=PyTZInfo_Check
);
pyobject_subclassable_native_type!(PyTzInfo, crate::ffi::PyObject);
#[cfg(Py_LIMITED_API)]
pyobject_native_type_core!(
PyTzInfo,
|py| {
static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
TYPE.import(py, "datetime", "tzinfo").unwrap().as_type_ptr()
},
"datetime",
"tzinfo",
#module=Some("datetime")
);
impl PyTzInfo {
pub fn utc(py: Python<'_>) -> PyResult<Borrowed<'static, '_, PyTzInfo>> {
#[cfg(not(Py_LIMITED_API))]
unsafe {
Ok(ensure_datetime_api(py)?
.TimeZone_UTC
.assume_borrowed(py)
.cast_unchecked())
}
#[cfg(Py_LIMITED_API)]
{
static UTC: PyOnceLock<Py<PyTzInfo>> = PyOnceLock::new();
UTC.get_or_try_init(py, || {
Ok(py
.import("datetime")?
.getattr("timezone")?
.getattr("utc")?
.cast_into()?
.unbind())
})
.map(|utc| utc.bind_borrowed(py))
}
}
pub fn timezone<'py, T>(py: Python<'py>, iana_name: T) -> PyResult<Bound<'py, PyTzInfo>>
where
T: IntoPyObject<'py, Target = PyString>,
{
static ZONE_INFO: PyOnceLock<Py<PyType>> = PyOnceLock::new();
let zoneinfo = ZONE_INFO.import(py, "zoneinfo", "ZoneInfo");
#[cfg(not(Py_3_9))]
let zoneinfo = zoneinfo
.or_else(|_| ZONE_INFO.import(py, "backports.zoneinfo", "ZoneInfo"))
.map_err(|_| PyImportError::new_err("Could not import \"backports.zoneinfo.ZoneInfo\". ZoneInfo is required when converting timezone-aware DateTime's. Please install \"backports.zoneinfo\" on python < 3.9"));
zoneinfo?
.call1((iana_name,))?
.cast_into()
.map_err(Into::into)
}
pub fn fixed_offset<'py, T>(py: Python<'py>, offset: T) -> PyResult<Bound<'py, PyTzInfo>>
where
T: IntoPyObject<'py, Target = PyDelta>,
{
#[cfg(not(Py_LIMITED_API))]
{
let api = ensure_datetime_api(py)?;
let delta = offset.into_pyobject(py).map_err(Into::into)?;
unsafe {
(api.TimeZone_FromTimeZone)(delta.as_ptr(), core::ptr::null_mut())
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
#[cfg(Py_LIMITED_API)]
{
static TIMEZONE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
Ok(TIMEZONE
.import(py, "datetime", "timezone")?
.call1((offset,))?
.cast_into()?)
}
}
}
#[repr(transparent)]
pub struct PyDelta(PyAny);
#[cfg(not(Py_LIMITED_API))]
pyobject_native_type!(
PyDelta,
crate::ffi::PyDateTime_Delta,
|py| expect_datetime_api(py).DeltaType,
"datetime",
"timedelta",
#module=Some("datetime"),
#checkfunction=PyDelta_Check
);
pyobject_subclassable_native_type!(PyDelta, crate::ffi::PyDateTime_Delta);
#[cfg(Py_LIMITED_API)]
pyobject_native_type_core!(
PyDelta,
|py| {
static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
TYPE.import(py, "datetime", "timedelta")
.unwrap()
.as_type_ptr()
},
"datetime",
"timedelta",
#module=Some("datetime")
);
impl PyDelta {
pub fn new(
py: Python<'_>,
days: i32,
seconds: i32,
microseconds: i32,
normalize: bool,
) -> PyResult<Bound<'_, PyDelta>> {
#[cfg(not(Py_LIMITED_API))]
{
let api = ensure_datetime_api(py)?;
unsafe {
(api.Delta_FromDelta)(
days as c_int,
seconds as c_int,
microseconds as c_int,
normalize as c_int,
api.DeltaType,
)
.assume_owned_or_err(py)
.cast_into_unchecked()
}
}
#[cfg(Py_LIMITED_API)]
let _ = normalize;
#[cfg(Py_LIMITED_API)]
Ok(Self::type_object(py)
.call1((days, seconds, microseconds))?
.cast_into()?)
}
}
#[cfg(not(Py_LIMITED_API))]
impl PyDeltaAccess for Bound<'_, PyDelta> {
fn get_days(&self) -> i32 {
unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
}
fn get_seconds(&self) -> i32 {
unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
}
fn get_microseconds(&self) -> i32 {
unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
}
}
#[cfg(not(Py_LIMITED_API))]
fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
match opt {
Some(tzi) => tzi.as_ptr(),
None => unsafe { ffi::Py_None() },
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(Py_LIMITED_API))]
use crate::ffi::PyDateTime_IMPORT;
#[cfg(feature = "macros")]
use crate::py_run;
use crate::types::{IntoPyDict, PyDate, PyDateTime, PyTime, PyTzInfo};
use alloc::ffi::CString;
use core::iter;
use assert_approx_eq::assert_approx_eq;
#[test]
#[cfg(feature = "macros")]
#[cfg_attr(target_arch = "wasm32", ignore)] fn test_datetime_fromtimestamp() {
Python::attach(|py| {
let dt = PyDateTime::from_timestamp(py, 100.0, None).unwrap();
py_run!(
py,
dt,
"import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
);
let utc = PyTzInfo::utc(py).unwrap();
let dt = PyDateTime::from_timestamp(py, 100.0, Some(&utc)).unwrap();
py_run!(
py,
dt,
"import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
);
})
}
#[test]
#[cfg(feature = "macros")]
#[cfg_attr(target_arch = "wasm32", ignore)] fn test_date_fromtimestamp() {
Python::attach(|py| {
let dt = PyDate::from_timestamp(py, 100.).unwrap();
py_run!(
py,
dt,
"import datetime; assert dt == datetime.date.fromtimestamp(100)"
);
})
}
#[test]
#[cfg(not(Py_LIMITED_API))]
#[cfg_attr(target_arch = "wasm32", ignore)] fn test_new_with_fold() {
Python::attach(|py| {
let a = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
let b = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
assert!(!a.unwrap().get_fold());
assert!(b.unwrap().get_fold());
});
}
#[test]
#[cfg_attr(target_arch = "wasm32", ignore)] fn test_get_tzinfo() {
crate::Python::attach(|py| {
let utc = PyTzInfo::utc(py).unwrap();
let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
assert!(dt.get_tzinfo().unwrap().eq(utc).unwrap());
let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
assert!(dt.get_tzinfo().is_none());
let t = PyTime::new(py, 0, 0, 0, 0, Some(&utc)).unwrap();
assert!(t.get_tzinfo().unwrap().eq(utc).unwrap());
let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
assert!(t.get_tzinfo().is_none());
});
}
#[test]
#[cfg(all(feature = "macros", feature = "chrono"))]
#[cfg_attr(target_arch = "wasm32", ignore)] fn test_timezone_from_offset() {
use crate::types::PyNone;
Python::attach(|py| {
assert!(
PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, -3600, 0, true).unwrap())
.unwrap()
.call_method1("utcoffset", (PyNone::get(py),))
.unwrap()
.cast_into::<PyDelta>()
.unwrap()
.eq(PyDelta::new(py, 0, -3600, 0, true).unwrap())
.unwrap()
);
assert!(
PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, 3600, 0, true).unwrap())
.unwrap()
.call_method1("utcoffset", (PyNone::get(py),))
.unwrap()
.cast_into::<PyDelta>()
.unwrap()
.eq(PyDelta::new(py, 0, 3600, 0, true).unwrap())
.unwrap()
);
PyTzInfo::fixed_offset(py, PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err();
})
}
fn _get_subclasses<'py>(
py: Python<'py>,
py_type: &str,
args: &str,
) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>)> {
let datetime = py.import("datetime")?;
let locals = [(py_type, datetime.getattr(py_type)?)]
.into_py_dict(py)
.unwrap();
let make_subclass_py = CString::new(format!("class Subklass({py_type}):\n pass"))?;
let make_sub_subclass_py = c"class SubSubklass(Subklass):\n pass";
py.run(&make_subclass_py, None, Some(&locals))?;
py.run(make_sub_subclass_py, None, Some(&locals))?;
let obj = py.eval(
&CString::new(format!("{py_type}({args})"))?,
None,
Some(&locals),
)?;
let sub_obj = py.eval(
&CString::new(format!("Subklass({args})"))?,
None,
Some(&locals),
)?;
let sub_sub_obj = py.eval(
&CString::new(format!("SubSubklass({args})"))?,
None,
Some(&locals),
)?;
Ok((obj, sub_obj, sub_sub_obj))
}
#[cfg(not(Py_LIMITED_API))]
macro_rules! assert_check_exact {
($check_func:ident, $check_func_exact:ident, $obj: expr) => {
unsafe {
use crate::ffi::*;
assert_ne!($check_func(($obj).as_ptr()), 0);
assert_ne!($check_func_exact(($obj).as_ptr()), 0);
}
};
}
#[cfg(not(Py_LIMITED_API))]
macro_rules! assert_check_only {
($check_func:ident, $check_func_exact:ident, $obj: expr) => {
unsafe {
use crate::ffi::*;
assert_ne!($check_func(($obj).as_ptr()), 0);
assert_eq!($check_func_exact(($obj).as_ptr()), 0);
}
};
}
#[test]
#[cfg(not(Py_LIMITED_API))]
fn test_date_check() {
Python::attach(|py| {
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "date", "2018, 1, 1").unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_exact!(PyDate_Check, PyDate_CheckExact, obj);
assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_obj);
assert_check_only!(PyDate_Check, PyDate_CheckExact, sub_sub_obj);
assert!(obj.is_instance_of::<PyDate>());
assert!(!obj.is_instance_of::<PyTime>());
assert!(!obj.is_instance_of::<PyDateTime>());
});
}
#[test]
#[cfg(not(Py_LIMITED_API))]
fn test_time_check() {
Python::attach(|py| {
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "time", "12, 30, 15").unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_exact!(PyTime_Check, PyTime_CheckExact, obj);
assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_obj);
assert_check_only!(PyTime_Check, PyTime_CheckExact, sub_sub_obj);
assert!(!obj.is_instance_of::<PyDate>());
assert!(obj.is_instance_of::<PyTime>());
assert!(!obj.is_instance_of::<PyDateTime>());
});
}
#[test]
#[cfg(not(Py_LIMITED_API))]
fn test_datetime_check() {
Python::attach(|py| {
let (obj, sub_obj, sub_sub_obj) =
_get_subclasses(py, "datetime", "2018, 1, 1, 13, 30, 15")
.map_err(|e| e.display(py))
.unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_only!(PyDate_Check, PyDate_CheckExact, obj);
assert_check_exact!(PyDateTime_Check, PyDateTime_CheckExact, obj);
assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_obj);
assert_check_only!(PyDateTime_Check, PyDateTime_CheckExact, sub_sub_obj);
assert!(obj.is_instance_of::<PyDate>());
assert!(!obj.is_instance_of::<PyTime>());
assert!(obj.is_instance_of::<PyDateTime>());
});
}
#[test]
#[cfg(not(Py_LIMITED_API))]
fn test_delta_check() {
Python::attach(|py| {
let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "timedelta", "1, -3").unwrap();
unsafe { PyDateTime_IMPORT() }
assert_check_exact!(PyDelta_Check, PyDelta_CheckExact, obj);
assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_obj);
assert_check_only!(PyDelta_Check, PyDelta_CheckExact, sub_sub_obj);
});
}
#[test]
fn test_datetime_utc() {
Python::attach(|py| {
let utc = PyTzInfo::utc(py).unwrap();
let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
let locals = [("dt", dt)].into_py_dict(py).unwrap();
let offset: f32 = py
.eval(c"dt.utcoffset().total_seconds()", None, Some(&locals))
.unwrap()
.extract()
.unwrap();
assert_approx_eq!(offset, 0f32);
});
}
static INVALID_DATES: &[(i32, u8, u8)] = &[
(-1, 1, 1),
(0, 1, 1),
(10000, 1, 1),
(2 << 30, 1, 1),
(2018, 0, 1),
(2018, 13, 1),
(2018, 1, 0),
(2017, 2, 29),
(2018, 1, 32),
];
static INVALID_TIMES: &[(u8, u8, u8, u32)] =
&[(25, 0, 0, 0), (255, 0, 0, 0), (0, 60, 0, 0), (0, 0, 61, 0)];
#[test]
fn test_pydate_out_of_bounds() {
Python::attach(|py| {
for val in INVALID_DATES {
let (year, month, day) = val;
let dt = PyDate::new(py, *year, *month, *day);
dt.unwrap_err();
}
});
}
#[test]
fn test_pytime_out_of_bounds() {
Python::attach(|py| {
for val in INVALID_TIMES {
let (hour, minute, second, microsecond) = val;
let dt = PyTime::new(py, *hour, *minute, *second, *microsecond, None);
dt.unwrap_err();
}
});
}
#[test]
fn test_pydatetime_out_of_bounds() {
Python::attach(|py| {
let valid_time = (0, 0, 0, 0);
let valid_date = (2018, 1, 1);
let invalid_dates = INVALID_DATES.iter().zip(iter::repeat(&valid_time));
let invalid_times = iter::repeat(&valid_date).zip(INVALID_TIMES.iter());
let vals = invalid_dates.chain(invalid_times);
for val in vals {
let (date, time) = val;
let (year, month, day) = date;
let (hour, minute, second, microsecond) = time;
let dt = PyDateTime::new(
py,
*year,
*month,
*day,
*hour,
*minute,
*second,
*microsecond,
None,
);
dt.unwrap_err();
}
});
}
}