added `checked_{add,sub}` methods to `[Naive]Date[Time]` types.
- Existing `+` and `-` operators use them, and properly panics with a correct error message on overflow/underflow.
This commit is contained in:
parent
43ee68b522
commit
3d00a0fd5a
26
src/date.rs
26
src/date.rs
|
@ -175,6 +175,24 @@ impl<Off:Offset> Date<Off> {
|
||||||
Date::from_utc(self.date, offset)
|
Date::from_utc(self.date, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds given `Duration` to the current date.
|
||||||
|
///
|
||||||
|
/// Returns `None` when it will result in overflow.
|
||||||
|
#[inline]
|
||||||
|
pub fn checked_add(self, rhs: Duration) -> Option<Date<Off>> {
|
||||||
|
let date = try_opt!(self.date.checked_add(rhs));
|
||||||
|
Some(Date { date: date, offset: self.offset })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtracts given `Duration` from the current date.
|
||||||
|
///
|
||||||
|
/// Returns `None` when it will result in overflow.
|
||||||
|
#[inline]
|
||||||
|
pub fn checked_sub(self, rhs: Duration) -> Option<Date<Off>> {
|
||||||
|
let date = try_opt!(self.date.checked_sub(rhs));
|
||||||
|
Some(Date { date: date, offset: self.offset })
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a view to the local date.
|
/// Returns a view to the local date.
|
||||||
fn local(&self) -> NaiveDate {
|
fn local(&self) -> NaiveDate {
|
||||||
self.offset.to_local_date(&self.date)
|
self.offset.to_local_date(&self.date)
|
||||||
|
@ -269,14 +287,16 @@ impl<Off: Offset, H: hash::Hasher + hash::Writer> hash::Hash<H> for Date<Off> {
|
||||||
impl<Off:Offset> Add<Duration> for Date<Off> {
|
impl<Off:Offset> Add<Duration> for Date<Off> {
|
||||||
type Output = Date<Off>;
|
type Output = Date<Off>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn add(self, rhs: Duration) -> Date<Off> {
|
fn add(self, rhs: Duration) -> Date<Off> {
|
||||||
Date { date: self.date + rhs, offset: self.offset }
|
self.checked_add(rhs).expect("`Date + Duration` overflowed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Off:Offset, Off2:Offset> Sub<Date<Off2>> for Date<Off> {
|
impl<Off:Offset, Off2:Offset> Sub<Date<Off2>> for Date<Off> {
|
||||||
type Output = Duration;
|
type Output = Duration;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn sub(self, rhs: Date<Off2>) -> Duration { self.date - rhs.date }
|
fn sub(self, rhs: Date<Off2>) -> Duration { self.date - rhs.date }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,7 +304,9 @@ impl<Off:Offset> Sub<Duration> for Date<Off> {
|
||||||
type Output = Date<Off>;
|
type Output = Date<Off>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sub(self, rhs: Duration) -> Date<Off> { self.add(-rhs) }
|
fn sub(self, rhs: Duration) -> Date<Off> {
|
||||||
|
self.checked_sub(rhs).expect("`Date - Duration` overflowed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Off: Offset> fmt::Debug for Date<Off> {
|
impl<Off: Offset> fmt::Debug for Date<Off> {
|
||||||
|
|
|
@ -64,6 +64,24 @@ impl<Off:Offset> DateTime<Off> {
|
||||||
DateTime::from_utc(self.datetime, offset)
|
DateTime::from_utc(self.datetime, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds given `Duration` to the current date and time.
|
||||||
|
///
|
||||||
|
/// Returns `None` when it will result in overflow.
|
||||||
|
#[inline]
|
||||||
|
pub fn checked_add(self, rhs: Duration) -> Option<DateTime<Off>> {
|
||||||
|
let datetime = try_opt!(self.datetime.checked_add(rhs));
|
||||||
|
Some(DateTime { datetime: datetime, offset: self.offset })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtracts given `Duration` from the current date and time.
|
||||||
|
///
|
||||||
|
/// Returns `None` when it will result in overflow.
|
||||||
|
#[inline]
|
||||||
|
pub fn checked_sub(self, rhs: Duration) -> Option<DateTime<Off>> {
|
||||||
|
let datetime = try_opt!(self.datetime.checked_sub(rhs));
|
||||||
|
Some(DateTime { datetime: datetime, offset: self.offset })
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a view to the local datetime.
|
/// Returns a view to the local datetime.
|
||||||
fn local(&self) -> NaiveDateTime {
|
fn local(&self) -> NaiveDateTime {
|
||||||
self.offset.to_local_datetime(&self.datetime)
|
self.offset.to_local_datetime(&self.datetime)
|
||||||
|
@ -190,14 +208,16 @@ impl<Off: Offset, H: hash::Hasher + hash::Writer> hash::Hash<H> for DateTime<Off
|
||||||
impl<Off:Offset> Add<Duration> for DateTime<Off> {
|
impl<Off:Offset> Add<Duration> for DateTime<Off> {
|
||||||
type Output = DateTime<Off>;
|
type Output = DateTime<Off>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn add(self, rhs: Duration) -> DateTime<Off> {
|
fn add(self, rhs: Duration) -> DateTime<Off> {
|
||||||
DateTime { datetime: self.datetime + rhs, offset: self.offset }
|
self.checked_add(rhs).expect("`DateTime + Duration` overflowed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Off:Offset, Off2:Offset> Sub<DateTime<Off2>> for DateTime<Off> {
|
impl<Off:Offset, Off2:Offset> Sub<DateTime<Off2>> for DateTime<Off> {
|
||||||
type Output = Duration;
|
type Output = Duration;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn sub(self, rhs: DateTime<Off2>) -> Duration { self.datetime - rhs.datetime }
|
fn sub(self, rhs: DateTime<Off2>) -> Duration { self.datetime - rhs.datetime }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +225,9 @@ impl<Off:Offset> Sub<Duration> for DateTime<Off> {
|
||||||
type Output = DateTime<Off>;
|
type Output = DateTime<Off>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sub(self, rhs: Duration) -> DateTime<Off> { self.add(-rhs) }
|
fn sub(self, rhs: Duration) -> DateTime<Off> {
|
||||||
|
self.checked_sub(rhs).expect("`DateTime - Duration` overflowed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Off: Offset> fmt::Debug for DateTime<Off> {
|
impl<Off: Offset> fmt::Debug for DateTime<Off> {
|
||||||
|
|
|
@ -201,6 +201,11 @@ pub use date::Date;
|
||||||
pub use time::Time;
|
pub use time::Time;
|
||||||
pub use datetime::DateTime;
|
pub use datetime::DateTime;
|
||||||
|
|
||||||
|
// useful throughout the codebase
|
||||||
|
macro_rules! try_opt {
|
||||||
|
($e:expr) => (match $e { Some(v) => v, None => return None })
|
||||||
|
}
|
||||||
|
|
||||||
mod div;
|
mod div;
|
||||||
pub mod duration {
|
pub mod duration {
|
||||||
//! ISO 8601 duration.
|
//! ISO 8601 duration.
|
||||||
|
|
|
@ -22,6 +22,28 @@ use self::internals::{DateImpl, Of, Mdf, YearFlags};
|
||||||
const MAX_YEAR: i32 = internals::MAX_YEAR as i32;
|
const MAX_YEAR: i32 = internals::MAX_YEAR as i32;
|
||||||
const MIN_YEAR: i32 = internals::MIN_YEAR as i32;
|
const MIN_YEAR: i32 = internals::MIN_YEAR as i32;
|
||||||
|
|
||||||
|
// MAX_YEAR-12-31 minus 0000-01-01
|
||||||
|
// = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + (0001-01-01 minus 0000-01-01) - 1 day
|
||||||
|
// = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + 365 days
|
||||||
|
// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 days
|
||||||
|
#[cfg(test)] // only used for testing
|
||||||
|
const MAX_DAYS_FROM_YEAR_0: i32 = MAX_YEAR * 365 +
|
||||||
|
MAX_YEAR / 4 -
|
||||||
|
MAX_YEAR / 100 +
|
||||||
|
MAX_YEAR / 400 + 365;
|
||||||
|
|
||||||
|
// MIN_YEAR-01-01 minus 0000-01-01
|
||||||
|
// = (MIN_YEAR+400n+1)-01-01 minus (400n+1)-01-01
|
||||||
|
// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - ((400n+1)-01-01 minus 0001-01-01)
|
||||||
|
// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - 146097n days
|
||||||
|
//
|
||||||
|
// n is set to 1000 for convenience.
|
||||||
|
#[cfg(test)] // only used for testing
|
||||||
|
const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 +
|
||||||
|
(MIN_YEAR + 400_000) / 4 -
|
||||||
|
(MIN_YEAR + 400_000) / 100 +
|
||||||
|
(MIN_YEAR + 400_000) / 400 - 146097_000;
|
||||||
|
|
||||||
/// ISO 8601 calendar date without timezone.
|
/// ISO 8601 calendar date without timezone.
|
||||||
/// Allows for every proleptic Gregorian date from Jan 1, 262145 BCE to Dec 31, 262143 CE.
|
/// Allows for every proleptic Gregorian date from Jan 1, 262145 BCE to Dec 31, 262143 CE.
|
||||||
/// Also supports the conversion from ISO 8601 ordinal and week date.
|
/// Also supports the conversion from ISO 8601 ordinal and week date.
|
||||||
|
@ -302,6 +324,40 @@ impl NaiveDate {
|
||||||
self.with_of(self.of().pred()).or_else(|| NaiveDate::from_ymd_opt(self.year() - 1, 12, 31))
|
self.with_of(self.of().pred()).or_else(|| NaiveDate::from_ymd_opt(self.year() - 1, 12, 31))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds the `days` part of given `Duration` to the current date.
|
||||||
|
///
|
||||||
|
/// Returns `None` when it will result in overflow.
|
||||||
|
pub fn checked_add(self, rhs: Duration) -> Option<NaiveDate> {
|
||||||
|
let year = self.year();
|
||||||
|
let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400);
|
||||||
|
let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal());
|
||||||
|
let cycle = try_opt!((cycle as i32).checked_add(try_opt!(rhs.num_days().to_i32())));
|
||||||
|
let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146097);
|
||||||
|
year_div_400 += cycle_div_400y;
|
||||||
|
|
||||||
|
let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32);
|
||||||
|
let flags = YearFlags::from_year_mod_400(year_mod_400 as i32);
|
||||||
|
NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32,
|
||||||
|
Of::new(ordinal, flags))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtracts the `days` part of given `Duration` from the current date.
|
||||||
|
///
|
||||||
|
/// Returns `None` when it will result in overflow.
|
||||||
|
pub fn checked_sub(self, rhs: Duration) -> Option<NaiveDate> {
|
||||||
|
let year = self.year();
|
||||||
|
let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400);
|
||||||
|
let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal());
|
||||||
|
let cycle = try_opt!((cycle as i32).checked_sub(try_opt!(rhs.num_days().to_i32())));
|
||||||
|
let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146097);
|
||||||
|
year_div_400 += cycle_div_400y;
|
||||||
|
|
||||||
|
let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32);
|
||||||
|
let flags = YearFlags::from_year_mod_400(year_mod_400 as i32);
|
||||||
|
NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32,
|
||||||
|
Of::new(ordinal, flags))
|
||||||
|
}
|
||||||
|
|
||||||
/// Formats the date in the specified format string.
|
/// Formats the date in the specified format string.
|
||||||
/// See the `format::strftime` module on the supported escape sequences.
|
/// See the `format::strftime` module on the supported escape sequences.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -387,20 +443,9 @@ impl<H: hash::Hasher + hash::Writer> hash::Hash<H> for NaiveDate {
|
||||||
impl Add<Duration> for NaiveDate {
|
impl Add<Duration> for NaiveDate {
|
||||||
type Output = NaiveDate;
|
type Output = NaiveDate;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn add(self, rhs: Duration) -> NaiveDate {
|
fn add(self, rhs: Duration) -> NaiveDate {
|
||||||
// TODO overflow currently fails
|
self.checked_add(rhs).expect("`NaiveDate + Duration` overflowed")
|
||||||
|
|
||||||
let year = self.year();
|
|
||||||
let (mut year_div_400, year_mod_400) = div_mod_floor(year, 400);
|
|
||||||
let cycle = internals::yo_to_cycle(year_mod_400 as u32, self.of().ordinal());
|
|
||||||
let cycle = (cycle as i32).checked_add(rhs.num_days().to_i32().unwrap()).unwrap();
|
|
||||||
let (cycle_div_400y, cycle) = div_mod_floor(cycle, 146097);
|
|
||||||
year_div_400 += cycle_div_400y;
|
|
||||||
|
|
||||||
let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32);
|
|
||||||
let flags = YearFlags::from_year_mod_400(year_mod_400 as i32);
|
|
||||||
NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32,
|
|
||||||
Of::new(ordinal, flags)).unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,7 +467,9 @@ impl Sub<Duration> for NaiveDate {
|
||||||
type Output = NaiveDate;
|
type Output = NaiveDate;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sub(self, rhs: Duration) -> NaiveDate { self.add(-rhs) }
|
fn sub(self, rhs: Duration) -> NaiveDate {
|
||||||
|
self.checked_sub(rhs).expect("`NaiveDate - Duration` overflowed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for NaiveDate {
|
impl fmt::Debug for NaiveDate {
|
||||||
|
@ -452,7 +499,9 @@ impl fmt::Display for NaiveDate {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{NaiveDate, MIN, MAX};
|
use super::NaiveDate;
|
||||||
|
use super::{MIN, MIN_YEAR, MIN_DAYS_FROM_YEAR_0};
|
||||||
|
use super::{MAX, MAX_YEAR, MAX_DAYS_FROM_YEAR_0};
|
||||||
use {Datelike, Weekday};
|
use {Datelike, Weekday};
|
||||||
use duration::Duration;
|
use duration::Duration;
|
||||||
use std::{i32, u32};
|
use std::{i32, u32};
|
||||||
|
@ -716,23 +765,32 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_add() {
|
fn test_date_add() {
|
||||||
fn check((y1,m1,d1): (i32, u32, u32), rhs: Duration, (y,m,d): (i32, u32, u32)) {
|
fn check((y1,m1,d1): (i32, u32, u32), rhs: Duration, ymd: Option<(i32, u32, u32)>) {
|
||||||
let lhs = NaiveDate::from_ymd(y1, m1, d1);
|
let lhs = NaiveDate::from_ymd(y1, m1, d1);
|
||||||
let sum = NaiveDate::from_ymd(y, m, d);
|
let sum = ymd.map(|(y,m,d)| NaiveDate::from_ymd(y, m, d));
|
||||||
assert_eq!(lhs + rhs, sum);
|
assert_eq!(lhs.checked_add(rhs), sum);
|
||||||
//assert_eq!(rhs + lhs, sum);
|
assert_eq!(lhs.checked_sub(-rhs), sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
check((2014, 1, 1), Duration::zero(), (2014, 1, 1));
|
check((2014, 1, 1), Duration::zero(), Some((2014, 1, 1)));
|
||||||
check((2014, 1, 1), Duration::seconds(86399), (2014, 1, 1));
|
check((2014, 1, 1), Duration::seconds(86399), Some((2014, 1, 1)));
|
||||||
check((2014, 1, 1), Duration::seconds(-86399), (2014, 1, 1)); // always round towards zero
|
// always round towards zero
|
||||||
check((2014, 1, 1), Duration::days(1), (2014, 1, 2));
|
check((2014, 1, 1), Duration::seconds(-86399), Some((2014, 1, 1)));
|
||||||
check((2014, 1, 1), Duration::days(-1), (2013, 12, 31));
|
check((2014, 1, 1), Duration::days(1), Some((2014, 1, 2)));
|
||||||
check((2014, 1, 1), Duration::days(364), (2014, 12, 31));
|
check((2014, 1, 1), Duration::days(-1), Some((2013, 12, 31)));
|
||||||
check((2014, 1, 1), Duration::days(365*4 + 1), (2018, 1, 1));
|
check((2014, 1, 1), Duration::days(364), Some((2014, 12, 31)));
|
||||||
check((2014, 1, 1), Duration::days(365*400 + 97), (2414, 1, 1));
|
check((2014, 1, 1), Duration::days(365*4 + 1), Some((2018, 1, 1)));
|
||||||
|
check((2014, 1, 1), Duration::days(365*400 + 97), Some((2414, 1, 1)));
|
||||||
|
|
||||||
check((-7, 1, 1), Duration::days(365*12 + 3), (5, 1, 1));
|
check((-7, 1, 1), Duration::days(365*12 + 3), Some((5, 1, 1)));
|
||||||
|
|
||||||
|
// overflow check
|
||||||
|
check((0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64), Some((MAX_YEAR, 12, 31)));
|
||||||
|
check((0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64 + 1), None);
|
||||||
|
check((0, 1, 1), Duration::max_value(), None);
|
||||||
|
check((0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64), Some((MIN_YEAR, 1, 1)));
|
||||||
|
check((0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64 - 1), None);
|
||||||
|
check((0, 1, 1), Duration::min_value(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -750,6 +808,9 @@ mod tests {
|
||||||
check((2015, 1, 3), (2014, 1, 1), Duration::days(365 + 2));
|
check((2015, 1, 3), (2014, 1, 1), Duration::days(365 + 2));
|
||||||
check((2018, 1, 1), (2014, 1, 1), Duration::days(365*4 + 1));
|
check((2018, 1, 1), (2014, 1, 1), Duration::days(365*4 + 1));
|
||||||
check((2414, 1, 1), (2014, 1, 1), Duration::days(365*400 + 97));
|
check((2414, 1, 1), (2014, 1, 1), Duration::days(365*400 + 97));
|
||||||
|
|
||||||
|
check((MAX_YEAR, 12, 31), (0, 1, 1), Duration::days(MAX_DAYS_FROM_YEAR_0 as i64));
|
||||||
|
check((MIN_YEAR, 1, 1), (0, 1, 1), Duration::days(MIN_DAYS_FROM_YEAR_0 as i64));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -81,6 +81,50 @@ impl NaiveDateTime {
|
||||||
(ndays - 719163) * 86400 + nseconds
|
(ndays - 719163) * 86400 + nseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds given `Duration` to the current date and time.
|
||||||
|
///
|
||||||
|
/// Returns `None` when it will result in overflow.
|
||||||
|
pub fn checked_add(self, rhs: Duration) -> Option<NaiveDateTime> {
|
||||||
|
// Duration does not directly give its parts, so we need some additional calculations.
|
||||||
|
let days = rhs.num_days();
|
||||||
|
let nanos = (rhs - Duration::days(days)).num_nanoseconds().unwrap();
|
||||||
|
debug_assert!(Duration::days(days) + Duration::nanoseconds(nanos) == rhs);
|
||||||
|
debug_assert!(-86400_000_000_000 < nanos && nanos < 86400_000_000_000);
|
||||||
|
|
||||||
|
let mut date = try_opt!(self.date.checked_add(Duration::days(days)));
|
||||||
|
let time = self.time + Duration::nanoseconds(nanos);
|
||||||
|
|
||||||
|
// time always wraps around, but date needs to be adjusted for overflow.
|
||||||
|
if nanos < 0 && time > self.time {
|
||||||
|
date = try_opt!(date.pred_opt());
|
||||||
|
} else if nanos > 0 && time < self.time {
|
||||||
|
date = try_opt!(date.succ_opt());
|
||||||
|
}
|
||||||
|
Some(NaiveDateTime { date: date, time: time })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtracts given `Duration` from the current date and time.
|
||||||
|
///
|
||||||
|
/// Returns `None` when it will result in overflow.
|
||||||
|
pub fn checked_sub(self, rhs: Duration) -> Option<NaiveDateTime> {
|
||||||
|
// Duration does not directly give its parts, so we need some additional calculations.
|
||||||
|
let days = rhs.num_days();
|
||||||
|
let nanos = (rhs - Duration::days(days)).num_nanoseconds().unwrap();
|
||||||
|
debug_assert!(Duration::days(days) + Duration::nanoseconds(nanos) == rhs);
|
||||||
|
debug_assert!(-86400_000_000_000 < nanos && nanos < 86400_000_000_000);
|
||||||
|
|
||||||
|
let mut date = try_opt!(self.date.checked_sub(Duration::days(days)));
|
||||||
|
let time = self.time - Duration::nanoseconds(nanos);
|
||||||
|
|
||||||
|
// time always wraps around, but date needs to be adjusted for overflow.
|
||||||
|
if nanos > 0 && time > self.time {
|
||||||
|
date = try_opt!(date.pred_opt());
|
||||||
|
} else if nanos < 0 && time < self.time {
|
||||||
|
date = try_opt!(date.succ_opt());
|
||||||
|
}
|
||||||
|
Some(NaiveDateTime { date: date, time: time })
|
||||||
|
}
|
||||||
|
|
||||||
/// Formats the combined date and time in the specified format string.
|
/// Formats the combined date and time in the specified format string.
|
||||||
/// See the `format::strftime` module on the supported escape sequences.
|
/// See the `format::strftime` module on the supported escape sequences.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -171,23 +215,9 @@ impl<H: hash::Hasher + hash::Writer> hash::Hash<H> for NaiveDateTime {
|
||||||
impl Add<Duration> for NaiveDateTime {
|
impl Add<Duration> for NaiveDateTime {
|
||||||
type Output = NaiveDateTime;
|
type Output = NaiveDateTime;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn add(self, rhs: Duration) -> NaiveDateTime {
|
fn add(self, rhs: Duration) -> NaiveDateTime {
|
||||||
// Duration does not directly give its parts, so we need some additional calculations.
|
self.checked_add(rhs).expect("`NaiveDateTime + Duration` overflowed")
|
||||||
let days = rhs.num_days();
|
|
||||||
let nanos = (rhs - Duration::days(days)).num_nanoseconds().unwrap();
|
|
||||||
debug_assert!(Duration::days(days) + Duration::nanoseconds(nanos) == rhs);
|
|
||||||
debug_assert!(-86400_000_000_000 < nanos && nanos < 86400_000_000_000);
|
|
||||||
|
|
||||||
let mut date = self.date + Duration::days(days);
|
|
||||||
let time = self.time + Duration::nanoseconds(nanos);
|
|
||||||
|
|
||||||
// time always wraps around, but date needs to be adjusted for overflow.
|
|
||||||
if nanos < 0 && time > self.time {
|
|
||||||
date = date.pred();
|
|
||||||
} else if nanos > 0 && time < self.time {
|
|
||||||
date = date.succ();
|
|
||||||
}
|
|
||||||
NaiveDateTime { date: date, time: time }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +233,9 @@ impl Sub<Duration> for NaiveDateTime {
|
||||||
type Output = NaiveDateTime;
|
type Output = NaiveDateTime;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sub(self, rhs: Duration) -> NaiveDateTime { self.add(-rhs) }
|
fn sub(self, rhs: Duration) -> NaiveDateTime {
|
||||||
|
self.checked_sub(rhs).expect("`NaiveDateTime - Duration` overflowed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for NaiveDateTime {
|
impl fmt::Debug for NaiveDateTime {
|
||||||
|
@ -221,7 +253,9 @@ impl fmt::Display for NaiveDateTime {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::NaiveDateTime;
|
use super::NaiveDateTime;
|
||||||
|
use Datelike;
|
||||||
use duration::Duration;
|
use duration::Duration;
|
||||||
|
use naive::date as naive_date;
|
||||||
use naive::date::NaiveDate;
|
use naive::date::NaiveDate;
|
||||||
use std::i64;
|
use std::i64;
|
||||||
|
|
||||||
|
@ -240,17 +274,35 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_datetime_add() {
|
fn test_datetime_add() {
|
||||||
let ymdhms = |&: y,m,d,h,n,s| NaiveDate::from_ymd(y,m,d).and_hms(h,n,s);
|
fn check((y,m,d,h,n,s): (i32,u32,u32,u32,u32,u32), rhs: Duration,
|
||||||
assert_eq!(ymdhms(2014, 5, 6, 7, 8, 9) + Duration::seconds(3600 + 60 + 1),
|
result: Option<(i32,u32,u32,u32,u32,u32)>) {
|
||||||
ymdhms(2014, 5, 6, 8, 9, 10));
|
let lhs = NaiveDate::from_ymd(y, m, d).and_hms(h, n, s);
|
||||||
assert_eq!(ymdhms(2014, 5, 6, 7, 8, 9) + Duration::seconds(-(3600 + 60 + 1)),
|
let sum = result.map(|(y,m,d,h,n,s)| NaiveDate::from_ymd(y, m, d).and_hms(h, n, s));
|
||||||
ymdhms(2014, 5, 6, 6, 7, 8));
|
assert_eq!(lhs.checked_add(rhs), sum);
|
||||||
assert_eq!(ymdhms(2014, 5, 6, 7, 8, 9) + Duration::seconds(86399),
|
assert_eq!(lhs.checked_sub(-rhs), sum);
|
||||||
ymdhms(2014, 5, 7, 7, 8, 8));
|
};
|
||||||
assert_eq!(ymdhms(2014, 5, 6, 7, 8, 9) + Duration::seconds(86400 * 10),
|
|
||||||
ymdhms(2014, 5, 16, 7, 8, 9));
|
check((2014,5,6, 7,8,9), Duration::seconds(3600 + 60 + 1), Some((2014,5,6, 8,9,10)));
|
||||||
assert_eq!(ymdhms(2014, 5, 6, 7, 8, 9) + Duration::seconds(-86400 * 10),
|
check((2014,5,6, 7,8,9), Duration::seconds(-(3600 + 60 + 1)), Some((2014,5,6, 6,7,8)));
|
||||||
ymdhms(2014, 4, 26, 7, 8, 9));
|
check((2014,5,6, 7,8,9), Duration::seconds(86399), Some((2014,5,7, 7,8,8)));
|
||||||
|
check((2014,5,6, 7,8,9), Duration::seconds(86400 * 10), Some((2014,5,16, 7,8,9)));
|
||||||
|
check((2014,5,6, 7,8,9), Duration::seconds(-86400 * 10), Some((2014,4,26, 7,8,9)));
|
||||||
|
check((2014,5,6, 7,8,9), Duration::seconds(86400 * 10), Some((2014,5,16, 7,8,9)));
|
||||||
|
|
||||||
|
// overflow check
|
||||||
|
// assumes that we have correct values for MAX/MIN_DAYS_FROM_YEAR_0 from `naive::date`.
|
||||||
|
// (they are private constants, but the equivalence is tested in that module.)
|
||||||
|
let max_days_from_year_0 = naive_date::MAX - NaiveDate::from_ymd(0,1,1);
|
||||||
|
check((0,1,1, 0,0,0), max_days_from_year_0, Some((naive_date::MAX.year(),12,31, 0,0,0)));
|
||||||
|
check((0,1,1, 0,0,0), max_days_from_year_0 + Duration::seconds(86399),
|
||||||
|
Some((naive_date::MAX.year(),12,31, 23,59,59)));
|
||||||
|
check((0,1,1, 0,0,0), max_days_from_year_0 + Duration::seconds(86400), None);
|
||||||
|
check((0,1,1, 0,0,0), Duration::max_value(), None);
|
||||||
|
|
||||||
|
let min_days_from_year_0 = naive_date::MIN - NaiveDate::from_ymd(0,1,1);
|
||||||
|
check((0,1,1, 0,0,0), min_days_from_year_0, Some((naive_date::MIN.year(),1,1, 0,0,0)));
|
||||||
|
check((0,1,1, 0,0,0), min_days_from_year_0 - Duration::seconds(1), None);
|
||||||
|
check((0,1,1, 0,0,0), Duration::min_value(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue