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 core::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);
209pyobject_subclassable_native_type!(PyDate, crate::ffi::PyDateTime_Date);
210
211#[cfg(Py_LIMITED_API)]
212pyobject_native_type_core!(
213 PyDate,
214 |py| {
215 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
216 TYPE.import(py, "datetime", "date").unwrap().as_type_ptr()
217 },
218 "datetime",
219 "date",
220 #module=Some("datetime")
221);
222
223impl PyDate {
224 pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
226 #[cfg(not(Py_LIMITED_API))]
227 {
228 let api = ensure_datetime_api(py)?;
229 unsafe {
230 (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
231 .assume_owned_or_err(py)
232 .cast_into_unchecked()
233 }
234 }
235 #[cfg(Py_LIMITED_API)]
236 Ok(Self::type_object(py)
237 .call((year, month, day), None)?
238 .cast_into()?)
239 }
240
241 pub fn from_timestamp(py: Python<'_>, timestamp: f64) -> PyResult<Bound<'_, PyDate>> {
245 #[cfg(not(Py_LIMITED_API))]
246 {
247 let time_tuple = PyTuple::new(py, [timestamp])?;
248
249 let _api = ensure_datetime_api(py)?;
251
252 unsafe {
253 PyDate_FromTimestamp(time_tuple.as_ptr())
254 .assume_owned_or_err(py)
255 .cast_into_unchecked()
256 }
257 }
258
259 #[cfg(Py_LIMITED_API)]
260 Ok(Self::type_object(py)
261 .call_method1("fromtimestamp", (timestamp,))?
262 .cast_into()?)
263 }
264}
265
266#[cfg(not(Py_LIMITED_API))]
267impl PyDateAccess for Bound<'_, PyDate> {
268 fn get_year(&self) -> i32 {
269 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
270 }
271
272 fn get_month(&self) -> u8 {
273 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
274 }
275
276 fn get_day(&self) -> u8 {
277 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
278 }
279}
280
281#[repr(transparent)]
286pub struct PyDateTime(PyAny);
287
288#[cfg(not(Py_LIMITED_API))]
289pyobject_native_type!(
290 PyDateTime,
291 crate::ffi::PyDateTime_DateTime,
292 |py| expect_datetime_api(py).DateTimeType,
293 "datetime",
294 "datetime",
295 #module=Some("datetime"),
296 #checkfunction=PyDateTime_Check
297);
298pyobject_subclassable_native_type!(PyDateTime, crate::ffi::PyDateTime_DateTime);
299
300#[cfg(Py_LIMITED_API)]
301pyobject_native_type_core!(
302 PyDateTime,
303 |py| {
304 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
305 TYPE.import(py, "datetime", "datetime")
306 .unwrap()
307 .as_type_ptr()
308 },
309 "datetime",
310 "datetime",
311 #module=Some("datetime")
312);
313
314impl PyDateTime {
315 #[allow(clippy::too_many_arguments)]
317 pub fn new<'py>(
318 py: Python<'py>,
319 year: i32,
320 month: u8,
321 day: u8,
322 hour: u8,
323 minute: u8,
324 second: u8,
325 microsecond: u32,
326 tzinfo: Option<&Bound<'py, PyTzInfo>>,
327 ) -> PyResult<Bound<'py, PyDateTime>> {
328 #[cfg(not(Py_LIMITED_API))]
329 {
330 let api = ensure_datetime_api(py)?;
331 unsafe {
332 (api.DateTime_FromDateAndTime)(
333 year,
334 c_int::from(month),
335 c_int::from(day),
336 c_int::from(hour),
337 c_int::from(minute),
338 c_int::from(second),
339 microsecond as c_int,
340 opt_to_pyobj(tzinfo),
341 api.DateTimeType,
342 )
343 .assume_owned_or_err(py)
344 .cast_into_unchecked()
345 }
346 }
347
348 #[cfg(Py_LIMITED_API)]
349 Ok(Self::type_object(py)
350 .call(
351 (year, month, day, hour, minute, second, microsecond, tzinfo),
352 None,
353 )?
354 .cast_into()?)
355 }
356
357 #[allow(clippy::too_many_arguments)]
365 pub fn new_with_fold<'py>(
366 py: Python<'py>,
367 year: i32,
368 month: u8,
369 day: u8,
370 hour: u8,
371 minute: u8,
372 second: u8,
373 microsecond: u32,
374 tzinfo: Option<&Bound<'py, PyTzInfo>>,
375 fold: bool,
376 ) -> PyResult<Bound<'py, PyDateTime>> {
377 #[cfg(not(Py_LIMITED_API))]
378 {
379 let api = ensure_datetime_api(py)?;
380 unsafe {
381 (api.DateTime_FromDateAndTimeAndFold)(
382 year,
383 c_int::from(month),
384 c_int::from(day),
385 c_int::from(hour),
386 c_int::from(minute),
387 c_int::from(second),
388 microsecond as c_int,
389 opt_to_pyobj(tzinfo),
390 c_int::from(fold),
391 api.DateTimeType,
392 )
393 .assume_owned_or_err(py)
394 .cast_into_unchecked()
395 }
396 }
397
398 #[cfg(Py_LIMITED_API)]
399 Ok(Self::type_object(py)
400 .call(
401 (year, month, day, hour, minute, second, microsecond, tzinfo),
402 Some(&[("fold", fold)].into_py_dict(py)?),
403 )?
404 .cast_into()?)
405 }
406
407 pub fn from_timestamp<'py>(
411 py: Python<'py>,
412 timestamp: f64,
413 tzinfo: Option<&Bound<'py, PyTzInfo>>,
414 ) -> PyResult<Bound<'py, PyDateTime>> {
415 #[cfg(not(Py_LIMITED_API))]
416 {
417 let args = (timestamp, tzinfo).into_pyobject(py)?;
418
419 let _api = ensure_datetime_api(py)?;
421
422 unsafe {
423 PyDateTime_FromTimestamp(args.as_ptr())
424 .assume_owned_or_err(py)
425 .cast_into_unchecked()
426 }
427 }
428
429 #[cfg(Py_LIMITED_API)]
430 Ok(Self::type_object(py)
431 .call_method1("fromtimestamp", (timestamp, tzinfo))?
432 .cast_into()?)
433 }
434}
435
436#[cfg(not(Py_LIMITED_API))]
437impl PyDateAccess for Bound<'_, PyDateTime> {
438 fn get_year(&self) -> i32 {
439 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
440 }
441
442 fn get_month(&self) -> u8 {
443 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
444 }
445
446 fn get_day(&self) -> u8 {
447 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
448 }
449}
450
451#[cfg(not(Py_LIMITED_API))]
452impl PyTimeAccess for Bound<'_, PyDateTime> {
453 fn get_hour(&self) -> u8 {
454 unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u8 }
455 }
456
457 fn get_minute(&self) -> u8 {
458 unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u8 }
459 }
460
461 fn get_second(&self) -> u8 {
462 unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u8 }
463 }
464
465 fn get_microsecond(&self) -> u32 {
466 unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 }
467 }
468
469 fn get_fold(&self) -> bool {
470 unsafe { PyDateTime_DATE_GET_FOLD(self.as_ptr()) > 0 }
471 }
472}
473
474impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyDateTime> {
475 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
476 #[cfg(all(not(Py_3_10), not(Py_LIMITED_API)))]
477 unsafe {
478 let ptr = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
479 if (*ptr).hastzinfo != 0 {
480 Some(
481 (*ptr)
482 .tzinfo
483 .assume_borrowed(self.py())
484 .to_owned()
485 .cast_into_unchecked(),
486 )
487 } else {
488 None
489 }
490 }
491
492 #[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
493 unsafe {
494 let res = PyDateTime_DATE_GET_TZINFO(self.as_ptr());
495 if Py_IsNone(res) == 1 {
496 None
497 } else {
498 Some(
499 res.assume_borrowed(self.py())
500 .to_owned()
501 .cast_into_unchecked(),
502 )
503 }
504 }
505
506 #[cfg(Py_LIMITED_API)]
507 unsafe {
508 let tzinfo = self.getattr(intern!(self.py(), "tzinfo")).ok()?;
509 if tzinfo.is_none() {
510 None
511 } else {
512 Some(tzinfo.cast_into_unchecked())
513 }
514 }
515 }
516}
517
518#[repr(transparent)]
523pub struct PyTime(PyAny);
524
525#[cfg(not(Py_LIMITED_API))]
526pyobject_native_type!(
527 PyTime,
528 crate::ffi::PyDateTime_Time,
529 |py| expect_datetime_api(py).TimeType,
530 "datetime",
531 "time",
532 #module=Some("datetime"),
533 #checkfunction=PyTime_Check
534);
535pyobject_subclassable_native_type!(PyTime, crate::ffi::PyDateTime_Time);
536
537#[cfg(Py_LIMITED_API)]
538pyobject_native_type_core!(
539 PyTime,
540 |py| {
541 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
542 TYPE.import(py, "datetime", "time").unwrap().as_type_ptr()
543 },
544 "datetime",
545 "time",
546 #module=Some("datetime")
547);
548
549impl PyTime {
550 pub fn new<'py>(
552 py: Python<'py>,
553 hour: u8,
554 minute: u8,
555 second: u8,
556 microsecond: u32,
557 tzinfo: Option<&Bound<'py, PyTzInfo>>,
558 ) -> PyResult<Bound<'py, PyTime>> {
559 #[cfg(not(Py_LIMITED_API))]
560 {
561 let api = ensure_datetime_api(py)?;
562 unsafe {
563 (api.Time_FromTime)(
564 c_int::from(hour),
565 c_int::from(minute),
566 c_int::from(second),
567 microsecond as c_int,
568 opt_to_pyobj(tzinfo),
569 api.TimeType,
570 )
571 .assume_owned_or_err(py)
572 .cast_into_unchecked()
573 }
574 }
575
576 #[cfg(Py_LIMITED_API)]
577 Ok(Self::type_object(py)
578 .call((hour, minute, second, microsecond, tzinfo), None)?
579 .cast_into()?)
580 }
581
582 pub fn new_with_fold<'py>(
584 py: Python<'py>,
585 hour: u8,
586 minute: u8,
587 second: u8,
588 microsecond: u32,
589 tzinfo: Option<&Bound<'py, PyTzInfo>>,
590 fold: bool,
591 ) -> PyResult<Bound<'py, PyTime>> {
592 #[cfg(not(Py_LIMITED_API))]
593 {
594 let api = ensure_datetime_api(py)?;
595 unsafe {
596 (api.Time_FromTimeAndFold)(
597 c_int::from(hour),
598 c_int::from(minute),
599 c_int::from(second),
600 microsecond as c_int,
601 opt_to_pyobj(tzinfo),
602 fold as c_int,
603 api.TimeType,
604 )
605 .assume_owned_or_err(py)
606 .cast_into_unchecked()
607 }
608 }
609
610 #[cfg(Py_LIMITED_API)]
611 Ok(Self::type_object(py)
612 .call(
613 (hour, minute, second, microsecond, tzinfo),
614 Some(&[("fold", fold)].into_py_dict(py)?),
615 )?
616 .cast_into()?)
617 }
618}
619
620#[cfg(not(Py_LIMITED_API))]
621impl PyTimeAccess for Bound<'_, PyTime> {
622 fn get_hour(&self) -> u8 {
623 unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 }
624 }
625
626 fn get_minute(&self) -> u8 {
627 unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 }
628 }
629
630 fn get_second(&self) -> u8 {
631 unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 }
632 }
633
634 fn get_microsecond(&self) -> u32 {
635 unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 }
636 }
637
638 fn get_fold(&self) -> bool {
639 unsafe { PyDateTime_TIME_GET_FOLD(self.as_ptr()) != 0 }
640 }
641}
642
643impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyTime> {
644 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
645 #[cfg(all(not(Py_3_10), not(Py_LIMITED_API)))]
646 unsafe {
647 let ptr = self.as_ptr() as *mut ffi::PyDateTime_Time;
648 if (*ptr).hastzinfo != 0 {
649 Some(
650 (*ptr)
651 .tzinfo
652 .assume_borrowed(self.py())
653 .to_owned()
654 .cast_into_unchecked(),
655 )
656 } else {
657 None
658 }
659 }
660
661 #[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
662 unsafe {
663 let res = PyDateTime_TIME_GET_TZINFO(self.as_ptr());
664 if Py_IsNone(res) == 1 {
665 None
666 } else {
667 Some(
668 res.assume_borrowed(self.py())
669 .to_owned()
670 .cast_into_unchecked(),
671 )
672 }
673 }
674
675 #[cfg(Py_LIMITED_API)]
676 unsafe {
677 let tzinfo = self.getattr(intern!(self.py(), "tzinfo")).ok()?;
678 if tzinfo.is_none() {
679 None
680 } else {
681 Some(tzinfo.cast_into_unchecked())
682 }
683 }
684 }
685}
686
687#[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);
711pyobject_subclassable_native_type!(PyTzInfo, crate::ffi::PyObject);
712
713#[cfg(Py_LIMITED_API)]
714pyobject_native_type_core!(
715 PyTzInfo,
716 |py| {
717 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
718 TYPE.import(py, "datetime", "tzinfo").unwrap().as_type_ptr()
719 },
720 "datetime",
721 "tzinfo",
722 #module=Some("datetime")
723);
724
725impl PyTzInfo {
726 pub fn utc(py: Python<'_>) -> PyResult<Borrowed<'static, '_, PyTzInfo>> {
728 #[cfg(not(Py_LIMITED_API))]
729 unsafe {
730 Ok(ensure_datetime_api(py)?
731 .TimeZone_UTC
732 .assume_borrowed(py)
733 .cast_unchecked())
734 }
735
736 #[cfg(Py_LIMITED_API)]
737 {
738 static UTC: PyOnceLock<Py<PyTzInfo>> = PyOnceLock::new();
739 UTC.get_or_try_init(py, || {
740 Ok(py
741 .import("datetime")?
742 .getattr("timezone")?
743 .getattr("utc")?
744 .cast_into()?
745 .unbind())
746 })
747 .map(|utc| utc.bind_borrowed(py))
748 }
749 }
750
751 pub fn timezone<'py, T>(py: Python<'py>, iana_name: T) -> PyResult<Bound<'py, PyTzInfo>>
753 where
754 T: IntoPyObject<'py, Target = PyString>,
755 {
756 static ZONE_INFO: PyOnceLock<Py<PyType>> = PyOnceLock::new();
757
758 let zoneinfo = ZONE_INFO.import(py, "zoneinfo", "ZoneInfo");
759
760 #[cfg(not(Py_3_9))]
761 let zoneinfo = zoneinfo
762 .or_else(|_| ZONE_INFO.import(py, "backports.zoneinfo", "ZoneInfo"))
763 .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"));
764
765 zoneinfo?
766 .call1((iana_name,))?
767 .cast_into()
768 .map_err(Into::into)
769 }
770
771 pub fn fixed_offset<'py, T>(py: Python<'py>, offset: T) -> PyResult<Bound<'py, PyTzInfo>>
773 where
774 T: IntoPyObject<'py, Target = PyDelta>,
775 {
776 #[cfg(not(Py_LIMITED_API))]
777 {
778 let api = ensure_datetime_api(py)?;
779 let delta = offset.into_pyobject(py).map_err(Into::into)?;
780 unsafe {
781 (api.TimeZone_FromTimeZone)(delta.as_ptr(), core::ptr::null_mut())
782 .assume_owned_or_err(py)
783 .cast_into_unchecked()
784 }
785 }
786
787 #[cfg(Py_LIMITED_API)]
788 {
789 static TIMEZONE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
790 Ok(TIMEZONE
791 .import(py, "datetime", "timezone")?
792 .call1((offset,))?
793 .cast_into()?)
794 }
795 }
796}
797
798#[repr(transparent)]
803pub struct PyDelta(PyAny);
804
805#[cfg(not(Py_LIMITED_API))]
806pyobject_native_type!(
807 PyDelta,
808 crate::ffi::PyDateTime_Delta,
809 |py| expect_datetime_api(py).DeltaType,
810 "datetime",
811 "timedelta",
812 #module=Some("datetime"),
813 #checkfunction=PyDelta_Check
814);
815pyobject_subclassable_native_type!(PyDelta, crate::ffi::PyDateTime_Delta);
816
817#[cfg(Py_LIMITED_API)]
818pyobject_native_type_core!(
819 PyDelta,
820 |py| {
821 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
822 TYPE.import(py, "datetime", "timedelta")
823 .unwrap()
824 .as_type_ptr()
825 },
826 "datetime",
827 "timedelta",
828 #module=Some("datetime")
829);
830
831impl PyDelta {
832 pub fn new(
834 py: Python<'_>,
835 days: i32,
836 seconds: i32,
837 microseconds: i32,
838 normalize: bool,
839 ) -> PyResult<Bound<'_, PyDelta>> {
840 #[cfg(not(Py_LIMITED_API))]
841 {
842 let api = ensure_datetime_api(py)?;
843 unsafe {
844 (api.Delta_FromDelta)(
845 days as c_int,
846 seconds as c_int,
847 microseconds as c_int,
848 normalize as c_int,
849 api.DeltaType,
850 )
851 .assume_owned_or_err(py)
852 .cast_into_unchecked()
853 }
854 }
855
856 #[cfg(Py_LIMITED_API)]
857 let _ = normalize;
858 #[cfg(Py_LIMITED_API)]
859 Ok(Self::type_object(py)
860 .call1((days, seconds, microseconds))?
861 .cast_into()?)
862 }
863}
864
865#[cfg(not(Py_LIMITED_API))]
866impl PyDeltaAccess for Bound<'_, PyDelta> {
867 fn get_days(&self) -> i32 {
868 unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
869 }
870
871 fn get_seconds(&self) -> i32 {
872 unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
873 }
874
875 fn get_microseconds(&self) -> i32 {
876 unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
877 }
878}
879
880#[cfg(not(Py_LIMITED_API))]
883fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
884 match opt {
885 Some(tzi) => tzi.as_ptr(),
886 None => unsafe { ffi::Py_None() },
887 }
888}
889
890#[cfg(test)]
891mod tests {
892 use super::*;
893 #[cfg(feature = "macros")]
894 use crate::py_run;
895
896 #[test]
897 #[cfg(feature = "macros")]
898 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_datetime_fromtimestamp() {
900 Python::attach(|py| {
901 let dt = PyDateTime::from_timestamp(py, 100.0, None).unwrap();
902 py_run!(
903 py,
904 dt,
905 "import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
906 );
907
908 let utc = PyTzInfo::utc(py).unwrap();
909 let dt = PyDateTime::from_timestamp(py, 100.0, Some(&utc)).unwrap();
910 py_run!(
911 py,
912 dt,
913 "import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
914 );
915 })
916 }
917
918 #[test]
919 #[cfg(feature = "macros")]
920 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_date_fromtimestamp() {
922 Python::attach(|py| {
923 let dt = PyDate::from_timestamp(py, 100.).unwrap();
924 py_run!(
925 py,
926 dt,
927 "import datetime; assert dt == datetime.date.fromtimestamp(100)"
928 );
929 })
930 }
931
932 #[test]
933 #[cfg(not(Py_LIMITED_API))]
934 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_new_with_fold() {
936 Python::attach(|py| {
937 let a = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
938 let b = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
939
940 assert!(!a.unwrap().get_fold());
941 assert!(b.unwrap().get_fold());
942 });
943 }
944
945 #[test]
946 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_get_tzinfo() {
948 crate::Python::attach(|py| {
949 let utc = PyTzInfo::utc(py).unwrap();
950
951 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
952
953 assert!(dt.get_tzinfo().unwrap().eq(utc).unwrap());
954
955 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
956
957 assert!(dt.get_tzinfo().is_none());
958
959 let t = PyTime::new(py, 0, 0, 0, 0, Some(&utc)).unwrap();
960
961 assert!(t.get_tzinfo().unwrap().eq(utc).unwrap());
962
963 let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
964
965 assert!(t.get_tzinfo().is_none());
966 });
967 }
968
969 #[test]
970 #[cfg(all(feature = "macros", feature = "chrono"))]
971 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_timezone_from_offset() {
973 use crate::types::PyNone;
974
975 Python::attach(|py| {
976 assert!(
977 PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, -3600, 0, true).unwrap())
978 .unwrap()
979 .call_method1("utcoffset", (PyNone::get(py),))
980 .unwrap()
981 .cast_into::<PyDelta>()
982 .unwrap()
983 .eq(PyDelta::new(py, 0, -3600, 0, true).unwrap())
984 .unwrap()
985 );
986
987 assert!(
988 PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, 3600, 0, true).unwrap())
989 .unwrap()
990 .call_method1("utcoffset", (PyNone::get(py),))
991 .unwrap()
992 .cast_into::<PyDelta>()
993 .unwrap()
994 .eq(PyDelta::new(py, 0, 3600, 0, true).unwrap())
995 .unwrap()
996 );
997
998 PyTzInfo::fixed_offset(py, PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err();
999 })
1000 }
1001}