1use crate::err::PyResult;
7use crate::ffi::{
8 self, PyDateTime_CAPI, PyDateTime_FromTimestamp, PyDateTime_IMPORT, PyDate_FromTimestamp,
9};
10use crate::ffi::{
11 PyDateTime_DATE_GET_FOLD, PyDateTime_DATE_GET_HOUR, PyDateTime_DATE_GET_MICROSECOND,
12 PyDateTime_DATE_GET_MINUTE, PyDateTime_DATE_GET_SECOND,
13};
14#[cfg(GraalPy)]
15use crate::ffi::{PyDateTime_DATE_GET_TZINFO, PyDateTime_TIME_GET_TZINFO, Py_IsNone};
16use crate::ffi::{
17 PyDateTime_DELTA_GET_DAYS, PyDateTime_DELTA_GET_MICROSECONDS, PyDateTime_DELTA_GET_SECONDS,
18};
19use crate::ffi::{PyDateTime_GET_DAY, PyDateTime_GET_MONTH, PyDateTime_GET_YEAR};
20use crate::ffi::{
21 PyDateTime_TIME_GET_FOLD, PyDateTime_TIME_GET_HOUR, PyDateTime_TIME_GET_MICROSECOND,
22 PyDateTime_TIME_GET_MINUTE, PyDateTime_TIME_GET_SECOND,
23};
24use crate::ffi_ptr_ext::FfiPtrExt;
25use crate::py_result_ext::PyResultExt;
26use crate::types::any::PyAnyMethods;
27use crate::types::PyTuple;
28use crate::{Bound, IntoPyObject, PyAny, PyErr, Python};
29use std::os::raw::c_int;
30#[cfg(any(feature = "chrono", feature = "jiff-02"))]
31use std::ptr;
32
33fn ensure_datetime_api(py: Python<'_>) -> PyResult<&'static PyDateTime_CAPI> {
34 if let Some(api) = unsafe { pyo3_ffi::PyDateTimeAPI().as_ref() } {
35 Ok(api)
36 } else {
37 unsafe {
38 PyDateTime_IMPORT();
39 pyo3_ffi::PyDateTimeAPI().as_ref()
40 }
41 .ok_or_else(|| PyErr::fetch(py))
42 }
43}
44
45fn expect_datetime_api(py: Python<'_>) -> &'static PyDateTime_CAPI {
46 ensure_datetime_api(py).expect("failed to import `datetime` C API")
47}
48
49macro_rules! ffi_fun_with_autoinit {
62 ($(#[$outer:meta] unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => {
63 $(
64 #[$outer]
65 #[allow(non_snake_case)]
66 unsafe fn $name($arg: *mut crate::ffi::PyObject) -> $ret {
70
71 let _ = ensure_datetime_api(unsafe { Python::assume_gil_acquired() });
72 unsafe { crate::ffi::$name($arg) }
73 }
74 )*
75
76
77 };
78}
79
80ffi_fun_with_autoinit! {
81 unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
83
84 unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
86
87 unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
89
90 unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
92
93 unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
95}
96
97pub trait PyDateAccess {
101 fn get_year(&self) -> i32;
106 fn get_month(&self) -> u8;
111 fn get_day(&self) -> u8;
116}
117
118pub trait PyDeltaAccess {
124 fn get_days(&self) -> i32;
129 fn get_seconds(&self) -> i32;
134 fn get_microseconds(&self) -> i32;
139}
140
141pub trait PyTimeAccess {
143 fn get_hour(&self) -> u8;
148 fn get_minute(&self) -> u8;
153 fn get_second(&self) -> u8;
158 fn get_microsecond(&self) -> u32;
163 fn get_fold(&self) -> bool;
170}
171
172pub trait PyTzInfoAccess<'py> {
174 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>>;
180}
181
182#[repr(transparent)]
187pub struct PyDate(PyAny);
188pyobject_native_type!(
189 PyDate,
190 crate::ffi::PyDateTime_Date,
191 |py| expect_datetime_api(py).DateType,
192 #module=Some("datetime"),
193 #checkfunction=PyDate_Check
194);
195pyobject_subclassable_native_type!(PyDate, crate::ffi::PyDateTime_Date);
196
197impl PyDate {
198 pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<Bound<'_, PyDate>> {
200 let api = ensure_datetime_api(py)?;
201 unsafe {
202 (api.Date_FromDate)(year, c_int::from(month), c_int::from(day), api.DateType)
203 .assume_owned_or_err(py)
204 .downcast_into_unchecked()
205 }
206 }
207
208 pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<Bound<'_, PyDate>> {
212 let time_tuple = PyTuple::new(py, [timestamp])?;
213
214 let _api = ensure_datetime_api(py)?;
216
217 unsafe {
218 PyDate_FromTimestamp(time_tuple.as_ptr())
219 .assume_owned_or_err(py)
220 .downcast_into_unchecked()
221 }
222 }
223}
224
225impl PyDateAccess for Bound<'_, PyDate> {
226 fn get_year(&self) -> i32 {
227 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
228 }
229
230 fn get_month(&self) -> u8 {
231 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
232 }
233
234 fn get_day(&self) -> u8 {
235 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
236 }
237}
238
239#[repr(transparent)]
244pub struct PyDateTime(PyAny);
245pyobject_native_type!(
246 PyDateTime,
247 crate::ffi::PyDateTime_DateTime,
248 |py| expect_datetime_api(py).DateTimeType,
249 #module=Some("datetime"),
250 #checkfunction=PyDateTime_Check
251);
252pyobject_subclassable_native_type!(PyDateTime, crate::ffi::PyDateTime_DateTime);
253
254impl PyDateTime {
255 #[allow(clippy::too_many_arguments)]
257 pub fn new<'py>(
258 py: Python<'py>,
259 year: i32,
260 month: u8,
261 day: u8,
262 hour: u8,
263 minute: u8,
264 second: u8,
265 microsecond: u32,
266 tzinfo: Option<&Bound<'py, PyTzInfo>>,
267 ) -> PyResult<Bound<'py, PyDateTime>> {
268 let api = ensure_datetime_api(py)?;
269 unsafe {
270 (api.DateTime_FromDateAndTime)(
271 year,
272 c_int::from(month),
273 c_int::from(day),
274 c_int::from(hour),
275 c_int::from(minute),
276 c_int::from(second),
277 microsecond as c_int,
278 opt_to_pyobj(tzinfo),
279 api.DateTimeType,
280 )
281 .assume_owned_or_err(py)
282 .downcast_into_unchecked()
283 }
284 }
285
286 #[allow(clippy::too_many_arguments)]
294 pub fn new_with_fold<'py>(
295 py: Python<'py>,
296 year: i32,
297 month: u8,
298 day: u8,
299 hour: u8,
300 minute: u8,
301 second: u8,
302 microsecond: u32,
303 tzinfo: Option<&Bound<'py, PyTzInfo>>,
304 fold: bool,
305 ) -> PyResult<Bound<'py, PyDateTime>> {
306 let api = ensure_datetime_api(py)?;
307 unsafe {
308 (api.DateTime_FromDateAndTimeAndFold)(
309 year,
310 c_int::from(month),
311 c_int::from(day),
312 c_int::from(hour),
313 c_int::from(minute),
314 c_int::from(second),
315 microsecond as c_int,
316 opt_to_pyobj(tzinfo),
317 c_int::from(fold),
318 api.DateTimeType,
319 )
320 .assume_owned_or_err(py)
321 .downcast_into_unchecked()
322 }
323 }
324
325 pub fn from_timestamp<'py>(
329 py: Python<'py>,
330 timestamp: f64,
331 tzinfo: Option<&Bound<'py, PyTzInfo>>,
332 ) -> PyResult<Bound<'py, PyDateTime>> {
333 let args = (timestamp, tzinfo).into_pyobject(py)?;
334
335 let _api = ensure_datetime_api(py)?;
337
338 unsafe {
339 PyDateTime_FromTimestamp(args.as_ptr())
340 .assume_owned_or_err(py)
341 .downcast_into_unchecked()
342 }
343 }
344}
345
346impl PyDateAccess for Bound<'_, PyDateTime> {
347 fn get_year(&self) -> i32 {
348 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
349 }
350
351 fn get_month(&self) -> u8 {
352 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
353 }
354
355 fn get_day(&self) -> u8 {
356 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
357 }
358}
359
360impl PyTimeAccess for Bound<'_, PyDateTime> {
361 fn get_hour(&self) -> u8 {
362 unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u8 }
363 }
364
365 fn get_minute(&self) -> u8 {
366 unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u8 }
367 }
368
369 fn get_second(&self) -> u8 {
370 unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u8 }
371 }
372
373 fn get_microsecond(&self) -> u32 {
374 unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 }
375 }
376
377 fn get_fold(&self) -> bool {
378 unsafe { PyDateTime_DATE_GET_FOLD(self.as_ptr()) > 0 }
379 }
380}
381
382impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyDateTime> {
383 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
384 let ptr = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
385 #[cfg(not(GraalPy))]
386 unsafe {
387 if (*ptr).hastzinfo != 0 {
388 Some(
389 (*ptr)
390 .tzinfo
391 .assume_borrowed(self.py())
392 .to_owned()
393 .downcast_into_unchecked(),
394 )
395 } else {
396 None
397 }
398 }
399
400 #[cfg(GraalPy)]
401 unsafe {
402 let res = PyDateTime_DATE_GET_TZINFO(ptr as *mut ffi::PyObject);
403 if Py_IsNone(res) == 1 {
404 None
405 } else {
406 Some(
407 res.assume_borrowed(self.py())
408 .to_owned()
409 .downcast_into_unchecked(),
410 )
411 }
412 }
413 }
414}
415
416#[repr(transparent)]
421pub struct PyTime(PyAny);
422pyobject_native_type!(
423 PyTime,
424 crate::ffi::PyDateTime_Time,
425 |py| expect_datetime_api(py).TimeType,
426 #module=Some("datetime"),
427 #checkfunction=PyTime_Check
428);
429pyobject_subclassable_native_type!(PyTime, crate::ffi::PyDateTime_Time);
430
431impl PyTime {
432 pub fn new<'py>(
434 py: Python<'py>,
435 hour: u8,
436 minute: u8,
437 second: u8,
438 microsecond: u32,
439 tzinfo: Option<&Bound<'py, PyTzInfo>>,
440 ) -> PyResult<Bound<'py, PyTime>> {
441 let api = ensure_datetime_api(py)?;
442 unsafe {
443 (api.Time_FromTime)(
444 c_int::from(hour),
445 c_int::from(minute),
446 c_int::from(second),
447 microsecond as c_int,
448 opt_to_pyobj(tzinfo),
449 api.TimeType,
450 )
451 .assume_owned_or_err(py)
452 .downcast_into_unchecked()
453 }
454 }
455
456 pub fn new_with_fold<'py>(
458 py: Python<'py>,
459 hour: u8,
460 minute: u8,
461 second: u8,
462 microsecond: u32,
463 tzinfo: Option<&Bound<'py, PyTzInfo>>,
464 fold: bool,
465 ) -> PyResult<Bound<'py, PyTime>> {
466 let api = ensure_datetime_api(py)?;
467 unsafe {
468 (api.Time_FromTimeAndFold)(
469 c_int::from(hour),
470 c_int::from(minute),
471 c_int::from(second),
472 microsecond as c_int,
473 opt_to_pyobj(tzinfo),
474 fold as c_int,
475 api.TimeType,
476 )
477 .assume_owned_or_err(py)
478 .downcast_into_unchecked()
479 }
480 }
481}
482
483impl PyTimeAccess for Bound<'_, PyTime> {
484 fn get_hour(&self) -> u8 {
485 unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 }
486 }
487
488 fn get_minute(&self) -> u8 {
489 unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 }
490 }
491
492 fn get_second(&self) -> u8 {
493 unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 }
494 }
495
496 fn get_microsecond(&self) -> u32 {
497 unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 }
498 }
499
500 fn get_fold(&self) -> bool {
501 unsafe { PyDateTime_TIME_GET_FOLD(self.as_ptr()) != 0 }
502 }
503}
504
505impl<'py> PyTzInfoAccess<'py> for Bound<'py, PyTime> {
506 fn get_tzinfo(&self) -> Option<Bound<'py, PyTzInfo>> {
507 let ptr = self.as_ptr() as *mut ffi::PyDateTime_Time;
508 #[cfg(not(GraalPy))]
509 unsafe {
510 if (*ptr).hastzinfo != 0 {
511 Some(
512 (*ptr)
513 .tzinfo
514 .assume_borrowed(self.py())
515 .to_owned()
516 .downcast_into_unchecked(),
517 )
518 } else {
519 None
520 }
521 }
522
523 #[cfg(GraalPy)]
524 unsafe {
525 let res = PyDateTime_TIME_GET_TZINFO(ptr as *mut ffi::PyObject);
526 if Py_IsNone(res) == 1 {
527 None
528 } else {
529 Some(
530 res.assume_borrowed(self.py())
531 .to_owned()
532 .downcast_into_unchecked(),
533 )
534 }
535 }
536 }
537}
538
539#[repr(transparent)]
548pub struct PyTzInfo(PyAny);
549pyobject_native_type!(
550 PyTzInfo,
551 crate::ffi::PyObject,
552 |py| expect_datetime_api(py).TZInfoType,
553 #module=Some("datetime"),
554 #checkfunction=PyTZInfo_Check
555);
556pyobject_subclassable_native_type!(PyTzInfo, crate::ffi::PyObject);
557
558pub fn timezone_utc(py: Python<'_>) -> Bound<'_, PyTzInfo> {
560 unsafe {
564 expect_datetime_api(py)
565 .TimeZone_UTC
566 .assume_borrowed(py)
567 .to_owned()
568 .downcast_into_unchecked()
569 }
570}
571
572#[cfg(any(feature = "chrono", feature = "jiff-02"))]
576pub(crate) fn timezone_from_offset<'py>(
577 offset: &Bound<'py, PyDelta>,
578) -> PyResult<Bound<'py, PyTzInfo>> {
579 let py = offset.py();
580 let api = ensure_datetime_api(py)?;
581 unsafe {
582 (api.TimeZone_FromTimeZone)(offset.as_ptr(), ptr::null_mut())
583 .assume_owned_or_err(py)
584 .downcast_into_unchecked()
585 }
586}
587
588#[repr(transparent)]
593pub struct PyDelta(PyAny);
594pyobject_native_type!(
595 PyDelta,
596 crate::ffi::PyDateTime_Delta,
597 |py| expect_datetime_api(py).DeltaType,
598 #module=Some("datetime"),
599 #checkfunction=PyDelta_Check
600);
601pyobject_subclassable_native_type!(PyDelta, crate::ffi::PyDateTime_Delta);
602
603impl PyDelta {
604 pub fn new(
606 py: Python<'_>,
607 days: i32,
608 seconds: i32,
609 microseconds: i32,
610 normalize: bool,
611 ) -> PyResult<Bound<'_, PyDelta>> {
612 let api = ensure_datetime_api(py)?;
613 unsafe {
614 (api.Delta_FromDelta)(
615 days as c_int,
616 seconds as c_int,
617 microseconds as c_int,
618 normalize as c_int,
619 api.DeltaType,
620 )
621 .assume_owned_or_err(py)
622 .downcast_into_unchecked()
623 }
624 }
625}
626
627impl PyDeltaAccess for Bound<'_, PyDelta> {
628 fn get_days(&self) -> i32 {
629 unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
630 }
631
632 fn get_seconds(&self) -> i32 {
633 unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
634 }
635
636 fn get_microseconds(&self) -> i32 {
637 unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
638 }
639}
640
641fn opt_to_pyobj(opt: Option<&Bound<'_, PyTzInfo>>) -> *mut ffi::PyObject {
644 match opt {
645 Some(tzi) => tzi.as_ptr(),
646 None => unsafe { ffi::Py_None() },
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653 #[cfg(feature = "macros")]
654 use crate::py_run;
655
656 #[test]
657 #[cfg(feature = "macros")]
658 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_datetime_fromtimestamp() {
660 Python::with_gil(|py| {
661 let dt = PyDateTime::from_timestamp(py, 100.0, None).unwrap();
662 py_run!(
663 py,
664 dt,
665 "import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
666 );
667
668 let dt = PyDateTime::from_timestamp(py, 100.0, Some(&timezone_utc(py))).unwrap();
669 py_run!(
670 py,
671 dt,
672 "import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
673 );
674 })
675 }
676
677 #[test]
678 #[cfg(feature = "macros")]
679 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_date_fromtimestamp() {
681 Python::with_gil(|py| {
682 let dt = PyDate::from_timestamp(py, 100).unwrap();
683 py_run!(
684 py,
685 dt,
686 "import datetime; assert dt == datetime.date.fromtimestamp(100)"
687 );
688 })
689 }
690
691 #[test]
692 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_new_with_fold() {
694 Python::with_gil(|py| {
695 let a = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
696 let b = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
697
698 assert!(!a.unwrap().get_fold());
699 assert!(b.unwrap().get_fold());
700 });
701 }
702
703 #[test]
704 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_get_tzinfo() {
706 crate::Python::with_gil(|py| {
707 let utc = timezone_utc(py);
708
709 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(&utc)).unwrap();
710
711 assert!(dt.get_tzinfo().unwrap().eq(&utc).unwrap());
712
713 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
714
715 assert!(dt.get_tzinfo().is_none());
716
717 let t = PyTime::new(py, 0, 0, 0, 0, Some(&utc)).unwrap();
718
719 assert!(t.get_tzinfo().unwrap().eq(utc).unwrap());
720
721 let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
722
723 assert!(t.get_tzinfo().is_none());
724 });
725 }
726
727 #[test]
728 #[cfg(all(feature = "macros", feature = "chrono"))]
729 #[cfg_attr(target_arch = "wasm32", ignore)] fn test_timezone_from_offset() {
731 use crate::types::PyNone;
732
733 Python::with_gil(|py| {
734 assert!(
735 timezone_from_offset(&PyDelta::new(py, 0, -3600, 0, true).unwrap())
736 .unwrap()
737 .call_method1("utcoffset", (PyNone::get(py),))
738 .unwrap()
739 .downcast_into::<PyDelta>()
740 .unwrap()
741 .eq(PyDelta::new(py, 0, -3600, 0, true).unwrap())
742 .unwrap()
743 );
744
745 assert!(
746 timezone_from_offset(&PyDelta::new(py, 0, 3600, 0, true).unwrap())
747 .unwrap()
748 .call_method1("utcoffset", (PyNone::get(py),))
749 .unwrap()
750 .downcast_into::<PyDelta>()
751 .unwrap()
752 .eq(PyDelta::new(py, 0, 3600, 0, true).unwrap())
753 .unwrap()
754 );
755
756 timezone_from_offset(&PyDelta::new(py, 1, 0, 0, true).unwrap()).unwrap_err();
757 })
758 }
759}