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(Py_3_10, not(Py_LIMITED_API)))]
18use crate::ffi::{PyDateTime_DATE_GET_TZINFO, PyDateTime_TIME_GET_TZINFO, Py_IsNone};
19use crate::types::any::PyAnyMethods;
20#[cfg(Py_3_9)]
21use crate::types::PyString;
22#[cfg(not(Py_LIMITED_API))]
23use crate::{ffi_ptr_ext::FfiPtrExt, py_result_ext::PyResultExt, types::PyTuple, BoundObject};
24#[cfg(any(Py_3_9, Py_LIMITED_API))]
25use crate::{sync::GILOnceCell, types::PyType, Py};
26#[cfg(Py_LIMITED_API)]
27use crate::{types::IntoPyDict, PyTypeCheck};
28use crate::{Borrowed, Bound, IntoPyObject, PyAny, PyErr, Python};
29#[cfg(not(Py_LIMITED_API))]
30use std::os::raw::c_int;
31
32#[cfg(not(Py_LIMITED_API))]
33fn ensure_datetime_api(py: Python<'_>) -> PyResult<&'static PyDateTime_CAPI> {
34 if let Some(api) = unsafe { pyo3_ffi::PyDateTimeAPI().as_ref() } {
35 Ok(api)
36 } else {
37 unsafe {
38 PyDateTime_IMPORT();
39 pyo3_ffi::PyDateTimeAPI().as_ref()
40 }
41 .ok_or_else(|| PyErr::fetch(py))
42 }
43}
44
45#[cfg(not(Py_LIMITED_API))]
46fn expect_datetime_api(py: Python<'_>) -> &'static PyDateTime_CAPI {
47 ensure_datetime_api(py).expect("failed to import `datetime` C API")
48}
49
50#[cfg(Py_LIMITED_API)]
51struct DatetimeTypes {
52 date: Py<PyType>,
53 datetime: Py<PyType>,
54 time: Py<PyType>,
55 timedelta: Py<PyType>,
56 timezone: Py<PyType>,
57 tzinfo: Py<PyType>,
58}
59
60#[cfg(Py_LIMITED_API)]
61impl DatetimeTypes {
62 fn get(py: Python<'_>) -> &Self {
63 Self::try_get(py).expect("failed to load datetime module")
64 }
65
66 fn try_get(py: Python<'_>) -> PyResult<&Self> {
67 static TYPES: GILOnceCell<DatetimeTypes> = GILOnceCell::new();
68 TYPES.get_or_try_init(py, || {
69 let datetime = py.import("datetime")?;
70 Ok::<_, PyErr>(Self {
71 date: datetime.getattr("date")?.downcast_into()?.into(),
72 datetime: datetime.getattr("datetime")?.downcast_into()?.into(),
73 time: datetime.getattr("time")?.downcast_into()?.into(),
74 timedelta: datetime.getattr("timedelta")?.downcast_into()?.into(),
75 timezone: datetime.getattr("timezone")?.downcast_into()?.into(),
76 tzinfo: datetime.getattr("tzinfo")?.downcast_into()?.into(),
77 })
78 })
79 }
80}
81
82#[cfg(not(Py_LIMITED_API))]
94macro_rules! ffi_fun_with_autoinit {
95 ($(#[$outer:meta] unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => {
96 $(
97 #[$outer]
98 #[allow(non_snake_case)]
99 unsafe fn $name($arg: *mut crate::ffi::PyObject) -> $ret {
103
104 let _ = ensure_datetime_api(unsafe { Python::assume_gil_acquired() });
105 unsafe { crate::ffi::$name($arg) }
106 }
107 )*
108
109
110 };
111}
112
113#[cfg(not(Py_LIMITED_API))]
114ffi_fun_with_autoinit! {
115 unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
117
118 unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
120
121 unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
123
124 unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
126
127 unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
129}
130
131#[cfg(not(Py_LIMITED_API))]
135pub trait PyDateAccess {
136 fn get_year(&self) -> i32;
141 fn get_month(&self) -> u8;
146 fn get_day(&self) -> u8;
151}
152
153#[cfg(not(Py_LIMITED_API))]
159pub trait PyDeltaAccess {
160 fn get_days(&self) -> i32;
165 fn get_seconds(&self) -> i32;
170 fn get_microseconds(&self) -> i32;
175}
176
177#[cfg(not(Py_LIMITED_API))]
179pub trait PyTimeAccess {
180 fn get_hour(&self) -> u8;
185 fn get_minute(&self) -> u8;
190 fn get_second(&self) -> u8;
195 fn get_microsecond(&self) -> u32;
200 fn get_fold(&self) -> bool;
207}
208
209pub trait PyTzInfoAccess<'py> {
211 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>>;
217}
218
219#[repr(transparent)]
224pub struct PyDate(PyAny);
225
226#[cfg(not(Py_LIMITED_API))]
227pyobject_native_type!(
228 PyDate,
229 crate::ffi::PyDateTime_Date,
230 |py| expect_datetime_api(py).DateType,
231 #module=Some("datetime"),
232 #checkfunction=PyDate_Check
233);
234#[cfg(not(Py_LIMITED_API))]
235pyobject_subclassable_native_type!(PyDate, crate::ffi::PyDateTime_Date);
236
237#[cfg(Py_LIMITED_API)]
238pyobject_native_type_named!(PyDate);
239
240#[cfg(Py_LIMITED_API)]
241impl PyTypeCheck for PyDate {
242 const NAME: &'static str = "PyDate";
243
244 fn type_check(object: &Bound<'_, PyAny>) -> bool {
245 let py = object.py();
246 DatetimeTypes::try_get(py)
247 .and_then(|module| object.is_instance(module.date.bind(py)))
248 .unwrap_or_default()
249 }
250}
251
252impl PyDate {
253 pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
255 #[cfg(not(Py_LIMITED_API))]
256 {
257 let api = ensure_datetime_api(py)?;
258 unsafe {
259 (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
260 .assume_owned_or_err(py)
261 .downcast_into_unchecked()
262 }
263 }
264 #[cfg(Py_LIMITED_API)]
265 unsafe {
266 Ok(DatetimeTypes::try_get(py)?
267 .date
268 .bind(py)
269 .call((year, month, day), None)?
270 .downcast_into_unchecked())
271 }
272 }
273
274 pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<Bound<'_, PyDate>> {
278 #[cfg(not(Py_LIMITED_API))]
279 {
280 let time_tuple = PyTuple::new(py, [timestamp])?;
281
282 let _api = ensure_datetime_api(py)?;
284
285 unsafe {
286 PyDate_FromTimestamp(time_tuple.as_ptr())
287 .assume_owned_or_err(py)
288 .downcast_into_unchecked()
289 }
290 }
291
292 #[cfg(Py_LIMITED_API)]
293 unsafe {
294 Ok(DatetimeTypes::try_get(py)?
295 .date
296 .bind(py)
297 .call_method1("fromtimestamp", (timestamp,))?
298 .downcast_into_unchecked())
299 }
300 }
301}
302
303#[cfg(not(Py_LIMITED_API))]
304impl PyDateAccess for Bound<'_, PyDate> {
305 fn get_year(&self) -> i32 {
306 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
307 }
308
309 fn get_month(&self) -> u8 {
310 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
311 }
312
313 fn get_day(&self) -> u8 {
314 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
315 }
316}
317
318#[repr(transparent)]
323pub struct PyDateTime(PyAny);
324
325#[cfg(not(Py_LIMITED_API))]
326pyobject_native_type!(
327 PyDateTime,
328 crate::ffi::PyDateTime_DateTime,
329 |py| expect_datetime_api(py).DateTimeType,
330 #module=Some("datetime"),
331 #checkfunction=PyDateTime_Check
332);
333#[cfg(not(Py_LIMITED_API))]
334pyobject_subclassable_native_type!(PyDateTime, crate::ffi::PyDateTime_DateTime);
335
336#[cfg(Py_LIMITED_API)]
337pyobject_native_type_named!(PyDateTime);
338
339#[cfg(Py_LIMITED_API)]
340impl PyTypeCheck for PyDateTime {
341 const NAME: &'static str = "PyDateTime";
342
343 fn type_check(object: &Bound<'_, PyAny>) -> bool {
344 let py = object.py();
345 DatetimeTypes::try_get(py)
346 .and_then(|module| object.is_instance(module.datetime.bind(py)))
347 .unwrap_or_default()
348 }
349}
350
351impl PyDateTime {
352 #[allow(clippy::too_many_arguments)]
354 pub fn new<'py>(
355 py: Python<'py>,
356 year: i32,
357 month: u8,
358 day: u8,
359 hour: u8,
360 minute: u8,
361 second: u8,
362 microsecond: u32,
363 tzinfo: Option<&Bound<'py, PyTzInfo>>,
364 ) -> PyResult<Bound<'py, PyDateTime>> {
365 #[cfg(not(Py_LIMITED_API))]
366 {
367 let api = ensure_datetime_api(py)?;
368 unsafe {
369 (api.DateTime_FromDateAndTime)(
370 year,
371 c_int::from(month),
372 c_int::from(day),
373 c_int::from(hour),
374 c_int::from(minute),
375 c_int::from(second),
376 microsecond as c_int,
377 opt_to_pyobj(tzinfo),
378 api.DateTimeType,
379 )
380 .assume_owned_or_err(py)
381 .downcast_into_unchecked()
382 }
383 }
384
385 #[cfg(Py_LIMITED_API)]
386 unsafe {
387 Ok(DatetimeTypes::try_get(py)?
388 .datetime
389 .bind(py)
390 .call(
391 (year, month, day, hour, minute, second, microsecond, tzinfo),
392 None,
393 )?
394 .downcast_into_unchecked())
395 }
396 }
397
398 #[allow(clippy::too_many_arguments)]
406 pub fn new_with_fold<'py>(
407 py: Python<'py>,
408 year: i32,
409 month: u8,
410 day: u8,
411 hour: u8,
412 minute: u8,
413 second: u8,
414 microsecond: u32,
415 tzinfo: Option<&Bound<'py, PyTzInfo>>,
416 fold: bool,
417 ) -> PyResult<Bound<'py, PyDateTime>> {
418 #[cfg(not(Py_LIMITED_API))]
419 {
420 let api = ensure_datetime_api(py)?;
421 unsafe {
422 (api.DateTime_FromDateAndTimeAndFold)(
423 year,
424 c_int::from(month),
425 c_int::from(day),
426 c_int::from(hour),
427 c_int::from(minute),
428 c_int::from(second),
429 microsecond as c_int,
430 opt_to_pyobj(tzinfo),
431 c_int::from(fold),
432 api.DateTimeType,
433 )
434 .assume_owned_or_err(py)
435 .downcast_into_unchecked()
436 }
437 }
438
439 #[cfg(Py_LIMITED_API)]
440 unsafe {
441 Ok(DatetimeTypes::try_get(py)?
442 .datetime
443 .bind(py)
444 .call(
445 (year, month, day, hour, minute, second, microsecond, tzinfo),
446 Some(&[("fold", fold)].into_py_dict(py)?),
447 )?
448 .downcast_into_unchecked())
449 }
450 }
451
452 pub fn from_timestamp<'py>(
456 py: Python<'py>,
457 timestamp: f64,
458 tzinfo: Option<&Bound<'py, PyTzInfo>>,
459 ) -> PyResult<Bound<'py, PyDateTime>> {
460 #[cfg(not(Py_LIMITED_API))]
461 {
462 let args = (timestamp, tzinfo).into_pyobject(py)?;
463
464 let _api = ensure_datetime_api(py)?;
466
467 unsafe {
468 PyDateTime_FromTimestamp(args.as_ptr())
469 .assume_owned_or_err(py)
470 .downcast_into_unchecked()
471 }
472 }
473
474 #[cfg(Py_LIMITED_API)]
475 unsafe {
476 Ok(DatetimeTypes::try_get(py)?
477 .datetime
478 .bind(py)
479 .call_method1("fromtimestamp", (timestamp, tzinfo))?
480 .downcast_into_unchecked())
481 }
482 }
483}
484
485#[cfg(not(Py_LIMITED_API))]
486impl PyDateAccess for Bound<'_, PyDateTime> {
487 fn get_year(&self) -> i32 {
488 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
489 }
490
491 fn get_month(&self) -> u8 {
492 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
493 }
494
495 fn get_day(&self) -> u8 {
496 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
497 }
498}
499
500#[cfg(not(Py_LIMITED_API))]
501impl PyTimeAccess for Bound<'_, PyDateTime> {
502 fn get_hour(&self) -> u8 {
503 unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u8 }
504 }
505
506 fn get_minute(&self) -> u8 {
507 unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u8 }
508 }
509
510 fn get_second(&self) -> u8 {
511 unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u8 }
512 }
513
514 fn get_microsecond(&self) -> u32 {
515 unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 }
516 }
517
518 fn get_fold(&self) -> bool {
519 unsafe { PyDateTime_DATE_GET_FOLD(self.as_ptr()) > 0 }
520 }
521}
522
523impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyDateTime> {
524 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
525 #[cfg(all(not(Py_3_10), not(Py_LIMITED_API)))]
526 unsafe {
527 let ptr = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
528 if (*ptr).hastzinfo != 0 {
529 Some(
530 (*ptr)
531 .tzinfo
532 .assume_borrowed(self.py())
533 .to_owned()
534 .downcast_into_unchecked(),
535 )
536 } else {
537 None
538 }
539 }
540
541 #[cfg(all(Py_3_10, not(Py_LIMITED_API)))]
542 unsafe {
543 let res = PyDateTime_DATE_GET_TZINFO(self.as_ptr());
544 if Py_IsNone(res) == 1 {
545 None
546 } else {
547 Some(
548 res.assume_borrowed(self.py())
549 .to_owned()
550 .downcast_into_unchecked(),
551 )
552 }
553 }
554
555 #[cfg(Py_LIMITED_API)]
556 unsafe {
557 let tzinfo = self.getattr(intern!(self.py(), "tzinfo")).ok()?;
558 if tzinfo.is_none() {
559 None
560 } else {
561 Some(tzinfo.downcast_into_unchecked())
562 }
563 }
564 }
565}
566
567#[repr(transparent)]
572pub struct PyTime(PyAny);
573
574#[cfg(not(Py_LIMITED_API))]
575pyobject_native_type!(
576 PyTime,
577 crate::ffi::PyDateTime_Time,
578 |py| expect_datetime_api(py).TimeType,
579 #module=Some("datetime"),
580 #checkfunction=PyTime_Check
581);
582#[cfg(not(Py_LIMITED_API))]
583pyobject_subclassable_native_type!(PyTime, crate::ffi::PyDateTime_Time);
584
585#[cfg(Py_LIMITED_API)]
586pyobject_native_type_named!(PyTime);
587
588#[cfg(Py_LIMITED_API)]
589impl PyTypeCheck for PyTime {
590 const NAME: &'static str = "PyTime";
591
592 fn type_check(object: &Bound<'_, PyAny>) -> bool {
593 let py = object.py();
594 DatetimeTypes::try_get(py)
595 .and_then(|module| object.is_instance(module.time.bind(py)))
596 .unwrap_or_default()
597 }
598}
599
600impl PyTime {
601 pub fn new<'py>(
603 py: Python<'py>,
604 hour: u8,
605 minute: u8,
606 second: u8,
607 microsecond: u32,
608 tzinfo: Option<&Bound<'py, PyTzInfo>>,
609 ) -> PyResult<Bound<'py, PyTime>> {
610 #[cfg(not(Py_LIMITED_API))]
611 {
612 let api = ensure_datetime_api(py)?;
613 unsafe {
614 (api.Time_FromTime)(
615 c_int::from(hour),
616 c_int::from(minute),
617 c_int::from(second),
618 microsecond as c_int,
619 opt_to_pyobj(tzinfo),
620 api.TimeType,
621 )
622 .assume_owned_or_err(py)
623 .downcast_into_unchecked()
624 }
625 }
626
627 #[cfg(Py_LIMITED_API)]
628 unsafe {
629 Ok(DatetimeTypes::try_get(py)?
630 .time
631 .bind(py)
632 .call((hour, minute, second, microsecond, tzinfo), None)?
633 .downcast_into_unchecked())
634 }
635 }
636
637 pub fn new_with_fold<'py>(
639 py: Python<'py>,
640 hour: u8,
641 minute: u8,
642 second: u8,
643 microsecond: u32,
644 tzinfo: Option<&Bound<'py, PyTzInfo>>,
645 fold: bool,
646 ) -> PyResult<Bound<'py, PyTime>> {
647 #[cfg(not(Py_LIMITED_API))]
648 {
649 let api = ensure_datetime_api(py)?;
650 unsafe {
651 (api.Time_FromTimeAndFold)(
652 c_int::from(hour),
653 c_int::from(minute),
654 c_int::from(second),
655 microsecond as c_int,
656 opt_to_pyobj(tzinfo),
657 fold as c_int,
658 api.TimeType,
659 )
660 .assume_owned_or_err(py)
661 .downcast_into_unchecked()
662 }
663 }
664
665 #[cfg(Py_LIMITED_API)]
666 #[cfg(Py_LIMITED_API)]
667 unsafe {
668 Ok(DatetimeTypes::try_get(py)?
669 .time
670 .bind(py)
671 .call(
672 (hour, minute, second, microsecond, tzinfo),
673 Some(&[("fold", fold)].into_py_dict(py)?),
674 )?
675 .downcast_into_unchecked())
676 }
677 }
678}
679
680#[cfg(not(Py_LIMITED_API))]
681impl PyTimeAccess for Bound<'_, PyTime> {
682 fn get_hour(&self) -> u8 {
683 unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 }
684 }
685
686 fn get_minute(&self) -> u8 {
687 unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 }
688 }
689
690 fn get_second(&self) -> u8 {
691 unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 }
692 }
693
694 fn get_microsecond(&self) -> u32 {
695 unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 }
696 }
697
698 fn get_fold(&self) -> bool {
699 unsafe { PyDateTime_TIME_GET_FOLD(self.as_ptr()) != 0 }
700 }
701}
702
703impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyTime> {
704 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
705 #[cfg(all(not(Py_3_10), not(Py_LIMITED_API)))]
706 unsafe {
707 let ptr = self.as_ptr() as *mut ffi::PyDateTime_Time;
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(Py_3_10, not(Py_LIMITED_API)))]
722 unsafe {
723 let res = PyDateTime_TIME_GET_TZINFO(self.as_ptr());
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
784impl PyTzInfo {
785 pub fn utc(py: Python<'_>) -> PyResult<Borrowed<'static, '_, PyTzInfo>> {
787 #[cfg(not(Py_LIMITED_API))]
788 unsafe {
789 Ok(ensure_datetime_api(py)?
790 .TimeZone_UTC
791 .assume_borrowed(py)
792 .downcast_unchecked())
793 }
794
795 #[cfg(Py_LIMITED_API)]
796 {
797 static UTC: GILOnceCell<Py<PyTzInfo>> = GILOnceCell::new();
798 UTC.get_or_try_init(py, || {
799 Ok(DatetimeTypes::get(py)
800 .timezone
801 .bind(py)
802 .getattr("utc")?
803 .downcast_into()?
804 .unbind())
805 })
806 .map(|utc| utc.bind_borrowed(py))
807 }
808 }
809
810 #[cfg(Py_3_9)]
812 pub fn timezone<'py, T>(py: Python<'py>, iana_name: T) -> PyResult<Bound<'py, PyTzInfo>>
813 where
814 T: IntoPyObject<'py, Target = PyString>,
815 {
816 static ZONE_INFO: GILOnceCell<Py<PyType>> = GILOnceCell::new();
817 ZONE_INFO
818 .import(py, "zoneinfo", "ZoneInfo")?
819 .call1((iana_name,))?
820 .downcast_into()
821 .map_err(Into::into)
822 }
823
824 pub fn fixed_offset<'py, T>(py: Python<'py>, offset: T) -> PyResult<Bound<'py, PyTzInfo>>
826 where
827 T: IntoPyObject<'py, Target = PyDelta>,
828 {
829 #[cfg(not(Py_LIMITED_API))]
830 {
831 let api = ensure_datetime_api(py)?;
832 let delta = offset.into_pyobject(py).map_err(Into::into)?;
833 unsafe {
834 (api.TimeZone_FromTimeZone)(delta.as_ptr(), std::ptr::null_mut())
835 .assume_owned_or_err(py)
836 .downcast_into_unchecked()
837 }
838 }
839
840 #[cfg(Py_LIMITED_API)]
841 unsafe {
842 Ok(DatetimeTypes::try_get(py)?
843 .timezone
844 .bind(py)
845 .call1((offset,))?
846 .downcast_into_unchecked())
847 }
848 }
849}
850
851#[deprecated(since = "0.25.0", note = "use `PyTzInfo::utc` instead")]
853pub fn timezone_utc(py: Python<'_>) -> Bound<'_, PyTzInfo> {
854 PyTzInfo::utc(py)
855 .expect("failed to import datetime.timezone.utc")
856 .to_owned()
857}
858
859#[repr(transparent)]
864pub struct PyDelta(PyAny);
865
866#[cfg(not(Py_LIMITED_API))]
867pyobject_native_type!(
868 PyDelta,
869 crate::ffi::PyDateTime_Delta,
870 |py| expect_datetime_api(py).DeltaType,
871 #module=Some("datetime"),
872 #checkfunction=PyDelta_Check
873);
874#[cfg(not(Py_LIMITED_API))]
875pyobject_subclassable_native_type!(PyDelta, crate::ffi::PyDateTime_Delta);
876
877#[cfg(Py_LIMITED_API)]
878pyobject_native_type_named!(PyDelta);
879
880#[cfg(Py_LIMITED_API)]
881impl PyTypeCheck for PyDelta {
882 const NAME: &'static str = "PyDelta";
883
884 fn type_check(object: &Bound<'_, PyAny>) -> bool {
885 let py = object.py();
886 DatetimeTypes::try_get(py)
887 .and_then(|module| object.is_instance(module.timedelta.bind(py)))
888 .unwrap_or_default()
889 }
890}
891
892impl PyDelta {
893 pub fn new(
895 py: Python<'_>,
896 days: i32,
897 seconds: i32,
898 microseconds: i32,
899 normalize: bool,
900 ) -> PyResult<Bound<'_, PyDelta>> {
901 #[cfg(not(Py_LIMITED_API))]
902 {
903 let api = ensure_datetime_api(py)?;
904 unsafe {
905 (api.Delta_FromDelta)(
906 days as c_int,
907 seconds as c_int,
908 microseconds as c_int,
909 normalize as c_int,
910 api.DeltaType,
911 )
912 .assume_owned_or_err(py)
913 .downcast_into_unchecked()
914 }
915 }
916
917 #[cfg(Py_LIMITED_API)]
918 unsafe {
919 let _ = normalize;
920 Ok(DatetimeTypes::try_get(py)?
921 .timedelta
922 .bind(py)
923 .call1((days, seconds, microseconds))?
924 .downcast_into_unchecked())
925 }
926 }
927}
928
929#[cfg(not(Py_LIMITED_API))]
930impl PyDeltaAccess for Bound<'_, PyDelta> {
931 fn get_days(&self) -> i32 {
932 unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
933 }
934
935 fn get_seconds(&self) -> i32 {
936 unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
937 }
938
939 fn get_microseconds(&self) -> i32 {
940 unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
941 }
942}
943
944#[cfg(not(Py_LIMITED_API))]
947fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
948 match opt {
949 Some(tzi) => tzi.as_ptr(),
950 None => unsafe { ffi::Py_None() },
951 }
952}
953
954#[cfg(test)]
955mod tests {
956 use super::*;
957 #[cfg(feature = "macros")]
958 use crate::py_run;
959
960 #[test]
961 #[cfg(feature = "macros")]
962 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_datetime_fromtimestamp() {
964 Python::with_gil(|py| {
965 let dt = PyDateTime::from_timestamp(py, 100.0, None).unwrap();
966 py_run!(
967 py,
968 dt,
969 "import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
970 );
971
972 let utc = PyTzInfo::utc(py).unwrap();
973 let dt = PyDateTime::from_timestamp(py, 100.0, Some(&utc)).unwrap();
974 py_run!(
975 py,
976 dt,
977 "import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
978 );
979 })
980 }
981
982 #[test]
983 #[cfg(feature = "macros")]
984 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_date_fromtimestamp() {
986 Python::with_gil(|py| {
987 let dt = PyDate::from_timestamp(py, 100).unwrap();
988 py_run!(
989 py,
990 dt,
991 "import datetime; assert dt == datetime.date.fromtimestamp(100)"
992 );
993 })
994 }
995
996 #[test]
997 #[cfg(not(Py_LIMITED_API))]
998 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_new_with_fold() {
1000 Python::with_gil(|py| {
1001 let a = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
1002 let b = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
1003
1004 assert!(!a.unwrap().get_fold());
1005 assert!(b.unwrap().get_fold());
1006 });
1007 }
1008
1009 #[test]
1010 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_get_tzinfo() {
1012 crate::Python::with_gil(|py| {
1013 let utc = PyTzInfo::utc(py).unwrap();
1014
1015 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
1016
1017 assert!(dt.get_tzinfo().unwrap().eq(utc).unwrap());
1018
1019 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
1020
1021 assert!(dt.get_tzinfo().is_none());
1022
1023 let t = PyTime::new(py, 0, 0, 0, 0, Some(&utc)).unwrap();
1024
1025 assert!(t.get_tzinfo().unwrap().eq(utc).unwrap());
1026
1027 let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
1028
1029 assert!(t.get_tzinfo().is_none());
1030 });
1031 }
1032
1033 #[test]
1034 #[cfg(all(feature = "macros", feature = "chrono"))]
1035 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_timezone_from_offset() {
1037 use crate::types::PyNone;
1038
1039 Python::with_gil(|py| {
1040 assert!(
1041 PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, -3600, 0, true).unwrap())
1042 .unwrap()
1043 .call_method1("utcoffset", (PyNone::get(py),))
1044 .unwrap()
1045 .downcast_into::<PyDelta>()
1046 .unwrap()
1047 .eq(PyDelta::new(py, 0, -3600, 0, true).unwrap())
1048 .unwrap()
1049 );
1050
1051 assert!(
1052 PyTzInfo::fixed_offset(py, PyDelta::new(py, 0, 3600, 0, true).unwrap())
1053 .unwrap()
1054 .call_method1("utcoffset", (PyNone::get(py),))
1055 .unwrap()
1056 .downcast_into::<PyDelta>()
1057 .unwrap()
1058 .eq(PyDelta::new(py, 0, 3600, 0, true).unwrap())
1059 .unwrap()
1060 );
1061
1062 PyTzInfo::fixed_offset(py, PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err();
1063 })
1064 }
1065}