1use chrono::{DateTime, FixedOffset, Utc};
15use pyo3::exceptions::{PyTypeError, PyValueError};
16use pyo3::prelude::*;
17use pyo3::types::PyDateTime;
18use tempoch::{Period, Time, TimeContext, UTC};
19
20use crate::errors::map_conversion_error;
21
22fn aware_datetime(value: &Bound<'_, PyAny>) -> PyResult<DateTime<FixedOffset>> {
23 if !value.is_instance_of::<PyDateTime>() {
24 return Err(PyTypeError::new_err(
25 "value must be a timezone-aware datetime.datetime",
26 ));
27 }
28
29 if value.call_method0("utcoffset")?.is_none() {
30 return Err(PyValueError::new_err(
31 "naive datetimes are not accepted; value must be timezone-aware",
32 ));
33 }
34
35 value.extract::<DateTime<FixedOffset>>().map_err(|error| {
36 PyValueError::new_err(format!(
37 "value must be a valid timezone-aware datetime.datetime: {error}"
38 ))
39 })
40}
41
42pub fn datetime_to_time(value: &Bound<'_, PyAny>) -> PyResult<Time<UTC>> {
47 datetime_to_time_with(value, &TimeContext::new())
48}
49
50pub fn datetime_to_time_with(
52 value: &Bound<'_, PyAny>,
53 context: &TimeContext,
54) -> PyResult<Time<UTC>> {
55 let datetime = aware_datetime(value)?.with_timezone(&Utc);
56 Time::<UTC>::try_from_chrono_with(datetime, context).map_err(map_conversion_error)
57}
58
59pub fn time_to_datetime<'py>(py: Python<'py>, value: Time<UTC>) -> PyResult<Bound<'py, PyAny>> {
61 time_to_datetime_with(py, value, &TimeContext::new())
62}
63
64pub fn time_to_datetime_with<'py>(
66 py: Python<'py>,
67 value: Time<UTC>,
68 context: &TimeContext,
69) -> PyResult<Bound<'py, PyAny>> {
70 let datetime = value
71 .try_to_chrono_with(context)
72 .map_err(map_conversion_error)?;
73 Ok(datetime.into_pyobject(py)?.into_any())
74}
75
76pub fn period_to_datetimes<'py>(
78 py: Python<'py>,
79 period: Period<UTC>,
80) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> {
81 period_to_datetimes_with(py, period, &TimeContext::new())
82}
83
84pub fn period_to_datetimes_with<'py>(
86 py: Python<'py>,
87 period: Period<UTC>,
88 context: &TimeContext,
89) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> {
90 Ok((
91 time_to_datetime_with(py, period.start, context)?,
92 time_to_datetime_with(py, period.end, context)?,
93 ))
94}