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