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::{PyDateTime, PyDelta, PyTzInfo};
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 let utc = PyTzInfo::utc(py)?;
132 Ok::<_, PyErr>(PyDateTime::new(py, 1970, 1, 1, 0, 0, 0, 0, Some(&utc))?.into())
133 })?
134 .bind_borrowed(py))
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use crate::types::PyDict;
141
142 #[test]
143 fn test_duration_frompyobject() {
144 Python::with_gil(|py| {
145 assert_eq!(
146 new_timedelta(py, 0, 0, 0).extract::<Duration>().unwrap(),
147 Duration::new(0, 0)
148 );
149 assert_eq!(
150 new_timedelta(py, 1, 0, 0).extract::<Duration>().unwrap(),
151 Duration::new(86400, 0)
152 );
153 assert_eq!(
154 new_timedelta(py, 0, 1, 0).extract::<Duration>().unwrap(),
155 Duration::new(1, 0)
156 );
157 assert_eq!(
158 new_timedelta(py, 0, 0, 1).extract::<Duration>().unwrap(),
159 Duration::new(0, 1_000)
160 );
161 assert_eq!(
162 new_timedelta(py, 1, 1, 1).extract::<Duration>().unwrap(),
163 Duration::new(86401, 1_000)
164 );
165 assert_eq!(
166 timedelta_class(py)
167 .getattr("max")
168 .unwrap()
169 .extract::<Duration>()
170 .unwrap(),
171 Duration::new(86399999999999, 999999000)
172 );
173 });
174 }
175
176 #[test]
177 fn test_duration_frompyobject_negative() {
178 Python::with_gil(|py| {
179 assert_eq!(
180 new_timedelta(py, 0, -1, 0)
181 .extract::<Duration>()
182 .unwrap_err()
183 .to_string(),
184 "ValueError: It is not possible to convert a negative timedelta to a Rust Duration"
185 );
186 })
187 }
188
189 #[test]
190 fn test_duration_into_pyobject() {
191 Python::with_gil(|py| {
192 let assert_eq = |l: Bound<'_, PyAny>, r: Bound<'_, PyAny>| {
193 assert!(l.eq(r).unwrap());
194 };
195
196 assert_eq(
197 Duration::new(0, 0).into_pyobject(py).unwrap().into_any(),
198 new_timedelta(py, 0, 0, 0),
199 );
200 assert_eq(
201 Duration::new(86400, 0)
202 .into_pyobject(py)
203 .unwrap()
204 .into_any(),
205 new_timedelta(py, 1, 0, 0),
206 );
207 assert_eq(
208 Duration::new(1, 0).into_pyobject(py).unwrap().into_any(),
209 new_timedelta(py, 0, 1, 0),
210 );
211 assert_eq(
212 Duration::new(0, 1_000)
213 .into_pyobject(py)
214 .unwrap()
215 .into_any(),
216 new_timedelta(py, 0, 0, 1),
217 );
218 assert_eq(
219 Duration::new(0, 1).into_pyobject(py).unwrap().into_any(),
220 new_timedelta(py, 0, 0, 0),
221 );
222 assert_eq(
223 Duration::new(86401, 1_000)
224 .into_pyobject(py)
225 .unwrap()
226 .into_any(),
227 new_timedelta(py, 1, 1, 1),
228 );
229 assert_eq(
230 Duration::new(86399999999999, 999999000)
231 .into_pyobject(py)
232 .unwrap()
233 .into_any(),
234 timedelta_class(py).getattr("max").unwrap(),
235 );
236 });
237 }
238
239 #[test]
240 fn test_duration_into_pyobject_overflow() {
241 Python::with_gil(|py| {
242 assert!(Duration::MAX.into_pyobject(py).is_err());
243 })
244 }
245
246 #[test]
247 fn test_time_frompyobject() {
248 Python::with_gil(|py| {
249 assert_eq!(
250 new_datetime(py, 1970, 1, 1, 0, 0, 0, 0)
251 .extract::<SystemTime>()
252 .unwrap(),
253 UNIX_EPOCH
254 );
255 assert_eq!(
256 new_datetime(py, 2020, 2, 3, 4, 5, 6, 7)
257 .extract::<SystemTime>()
258 .unwrap(),
259 UNIX_EPOCH
260 .checked_add(Duration::new(1580702706, 7000))
261 .unwrap()
262 );
263 assert_eq!(
264 max_datetime(py).extract::<SystemTime>().unwrap(),
265 UNIX_EPOCH
266 .checked_add(Duration::new(253402300799, 999999000))
267 .unwrap()
268 );
269 });
270 }
271
272 #[test]
273 fn test_time_frompyobject_before_epoch() {
274 Python::with_gil(|py| {
275 assert_eq!(
276 new_datetime(py, 1950, 1, 1, 0, 0, 0, 0)
277 .extract::<SystemTime>()
278 .unwrap_err()
279 .to_string(),
280 "ValueError: It is not possible to convert a negative timedelta to a Rust Duration"
281 );
282 })
283 }
284
285 #[test]
286 fn test_time_intopyobject() {
287 Python::with_gil(|py| {
288 let assert_eq = |l: Bound<'_, PyDateTime>, r: Bound<'_, PyDateTime>| {
289 assert!(l.eq(r).unwrap());
290 };
291
292 assert_eq(
293 UNIX_EPOCH
294 .checked_add(Duration::new(1580702706, 7123))
295 .unwrap()
296 .into_pyobject(py)
297 .unwrap(),
298 new_datetime(py, 2020, 2, 3, 4, 5, 6, 7),
299 );
300 assert_eq(
301 UNIX_EPOCH
302 .checked_add(Duration::new(253402300799, 999999000))
303 .unwrap()
304 .into_pyobject(py)
305 .unwrap(),
306 max_datetime(py),
307 );
308 });
309 }
310
311 #[allow(clippy::too_many_arguments)]
312 fn new_datetime(
313 py: Python<'_>,
314 year: i32,
315 month: u8,
316 day: u8,
317 hour: u8,
318 minute: u8,
319 second: u8,
320 microsecond: u32,
321 ) -> Bound<'_, PyDateTime> {
322 let utc = PyTzInfo::utc(py).unwrap();
323 PyDateTime::new(
324 py,
325 year,
326 month,
327 day,
328 hour,
329 minute,
330 second,
331 microsecond,
332 Some(&utc),
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
341 .set_item("tzinfo", PyTzInfo::utc(py).unwrap())
342 .unwrap();
343 naive_max
344 .call_method("replace", (), Some(&kargs))
345 .unwrap()
346 .downcast_into()
347 .unwrap()
348 }
349
350 #[test]
351 fn test_time_intopyobject_overflow() {
352 let big_system_time = UNIX_EPOCH
353 .checked_add(Duration::new(300000000000, 0))
354 .unwrap();
355 Python::with_gil(|py| {
356 assert!(big_system_time.into_pyobject(py).is_err());
357 })
358 }
359
360 fn new_timedelta(
361 py: Python<'_>,
362 days: i32,
363 seconds: i32,
364 microseconds: i32,
365 ) -> Bound<'_, PyAny> {
366 timedelta_class(py)
367 .call1((days, seconds, microseconds))
368 .unwrap()
369 }
370
371 fn datetime_class(py: Python<'_>) -> Bound<'_, PyAny> {
372 py.import("datetime").unwrap().getattr("datetime").unwrap()
373 }
374
375 fn timedelta_class(py: Python<'_>) -> Bound<'_, PyAny> {
376 py.import("datetime").unwrap().getattr("timedelta").unwrap()
377 }
378}