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