1#![cfg(feature = "time")]
2
3#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"time\"] }")]
14use crate::exceptions::{PyTypeError, PyValueError};
54#[cfg(Py_LIMITED_API)]
55use crate::intern;
56#[cfg(not(Py_LIMITED_API))]
57use crate::types::datetime::{PyDateAccess, PyDeltaAccess};
58use crate::types::{PyAnyMethods, PyDate, PyDateTime, PyDelta, PyNone, PyTime, PyTzInfo};
59#[cfg(not(Py_LIMITED_API))]
60use crate::types::{PyTimeAccess, PyTzInfoAccess};
61use crate::{Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python};
62use time::{
63 Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcDateTime, UtcOffset,
64};
65
66const SECONDS_PER_DAY: i64 = 86_400;
67
68macro_rules! impl_into_py_for_ref {
70 ($type:ty, $target:ty) => {
71 impl<'py> IntoPyObject<'py> for &$type {
72 type Target = $target;
73 type Output = Bound<'py, Self::Target>;
74 type Error = PyErr;
75
76 #[inline]
77 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
78 (*self).into_pyobject(py)
79 }
80 }
81 };
82}
83
84macro_rules! month_from_number {
86 ($month:expr) => {
87 match $month {
88 1 => Month::January,
89 2 => Month::February,
90 3 => Month::March,
91 4 => Month::April,
92 5 => Month::May,
93 6 => Month::June,
94 7 => Month::July,
95 8 => Month::August,
96 9 => Month::September,
97 10 => Month::October,
98 11 => Month::November,
99 12 => Month::December,
100 _ => return Err(PyValueError::new_err("invalid month value")),
101 }
102 };
103}
104
105fn extract_date_time(dt: &Bound<'_, PyAny>) -> PyResult<(Date, Time)> {
106 #[cfg(not(Py_LIMITED_API))]
107 {
108 let dt = dt.cast::<PyDateTime>()?;
109 let date = Date::from_calendar_date(
110 dt.get_year(),
111 month_from_number!(dt.get_month()),
112 dt.get_day(),
113 )
114 .map_err(|_| PyValueError::new_err("invalid or out-of-range date"))?;
115
116 let time = Time::from_hms_micro(
117 dt.get_hour(),
118 dt.get_minute(),
119 dt.get_second(),
120 dt.get_microsecond(),
121 )
122 .map_err(|_| PyValueError::new_err("invalid or out-of-range time"))?;
123 Ok((date, time))
124 }
125
126 #[cfg(Py_LIMITED_API)]
127 {
128 let date = Date::from_calendar_date(
129 dt.getattr(intern!(dt.py(), "year"))?.extract()?,
130 month_from_number!(dt.getattr(intern!(dt.py(), "month"))?.extract::<u8>()?),
131 dt.getattr(intern!(dt.py(), "day"))?.extract()?,
132 )
133 .map_err(|_| PyValueError::new_err("invalid or out-of-range date"))?;
134
135 let time = Time::from_hms_micro(
136 dt.getattr(intern!(dt.py(), "hour"))?.extract()?,
137 dt.getattr(intern!(dt.py(), "minute"))?.extract()?,
138 dt.getattr(intern!(dt.py(), "second"))?.extract()?,
139 dt.getattr(intern!(dt.py(), "microsecond"))?.extract()?,
140 )
141 .map_err(|_| PyValueError::new_err("invalid or out-of-range time"))?;
142
143 Ok((date, time))
144 }
145}
146
147impl<'py> IntoPyObject<'py> for Duration {
148 type Target = PyDelta;
149 type Output = Bound<'py, Self::Target>;
150 type Error = PyErr;
151
152 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
153 let total_seconds = self.whole_seconds();
154 let micro_seconds = self.subsec_microseconds();
155
156 let (days, seconds) = if total_seconds < 0 && total_seconds % SECONDS_PER_DAY != 0 {
159 let days = total_seconds.div_euclid(SECONDS_PER_DAY);
162 let seconds = total_seconds.rem_euclid(SECONDS_PER_DAY);
163 (days, seconds)
164 } else {
165 (
167 total_seconds / SECONDS_PER_DAY,
168 total_seconds % SECONDS_PER_DAY,
169 )
170 };
171 PyDelta::new(
174 py,
175 days.try_into().expect("days overflow"),
176 seconds.try_into().expect("seconds overflow"),
177 micro_seconds,
178 true,
179 )
180 }
181}
182
183impl FromPyObject<'_, '_> for Duration {
184 type Error = PyErr;
185
186 fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
187 #[cfg(not(Py_LIMITED_API))]
188 let (days, seconds, microseconds) = {
189 let delta = ob.cast::<PyDelta>()?;
190 (
191 delta.get_days().into(),
192 delta.get_seconds().into(),
193 delta.get_microseconds().into(),
194 )
195 };
196
197 #[cfg(Py_LIMITED_API)]
198 let (days, seconds, microseconds) = {
199 (
200 ob.getattr(intern!(ob.py(), "days"))?.extract()?,
201 ob.getattr(intern!(ob.py(), "seconds"))?.extract()?,
202 ob.getattr(intern!(ob.py(), "microseconds"))?.extract()?,
203 )
204 };
205
206 Ok(
207 Duration::days(days)
208 + Duration::seconds(seconds)
209 + Duration::microseconds(microseconds),
210 )
211 }
212}
213
214impl<'py> IntoPyObject<'py> for Date {
215 type Target = PyDate;
216 type Output = Bound<'py, Self::Target>;
217 type Error = PyErr;
218
219 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
220 let year = self.year();
221 let month = self.month() as u8;
222 let day = self.day();
223
224 PyDate::new(py, year, month, day)
225 }
226}
227
228impl FromPyObject<'_, '_> for Date {
229 type Error = PyErr;
230
231 fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
232 let (year, month, day) = {
233 #[cfg(not(Py_LIMITED_API))]
234 {
235 let date = ob.cast::<PyDate>()?;
236 (date.get_year(), date.get_month(), date.get_day())
237 }
238
239 #[cfg(Py_LIMITED_API)]
240 {
241 let year = ob.getattr(intern!(ob.py(), "year"))?.extract()?;
242 let month: u8 = ob.getattr(intern!(ob.py(), "month"))?.extract()?;
243 let day = ob.getattr(intern!(ob.py(), "day"))?.extract()?;
244 (year, month, day)
245 }
246 };
247
248 let month = month_from_number!(month);
250
251 Date::from_calendar_date(year, month, day)
252 .map_err(|_| PyValueError::new_err("invalid or out-of-range date"))
253 }
254}
255
256impl<'py> IntoPyObject<'py> for Time {
257 type Target = PyTime;
258 type Output = Bound<'py, Self::Target>;
259 type Error = PyErr;
260
261 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
262 let hour = self.hour();
263 let minute = self.minute();
264 let second = self.second();
265 let microsecond = self.microsecond();
266
267 PyTime::new(py, hour, minute, second, microsecond, None)
268 }
269}
270
271impl FromPyObject<'_, '_> for Time {
272 type Error = PyErr;
273
274 fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
275 let (hour, minute, second, microsecond) = {
276 #[cfg(not(Py_LIMITED_API))]
277 {
278 let time = ob.cast::<PyTime>()?;
279 let hour: u8 = time.get_hour();
280 let minute: u8 = time.get_minute();
281 let second: u8 = time.get_second();
282 let microsecond = time.get_microsecond();
283 (hour, minute, second, microsecond)
284 }
285
286 #[cfg(Py_LIMITED_API)]
287 {
288 let hour: u8 = ob.getattr(intern!(ob.py(), "hour"))?.extract()?;
289 let minute: u8 = ob.getattr(intern!(ob.py(), "minute"))?.extract()?;
290 let second: u8 = ob.getattr(intern!(ob.py(), "second"))?.extract()?;
291 let microsecond = ob.getattr(intern!(ob.py(), "microsecond"))?.extract()?;
292 (hour, minute, second, microsecond)
293 }
294 };
295
296 Time::from_hms_micro(hour, minute, second, microsecond)
297 .map_err(|_| PyValueError::new_err("invalid or out-of-range time"))
298 }
299}
300
301impl<'py> IntoPyObject<'py> for PrimitiveDateTime {
302 type Target = PyDateTime;
303 type Output = Bound<'py, Self::Target>;
304 type Error = PyErr;
305
306 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
307 let date = self.date();
308 let time = self.time();
309
310 let year = date.year();
311 let month = date.month() as u8;
312 let day = date.day();
313 let hour = time.hour();
314 let minute = time.minute();
315 let second = time.second();
316 let microsecond = time.microsecond();
317
318 PyDateTime::new(
319 py,
320 year,
321 month,
322 day,
323 hour,
324 minute,
325 second,
326 microsecond,
327 None,
328 )
329 }
330}
331
332impl FromPyObject<'_, '_> for PrimitiveDateTime {
333 type Error = PyErr;
334
335 fn extract(dt: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
336 let has_tzinfo = {
337 #[cfg(not(Py_LIMITED_API))]
338 {
339 let dt = dt.cast::<PyDateTime>()?;
340 dt.get_tzinfo().is_some()
341 }
342 #[cfg(Py_LIMITED_API)]
343 {
344 !dt.getattr(intern!(dt.py(), "tzinfo"))?.is_none()
345 }
346 };
347
348 if has_tzinfo {
349 return Err(PyTypeError::new_err("expected a datetime without tzinfo"));
350 }
351
352 let (date, time) = extract_date_time(&dt)?;
353
354 Ok(PrimitiveDateTime::new(date, time))
355 }
356}
357
358impl<'py> IntoPyObject<'py> for UtcOffset {
359 type Target = PyTzInfo;
360 type Output = Bound<'py, Self::Target>;
361 type Error = PyErr;
362
363 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
364 let seconds_offset = self.whole_seconds();
366 let td = PyDelta::new(py, 0, seconds_offset, 0, true)?;
367 PyTzInfo::fixed_offset(py, td)
368 }
369}
370
371impl FromPyObject<'_, '_> for UtcOffset {
372 type Error = PyErr;
373
374 fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
375 #[cfg(not(Py_LIMITED_API))]
376 let ob = ob.cast::<PyTzInfo>()?;
377
378 let py_timedelta = ob.call_method1("utcoffset", (PyNone::get(ob.py()),))?;
380 if py_timedelta.is_none() {
381 return Err(PyTypeError::new_err(format!(
382 "{ob:?} is not a fixed offset timezone"
383 )));
384 }
385
386 let total_seconds: Duration = py_timedelta.extract()?;
387 let seconds = total_seconds.whole_seconds();
388
389 UtcOffset::from_whole_seconds(seconds as i32)
391 .map_err(|_| PyValueError::new_err("UTC offset out of bounds"))
392 }
393}
394
395impl<'py> IntoPyObject<'py> for OffsetDateTime {
396 type Target = PyDateTime;
397 type Output = Bound<'py, Self::Target>;
398 type Error = PyErr;
399
400 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
401 let date = self.date();
402 let time = self.time();
403 let offset = self.offset();
404
405 let py_tzinfo = offset.into_pyobject(py)?;
407
408 let year = date.year();
409 let month = date.month() as u8;
410 let day = date.day();
411 let hour = time.hour();
412 let minute = time.minute();
413 let second = time.second();
414 let microsecond = time.microsecond();
415
416 PyDateTime::new(
417 py,
418 year,
419 month,
420 day,
421 hour,
422 minute,
423 second,
424 microsecond,
425 Some(py_tzinfo.cast()?),
426 )
427 }
428}
429
430impl FromPyObject<'_, '_> for OffsetDateTime {
431 type Error = PyErr;
432
433 fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
434 let offset: UtcOffset = {
435 #[cfg(not(Py_LIMITED_API))]
436 {
437 let dt = ob.cast::<PyDateTime>()?;
438 let tzinfo = dt.get_tzinfo().ok_or_else(|| {
439 PyTypeError::new_err("expected a datetime with non-None tzinfo")
440 })?;
441 tzinfo.extract()?
442 }
443 #[cfg(Py_LIMITED_API)]
444 {
445 let tzinfo = ob.getattr(intern!(ob.py(), "tzinfo"))?;
446 if tzinfo.is_none() {
447 return Err(PyTypeError::new_err(
448 "expected a datetime with non-None tzinfo",
449 ));
450 }
451 tzinfo.extract()?
452 }
453 };
454
455 let (date, time) = extract_date_time(&ob)?;
456
457 let primitive_dt = PrimitiveDateTime::new(date, time);
458 Ok(primitive_dt.assume_offset(offset))
459 }
460}
461
462impl<'py> IntoPyObject<'py> for UtcDateTime {
463 type Target = PyDateTime;
464 type Output = Bound<'py, Self::Target>;
465 type Error = PyErr;
466
467 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
468 let date = self.date();
469 let time = self.time();
470
471 let py_tzinfo = PyTzInfo::utc(py)?;
472
473 let year = date.year();
474 let month = date.month() as u8;
475 let day = date.day();
476 let hour = time.hour();
477 let minute = time.minute();
478 let second = time.second();
479 let microsecond = time.microsecond();
480
481 PyDateTime::new(
482 py,
483 year,
484 month,
485 day,
486 hour,
487 minute,
488 second,
489 microsecond,
490 Some(&py_tzinfo),
491 )
492 }
493}
494
495impl FromPyObject<'_, '_> for UtcDateTime {
496 type Error = PyErr;
497
498 fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
499 let tzinfo = {
500 #[cfg(not(Py_LIMITED_API))]
501 {
502 let dt = ob.cast::<PyDateTime>()?;
503 dt.get_tzinfo().ok_or_else(|| {
504 PyTypeError::new_err("expected a datetime with non-None tzinfo")
505 })?
506 }
507
508 #[cfg(Py_LIMITED_API)]
509 {
510 let tzinfo = ob.getattr(intern!(ob.py(), "tzinfo"))?;
511 if tzinfo.is_none() {
512 return Err(PyTypeError::new_err(
513 "expected a datetime with non-None tzinfo",
514 ));
515 }
516 tzinfo
517 }
518 };
519
520 let is_utc = tzinfo.eq(PyTzInfo::utc(ob.py())?)?;
522
523 if !is_utc {
524 return Err(PyValueError::new_err(
525 "expected a datetime with UTC timezone",
526 ));
527 }
528
529 let (date, time) = extract_date_time(&ob)?;
530 let primitive_dt = PrimitiveDateTime::new(date, time);
531 Ok(primitive_dt.assume_utc().into())
532 }
533}
534
535impl_into_py_for_ref!(Duration, PyDelta);
536impl_into_py_for_ref!(Date, PyDate);
537impl_into_py_for_ref!(Time, PyTime);
538impl_into_py_for_ref!(PrimitiveDateTime, PyDateTime);
539impl_into_py_for_ref!(UtcOffset, PyTzInfo);
540impl_into_py_for_ref!(OffsetDateTime, PyDateTime);
541impl_into_py_for_ref!(UtcDateTime, PyDateTime);
542
543#[cfg(test)]
544mod tests {
545 use super::*;
546 use crate::intern;
547 use crate::types::any::PyAnyMethods;
548 use crate::types::PyTypeMethods;
549
550 mod utils {
551 use super::*;
552
553 pub(crate) fn extract_py_delta_from_duration(
554 duration: Duration,
555 py: Python<'_>,
556 ) -> (i64, i64, i64) {
557 let py_delta = duration.into_pyobject(py).unwrap();
558 let days = py_delta
559 .getattr(intern!(py, "days"))
560 .unwrap()
561 .extract::<i64>()
562 .unwrap();
563 let seconds = py_delta
564 .getattr(intern!(py, "seconds"))
565 .unwrap()
566 .extract::<i64>()
567 .unwrap();
568 let microseconds = py_delta
569 .getattr(intern!(py, "microseconds"))
570 .unwrap()
571 .extract::<i64>()
572 .unwrap();
573 (days, seconds, microseconds)
574 }
575
576 pub(crate) fn extract_py_date_from_date(date: Date, py: Python<'_>) -> (i32, u8, u8) {
577 let py_date = date.into_pyobject(py).unwrap();
578
579 let year = py_date
581 .getattr(intern!(py, "year"))
582 .unwrap()
583 .extract::<i32>()
584 .unwrap();
585 let month = py_date
586 .getattr(intern!(py, "month"))
587 .unwrap()
588 .extract::<u8>()
589 .unwrap();
590 let day = py_date
591 .getattr(intern!(py, "day"))
592 .unwrap()
593 .extract::<u8>()
594 .unwrap();
595 (year, month, day)
596 }
597
598 pub(crate) fn create_date_from_py_date(
599 py: Python<'_>,
600 year: i32,
601 month: u8,
602 day: u8,
603 ) -> PyResult<Date> {
604 let datetime = py.import("datetime").unwrap();
605 let date_type = datetime.getattr(intern!(py, "date")).unwrap();
606 let py_date = date_type.call1((year, month, day));
607 match py_date {
608 Ok(py_date) => py_date.extract(),
609 Err(err) => Err(err),
610 }
611 }
612
613 pub(crate) fn create_time_from_py_time(
614 py: Python<'_>,
615 hour: u8,
616 minute: u8,
617 second: u8,
618 microseocnd: u32,
619 ) -> PyResult<Time> {
620 let datetime = py.import("datetime").unwrap();
621 let time_type = datetime.getattr(intern!(py, "time")).unwrap();
622 let py_time = time_type.call1((hour, minute, second, microseocnd));
623 match py_time {
624 Ok(py_time) => py_time.extract(),
625 Err(err) => Err(err),
626 }
627 }
628
629 pub(crate) fn extract_py_time_from_time(time: Time, py: Python<'_>) -> (u8, u8, u8, u32) {
630 let py_time = time.into_pyobject(py).unwrap();
631 let hour = py_time
632 .getattr(intern!(py, "hour"))
633 .unwrap()
634 .extract::<u8>()
635 .unwrap();
636 let minute = py_time
637 .getattr(intern!(py, "minute"))
638 .unwrap()
639 .extract::<u8>()
640 .unwrap();
641 let second = py_time
642 .getattr(intern!(py, "second"))
643 .unwrap()
644 .extract::<u8>()
645 .unwrap();
646 let microsecond = py_time
647 .getattr(intern!(py, "microsecond"))
648 .unwrap()
649 .extract::<u32>()
650 .unwrap();
651 (hour, minute, second, microsecond)
652 }
653
654 pub(crate) fn extract_date_time_from_primitive_date_time(
655 dt: PrimitiveDateTime,
656 py: Python<'_>,
657 ) -> (u32, u8, u8, u8, u8, u8, u32) {
658 let py_dt = dt.into_pyobject(py).unwrap();
659 let year = py_dt
660 .getattr(intern!(py, "year"))
661 .unwrap()
662 .extract::<u32>()
663 .unwrap();
664 let month = py_dt
665 .getattr(intern!(py, "month"))
666 .unwrap()
667 .extract::<u8>()
668 .unwrap();
669 let day = py_dt
670 .getattr(intern!(py, "day"))
671 .unwrap()
672 .extract::<u8>()
673 .unwrap();
674 let hour = py_dt
675 .getattr(intern!(py, "hour"))
676 .unwrap()
677 .extract::<u8>()
678 .unwrap();
679 let minute = py_dt
680 .getattr(intern!(py, "minute"))
681 .unwrap()
682 .extract::<u8>()
683 .unwrap();
684 let second = py_dt
685 .getattr(intern!(py, "second"))
686 .unwrap()
687 .extract::<u8>()
688 .unwrap();
689 let microsecond = py_dt
690 .getattr(intern!(py, "microsecond"))
691 .unwrap()
692 .extract::<u32>()
693 .unwrap();
694 (year, month, day, hour, minute, second, microsecond)
695 }
696
697 #[expect(clippy::too_many_arguments)]
698 pub(crate) fn create_primitive_date_time_from_py(
699 py: Python<'_>,
700 year: u32,
701 month: u8,
702 day: u8,
703 hour: u8,
704 minute: u8,
705 second: u8,
706 microsecond: u32,
707 ) -> PyResult<PrimitiveDateTime> {
708 let datetime = py.import("datetime").unwrap();
709 let datetime_type = datetime.getattr(intern!(py, "datetime")).unwrap();
710 let py_dt = datetime_type.call1((year, month, day, hour, minute, second, microsecond));
711 match py_dt {
712 Ok(py_dt) => py_dt.extract(),
713 Err(err) => Err(err),
714 }
715 }
716
717 pub(crate) fn extract_total_seconds_from_utcoffset(
718 offset: UtcOffset,
719 py: Python<'_>,
720 ) -> f64 {
721 let py_tz = offset.into_pyobject(py).unwrap();
722 let utc_offset = py_tz.call_method1("utcoffset", (py.None(),)).unwrap();
723 let total_seconds = utc_offset
724 .getattr(intern!(py, "total_seconds"))
725 .unwrap()
726 .call0()
727 .unwrap()
728 .extract::<f64>()
729 .unwrap();
730 total_seconds
731 }
732
733 pub(crate) fn extract_from_utc_date_time(
734 dt: UtcDateTime,
735 py: Python<'_>,
736 ) -> (u32, u8, u8, u8, u8, u8, u32) {
737 let py_dt = dt.into_pyobject(py).unwrap();
738 let year = py_dt
739 .getattr(intern!(py, "year"))
740 .unwrap()
741 .extract::<u32>()
742 .unwrap();
743 let month = py_dt
744 .getattr(intern!(py, "month"))
745 .unwrap()
746 .extract::<u8>()
747 .unwrap();
748 let day = py_dt
749 .getattr(intern!(py, "day"))
750 .unwrap()
751 .extract::<u8>()
752 .unwrap();
753 let hour = py_dt
754 .getattr(intern!(py, "hour"))
755 .unwrap()
756 .extract::<u8>()
757 .unwrap();
758 let minute = py_dt
759 .getattr(intern!(py, "minute"))
760 .unwrap()
761 .extract::<u8>()
762 .unwrap();
763 let second = py_dt
764 .getattr(intern!(py, "second"))
765 .unwrap()
766 .extract::<u8>()
767 .unwrap();
768 let microsecond = py_dt
769 .getattr(intern!(py, "microsecond"))
770 .unwrap()
771 .extract::<u32>()
772 .unwrap();
773 (year, month, day, hour, minute, second, microsecond)
774 }
775 }
776 #[test]
777 fn test_time_duration_conversion() {
778 Python::attach(|py| {
779 let duration = Duration::new(1, 500_000_000); let (_, seconds, microseconds) = utils::extract_py_delta_from_duration(duration, py);
782 assert_eq!(seconds, 1);
783 assert_eq!(microseconds, 500_000);
784
785 let neg_duration = Duration::new(-10, 0); let (days, seconds, _) = utils::extract_py_delta_from_duration(neg_duration, py);
788 assert_eq!(days, -1);
789 assert_eq!(seconds, 86390); let exact_day = Duration::seconds(-86_400); let (days, seconds, microseconds) =
794 utils::extract_py_delta_from_duration(exact_day, py);
795 assert_eq!(days, -1);
796 assert_eq!(seconds, 0);
797 assert_eq!(microseconds, 0);
798 });
799 }
800
801 #[test]
802 fn test_time_duration_conversion_large_values() {
803 Python::attach(|py| {
804 let large_duration = Duration::seconds(86_399_999_000_000); let (days, _, _) = utils::extract_py_delta_from_duration(large_duration, py);
807 assert!(days > 999_000_000);
808
809 let too_large = Duration::seconds(86_400_000_000_000); let result = too_large.into_pyobject(py);
812 assert!(result.is_err());
813 let err_type = result.unwrap_err().get_type(py).name().unwrap();
814 assert_eq!(err_type, "OverflowError");
815 });
816 }
817
818 #[test]
819 fn test_time_duration_nanosecond_resolution() {
820 Python::attach(|py| {
821 let duration = Duration::new(0, 1_234_567);
823 let (_, _, microseconds) = utils::extract_py_delta_from_duration(duration, py);
824 assert_eq!(microseconds, 1234);
826 });
827 }
828
829 #[test]
830 fn test_time_duration_from_python() {
831 Python::attach(|py| {
832 let datetime = py.import("datetime").unwrap();
834 let timedelta = datetime.getattr(intern!(py, "timedelta")).unwrap();
835
836 let py_delta1 = timedelta.call1((3, 7200, 500000)).unwrap();
838 let duration1: Duration = py_delta1.extract().unwrap();
839 assert_eq!(duration1.whole_days(), 3);
840 assert_eq!(duration1.whole_seconds() % 86400, 7200);
841 assert_eq!(duration1.subsec_nanoseconds(), 500000000);
842
843 let py_delta2 = timedelta.call1((-2, 43200)).unwrap();
845 let duration2: Duration = py_delta2.extract().unwrap();
846 assert_eq!(duration2.whole_days(), -1);
847 assert_eq!(duration2.whole_seconds(), -129600);
848 });
849 }
850
851 #[test]
852 fn test_time_date_conversion() {
853 Python::attach(|py| {
854 let date = Date::from_calendar_date(2023, Month::April, 15).unwrap();
856 let (year, month, day) = utils::extract_py_date_from_date(date, py);
857 assert_eq!(year, 2023);
858 assert_eq!(month, 4);
859 assert_eq!(day, 15);
860
861 let min_date = Date::from_calendar_date(1, Month::January, 1).unwrap();
863 let (min_year, min_month, min_day) = utils::extract_py_date_from_date(min_date, py);
864 assert_eq!(min_year, 1);
865 assert_eq!(min_month, 1);
866 assert_eq!(min_day, 1);
867
868 let max_date = Date::from_calendar_date(9999, Month::December, 31).unwrap();
869 let (max_year, max_month, max_day) = utils::extract_py_date_from_date(max_date, py);
870 assert_eq!(max_year, 9999);
871 assert_eq!(max_month, 12);
872 assert_eq!(max_day, 31);
873 });
874 }
875
876 #[test]
877 fn test_time_date_from_python() {
878 Python::attach(|py| {
879 let date1 = utils::create_date_from_py_date(py, 2023, 4, 15).unwrap();
880 assert_eq!(date1.year(), 2023);
881 assert_eq!(date1.month(), Month::April);
882 assert_eq!(date1.day(), 15);
883
884 let date2 = utils::create_date_from_py_date(py, 1, 1, 1).unwrap();
886 assert_eq!(date2.year(), 1);
887 assert_eq!(date2.month(), Month::January);
888 assert_eq!(date2.day(), 1);
889
890 let date3 = utils::create_date_from_py_date(py, 9999, 12, 31).unwrap();
892 assert_eq!(date3.year(), 9999);
893 assert_eq!(date3.month(), Month::December);
894 assert_eq!(date3.day(), 31);
895
896 let date4 = utils::create_date_from_py_date(py, 2024, 2, 29).unwrap();
898 assert_eq!(date4.year(), 2024);
899 assert_eq!(date4.month(), Month::February);
900 assert_eq!(date4.day(), 29);
901 });
902 }
903
904 #[test]
905 fn test_time_date_invalid_values() {
906 Python::attach(|py| {
907 let invalid_date = utils::create_date_from_py_date(py, 2023, 2, 30);
908 assert!(invalid_date.is_err());
909
910 let another_invalid_date = utils::create_date_from_py_date(py, 2023, 13, 1);
912 assert!(another_invalid_date.is_err());
913 });
914 }
915
916 #[test]
917 fn test_time_time_conversion() {
918 Python::attach(|py| {
919 let time = Time::from_hms_micro(14, 30, 45, 123456).unwrap();
921 let (hour, minute, second, microsecond) = utils::extract_py_time_from_time(time, py);
922 assert_eq!(hour, 14);
923 assert_eq!(minute, 30);
924 assert_eq!(second, 45);
925 assert_eq!(microsecond, 123456);
926
927 let min_time = Time::from_hms_micro(0, 0, 0, 0).unwrap();
929 let (min_hour, min_minute, min_second, min_microsecond) =
930 utils::extract_py_time_from_time(min_time, py);
931 assert_eq!(min_hour, 0);
932 assert_eq!(min_minute, 0);
933 assert_eq!(min_second, 0);
934 assert_eq!(min_microsecond, 0);
935
936 let max_time = Time::from_hms_micro(23, 59, 59, 999999).unwrap();
937 let (max_hour, max_minute, max_second, max_microsecond) =
938 utils::extract_py_time_from_time(max_time, py);
939 assert_eq!(max_hour, 23);
940 assert_eq!(max_minute, 59);
941 assert_eq!(max_second, 59);
942 assert_eq!(max_microsecond, 999999);
943 });
944 }
945
946 #[test]
947 fn test_time_time_from_python() {
948 Python::attach(|py| {
949 let time1 = utils::create_time_from_py_time(py, 14, 30, 45, 123456).unwrap();
950 assert_eq!(time1.hour(), 14);
951 assert_eq!(time1.minute(), 30);
952 assert_eq!(time1.second(), 45);
953 assert_eq!(time1.microsecond(), 123456);
954
955 let time2 = utils::create_time_from_py_time(py, 0, 0, 0, 0).unwrap();
957 assert_eq!(time2.hour(), 0);
958 assert_eq!(time2.minute(), 0);
959 assert_eq!(time2.second(), 0);
960 assert_eq!(time2.microsecond(), 0);
961
962 let time3 = utils::create_time_from_py_time(py, 23, 59, 59, 999999).unwrap();
964 assert_eq!(time3.hour(), 23);
965 assert_eq!(time3.minute(), 59);
966 assert_eq!(time3.second(), 59);
967 assert_eq!(time3.microsecond(), 999999);
968 });
969 }
970
971 #[test]
972 fn test_time_time_invalid_values() {
973 Python::attach(|py| {
974 let result = utils::create_time_from_py_time(py, 24, 0, 0, 0);
975 assert!(result.is_err());
976 let result = utils::create_time_from_py_time(py, 12, 60, 0, 0);
977 assert!(result.is_err());
978 let result = utils::create_time_from_py_time(py, 12, 30, 60, 0);
979 assert!(result.is_err());
980 let result = utils::create_time_from_py_time(py, 12, 30, 30, 1000000);
981 assert!(result.is_err());
982 });
983 }
984
985 #[test]
986 fn test_time_time_with_timezone() {
987 Python::attach(|py| {
988 let datetime = py.import("datetime").unwrap();
990 let time_type = datetime.getattr(intern!(py, "time")).unwrap();
991 let tz_utc = PyTzInfo::utc(py).unwrap();
992
993 let py_time_with_tz = time_type.call1((12, 30, 45, 0, tz_utc)).unwrap();
995 let time: Time = py_time_with_tz.extract().unwrap();
996
997 assert_eq!(time.hour(), 12);
998 assert_eq!(time.minute(), 30);
999 assert_eq!(time.second(), 45);
1000 });
1001 }
1002
1003 #[test]
1004 fn test_time_primitive_datetime_conversion() {
1005 Python::attach(|py| {
1006 let date = Date::from_calendar_date(2023, Month::April, 15).unwrap();
1008 let time = Time::from_hms_micro(14, 30, 45, 123456).unwrap();
1009 let dt = PrimitiveDateTime::new(date, time);
1010 let (year, month, day, hour, minute, second, microsecond) =
1011 utils::extract_date_time_from_primitive_date_time(dt, py);
1012
1013 assert_eq!(year, 2023);
1014 assert_eq!(month, 4);
1015 assert_eq!(day, 15);
1016 assert_eq!(hour, 14);
1017 assert_eq!(minute, 30);
1018 assert_eq!(second, 45);
1019 assert_eq!(microsecond, 123456);
1020
1021 let min_date = Date::from_calendar_date(1, Month::January, 1).unwrap();
1023 let min_time = Time::from_hms_micro(0, 0, 0, 0).unwrap();
1024 let min_dt = PrimitiveDateTime::new(min_date, min_time);
1025 let (year, month, day, hour, minute, second, microsecond) =
1026 utils::extract_date_time_from_primitive_date_time(min_dt, py);
1027 assert_eq!(year, 1);
1028 assert_eq!(month, 1);
1029 assert_eq!(day, 1);
1030 assert_eq!(hour, 0);
1031 assert_eq!(minute, 0);
1032 assert_eq!(second, 0);
1033 assert_eq!(microsecond, 0);
1034 });
1035 }
1036
1037 #[test]
1038 fn test_time_primitive_datetime_from_python() {
1039 Python::attach(|py| {
1040 let dt1 =
1041 utils::create_primitive_date_time_from_py(py, 2023, 4, 15, 14, 30, 45, 123456)
1042 .unwrap();
1043 assert_eq!(dt1.year(), 2023);
1044 assert_eq!(dt1.month(), Month::April);
1045 assert_eq!(dt1.day(), 15);
1046 assert_eq!(dt1.hour(), 14);
1047 assert_eq!(dt1.minute(), 30);
1048 assert_eq!(dt1.second(), 45);
1049 assert_eq!(dt1.microsecond(), 123456);
1050
1051 let dt2 = utils::create_primitive_date_time_from_py(py, 1, 1, 1, 0, 0, 0, 0).unwrap();
1052 assert_eq!(dt2.year(), 1);
1053 assert_eq!(dt2.month(), Month::January);
1054 assert_eq!(dt2.day(), 1);
1055 assert_eq!(dt2.hour(), 0);
1056 assert_eq!(dt2.minute(), 0);
1057 });
1058 }
1059
1060 #[test]
1061 fn test_time_utc_offset_conversion() {
1062 Python::attach(|py| {
1063 let offset = UtcOffset::from_hms(5, 30, 0).unwrap();
1065 let total_seconds = utils::extract_total_seconds_from_utcoffset(offset, py);
1066 assert_eq!(total_seconds, 5.0 * 3600.0 + 30.0 * 60.0);
1067
1068 let neg_offset = UtcOffset::from_hms(-8, -15, 0).unwrap();
1070 let neg_total_seconds = utils::extract_total_seconds_from_utcoffset(neg_offset, py);
1071 assert_eq!(neg_total_seconds, -8.0 * 3600.0 - 15.0 * 60.0);
1072 });
1073 }
1074
1075 #[test]
1076 fn test_time_utc_offset_from_python() {
1077 Python::attach(|py| {
1078 let datetime = py.import("datetime").unwrap();
1080 let timezone = datetime.getattr(intern!(py, "timezone")).unwrap();
1081 let timedelta = datetime.getattr(intern!(py, "timedelta")).unwrap();
1082
1083 let tz_utc = PyTzInfo::utc(py).unwrap();
1085 let utc_offset: UtcOffset = tz_utc.extract().unwrap();
1086 assert_eq!(utc_offset.whole_hours(), 0);
1087 assert_eq!(utc_offset.minutes_past_hour(), 0);
1088 assert_eq!(utc_offset.seconds_past_minute(), 0);
1089
1090 let td_pos = timedelta.call1((0, 19800, 0)).unwrap(); let tz_pos = timezone.call1((td_pos,)).unwrap();
1093 let offset_pos: UtcOffset = tz_pos.extract().unwrap();
1094 assert_eq!(offset_pos.whole_hours(), 5);
1095 assert_eq!(offset_pos.minutes_past_hour(), 30);
1096
1097 let td_neg = timedelta.call1((0, -30900, 0)).unwrap(); let tz_neg = timezone.call1((td_neg,)).unwrap();
1100 let offset_neg: UtcOffset = tz_neg.extract().unwrap();
1101 assert_eq!(offset_neg.whole_hours(), -8);
1102 assert_eq!(offset_neg.minutes_past_hour(), -35);
1103 });
1104 }
1105
1106 #[test]
1107 fn test_time_offset_datetime_conversion() {
1108 Python::attach(|py| {
1109 let date = Date::from_calendar_date(2023, Month::April, 15).unwrap();
1111 let time = Time::from_hms_micro(14, 30, 45, 123456).unwrap();
1112 let offset = UtcOffset::from_hms(5, 30, 0).unwrap();
1113 let dt = PrimitiveDateTime::new(date, time).assume_offset(offset);
1114
1115 let py_dt = dt.into_pyobject(py).unwrap();
1117
1118 let year = py_dt
1120 .getattr(intern!(py, "year"))
1121 .unwrap()
1122 .extract::<i32>()
1123 .unwrap();
1124 let month = py_dt
1125 .getattr(intern!(py, "month"))
1126 .unwrap()
1127 .extract::<u8>()
1128 .unwrap();
1129 let day = py_dt
1130 .getattr(intern!(py, "day"))
1131 .unwrap()
1132 .extract::<u8>()
1133 .unwrap();
1134 let hour = py_dt
1135 .getattr(intern!(py, "hour"))
1136 .unwrap()
1137 .extract::<u8>()
1138 .unwrap();
1139 let minute = py_dt
1140 .getattr(intern!(py, "minute"))
1141 .unwrap()
1142 .extract::<u8>()
1143 .unwrap();
1144 let second = py_dt
1145 .getattr(intern!(py, "second"))
1146 .unwrap()
1147 .extract::<u8>()
1148 .unwrap();
1149 let microsecond = py_dt
1150 .getattr(intern!(py, "microsecond"))
1151 .unwrap()
1152 .extract::<u32>()
1153 .unwrap();
1154
1155 assert_eq!(year, 2023);
1156 assert_eq!(month, 4);
1157 assert_eq!(day, 15);
1158 assert_eq!(hour, 14);
1159 assert_eq!(minute, 30);
1160 assert_eq!(second, 45);
1161 assert_eq!(microsecond, 123456);
1162
1163 let tzinfo = py_dt.getattr(intern!(py, "tzinfo")).unwrap();
1165 let utcoffset = tzinfo.call_method1("utcoffset", (py_dt,)).unwrap();
1166 let seconds = utcoffset
1167 .call_method0("total_seconds")
1168 .unwrap()
1169 .extract::<f64>()
1170 .unwrap();
1171 assert_eq!(seconds, 5.0 * 3600.0 + 30.0 * 60.0);
1172 });
1173 }
1174
1175 #[test]
1176 fn test_time_offset_datetime_from_python() {
1177 Python::attach(|py| {
1178 let datetime = py.import("datetime").unwrap();
1180 let datetime_type = datetime.getattr(intern!(py, "datetime")).unwrap();
1181 let timezone = datetime.getattr(intern!(py, "timezone")).unwrap();
1182 let timedelta = datetime.getattr(intern!(py, "timedelta")).unwrap();
1183
1184 let td = timedelta.call1((0, 19800, 0)).unwrap(); let tz = timezone.call1((td,)).unwrap();
1187
1188 let py_dt = datetime_type
1190 .call1((2023, 4, 15, 14, 30, 45, 123456, tz))
1191 .unwrap();
1192
1193 let dt: OffsetDateTime = py_dt.extract().unwrap();
1195
1196 assert_eq!(dt.year(), 2023);
1198 assert_eq!(dt.month(), Month::April);
1199 assert_eq!(dt.day(), 15);
1200 assert_eq!(dt.hour(), 14);
1201 assert_eq!(dt.minute(), 30);
1202 assert_eq!(dt.second(), 45);
1203 assert_eq!(dt.microsecond(), 123456);
1204 assert_eq!(dt.offset().whole_hours(), 5);
1205 assert_eq!(dt.offset().minutes_past_hour(), 30);
1206 });
1207 }
1208
1209 #[test]
1210 fn test_time_utc_datetime_conversion() {
1211 Python::attach(|py| {
1212 let date = Date::from_calendar_date(2023, Month::April, 15).unwrap();
1213 let time = Time::from_hms_micro(14, 30, 45, 123456).unwrap();
1214 let primitive_dt = PrimitiveDateTime::new(date, time);
1215 let dt: UtcDateTime = primitive_dt.assume_utc().into();
1216 let (year, month, day, hour, minute, second, microsecond) =
1217 utils::extract_from_utc_date_time(dt, py);
1218
1219 assert_eq!(year, 2023);
1220 assert_eq!(month, 4);
1221 assert_eq!(day, 15);
1222 assert_eq!(hour, 14);
1223 assert_eq!(minute, 30);
1224 assert_eq!(second, 45);
1225 assert_eq!(microsecond, 123456);
1226 });
1227 }
1228
1229 #[test]
1230 fn test_time_utc_datetime_from_python() {
1231 Python::attach(|py| {
1232 let datetime = py.import("datetime").unwrap();
1234 let datetime_type = datetime.getattr(intern!(py, "datetime")).unwrap();
1235 let tz_utc = PyTzInfo::utc(py).unwrap();
1236
1237 let py_dt = datetime_type
1239 .call1((2023, 4, 15, 14, 30, 45, 123456, tz_utc))
1240 .unwrap();
1241
1242 let dt: UtcDateTime = py_dt.extract().unwrap();
1244
1245 assert_eq!(dt.year(), 2023);
1247 assert_eq!(dt.month(), Month::April);
1248 assert_eq!(dt.day(), 15);
1249 assert_eq!(dt.hour(), 14);
1250 assert_eq!(dt.minute(), 30);
1251 assert_eq!(dt.second(), 45);
1252 assert_eq!(dt.microsecond(), 123456);
1253 });
1254 }
1255
1256 #[test]
1257 fn test_time_utc_datetime_non_utc_timezone() {
1258 Python::attach(|py| {
1259 let datetime = py.import("datetime").unwrap();
1261 let datetime_type = datetime.getattr(intern!(py, "datetime")).unwrap();
1262 let timezone = datetime.getattr(intern!(py, "timezone")).unwrap();
1263 let timedelta = datetime.getattr(intern!(py, "timedelta")).unwrap();
1264
1265 let td = timedelta.call1((0, -18000, 0)).unwrap(); let tz_est = timezone.call1((td,)).unwrap();
1268
1269 let py_dt = datetime_type
1271 .call1((2023, 4, 15, 14, 30, 45, 123456, tz_est))
1272 .unwrap();
1273
1274 let result: Result<UtcDateTime, _> = py_dt.extract();
1276 assert!(result.is_err());
1277 });
1278 }
1279
1280 #[cfg(not(any(target_arch = "wasm32", Py_GIL_DISABLED)))]
1281 mod proptests {
1282 use super::*;
1283 use proptest::proptest;
1284
1285 proptest! {
1286 #[test]
1287 fn test_time_duration_roundtrip(days in -9999i64..=9999i64, seconds in -86399i64..=86399i64, microseconds in -999999i64..=999999i64) {
1288 Python::attach(|py| {
1290 let duration = Duration::days(days) + Duration::seconds(seconds) + Duration::microseconds(microseconds);
1291
1292 let max_seconds = 86_399_999_913_600;
1294 if duration.whole_seconds() <= max_seconds && duration.whole_seconds() >= -max_seconds {
1295 let py_delta = duration.into_pyobject(py).unwrap();
1296
1297 let total_seconds = py_delta.call_method0(intern!(py, "total_seconds")).unwrap().extract::<f64>().unwrap();
1300 let expected_seconds = duration.whole_seconds() as f64 + (duration.subsec_nanoseconds() as f64 / 1_000_000_000.0);
1301
1302 assert_eq!(total_seconds, expected_seconds);
1304 }
1305 })
1306 }
1307
1308 #[test]
1309 fn test_all_valid_dates(
1310 year in 1i32..=9999,
1311 month_num in 1u8..=12,
1312 ) {
1313 Python::attach(|py| {
1314 let month = match month_num {
1315 1 => (Month::January, 31),
1316 2 => {
1317 if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
1319 (Month::February, 29)
1320 } else {
1321 (Month::February, 28)
1322 }
1323 },
1324 3 => (Month::March, 31),
1325 4 => (Month::April, 30),
1326 5 => (Month::May, 31),
1327 6 => (Month::June, 30),
1328 7 => (Month::July, 31),
1329 8 => (Month::August, 31),
1330 9 => (Month::September, 30),
1331 10 => (Month::October, 31),
1332 11 => (Month::November, 30),
1333 12 => (Month::December, 31),
1334 _ => unreachable!(),
1335 };
1336
1337 for day in 1..=month.1 {
1339 let date = Date::from_calendar_date(year, month.0, day).unwrap();
1340 let py_date = date.into_pyobject(py).unwrap();
1341 let roundtripped: Date = py_date.extract().unwrap();
1342 assert_eq!(date, roundtripped);
1343 }
1344 });
1345 }
1346
1347 #[test]
1348 fn test_time_time_roundtrip_random(
1349 hour in 0u8..=23u8,
1350 minute in 0u8..=59u8,
1351 second in 0u8..=59u8,
1352 microsecond in 0u32..=999999u32
1353 ) {
1354 Python::attach(|py| {
1355 let time = Time::from_hms_micro(hour, minute, second, microsecond).unwrap();
1356 let py_time = time.into_pyobject(py).unwrap();
1357 let roundtripped: Time = py_time.extract().unwrap();
1358 assert_eq!(time, roundtripped);
1359 });
1360 }
1361
1362 #[test]
1363 fn test_time_primitive_datetime_roundtrip_random(
1364 year in 1i32..=9999i32,
1365 month in 1u8..=12u8,
1366 day in 1u8..=28u8, hour in 0u8..=23u8,
1368 minute in 0u8..=59u8,
1369 second in 0u8..=59u8,
1370 microsecond in 0u32..=999999u32
1371 ) {
1372 Python::attach(|py| {
1373 let month = match month {
1374 1 => Month::January,
1375 2 => Month::February,
1376 3 => Month::March,
1377 4 => Month::April,
1378 5 => Month::May,
1379 6 => Month::June,
1380 7 => Month::July,
1381 8 => Month::August,
1382 9 => Month::September,
1383 10 => Month::October,
1384 11 => Month::November,
1385 12 => Month::December,
1386 _ => unreachable!(),
1387 };
1388
1389 let date = Date::from_calendar_date(year, month, day).unwrap();
1390 let time = Time::from_hms_micro(hour, minute, second, microsecond).unwrap();
1391 let dt = PrimitiveDateTime::new(date, time);
1392
1393 let py_dt = dt.into_pyobject(py).unwrap();
1394 let roundtripped: PrimitiveDateTime = py_dt.extract().unwrap();
1395 assert_eq!(dt, roundtripped);
1396 });
1397 }
1398
1399 #[test]
1400 fn test_time_utc_offset_roundtrip_random(
1401 hours in -23i8..=23i8,
1402 minutes in -59i8..=59i8
1403 ) {
1404 if (hours < 0 && minutes > 0) || (hours > 0 && minutes < 0) {
1406 return Ok(());
1407 }
1408
1409 Python::attach(|py| {
1410 if let Ok(offset) = UtcOffset::from_hms(hours, minutes, 0) {
1411 let py_tz = offset.into_pyobject(py).unwrap();
1412 let roundtripped: UtcOffset = py_tz.extract().unwrap();
1413 assert_eq!(roundtripped.whole_hours(), hours);
1414 assert_eq!(roundtripped.minutes_past_hour(), minutes);
1415 }
1416 });
1417 }
1418
1419 #[test]
1420 fn test_time_offset_datetime_roundtrip_random(
1421 year in 1i32..=9999i32,
1422 month in 1u8..=12u8,
1423 day in 1u8..=28u8, hour in 0u8..=23u8,
1425 minute in 0u8..=59u8,
1426 second in 0u8..=59u8,
1427 microsecond in 0u32..=999999u32,
1428 tz_hour in -23i8..=23i8,
1429 tz_minute in 0i8..=59i8
1430 ) {
1431 Python::attach(|py| {
1432 let month = match month {
1433 1 => Month::January,
1434 2 => Month::February,
1435 3 => Month::March,
1436 4 => Month::April,
1437 5 => Month::May,
1438 6 => Month::June,
1439 7 => Month::July,
1440 8 => Month::August,
1441 9 => Month::September,
1442 10 => Month::October,
1443 11 => Month::November,
1444 12 => Month::December,
1445 _ => unreachable!(),
1446 };
1447
1448 let date = Date::from_calendar_date(year, month, day).unwrap();
1449 let time = Time::from_hms_micro(hour, minute, second, microsecond).unwrap();
1450
1451 let tz_minute = if tz_hour < 0 { -tz_minute } else { tz_minute };
1453
1454 if let Ok(offset) = UtcOffset::from_hms(tz_hour, tz_minute, 0) {
1455 let dt = PrimitiveDateTime::new(date, time).assume_offset(offset);
1456 let py_dt = dt.into_pyobject(py).unwrap();
1457 let roundtripped: OffsetDateTime = py_dt.extract().unwrap();
1458
1459 assert_eq!(dt.year(), roundtripped.year());
1460 assert_eq!(dt.month(), roundtripped.month());
1461 assert_eq!(dt.day(), roundtripped.day());
1462 assert_eq!(dt.hour(), roundtripped.hour());
1463 assert_eq!(dt.minute(), roundtripped.minute());
1464 assert_eq!(dt.second(), roundtripped.second());
1465 assert_eq!(dt.microsecond(), roundtripped.microsecond());
1466 assert_eq!(dt.offset().whole_hours(), roundtripped.offset().whole_hours());
1467 assert_eq!(dt.offset().minutes_past_hour(), roundtripped.offset().minutes_past_hour());
1468 }
1469 });
1470 }
1471
1472 #[test]
1473 fn test_time_utc_datetime_roundtrip_random(
1474 year in 1i32..=9999i32,
1475 month in 1u8..=12u8,
1476 day in 1u8..=28u8, hour in 0u8..=23u8,
1478 minute in 0u8..=59u8,
1479 second in 0u8..=59u8,
1480 microsecond in 0u32..=999999u32
1481 ) {
1482 Python::attach(|py| {
1483 let month = match month {
1484 1 => Month::January,
1485 2 => Month::February,
1486 3 => Month::March,
1487 4 => Month::April,
1488 5 => Month::May,
1489 6 => Month::June,
1490 7 => Month::July,
1491 8 => Month::August,
1492 9 => Month::September,
1493 10 => Month::October,
1494 11 => Month::November,
1495 12 => Month::December,
1496 _ => unreachable!(),
1497 };
1498
1499 let date = Date::from_calendar_date(year, month, day).unwrap();
1500 let time = Time::from_hms_micro(hour, minute, second, microsecond).unwrap();
1501 let primitive_dt = PrimitiveDateTime::new(date, time);
1502 let dt: UtcDateTime = primitive_dt.assume_utc().into();
1503
1504 let py_dt = dt.into_pyobject(py).unwrap();
1505 let roundtripped: UtcDateTime = py_dt.extract().unwrap();
1506
1507 assert_eq!(dt.year(), roundtripped.year());
1508 assert_eq!(dt.month(), roundtripped.month());
1509 assert_eq!(dt.day(), roundtripped.day());
1510 assert_eq!(dt.hour(), roundtripped.hour());
1511 assert_eq!(dt.minute(), roundtripped.minute());
1512 assert_eq!(dt.second(), roundtripped.second());
1513 assert_eq!(dt.microsecond(), roundtripped.microsecond());
1514 })
1515 }
1516 }
1517 }
1518}