1use crate::err::PyResult;
7use crate::ffi::{
8 self, PyDateTime_CAPI, PyDateTime_FromTimestamp, PyDateTime_IMPORT, PyDate_FromTimestamp,
9};
10use crate::ffi::{
11 PyDateTime_DATE_GET_FOLD, PyDateTime_DATE_GET_HOUR, PyDateTime_DATE_GET_MICROSECOND,
12 PyDateTime_DATE_GET_MINUTE, PyDateTime_DATE_GET_SECOND,
13};
14#[cfg(GraalPy)]
15use crate::ffi::{PyDateTime_DATE_GET_TZINFO, PyDateTime_TIME_GET_TZINFO, Py_IsNone};
16use crate::ffi::{
17 PyDateTime_DELTA_GET_DAYS, PyDateTime_DELTA_GET_MICROSECONDS, PyDateTime_DELTA_GET_SECONDS,
18};
19use crate::ffi::{PyDateTime_GET_DAY, PyDateTime_GET_MONTH, PyDateTime_GET_YEAR};
20use crate::ffi::{
21 PyDateTime_TIME_GET_FOLD, PyDateTime_TIME_GET_HOUR, PyDateTime_TIME_GET_MICROSECOND,
22 PyDateTime_TIME_GET_MINUTE, PyDateTime_TIME_GET_SECOND,
23};
24use crate::ffi_ptr_ext::FfiPtrExt;
25#[cfg(feature = "gil-refs")]
26use crate::instance::PyNativeType;
27use crate::py_result_ext::PyResultExt;
28use crate::types::any::PyAnyMethods;
29use crate::types::PyTuple;
30use crate::{Bound, IntoPy, Py, PyAny, PyErr, Python};
31use std::os::raw::c_int;
32#[cfg(feature = "chrono")]
33use std::ptr;
34
35fn ensure_datetime_api(py: Python<'_>) -> PyResult<&'static PyDateTime_CAPI> {
36 if let Some(api) = unsafe { pyo3_ffi::PyDateTimeAPI().as_ref() } {
37 Ok(api)
38 } else {
39 unsafe {
40 PyDateTime_IMPORT();
41 pyo3_ffi::PyDateTimeAPI().as_ref()
42 }
43 .ok_or_else(|| PyErr::fetch(py))
44 }
45}
46
47fn expect_datetime_api(py: Python<'_>) -> &'static PyDateTime_CAPI {
48 ensure_datetime_api(py).expect("failed to import `datetime` C API")
49}
50
51macro_rules! ffi_fun_with_autoinit {
64 ($(#[$outer:meta] unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => {
65 $(
66 #[$outer]
67 #[allow(non_snake_case)]
68 unsafe fn $name($arg: *mut crate::ffi::PyObject) -> $ret {
72
73 let _ = ensure_datetime_api(Python::assume_gil_acquired());
74 crate::ffi::$name($arg)
75 }
76 )*
77
78
79 };
80}
81
82ffi_fun_with_autoinit! {
83 unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
85
86 unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
88
89 unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
91
92 unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
94
95 unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
97}
98
99pub trait PyDateAccess {
103 fn get_year(&self) -> i32;
108 fn get_month(&self) -> u8;
113 fn get_day(&self) -> u8;
118}
119
120pub trait PyDeltaAccess {
126 fn get_days(&self) -> i32;
131 fn get_seconds(&self) -> i32;
136 fn get_microseconds(&self) -> i32;
141}
142
143pub trait PyTimeAccess {
145 fn get_hour(&self) -> u8;
150 fn get_minute(&self) -> u8;
155 fn get_second(&self) -> u8;
160 fn get_microsecond(&self) -> u32;
165 fn get_fold(&self) -> bool;
172}
173
174pub trait PyTzInfoAccess<'py> {
176 #[cfg(feature = "gil-refs")]
178 #[deprecated(
179 since = "0.21.0",
180 note = "`get_tzinfo` will be replaced by `get_tzinfo_bound` in a future PyO3 version"
181 )]
182 fn get_tzinfo(&self) -> Option<&'py PyTzInfo> {
183 self.get_tzinfo_bound().map(Bound::into_gil_ref)
184 }
185
186 fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>>;
192}
193
194#[repr(transparent)]
199pub struct PyDate(PyAny);
200pyobject_native_type!(
201 PyDate,
202 crate::ffi::PyDateTime_Date,
203 |py| expect_datetime_api(py).DateType,
204 #module=Some("datetime"),
205 #checkfunction=PyDate_Check
206);
207
208impl PyDate {
209 #[cfg(feature = "gil-refs")]
211 #[deprecated(
212 since = "0.21.0",
213 note = "`PyDate::new` will be replaced by `PyDate::new_bound` in a future PyO3 version"
214 )]
215 pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate> {
216 Self::new_bound(py, year, month, day).map(Bound::into_gil_ref)
217 }
218
219 pub fn new_bound(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
221 let api = ensure_datetime_api(py)?;
222 unsafe {
223 (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
224 .assume_owned_or_err(py)
225 .downcast_into_unchecked()
226 }
227 }
228
229 #[cfg(feature = "gil-refs")]
231 #[deprecated(
232 since = "0.21.0",
233 note = "`PyDate::from_timestamp` will be replaced by `PyDate::from_timestamp_bound` in a future PyO3 version"
234 )]
235 pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> {
236 Self::from_timestamp_bound(py, timestamp).map(Bound::into_gil_ref)
237 }
238
239 pub fn from_timestamp_bound(py: Python<'_>, timestamp: i64) -> PyResult<Bound<'_, PyDate>> {
243 let time_tuple = PyTuple::new_bound(py, [timestamp]);
244
245 let _api = ensure_datetime_api(py)?;
247
248 unsafe {
249 PyDate_FromTimestamp(time_tuple.as_ptr())
250 .assume_owned_or_err(py)
251 .downcast_into_unchecked()
252 }
253 }
254}
255
256#[cfg(feature = "gil-refs")]
257impl PyDateAccess for PyDate {
258 fn get_year(&self) -> i32 {
259 self.as_borrowed().get_year()
260 }
261
262 fn get_month(&self) -> u8 {
263 self.as_borrowed().get_month()
264 }
265
266 fn get_day(&self) -> u8 {
267 self.as_borrowed().get_day()
268 }
269}
270
271impl PyDateAccess for Bound<'_, PyDate> {
272 fn get_year(&self) -> i32 {
273 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
274 }
275
276 fn get_month(&self) -> u8 {
277 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
278 }
279
280 fn get_day(&self) -> u8 {
281 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
282 }
283}
284
285#[repr(transparent)]
290pub struct PyDateTime(PyAny);
291pyobject_native_type!(
292 PyDateTime,
293 crate::ffi::PyDateTime_DateTime,
294 |py| expect_datetime_api(py).DateTimeType,
295 #module=Some("datetime"),
296 #checkfunction=PyDateTime_Check
297);
298
299impl PyDateTime {
300 #[cfg(feature = "gil-refs")]
302 #[deprecated(
303 since = "0.21.0",
304 note = "`PyDateTime::new` will be replaced by `PyDateTime::new_bound` in a future PyO3 version"
305 )]
306 #[allow(clippy::too_many_arguments)]
307 pub fn new<'py>(
308 py: Python<'py>,
309 year: i32,
310 month: u8,
311 day: u8,
312 hour: u8,
313 minute: u8,
314 second: u8,
315 microsecond: u32,
316 tzinfo: Option<&'py PyTzInfo>,
317 ) -> PyResult<&'py PyDateTime> {
318 Self::new_bound(
319 py,
320 year,
321 month,
322 day,
323 hour,
324 minute,
325 second,
326 microsecond,
327 tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
328 )
329 .map(Bound::into_gil_ref)
330 }
331
332 #[allow(clippy::too_many_arguments)]
334 pub fn new_bound<'py>(
335 py: Python<'py>,
336 year: i32,
337 month: u8,
338 day: u8,
339 hour: u8,
340 minute: u8,
341 second: u8,
342 microsecond: u32,
343 tzinfo: Option<&Bound<'py, PyTzInfo>>,
344 ) -> PyResult<Bound<'py, PyDateTime>> {
345 let api = ensure_datetime_api(py)?;
346 unsafe {
347 (api.DateTime_FromDateAndTime)(
348 year,
349 c_int::from(month),
350 c_int::from(day),
351 c_int::from(hour),
352 c_int::from(minute),
353 c_int::from(second),
354 microsecond as c_int,
355 opt_to_pyobj(tzinfo),
356 api.DateTimeType,
357 )
358 .assume_owned_or_err(py)
359 .downcast_into_unchecked()
360 }
361 }
362
363 #[cfg(feature = "gil-refs")]
365 #[deprecated(
366 since = "0.21.0",
367 note = "`PyDateTime::new_with_fold` will be replaced by `PyDateTime::new_bound_with_fold` in a future PyO3 version"
368 )]
369 #[allow(clippy::too_many_arguments)]
370 pub fn new_with_fold<'py>(
371 py: Python<'py>,
372 year: i32,
373 month: u8,
374 day: u8,
375 hour: u8,
376 minute: u8,
377 second: u8,
378 microsecond: u32,
379 tzinfo: Option<&'py PyTzInfo>,
380 fold: bool,
381 ) -> PyResult<&'py PyDateTime> {
382 Self::new_bound_with_fold(
383 py,
384 year,
385 month,
386 day,
387 hour,
388 minute,
389 second,
390 microsecond,
391 tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
392 fold,
393 )
394 .map(Bound::into_gil_ref)
395 }
396
397 #[allow(clippy::too_many_arguments)]
405 pub fn new_bound_with_fold<'py>(
406 py: Python<'py>,
407 year: i32,
408 month: u8,
409 day: u8,
410 hour: u8,
411 minute: u8,
412 second: u8,
413 microsecond: u32,
414 tzinfo: Option<&Bound<'py, PyTzInfo>>,
415 fold: bool,
416 ) -> PyResult<Bound<'py, PyDateTime>> {
417 let api = ensure_datetime_api(py)?;
418 unsafe {
419 (api.DateTime_FromDateAndTimeAndFold)(
420 year,
421 c_int::from(month),
422 c_int::from(day),
423 c_int::from(hour),
424 c_int::from(minute),
425 c_int::from(second),
426 microsecond as c_int,
427 opt_to_pyobj(tzinfo),
428 c_int::from(fold),
429 api.DateTimeType,
430 )
431 .assume_owned_or_err(py)
432 .downcast_into_unchecked()
433 }
434 }
435
436 #[cfg(feature = "gil-refs")]
438 #[deprecated(
439 since = "0.21.0",
440 note = "`PyDateTime::from_timestamp` will be replaced by `PyDateTime::from_timestamp_bound` in a future PyO3 version"
441 )]
442 pub fn from_timestamp<'py>(
443 py: Python<'py>,
444 timestamp: f64,
445 tzinfo: Option<&'py PyTzInfo>,
446 ) -> PyResult<&'py PyDateTime> {
447 Self::from_timestamp_bound(py, timestamp, tzinfo.map(PyTzInfo::as_borrowed).as_deref())
448 .map(Bound::into_gil_ref)
449 }
450
451 pub fn from_timestamp_bound<'py>(
455 py: Python<'py>,
456 timestamp: f64,
457 tzinfo: Option<&Bound<'py, PyTzInfo>>,
458 ) -> PyResult<Bound<'py, PyDateTime>> {
459 let args = IntoPy::<Py<PyTuple>>::into_py((timestamp, tzinfo), py).into_bound(py);
460
461 let _api = ensure_datetime_api(py)?;
463
464 unsafe {
465 PyDateTime_FromTimestamp(args.as_ptr())
466 .assume_owned_or_err(py)
467 .downcast_into_unchecked()
468 }
469 }
470}
471
472#[cfg(feature = "gil-refs")]
473impl PyDateAccess for PyDateTime {
474 fn get_year(&self) -> i32 {
475 self.as_borrowed().get_year()
476 }
477
478 fn get_month(&self) -> u8 {
479 self.as_borrowed().get_month()
480 }
481
482 fn get_day(&self) -> u8 {
483 self.as_borrowed().get_day()
484 }
485}
486
487impl PyDateAccess for Bound<'_, PyDateTime> {
488 fn get_year(&self) -> i32 {
489 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
490 }
491
492 fn get_month(&self) -> u8 {
493 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
494 }
495
496 fn get_day(&self) -> u8 {
497 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
498 }
499}
500
501#[cfg(feature = "gil-refs")]
502impl PyTimeAccess for PyDateTime {
503 fn get_hour(&self) -> u8 {
504 self.as_borrowed().get_hour()
505 }
506
507 fn get_minute(&self) -> u8 {
508 self.as_borrowed().get_minute()
509 }
510
511 fn get_second(&self) -> u8 {
512 self.as_borrowed().get_second()
513 }
514
515 fn get_microsecond(&self) -> u32 {
516 self.as_borrowed().get_microsecond()
517 }
518
519 fn get_fold(&self) -> bool {
520 self.as_borrowed().get_fold()
521 }
522}
523
524impl PyTimeAccess for Bound<'_, PyDateTime> {
525 fn get_hour(&self) -> u8 {
526 unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u8 }
527 }
528
529 fn get_minute(&self) -> u8 {
530 unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u8 }
531 }
532
533 fn get_second(&self) -> u8 {
534 unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u8 }
535 }
536
537 fn get_microsecond(&self) -> u32 {
538 unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 }
539 }
540
541 fn get_fold(&self) -> bool {
542 unsafe { PyDateTime_DATE_GET_FOLD(self.as_ptr()) > 0 }
543 }
544}
545
546#[cfg(feature = "gil-refs")]
547impl<'py> PyTzInfoAccess<'py> for &'py PyDateTime {
548 fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>> {
549 self.as_borrowed().get_tzinfo_bound()
550 }
551}
552
553impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyDateTime> {
554 fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>> {
555 let ptr = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
556 #[cfg(not(GraalPy))]
557 unsafe {
558 if (*ptr).hastzinfo != 0 {
559 Some(
560 (*ptr)
561 .tzinfo
562 .assume_borrowed(self.py())
563 .to_owned()
564 .downcast_into_unchecked(),
565 )
566 } else {
567 None
568 }
569 }
570
571 #[cfg(GraalPy)]
572 unsafe {
573 let res = PyDateTime_DATE_GET_TZINFO(ptr as *mut ffi::PyObject);
574 if Py_IsNone(res) == 1 {
575 None
576 } else {
577 Some(
578 res.assume_borrowed(self.py())
579 .to_owned()
580 .downcast_into_unchecked(),
581 )
582 }
583 }
584 }
585}
586
587#[repr(transparent)]
592pub struct PyTime(PyAny);
593pyobject_native_type!(
594 PyTime,
595 crate::ffi::PyDateTime_Time,
596 |py| expect_datetime_api(py).TimeType,
597 #module=Some("datetime"),
598 #checkfunction=PyTime_Check
599);
600
601impl PyTime {
602 #[cfg(feature = "gil-refs")]
604 #[deprecated(
605 since = "0.21.0",
606 note = "`PyTime::new` will be replaced by `PyTime::new_bound` in a future PyO3 version"
607 )]
608 pub fn new<'py>(
609 py: Python<'py>,
610 hour: u8,
611 minute: u8,
612 second: u8,
613 microsecond: u32,
614 tzinfo: Option<&'py PyTzInfo>,
615 ) -> PyResult<&'py PyTime> {
616 Self::new_bound(
617 py,
618 hour,
619 minute,
620 second,
621 microsecond,
622 tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
623 )
624 .map(Bound::into_gil_ref)
625 }
626
627 pub fn new_bound<'py>(
629 py: Python<'py>,
630 hour: u8,
631 minute: u8,
632 second: u8,
633 microsecond: u32,
634 tzinfo: Option<&Bound<'py, PyTzInfo>>,
635 ) -> PyResult<Bound<'py, PyTime>> {
636 let api = ensure_datetime_api(py)?;
637 unsafe {
638 (api.Time_FromTime)(
639 c_int::from(hour),
640 c_int::from(minute),
641 c_int::from(second),
642 microsecond as c_int,
643 opt_to_pyobj(tzinfo),
644 api.TimeType,
645 )
646 .assume_owned_or_err(py)
647 .downcast_into_unchecked()
648 }
649 }
650
651 #[cfg(feature = "gil-refs")]
653 #[deprecated(
654 since = "0.21.0",
655 note = "`PyTime::new_with_fold` will be replaced by `PyTime::new_bound_with_fold` in a future PyO3 version"
656 )]
657 pub fn new_with_fold<'py>(
658 py: Python<'py>,
659 hour: u8,
660 minute: u8,
661 second: u8,
662 microsecond: u32,
663 tzinfo: Option<&'py PyTzInfo>,
664 fold: bool,
665 ) -> PyResult<&'py PyTime> {
666 Self::new_bound_with_fold(
667 py,
668 hour,
669 minute,
670 second,
671 microsecond,
672 tzinfo.map(PyTzInfo::as_borrowed).as_deref(),
673 fold,
674 )
675 .map(Bound::into_gil_ref)
676 }
677
678 pub fn new_bound_with_fold<'py>(
680 py: Python<'py>,
681 hour: u8,
682 minute: u8,
683 second: u8,
684 microsecond: u32,
685 tzinfo: Option<&Bound<'py, PyTzInfo>>,
686 fold: bool,
687 ) -> PyResult<Bound<'py, PyTime>> {
688 let api = ensure_datetime_api(py)?;
689 unsafe {
690 (api.Time_FromTimeAndFold)(
691 c_int::from(hour),
692 c_int::from(minute),
693 c_int::from(second),
694 microsecond as c_int,
695 opt_to_pyobj(tzinfo),
696 fold as c_int,
697 api.TimeType,
698 )
699 .assume_owned_or_err(py)
700 .downcast_into_unchecked()
701 }
702 }
703}
704
705#[cfg(feature = "gil-refs")]
706impl PyTimeAccess for PyTime {
707 fn get_hour(&self) -> u8 {
708 self.as_borrowed().get_hour()
709 }
710
711 fn get_minute(&self) -> u8 {
712 self.as_borrowed().get_minute()
713 }
714
715 fn get_second(&self) -> u8 {
716 self.as_borrowed().get_second()
717 }
718
719 fn get_microsecond(&self) -> u32 {
720 self.as_borrowed().get_microsecond()
721 }
722
723 fn get_fold(&self) -> bool {
724 self.as_borrowed().get_fold()
725 }
726}
727
728impl PyTimeAccess for Bound<'_, PyTime> {
729 fn get_hour(&self) -> u8 {
730 unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 }
731 }
732
733 fn get_minute(&self) -> u8 {
734 unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 }
735 }
736
737 fn get_second(&self) -> u8 {
738 unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 }
739 }
740
741 fn get_microsecond(&self) -> u32 {
742 unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 }
743 }
744
745 fn get_fold(&self) -> bool {
746 unsafe { PyDateTime_TIME_GET_FOLD(self.as_ptr()) != 0 }
747 }
748}
749
750#[cfg(feature = "gil-refs")]
751impl<'py> PyTzInfoAccess<'py> for &'py PyTime {
752 fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>> {
753 self.as_borrowed().get_tzinfo_bound()
754 }
755}
756
757impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyTime> {
758 fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>> {
759 let ptr = self.as_ptr() as *mut ffi::PyDateTime_Time;
760 #[cfg(not(GraalPy))]
761 unsafe {
762 if (*ptr).hastzinfo != 0 {
763 Some(
764 (*ptr)
765 .tzinfo
766 .assume_borrowed(self.py())
767 .to_owned()
768 .downcast_into_unchecked(),
769 )
770 } else {
771 None
772 }
773 }
774
775 #[cfg(GraalPy)]
776 unsafe {
777 let res = PyDateTime_TIME_GET_TZINFO(ptr as *mut ffi::PyObject);
778 if Py_IsNone(res) == 1 {
779 None
780 } else {
781 Some(
782 res.assume_borrowed(self.py())
783 .to_owned()
784 .downcast_into_unchecked(),
785 )
786 }
787 }
788 }
789}
790
791#[repr(transparent)]
800pub struct PyTzInfo(PyAny);
801pyobject_native_type!(
802 PyTzInfo,
803 crate::ffi::PyObject,
804 |py| expect_datetime_api(py).TZInfoType,
805 #module=Some("datetime"),
806 #checkfunction=PyTZInfo_Check
807);
808
809#[cfg(feature = "gil-refs")]
811#[deprecated(
812 since = "0.21.0",
813 note = "`timezone_utc` will be replaced by `timezone_utc_bound` in a future PyO3 version"
814)]
815pub fn timezone_utc(py: Python<'_>) -> &PyTzInfo {
816 timezone_utc_bound(py).into_gil_ref()
817}
818
819pub fn timezone_utc_bound(py: Python<'_>) -> Bound<'_, PyTzInfo> {
821 unsafe {
825 expect_datetime_api(py)
826 .TimeZone_UTC
827 .assume_borrowed(py)
828 .to_owned()
829 .downcast_into_unchecked()
830 }
831}
832
833#[cfg(feature = "chrono")]
837pub(crate) fn timezone_from_offset<'py>(
838 offset: &Bound<'py, PyDelta>,
839) -> PyResult<Bound<'py, PyTzInfo>> {
840 let py = offset.py();
841 let api = ensure_datetime_api(py)?;
842 unsafe {
843 (api.TimeZone_FromTimeZone)(offset.as_ptr(), ptr::null_mut())
844 .assume_owned_or_err(py)
845 .downcast_into_unchecked()
846 }
847}
848
849#[repr(transparent)]
854pub struct PyDelta(PyAny);
855pyobject_native_type!(
856 PyDelta,
857 crate::ffi::PyDateTime_Delta,
858 |py| expect_datetime_api(py).DeltaType,
859 #module=Some("datetime"),
860 #checkfunction=PyDelta_Check
861);
862
863impl PyDelta {
864 #[cfg(feature = "gil-refs")]
866 #[deprecated(
867 since = "0.21.0",
868 note = "`PyDelta::new` will be replaced by `PyDelta::new_bound` in a future PyO3 version"
869 )]
870 pub fn new(
871 py: Python<'_>,
872 days: i32,
873 seconds: i32,
874 microseconds: i32,
875 normalize: bool,
876 ) -> PyResult<&PyDelta> {
877 Self::new_bound(py, days, seconds, microseconds, normalize).map(Bound::into_gil_ref)
878 }
879
880 pub fn new_bound(
882 py: Python<'_>,
883 days: i32,
884 seconds: i32,
885 microseconds: i32,
886 normalize: bool,
887 ) -> PyResult<Bound<'_, PyDelta>> {
888 let api = ensure_datetime_api(py)?;
889 unsafe {
890 (api.Delta_FromDelta)(
891 days as c_int,
892 seconds as c_int,
893 microseconds as c_int,
894 normalize as c_int,
895 api.DeltaType,
896 )
897 .assume_owned_or_err(py)
898 .downcast_into_unchecked()
899 }
900 }
901}
902
903#[cfg(feature = "gil-refs")]
904impl PyDeltaAccess for PyDelta {
905 fn get_days(&self) -> i32 {
906 self.as_borrowed().get_days()
907 }
908
909 fn get_seconds(&self) -> i32 {
910 self.as_borrowed().get_seconds()
911 }
912
913 fn get_microseconds(&self) -> i32 {
914 self.as_borrowed().get_microseconds()
915 }
916}
917
918impl PyDeltaAccess for Bound<'_, PyDelta> {
919 fn get_days(&self) -> i32 {
920 unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
921 }
922
923 fn get_seconds(&self) -> i32 {
924 unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
925 }
926
927 fn get_microseconds(&self) -> i32 {
928 unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
929 }
930}
931
932fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
935 match opt {
936 Some(tzi) => tzi.as_ptr(),
937 None => unsafe { ffi::Py_None() },
938 }
939}
940
941#[cfg(test)]
942mod tests {
943 use super::*;
944 #[cfg(feature = "macros")]
945 use crate::py_run;
946
947 #[test]
948 #[cfg(feature = "macros")]
949 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_datetime_fromtimestamp() {
951 Python::with_gil(|py| {
952 let dt = PyDateTime::from_timestamp_bound(py, 100.0, None).unwrap();
953 py_run!(
954 py,
955 dt,
956 "import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
957 );
958
959 let dt =
960 PyDateTime::from_timestamp_bound(py, 100.0, Some(&timezone_utc_bound(py))).unwrap();
961 py_run!(
962 py,
963 dt,
964 "import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
965 );
966 })
967 }
968
969 #[test]
970 #[cfg(feature = "macros")]
971 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_date_fromtimestamp() {
973 Python::with_gil(|py| {
974 let dt = PyDate::from_timestamp_bound(py, 100).unwrap();
975 py_run!(
976 py,
977 dt,
978 "import datetime; assert dt == datetime.date.fromtimestamp(100)"
979 );
980 })
981 }
982
983 #[test]
984 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_new_with_fold() {
986 Python::with_gil(|py| {
987 let a =
988 PyDateTime::new_bound_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
989 let b =
990 PyDateTime::new_bound_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
991
992 assert!(!a.unwrap().get_fold());
993 assert!(b.unwrap().get_fold());
994 });
995 }
996
997 #[test]
998 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_get_tzinfo() {
1000 crate::Python::with_gil(|py| {
1001 let utc = timezone_utc_bound(py);
1002
1003 let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
1004
1005 assert!(dt.get_tzinfo_bound().unwrap().eq(&utc).unwrap());
1006
1007 let dt = PyDateTime::new_bound(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
1008
1009 assert!(dt.get_tzinfo_bound().is_none());
1010
1011 let t = PyTime::new_bound(py, 0, 0, 0, 0, Some(&utc)).unwrap();
1012
1013 assert!(t.get_tzinfo_bound().unwrap().eq(utc).unwrap());
1014
1015 let t = PyTime::new_bound(py, 0, 0, 0, 0, None).unwrap();
1016
1017 assert!(t.get_tzinfo_bound().is_none());
1018 });
1019 }
1020
1021 #[test]
1022 #[cfg(all(feature = "macros", feature = "chrono"))]
1023 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_timezone_from_offset() {
1025 Python::with_gil(|py| {
1026 assert!(
1027 timezone_from_offset(&PyDelta::new_bound(py, 0, -3600, 0, true).unwrap())
1028 .unwrap()
1029 .call_method1("utcoffset", ((),))
1030 .unwrap()
1031 .downcast_into::<PyDelta>()
1032 .unwrap()
1033 .eq(PyDelta::new_bound(py, 0, -3600, 0, true).unwrap())
1034 .unwrap()
1035 );
1036
1037 assert!(
1038 timezone_from_offset(&PyDelta::new_bound(py, 0, 3600, 0, true).unwrap())
1039 .unwrap()
1040 .call_method1("utcoffset", ((),))
1041 .unwrap()
1042 .downcast_into::<PyDelta>()
1043 .unwrap()
1044 .eq(PyDelta::new_bound(py, 0, 3600, 0, true).unwrap())
1045 .unwrap()
1046 );
1047
1048 timezone_from_offset(&PyDelta::new_bound(py, 1, 0, 0, true).unwrap()).unwrap_err();
1049 })
1050 }
1051}