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