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