From 1134d91fbe144fe55c49c913c3552fd70d382c89 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 659587115c..9b054b3000 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -713,7 +713,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 { @@ -942,6 +943,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. /// @@ -955,6 +968,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)) } @@ -971,6 +987,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)) } @@ -987,6 +1006,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)) } @@ -1003,6 +1025,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)) } @@ -1019,6 +1044,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)) } @@ -1035,6 +1063,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)) } @@ -1051,6 +1082,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)) } }