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