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