Fix panic for negative inputs to timestamp_millis.

This patch fixes the case where a negative millisecond offset is passed
to Timezone::timestamp_millis and timestamp_millis_opt and adds a test
case for it. Without this patch, calling timestamp_offset with a
negative value will panic with an overflow like this:

```
---- tests::test_parse_samples stdout ----
thread 'tests::test_parse_samples' panicked at 'attempt to multiply with
overflow',
/home/c/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.4.6/src/offset/mod.rs:349:34
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a
verbose backtrace.
stack backtrace:
   0: std::sys::unix::backtrace::tracing:👿:unwind_backtrace
             at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::print
             at libstd/sys_common/backtrace.rs:71
             at libstd/sys_common/backtrace.rs:59
   2: std::panicking::default_hook::{{closure}}
             at libstd/panicking.rs:211
   3: std::panicking::default_hook
             at libstd/panicking.rs:221
   4: std::panicking::rust_panic_with_hook
             at libstd/panicking.rs:477
   5: std::panicking::continue_panic_fmt
             at libstd/panicking.rs:391
   6: rust_begin_unwind
             at libstd/panicking.rs:326
   7: core::panicking::panic_fmt
             at libcore/panicking.rs:77
   8: core::panicking::panic
             at libcore/panicking.rs:52
   9: chrono::offset::TimeZone::timestamp_millis_opt
             at
/home/c/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.4.6/src/offset/mod.rs:349
  10: chrono::offset::TimeZone::timestamp_millis
             at
/home/c/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.4.6/src/offset/mod.rs:327
```
This commit is contained in:
Casey Marshall 2018-12-02 11:13:19 -06:00
parent 5dc483990a
commit b39597f537
1 changed files with 81 additions and 31 deletions

View File

@ -20,10 +20,10 @@
use std::fmt; use std::fmt;
use format::{parse, ParseResult, Parsed, StrftimeItems};
use naive::{NaiveDate, NaiveDateTime, NaiveTime};
use Weekday; use Weekday;
use naive::{NaiveDate, NaiveTime, NaiveDateTime};
use {Date, DateTime}; use {Date, DateTime};
use format::{parse, Parsed, ParseResult, StrftimeItems};
/// The conversion result from the local time to the timezone-aware datetime types. /// The conversion result from the local time to the timezone-aware datetime types.
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
@ -41,17 +41,26 @@ pub enum LocalResult<T> {
impl<T> LocalResult<T> { impl<T> LocalResult<T> {
/// Returns `Some` only when the conversion result is unique, or `None` otherwise. /// Returns `Some` only when the conversion result is unique, or `None` otherwise.
pub fn single(self) -> Option<T> { pub fn single(self) -> Option<T> {
match self { LocalResult::Single(t) => Some(t), _ => None } match self {
LocalResult::Single(t) => Some(t),
_ => None,
}
} }
/// Returns `Some` for the earliest possible conversion result, or `None` if none. /// Returns `Some` for the earliest possible conversion result, or `None` if none.
pub fn earliest(self) -> Option<T> { pub fn earliest(self) -> Option<T> {
match self { LocalResult::Single(t) | LocalResult::Ambiguous(t,_) => Some(t), _ => None } match self {
LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => Some(t),
_ => None,
}
} }
/// Returns `Some` for the latest possible conversion result, or `None` if none. /// Returns `Some` for the latest possible conversion result, or `None` if none.
pub fn latest(self) -> Option<T> { pub fn latest(self) -> Option<T> {
match self { LocalResult::Single(t) | LocalResult::Ambiguous(_,t) => Some(t), _ => None } match self {
LocalResult::Single(t) | LocalResult::Ambiguous(_, t) => Some(t),
_ => None,
}
} }
/// Maps a `LocalResult<T>` into `LocalResult<U>` with given function. /// Maps a `LocalResult<T>` into `LocalResult<U>` with given function.
@ -72,7 +81,8 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
#[inline] #[inline]
pub fn and_time(self, time: NaiveTime) -> LocalResult<DateTime<Tz>> { pub fn and_time(self, time: NaiveTime) -> LocalResult<DateTime<Tz>> {
match self { match self {
LocalResult::Single(d) => d.and_time(time) LocalResult::Single(d) => d
.and_time(time)
.map_or(LocalResult::None, LocalResult::Single), .map_or(LocalResult::None, LocalResult::Single),
_ => LocalResult::None, _ => LocalResult::None,
} }
@ -85,7 +95,8 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
#[inline] #[inline]
pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult<DateTime<Tz>> { pub fn and_hms_opt(self, hour: u32, min: u32, sec: u32) -> LocalResult<DateTime<Tz>> {
match self { match self {
LocalResult::Single(d) => d.and_hms_opt(hour, min, sec) LocalResult::Single(d) => d
.and_hms_opt(hour, min, sec)
.map_or(LocalResult::None, LocalResult::Single), .map_or(LocalResult::None, LocalResult::Single),
_ => LocalResult::None, _ => LocalResult::None,
} }
@ -97,10 +108,16 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
/// ///
/// Propagates any error. Ambiguous result would be discarded. /// Propagates any error. Ambiguous result would be discarded.
#[inline] #[inline]
pub fn and_hms_milli_opt(self, hour: u32, min: u32, sec: u32, pub fn and_hms_milli_opt(
milli: u32) -> LocalResult<DateTime<Tz>> { self,
hour: u32,
min: u32,
sec: u32,
milli: u32,
) -> LocalResult<DateTime<Tz>> {
match self { match self {
LocalResult::Single(d) => d.and_hms_milli_opt(hour, min, sec, milli) LocalResult::Single(d) => d
.and_hms_milli_opt(hour, min, sec, milli)
.map_or(LocalResult::None, LocalResult::Single), .map_or(LocalResult::None, LocalResult::Single),
_ => LocalResult::None, _ => LocalResult::None,
} }
@ -112,10 +129,16 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
/// ///
/// Propagates any error. Ambiguous result would be discarded. /// Propagates any error. Ambiguous result would be discarded.
#[inline] #[inline]
pub fn and_hms_micro_opt(self, hour: u32, min: u32, sec: u32, pub fn and_hms_micro_opt(
micro: u32) -> LocalResult<DateTime<Tz>> { self,
hour: u32,
min: u32,
sec: u32,
micro: u32,
) -> LocalResult<DateTime<Tz>> {
match self { match self {
LocalResult::Single(d) => d.and_hms_micro_opt(hour, min, sec, micro) LocalResult::Single(d) => d
.and_hms_micro_opt(hour, min, sec, micro)
.map_or(LocalResult::None, LocalResult::Single), .map_or(LocalResult::None, LocalResult::Single),
_ => LocalResult::None, _ => LocalResult::None,
} }
@ -127,15 +150,20 @@ impl<Tz: TimeZone> LocalResult<Date<Tz>> {
/// ///
/// Propagates any error. Ambiguous result would be discarded. /// Propagates any error. Ambiguous result would be discarded.
#[inline] #[inline]
pub fn and_hms_nano_opt(self, hour: u32, min: u32, sec: u32, pub fn and_hms_nano_opt(
nano: u32) -> LocalResult<DateTime<Tz>> { self,
hour: u32,
min: u32,
sec: u32,
nano: u32,
) -> LocalResult<DateTime<Tz>> {
match self { match self {
LocalResult::Single(d) => d.and_hms_nano_opt(hour, min, sec, nano) LocalResult::Single(d) => d
.and_hms_nano_opt(hour, min, sec, nano)
.map_or(LocalResult::None, LocalResult::Single), .map_or(LocalResult::None, LocalResult::Single),
_ => LocalResult::None, _ => LocalResult::None,
} }
} }
} }
impl<T: fmt::Debug> LocalResult<T> { impl<T: fmt::Debug> LocalResult<T> {
@ -144,7 +172,7 @@ impl<T: fmt::Debug> LocalResult<T> {
match self { match self {
LocalResult::None => panic!("No such local time"), LocalResult::None => panic!("No such local time"),
LocalResult::Single(t) => t, LocalResult::Single(t) => t,
LocalResult::Ambiguous(t1,t2) => { LocalResult::Ambiguous(t1, t2) => {
panic!("Ambiguous local time, ranging from {:?} to {:?}", t1, t2) panic!("Ambiguous local time, ranging from {:?} to {:?}", t1, t2)
} }
} }
@ -345,7 +373,11 @@ pub trait TimeZone: Sized + Clone {
/// }; /// };
/// ~~~~ /// ~~~~
fn timestamp_millis_opt(&self, millis: i64) -> LocalResult<DateTime<Self>> { fn timestamp_millis_opt(&self, millis: i64) -> LocalResult<DateTime<Self>> {
let (secs, millis) = (millis / 1000, millis % 1000); let (mut secs, mut millis) = (millis / 1000, millis % 1000);
if millis < 0 {
secs -= 1;
millis += 1000;
}
self.timestamp_opt(secs, millis as u32 * 1_000_000) self.timestamp_opt(secs, millis as u32 * 1_000_000)
} }
@ -384,9 +416,8 @@ pub trait TimeZone: Sized + Clone {
/// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible.
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Self>> { fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Self>> {
self.offset_from_local_datetime(local).map(|offset| { self.offset_from_local_datetime(local)
DateTime::from_utc(*local - offset.fix(), offset) .map(|offset| DateTime::from_utc(*local - offset.fix(), offset))
})
} }
/// Creates the offset for given UTC `NaiveDate`. This cannot fail. /// Creates the offset for given UTC `NaiveDate`. This cannot fail.
@ -408,12 +439,31 @@ pub trait TimeZone: Sized + Clone {
} }
} }
mod utc;
mod fixed; mod fixed;
#[cfg(feature="clock")] #[cfg(feature = "clock")]
mod local; mod local;
mod utc;
pub use self::utc::Utc;
pub use self::fixed::FixedOffset; pub use self::fixed::FixedOffset;
#[cfg(feature="clock")] #[cfg(feature = "clock")]
pub use self::local::Local; pub use self::local::Local;
pub use self::utc::Utc;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_negative_millis() {
let dt = Utc.timestamp_millis(-1000);
assert_eq!(dt.to_string(), "1969-12-31 23:59:59 UTC");
let dt = Utc.timestamp_millis(-999);
assert_eq!(dt.to_string(), "1969-12-31 23:59:59.001 UTC");
let dt = Utc.timestamp_millis(-1);
assert_eq!(dt.to_string(), "1969-12-31 23:59:59.999 UTC");
let dt = Utc.timestamp_millis(-60000);
assert_eq!(dt.to_string(), "1969-12-31 23:59:00 UTC");
let dt = Utc.timestamp_millis(-3600000);
assert_eq!(dt.to_string(), "1969-12-31 23:00:00 UTC");
}
}