Skip to content

Commit

Permalink
Set correct fold when converting to ambiguous chrono::DateTime<Tz>
Browse files Browse the repository at this point in the history
  • Loading branch information
bschoenmaeckers committed Dec 13, 2024
1 parent 1861f3b commit 28999ab
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 5 deletions.
2 changes: 1 addition & 1 deletion newsfragments/4791.fixed.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
use `datetime.fold` to distinguish ambiguous datetimes when converting to `chrono::DateTime<Tz>`
use `datetime.fold` to distinguish ambiguous datetimes when converting to and from `chrono::DateTime<Tz>`
17 changes: 13 additions & 4 deletions src/conversions/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ use crate::sync::GILOnceCell;
use crate::types::any::PyAnyMethods;
#[cfg(not(Py_LIMITED_API))]
use crate::types::datetime::timezone_from_offset;
#[cfg(Py_LIMITED_API)]
use crate::types::IntoPyDict;
use crate::types::PyNone;
#[cfg(not(Py_LIMITED_API))]
use crate::types::{
Expand Down Expand Up @@ -466,14 +468,21 @@ where
truncated_leap_second,
} = (&self.naive_local().time()).into();

let fold = matches!(
self.timezone().offset_from_local_datetime(&self.naive_local()),
LocalResult::Ambiguous(_, latest) if self.offset().fix() == latest.fix()
);

#[cfg(not(Py_LIMITED_API))]
let datetime = PyDateTime::new(py, year, month, day, hour, min, sec, micro, Some(tz))?;
let datetime =
PyDateTime::new_with_fold(py, year, month, day, hour, min, sec, micro, Some(tz), fold)?;

#[cfg(Py_LIMITED_API)]
let datetime = DatetimeTypes::try_get(py).and_then(|dt| {
dt.datetime
.bind(py)
.call1((year, month, day, hour, min, sec, micro, tz))
dt.datetime.bind(py).call(
(year, month, day, hour, min, sec, micro, tz),
Some(&[("fold", fold as u8)].into_py_dict(py)?),
)
})?;

if truncated_leap_second {
Expand Down
52 changes: 52 additions & 0 deletions src/conversions/chrono_tz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ impl FromPyObject<'_> for Tz {
#[cfg(all(test, not(windows)))] // Troubles loading timezones on Windows
mod tests {
use super::*;
use crate::prelude::PyAnyMethods;
use crate::Python;
use chrono::{DateTime, Utc};
use chrono_tz::Tz;

#[test]
fn test_frompyobject() {
Expand All @@ -114,6 +118,54 @@ mod tests {
});
}

#[test]
fn test_ambiguous_datetime_to_pyobject() {
let dates = [
DateTime::<Utc>::from_str("2020-10-24 23:00:00 UTC").unwrap(),
DateTime::<Utc>::from_str("2020-10-25 00:00:00 UTC").unwrap(),
DateTime::<Utc>::from_str("2020-10-25 01:00:00 UTC").unwrap(),
];

let dates = dates.map(|dt| dt.with_timezone(&Tz::Europe__London));

assert_eq!(
dates.map(|dt| dt.to_string()),
[
"2020-10-25 00:00:00 BST",
"2020-10-25 01:00:00 BST",
"2020-10-25 01:00:00 GMT"
]
);

let dates = Python::with_gil(|py| {
let pydates = dates.map(|dt| dt.into_pyobject(py).unwrap());
assert_eq!(
pydates
.clone()
.map(|dt| dt.getattr("hour").unwrap().extract::<usize>().unwrap()),
[0, 1, 1]
);

assert_eq!(
pydates
.clone()
.map(|dt| dt.getattr("fold").unwrap().extract::<usize>().unwrap() > 0),
[false, false, true]
);

pydates.map(|dt| dt.extract::<DateTime<Tz>>().unwrap())
});

assert_eq!(
dates.map(|dt| dt.to_string()),
[
"2020-10-25 00:00:00 BST",
"2020-10-25 01:00:00 BST",
"2020-10-25 01:00:00 GMT"
]
);
}

#[test]
#[cfg(not(Py_GIL_DISABLED))] // https://github.com/python/cpython/issues/116738#issuecomment-2404360445
fn test_into_pyobject() {
Expand Down

0 comments on commit 28999ab

Please sign in to comment.