1//! Safe Rust wrappers for types defined in the Python `datetime` library
2//!
3//! For more details about these types, see the [Python
4//! documentation](https://docs.python.org/3/library/datetime.html)
5
6use 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};
14use crate::ffi::{
15 PyDateTime_DELTA_GET_DAYS, PyDateTime_DELTA_GET_MICROSECONDS, PyDateTime_DELTA_GET_SECONDS,
16};
17use crate::ffi::{PyDateTime_GET_DAY, PyDateTime_GET_MONTH, PyDateTime_GET_YEAR};
18use crate::ffi::{
19 PyDateTime_TIME_GET_FOLD, PyDateTime_TIME_GET_HOUR, PyDateTime_TIME_GET_MICROSECOND,
20 PyDateTime_TIME_GET_MINUTE, PyDateTime_TIME_GET_SECOND,
21};
22use crate::instance::PyNativeType;
23use crate::types::PyTuple;
24use crate::{IntoPy, Py, PyAny, Python};
25use std::os::raw::c_int;
26
27fn ensure_datetime_api(_py: Python<'_>) -> &'static PyDateTime_CAPI {
28 unsafe {
29 if pyo3_ffi::PyDateTimeAPI().is_null() {
30 PyDateTime_IMPORT()
31 }
32
33 &*pyo3_ffi::PyDateTimeAPI()
34 }
35}
36
37// Type Check macros
38//
39// These are bindings around the C API typecheck macros, all of them return
40// `1` if True and `0` if False. In all type check macros, the argument (`op`)
41// must not be `NULL`. The implementations here all call ensure_datetime_api
42// to ensure that the PyDateTimeAPI is initialized before use
43//
44//
45// # Safety
46//
47// These functions must only be called when the GIL is held!
48
49macro_rules! ffi_fun_with_autoinit {
50 ($(#[$outer:meta] unsafe fn $name: ident($arg: ident: *mut PyObject) -> $ret: ty;)*) => {
51 $(
52 #[$outer]
53 #[allow(non_snake_case)]
54 /// # Safety
55 ///
56 /// Must only be called while the GIL is held
57 unsafe fn $name($arg: *mut crate::ffi::PyObject) -> $ret {
58
59 let _ = ensure_datetime_api(Python::assume_gil_acquired());
60 crate::ffi::$name($arg)
61 }
62 )*
63
64
65 };
66}
67
68ffi_fun_with_autoinit! {
69 /// Check if `op` is a `PyDateTimeAPI.DateType` or subtype.
70 unsafe fn PyDate_Check(op: *mut PyObject) -> c_int;
71
72 /// Check if `op` is a `PyDateTimeAPI.DateTimeType` or subtype.
73 unsafe fn PyDateTime_Check(op: *mut PyObject) -> c_int;
74
75 /// Check if `op` is a `PyDateTimeAPI.TimeType` or subtype.
76 unsafe fn PyTime_Check(op: *mut PyObject) -> c_int;
77
78 /// Check if `op` is a `PyDateTimeAPI.DetaType` or subtype.
79 unsafe fn PyDelta_Check(op: *mut PyObject) -> c_int;
80
81 /// Check if `op` is a `PyDateTimeAPI.TZInfoType` or subtype.
82 unsafe fn PyTZInfo_Check(op: *mut PyObject) -> c_int;
83}
84
85// Access traits
86
87/// Trait for accessing the date components of a struct containing a date.
88pub trait PyDateAccess {
89 /// Returns the year, as a positive int.
90 ///
91 /// Implementations should conform to the upstream documentation:
92 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_GET_YEAR>
93 fn get_year(&self) -> i32;
94 /// Returns the month, as an int from 1 through 12.
95 ///
96 /// Implementations should conform to the upstream documentation:
97 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_GET_MONTH>
98 fn get_month(&self) -> u8;
99 /// Returns the day, as an int from 1 through 31.
100 ///
101 /// Implementations should conform to the upstream documentation:
102 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_GET_DAY>
103 fn get_day(&self) -> u8;
104}
105
106/// Trait for accessing the components of a struct containing a timedelta.
107///
108/// Note: These access the individual components of a (day, second,
109/// microsecond) representation of the delta, they are *not* intended as
110/// aliases for calculating the total duration in each of these units.
111pub trait PyDeltaAccess {
112 /// Returns the number of days, as an int from -999999999 to 999999999.
113 ///
114 /// Implementations should conform to the upstream documentation:
115 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DELTA_GET_DAYS>
116 fn get_days(&self) -> i32;
117 /// Returns the number of seconds, as an int from 0 through 86399.
118 ///
119 /// Implementations should conform to the upstream documentation:
120 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DELTA_GET_DAYS>
121 fn get_seconds(&self) -> i32;
122 /// Returns the number of microseconds, as an int from 0 through 999999.
123 ///
124 /// Implementations should conform to the upstream documentation:
125 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DELTA_GET_DAYS>
126 fn get_microseconds(&self) -> i32;
127}
128
129/// Trait for accessing the time components of a struct containing a time.
130pub trait PyTimeAccess {
131 /// Returns the hour, as an int from 0 through 23.
132 ///
133 /// Implementations should conform to the upstream documentation:
134 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_HOUR>
135 fn get_hour(&self) -> u8;
136 /// Returns the minute, as an int from 0 through 59.
137 ///
138 /// Implementations should conform to the upstream documentation:
139 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_MINUTE>
140 fn get_minute(&self) -> u8;
141 /// Returns the second, as an int from 0 through 59.
142 ///
143 /// Implementations should conform to the upstream documentation:
144 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_SECOND>
145 fn get_second(&self) -> u8;
146 /// Returns the microsecond, as an int from 0 through 999999.
147 ///
148 /// Implementations should conform to the upstream documentation:
149 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_MICROSECOND>
150 fn get_microsecond(&self) -> u32;
151 /// Returns whether this date is the later of two moments with the
152 /// same representation, during a repeated interval.
153 ///
154 /// This typically occurs at the end of daylight savings time. Only valid if the
155 /// represented time is ambiguous.
156 /// See [PEP 495](https://www.python.org/dev/peps/pep-0495/) for more detail.
157 fn get_fold(&self) -> bool;
158}
159
160/// Trait for accessing the components of a struct containing a tzinfo.
161pub trait PyTzInfoAccess {
162 /// Returns the tzinfo (which may be None).
163 ///
164 /// Implementations should conform to the upstream documentation:
165 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_DATE_GET_TZINFO>
166 /// <https://docs.python.org/3/c-api/datetime.html#c.PyDateTime_TIME_GET_TZINFO>
167 fn get_tzinfo(&self) -> Option<&PyTzInfo>;
168}
169
170/// Bindings around `datetime.date`
171#[repr(transparent)]
172pub struct PyDate(PyAny);
173pyobject_native_type!(
174 PyDate,
175 crate::ffi::PyDateTime_Date,
176 |py| ensure_datetime_api(py).DateType,
177 #module=Some("datetime"),
178 #checkfunction=PyDate_Check
179);
180
181impl PyDate {
182 /// Creates a new `datetime.date`.
183 pub fn new(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate> {
184 unsafe {
185 let ptr = (ensure_datetime_api(py).Date_FromDate)(
186 year,
187 c_int::from(month),
188 c_int::from(day),
189 ensure_datetime_api(py).DateType,
190 );
191 py.from_owned_ptr_or_err(ptr)
192 }
193 }
194
195 /// Construct a `datetime.date` from a POSIX timestamp
196 ///
197 /// This is equivalent to `datetime.date.fromtimestamp`
198 pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> {
199 let time_tuple = PyTuple::new(py, [timestamp]);
200
201 // safety ensure that the API is loaded
202 let _api = ensure_datetime_api(py);
203
204 unsafe {
205 let ptr = PyDate_FromTimestamp(time_tuple.as_ptr());
206 py.from_owned_ptr_or_err(ptr)
207 }
208 }
209}
210
211impl PyDateAccess for PyDate {
212 fn get_year(&self) -> i32 {
213 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
214 }
215
216 fn get_month(&self) -> u8 {
217 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
218 }
219
220 fn get_day(&self) -> u8 {
221 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
222 }
223}
224
225/// Bindings for `datetime.datetime`
226#[repr(transparent)]
227pub struct PyDateTime(PyAny);
228pyobject_native_type!(
229 PyDateTime,
230 crate::ffi::PyDateTime_DateTime,
231 |py| ensure_datetime_api(py).DateTimeType,
232 #module=Some("datetime"),
233 #checkfunction=PyDateTime_Check
234);
235
236impl PyDateTime {
237 /// Creates a new `datetime.datetime` object.
238 #[allow(clippy::too_many_arguments)]
239 pub fn new<'p>(
240 py: Python<'p>,
241 year: i32,
242 month: u8,
243 day: u8,
244 hour: u8,
245 minute: u8,
246 second: u8,
247 microsecond: u32,
248 tzinfo: Option<&PyTzInfo>,
249 ) -> PyResult<&'p PyDateTime> {
250 let api = ensure_datetime_api(py);
251 unsafe {
252 let ptr = (api.DateTime_FromDateAndTime)(
253 year,
254 c_int::from(month),
255 c_int::from(day),
256 c_int::from(hour),
257 c_int::from(minute),
258 c_int::from(second),
259 microsecond as c_int,
260 opt_to_pyobj(tzinfo),
261 api.DateTimeType,
262 );
263 py.from_owned_ptr_or_err(ptr)
264 }
265 }
266
267 /// Alternate constructor that takes a `fold` parameter. A `true` value for this parameter
268 /// signifies this this datetime is the later of two moments with the same representation,
269 /// during a repeated interval.
270 ///
271 /// This typically occurs at the end of daylight savings time. Only valid if the
272 /// represented time is ambiguous.
273 /// See [PEP 495](https://www.python.org/dev/peps/pep-0495/) for more detail.
274 #[allow(clippy::too_many_arguments)]
275 pub fn new_with_fold<'p>(
276 py: Python<'p>,
277 year: i32,
278 month: u8,
279 day: u8,
280 hour: u8,
281 minute: u8,
282 second: u8,
283 microsecond: u32,
284 tzinfo: Option<&PyTzInfo>,
285 fold: bool,
286 ) -> PyResult<&'p PyDateTime> {
287 let api = ensure_datetime_api(py);
288 unsafe {
289 let ptr = (api.DateTime_FromDateAndTimeAndFold)(
290 year,
291 c_int::from(month),
292 c_int::from(day),
293 c_int::from(hour),
294 c_int::from(minute),
295 c_int::from(second),
296 microsecond as c_int,
297 opt_to_pyobj(tzinfo),
298 c_int::from(fold),
299 api.DateTimeType,
300 );
301 py.from_owned_ptr_or_err(ptr)
302 }
303 }
304
305 /// Construct a `datetime` object from a POSIX timestamp
306 ///
307 /// This is equivalent to `datetime.datetime.fromtimestamp`
308 pub fn from_timestamp<'p>(
309 py: Python<'p>,
310 timestamp: f64,
311 tzinfo: Option<&PyTzInfo>,
312 ) -> PyResult<&'p PyDateTime> {
313 let args: Py<PyTuple> = (timestamp, tzinfo).into_py(py);
314
315 // safety ensure API is loaded
316 let _api = ensure_datetime_api(py);
317
318 unsafe {
319 let ptr = PyDateTime_FromTimestamp(args.as_ptr());
320 py.from_owned_ptr_or_err(ptr)
321 }
322 }
323}
324
325impl PyDateAccess for PyDateTime {
326 fn get_year(&self) -> i32 {
327 unsafe { PyDateTime_GET_YEAR(self.as_ptr()) }
328 }
329
330 fn get_month(&self) -> u8 {
331 unsafe { PyDateTime_GET_MONTH(self.as_ptr()) as u8 }
332 }
333
334 fn get_day(&self) -> u8 {
335 unsafe { PyDateTime_GET_DAY(self.as_ptr()) as u8 }
336 }
337}
338
339impl PyTimeAccess for PyDateTime {
340 fn get_hour(&self) -> u8 {
341 unsafe { PyDateTime_DATE_GET_HOUR(self.as_ptr()) as u8 }
342 }
343
344 fn get_minute(&self) -> u8 {
345 unsafe { PyDateTime_DATE_GET_MINUTE(self.as_ptr()) as u8 }
346 }
347
348 fn get_second(&self) -> u8 {
349 unsafe { PyDateTime_DATE_GET_SECOND(self.as_ptr()) as u8 }
350 }
351
352 fn get_microsecond(&self) -> u32 {
353 unsafe { PyDateTime_DATE_GET_MICROSECOND(self.as_ptr()) as u32 }
354 }
355
356 fn get_fold(&self) -> bool {
357 unsafe { PyDateTime_DATE_GET_FOLD(self.as_ptr()) > 0 }
358 }
359}
360
361impl PyTzInfoAccess for PyDateTime {
362 fn get_tzinfo(&self) -> Option<&PyTzInfo> {
363 let ptr: *mut PyDateTime_DateTime = self.as_ptr() as *mut ffi::PyDateTime_DateTime;
364 unsafe {
365 if (*ptr).hastzinfo != 0 {
366 Some(self.py().from_borrowed_ptr((*ptr).tzinfo))
367 } else {
368 None
369 }
370 }
371 }
372}
373
374/// Bindings for `datetime.time`
375#[repr(transparent)]
376pub struct PyTime(PyAny);
377pyobject_native_type!(
378 PyTime,
379 crate::ffi::PyDateTime_Time,
380 |py| ensure_datetime_api(py).TimeType,
381 #module=Some("datetime"),
382 #checkfunction=PyTime_Check
383);
384
385impl PyTime {
386 /// Creates a new `datetime.time` object.
387 pub fn new<'p>(
388 py: Python<'p>,
389 hour: u8,
390 minute: u8,
391 second: u8,
392 microsecond: u32,
393 tzinfo: Option<&PyTzInfo>,
394 ) -> PyResult<&'p PyTime> {
395 let api = ensure_datetime_api(py);
396 unsafe {
397 let ptr = (api.Time_FromTime)(
398 c_int::from(hour),
399 c_int::from(minute),
400 c_int::from(second),
401 microsecond as c_int,
402 opt_to_pyobj(tzinfo),
403 api.TimeType,
404 );
405 py.from_owned_ptr_or_err(ptr)
406 }
407 }
408
409 /// Alternate constructor that takes a `fold` argument. See [`PyDateTime::new_with_fold`].
410 pub fn new_with_fold<'p>(
411 py: Python<'p>,
412 hour: u8,
413 minute: u8,
414 second: u8,
415 microsecond: u32,
416 tzinfo: Option<&PyTzInfo>,
417 fold: bool,
418 ) -> PyResult<&'p PyTime> {
419 let api = ensure_datetime_api(py);
420 unsafe {
421 let ptr = (api.Time_FromTimeAndFold)(
422 c_int::from(hour),
423 c_int::from(minute),
424 c_int::from(second),
425 microsecond as c_int,
426 opt_to_pyobj(tzinfo),
427 fold as c_int,
428 api.TimeType,
429 );
430 py.from_owned_ptr_or_err(ptr)
431 }
432 }
433}
434
435impl PyTimeAccess for PyTime {
436 fn get_hour(&self) -> u8 {
437 unsafe { PyDateTime_TIME_GET_HOUR(self.as_ptr()) as u8 }
438 }
439
440 fn get_minute(&self) -> u8 {
441 unsafe { PyDateTime_TIME_GET_MINUTE(self.as_ptr()) as u8 }
442 }
443
444 fn get_second(&self) -> u8 {
445 unsafe { PyDateTime_TIME_GET_SECOND(self.as_ptr()) as u8 }
446 }
447
448 fn get_microsecond(&self) -> u32 {
449 unsafe { PyDateTime_TIME_GET_MICROSECOND(self.as_ptr()) as u32 }
450 }
451
452 fn get_fold(&self) -> bool {
453 unsafe { PyDateTime_TIME_GET_FOLD(self.as_ptr()) != 0 }
454 }
455}
456
457impl PyTzInfoAccess for PyTime {
458 fn get_tzinfo(&self) -> Option<&PyTzInfo> {
459 let ptr: *mut PyDateTime_Time = self.as_ptr() as *mut ffi::PyDateTime_Time;
460 unsafe {
461 if (*ptr).hastzinfo != 0 {
462 Some(self.py().from_borrowed_ptr((*ptr).tzinfo))
463 } else {
464 None
465 }
466 }
467 }
468}
469
470/// Bindings for `datetime.tzinfo`.
471///
472/// This is an abstract base class and cannot be constructed directly.
473/// For concrete time zone implementations, see [`timezone_utc`] and
474/// the [`zoneinfo` module](https://docs.python.org/3/library/zoneinfo.html).
475#[repr(transparent)]
476pub struct PyTzInfo(PyAny);
477pyobject_native_type!(
478 PyTzInfo,
479 crate::ffi::PyObject,
480 |py| ensure_datetime_api(py).TZInfoType,
481 #module=Some("datetime"),
482 #checkfunction=PyTZInfo_Check
483);
484
485/// Equivalent to `datetime.timezone.utc`
486pub fn timezone_utc(py: Python<'_>) -> &PyTzInfo {
487 unsafe { &*(ensure_datetime_api(py).TimeZone_UTC as *const PyTzInfo) }
488}
489
490/// Bindings for `datetime.timedelta`
491#[repr(transparent)]
492pub struct PyDelta(PyAny);
493pyobject_native_type!(
494 PyDelta,
495 crate::ffi::PyDateTime_Delta,
496 |py| ensure_datetime_api(py).DeltaType,
497 #module=Some("datetime"),
498 #checkfunction=PyDelta_Check
499);
500
501impl PyDelta {
502 /// Creates a new `timedelta`.
503 pub fn new(
504 py: Python<'_>,
505 days: i32,
506 seconds: i32,
507 microseconds: i32,
508 normalize: bool,
509 ) -> PyResult<&PyDelta> {
510 let api: &PyDateTime_CAPI = ensure_datetime_api(py);
511 unsafe {
512 let ptr: *mut PyObject = (api.Delta_FromDelta)(
513 days as c_int,
514 seconds as c_int,
515 microseconds as c_int,
516 normalize as c_int,
517 api.DeltaType,
518 );
519 py.from_owned_ptr_or_err(ptr)
520 }
521 }
522}
523
524impl PyDeltaAccess for PyDelta {
525 fn get_days(&self) -> i32 {
526 unsafe { PyDateTime_DELTA_GET_DAYS(self.as_ptr()) }
527 }
528
529 fn get_seconds(&self) -> i32 {
530 unsafe { PyDateTime_DELTA_GET_SECONDS(self.as_ptr()) }
531 }
532
533 fn get_microseconds(&self) -> i32 {
534 unsafe { PyDateTime_DELTA_GET_MICROSECONDS(self.as_ptr()) }
535 }
536}
537
538// Utility function which returns a borrowed reference to either
539// the underlying tzinfo or None.
540fn opt_to_pyobj(opt: Option<&PyTzInfo>) -> *mut ffi::PyObject {
541 match opt {
542 Some(tzi: &PyTzInfo) => tzi.as_ptr(),
543 None => unsafe { ffi::Py_None() },
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550 #[cfg(feature = "macros")]
551 use crate::py_run;
552
553 #[test]
554 #[cfg(feature = "macros")]
555 #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
556 fn test_datetime_fromtimestamp() {
557 Python::with_gil(|py| {
558 let dt = PyDateTime::from_timestamp(py, 100.0, None).unwrap();
559 py_run!(
560 py,
561 dt,
562 "import datetime; assert dt == datetime.datetime.fromtimestamp(100)"
563 );
564
565 let dt = PyDateTime::from_timestamp(py, 100.0, Some(timezone_utc(py))).unwrap();
566 py_run!(
567 py,
568 dt,
569 "import datetime; assert dt == datetime.datetime.fromtimestamp(100, datetime.timezone.utc)"
570 );
571 })
572 }
573
574 #[test]
575 #[cfg(feature = "macros")]
576 #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
577 fn test_date_fromtimestamp() {
578 Python::with_gil(|py| {
579 let dt = PyDate::from_timestamp(py, 100).unwrap();
580 py_run!(
581 py,
582 dt,
583 "import datetime; assert dt == datetime.date.fromtimestamp(100)"
584 );
585 })
586 }
587
588 #[test]
589 #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
590 fn test_new_with_fold() {
591 Python::with_gil(|py| {
592 let a = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, false);
593 let b = PyDateTime::new_with_fold(py, 2021, 1, 23, 20, 32, 40, 341516, None, true);
594
595 assert!(!a.unwrap().get_fold());
596 assert!(b.unwrap().get_fold());
597 });
598 }
599
600 #[test]
601 #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons
602 fn test_get_tzinfo() {
603 crate::Python::with_gil(|py| {
604 let utc = timezone_utc(py);
605
606 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, Some(utc)).unwrap();
607
608 assert!(dt.get_tzinfo().unwrap().eq(utc).unwrap());
609
610 let dt = PyDateTime::new(py, 2018, 1, 1, 0, 0, 0, 0, None).unwrap();
611
612 assert!(dt.get_tzinfo().is_none());
613
614 let t = PyTime::new(py, 0, 0, 0, 0, Some(utc)).unwrap();
615
616 assert!(t.get_tzinfo().unwrap().eq(utc).unwrap());
617
618 let t = PyTime::new(py, 0, 0, 0, 0, None).unwrap();
619
620 assert!(t.get_tzinfo().is_none());
621 });
622 }
623}
624