Skip to content

Commit c1a213e

Browse files
committed
Fix out-of-range panics in methods that use map_local
1 parent 35e65f0 commit c1a213e

File tree

1 file changed

+35
-1
lines changed

1 file changed

+35
-1
lines changed

src/datetime/mod.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,8 @@ fn map_local<Tz: TimeZone, F>(dt: &DateTime<Tz>, mut f: F) -> Option<DateTime<Tz
612612
where
613613
F: FnMut(NaiveDateTime) -> Option<NaiveDateTime>,
614614
{
615-
f(dt.naive_local()).and_then(|datetime| datetime.and_local_timezone(dt.timezone()).single())
615+
f(dt.overflowing_naive_local())
616+
.and_then(|datetime| datetime.and_local_timezone(dt.timezone()).single())
616617
}
617618

618619
impl DateTime<FixedOffset> {
@@ -940,6 +941,18 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
940941
self.overflowing_naive_local().iso_week()
941942
}
942943

944+
// Note on short-circuiting.
945+
//
946+
// The `with_*` methods have an interesting property: if the local `NaiveDateTime` would be
947+
// out-of-range, there is only exactly one year/month/day/ordinal they can be set to that would
948+
// result in a valid `DateTime`: the one that is already there.
949+
// This is thanks to the restriction that an offset is always less then one day, 24h.
950+
//
951+
// The methods below all end up constructing a new `NaiveDate`, which validates the
952+
// resulting `NaiveDateTime` is in range.
953+
// To prevent failing when the resulting `DateTime` could be in range, all the following
954+
// methods short-circuit when possible.
955+
943956
#[inline]
944957
/// Makes a new `DateTime` with the year number changed, while keeping the same month and day.
945958
///
@@ -953,6 +966,9 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
953966
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
954967
/// daylight saving time transition.
955968
fn with_year(&self, year: i32) -> Option<DateTime<Tz>> {
969+
if self.year() == year {
970+
return Some(self.clone()); // See note on short-circuiting above.
971+
}
956972
map_local(self, |datetime| datetime.with_year(year))
957973
}
958974

@@ -969,6 +985,9 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
969985
/// daylight saving time transition.
970986
#[inline]
971987
fn with_month(&self, month: u32) -> Option<DateTime<Tz>> {
988+
if self.month() == month {
989+
return Some(self.clone()); // See note on short-circuiting above.
990+
}
972991
map_local(self, |datetime| datetime.with_month(month))
973992
}
974993

@@ -985,6 +1004,9 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
9851004
/// daylight saving time transition.
9861005
#[inline]
9871006
fn with_month0(&self, month0: u32) -> Option<DateTime<Tz>> {
1007+
if self.month0() == month0 {
1008+
return Some(self.clone()); // See note on short-circuiting above.
1009+
}
9881010
map_local(self, |datetime| datetime.with_month0(month0))
9891011
}
9901012

@@ -1001,6 +1023,9 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
10011023
/// daylight saving time transition.
10021024
#[inline]
10031025
fn with_day(&self, day: u32) -> Option<DateTime<Tz>> {
1026+
if self.day() == day {
1027+
return Some(self.clone()); // See note on short-circuiting above.
1028+
}
10041029
map_local(self, |datetime| datetime.with_day(day))
10051030
}
10061031

@@ -1017,6 +1042,9 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
10171042
/// daylight saving time transition.
10181043
#[inline]
10191044
fn with_day0(&self, day0: u32) -> Option<DateTime<Tz>> {
1045+
if self.day0() == day0 {
1046+
return Some(self.clone()); // See note on short-circuiting above.
1047+
}
10201048
map_local(self, |datetime| datetime.with_day0(day0))
10211049
}
10221050

@@ -1033,6 +1061,9 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
10331061
/// daylight saving time transition.
10341062
#[inline]
10351063
fn with_ordinal(&self, ordinal: u32) -> Option<DateTime<Tz>> {
1064+
if self.ordinal() == ordinal {
1065+
return Some(self.clone()); // See note on short-circuiting above.
1066+
}
10361067
map_local(self, |datetime| datetime.with_ordinal(ordinal))
10371068
}
10381069

@@ -1049,6 +1080,9 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
10491080
/// daylight saving time transition.
10501081
#[inline]
10511082
fn with_ordinal0(&self, ordinal0: u32) -> Option<DateTime<Tz>> {
1083+
if self.ordinal0() == ordinal0 {
1084+
return Some(self.clone()); // See note on short-circuiting above.
1085+
}
10521086
map_local(self, |datetime| datetime.with_ordinal0(ordinal0))
10531087
}
10541088
}

0 commit comments

Comments
 (0)