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