1#[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#[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 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 unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
88
89 unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
91
92 unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
94
95 unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
97
98 unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
100}
101
102#[cfg(not(Py_LIMITED_API))]
106pub trait PyDateAccess {
107 fn get_year(&self) -> i32;
112 fn get_month(&self) -> u8;
117 fn get_day(&self) -> u8;
122}
123
124#[cfg(not(Py_LIMITED_API))]
130pub trait PyDeltaAccess {
131 fn get_days(&self) -> i32;
136 fn get_seconds(&self) -> i32;
141 fn get_microseconds(&self) -> i32;
146}
147
148#[cfg(not(Py_LIMITED_API))]
150pub trait PyTimeAccess {
151 fn get_hour(&self) -> u8;
156 fn get_minute(&self) -> u8;
161 fn get_second(&self) -> u8;
166 fn get_microsecond(&self) -> u32;
171 fn get_fold(&self) -> bool;
178}
179
180pub trait PyTzInfoAccess<'py> {
182 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>>;
188}
189
190#[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 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 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 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#[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 #[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 #[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 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 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#[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 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 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#[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 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 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 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#[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 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#[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)] 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)] 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)] 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)] 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)] 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}