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