1#[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#[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 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 unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
90
91 unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
93
94 unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
96
97 unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
99
100 unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
102}
103
104#[cfg(not(Py_LIMITED_API))]
108pub trait PyDateAccess {
109 fn get_year(&self) -> i32;
114 fn get_month(&self) -> u8;
119 fn get_day(&self) -> u8;
124}
125
126#[cfg(not(Py_LIMITED_API))]
132pub trait PyDeltaAccess {
133 fn get_days(&self) -> i32;
138 fn get_seconds(&self) -> i32;
143 fn get_microseconds(&self) -> i32;
148}
149
150#[cfg(not(Py_LIMITED_API))]
152pub trait PyTimeAccess {
153 fn get_hour(&self) -> u8;
158 fn get_minute(&self) -> u8;
163 fn get_second(&self) -> u8;
168 fn get_microsecond(&self) -> u32;
173 fn get_fold(&self) -> bool;
180}
181
182pub trait PyTzInfoAccess<'py> {
184 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>>;
190}
191
192#[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 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 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 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#[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 #[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 #[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 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 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#[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 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 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#[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 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 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 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#[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#[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 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#[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)] 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)] 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)] 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)] 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)] 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}