Skip to main content

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 core::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    "datetime",
205    "date",
206    #module=Some("datetime"),
207    #checkfunction=PyDate_Check
208);
209pyobject_subclassable_native_type!(PyDate, crate::ffi::PyDateTime_Date);
210
211#[cfg(Py_LIMITED_API)]
212pyobject_native_type_core!(
213    PyDate,
214    |py| {
215        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
216        TYPE.import(py, "datetime", "date").unwrap().as_type_ptr()
217    },
218    "datetime",
219    "date",
220    #module=Some("datetime")
221);
222
223impl PyDate {
224    /// Creates a new `datetime.date`.
225    pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
226        #[cfg(not(Py_LIMITED_API))]
227        {
228            let api = ensure_datetime_api(py)?;
229            unsafe {
230                (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
231                    .assume_owned_or_err(py)
232                    .cast_into_unchecked()
233            }
234        }
235        #[cfg(Py_LIMITED_API)]
236        Ok(Self::type_object(py)
237            .call((year, month, day), None)?
238            .cast_into()?)
239    }
240
241    /// Construct a `datetime.date` from a POSIX timestamp
242    ///
243    /// This is equivalent to `datetime.date.fromtimestamp`
244    pub fn from_timestamp(py: Python<'_>, timestamp: f64) -> PyResult<Bound<'_, PyDate>> {
245        #[cfg(not(Py_LIMITED_API))]
246        {
247            let time_tuple = PyTuple::new(py, [timestamp])?;
248
249            // safety ensure that the API is loaded
250            let _api = ensure_datetime_api(py)?;
251
252            unsafe {
253                PyDate_FromTimestamp(time_tuple.as_ptr())
254                    .assume_owned_or_err(py)
255                    .cast_into_unchecked()
256            }
257        }
258
259        #[cfg(Py_LIMITED_API)]
260        Ok(Self::type_object(py)
261            .call_method1("fromtimestamp", (timestamp,))?
262            .cast_into()?)
263    }
264}
265
266#[cfg(not(Py_LIMITED_API))]
267impl PyDateAccess for Bound<'_, PyDate> {
268    fn get_year(&self) -> i32 {
269        unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
270    }
271
272    fn get_month(&self) -> u8 {
273        unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
274    }
275
276    fn get_day(&self) -> u8 {
277        unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
278    }
279}
280
281/// Bindings for `datetime.datetime`.
282///
283/// Values of this type are accessed via PyO3's smart pointers, e.g. as
284/// [`Py<PyDateTime>`][crate::Py] or [`Bound<'py, PyDateTime>`][Bound].
285#[repr(transparent)]
286pub struct PyDateTime(PyAny);
287
288#[cfg(not(Py_LIMITED_API))]
289pyobject_native_type!(
290    PyDateTime,
291    crate::ffi::PyDateTime_DateTime,
292    |py| expect_datetime_api(py).DateTimeType,
293    "datetime",
294    "datetime",
295    #module=Some("datetime"),
296    #checkfunction=PyDateTime_Check
297);
298pyobject_subclassable_native_type!(PyDateTime, crate::ffi::PyDateTime_DateTime);
299
300#[cfg(Py_LIMITED_API)]
301pyobject_native_type_core!(
302    PyDateTime,
303    |py| {
304        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
305        TYPE.import(py, "datetime", "datetime")
306            .unwrap()
307            .as_type_ptr()
308    },
309    "datetime",
310    "datetime",
311    #module=Some("datetime")
312);
313
314impl PyDateTime {
315    /// Creates a new `datetime.datetime` object.
316    #[allow(clippy::too_many_arguments)]
317    pub fn new<'py>(
318        py: Python<'py>,
319        year: i32,
320        month: u8,
321        day: u8,
322        hour: u8,
323        minute: u8,
324        second: u8,
325        microsecond: u32,
326        tzinfo: Option<&Bound<'py, PyTzInfo>>,
327    ) -> PyResult<Bound<'py, PyDateTime>> {
328        #[cfg(not(Py_LIMITED_API))]
329        {
330            let api = ensure_datetime_api(py)?;
331            unsafe {
332                (api.DateTime_FromDateAndTime)(
333                    year,
334                    c_int::from(month),
335                    c_int::from(day),
336                    c_int::from(hour),
337                    c_int::from(minute),
338                    c_int::from(second),
339                    microsecond as c_int,
340                    opt_to_pyobj(tzinfo),
341                    api.DateTimeType,
342                )
343                .assume_owned_or_err(py)
344                .cast_into_unchecked()
345            }
346        }
347
348        #[cfg(Py_LIMITED_API)]
349        Ok(Self::type_object(py)
350            .call(
351                (year, month, day, hour, minute, second, microsecond, tzinfo),
352                None,
353            )?
354            .cast_into()?)
355    }
356
357    /// Alternate constructor that takes a `fold` parameter. A `true` value for this parameter
358    /// signifies this this datetime is the later of two moments with the same representation,
359    /// during a repeated interval.
360    ///
361    /// This typically occurs at the end of daylight savings time. Only valid if the
362    /// represented time is ambiguous.
363    /// See [PEP 495](https://www.python.org/dev/peps/pep-0495/) for more detail.
364    #[allow(clippy::too_many_arguments)]
365    pub fn new_with_fold<'py>(
366        py: Python<'py>,
367        year: i32,
368        month: u8,
369        day: u8,
370        hour: u8,
371        minute: u8,
372        second: u8,
373        microsecond: u32,
374        tzinfo: Option<&Bound<'py, PyTzInfo>>,
375        fold: bool,
376    ) -> PyResult<Bound<'py, PyDateTime>> {
377        #[cfg(not(Py_LIMITED_API))]
378        {
379            let api = ensure_datetime_api(py)?;
380            unsafe {
381                (api.DateTime_FromDateAndTimeAndFold)(
382                    year,
383                    c_int::from(month),
384                    c_int::from(day),
385                    c_int::from(hour),
386                    c_int::from(minute),
387                    c_int::from(second),
388                    microsecond as c_int,
389                    opt_to_pyobj(tzinfo),
390                    c_int::from(fold),
391                    api.DateTimeType,
392                )
393                .assume_owned_or_err(py)
394                .cast_into_unchecked()
395            }
396        }
397
398        #[cfg(Py_LIMITED_API)]
399        Ok(Self::type_object(py)
400            .call(
401                (year, month, day, hour, minute, second, microsecond, tzinfo),
402                Some(&[("fold", fold)].into_py_dict(py)?),
403            )?
404            .cast_into()?)
405    }
406
407    /// Construct a `datetime` object from a POSIX timestamp
408    ///
409    /// This is equivalent to `datetime.datetime.fromtimestamp`
410    pub fn from_timestamp<'py>(
411        py: Python<'py>,
412        timestamp: f64,
413        tzinfo: Option<&Bound<'py, PyTzInfo>>,
414    ) -> PyResult<Bound<'py, PyDateTime>> {
415        #[cfg(not(Py_LIMITED_API))]
416        {
417            let args = (timestamp, tzinfo).into_pyobject(py)?;
418
419            // safety ensure API is loaded
420            let _api = ensure_datetime_api(py)?;
421
422            unsafe {
423                PyDateTime_FromTimestamp(args.as_ptr())
424                    .assume_owned_or_err(py)
425                    .cast_into_unchecked()
426            }
427        }
428
429        #[cfg(Py_LIMITED_API)]
430        Ok(Self::type_object(py)
431            .call_method1("fromtimestamp", (timestamp, tzinfo))?
432            .cast_into()?)
433    }
434}
435
436#[cfg(not(Py_LIMITED_API))]
437impl PyDateAccess for Bound<'_, PyDateTime> {
438    fn get_year(&self) -> i32 {
439        unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
440    }
441
442    fn get_month(&self) -> u8 {
443        unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
444    }
445
446    fn get_day(&self) -> u8 {
447        unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
448    }
449}
450
451#[cfg(not(Py_LIMITED_API))]
452impl PyTimeAccess for Bound<'_, PyDateTime> {
453    fn get_hour(&self) -> u8 {
454        unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u8 }
455    }
456
457    fn get_minute(&self) -> u8 {
458        unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u8 }
459    }
460
461    fn get_second(&self) -> u8 {
462        unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u8 }
463    }
464
465    fn get_microsecond(&self) -> u32 {
466        unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 }
467    }
468
469    fn get_fold(&self) -> bool {
470        unsafe { PyDateTime_DATE_GET_FOLD(self.as_ptr()) > 0 }
471    }
472}
473
474impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyDateTime> {
475    fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
476        #[cfg(all(not(Py_3_10), not(Py_LIMITED_API)))]
477        unsafe {
478            let ptr = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
479            if (*ptr).hastzinfo != 0 {
480                Some(
481                    (*ptr)
482                        .tzinfo
483                        .assume_borrowed(self.py())
484                        .to_owned()
485                        .cast_into_unchecked(),
486                )
487            } else {
488                None
489            }
490        }
491
492        #[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
493        unsafe {
494            let res = PyDateTime_DATE_GET_TZINFO(self.as_ptr());
495            if Py_IsNone(res) == 1 {
496                None
497            } else {
498                Some(
499                    res.assume_borrowed(self.py())
500                        .to_owned()
501                        .cast_into_unchecked(),
502                )
503            }
504        }
505
506        #[cfg(Py_LIMITED_API)]
507        unsafe {
508            let tzinfo = self.getattr(intern!(self.py(), "tzinfo")).ok()?;
509            if tzinfo.is_none() {
510                None
511            } else {
512                Some(tzinfo.cast_into_unchecked())
513            }
514        }
515    }
516}
517
518/// Bindings for `datetime.time`.
519///
520/// Values of this type are accessed via PyO3's smart pointers, e.g. as
521/// [`Py<PyTime>`][crate::Py] or [`Bound<'py, PyTime>`][Bound].
522#[repr(transparent)]
523pub struct PyTime(PyAny);
524
525#[cfg(not(Py_LIMITED_API))]
526pyobject_native_type!(
527    PyTime,
528    crate::ffi::PyDateTime_Time,
529    |py| expect_datetime_api(py).TimeType,
530    "datetime",
531    "time",
532    #module=Some("datetime"),
533    #checkfunction=PyTime_Check
534);
535pyobject_subclassable_native_type!(PyTime, crate::ffi::PyDateTime_Time);
536
537#[cfg(Py_LIMITED_API)]
538pyobject_native_type_core!(
539    PyTime,
540    |py| {
541        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
542        TYPE.import(py, "datetime", "time").unwrap().as_type_ptr()
543    },
544    "datetime",
545    "time",
546    #module=Some("datetime")
547);
548
549impl PyTime {
550    /// Creates a new `datetime.time` object.
551    pub fn new<'py>(
552        py: Python<'py>,
553        hour: u8,
554        minute: u8,
555        second: u8,
556        microsecond: u32,
557        tzinfo: Option<&Bound<'py, PyTzInfo>>,
558    ) -> PyResult<Bound<'py, PyTime>> {
559        #[cfg(not(Py_LIMITED_API))]
560        {
561            let api = ensure_datetime_api(py)?;
562            unsafe {
563                (api.Time_FromTime)(
564                    c_int::from(hour),
565                    c_int::from(minute),
566                    c_int::from(second),
567                    microsecond as c_int,
568                    opt_to_pyobj(tzinfo),
569                    api.TimeType,
570                )
571                .assume_owned_or_err(py)
572                .cast_into_unchecked()
573            }
574        }
575
576        #[cfg(Py_LIMITED_API)]
577        Ok(Self::type_object(py)
578            .call((hour, minute, second, microsecond, tzinfo), None)?
579            .cast_into()?)
580    }
581
582    /// Alternate constructor that takes a `fold` argument. See [`PyDateTime::new_with_fold`].
583    pub fn new_with_fold<'py>(
584        py: Python<'py>,
585        hour: u8,
586        minute: u8,
587        second: u8,
588        microsecond: u32,
589        tzinfo: Option<&Bound<'py, PyTzInfo>>,
590        fold: bool,
591    ) -> PyResult<Bound<'py, PyTime>> {
592        #[cfg(not(Py_LIMITED_API))]
593        {
594            let api = ensure_datetime_api(py)?;
595            unsafe {
596                (api.Time_FromTimeAndFold)(
597                    c_int::from(hour),
598                    c_int::from(minute),
599                    c_int::from(second),
600                    microsecond as c_int,
601                    opt_to_pyobj(tzinfo),
602                    fold as c_int,
603                    api.TimeType,
604                )
605                .assume_owned_or_err(py)
606                .cast_into_unchecked()
607            }
608        }
609
610        #[cfg(Py_LIMITED_API)]
611        Ok(Self::type_object(py)
612            .call(
613                (hour, minute, second, microsecond, tzinfo),
614                Some(&[("fold", fold)].into_py_dict(py)?),
615            )?
616            .cast_into()?)
617    }
618}
619
620#[cfg(not(Py_LIMITED_API))]
621impl PyTimeAccess for Bound<'_, PyTime> {
622    fn get_hour(&self) -> u8 {
623        unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 }
624    }
625
626    fn get_minute(&self) -> u8 {
627        unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 }
628    }
629
630    fn get_second(&self) -> u8 {
631        unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 }
632    }
633
634    fn get_microsecond(&self) -> u32 {
635        unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 }
636    }
637
638    fn get_fold(&self) -> bool {
639        unsafe { PyDateTime_TIME_GET_FOLD(self.as_ptr()) != 0 }
640    }
641}
642
643impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyTime> {
644    fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
645        #[cfg(all(not(Py_3_10), not(Py_LIMITED_API)))]
646        unsafe {
647            let ptr = self.as_ptr() as *mut ffi::PyDateTime_Time;
648            if (*ptr).hastzinfo != 0 {
649                Some(
650                    (*ptr)
651                        .tzinfo
652                        .assume_borrowed(self.py())
653                        .to_owned()
654                        .cast_into_unchecked(),
655                )
656            } else {
657                None
658            }
659        }
660
661        #[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
662        unsafe {
663            let res = PyDateTime_TIME_GET_TZINFO(self.as_ptr());
664            if Py_IsNone(res) == 1 {
665                None
666            } else {
667                Some(
668                    res.assume_borrowed(self.py())
669                        .to_owned()
670                        .cast_into_unchecked(),
671                )
672            }
673        }
674
675        #[cfg(Py_LIMITED_API)]
676        unsafe {
677            let tzinfo = self.getattr(intern!(self.py(), "tzinfo")).ok()?;
678            if tzinfo.is_none() {
679                None
680            } else {
681                Some(tzinfo.cast_into_unchecked())
682            }
683        }
684    }
685}
686
687/// Bindings for `datetime.tzinfo`.
688///
689/// Values of this type are accessed via PyO3's smart pointers, e.g. as
690/// [`Py<PyTzInfo>`][crate::Py] or [`Bound<'py, PyTzInfo>`][Bound].
691///
692/// This is an abstract base class, the primary implementations are
693/// [`datetime.timezone`](https://docs.python.org/3/library/datetime.html#timezone-objects)
694/// and the [`zoneinfo` module](https://docs.python.org/3/library/zoneinfo.html).
695///
696/// The constructors [`PyTzInfo::utc`], [`PyTzInfo::fixed_offset`] and [`PyTzInfo::timezone`]
697/// create these concrete subclasses.
698#[repr(transparent)]
699pub struct PyTzInfo(PyAny);
700
701#[cfg(not(Py_LIMITED_API))]
702pyobject_native_type!(
703    PyTzInfo,
704    crate::ffi::PyObject,
705    |py| expect_datetime_api(py).TZInfoType,
706    "datetime",
707    "tzinfo",
708    #module=Some("datetime"),
709    #checkfunction=PyTZInfo_Check
710);
711pyobject_subclassable_native_type!(PyTzInfo, crate::ffi::PyObject);
712
713#[cfg(Py_LIMITED_API)]
714pyobject_native_type_core!(
715    PyTzInfo,
716    |py| {
717        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
718        TYPE.import(py, "datetime", "tzinfo").unwrap().as_type_ptr()
719    },
720    "datetime",
721    "tzinfo",
722    #module=Some("datetime")
723);
724
725impl PyTzInfo {
726    /// Equivalent to `datetime.timezone.utc`
727    pub fn utc(py: Python<'_>) -> PyResult<Borrowed<'static, '_, PyTzInfo>> {
728        #[cfg(not(Py_LIMITED_API))]
729        unsafe {
730            Ok(ensure_datetime_api(py)?
731                .TimeZone_UTC
732                .assume_borrowed(py)
733                .cast_unchecked())
734        }
735
736        #[cfg(Py_LIMITED_API)]
737        {
738            static UTC: PyOnceLock<Py<PyTzInfo>> = PyOnceLock::new();
739            UTC.get_or_try_init(py, || {
740                Ok(py
741                    .import("datetime")?
742                    .getattr("timezone")?
743                    .getattr("utc")?
744                    .cast_into()?
745                    .unbind())
746            })
747            .map(|utc| utc.bind_borrowed(py))
748        }
749    }
750
751    /// Equivalent to `zoneinfo.ZoneInfo` constructor
752    pub fn timezone<'py, T>(py: Python<'py>, iana_name: T) -> PyResult<Bound<'py, PyTzInfo>>
753    where
754        T: IntoPyObject<'py, Target = PyString>,
755    {
756        static ZONE_INFO: PyOnceLock<Py<PyType>> = PyOnceLock::new();
757
758        let zoneinfo = ZONE_INFO.import(py, "zoneinfo", "ZoneInfo");
759
760        #[cfg(not(Py_3_9))]
761        let zoneinfo = zoneinfo
762            .or_else(|_| ZONE_INFO.import(py, "backports.zoneinfo", "ZoneInfo"))
763            .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"));
764
765        zoneinfo?
766            .call1((iana_name,))?
767            .cast_into()
768            .map_err(Into::into)
769    }
770
771    /// Equivalent to `datetime.timezone` constructor
772    pub fn fixed_offset<'py, T>(py: Python<'py>, offset: T) -> PyResult<Bound<'py, PyTzInfo>>
773    where
774        T: IntoPyObject<'py, Target = PyDelta>,
775    {
776        #[cfg(not(Py_LIMITED_API))]
777        {
778            let api = ensure_datetime_api(py)?;
779            let delta = offset.into_pyobject(py).map_err(Into::into)?;
780            unsafe {
781                (api.TimeZone_FromTimeZone)(delta.as_ptr(), core::ptr::null_mut())
782                    .assume_owned_or_err(py)
783                    .cast_into_unchecked()
784            }
785        }
786
787        #[cfg(Py_LIMITED_API)]
788        {
789            static TIMEZONE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
790            Ok(TIMEZONE
791                .import(py, "datetime", "timezone")?
792                .call1((offset,))?
793                .cast_into()?)
794        }
795    }
796}
797
798/// Bindings for `datetime.timedelta`.
799///
800/// Values of this type are accessed via PyO3's smart pointers, e.g. as
801/// [`Py<PyDelta>`][crate::Py] or [`Bound<'py, PyDelta>`][Bound].
802#[repr(transparent)]
803pub struct PyDelta(PyAny);
804
805#[cfg(not(Py_LIMITED_API))]
806pyobject_native_type!(
807    PyDelta,
808    crate::ffi::PyDateTime_Delta,
809    |py| expect_datetime_api(py).DeltaType,
810    "datetime",
811    "timedelta",
812    #module=Some("datetime"),
813    #checkfunction=PyDelta_Check
814);
815pyobject_subclassable_native_type!(PyDelta, crate::ffi::PyDateTime_Delta);
816
817#[cfg(Py_LIMITED_API)]
818pyobject_native_type_core!(
819    PyDelta,
820    |py| {
821        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
822        TYPE.import(py, "datetime", "timedelta")
823            .unwrap()
824            .as_type_ptr()
825    },
826    "datetime",
827    "timedelta",
828    #module=Some("datetime")
829);
830
831impl PyDelta {
832    /// Creates a new `timedelta`.
833    pub fn new(
834        py: Python<'_>,
835        days: i32,
836        seconds: i32,
837        microseconds: i32,
838        normalize: bool,
839    ) -> PyResult<Bound<'_, PyDelta>> {
840        #[cfg(not(Py_LIMITED_API))]
841        {
842            let api = ensure_datetime_api(py)?;
843            unsafe {
844                (api.Delta_FromDelta)(
845                    days as c_int,
846                    seconds as c_int,
847                    microseconds as c_int,
848                    normalize as c_int,
849                    api.DeltaType,
850                )
851                .assume_owned_or_err(py)
852                .cast_into_unchecked()
853            }
854        }
855
856        #[cfg(Py_LIMITED_API)]
857        let _ = normalize;
858        #[cfg(Py_LIMITED_API)]
859        Ok(Self::type_object(py)
860            .call1((days, seconds, microseconds))?
861            .cast_into()?)
862    }
863}
864
865#[cfg(not(Py_LIMITED_API))]
866impl PyDeltaAccess for Bound<'_, PyDelta> {
867    fn get_days(&self) -> i32 {
868        unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
869    }
870
871    fn get_seconds(&self) -> i32 {
872        unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
873    }
874
875    fn get_microseconds(&self) -> i32 {
876        unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
877    }
878}
879
880// Utility function which returns a borrowed reference to either
881// the underlying tzinfo or None.
882#[cfg(not(Py_LIMITED_API))]
883fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
884    match opt {
885        Some(tzi) => tzi.as_ptr(),
886        None => unsafe { ffi::Py_None() },
887    }
888}
889
890#[cfg(test)]
891mod tests {
892    use super::*;
893    #[cfg(feature = "macros")]
894    use crate::py_run;
895
896    #[test]
897    #[cfg(feature = "macros")]
898    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
899    fn test_datetime_fromtimestamp() {
900        Python::attach(|py| {
901            let dt = PyDateTime::from_timestamp(py, 100.0, None).unwrap();
902            py_run!(
903                py,
904                dt,
905                "import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
906            );
907
908            let utc = PyTzInfo::utc(py).unwrap();
909            let dt = PyDateTime::from_timestamp(py, 100.0, Some(&utc)).unwrap();
910            py_run!(
911                py,
912                dt,
913                "import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
914            );
915        })
916    }
917
918    #[test]
919    #[cfg(feature = "macros")]
920    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
921    fn test_date_fromtimestamp() {
922        Python::attach(|py| {
923            let dt = PyDate::from_timestamp(py, 100.).unwrap();
924            py_run!(
925                py,
926                dt,
927                "import datetime; assert dt == datetime.date.fromtimestamp(100)"
928            );
929        })
930    }
931
932    #[test]
933    #[cfg(not(Py_LIMITED_API))]
934    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
935    fn test_new_with_fold() {
936        Python::attach(|py| {
937            let a = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
938            let b = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
939
940            assert!(!a.unwrap().get_fold());
941            assert!(b.unwrap().get_fold());
942        });
943    }
944
945    #[test]
946    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
947    fn test_get_tzinfo() {
948        crate::Python::attach(|py| {
949            let utc = PyTzInfo::utc(py).unwrap();
950
951            let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
952
953            assert!(dt.get_tzinfo().unwrap().eq(utc).unwrap());
954
955            let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
956
957            assert!(dt.get_tzinfo().is_none());
958
959            let t = PyTime::new(py, 0, 0, 0, 0, Some(&utc)).unwrap();
960
961            assert!(t.get_tzinfo().unwrap().eq(utc).unwrap());
962
963            let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
964
965            assert!(t.get_tzinfo().is_none());
966        });
967    }
968
969    #[test]
970    #[cfg(all(feature = "macros", feature = "chrono"))]
971    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
972    fn test_timezone_from_offset() {
973        use crate::types::PyNone;
974
975        Python::attach(|py| {
976            assert!(
977                PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, -3600, 0, true).unwrap())
978                    .unwrap()
979                    .call_method1("utcoffset", (PyNone::get(py),))
980                    .unwrap()
981                    .cast_into::<PyDelta>()
982                    .unwrap()
983                    .eq(PyDelta::new(py, 0, -3600, 0, true).unwrap())
984                    .unwrap()
985            );
986
987            assert!(
988                PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, 3600, 0, true).unwrap())
989                    .unwrap()
990                    .call_method1("utcoffset", (PyNone::get(py),))
991                    .unwrap()
992                    .cast_into::<PyDelta>()
993                    .unwrap()
994                    .eq(PyDelta::new(py, 0, 3600, 0, true).unwrap())
995                    .unwrap()
996            );
997
998            PyTzInfo::fixed_offset(py, PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err();
999        })
1000    }
1001}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here