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