1use crate::conversion::IntoPyObject;
2use crate::exceptions::{PyOverflowError, PyValueError};
3use crate::sync::GILOnceCell;
4use crate::types::any::PyAnyMethods;
5#[cfg(Py_LIMITED_API)]
6use crate::types::PyType;
7#[cfg(not(Py_LIMITED_API))]
8use crate::types::{timezone_utc, PyDateTime, PyDelta, PyDeltaAccess};
9#[cfg(Py_LIMITED_API)]
10use crate::Py;
11use crate::{intern, Bound, FromPyObject, PyAny, PyErr, PyObject, PyResult, Python};
12#[allow(deprecated)]
13use crate::{IntoPy, ToPyObject};
14use std::time::{Duration, SystemTime, UNIX_EPOCH};
15
16const SECONDS_PER_DAY: u64 = 24 * 60 * 60;
17
18impl FromPyObject<'_> for Duration {
19 fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
20 #[cfg(not(Py_LIMITED_API))]
21 let (days, seconds, microseconds) = {
22 let delta = obj.downcast::<PyDelta>()?;
23 (
24 delta.get_days(),
25 delta.get_seconds(),
26 delta.get_microseconds(),
27 )
28 };
29 #[cfg(Py_LIMITED_API)]
30 let (days, seconds, microseconds): (i32, i32, i32) = {
31 (
32 obj.getattr(intern!(obj.py(), "days"))?.extract()?,
33 obj.getattr(intern!(obj.py(), "seconds"))?.extract()?,
34 obj.getattr(intern!(obj.py(), "microseconds"))?.extract()?,
35 )
36 };
37
38 let days = u64::try_from(days).map_err(|_| {
40 PyValueError::new_err(
41 "It is not possible to convert a negative timedelta to a Rust Duration",
42 )
43 })?;
44 let seconds = u64::try_from(seconds).unwrap(); let microseconds = u32::try_from(microseconds).unwrap(); let total_seconds = days * SECONDS_PER_DAY + seconds; let nanoseconds = microseconds.checked_mul(1_000).unwrap(); Ok(Duration::new(total_seconds, nanoseconds))
52 }
53}
54
55#[allow(deprecated)]
56impl ToPyObject for Duration {
57 #[inline]
58 fn to_object(&self, py: Python<'_>) -> PyObject {
59 self.into_pyobject(py).unwrap().into_any().unbind()
60 }
61}
62
63#[allow(deprecated)]
64impl IntoPy<PyObject> for Duration {
65 #[inline]
66 fn into_py(self, py: Python<'_>) -> PyObject {
67 self.into_pyobject(py).unwrap().into_any().unbind()
68 }
69}
70
71impl<'py> IntoPyObject<'py> for Duration {
72 #[cfg(not(Py_LIMITED_API))]
73 type Target = PyDelta;
74 #[cfg(Py_LIMITED_API)]
75 type Target = PyAny;
76 type Output = Bound<'py, Self::Target>;
77 type Error = PyErr;
78
79 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
80 let days = self.as_secs() / SECONDS_PER_DAY;
81 let seconds = self.as_secs() % SECONDS_PER_DAY;
82 let microseconds = self.subsec_micros();
83
84 #[cfg(not(Py_LIMITED_API))]
85 {
86 PyDelta::new(
87 py,
88 days.try_into()?,
89 seconds.try_into().unwrap(),
90 microseconds.try_into().unwrap(),
91 false,
92 )
93 }
94 #[cfg(Py_LIMITED_API)]
95 {
96 static TIMEDELTA: GILOnceCell<Py<PyType>> = GILOnceCell::new();
97 TIMEDELTA
98 .import(py, "datetime", "timedelta")?
99 .call1((days, seconds, microseconds))
100 }
101 }
102}
103
104impl<'py> IntoPyObject<'py> for &Duration {
105 #[cfg(not(Py_LIMITED_API))]
106 type Target = PyDelta;
107 #[cfg(Py_LIMITED_API)]
108 type Target = PyAny;
109 type Output = Bound<'py, Self::Target>;
110 type Error = PyErr;
111
112 #[inline]
113 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
114 (*self).into_pyobject(py)
115 }
116}
117
118impl FromPyObject<'_> for SystemTime {
125 fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
126 let duration_since_unix_epoch: Duration = obj
127 .call_method1(intern!(obj.py(), "__sub__"), (unix_epoch_py(obj.py())?,))?
128 .extract()?;
129 UNIX_EPOCH
130 .checked_add(duration_since_unix_epoch)
131 .ok_or_else(|| {
132 PyOverflowError::new_err("Overflow error when converting the time to Rust")
133 })
134 }
135}
136
137#[allow(deprecated)]
138impl ToPyObject for SystemTime {
139 #[inline]
140 fn to_object(&self, py: Python<'_>) -> PyObject {
141 self.into_pyobject(py).unwrap().into_any().unbind()
142 }
143}
144
145#[allow(deprecated)]
146impl IntoPy<PyObject> for SystemTime {
147 #[inline]
148 fn into_py(self, py: Python<'_>) -> PyObject {
149 self.into_pyobject(py).unwrap().into_any().unbind()
150 }
151}
152
153impl<'py> IntoPyObject<'py> for SystemTime {
154 type Target = PyAny;
155 type Output = Bound<'py, Self::Target>;
156 type Error = PyErr;
157
158 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
159 let duration_since_unix_epoch =
160 self.duration_since(UNIX_EPOCH).unwrap().into_pyobject(py)?;
161 unix_epoch_py(py)?
162 .bind(py)
163 .call_method1(intern!(py, "__add__"), (duration_since_unix_epoch,))
164 }
165}
166
167impl<'py> IntoPyObject<'py> for &SystemTime {
168 type Target = PyAny;
169 type Output = Bound<'py, Self::Target>;
170 type Error = PyErr;
171
172 #[inline]
173 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
174 (*self).into_pyobject(py)
175 }
176}
177
178fn unix_epoch_py(py: Python<'_>) -> PyResult<&PyObject> {
179 static UNIX_EPOCH: GILOnceCell<PyObject> = GILOnceCell::new();
180 UNIX_EPOCH.get_or_try_init(py, || {
181 #[cfg(not(Py_LIMITED_API))]
182 {
183 Ok::<_, PyErr>(
184 PyDateTime::new(py, 1970, 1, 1, 0, 0, 0, 0, Some(&timezone_utc(py)))?.into(),
185 )
186 }
187 #[cfg(Py_LIMITED_API)]
188 {
189 let datetime = py.import("datetime")?;
190 let utc = datetime.getattr("timezone")?.getattr("utc")?;
191 Ok::<_, PyErr>(
192 datetime
193 .getattr("datetime")?
194 .call1((1970, 1, 1, 0, 0, 0, 0, utc))
195 .unwrap()
196 .into(),
197 )
198 }
199 })
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use crate::types::PyDict;
206
207 #[test]
208 fn test_duration_frompyobject() {
209 Python::with_gil(|py| {
210 assert_eq!(
211 new_timedelta(py, 0, 0, 0).extract::<Duration>().unwrap(),
212 Duration::new(0, 0)
213 );
214 assert_eq!(
215 new_timedelta(py, 1, 0, 0).extract::<Duration>().unwrap(),
216 Duration::new(86400, 0)
217 );
218 assert_eq!(
219 new_timedelta(py, 0, 1, 0).extract::<Duration>().unwrap(),
220 Duration::new(1, 0)
221 );
222 assert_eq!(
223 new_timedelta(py, 0, 0, 1).extract::<Duration>().unwrap(),
224 Duration::new(0, 1_000)
225 );
226 assert_eq!(
227 new_timedelta(py, 1, 1, 1).extract::<Duration>().unwrap(),
228 Duration::new(86401, 1_000)
229 );
230 assert_eq!(
231 timedelta_class(py)
232 .getattr("max")
233 .unwrap()
234 .extract::<Duration>()
235 .unwrap(),
236 Duration::new(86399999999999, 999999000)
237 );
238 });
239 }
240
241 #[test]
242 fn test_duration_frompyobject_negative() {
243 Python::with_gil(|py| {
244 assert_eq!(
245 new_timedelta(py, 0, -1, 0)
246 .extract::<Duration>()
247 .unwrap_err()
248 .to_string(),
249 "ValueError: It is not possible to convert a negative timedelta to a Rust Duration"
250 );
251 })
252 }
253
254 #[test]
255 fn test_duration_into_pyobject() {
256 Python::with_gil(|py| {
257 let assert_eq = |l: Bound<'_, PyAny>, r: Bound<'_, PyAny>| {
258 assert!(l.eq(r).unwrap());
259 };
260
261 assert_eq(
262 Duration::new(0, 0).into_pyobject(py).unwrap().into_any(),
263 new_timedelta(py, 0, 0, 0),
264 );
265 assert_eq(
266 Duration::new(86400, 0)
267 .into_pyobject(py)
268 .unwrap()
269 .into_any(),
270 new_timedelta(py, 1, 0, 0),
271 );
272 assert_eq(
273 Duration::new(1, 0).into_pyobject(py).unwrap().into_any(),
274 new_timedelta(py, 0, 1, 0),
275 );
276 assert_eq(
277 Duration::new(0, 1_000)
278 .into_pyobject(py)
279 .unwrap()
280 .into_any(),
281 new_timedelta(py, 0, 0, 1),
282 );
283 assert_eq(
284 Duration::new(0, 1).into_pyobject(py).unwrap().into_any(),
285 new_timedelta(py, 0, 0, 0),
286 );
287 assert_eq(
288 Duration::new(86401, 1_000)
289 .into_pyobject(py)
290 .unwrap()
291 .into_any(),
292 new_timedelta(py, 1, 1, 1),
293 );
294 assert_eq(
295 Duration::new(86399999999999, 999999000)
296 .into_pyobject(py)
297 .unwrap()
298 .into_any(),
299 timedelta_class(py).getattr("max").unwrap(),
300 );
301 });
302 }
303
304 #[test]
305 fn test_duration_into_pyobject_overflow() {
306 Python::with_gil(|py| {
307 assert!(Duration::MAX.into_pyobject(py).is_err());
308 })
309 }
310
311 #[test]
312 fn test_time_frompyobject() {
313 Python::with_gil(|py| {
314 assert_eq!(
315 new_datetime(py, 1970, 1, 1, 0, 0, 0, 0)
316 .extract::<SystemTime>()
317 .unwrap(),
318 UNIX_EPOCH
319 );
320 assert_eq!(
321 new_datetime(py, 2020, 2, 3, 4, 5, 6, 7)
322 .extract::<SystemTime>()
323 .unwrap(),
324 UNIX_EPOCH
325 .checked_add(Duration::new(1580702706, 7000))
326 .unwrap()
327 );
328 assert_eq!(
329 max_datetime(py).extract::<SystemTime>().unwrap(),
330 UNIX_EPOCH
331 .checked_add(Duration::new(253402300799, 999999000))
332 .unwrap()
333 );
334 });
335 }
336
337 #[test]
338 fn test_time_frompyobject_before_epoch() {
339 Python::with_gil(|py| {
340 assert_eq!(
341 new_datetime(py, 1950, 1, 1, 0, 0, 0, 0)
342 .extract::<SystemTime>()
343 .unwrap_err()
344 .to_string(),
345 "ValueError: It is not possible to convert a negative timedelta to a Rust Duration"
346 );
347 })
348 }
349
350 #[test]
351 fn test_time_intopyobject() {
352 Python::with_gil(|py| {
353 let assert_eq = |l: Bound<'_, PyAny>, r: Bound<'_, PyAny>| {
354 assert!(l.eq(r).unwrap());
355 };
356
357 assert_eq(
358 UNIX_EPOCH
359 .checked_add(Duration::new(1580702706, 7123))
360 .unwrap()
361 .into_pyobject(py)
362 .unwrap(),
363 new_datetime(py, 2020, 2, 3, 4, 5, 6, 7),
364 );
365 assert_eq(
366 UNIX_EPOCH
367 .checked_add(Duration::new(253402300799, 999999000))
368 .unwrap()
369 .into_pyobject(py)
370 .unwrap(),
371 max_datetime(py),
372 );
373 });
374 }
375
376 #[allow(clippy::too_many_arguments)]
377 fn new_datetime(
378 py: Python<'_>,
379 year: i32,
380 month: u8,
381 day: u8,
382 hour: u8,
383 minute: u8,
384 second: u8,
385 microsecond: u32,
386 ) -> Bound<'_, PyAny> {
387 datetime_class(py)
388 .call1((
389 year,
390 month,
391 day,
392 hour,
393 minute,
394 second,
395 microsecond,
396 tz_utc(py),
397 ))
398 .unwrap()
399 }
400
401 fn max_datetime(py: Python<'_>) -> Bound<'_, PyAny> {
402 let naive_max = datetime_class(py).getattr("max").unwrap();
403 let kargs = PyDict::new(py);
404 kargs.set_item("tzinfo", tz_utc(py)).unwrap();
405 naive_max.call_method("replace", (), Some(&kargs)).unwrap()
406 }
407
408 #[test]
409 fn test_time_intopyobject_overflow() {
410 let big_system_time = UNIX_EPOCH
411 .checked_add(Duration::new(300000000000, 0))
412 .unwrap();
413 Python::with_gil(|py| {
414 assert!(big_system_time.into_pyobject(py).is_err());
415 })
416 }
417
418 fn tz_utc(py: Python<'_>) -> Bound<'_, PyAny> {
419 py.import("datetime")
420 .unwrap()
421 .getattr("timezone")
422 .unwrap()
423 .getattr("utc")
424 .unwrap()
425 }
426
427 fn new_timedelta(
428 py: Python<'_>,
429 days: i32,
430 seconds: i32,
431 microseconds: i32,
432 ) -> Bound<'_, PyAny> {
433 timedelta_class(py)
434 .call1((days, seconds, microseconds))
435 .unwrap()
436 }
437
438 fn datetime_class(py: Python<'_>) -> Bound<'_, PyAny> {
439 py.import("datetime").unwrap().getattr("datetime").unwrap()
440 }
441
442 fn timedelta_class(py: Python<'_>) -> Bound<'_, PyAny> {
443 py.import("datetime").unwrap().getattr("timedelta").unwrap()
444 }
445}