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