Skip to content

Commit

Permalink
Fix out-of-range panics in methods that use map_local
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed May 23, 2023
1 parent 191fe0e commit 86a980f
Showing 1 changed file with 35 additions and 1 deletion.
36 changes: 35 additions & 1 deletion src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,8 @@ fn map_local<Tz: TimeZone, F>(dt: &DateTime<Tz>, mut f: F) -> Option<DateTime<Tz
where
F: FnMut(NaiveDateTime) -> Option<NaiveDateTime>,
{
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<FixedOffset> {
Expand Down Expand Up @@ -903,38 +904,71 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
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<DateTime<Tz>> {
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<DateTime<Tz>> {
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<DateTime<Tz>> {
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<DateTime<Tz>> {
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<DateTime<Tz>> {
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<DateTime<Tz>> {
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<DateTime<Tz>> {
if self.ordinal0() == ordinal0 {
return Some(self.clone()); // See note on short-circuiting above.
}
map_local(self, |datetime| datetime.with_ordinal0(ordinal0))
}
}
Expand Down

0 comments on commit 86a980f

Please sign in to comment.