pyo3/types/
datetime.rs

1//! Safe Rust wrappers for types defined in the Python `datetime` library
2//!
3//! For more details about these types, see the [Python
4//! documentation](https://docs.python.org/3/library/datetime.html)
5
6use crate::err::PyResult;
7use crate::ffi::{
8    self, PyDateTime_CAPI, PyDateTime_FromTimestamp, PyDateTime_IMPORT, PyDate_FromTimestamp,
9};
10use crate::ffi::{
11    PyDateTime_DATE_GET_FOLD, PyDateTime_DATE_GET_HOUR, PyDateTime_DATE_GET_MICROSECOND,
12    PyDateTime_DATE_GET_MINUTE, PyDateTime_DATE_GET_SECOND,
13};
14#[cfg(GraalPy)]
15use crate::ffi::{PyDateTime_DATE_GET_TZINFO, PyDateTime_TIME_GET_TZINFO, Py_IsNone};
16use crate::ffi::{
17    PyDateTime_DELTA_GET_DAYS, PyDateTime_DELTA_GET_MICROSECONDS, PyDateTime_DELTA_GET_SECONDS,
18};
19use crate::ffi::{PyDateTime_GET_DAY, PyDateTime_GET_MONTH, PyDateTime_GET_YEAR};
20use crate::ffi::{
21    PyDateTime_TIME_GET_FOLD, PyDateTime_TIME_GET_HOUR, PyDateTime_TIME_GET_MICROSECOND,
22    PyDateTime_TIME_GET_MINUTE, PyDateTime_TIME_GET_SECOND,
23};
24use crate::ffi_ptr_ext::FfiPtrExt;
25#[cfg(feature = "gil-refs")]
26use crate::instance::PyNativeType;
27use crate::py_result_ext::PyResultExt;
28use crate::types::any::PyAnyMethods;
29use crate::types::PyTuple;
30use crate::{Bound, IntoPy, Py, PyAny, PyErr, Python};
31use std::os::raw::c_int;
32#[cfg(feature = "chrono")]
33use std::ptr;
34
35fn ensure_datetime_api(py: Python<'_>) -> PyResult<&'static PyDateTime_CAPI> {
36    if let Some(api) = unsafe { pyo3_ffi::PyDateTimeAPI().as_ref() } {
37        Ok(api)
38    } else {
39        unsafe {
40            PyDateTime_IMPORT();
41            pyo3_ffi::PyDateTimeAPI().as_ref()
42        }
43        .ok_or_else(|| PyErr::fetch(py))
44    }
45}
46
47fn expect_datetime_api(py: Python<'_>) -> &'static PyDateTime_CAPI {
48    ensure_datetime_api(py).expect("failed to import `datetime` C API")
49}
50
51// Type Check macros
52//
53// These are bindings around the C API typecheck macros, all of them return
54// `1` if True and `0` if False. In all type check macros, the argument (`op`)
55// must not be `NULL`. The implementations here all call ensure_datetime_api
56// to ensure that the PyDateTimeAPI is initialized before use
57//
58//
59// # Safety
60//
61// These functions must only be called when the GIL is held!
62
63macro_rules! ffi_fun_with_autoinit {
64    ($(#[$outer:meta] unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => {
65        $(
66            #[$outer]
67            #[allow(non_snake_case)]
68            /// # Safety
69            ///
70            /// Must only be called while the GIL is held
71            unsafe fn $name($arg: *mut crate::ffi::PyObject) -> $ret {
72
73                let _ = ensure_datetime_api(Python::assume_gil_acquired());
74                crate::ffi::$name($arg)
75            }
76        )*
77
78
79    };
80}
81
82ffi_fun_with_autoinit! {
83    /// Check if `op` is a `PyDateTimeAPI.DateType` or subtype.
84    unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
85
86    /// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype.
87    unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
88
89    /// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype.
90    unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
91
92    /// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype.
93    unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
94
95    /// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype.
96    unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
97}
98
99// Access traits
100
101/// Trait for accessing the date components of a struct containing a date.
102pub trait PyDateAccess {
103    /// Returns the year, as a positive int.
104    ///
105    /// Implementations should conform to the upstream documentation:
106    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_GET_YEAR>
107    fn get_year(&self) -> i32;
108    /// Returns the month, as an int from 1 through 12.
109    ///
110    /// Implementations should conform to the upstream documentation:
111    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_GET_MONTH>
112    fn get_month(&self) -> u8;
113    /// Returns the day, as an int from 1 through 31.
114    ///
115    /// Implementations should conform to the upstream documentation:
116    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_GET_DAY>
117    fn get_day(&self) -> u8;
118}
119
120/// Trait for accessing the components of a struct containing a timedelta.
121///
122/// Note: These access the individual components of a (day, second,
123/// microsecond) representation of the delta, they are *not* intended as
124/// aliases for calculating the total duration in each of these units.
125pub trait PyDeltaAccess {
126    /// Returns the number of days, as an int from -999999999 to 999999999.
127    ///
128    /// Implementations should conform to the upstream documentation:
129    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DELTA_GET_DAYS>
130    fn get_days(&self) -> i32;
131    /// Returns the number of seconds, as an int from 0 through 86399.
132    ///
133    /// Implementations should conform to the upstream documentation:
134    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DELTA_GET_DAYS>
135    fn get_seconds(&self) -> i32;
136    /// Returns the number of microseconds, as an int from 0 through 999999.
137    ///
138    /// Implementations should conform to the upstream documentation:
139    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DELTA_GET_DAYS>
140    fn get_microseconds(&self) -> i32;
141}
142
143/// Trait for accessing the time components of a struct containing a time.
144pub trait PyTimeAccess {
145    /// Returns the hour, as an int from 0 through 23.
146    ///
147    /// Implementations should conform to the upstream documentation:
148    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_HOUR>
149    fn get_hour(&self) -> u8;
150    /// Returns the minute, as an int from 0 through 59.
151    ///
152    /// Implementations should conform to the upstream documentation:
153    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_MINUTE>
154    fn get_minute(&self) -> u8;
155    /// Returns the second, as an int from 0 through 59.
156    ///
157    /// Implementations should conform to the upstream documentation:
158    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_SECOND>
159    fn get_second(&self) -> u8;
160    /// Returns the microsecond, as an int from 0 through 999999.
161    ///
162    /// Implementations should conform to the upstream documentation:
163    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_MICROSECOND>
164    fn get_microsecond(&self) -> u32;
165    /// Returns whether this date is the later of two moments with the
166    /// same representation, during a repeated interval.
167    ///
168    /// This typically occurs at the end of daylight savings time. Only valid if the
169    /// represented time is ambiguous.
170    /// See [PEP 495](https://www.python.org/dev/peps/pep-0495/) for more detail.
171    fn get_fold(&self) -> bool;
172}
173
174/// Trait for accessing the components of a struct containing a tzinfo.
175pub trait PyTzInfoAccess<'py> {
176    /// Deprecated form of `get_tzinfo_bound`.
177    #[cfg(feature = "gil-refs")]
178    #[deprecated(
179        since = "0.21.0",
180        note = "`get_tzinfo` will be replaced by `get_tzinfo_bound` in a future PyO3 version"
181    )]
182    fn get_tzinfo(&self) -> Option<&'py PyTzInfo> {
183        self.get_tzinfo_bound().map(Bound::into_gil_ref)
184    }
185
186    /// Returns the tzinfo (which may be None).
187    ///
188    /// Implementations should conform to the upstream documentation:
189    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_TZINFO>
190    /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_TIME_GET_TZINFO>
191    fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>>;
192}
193
194/// Bindings around `datetime.date`.
195///
196/// Values of this type are accessed via PyO3's smart pointers, e.g. as
197/// [`Py<PyDate>`][crate::Py] or [`Bound<'py, PyDate>`][Bound].
198#[repr(transparent)]
199pub struct PyDate(PyAny);
200pyobject_native_type!(
201    PyDate,
202    crate::ffi::PyDateTime_Date,
203    |py| expect_datetime_api(py).DateType,
204    #module=Some("datetime"),
205    #checkfunction=PyDate_Check
206);
207
208impl PyDate {
209    /// Deprecated form of [`PyDate::new_bound`].
210    #[cfg(feature = "gil-refs")]
211    #[deprecated(
212        since = "0.21.0",
213        note = "`PyDate::new` will be replaced by `PyDate::new_bound` in a future PyO3 version"
214    )]
215    pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate> {
216        Self::new_bound(py, year, month, day).map(Bound::into_gil_ref)
217    }
218
219    /// Creates a new `datetime.date`.
220    pub fn new_bound(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
221        let api = ensure_datetime_api(py)?;
222        unsafe {
223            (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
224                .assume_owned_or_err(py)
225                .downcast_into_unchecked()
226        }
227    }
228
229    /// Deprecated form of [`PyDate::from_timestamp_bound`].
230    #[cfg(feature = "gil-refs")]
231    #[deprecated(
232        since = "0.21.0",
233        note = "`PyDate::from_timestamp` will be replaced by `PyDate::from_timestamp_bound` in a future PyO3 version"
234    )]
235    pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> {
236        Self::from_timestamp_bound(py, timestamp).map(Bound::into_gil_ref)
237    }
238
239    /// Construct a `datetime.date` from a POSIX timestamp
240    ///
241    /// This is equivalent to `datetime.date.fromtimestamp`
242    pub fn from_timestamp_bound(py: Python<'_>, timestamp: i64) -> PyResult<Bound<'_, PyDate>> {
243        let time_tuple = PyTuple::new_bound(py, [timestamp]);
244
245        // safety ensure that the API is loaded
246        let _api = ensure_datetime_api(py)?;
247
248        unsafe {
249            PyDate_FromTimestamp(time_tuple.as_ptr())
250                .assume_owned_or_err(py)
251                .downcast_into_unchecked()
252        }
253    }
254}
255
256#[cfg(feature = "gil-refs")]
257impl PyDateAccess for PyDate {
258    fn get_year(&self) -> i32 {
259        self.as_borrowed().get_year()
260    }
261
262    fn get_month(&self) -> u8 {
263        self.as_borrowed().get_month()
264    }
265
266    fn get_day(&self) -> u8 {
267        self.as_borrowed().get_day()
268    }
269}
270
271impl PyDateAccess for Bound<'_, PyDate> {
272    fn get_year(&self) -> i32 {
273        unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
274    }
275
276    fn get_month(&self) -> u8 {
277        unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
278    }
279
280    fn get_day(&self) -> u8 {
281        unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
282    }
283}
284
285/// Bindings for `datetime.datetime`.
286///
287/// Values of this type are accessed via PyO3's smart pointers, e.g. as
288/// [`Py<PyDateTime>`][crate::Py] or [`Bound<'py, PyDateTime>`][Bound].
289#[repr(transparent)]
290pub struct PyDateTime(PyAny);
291pyobject_native_type!(
292    PyDateTime,
293    crate::ffi::PyDateTime_DateTime,
294    |py| expect_datetime_api(py).DateTimeType,
295    #module=Some("datetime"),
296    #checkfunction=PyDateTime_Check
297);
298
299impl PyDateTime {
300    /// Deprecated form of [`PyDateTime::new_bound`].
301    #[cfg(feature = "gil-refs")]
302    #[deprecated(
303        since = "0.21.0",
304        note = "`PyDateTime::new` will be replaced by `PyDateTime::new_bound` in a future PyO3 version"
305    )]
306    #[allow(clippy::too_many_arguments)]
307    pub fn new<'py>(
308        py: Python<'py>,
309        year: i32,
310        month: u8,
311        day: u8,
312        hour: u8,
313        minute: u8,
314        second: u8,
315        microsecond: u32,
316        tzinfo: Option<&'py PyTzInfo>,
317    ) -> PyResult<&'py PyDateTime> {
318        Self::new_bound(
319            py,
320            year,
321            month,
322            day,
323            hour,
324            minute,
325            second,
326            microsecond,
327            tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
328        )
329        .map(Bound::into_gil_ref)
330    }
331
332    /// Creates a new `datetime.datetime` object.
333    #[allow(clippy::too_many_arguments)]
334    pub fn new_bound<'py>(
335        py: Python<'py>,
336        year: i32,
337        month: u8,
338        day: u8,
339        hour: u8,
340        minute: u8,
341        second: u8,
342        microsecond: u32,
343        tzinfo: Option<&Bound<'py, PyTzInfo>>,
344    ) -> PyResult<Bound<'py, PyDateTime>> {
345        let api = ensure_datetime_api(py)?;
346        unsafe {
347            (api.DateTime_FromDateAndTime)(
348                year,
349                c_int::from(month),
350                c_int::from(day),
351                c_int::from(hour),
352                c_int::from(minute),
353                c_int::from(second),
354                microsecond as c_int,
355                opt_to_pyobj(tzinfo),
356                api.DateTimeType,
357            )
358            .assume_owned_or_err(py)
359            .downcast_into_unchecked()
360        }
361    }
362
363    /// Deprecated form of [`PyDateTime::new_bound_with_fold`].
364    #[cfg(feature = "gil-refs")]
365    #[deprecated(
366        since = "0.21.0",
367        note = "`PyDateTime::new_with_fold` will be replaced by `PyDateTime::new_bound_with_fold` in a future PyO3 version"
368    )]
369    #[allow(clippy::too_many_arguments)]
370    pub fn new_with_fold<'py>(
371        py: Python<'py>,
372        year: i32,
373        month: u8,
374        day: u8,
375        hour: u8,
376        minute: u8,
377        second: u8,
378        microsecond: u32,
379        tzinfo: Option<&'py PyTzInfo>,
380        fold: bool,
381    ) -> PyResult<&'py PyDateTime> {
382        Self::new_bound_with_fold(
383            py,
384            year,
385            month,
386            day,
387            hour,
388            minute,
389            second,
390            microsecond,
391            tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
392            fold,
393        )
394        .map(Bound::into_gil_ref)
395    }
396
397    /// Alternate constructor that takes a `fold` parameter. A `true` value for this parameter
398    /// signifies this this datetime is the later of two moments with the same representation,
399    /// during a repeated interval.
400    ///
401    /// This typically occurs at the end of daylight savings time. Only valid if the
402    /// represented time is ambiguous.
403    /// See [PEP 495](https://www.python.org/dev/peps/pep-0495/) for more detail.
404    #[allow(clippy::too_many_arguments)]
405    pub fn new_bound_with_fold<'py>(
406        py: Python<'py>,
407        year: i32,
408        month: u8,
409        day: u8,
410        hour: u8,
411        minute: u8,
412        second: u8,
413        microsecond: u32,
414        tzinfo: Option<&Bound<'py, PyTzInfo>>,
415        fold: bool,
416    ) -> PyResult<Bound<'py, PyDateTime>> {
417        let api = ensure_datetime_api(py)?;
418        unsafe {
419            (api.DateTime_FromDateAndTimeAndFold)(
420                year,
421                c_int::from(month),
422                c_int::from(day),
423                c_int::from(hour),
424                c_int::from(minute),
425                c_int::from(second),
426                microsecond as c_int,
427                opt_to_pyobj(tzinfo),
428                c_int::from(fold),
429                api.DateTimeType,
430            )
431            .assume_owned_or_err(py)
432            .downcast_into_unchecked()
433        }
434    }
435
436    /// Deprecated form of [`PyDateTime::from_timestamp_bound`].
437    #[cfg(feature = "gil-refs")]
438    #[deprecated(
439        since = "0.21.0",
440        note = "`PyDateTime::from_timestamp` will be replaced by `PyDateTime::from_timestamp_bound` in a future PyO3 version"
441    )]
442    pub fn from_timestamp<'py>(
443        py: Python<'py>,
444        timestamp: f64,
445        tzinfo: Option<&'py PyTzInfo>,
446    ) -> PyResult<&'py PyDateTime> {
447        Self::from_timestamp_bound(py, timestamp, tzinfo.map(PyTzInfo::as_borrowed).as_deref())
448            .map(Bound::into_gil_ref)
449    }
450
451    /// Construct a `datetime` object from a POSIX timestamp
452    ///
453    /// This is equivalent to `datetime.datetime.fromtimestamp`
454    pub fn from_timestamp_bound<'py>(
455        py: Python<'py>,
456        timestamp: f64,
457        tzinfo: Option<&Bound<'py, PyTzInfo>>,
458    ) -> PyResult<Bound<'py, PyDateTime>> {
459        let args = IntoPy::<Py<PyTuple>>::into_py((timestamp, tzinfo), py).into_bound(py);
460
461        // safety ensure API is loaded
462        let _api = ensure_datetime_api(py)?;
463
464        unsafe {
465            PyDateTime_FromTimestamp(args.as_ptr())
466                .assume_owned_or_err(py)
467                .downcast_into_unchecked()
468        }
469    }
470}
471
472#[cfg(feature = "gil-refs")]
473impl PyDateAccess for PyDateTime {
474    fn get_year(&self) -> i32 {
475        self.as_borrowed().get_year()
476    }
477
478    fn get_month(&self) -> u8 {
479        self.as_borrowed().get_month()
480    }
481
482    fn get_day(&self) -> u8 {
483        self.as_borrowed().get_day()
484    }
485}
486
487impl PyDateAccess for Bound<'_, PyDateTime> {
488    fn get_year(&self) -> i32 {
489        unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
490    }
491
492    fn get_month(&self) -> u8 {
493        unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
494    }
495
496    fn get_day(&self) -> u8 {
497        unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
498    }
499}
500
501#[cfg(feature = "gil-refs")]
502impl PyTimeAccess for PyDateTime {
503    fn get_hour(&self) -> u8 {
504        self.as_borrowed().get_hour()
505    }
506
507    fn get_minute(&self) -> u8 {
508        self.as_borrowed().get_minute()
509    }
510
511    fn get_second(&self) -> u8 {
512        self.as_borrowed().get_second()
513    }
514
515    fn get_microsecond(&self) -> u32 {
516        self.as_borrowed().get_microsecond()
517    }
518
519    fn get_fold(&self) -> bool {
520        self.as_borrowed().get_fold()
521    }
522}
523
524impl PyTimeAccess for Bound<'_, PyDateTime> {
525    fn get_hour(&self) -> u8 {
526        unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u8 }
527    }
528
529    fn get_minute(&self) -> u8 {
530        unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u8 }
531    }
532
533    fn get_second(&self) -> u8 {
534        unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u8 }
535    }
536
537    fn get_microsecond(&self) -> u32 {
538        unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 }
539    }
540
541    fn get_fold(&self) -> bool {
542        unsafe { PyDateTime_DATE_GET_FOLD(self.as_ptr()) > 0 }
543    }
544}
545
546#[cfg(feature = "gil-refs")]
547impl<'py> PyTzInfoAccess<'py> for &'py PyDateTime {
548    fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>> {
549        self.as_borrowed().get_tzinfo_bound()
550    }
551}
552
553impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyDateTime> {
554    fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>> {
555        let ptr = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
556        #[cfg(not(GraalPy))]
557        unsafe {
558            if (*ptr).hastzinfo != 0 {
559                Some(
560                    (*ptr)
561                        .tzinfo
562                        .assume_borrowed(self.py())
563                        .to_owned()
564                        .downcast_into_unchecked(),
565                )
566            } else {
567                None
568            }
569        }
570
571        #[cfg(GraalPy)]
572        unsafe {
573            let res = PyDateTime_DATE_GET_TZINFO(ptr as *mut ffi::PyObject);
574            if Py_IsNone(res) == 1 {
575                None
576            } else {
577                Some(
578                    res.assume_borrowed(self.py())
579                        .to_owned()
580                        .downcast_into_unchecked(),
581                )
582            }
583        }
584    }
585}
586
587/// Bindings for `datetime.time`.
588///
589/// Values of this type are accessed via PyO3's smart pointers, e.g. as
590/// [`Py<PyTime>`][crate::Py] or [`Bound<'py, PyTime>`][Bound].
591#[repr(transparent)]
592pub struct PyTime(PyAny);
593pyobject_native_type!(
594    PyTime,
595    crate::ffi::PyDateTime_Time,
596    |py| expect_datetime_api(py).TimeType,
597    #module=Some("datetime"),
598    #checkfunction=PyTime_Check
599);
600
601impl PyTime {
602    /// Deprecated form of [`PyTime::new_bound`].
603    #[cfg(feature = "gil-refs")]
604    #[deprecated(
605        since = "0.21.0",
606        note = "`PyTime::new` will be replaced by `PyTime::new_bound` in a future PyO3 version"
607    )]
608    pub fn new<'py>(
609        py: Python<'py>,
610        hour: u8,
611        minute: u8,
612        second: u8,
613        microsecond: u32,
614        tzinfo: Option<&'py PyTzInfo>,
615    ) -> PyResult<&'py PyTime> {
616        Self::new_bound(
617            py,
618            hour,
619            minute,
620            second,
621            microsecond,
622            tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
623        )
624        .map(Bound::into_gil_ref)
625    }
626
627    /// Creates a new `datetime.time` object.
628    pub fn new_bound<'py>(
629        py: Python<'py>,
630        hour: u8,
631        minute: u8,
632        second: u8,
633        microsecond: u32,
634        tzinfo: Option<&Bound<'py, PyTzInfo>>,
635    ) -> PyResult<Bound<'py, PyTime>> {
636        let api = ensure_datetime_api(py)?;
637        unsafe {
638            (api.Time_FromTime)(
639                c_int::from(hour),
640                c_int::from(minute),
641                c_int::from(second),
642                microsecond as c_int,
643                opt_to_pyobj(tzinfo),
644                api.TimeType,
645            )
646            .assume_owned_or_err(py)
647            .downcast_into_unchecked()
648        }
649    }
650
651    /// Deprecated form of [`PyTime::new_bound_with_fold`].
652    #[cfg(feature = "gil-refs")]
653    #[deprecated(
654        since = "0.21.0",
655        note = "`PyTime::new_with_fold` will be replaced by `PyTime::new_bound_with_fold` in a future PyO3 version"
656    )]
657    pub fn new_with_fold<'py>(
658        py: Python<'py>,
659        hour: u8,
660        minute: u8,
661        second: u8,
662        microsecond: u32,
663        tzinfo: Option<&'py PyTzInfo>,
664        fold: bool,
665    ) -> PyResult<&'py PyTime> {
666        Self::new_bound_with_fold(
667            py,
668            hour,
669            minute,
670            second,
671            microsecond,
672            tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
673            fold,
674        )
675        .map(Bound::into_gil_ref)
676    }
677
678    /// Alternate constructor that takes a `fold` argument. See [`PyDateTime::new_bound_with_fold`].
679    pub fn new_bound_with_fold<'py>(
680        py: Python<'py>,
681        hour: u8,
682        minute: u8,
683        second: u8,
684        microsecond: u32,
685        tzinfo: Option<&Bound<'py, PyTzInfo>>,
686        fold: bool,
687    ) -> PyResult<Bound<'py, PyTime>> {
688        let api = ensure_datetime_api(py)?;
689        unsafe {
690            (api.Time_FromTimeAndFold)(
691                c_int::from(hour),
692                c_int::from(minute),
693                c_int::from(second),
694                microsecond as c_int,
695                opt_to_pyobj(tzinfo),
696                fold as c_int,
697                api.TimeType,
698            )
699            .assume_owned_or_err(py)
700            .downcast_into_unchecked()
701        }
702    }
703}
704
705#[cfg(feature = "gil-refs")]
706impl PyTimeAccess for PyTime {
707    fn get_hour(&self) -> u8 {
708        self.as_borrowed().get_hour()
709    }
710
711    fn get_minute(&self) -> u8 {
712        self.as_borrowed().get_minute()
713    }
714
715    fn get_second(&self) -> u8 {
716        self.as_borrowed().get_second()
717    }
718
719    fn get_microsecond(&self) -> u32 {
720        self.as_borrowed().get_microsecond()
721    }
722
723    fn get_fold(&self) -> bool {
724        self.as_borrowed().get_fold()
725    }
726}
727
728impl PyTimeAccess for Bound<'_, PyTime> {
729    fn get_hour(&self) -> u8 {
730        unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 }
731    }
732
733    fn get_minute(&self) -> u8 {
734        unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 }
735    }
736
737    fn get_second(&self) -> u8 {
738        unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 }
739    }
740
741    fn get_microsecond(&self) -> u32 {
742        unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 }
743    }
744
745    fn get_fold(&self) -> bool {
746        unsafe { PyDateTime_TIME_GET_FOLD(self.as_ptr()) != 0 }
747    }
748}
749
750#[cfg(feature = "gil-refs")]
751impl<'py> PyTzInfoAccess<'py> for &'py PyTime {
752    fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>> {
753        self.as_borrowed().get_tzinfo_bound()
754    }
755}
756
757impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyTime> {
758    fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>> {
759        let ptr = self.as_ptr() as *mut ffi::PyDateTime_Time;
760        #[cfg(not(GraalPy))]
761        unsafe {
762            if (*ptr).hastzinfo != 0 {
763                Some(
764                    (*ptr)
765                        .tzinfo
766                        .assume_borrowed(self.py())
767                        .to_owned()
768                        .downcast_into_unchecked(),
769                )
770            } else {
771                None
772            }
773        }
774
775        #[cfg(GraalPy)]
776        unsafe {
777            let res = PyDateTime_TIME_GET_TZINFO(ptr as *mut ffi::PyObject);
778            if Py_IsNone(res) == 1 {
779                None
780            } else {
781                Some(
782                    res.assume_borrowed(self.py())
783                        .to_owned()
784                        .downcast_into_unchecked(),
785                )
786            }
787        }
788    }
789}
790
791/// Bindings for `datetime.tzinfo`.
792///
793/// Values of this type are accessed via PyO3's smart pointers, e.g. as
794/// [`Py<PyTzInfo>`][crate::Py] or [`Bound<'py, PyTzInfo>`][Bound].
795///
796/// This is an abstract base class and cannot be constructed directly.
797/// For concrete time zone implementations, see [`timezone_utc_bound`] and
798/// the [`zoneinfo` module](https://docs.python.org/3/library/zoneinfo.html).
799#[repr(transparent)]
800pub struct PyTzInfo(PyAny);
801pyobject_native_type!(
802    PyTzInfo,
803    crate::ffi::PyObject,
804    |py| expect_datetime_api(py).TZInfoType,
805    #module=Some("datetime"),
806    #checkfunction=PyTZInfo_Check
807);
808
809/// Deprecated form of [`timezone_utc_bound`].
810#[cfg(feature = "gil-refs")]
811#[deprecated(
812    since = "0.21.0",
813    note = "`timezone_utc` will be replaced by `timezone_utc_bound` in a future PyO3 version"
814)]
815pub fn timezone_utc(py: Python<'_>) -> &PyTzInfo {
816    timezone_utc_bound(py).into_gil_ref()
817}
818
819/// Equivalent to `datetime.timezone.utc`
820pub fn timezone_utc_bound(py: Python<'_>) -> Bound<'_, PyTzInfo> {
821    // TODO: this _could_ have a borrowed form `timezone_utc_borrowed`, but that seems
822    // like an edge case optimization and we'd prefer in PyO3 0.21 to use `Bound` as
823    // much as possible
824    unsafe {
825        expect_datetime_api(py)
826            .TimeZone_UTC
827            .assume_borrowed(py)
828            .to_owned()
829            .downcast_into_unchecked()
830    }
831}
832
833/// Equivalent to `datetime.timezone` constructor
834///
835/// Only used internally
836#[cfg(feature = "chrono")]
837pub(crate) fn timezone_from_offset<'py>(
838    offset: &Bound<'py, PyDelta>,
839) -> PyResult<Bound<'py, PyTzInfo>> {
840    let py = offset.py();
841    let api = ensure_datetime_api(py)?;
842    unsafe {
843        (api.TimeZone_FromTimeZone)(offset.as_ptr(), ptr::null_mut())
844            .assume_owned_or_err(py)
845            .downcast_into_unchecked()
846    }
847}
848
849/// Bindings for `datetime.timedelta`.
850///
851/// Values of this type are accessed via PyO3's smart pointers, e.g. as
852/// [`Py<PyDelta>`][crate::Py] or [`Bound<'py, PyDelta>`][Bound].
853#[repr(transparent)]
854pub struct PyDelta(PyAny);
855pyobject_native_type!(
856    PyDelta,
857    crate::ffi::PyDateTime_Delta,
858    |py| expect_datetime_api(py).DeltaType,
859    #module=Some("datetime"),
860    #checkfunction=PyDelta_Check
861);
862
863impl PyDelta {
864    /// Deprecated form of [`PyDelta::new_bound`].
865    #[cfg(feature = "gil-refs")]
866    #[deprecated(
867        since = "0.21.0",
868        note = "`PyDelta::new` will be replaced by `PyDelta::new_bound` in a future PyO3 version"
869    )]
870    pub fn new(
871        py: Python<'_>,
872        days: i32,
873        seconds: i32,
874        microseconds: i32,
875        normalize: bool,
876    ) -> PyResult<&PyDelta> {
877        Self::new_bound(py, days, seconds, microseconds, normalize).map(Bound::into_gil_ref)
878    }
879
880    /// Creates a new `timedelta`.
881    pub fn new_bound(
882        py: Python<'_>,
883        days: i32,
884        seconds: i32,
885        microseconds: i32,
886        normalize: bool,
887    ) -> PyResult<Bound<'_, PyDelta>> {
888        let api = ensure_datetime_api(py)?;
889        unsafe {
890            (api.Delta_FromDelta)(
891                days as c_int,
892                seconds as c_int,
893                microseconds as c_int,
894                normalize as c_int,
895                api.DeltaType,
896            )
897            .assume_owned_or_err(py)
898            .downcast_into_unchecked()
899        }
900    }
901}
902
903#[cfg(feature = "gil-refs")]
904impl PyDeltaAccess for PyDelta {
905    fn get_days(&self) -> i32 {
906        self.as_borrowed().get_days()
907    }
908
909    fn get_seconds(&self) -> i32 {
910        self.as_borrowed().get_seconds()
911    }
912
913    fn get_microseconds(&self) -> i32 {
914        self.as_borrowed().get_microseconds()
915    }
916}
917
918impl PyDeltaAccess for Bound<'_, PyDelta> {
919    fn get_days(&self) -> i32 {
920        unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
921    }
922
923    fn get_seconds(&self) -> i32 {
924        unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
925    }
926
927    fn get_microseconds(&self) -> i32 {
928        unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
929    }
930}
931
932// Utility function which returns a borrowed reference to either
933// the underlying tzinfo or None.
934fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
935    match opt {
936        Some(tzi) => tzi.as_ptr(),
937        None => unsafe { ffi::Py_None() },
938    }
939}
940
941#[cfg(test)]
942mod tests {
943    use super::*;
944    #[cfg(feature = "macros")]
945    use crate::py_run;
946
947    #[test]
948    #[cfg(feature = "macros")]
949    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
950    fn test_datetime_fromtimestamp() {
951        Python::with_gil(|py| {
952            let dt = PyDateTime::from_timestamp_bound(py, 100.0, None).unwrap();
953            py_run!(
954                py,
955                dt,
956                "import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
957            );
958
959            let dt =
960                PyDateTime::from_timestamp_bound(py, 100.0, Some(&timezone_utc_bound(py))).unwrap();
961            py_run!(
962                py,
963                dt,
964                "import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
965            );
966        })
967    }
968
969    #[test]
970    #[cfg(feature = "macros")]
971    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
972    fn test_date_fromtimestamp() {
973        Python::with_gil(|py| {
974            let dt = PyDate::from_timestamp_bound(py, 100).unwrap();
975            py_run!(
976                py,
977                dt,
978                "import datetime; assert dt == datetime.date.fromtimestamp(100)"
979            );
980        })
981    }
982
983    #[test]
984    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
985    fn test_new_with_fold() {
986        Python::with_gil(|py| {
987            let a =
988                PyDateTime::new_bound_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
989            let b =
990                PyDateTime::new_bound_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
991
992            assert!(!a.unwrap().get_fold());
993            assert!(b.unwrap().get_fold());
994        });
995    }
996
997    #[test]
998    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
999    fn test_get_tzinfo() {
1000        crate::Python::with_gil(|py| {
1001            let utc = timezone_utc_bound(py);
1002
1003            let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
1004
1005            assert!(dt.get_tzinfo_bound().unwrap().eq(&utc).unwrap());
1006
1007            let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
1008
1009            assert!(dt.get_tzinfo_bound().is_none());
1010
1011            let t = PyTime::new_bound(py, 0, 0, 0, 0, Some(&utc)).unwrap();
1012
1013            assert!(t.get_tzinfo_bound().unwrap().eq(utc).unwrap());
1014
1015            let t = PyTime::new_bound(py, 0, 0, 0, 0, None).unwrap();
1016
1017            assert!(t.get_tzinfo_bound().is_none());
1018        });
1019    }
1020
1021    #[test]
1022    #[cfg(all(feature = "macros", feature = "chrono"))]
1023    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
1024    fn test_timezone_from_offset() {
1025        Python::with_gil(|py| {
1026            assert!(
1027                timezone_from_offset(&PyDelta::new_bound(py, 0, -3600, 0, true).unwrap())
1028                    .unwrap()
1029                    .call_method1("utcoffset", ((),))
1030                    .unwrap()
1031                    .downcast_into::<PyDelta>()
1032                    .unwrap()
1033                    .eq(PyDelta::new_bound(py, 0, -3600, 0, true).unwrap())
1034                    .unwrap()
1035            );
1036
1037            assert!(
1038                timezone_from_offset(&PyDelta::new_bound(py, 0, 3600, 0, true).unwrap())
1039                    .unwrap()
1040                    .call_method1("utcoffset", ((),))
1041                    .unwrap()
1042                    .downcast_into::<PyDelta>()
1043                    .unwrap()
1044                    .eq(PyDelta::new_bound(py, 0, 3600, 0, true).unwrap())
1045                    .unwrap()
1046            );
1047
1048            timezone_from_offset(&PyDelta::new_bound(py, 1, 0, 0, true).unwrap()).unwrap_err();
1049        })
1050    }
1051}