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