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