1use crate::err::PyResult;
7#[cfg(not(Py_3_9))]
8use crate::exceptions::PyImportError;
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};
21use crate::types::{any::PyAnyMethods, PyString, PyType};
22#[cfg(not(Py_LIMITED_API))]
23use crate::{ffi_ptr_ext::FfiPtrExt, py_result_ext::PyResultExt, types::PyTuple, BoundObject};
24use crate::{sync::GILOnceCell, Py};
25#[cfg(Py_LIMITED_API)]
26use crate::{types::IntoPyDict, PyTypeCheck};
27use crate::{Borrowed, Bound, IntoPyObject, PyAny, PyErr, Python};
28#[cfg(not(Py_LIMITED_API))]
29use std::os::raw::c_int;
30
31#[cfg(not(Py_LIMITED_API))]
32fn ensure_datetime_api(py: Python<'_>) -> PyResult<&'static PyDateTime_CAPI> {
33 if let Some(api) = unsafe { pyo3_ffi::PyDateTimeAPI().as_ref() } {
34 Ok(api)
35 } else {
36 unsafe {
37 PyDateTime_IMPORT();
38 pyo3_ffi::PyDateTimeAPI().as_ref()
39 }
40 .ok_or_else(|| PyErr::fetch(py))
41 }
42}
43
44#[cfg(not(Py_LIMITED_API))]
45fn expect_datetime_api(py: Python<'_>) -> &'static PyDateTime_CAPI {
46 ensure_datetime_api(py).expect("failed to import `datetime` C API")
47}
48
49#[cfg(Py_LIMITED_API)]
50struct DatetimeTypes {
51 date: Py<PyType>,
52 datetime: Py<PyType>,
53 time: Py<PyType>,
54 timedelta: Py<PyType>,
55 timezone: Py<PyType>,
56 tzinfo: Py<PyType>,
57}
58
59#[cfg(Py_LIMITED_API)]
60impl DatetimeTypes {
61 fn get(py: Python<'_>) -> &Self {
62 Self::try_get(py).expect("failed to load datetime module")
63 }
64
65 fn try_get(py: Python<'_>) -> PyResult<&Self> {
66 static TYPES: GILOnceCell<DatetimeTypes> = GILOnceCell::new();
67 TYPES.get_or_try_init(py, || {
68 let datetime = py.import("datetime")?;
69 Ok::<_, PyErr>(Self {
70 date: datetime.getattr("date")?.downcast_into()?.into(),
71 datetime: datetime.getattr("datetime")?.downcast_into()?.into(),
72 time: datetime.getattr("time")?.downcast_into()?.into(),
73 timedelta: datetime.getattr("timedelta")?.downcast_into()?.into(),
74 timezone: datetime.getattr("timezone")?.downcast_into()?.into(),
75 tzinfo: datetime.getattr("tzinfo")?.downcast_into()?.into(),
76 })
77 })
78 }
79}
80
81#[cfg(not(Py_LIMITED_API))]
93macro_rules! ffi_fun_with_autoinit {
94 ($(#[$outer:meta] unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => {
95 $(
96 #[$outer]
97 #[allow(non_snake_case)]
98 unsafe fn $name($arg: *mut crate::ffi::PyObject) -> $ret {
102
103 let _ = ensure_datetime_api(unsafe { Python::assume_gil_acquired() });
104 unsafe { crate::ffi::$name($arg) }
105 }
106 )*
107
108
109 };
110}
111
112#[cfg(not(Py_LIMITED_API))]
113ffi_fun_with_autoinit! {
114 unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
116
117 unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
119
120 unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
122
123 unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
125
126 unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
128}
129
130#[cfg(not(Py_LIMITED_API))]
134pub trait PyDateAccess {
135 fn get_year(&self) -> i32;
140 fn get_month(&self) -> u8;
145 fn get_day(&self) -> u8;
150}
151
152#[cfg(not(Py_LIMITED_API))]
158pub trait PyDeltaAccess {
159 fn get_days(&self) -> i32;
164 fn get_seconds(&self) -> i32;
169 fn get_microseconds(&self) -> i32;
174}
175
176#[cfg(not(Py_LIMITED_API))]
178pub trait PyTimeAccess {
179 fn get_hour(&self) -> u8;
184 fn get_minute(&self) -> u8;
189 fn get_second(&self) -> u8;
194 fn get_microsecond(&self) -> u32;
199 fn get_fold(&self) -> bool;
206}
207
208pub trait PyTzInfoAccess<'py> {
210 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>>;
216}
217
218#[repr(transparent)]
223pub struct PyDate(PyAny);
224
225#[cfg(not(Py_LIMITED_API))]
226pyobject_native_type!(
227 PyDate,
228 crate::ffi::PyDateTime_Date,
229 |py| expect_datetime_api(py).DateType,
230 #module=Some("datetime"),
231 #checkfunction=PyDate_Check
232);
233#[cfg(not(Py_LIMITED_API))]
234pyobject_subclassable_native_type!(PyDate, crate::ffi::PyDateTime_Date);
235
236#[cfg(Py_LIMITED_API)]
237pyobject_native_type_named!(PyDate);
238
239#[cfg(Py_LIMITED_API)]
240impl PyTypeCheck for PyDate {
241 const NAME: &'static str = "PyDate";
242 #[cfg(feature = "experimental-inspect")]
243 const PYTHON_TYPE: &'static str = "datetime.date";
244
245 fn type_check(object: &Bound<'_, PyAny>) -> bool {
246 let py = object.py();
247 DatetimeTypes::try_get(py)
248 .and_then(|module| object.is_instance(module.date.bind(py)))
249 .unwrap_or_default()
250 }
251}
252
253impl PyDate {
254 pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
256 #[cfg(not(Py_LIMITED_API))]
257 {
258 let api = ensure_datetime_api(py)?;
259 unsafe {
260 (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
261 .assume_owned_or_err(py)
262 .downcast_into_unchecked()
263 }
264 }
265 #[cfg(Py_LIMITED_API)]
266 unsafe {
267 Ok(DatetimeTypes::try_get(py)?
268 .date
269 .bind(py)
270 .call((year, month, day), None)?
271 .downcast_into_unchecked())
272 }
273 }
274
275 pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<Bound<'_, PyDate>> {
279 #[cfg(not(Py_LIMITED_API))]
280 {
281 let time_tuple = PyTuple::new(py, [timestamp])?;
282
283 let _api = ensure_datetime_api(py)?;
285
286 unsafe {
287 PyDate_FromTimestamp(time_tuple.as_ptr())
288 .assume_owned_or_err(py)
289 .downcast_into_unchecked()
290 }
291 }
292
293 #[cfg(Py_LIMITED_API)]
294 unsafe {
295 Ok(DatetimeTypes::try_get(py)?
296 .date
297 .bind(py)
298 .call_method1("fromtimestamp", (timestamp,))?
299 .downcast_into_unchecked())
300 }
301 }
302}
303
304#[cfg(not(Py_LIMITED_API))]
305impl PyDateAccess for Bound<'_, PyDate> {
306 fn get_year(&self) -> i32 {
307 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
308 }
309
310 fn get_month(&self) -> u8 {
311 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
312 }
313
314 fn get_day(&self) -> u8 {
315 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
316 }
317}
318
319#[repr(transparent)]
324pub struct PyDateTime(PyAny);
325
326#[cfg(not(Py_LIMITED_API))]
327pyobject_native_type!(
328 PyDateTime,
329 crate::ffi::PyDateTime_DateTime,
330 |py| expect_datetime_api(py).DateTimeType,
331 #module=Some("datetime"),
332 #checkfunction=PyDateTime_Check
333);
334#[cfg(not(Py_LIMITED_API))]
335pyobject_subclassable_native_type!(PyDateTime, crate::ffi::PyDateTime_DateTime);
336
337#[cfg(Py_LIMITED_API)]
338pyobject_native_type_named!(PyDateTime);
339
340#[cfg(Py_LIMITED_API)]
341impl PyTypeCheck for PyDateTime {
342 const NAME: &'static str = "PyDateTime";
343 #[cfg(feature = "experimental-inspect")]
344 const PYTHON_TYPE: &'static str = "datetime.datetime";
345
346 fn type_check(object: &Bound<'_, PyAny>) -> bool {
347 let py = object.py();
348 DatetimeTypes::try_get(py)
349 .and_then(|module| object.is_instance(module.datetime.bind(py)))
350 .unwrap_or_default()
351 }
352}
353
354impl PyDateTime {
355 #[allow(clippy::too_many_arguments)]
357 pub fn new<'py>(
358 py: Python<'py>,
359 year: i32,
360 month: u8,
361 day: u8,
362 hour: u8,
363 minute: u8,
364 second: u8,
365 microsecond: u32,
366 tzinfo: Option<&Bound<'py, PyTzInfo>>,
367 ) -> PyResult<Bound<'py, PyDateTime>> {
368 #[cfg(not(Py_LIMITED_API))]
369 {
370 let api = ensure_datetime_api(py)?;
371 unsafe {
372 (api.DateTime_FromDateAndTime)(
373 year,
374 c_int::from(month),
375 c_int::from(day),
376 c_int::from(hour),
377 c_int::from(minute),
378 c_int::from(second),
379 microsecond as c_int,
380 opt_to_pyobj(tzinfo),
381 api.DateTimeType,
382 )
383 .assume_owned_or_err(py)
384 .downcast_into_unchecked()
385 }
386 }
387
388 #[cfg(Py_LIMITED_API)]
389 unsafe {
390 Ok(DatetimeTypes::try_get(py)?
391 .datetime
392 .bind(py)
393 .call(
394 (year, month, day, hour, minute, second, microsecond, tzinfo),
395 None,
396 )?
397 .downcast_into_unchecked())
398 }
399 }
400
401 #[allow(clippy::too_many_arguments)]
409 pub fn new_with_fold<'py>(
410 py: Python<'py>,
411 year: i32,
412 month: u8,
413 day: u8,
414 hour: u8,
415 minute: u8,
416 second: u8,
417 microsecond: u32,
418 tzinfo: Option<&Bound<'py, PyTzInfo>>,
419 fold: bool,
420 ) -> PyResult<Bound<'py, PyDateTime>> {
421 #[cfg(not(Py_LIMITED_API))]
422 {
423 let api = ensure_datetime_api(py)?;
424 unsafe {
425 (api.DateTime_FromDateAndTimeAndFold)(
426 year,
427 c_int::from(month),
428 c_int::from(day),
429 c_int::from(hour),
430 c_int::from(minute),
431 c_int::from(second),
432 microsecond as c_int,
433 opt_to_pyobj(tzinfo),
434 c_int::from(fold),
435 api.DateTimeType,
436 )
437 .assume_owned_or_err(py)
438 .downcast_into_unchecked()
439 }
440 }
441
442 #[cfg(Py_LIMITED_API)]
443 unsafe {
444 Ok(DatetimeTypes::try_get(py)?
445 .datetime
446 .bind(py)
447 .call(
448 (year, month, day, hour, minute, second, microsecond, tzinfo),
449 Some(&[("fold", fold)].into_py_dict(py)?),
450 )?
451 .downcast_into_unchecked())
452 }
453 }
454
455 pub fn from_timestamp<'py>(
459 py: Python<'py>,
460 timestamp: f64,
461 tzinfo: Option<&Bound<'py, PyTzInfo>>,
462 ) -> PyResult<Bound<'py, PyDateTime>> {
463 #[cfg(not(Py_LIMITED_API))]
464 {
465 let args = (timestamp, tzinfo).into_pyobject(py)?;
466
467 let _api = ensure_datetime_api(py)?;
469
470 unsafe {
471 PyDateTime_FromTimestamp(args.as_ptr())
472 .assume_owned_or_err(py)
473 .downcast_into_unchecked()
474 }
475 }
476
477 #[cfg(Py_LIMITED_API)]
478 unsafe {
479 Ok(DatetimeTypes::try_get(py)?
480 .datetime
481 .bind(py)
482 .call_method1("fromtimestamp", (timestamp, tzinfo))?
483 .downcast_into_unchecked())
484 }
485 }
486}
487
488#[cfg(not(Py_LIMITED_API))]
489impl PyDateAccess for Bound<'_, PyDateTime> {
490 fn get_year(&self) -> i32 {
491 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
492 }
493
494 fn get_month(&self) -> u8 {
495 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
496 }
497
498 fn get_day(&self) -> u8 {
499 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
500 }
501}
502
503#[cfg(not(Py_LIMITED_API))]
504impl PyTimeAccess for Bound<'_, PyDateTime> {
505 fn get_hour(&self) -> u8 {
506 unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u8 }
507 }
508
509 fn get_minute(&self) -> u8 {
510 unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u8 }
511 }
512
513 fn get_second(&self) -> u8 {
514 unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u8 }
515 }
516
517 fn get_microsecond(&self) -> u32 {
518 unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 }
519 }
520
521 fn get_fold(&self) -> bool {
522 unsafe { PyDateTime_DATE_GET_FOLD(self.as_ptr()) > 0 }
523 }
524}
525
526impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyDateTime> {
527 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
528 #[cfg(all(not(Py_3_10), not(Py_LIMITED_API)))]
529 unsafe {
530 let ptr = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
531 if (*ptr).hastzinfo != 0 {
532 Some(
533 (*ptr)
534 .tzinfo
535 .assume_borrowed(self.py())
536 .to_owned()
537 .downcast_into_unchecked(),
538 )
539 } else {
540 None
541 }
542 }
543
544 #[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
545 unsafe {
546 let res = PyDateTime_DATE_GET_TZINFO(self.as_ptr());
547 if Py_IsNone(res) == 1 {
548 None
549 } else {
550 Some(
551 res.assume_borrowed(self.py())
552 .to_owned()
553 .downcast_into_unchecked(),
554 )
555 }
556 }
557
558 #[cfg(Py_LIMITED_API)]
559 unsafe {
560 let tzinfo = self.getattr(intern!(self.py(), "tzinfo")).ok()?;
561 if tzinfo.is_none() {
562 None
563 } else {
564 Some(tzinfo.downcast_into_unchecked())
565 }
566 }
567 }
568}
569
570#[repr(transparent)]
575pub struct PyTime(PyAny);
576
577#[cfg(not(Py_LIMITED_API))]
578pyobject_native_type!(
579 PyTime,
580 crate::ffi::PyDateTime_Time,
581 |py| expect_datetime_api(py).TimeType,
582 #module=Some("datetime"),
583 #checkfunction=PyTime_Check
584);
585#[cfg(not(Py_LIMITED_API))]
586pyobject_subclassable_native_type!(PyTime, crate::ffi::PyDateTime_Time);
587
588#[cfg(Py_LIMITED_API)]
589pyobject_native_type_named!(PyTime);
590
591#[cfg(Py_LIMITED_API)]
592impl PyTypeCheck for PyTime {
593 const NAME: &'static str = "PyTime";
594 #[cfg(feature = "experimental-inspect")]
595 const PYTHON_TYPE: &'static str = "datetime.time";
596
597 fn type_check(object: &Bound<'_, PyAny>) -> bool {
598 let py = object.py();
599 DatetimeTypes::try_get(py)
600 .and_then(|module| object.is_instance(module.time.bind(py)))
601 .unwrap_or_default()
602 }
603}
604
605impl PyTime {
606 pub fn new<'py>(
608 py: Python<'py>,
609 hour: u8,
610 minute: u8,
611 second: u8,
612 microsecond: u32,
613 tzinfo: Option<&Bound<'py, PyTzInfo>>,
614 ) -> PyResult<Bound<'py, PyTime>> {
615 #[cfg(not(Py_LIMITED_API))]
616 {
617 let api = ensure_datetime_api(py)?;
618 unsafe {
619 (api.Time_FromTime)(
620 c_int::from(hour),
621 c_int::from(minute),
622 c_int::from(second),
623 microsecond as c_int,
624 opt_to_pyobj(tzinfo),
625 api.TimeType,
626 )
627 .assume_owned_or_err(py)
628 .downcast_into_unchecked()
629 }
630 }
631
632 #[cfg(Py_LIMITED_API)]
633 unsafe {
634 Ok(DatetimeTypes::try_get(py)?
635 .time
636 .bind(py)
637 .call((hour, minute, second, microsecond, tzinfo), None)?
638 .downcast_into_unchecked())
639 }
640 }
641
642 pub fn new_with_fold<'py>(
644 py: Python<'py>,
645 hour: u8,
646 minute: u8,
647 second: u8,
648 microsecond: u32,
649 tzinfo: Option<&Bound<'py, PyTzInfo>>,
650 fold: bool,
651 ) -> PyResult<Bound<'py, PyTime>> {
652 #[cfg(not(Py_LIMITED_API))]
653 {
654 let api = ensure_datetime_api(py)?;
655 unsafe {
656 (api.Time_FromTimeAndFold)(
657 c_int::from(hour),
658 c_int::from(minute),
659 c_int::from(second),
660 microsecond as c_int,
661 opt_to_pyobj(tzinfo),
662 fold as c_int,
663 api.TimeType,
664 )
665 .assume_owned_or_err(py)
666 .downcast_into_unchecked()
667 }
668 }
669
670 #[cfg(Py_LIMITED_API)]
671 #[cfg(Py_LIMITED_API)]
672 unsafe {
673 Ok(DatetimeTypes::try_get(py)?
674 .time
675 .bind(py)
676 .call(
677 (hour, minute, second, microsecond, tzinfo),
678 Some(&[("fold", fold)].into_py_dict(py)?),
679 )?
680 .downcast_into_unchecked())
681 }
682 }
683}
684
685#[cfg(not(Py_LIMITED_API))]
686impl PyTimeAccess for Bound<'_, PyTime> {
687 fn get_hour(&self) -> u8 {
688 unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 }
689 }
690
691 fn get_minute(&self) -> u8 {
692 unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 }
693 }
694
695 fn get_second(&self) -> u8 {
696 unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 }
697 }
698
699 fn get_microsecond(&self) -> u32 {
700 unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 }
701 }
702
703 fn get_fold(&self) -> bool {
704 unsafe { PyDateTime_TIME_GET_FOLD(self.as_ptr()) != 0 }
705 }
706}
707
708impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyTime> {
709 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
710 #[cfg(all(not(Py_3_10), not(Py_LIMITED_API)))]
711 unsafe {
712 let ptr = self.as_ptr() as *mut ffi::PyDateTime_Time;
713 if (*ptr).hastzinfo != 0 {
714 Some(
715 (*ptr)
716 .tzinfo
717 .assume_borrowed(self.py())
718 .to_owned()
719 .downcast_into_unchecked(),
720 )
721 } else {
722 None
723 }
724 }
725
726 #[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
727 unsafe {
728 let res = PyDateTime_TIME_GET_TZINFO(self.as_ptr());
729 if Py_IsNone(res) == 1 {
730 None
731 } else {
732 Some(
733 res.assume_borrowed(self.py())
734 .to_owned()
735 .downcast_into_unchecked(),
736 )
737 }
738 }
739
740 #[cfg(Py_LIMITED_API)]
741 unsafe {
742 let tzinfo = self.getattr(intern!(self.py(), "tzinfo")).ok()?;
743 if tzinfo.is_none() {
744 None
745 } else {
746 Some(tzinfo.downcast_into_unchecked())
747 }
748 }
749 }
750}
751
752#[repr(transparent)]
761pub struct PyTzInfo(PyAny);
762
763#[cfg(not(Py_LIMITED_API))]
764pyobject_native_type!(
765 PyTzInfo,
766 crate::ffi::PyObject,
767 |py| expect_datetime_api(py).TZInfoType,
768 #module=Some("datetime"),
769 #checkfunction=PyTZInfo_Check
770);
771#[cfg(not(Py_LIMITED_API))]
772pyobject_subclassable_native_type!(PyTzInfo, crate::ffi::PyObject);
773
774#[cfg(Py_LIMITED_API)]
775pyobject_native_type_named!(PyTzInfo);
776
777#[cfg(Py_LIMITED_API)]
778impl PyTypeCheck for PyTzInfo {
779 const NAME: &'static str = "PyTzInfo";
780 #[cfg(feature = "experimental-inspect")]
781 const PYTHON_TYPE: &'static str = "datetime.tzinfo";
782
783 fn type_check(object: &Bound<'_, PyAny>) -> bool {
784 let py = object.py();
785 DatetimeTypes::try_get(py)
786 .and_then(|module| object.is_instance(module.tzinfo.bind(py)))
787 .unwrap_or_default()
788 }
789}
790
791impl PyTzInfo {
792 pub fn utc(py: Python<'_>) -> PyResult<Borrowed<'static, '_, PyTzInfo>> {
794 #[cfg(not(Py_LIMITED_API))]
795 unsafe {
796 Ok(ensure_datetime_api(py)?
797 .TimeZone_UTC
798 .assume_borrowed(py)
799 .downcast_unchecked())
800 }
801
802 #[cfg(Py_LIMITED_API)]
803 {
804 static UTC: GILOnceCell<Py<PyTzInfo>> = GILOnceCell::new();
805 UTC.get_or_try_init(py, || {
806 Ok(DatetimeTypes::get(py)
807 .timezone
808 .bind(py)
809 .getattr("utc")?
810 .downcast_into()?
811 .unbind())
812 })
813 .map(|utc| utc.bind_borrowed(py))
814 }
815 }
816
817 pub fn timezone<'py, T>(py: Python<'py>, iana_name: T) -> PyResult<Bound<'py, PyTzInfo>>
819 where
820 T: IntoPyObject<'py, Target = PyString>,
821 {
822 static ZONE_INFO: GILOnceCell<Py<PyType>> = GILOnceCell::new();
823
824 let zoneinfo = ZONE_INFO.import(py, "zoneinfo", "ZoneInfo");
825
826 #[cfg(not(Py_3_9))]
827 let zoneinfo = zoneinfo
828 .or_else(|_| ZONE_INFO.import(py, "backports.zoneinfo", "ZoneInfo"))
829 .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"));
830
831 zoneinfo?
832 .call1((iana_name,))?
833 .downcast_into()
834 .map_err(Into::into)
835 }
836
837 pub fn fixed_offset<'py, T>(py: Python<'py>, offset: T) -> PyResult<Bound<'py, PyTzInfo>>
839 where
840 T: IntoPyObject<'py, Target = PyDelta>,
841 {
842 #[cfg(not(Py_LIMITED_API))]
843 {
844 let api = ensure_datetime_api(py)?;
845 let delta = offset.into_pyobject(py).map_err(Into::into)?;
846 unsafe {
847 (api.TimeZone_FromTimeZone)(delta.as_ptr(), std::ptr::null_mut())
848 .assume_owned_or_err(py)
849 .downcast_into_unchecked()
850 }
851 }
852
853 #[cfg(Py_LIMITED_API)]
854 unsafe {
855 Ok(DatetimeTypes::try_get(py)?
856 .timezone
857 .bind(py)
858 .call1((offset,))?
859 .downcast_into_unchecked())
860 }
861 }
862}
863
864#[deprecated(since = "0.25.0", note = "use `PyTzInfo::utc` instead")]
866pub fn timezone_utc(py: Python<'_>) -> Bound<'_, PyTzInfo> {
867 PyTzInfo::utc(py)
868 .expect("failed to import datetime.timezone.utc")
869 .to_owned()
870}
871
872#[repr(transparent)]
877pub struct PyDelta(PyAny);
878
879#[cfg(not(Py_LIMITED_API))]
880pyobject_native_type!(
881 PyDelta,
882 crate::ffi::PyDateTime_Delta,
883 |py| expect_datetime_api(py).DeltaType,
884 #module=Some("datetime"),
885 #checkfunction=PyDelta_Check
886);
887#[cfg(not(Py_LIMITED_API))]
888pyobject_subclassable_native_type!(PyDelta, crate::ffi::PyDateTime_Delta);
889
890#[cfg(Py_LIMITED_API)]
891pyobject_native_type_named!(PyDelta);
892
893#[cfg(Py_LIMITED_API)]
894impl PyTypeCheck for PyDelta {
895 const NAME: &'static str = "PyDelta";
896 #[cfg(feature = "experimental-inspect")]
897 const PYTHON_TYPE: &'static str = "datetime.timedelta";
898
899 fn type_check(object: &Bound<'_, PyAny>) -> bool {
900 let py = object.py();
901 DatetimeTypes::try_get(py)
902 .and_then(|module| object.is_instance(module.timedelta.bind(py)))
903 .unwrap_or_default()
904 }
905}
906
907impl PyDelta {
908 pub fn new(
910 py: Python<'_>,
911 days: i32,
912 seconds: i32,
913 microseconds: i32,
914 normalize: bool,
915 ) -> PyResult<Bound<'_, PyDelta>> {
916 #[cfg(not(Py_LIMITED_API))]
917 {
918 let api = ensure_datetime_api(py)?;
919 unsafe {
920 (api.Delta_FromDelta)(
921 days as c_int,
922 seconds as c_int,
923 microseconds as c_int,
924 normalize as c_int,
925 api.DeltaType,
926 )
927 .assume_owned_or_err(py)
928 .downcast_into_unchecked()
929 }
930 }
931
932 #[cfg(Py_LIMITED_API)]
933 unsafe {
934 let _ = normalize;
935 Ok(DatetimeTypes::try_get(py)?
936 .timedelta
937 .bind(py)
938 .call1((days, seconds, microseconds))?
939 .downcast_into_unchecked())
940 }
941 }
942}
943
944#[cfg(not(Py_LIMITED_API))]
945impl PyDeltaAccess for Bound<'_, PyDelta> {
946 fn get_days(&self) -> i32 {
947 unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
948 }
949
950 fn get_seconds(&self) -> i32 {
951 unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
952 }
953
954 fn get_microseconds(&self) -> i32 {
955 unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
956 }
957}
958
959#[cfg(not(Py_LIMITED_API))]
962fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
963 match opt {
964 Some(tzi) => tzi.as_ptr(),
965 None => unsafe { ffi::Py_None() },
966 }
967}
968
969#[cfg(test)]
970mod tests {
971 use super::*;
972 #[cfg(feature = "macros")]
973 use crate::py_run;
974
975 #[test]
976 #[cfg(feature = "macros")]
977 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_datetime_fromtimestamp() {
979 Python::attach(|py| {
980 let dt = PyDateTime::from_timestamp(py, 100.0, None).unwrap();
981 py_run!(
982 py,
983 dt,
984 "import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
985 );
986
987 let utc = PyTzInfo::utc(py).unwrap();
988 let dt = PyDateTime::from_timestamp(py, 100.0, Some(&utc)).unwrap();
989 py_run!(
990 py,
991 dt,
992 "import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
993 );
994 })
995 }
996
997 #[test]
998 #[cfg(feature = "macros")]
999 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_date_fromtimestamp() {
1001 Python::attach(|py| {
1002 let dt = PyDate::from_timestamp(py, 100).unwrap();
1003 py_run!(
1004 py,
1005 dt,
1006 "import datetime; assert dt == datetime.date.fromtimestamp(100)"
1007 );
1008 })
1009 }
1010
1011 #[test]
1012 #[cfg(not(Py_LIMITED_API))]
1013 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_new_with_fold() {
1015 Python::attach(|py| {
1016 let a = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
1017 let b = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
1018
1019 assert!(!a.unwrap().get_fold());
1020 assert!(b.unwrap().get_fold());
1021 });
1022 }
1023
1024 #[test]
1025 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_get_tzinfo() {
1027 crate::Python::attach(|py| {
1028 let utc = PyTzInfo::utc(py).unwrap();
1029
1030 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
1031
1032 assert!(dt.get_tzinfo().unwrap().eq(utc).unwrap());
1033
1034 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
1035
1036 assert!(dt.get_tzinfo().is_none());
1037
1038 let t = PyTime::new(py, 0, 0, 0, 0, Some(&utc)).unwrap();
1039
1040 assert!(t.get_tzinfo().unwrap().eq(utc).unwrap());
1041
1042 let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
1043
1044 assert!(t.get_tzinfo().is_none());
1045 });
1046 }
1047
1048 #[test]
1049 #[cfg(all(feature = "macros", feature = "chrono"))]
1050 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_timezone_from_offset() {
1052 use crate::types::PyNone;
1053
1054 Python::attach(|py| {
1055 assert!(
1056 PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, -3600, 0, true).unwrap())
1057 .unwrap()
1058 .call_method1("utcoffset", (PyNone::get(py),))
1059 .unwrap()
1060 .downcast_into::<PyDelta>()
1061 .unwrap()
1062 .eq(PyDelta::new(py, 0, -3600, 0, true).unwrap())
1063 .unwrap()
1064 );
1065
1066 assert!(
1067 PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, 3600, 0, true).unwrap())
1068 .unwrap()
1069 .call_method1("utcoffset", (PyNone::get(py),))
1070 .unwrap()
1071 .downcast_into::<PyDelta>()
1072 .unwrap()
1073 .eq(PyDelta::new(py, 0, 3600, 0, true).unwrap())
1074 .unwrap()
1075 );
1076
1077 PyTzInfo::fixed_offset(py, PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err();
1078 })
1079 }
1080}