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 and cannot be constructed directly.
696/// For concrete time zone implementations, see [`timezone_utc`] and
697/// the [`zoneinfo` module](https://docs.python.org/3/library/zoneinfo.html).
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);
711#[cfg(not(Py_LIMITED_API))]
712pyobject_subclassable_native_type!(PyTzInfo, crate::ffi::PyObject);
713
714#[cfg(Py_LIMITED_API)]
715pyobject_native_type_core!(
716    PyTzInfo,
717    |py| {
718        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
719        TYPE.import(py, "datetime", "tzinfo").unwrap().as_type_ptr()
720    },
721    "datetime",
722    "tzinfo",
723    #module=Some("datetime")
724);
725
726impl PyTzInfo {
727    /// Equivalent to `datetime.timezone.utc`
728    pub fn utc(py: Python<'_>) -> PyResult<Borrowed<'static, '_, PyTzInfo>> {
729        #[cfg(not(Py_LIMITED_API))]
730        unsafe {
731            Ok(ensure_datetime_api(py)?
732                .TimeZone_UTC
733                .assume_borrowed(py)
734                .cast_unchecked())
735        }
736
737        #[cfg(Py_LIMITED_API)]
738        {
739            static UTC: PyOnceLock<Py<PyTzInfo>> = PyOnceLock::new();
740            UTC.get_or_try_init(py, || {
741                Ok(py
742                    .import("datetime")?
743                    .getattr("timezone")?
744                    .getattr("utc")?
745                    .cast_into()?
746                    .unbind())
747            })
748            .map(|utc| utc.bind_borrowed(py))
749        }
750    }
751
752    /// Equivalent to `zoneinfo.ZoneInfo` constructor
753    pub fn timezone<'py, T>(py: Python<'py>, iana_name: T) -> PyResult<Bound<'py, PyTzInfo>>
754    where
755        T: IntoPyObject<'py, Target = PyString>,
756    {
757        static ZONE_INFO: PyOnceLock<Py<PyType>> = PyOnceLock::new();
758
759        let zoneinfo = ZONE_INFO.import(py, "zoneinfo", "ZoneInfo");
760
761        #[cfg(not(Py_3_9))]
762        let zoneinfo = zoneinfo
763            .or_else(|_| ZONE_INFO.import(py, "backports.zoneinfo", "ZoneInfo"))
764            .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"));
765
766        zoneinfo?
767            .call1((iana_name,))?
768            .cast_into()
769            .map_err(Into::into)
770    }
771
772    /// Equivalent to `datetime.timezone` constructor
773    pub fn fixed_offset<'py, T>(py: Python<'py>, offset: T) -> PyResult<Bound<'py, PyTzInfo>>
774    where
775        T: IntoPyObject<'py, Target = PyDelta>,
776    {
777        #[cfg(not(Py_LIMITED_API))]
778        {
779            let api = ensure_datetime_api(py)?;
780            let delta = offset.into_pyobject(py).map_err(Into::into)?;
781            unsafe {
782                (api.TimeZone_FromTimeZone)(delta.as_ptr(), std::ptr::null_mut())
783                    .assume_owned_or_err(py)
784                    .cast_into_unchecked()
785            }
786        }
787
788        #[cfg(Py_LIMITED_API)]
789        {
790            static TIMEZONE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
791            Ok(TIMEZONE
792                .import(py, "datetime", "timezone")?
793                .call1((offset,))?
794                .cast_into()?)
795        }
796    }
797}
798
799/// Equivalent to `datetime.timezone.utc`
800#[deprecated(since = "0.25.0", note = "use `PyTzInfo::utc` instead")]
801pub fn timezone_utc(py: Python<'_>) -> Bound<'_, PyTzInfo> {
802    PyTzInfo::utc(py)
803        .expect("failed to import datetime.timezone.utc")
804        .to_owned()
805}
806
807/// Bindings for `datetime.timedelta`.
808///
809/// Values of this type are accessed via PyO3's smart pointers, e.g. as
810/// [`Py<PyDelta>`][crate::Py] or [`Bound<'py, PyDelta>`][Bound].
811#[repr(transparent)]
812pub struct PyDelta(PyAny);
813
814#[cfg(not(Py_LIMITED_API))]
815pyobject_native_type!(
816    PyDelta,
817    crate::ffi::PyDateTime_Delta,
818    |py| expect_datetime_api(py).DeltaType,
819    "datetime",
820    "timedelta",
821    #module=Some("datetime"),
822    #checkfunction=PyDelta_Check
823);
824#[cfg(not(Py_LIMITED_API))]
825pyobject_subclassable_native_type!(PyDelta, crate::ffi::PyDateTime_Delta);
826
827#[cfg(Py_LIMITED_API)]
828pyobject_native_type_core!(
829    PyDelta,
830    |py| {
831        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
832        TYPE.import(py, "datetime", "timedelta")
833            .unwrap()
834            .as_type_ptr()
835    },
836    "datetime",
837    "timedelta",
838    #module=Some("datetime")
839);
840
841impl PyDelta {
842    /// Creates a new `timedelta`.
843    pub fn new(
844        py: Python<'_>,
845        days: i32,
846        seconds: i32,
847        microseconds: i32,
848        normalize: bool,
849    ) -> PyResult<Bound<'_, PyDelta>> {
850        #[cfg(not(Py_LIMITED_API))]
851        {
852            let api = ensure_datetime_api(py)?;
853            unsafe {
854                (api.Delta_FromDelta)(
855                    days as c_int,
856                    seconds as c_int,
857                    microseconds as c_int,
858                    normalize as c_int,
859                    api.DeltaType,
860                )
861                .assume_owned_or_err(py)
862                .cast_into_unchecked()
863            }
864        }
865
866        #[cfg(Py_LIMITED_API)]
867        let _ = normalize;
868        #[cfg(Py_LIMITED_API)]
869        Ok(Self::type_object(py)
870            .call1((days, seconds, microseconds))?
871            .cast_into()?)
872    }
873}
874
875#[cfg(not(Py_LIMITED_API))]
876impl PyDeltaAccess for Bound<'_, PyDelta> {
877    fn get_days(&self) -> i32 {
878        unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
879    }
880
881    fn get_seconds(&self) -> i32 {
882        unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
883    }
884
885    fn get_microseconds(&self) -> i32 {
886        unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
887    }
888}
889
890// Utility function which returns a borrowed reference to either
891// the underlying tzinfo or None.
892#[cfg(not(Py_LIMITED_API))]
893fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
894    match opt {
895        Some(tzi) => tzi.as_ptr(),
896        None => unsafe { ffi::Py_None() },
897    }
898}
899
900#[cfg(test)]
901mod tests {
902    use super::*;
903    #[cfg(feature = "macros")]
904    use crate::py_run;
905
906    #[test]
907    #[cfg(feature = "macros")]
908    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
909    fn test_datetime_fromtimestamp() {
910        Python::attach(|py| {
911            let dt = PyDateTime::from_timestamp(py, 100.0, None).unwrap();
912            py_run!(
913                py,
914                dt,
915                "import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
916            );
917
918            let utc = PyTzInfo::utc(py).unwrap();
919            let dt = PyDateTime::from_timestamp(py, 100.0, Some(&utc)).unwrap();
920            py_run!(
921                py,
922                dt,
923                "import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
924            );
925        })
926    }
927
928    #[test]
929    #[cfg(feature = "macros")]
930    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
931    fn test_date_fromtimestamp() {
932        Python::attach(|py| {
933            let dt = PyDate::from_timestamp(py, 100).unwrap();
934            py_run!(
935                py,
936                dt,
937                "import datetime; assert dt == datetime.date.fromtimestamp(100)"
938            );
939        })
940    }
941
942    #[test]
943    #[cfg(not(Py_LIMITED_API))]
944    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
945    fn test_new_with_fold() {
946        Python::attach(|py| {
947            let a = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
948            let b = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
949
950            assert!(!a.unwrap().get_fold());
951            assert!(b.unwrap().get_fold());
952        });
953    }
954
955    #[test]
956    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
957    fn test_get_tzinfo() {
958        crate::Python::attach(|py| {
959            let utc = PyTzInfo::utc(py).unwrap();
960
961            let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
962
963            assert!(dt.get_tzinfo().unwrap().eq(utc).unwrap());
964
965            let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
966
967            assert!(dt.get_tzinfo().is_none());
968
969            let t = PyTime::new(py, 0, 0, 0, 0, Some(&utc)).unwrap();
970
971            assert!(t.get_tzinfo().unwrap().eq(utc).unwrap());
972
973            let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
974
975            assert!(t.get_tzinfo().is_none());
976        });
977    }
978
979    #[test]
980    #[cfg(all(feature = "macros", feature = "chrono"))]
981    #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
982    fn test_timezone_from_offset() {
983        use crate::types::PyNone;
984
985        Python::attach(|py| {
986            assert!(
987                PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, -3600, 0, true).unwrap())
988                    .unwrap()
989                    .call_method1("utcoffset", (PyNone::get(py),))
990                    .unwrap()
991                    .cast_into::<PyDelta>()
992                    .unwrap()
993                    .eq(PyDelta::new(py, 0, -3600, 0, true).unwrap())
994                    .unwrap()
995            );
996
997            assert!(
998                PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, 3600, 0, true).unwrap())
999                    .unwrap()
1000                    .call_method1("utcoffset", (PyNone::get(py),))
1001                    .unwrap()
1002                    .cast_into::<PyDelta>()
1003                    .unwrap()
1004                    .eq(PyDelta::new(py, 0, 3600, 0, true).unwrap())
1005                    .unwrap()
1006            );
1007
1008            PyTzInfo::fixed_offset(py, PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err();
1009        })
1010    }
1011}