From 94737185588bf3b57a63ab5b424c2e505d3695f6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 18:10:01 +0200 Subject: [PATCH] Fix out-of-range panics in methods that use `map_local` --- src/datetime/mod.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 5a7dcd776d..c19d73e930 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -612,7 +612,8 @@ fn map_local(dt: &DateTime, mut f: F) -> Option Option, { - f(dt.naive_local()).and_then(|datetime| datetime.and_local_timezone(dt.timezone()).single()) + f(dt.overflowing_naive_local()) + .and_then(|datetime| datetime.and_local_timezone(dt.timezone()).single()) } impl DateTime { @@ -940,6 +941,18 @@ impl Datelike for DateTime { self.overflowing_naive_local().iso_week() } + // Note on short-circuiting. + // + // The `with_*` methods have an interesting property: if the local `NaiveDateTime` would be + // out-of-range, there is only exactly one year/month/day/ordinal they can be set to that would + // result in a valid `DateTime`: the one that is already there. + // This is thanks to the restriction that an offset is always less then one day, 24h. + // + // The methods below all end up constructing a new `NaiveDate`, which validates the + // resulting `NaiveDateTime` is in range. + // To prevent failing when the resulting `DateTime` could be in range, all the following + // methods short-circuit when possible. + #[inline] /// Makes a new `DateTime` with the year number changed, while keeping the same month and day. /// @@ -953,6 +966,9 @@ impl Datelike for DateTime { /// - The local time at the resulting date does not exist or is ambiguous, for example during a /// daylight saving time transition. fn with_year(&self, year: i32) -> Option> { + if self.year() == year { + return Some(self.clone()); // See note on short-circuiting above. + } map_local(self, |datetime| datetime.with_year(year)) } @@ -969,6 +985,9 @@ impl Datelike for DateTime { /// daylight saving time transition. #[inline] fn with_month(&self, month: u32) -> Option> { + if self.month() == month { + return Some(self.clone()); // See note on short-circuiting above. + } map_local(self, |datetime| datetime.with_month(month)) } @@ -985,6 +1004,9 @@ impl Datelike for DateTime { /// daylight saving time transition. #[inline] fn with_month0(&self, month0: u32) -> Option> { + if self.month0() == month0 { + return Some(self.clone()); // See note on short-circuiting above. + } map_local(self, |datetime| datetime.with_month0(month0)) } @@ -1001,6 +1023,9 @@ impl Datelike for DateTime { /// daylight saving time transition. #[inline] fn with_day(&self, day: u32) -> Option> { + if self.day() == day { + return Some(self.clone()); // See note on short-circuiting above. + } map_local(self, |datetime| datetime.with_day(day)) } @@ -1017,6 +1042,9 @@ impl Datelike for DateTime { /// daylight saving time transition. #[inline] fn with_day0(&self, day0: u32) -> Option> { + if self.day0() == day0 { + return Some(self.clone()); // See note on short-circuiting above. + } map_local(self, |datetime| datetime.with_day0(day0)) } @@ -1033,6 +1061,9 @@ impl Datelike for DateTime { /// daylight saving time transition. #[inline] fn with_ordinal(&self, ordinal: u32) -> Option> { + if self.ordinal() == ordinal { + return Some(self.clone()); // See note on short-circuiting above. + } map_local(self, |datetime| datetime.with_ordinal(ordinal)) } @@ -1049,6 +1080,9 @@ impl Datelike for DateTime { /// daylight saving time transition. #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option> { + if self.ordinal0() == ordinal0 { + return Some(self.clone()); // See note on short-circuiting above. + } map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) } }