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