From 86a980f944a75bdc72523d6f8a2cd322754ea53d 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 07cf7abdad..33aa957e32 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -575,7 +575,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 { @@ -903,38 +904,71 @@ 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] 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)) } #[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)) } #[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)) } #[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)) } #[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)) } #[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)) } #[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)) } }