1use crate::err::PyResult;
7use crate::ffi::{
8 self, PyDateTime_CAPI, PyDateTime_FromTimestamp, PyDateTime_IMPORT, PyDate_FromTimestamp,
9};
10use crate::ffi::{
11 PyDateTime_DATE_GET_FOLD, PyDateTime_DATE_GET_HOUR, PyDateTime_DATE_GET_MICROSECOND,
12 PyDateTime_DATE_GET_MINUTE, PyDateTime_DATE_GET_SECOND,
13};
14#[cfg(GraalPy)]
15use crate::ffi::{PyDateTime_DATE_GET_TZINFO, PyDateTime_TIME_GET_TZINFO, Py_IsNone};
16use crate::ffi::{
17 PyDateTime_DELTA_GET_DAYS, PyDateTime_DELTA_GET_MICROSECONDS, PyDateTime_DELTA_GET_SECONDS,
18};
19use crate::ffi::{PyDateTime_GET_DAY, PyDateTime_GET_MONTH, PyDateTime_GET_YEAR};
20use crate::ffi::{
21 PyDateTime_TIME_GET_FOLD, PyDateTime_TIME_GET_HOUR, PyDateTime_TIME_GET_MICROSECOND,
22 PyDateTime_TIME_GET_MINUTE, PyDateTime_TIME_GET_SECOND,
23};
24use crate::ffi_ptr_ext::FfiPtrExt;
25use crate::py_result_ext::PyResultExt;
26use crate::types::any::PyAnyMethods;
27use crate::types::PyTuple;
28use crate::{Bound, IntoPyObject, PyAny, PyErr, Python};
29use std::os::raw::c_int;
30#[cfg(any(feature = "chrono", feature = "jiff-02"))]
31use std::ptr;
32
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
45fn expect_datetime_api(py: Python<'_>) -> &'static PyDateTime_CAPI {
46 ensure_datetime_api(py).expect("failed to import `datetime` C API")
47}
48
49macro_rules! ffi_fun_with_autoinit {
62 ($(#[$outer:meta] unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => {
63 $(
64 #[$outer]
65 #[allow(non_snake_case)]
66 unsafe fn $name($arg: *mut crate::ffi::PyObject) -> $ret {
70
71 let _ = ensure_datetime_api(Python::assume_gil_acquired());
72 crate::ffi::$name($arg)
73 }
74 )*
75
76
77 };
78}
79
80ffi_fun_with_autoinit! {
81 unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
83
84 unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
86
87 unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
89
90 unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
92
93 unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
95}
96
97pub trait PyDateAccess {
101 fn get_year(&self) -> i32;
106 fn get_month(&self) -> u8;
111 fn get_day(&self) -> u8;
116}
117
118pub trait PyDeltaAccess {
124 fn get_days(&self) -> i32;
129 fn get_seconds(&self) -> i32;
134 fn get_microseconds(&self) -> i32;
139}
140
141pub trait PyTimeAccess {
143 fn get_hour(&self) -> u8;
148 fn get_minute(&self) -> u8;
153 fn get_second(&self) -> u8;
158 fn get_microsecond(&self) -> u32;
163 fn get_fold(&self) -> bool;
170}
171
172pub trait PyTzInfoAccess<'py> {
174 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>>;
180
181 #[deprecated(since = "0.23.0", note = "renamed to `PyTzInfoAccess::get_tzinfo`")]
183 #[inline]
184 fn get_tzinfo_bound(&self) -> Option<Bound<'py, PyTzInfo>> {
185 self.get_tzinfo()
186 }
187}
188
189#[repr(transparent)]
194pub struct PyDate(PyAny);
195pyobject_native_type!(
196 PyDate,
197 crate::ffi::PyDateTime_Date,
198 |py| expect_datetime_api(py).DateType,
199 #module=Some("datetime"),
200 #checkfunction=PyDate_Check
201);
202pyobject_subclassable_native_type!(PyDate, crate::ffi::PyDateTime_Date);
203
204impl PyDate {
205 pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
207 let api = ensure_datetime_api(py)?;
208 unsafe {
209 (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
210 .assume_owned_or_err(py)
211 .downcast_into_unchecked()
212 }
213 }
214
215 #[deprecated(since = "0.23.0", note = "renamed to `PyDate::new`")]
217 #[inline]
218 pub fn new_bound(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
219 Self::new(py, year, month, day)
220 }
221
222 pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<Bound<'_, PyDate>> {
226 let time_tuple = PyTuple::new(py, [timestamp])?;
227
228 let _api = ensure_datetime_api(py)?;
230
231 unsafe {
232 PyDate_FromTimestamp(time_tuple.as_ptr())
233 .assume_owned_or_err(py)
234 .downcast_into_unchecked()
235 }
236 }
237
238 #[deprecated(since = "0.23.0", note = "renamed to `PyDate::from_timestamp`")]
240 #[inline]
241 pub fn from_timestamp_bound(py: Python<'_>, timestamp: i64) -> PyResult<Bound<'_, PyDate>> {
242 Self::from_timestamp(py, timestamp)
243 }
244}
245
246impl PyDateAccess for Bound<'_, PyDate> {
247 fn get_year(&self) -> i32 {
248 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
249 }
250
251 fn get_month(&self) -> u8 {
252 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
253 }
254
255 fn get_day(&self) -> u8 {
256 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
257 }
258}
259
260#[repr(transparent)]
265pub struct PyDateTime(PyAny);
266pyobject_native_type!(
267 PyDateTime,
268 crate::ffi::PyDateTime_DateTime,
269 |py| expect_datetime_api(py).DateTimeType,
270 #module=Some("datetime"),
271 #checkfunction=PyDateTime_Check
272);
273pyobject_subclassable_native_type!(PyDateTime, crate::ffi::PyDateTime_DateTime);
274
275impl PyDateTime {
276 #[allow(clippy::too_many_arguments)]
278 pub fn new<'py>(
279 py: Python<'py>,
280 year: i32,
281 month: u8,
282 day: u8,
283 hour: u8,
284 minute: u8,
285 second: u8,
286 microsecond: u32,
287 tzinfo: Option<&Bound<'py, PyTzInfo>>,
288 ) -> PyResult<Bound<'py, PyDateTime>> {
289 let api = ensure_datetime_api(py)?;
290 unsafe {
291 (api.DateTime_FromDateAndTime)(
292 year,
293 c_int::from(month),
294 c_int::from(day),
295 c_int::from(hour),
296 c_int::from(minute),
297 c_int::from(second),
298 microsecond as c_int,
299 opt_to_pyobj(tzinfo),
300 api.DateTimeType,
301 )
302 .assume_owned_or_err(py)
303 .downcast_into_unchecked()
304 }
305 }
306
307 #[deprecated(since = "0.23.0", note = "renamed to `PyDateTime::new`")]
309 #[inline]
310 #[allow(clippy::too_many_arguments)]
311 pub fn new_bound<'py>(
312 py: Python<'py>,
313 year: i32,
314 month: u8,
315 day: u8,
316 hour: u8,
317 minute: u8,
318 second: u8,
319 microsecond: u32,
320 tzinfo: Option<&Bound<'py, PyTzInfo>>,
321 ) -> PyResult<Bound<'py, PyDateTime>> {
322 Self::new(
323 py,
324 year,
325 month,
326 day,
327 hour,
328 minute,
329 second,
330 microsecond,
331 tzinfo,
332 )
333 }
334
335 #[allow(clippy::too_many_arguments)]
343 pub fn new_with_fold<'py>(
344 py: Python<'py>,
345 year: i32,
346 month: u8,
347 day: u8,
348 hour: u8,
349 minute: u8,
350 second: u8,
351 microsecond: u32,
352 tzinfo: Option<&Bound<'py, PyTzInfo>>,
353 fold: bool,
354 ) -> PyResult<Bound<'py, PyDateTime>> {
355 let api = ensure_datetime_api(py)?;
356 unsafe {
357 (api.DateTime_FromDateAndTimeAndFold)(
358 year,
359 c_int::from(month),
360 c_int::from(day),
361 c_int::from(hour),
362 c_int::from(minute),
363 c_int::from(second),
364 microsecond as c_int,
365 opt_to_pyobj(tzinfo),
366 c_int::from(fold),
367 api.DateTimeType,
368 )
369 .assume_owned_or_err(py)
370 .downcast_into_unchecked()
371 }
372 }
373
374 #[deprecated(since = "0.23.0", note = "renamed to `PyDateTime::new_with_fold`")]
376 #[inline]
377 #[allow(clippy::too_many_arguments)]
378 pub fn new_bound_with_fold<'py>(
379 py: Python<'py>,
380 year: i32,
381 month: u8,
382 day: u8,
383 hour: u8,
384 minute: u8,
385 second: u8,
386 microsecond: u32,
387 tzinfo: Option<&Bound<'py, PyTzInfo>>,
388 fold: bool,
389 ) -> PyResult<Bound<'py, PyDateTime>> {
390 Self::new_with_fold(
391 py,
392 year,
393 month,
394 day,
395 hour,
396 minute,
397 second,
398 microsecond,
399 tzinfo,
400 fold,
401 )
402 }
403
404 pub fn from_timestamp<'py>(
408 py: Python<'py>,
409 timestamp: f64,
410 tzinfo: Option<&Bound<'py, PyTzInfo>>,
411 ) -> PyResult<Bound<'py, PyDateTime>> {
412 let args = (timestamp, tzinfo).into_pyobject(py)?;
413
414 let _api = ensure_datetime_api(py)?;
416
417 unsafe {
418 PyDateTime_FromTimestamp(args.as_ptr())
419 .assume_owned_or_err(py)
420 .downcast_into_unchecked()
421 }
422 }
423
424 #[deprecated(since = "0.23.0", note = "renamed to `PyDateTime::from_timestamp`")]
426 #[inline]
427 pub fn from_timestamp_bound<'py>(
428 py: Python<'py>,
429 timestamp: f64,
430 tzinfo: Option<&Bound<'py, PyTzInfo>>,
431 ) -> PyResult<Bound<'py, PyDateTime>> {
432 Self::from_timestamp(py, timestamp, tzinfo)
433 }
434}
435
436impl PyDateAccess for Bound<'_, PyDateTime> {
437 fn get_year(&self) -> i32 {
438 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
439 }
440
441 fn get_month(&self) -> u8 {
442 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
443 }
444
445 fn get_day(&self) -> u8 {
446 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
447 }
448}
449
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 let ptr = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
475 #[cfg(not(GraalPy))]
476 unsafe {
477 if (*ptr).hastzinfo != 0 {
478 Some(
479 (*ptr)
480 .tzinfo
481 .assume_borrowed(self.py())
482 .to_owned()
483 .downcast_into_unchecked(),
484 )
485 } else {
486 None
487 }
488 }
489
490 #[cfg(GraalPy)]
491 unsafe {
492 let res = PyDateTime_DATE_GET_TZINFO(ptr as *mut ffi::PyObject);
493 if Py_IsNone(res) == 1 {
494 None
495 } else {
496 Some(
497 res.assume_borrowed(self.py())
498 .to_owned()
499 .downcast_into_unchecked(),
500 )
501 }
502 }
503 }
504}
505
506#[repr(transparent)]
511pub struct PyTime(PyAny);
512pyobject_native_type!(
513 PyTime,
514 crate::ffi::PyDateTime_Time,
515 |py| expect_datetime_api(py).TimeType,
516 #module=Some("datetime"),
517 #checkfunction=PyTime_Check
518);
519pyobject_subclassable_native_type!(PyTime, crate::ffi::PyDateTime_Time);
520
521impl PyTime {
522 pub fn new<'py>(
524 py: Python<'py>,
525 hour: u8,
526 minute: u8,
527 second: u8,
528 microsecond: u32,
529 tzinfo: Option<&Bound<'py, PyTzInfo>>,
530 ) -> PyResult<Bound<'py, PyTime>> {
531 let api = ensure_datetime_api(py)?;
532 unsafe {
533 (api.Time_FromTime)(
534 c_int::from(hour),
535 c_int::from(minute),
536 c_int::from(second),
537 microsecond as c_int,
538 opt_to_pyobj(tzinfo),
539 api.TimeType,
540 )
541 .assume_owned_or_err(py)
542 .downcast_into_unchecked()
543 }
544 }
545
546 #[deprecated(since = "0.23.0", note = "renamed to `PyTime::new`")]
548 #[inline]
549 pub fn new_bound<'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 Self::new(py, hour, minute, second, microsecond, tzinfo)
558 }
559
560 pub fn new_with_fold<'py>(
562 py: Python<'py>,
563 hour: u8,
564 minute: u8,
565 second: u8,
566 microsecond: u32,
567 tzinfo: Option<&Bound<'py, PyTzInfo>>,
568 fold: bool,
569 ) -> PyResult<Bound<'py, PyTime>> {
570 let api = ensure_datetime_api(py)?;
571 unsafe {
572 (api.Time_FromTimeAndFold)(
573 c_int::from(hour),
574 c_int::from(minute),
575 c_int::from(second),
576 microsecond as c_int,
577 opt_to_pyobj(tzinfo),
578 fold as c_int,
579 api.TimeType,
580 )
581 .assume_owned_or_err(py)
582 .downcast_into_unchecked()
583 }
584 }
585
586 #[deprecated(since = "0.23.0", note = "renamed to `PyTime::new_with_fold`")]
588 #[inline]
589 pub fn new_bound_with_fold<'py>(
590 py: Python<'py>,
591 hour: u8,
592 minute: u8,
593 second: u8,
594 microsecond: u32,
595 tzinfo: Option<&Bound<'py, PyTzInfo>>,
596 fold: bool,
597 ) -> PyResult<Bound<'py, PyTime>> {
598 Self::new_with_fold(py, hour, minute, second, microsecond, tzinfo, fold)
599 }
600}
601
602impl PyTimeAccess for Bound<'_, PyTime> {
603 fn get_hour(&self) -> u8 {
604 unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 }
605 }
606
607 fn get_minute(&self) -> u8 {
608 unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 }
609 }
610
611 fn get_second(&self) -> u8 {
612 unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 }
613 }
614
615 fn get_microsecond(&self) -> u32 {
616 unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 }
617 }
618
619 fn get_fold(&self) -> bool {
620 unsafe { PyDateTime_TIME_GET_FOLD(self.as_ptr()) != 0 }
621 }
622}
623
624impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyTime> {
625 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
626 let ptr = self.as_ptr() as *mut ffi::PyDateTime_Time;
627 #[cfg(not(GraalPy))]
628 unsafe {
629 if (*ptr).hastzinfo != 0 {
630 Some(
631 (*ptr)
632 .tzinfo
633 .assume_borrowed(self.py())
634 .to_owned()
635 .downcast_into_unchecked(),
636 )
637 } else {
638 None
639 }
640 }
641
642 #[cfg(GraalPy)]
643 unsafe {
644 let res = PyDateTime_TIME_GET_TZINFO(ptr as *mut ffi::PyObject);
645 if Py_IsNone(res) == 1 {
646 None
647 } else {
648 Some(
649 res.assume_borrowed(self.py())
650 .to_owned()
651 .downcast_into_unchecked(),
652 )
653 }
654 }
655 }
656}
657
658#[repr(transparent)]
667pub struct PyTzInfo(PyAny);
668pyobject_native_type!(
669 PyTzInfo,
670 crate::ffi::PyObject,
671 |py| expect_datetime_api(py).TZInfoType,
672 #module=Some("datetime"),
673 #checkfunction=PyTZInfo_Check
674);
675pyobject_subclassable_native_type!(PyTzInfo, crate::ffi::PyObject);
676
677pub fn timezone_utc(py: Python<'_>) -> Bound<'_, PyTzInfo> {
679 unsafe {
683 expect_datetime_api(py)
684 .TimeZone_UTC
685 .assume_borrowed(py)
686 .to_owned()
687 .downcast_into_unchecked()
688 }
689}
690
691#[deprecated(since = "0.23.0", note = "renamed to `timezone_utc`")]
693#[inline]
694pub fn timezone_utc_bound(py: Python<'_>) -> Bound<'_, PyTzInfo> {
695 timezone_utc(py)
696}
697
698#[cfg(any(feature = "chrono", feature = "jiff-02"))]
702pub(crate) fn timezone_from_offset<'py>(
703 offset: &Bound<'py, PyDelta>,
704) -> PyResult<Bound<'py, PyTzInfo>> {
705 let py = offset.py();
706 let api = ensure_datetime_api(py)?;
707 unsafe {
708 (api.TimeZone_FromTimeZone)(offset.as_ptr(), ptr::null_mut())
709 .assume_owned_or_err(py)
710 .downcast_into_unchecked()
711 }
712}
713
714#[repr(transparent)]
719pub struct PyDelta(PyAny);
720pyobject_native_type!(
721 PyDelta,
722 crate::ffi::PyDateTime_Delta,
723 |py| expect_datetime_api(py).DeltaType,
724 #module=Some("datetime"),
725 #checkfunction=PyDelta_Check
726);
727pyobject_subclassable_native_type!(PyDelta, crate::ffi::PyDateTime_Delta);
728
729impl PyDelta {
730 pub fn new(
732 py: Python<'_>,
733 days: i32,
734 seconds: i32,
735 microseconds: i32,
736 normalize: bool,
737 ) -> PyResult<Bound<'_, PyDelta>> {
738 let api = ensure_datetime_api(py)?;
739 unsafe {
740 (api.Delta_FromDelta)(
741 days as c_int,
742 seconds as c_int,
743 microseconds as c_int,
744 normalize as c_int,
745 api.DeltaType,
746 )
747 .assume_owned_or_err(py)
748 .downcast_into_unchecked()
749 }
750 }
751
752 #[deprecated(since = "0.23.0", note = "renamed to `PyDelta::new`")]
754 #[inline]
755 pub fn new_bound(
756 py: Python<'_>,
757 days: i32,
758 seconds: i32,
759 microseconds: i32,
760 normalize: bool,
761 ) -> PyResult<Bound<'_, PyDelta>> {
762 Self::new(py, days, seconds, microseconds, normalize)
763 }
764}
765
766impl PyDeltaAccess for Bound<'_, PyDelta> {
767 fn get_days(&self) -> i32 {
768 unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
769 }
770
771 fn get_seconds(&self) -> i32 {
772 unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
773 }
774
775 fn get_microseconds(&self) -> i32 {
776 unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
777 }
778}
779
780fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
783 match opt {
784 Some(tzi) => tzi.as_ptr(),
785 None => unsafe { ffi::Py_None() },
786 }
787}
788
789#[cfg(test)]
790mod tests {
791 use super::*;
792 #[cfg(feature = "macros")]
793 use crate::py_run;
794
795 #[test]
796 #[cfg(feature = "macros")]
797 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_datetime_fromtimestamp() {
799 Python::with_gil(|py| {
800 let dt = PyDateTime::from_timestamp(py, 100.0, None).unwrap();
801 py_run!(
802 py,
803 dt,
804 "import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
805 );
806
807 let dt = PyDateTime::from_timestamp(py, 100.0, Some(&timezone_utc(py))).unwrap();
808 py_run!(
809 py,
810 dt,
811 "import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
812 );
813 })
814 }
815
816 #[test]
817 #[cfg(feature = "macros")]
818 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_date_fromtimestamp() {
820 Python::with_gil(|py| {
821 let dt = PyDate::from_timestamp(py, 100).unwrap();
822 py_run!(
823 py,
824 dt,
825 "import datetime; assert dt == datetime.date.fromtimestamp(100)"
826 );
827 })
828 }
829
830 #[test]
831 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_new_with_fold() {
833 Python::with_gil(|py| {
834 let a = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
835 let b = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
836
837 assert!(!a.unwrap().get_fold());
838 assert!(b.unwrap().get_fold());
839 });
840 }
841
842 #[test]
843 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_get_tzinfo() {
845 crate::Python::with_gil(|py| {
846 let utc = timezone_utc(py);
847
848 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
849
850 assert!(dt.get_tzinfo().unwrap().eq(&utc).unwrap());
851
852 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
853
854 assert!(dt.get_tzinfo().is_none());
855
856 let t = PyTime::new(py, 0, 0, 0, 0, Some(&utc)).unwrap();
857
858 assert!(t.get_tzinfo().unwrap().eq(utc).unwrap());
859
860 let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
861
862 assert!(t.get_tzinfo().is_none());
863 });
864 }
865
866 #[test]
867 #[cfg(all(feature = "macros", feature = "chrono"))]
868 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_timezone_from_offset() {
870 use crate::types::PyNone;
871
872 Python::with_gil(|py| {
873 assert!(
874 timezone_from_offset(&PyDelta::new(py, 0, -3600, 0, true).unwrap())
875 .unwrap()
876 .call_method1("utcoffset", (PyNone::get(py),))
877 .unwrap()
878 .downcast_into::<PyDelta>()
879 .unwrap()
880 .eq(PyDelta::new(py, 0, -3600, 0, true).unwrap())
881 .unwrap()
882 );
883
884 assert!(
885 timezone_from_offset(&PyDelta::new(py, 0, 3600, 0, true).unwrap())
886 .unwrap()
887 .call_method1("utcoffset", (PyNone::get(py),))
888 .unwrap()
889 .downcast_into::<PyDelta>()
890 .unwrap()
891 .eq(PyDelta::new(py, 0, 3600, 0, true).unwrap())
892 .unwrap()
893 );
894
895 timezone_from_offset(&PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err();
896 })
897 }
898}