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