Skip to content

Commit

Permalink
Fix panic in NaiveDateTime::and_local_timezone
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Aug 6, 2023
1 parent 90c8384 commit cdc9884
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 4 deletions.
24 changes: 22 additions & 2 deletions src/naive/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::format::DelayedFormat;
use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems};
use crate::format::{Fixed, Item, Numeric, Pad};
use crate::naive::{Days, IsoWeek, NaiveDate, NaiveTime};
use crate::offset::Utc;
use crate::offset::{Offset, Utc};
use crate::oldtime::Duration as OldDuration;
use crate::{DateTime, Datelike, FixedOffset, LocalResult, Months, TimeZone, Timelike, Weekday};
#[cfg(feature = "rustc-serialize")]
Expand Down Expand Up @@ -948,7 +948,8 @@ impl NaiveDateTime {
/// ```
#[must_use]
pub fn and_local_timezone<Tz: TimeZone>(&self, tz: Tz) -> LocalResult<DateTime<Tz>> {
tz.from_local_datetime(self)
let local_result_offset = tz.offset_from_local_datetime(self);
local_time_min_offset(local_result_offset, self).unwrap_or(LocalResult::None)
}

/// Converts the `NaiveDateTime` into the timezone-aware `DateTime<Utc>`.
Expand All @@ -971,6 +972,25 @@ impl NaiveDateTime {
pub const MAX: Self = Self { date: NaiveDate::MAX, time: NaiveTime::MAX };
}

/// Helper function to map `LocalResult<FixedOffset>` to `LocalResult<DateTime<Local>>`.
/// Returns `None` on out-of-range.
fn local_time_min_offset<Tz: TimeZone>(
local_result: LocalResult<Tz::Offset>,
local_time: &NaiveDateTime,
) -> Option<LocalResult<DateTime<Tz>>> {
Some(match local_result {
LocalResult::None => LocalResult::None,
LocalResult::Single(offset) => LocalResult::Single(DateTime::from_naive_utc_and_offset(
local_time.checked_sub_offset(offset.fix())?,
offset,
)),
LocalResult::Ambiguous(o1, o2) => LocalResult::Ambiguous(
DateTime::from_naive_utc_and_offset(local_time.checked_sub_offset(o1.fix())?, o1),
DateTime::from_naive_utc_and_offset(local_time.checked_sub_offset(o2.fix())?, o2),
),
})
}

impl Datelike for NaiveDateTime {
/// Returns the year number in the [calendar date](./struct.NaiveDate.html#calendar-date).
///
Expand Down
24 changes: 22 additions & 2 deletions src/naive/datetime/tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::NaiveDateTime;
use crate::oldtime::Duration;
use crate::NaiveDate;
use crate::{Datelike, FixedOffset, Utc};
use crate::{Datelike, FixedOffset, LocalResult, NaiveDate, Utc};

#[test]
fn test_datetime_from_timestamp_millis() {
Expand Down Expand Up @@ -431,3 +430,24 @@ fn test_checked_sub_offset() {
// out of range
assert!(NaiveDateTime::MAX.checked_sub_offset(negative_offset).is_none());
}

#[test]
fn test_and_timezone_min_max_dates() {
for offset_hour in -23..=23 {
dbg!(offset_hour);
let offset = FixedOffset::east_opt(offset_hour * 60 * 60).unwrap();

let local_max = NaiveDateTime::MAX.and_local_timezone(offset);
if offset_hour >= 0 {
assert_eq!(local_max.unwrap().naive_local(), NaiveDateTime::MAX);
} else {
assert_eq!(local_max, LocalResult::None);
}
let local_min = NaiveDateTime::MIN.and_local_timezone(offset);
if offset_hour <= 0 {
assert_eq!(local_min.unwrap().naive_local(), NaiveDateTime::MIN);
} else {
assert_eq!(local_min, LocalResult::None);
}
}
}

0 comments on commit cdc9884

Please sign in to comment.