diff --git a/src/naive/date.rs b/src/naive/date.rs index 61bff19..cd900a5 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -86,6 +86,9 @@ const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 + (MIN_YEAR + 400_000) / 100 + (MIN_YEAR + 400_000) / 400 - 146097_000; +#[cfg(test)] // only used for testing, but duplicated in naive::datetime +const MAX_BITS: usize = 44; + /// ISO 8601 calendar date without timezone. /// Allows for every [proleptic Gregorian date](./index.html#calendar-date) /// from Jan 1, 262145 BCE to Dec 31, 262143 CE. @@ -109,6 +112,13 @@ fn test_date_bounds() { "`MIN` should have a year flag {:?}", calculated_min.of().flags()); assert!(MAX == calculated_max, "`MAX` should have a year flag {:?}", calculated_max.of().flags()); + + // let's also check that the entire range do not exceed 2^44 seconds + // (sometimes used for bounding `Duration` against overflow) + let maxsecs = (MAX - MIN).num_seconds(); + let maxsecs = maxsecs + 86401; // also take care of DateTime + assert!(maxsecs < (1 << MAX_BITS), + "The entire `NaiveDate` range somehow exceeds 2^{} seconds", MAX_BITS); } impl NaiveDate { @@ -795,8 +805,8 @@ impl NaiveDate { /// let d = NaiveDate::from_ymd(2015, 9, 5); /// assert_eq!(d.checked_add(Duration::days(40)), Some(NaiveDate::from_ymd(2015, 10, 15))); /// assert_eq!(d.checked_add(Duration::days(-40)), Some(NaiveDate::from_ymd(2015, 7, 27))); - /// assert_eq!(d.checked_add(Duration::days(1000_000_000)), None); - /// assert_eq!(d.checked_add(Duration::days(-1000_000_000)), None); + /// assert_eq!(d.checked_add(Duration::days(1_000_000_000)), None); + /// assert_eq!(d.checked_add(Duration::days(-1_000_000_000)), None); /// assert_eq!(MAX.checked_add(Duration::days(1)), None); /// ~~~~ pub fn checked_add(self, rhs: Duration) -> Option { @@ -826,8 +836,8 @@ impl NaiveDate { /// let d = NaiveDate::from_ymd(2015, 9, 5); /// assert_eq!(d.checked_sub(Duration::days(40)), Some(NaiveDate::from_ymd(2015, 7, 27))); /// assert_eq!(d.checked_sub(Duration::days(-40)), Some(NaiveDate::from_ymd(2015, 10, 15))); - /// assert_eq!(d.checked_sub(Duration::days(1000_000_000)), None); - /// assert_eq!(d.checked_sub(Duration::days(-1000_000_000)), None); + /// assert_eq!(d.checked_sub(Duration::days(1_000_000_000)), None); + /// assert_eq!(d.checked_sub(Duration::days(-1_000_000_000)), None); /// assert_eq!(MIN.checked_sub(Duration::days(1)), None); /// ~~~~ pub fn checked_sub(self, rhs: Duration) -> Option { diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index 1249611..661fe59 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -16,6 +16,14 @@ use naive::date::NaiveDate; use format::{Item, Numeric, Pad, Fixed}; use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems}; +/// The tight upper bound guarantees that a duration with `|Duration| >= 2^MAX_SECS_BITS` +/// will always overflow the addition with any date and time type. +/// +/// So why is this needed? `Duration::seconds(rhs)` may overflow, and we don't have +/// an alternative returning `Option` or `Result`. Thus we need some early bound to avoid +/// touching that call when we are already sure that it WILL overflow... +const MAX_SECS_BITS: usize = 44; + /// ISO 8601 combined date and time without timezone. /// /// # Example @@ -337,45 +345,133 @@ impl NaiveDateTime { /// Adds given `Duration` to the current date and time. /// + /// As a part of Chrono's [leap second handling](../time/index.html#leap-second-handling), + /// the addition assumes that **there is no leap second ever**, + /// except when the `NaiveDateTime` itself represents a leap second + /// in which case the assumption becomes that **there is exactly a single leap second ever**. + /// /// Returns `None` when it will result in overflow. + /// + /// # Example + /// + /// ~~~~ + /// use chrono::{NaiveDate, Duration}; + /// + /// let from_ymd = NaiveDate::from_ymd; + /// + /// let d = from_ymd(2016, 7, 8); + /// let hms = |h, m, s| d.and_hms(h, m, s); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::zero()), Some(hms(3, 5, 7))); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::seconds(1)), Some(hms(3, 5, 8))); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::seconds(-1)), Some(hms(3, 5, 6))); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::seconds(3600 + 60)), Some(hms(4, 6, 7))); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::seconds(86400)), + /// Some(from_ymd(2016, 7, 9).and_hms(3, 5, 7))); + /// + /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); + /// assert_eq!(hmsm(3, 5, 7, 980).checked_add(Duration::milliseconds(450)), + /// Some(hmsm(3, 5, 8, 430))); + /// ~~~~ + /// + /// Overflow returns `None`. + /// + /// ~~~~ + /// # use chrono::{NaiveDate, Duration}; + /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); + /// assert_eq!(hms(3, 5, 7).checked_add(Duration::days(1_000_000_000)), None); + /// ~~~~ + /// + /// Leap seconds are handled, + /// but the addition assumes that it is the only leap second happened. + /// + /// ~~~~ + /// # use chrono::{NaiveDate, Duration}; + /// # let from_ymd = NaiveDate::from_ymd; + /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); + /// let leap = hmsm(3, 5, 59, 1_300); + /// assert_eq!(leap.checked_add(Duration::zero()), Some(hmsm(3, 5, 59, 1_300))); + /// assert_eq!(leap.checked_add(Duration::milliseconds(-500)), Some(hmsm(3, 5, 59, 800))); + /// assert_eq!(leap.checked_add(Duration::milliseconds(500)), Some(hmsm(3, 5, 59, 1_800))); + /// assert_eq!(leap.checked_add(Duration::milliseconds(800)), Some(hmsm(3, 6, 0, 100))); + /// assert_eq!(leap.checked_add(Duration::seconds(10)), Some(hmsm(3, 6, 9, 300))); + /// assert_eq!(leap.checked_add(Duration::seconds(-10)), Some(hmsm(3, 5, 50, 300))); + /// assert_eq!(leap.checked_add(Duration::days(1)), + /// Some(from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300))); + /// ~~~~ pub fn checked_add(self, rhs: Duration) -> Option { - // 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 (time, rhs) = self.time.overflowing_add(rhs); - 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()); + // early checking to avoid overflow in Duration::seconds + if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { + return None; } + + let date = try_opt!(self.date.checked_add(Duration::seconds(rhs))); Some(NaiveDateTime { date: date, time: time }) } /// Subtracts given `Duration` from the current date and time. /// + /// As a part of Chrono's [leap second handling](../time/index.html#leap-second-handling), + /// the subtraction assumes that **there is no leap second ever**, + /// except when the `NaiveDateTime` itself represents a leap second + /// in which case the assumption becomes that **there is exactly a single leap second ever**. + /// /// Returns `None` when it will result in overflow. + /// + /// # Example + /// + /// ~~~~ + /// use chrono::{NaiveDate, Duration}; + /// + /// let from_ymd = NaiveDate::from_ymd; + /// + /// let d = from_ymd(2016, 7, 8); + /// let hms = |h, m, s| d.and_hms(h, m, s); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::zero()), Some(hms(3, 5, 7))); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::seconds(1)), Some(hms(3, 5, 6))); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::seconds(-1)), Some(hms(3, 5, 8))); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::seconds(3600 + 60)), Some(hms(2, 4, 7))); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::seconds(86400)), + /// Some(from_ymd(2016, 7, 7).and_hms(3, 5, 7))); + /// + /// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); + /// assert_eq!(hmsm(3, 5, 7, 450).checked_sub(Duration::milliseconds(670)), + /// Some(hmsm(3, 5, 6, 780))); + /// ~~~~ + /// + /// Overflow returns `None`. + /// + /// ~~~~ + /// # use chrono::{NaiveDate, Duration}; + /// # let hms = |h, m, s| NaiveDate::from_ymd(2016, 7, 8).and_hms(h, m, s); + /// assert_eq!(hms(3, 5, 7).checked_sub(Duration::days(1_000_000_000)), None); + /// ~~~~ + /// + /// Leap seconds are handled, + /// but the subtraction assumes that it is the only leap second happened. + /// + /// ~~~~ + /// # use chrono::{NaiveDate, Duration}; + /// # let from_ymd = NaiveDate::from_ymd; + /// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); + /// let leap = hmsm(3, 5, 59, 1_300); + /// assert_eq!(leap.checked_sub(Duration::zero()), Some(hmsm(3, 5, 59, 1_300))); + /// assert_eq!(leap.checked_sub(Duration::milliseconds(200)), Some(hmsm(3, 5, 59, 1_100))); + /// assert_eq!(leap.checked_sub(Duration::milliseconds(500)), Some(hmsm(3, 5, 59, 800))); + /// assert_eq!(leap.checked_sub(Duration::seconds(60)), Some(hmsm(3, 5, 0, 300))); + /// assert_eq!(leap.checked_sub(Duration::days(1)), + /// Some(from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300))); + /// ~~~~ pub fn checked_sub(self, rhs: Duration) -> Option { - // 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 (time, rhs) = self.time.overflowing_sub(rhs); - 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()); + // early checking to avoid overflow in Duration::seconds + if rhs <= (-1 << MAX_SECS_BITS) || rhs >= (1 << MAX_SECS_BITS) { + return None; } + + let date = try_opt!(self.date.checked_sub(Duration::seconds(rhs))); Some(NaiveDateTime { date: date, time: time }) } @@ -947,6 +1043,55 @@ impl hash::Hash for NaiveDateTime { } } +/// An addition of `Duration` to `NaiveDateTime` yields another `NaiveDateTime`. +/// +/// As a part of Chrono's [leap second handling](../time/index.html#leap-second-handling), +/// the addition assumes that **there is no leap second ever**, +/// except when the `NaiveDateTime` itself represents a leap second +/// in which case the assumption becomes that **there is exactly a single leap second ever**. +/// +/// Panics on underflow or overflow. +/// Use [`NaiveDateTime::checked_add`](#method.checked_add) to detect that. +/// +/// # Example +/// +/// ~~~~ +/// use chrono::{NaiveDate, Duration}; +/// +/// let from_ymd = NaiveDate::from_ymd; +/// +/// let d = from_ymd(2016, 7, 8); +/// let hms = |h, m, s| d.and_hms(h, m, s); +/// assert_eq!(hms(3, 5, 7) + Duration::zero(), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) + Duration::seconds(1), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) + Duration::seconds(-1), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) + Duration::seconds(3600 + 60), hms(4, 6, 7)); +/// assert_eq!(hms(3, 5, 7) + Duration::seconds(86400), +/// from_ymd(2016, 7, 9).and_hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) + Duration::days(365), +/// from_ymd(2017, 7, 8).and_hms(3, 5, 7)); +/// +/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); +/// assert_eq!(hmsm(3, 5, 7, 980) + Duration::milliseconds(450), hmsm(3, 5, 8, 430)); +/// ~~~~ +/// +/// Leap seconds are handled, +/// but the addition assumes that it is the only leap second happened. +/// +/// ~~~~ +/// # use chrono::{NaiveDate, Duration}; +/// # let from_ymd = NaiveDate::from_ymd; +/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); +/// let leap = hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap + Duration::zero(), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap + Duration::milliseconds(-500), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap + Duration::milliseconds(500), hmsm(3, 5, 59, 1_800)); +/// assert_eq!(leap + Duration::milliseconds(800), hmsm(3, 6, 0, 100)); +/// assert_eq!(leap + Duration::seconds(10), hmsm(3, 6, 9, 300)); +/// assert_eq!(leap + Duration::seconds(-10), hmsm(3, 5, 50, 300)); +/// assert_eq!(leap + Duration::days(1), +/// from_ymd(2016, 7, 9).and_hms_milli(3, 5, 59, 300)); +/// ~~~~ impl Add for NaiveDateTime { type Output = NaiveDateTime; @@ -956,6 +1101,44 @@ impl Add for NaiveDateTime { } } +/// A subtraction of `NaiveDateTime` from `NaiveDateTime` yields a `Duration`. +/// This does not overflow or underflow at all. +/// +/// As a part of Chrono's [leap second handling](../time/index.html#leap-second-handling), +/// the subtraction assumes that **there is no leap second ever**, +/// except when any of the `NaiveDateTime`s themselves represents a leap second +/// in which case the assumption becomes that +/// **there are exactly one (or two) leap second(s) ever**. +/// +/// # Example +/// +/// ~~~~ +/// use chrono::{NaiveDate, Duration}; +/// +/// let from_ymd = NaiveDate::from_ymd; +/// +/// let d = from_ymd(2016, 7, 8); +/// assert_eq!(d.and_hms(3, 5, 7) - d.and_hms(2, 4, 6), +/// Duration::seconds(3600 + 60 + 1)); +/// +/// // July 8 is 190th day in the year 2016 +/// let d0 = from_ymd(2016, 1, 1); +/// assert_eq!(d.and_hms_milli(0, 7, 6, 500) - d0.and_hms(0, 0, 0), +/// Duration::seconds(189 * 86400 + 7 * 60 + 6) + Duration::milliseconds(500)); +/// ~~~~ +/// +/// Leap seconds are handled, but the subtraction assumes that +/// there were no other leap seconds happened. +/// +/// ~~~~ +/// # use chrono::{NaiveDate, Duration}; +/// # let from_ymd = NaiveDate::from_ymd; +/// let leap = from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); +/// assert_eq!(leap - from_ymd(2015, 6, 30).and_hms(23, 0, 0), +/// Duration::seconds(3600) + Duration::milliseconds(500)); +/// assert_eq!(from_ymd(2015, 7, 1).and_hms(1, 0, 0) - leap, +/// Duration::seconds(3600) - Duration::milliseconds(500)); +/// ~~~~ impl Sub for NaiveDateTime { type Output = Duration; @@ -964,6 +1147,54 @@ impl Sub for NaiveDateTime { } } +/// A subtraction of `Duration` from `NaiveDateTime` yields another `NaiveDateTime`. +/// It is same to the addition with a negated `Duration`. +/// +/// As a part of Chrono's [leap second handling](../time/index.html#leap-second-handling), +/// the addition assumes that **there is no leap second ever**, +/// except when the `NaiveDateTime` itself represents a leap second +/// in which case the assumption becomes that **there is exactly a single leap second ever**. +/// +/// Panics on underflow or overflow. +/// Use [`NaiveDateTime::checked_sub`](#method.checked_sub) to detect that. +/// +/// # Example +/// +/// ~~~~ +/// use chrono::{NaiveDate, Duration}; +/// +/// let from_ymd = NaiveDate::from_ymd; +/// +/// let d = from_ymd(2016, 7, 8); +/// let hms = |h, m, s| d.and_hms(h, m, s); +/// assert_eq!(hms(3, 5, 7) - Duration::zero(), hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) - Duration::seconds(1), hms(3, 5, 6)); +/// assert_eq!(hms(3, 5, 7) - Duration::seconds(-1), hms(3, 5, 8)); +/// assert_eq!(hms(3, 5, 7) - Duration::seconds(3600 + 60), hms(2, 4, 7)); +/// assert_eq!(hms(3, 5, 7) - Duration::seconds(86400), +/// from_ymd(2016, 7, 7).and_hms(3, 5, 7)); +/// assert_eq!(hms(3, 5, 7) - Duration::days(365), +/// from_ymd(2015, 7, 9).and_hms(3, 5, 7)); +/// +/// let hmsm = |h, m, s, milli| d.and_hms_milli(h, m, s, milli); +/// assert_eq!(hmsm(3, 5, 7, 450) - Duration::milliseconds(670), hmsm(3, 5, 6, 780)); +/// ~~~~ +/// +/// Leap seconds are handled, +/// but the subtraction assumes that it is the only leap second happened. +/// +/// ~~~~ +/// # use chrono::{NaiveDate, Duration}; +/// # let from_ymd = NaiveDate::from_ymd; +/// # let hmsm = |h, m, s, milli| from_ymd(2016, 7, 8).and_hms_milli(h, m, s, milli); +/// let leap = hmsm(3, 5, 59, 1_300); +/// assert_eq!(leap - Duration::zero(), hmsm(3, 5, 59, 1_300)); +/// assert_eq!(leap - Duration::milliseconds(200), hmsm(3, 5, 59, 1_100)); +/// assert_eq!(leap - Duration::milliseconds(500), hmsm(3, 5, 59, 800)); +/// assert_eq!(leap - Duration::seconds(60), hmsm(3, 5, 0, 300)); +/// assert_eq!(leap - Duration::days(1), +/// from_ymd(2016, 7, 7).and_hms_milli(3, 6, 0, 300)); +/// ~~~~ impl Sub for NaiveDateTime { type Output = NaiveDateTime; @@ -973,18 +1204,86 @@ impl Sub for NaiveDateTime { } } +/// The `Debug` output of the naive date and time `dt` is same to +/// [`dt.format("%Y-%m-%dT%H:%M:%S%.f")`](../../format/strftime/index.html). +/// +/// The string printed can be readily parsed via the `parse` method on `str`. +/// +/// It should be noted that, for leap seconds not on the minute boundary, +/// it may print a representation not distinguishable from non-leap seconds. +/// This doesn't matter in practice, since such leap seconds never happened. +/// (By the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment.) +/// +/// # Example +/// +/// ~~~~ +/// use chrono::NaiveDate; +/// +/// let dt = NaiveDate::from_ymd(2016, 11, 15).and_hms(7, 39, 24); +/// assert_eq!(format!("{:?}", dt), "2016-11-15T07:39:24"); +/// ~~~~ +/// +/// Leap seconds may also be used. +/// +/// ~~~~ +/// # use chrono::NaiveDate; +/// let dt = NaiveDate::from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); +/// assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60.500"); +/// ~~~~ impl fmt::Debug for NaiveDateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}T{:?}", self.date, self.time) } } +/// The `Debug` output of the naive date and time `dt` is same to +/// [`dt.format("%Y-%m-%d %H:%M:%S%.f")`](../../format/strftime/index.html). +/// +/// It should be noted that, for leap seconds not on the minute boundary, +/// it may print a representation not distinguishable from non-leap seconds. +/// This doesn't matter in practice, since such leap seconds never happened. +/// (By the time of the first leap second on 1972-06-30, +/// every time zone offset around the world has standardized to the 5-minute alignment.) +/// +/// # Example +/// +/// ~~~~ +/// use chrono::NaiveDate; +/// +/// let dt = NaiveDate::from_ymd(2016, 11, 15).and_hms(7, 39, 24); +/// assert_eq!(format!("{}", dt), "2016-11-15 07:39:24"); +/// ~~~~ +/// +/// Leap seconds may also be used. +/// +/// ~~~~ +/// # use chrono::NaiveDate; +/// let dt = NaiveDate::from_ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_500); +/// assert_eq!(format!("{}", dt), "2015-06-30 23:59:60.500"); +/// ~~~~ impl fmt::Display for NaiveDateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {}", self.date, self.time) } } +/// Parsing a `str` into a `NaiveDateTime` uses the same format, +/// [`%Y-%m-%dT%H:%M:%S%.f`](../../format/strftime/index.html), as in `Debug`. +/// +/// # Example +/// +/// ~~~~ +/// use chrono::{NaiveDateTime, NaiveDate}; +/// +/// let dt = NaiveDate::from_ymd(2015, 9, 18).and_hms(23, 56, 4); +/// assert_eq!("2015-09-18T23:56:04".parse::(), Ok(dt)); +/// +/// let dt = NaiveDate::from_ymd(12345, 6, 7).and_hms_milli(7, 59, 59, 1_500); // leap second +/// assert_eq!("+12345-6-7T7:59:60.5".parse::(), Ok(dt)); +/// +/// assert!("foo".parse::().is_err()); +/// ~~~~ impl str::FromStr for NaiveDateTime { type Err = ParseError; diff --git a/src/naive/time.rs b/src/naive/time.rs index 4c26533..c99b5ec 100644 --- a/src/naive/time.rs +++ b/src/naive/time.rs @@ -129,6 +129,13 @@ //! The leap second in the human-readable representation //! will be represented as the second part being 60, as required by ISO 8601. //! +//! ~~~~ +//! use chrono::{UTC, TimeZone}; +//! +//! let dt = UTC.ymd(2015, 6, 30).and_hms_milli(23, 59, 59, 1_000); +//! assert_eq!(format!("{:?}", dt), "2015-06-30T23:59:60Z"); +//! ~~~~ +//! //! There are hypothetical leap seconds not on the minute boundary //! nevertheless supported by Chrono. //! They are allowed for the sake of completeness and consistency; @@ -136,6 +143,17 @@ //! For such cases the human-readable representation is ambiguous //! and would be read back to the next non-leap second. //! +//! ~~~~ +//! use chrono::{DateTime, UTC, TimeZone}; +//! +//! let dt = UTC.ymd(2015, 6, 30).and_hms_milli(23, 56, 4, 1_000); +//! assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); +//! +//! let dt = UTC.ymd(2015, 6, 30).and_hms(23, 56, 5); +//! assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); +//! assert_eq!(DateTime::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt); +//! ~~~~ +//! //! Since Chrono alone cannot determine any existence of leap seconds, //! **there is absolutely no guarantee that the leap second read has actually happened**. @@ -497,6 +515,113 @@ impl NaiveTime { parsed.to_naive_time() } + /// Adds given `Duration` to the current time, + /// and also returns the number of *seconds* + /// in the integral number of days ignored from the addition. + /// (We cannot return `Duration` because it is subject to overflow or underflow.) + /// + /// # Example + /// + /// ~~~~ + /// use chrono::{NaiveTime, Duration}; + /// + /// let from_hms = NaiveTime::from_hms; + /// + /// assert_eq!(from_hms(3, 4, 5).overflowing_add(Duration::hours(11)), + /// (from_hms(14, 4, 5), 0)); + /// assert_eq!(from_hms(3, 4, 5).overflowing_add(Duration::hours(23)), + /// (from_hms(2, 4, 5), 86400)); + /// assert_eq!(from_hms(3, 4, 5).overflowing_add(Duration::hours(-7)), + /// (from_hms(20, 4, 5), -86400)); + /// ~~~~ + pub fn overflowing_add(&self, mut rhs: Duration) -> (NaiveTime, i64) { + let mut secs = self.secs; + let mut frac = self.frac; + + // check if `self` is a leap second and adding `rhs` would escape that leap second. + // if it's the case, update `self` and `rhs` to involve no leap second; + // otherwise the addition immediately finishes. + if frac >= 1_000_000_000 { + let rfrac = 2_000_000_000 - frac; + if rhs >= Duration::nanoseconds(rfrac as i64) { + rhs = rhs - Duration::nanoseconds(rfrac as i64); + secs += 1; + frac = 0; + } else if rhs < Duration::nanoseconds(-(frac as i64)) { + rhs = rhs + Duration::nanoseconds(frac as i64); + frac = 0; + } else { + frac = (frac as i64 + rhs.num_nanoseconds().unwrap()) as u32; + debug_assert!(frac < 2_000_000_000); + return (NaiveTime { secs: secs, frac: frac }, 0); + } + } + debug_assert!(secs <= 86400); + debug_assert!(frac < 1_000_000_000); + + let rhssecs = rhs.num_seconds(); + let rhsfrac = (rhs - Duration::seconds(rhssecs)).num_nanoseconds().unwrap(); + debug_assert!(Duration::seconds(rhssecs) + Duration::nanoseconds(rhsfrac) == rhs); + let rhssecsinday = rhssecs % 86400; + let mut morerhssecs = rhssecs - rhssecsinday; + let rhssecs = rhssecsinday as i32; + let rhsfrac = rhsfrac as i32; + debug_assert!(-86400 < rhssecs && rhssecs < 86400); + debug_assert!(morerhssecs % 86400 == 0); + debug_assert!(-1_000_000_000 < rhsfrac && rhsfrac < 1_000_000_000); + + let mut secs = secs as i32 + rhssecs; + let mut frac = frac as i32 + rhsfrac; + debug_assert!(-86400 < secs && secs < 2 * 86400); + debug_assert!(-1_000_000_000 < frac && frac < 2_000_000_000); + + if frac < 0 { + frac += 1_000_000_000; + secs -= 1; + } else if frac >= 1_000_000_000 { + frac -= 1_000_000_000; + secs += 1; + } + debug_assert!(-86400 <= secs && secs < 2 * 86400); + debug_assert!(0 <= frac && frac < 1_000_000_000); + + if secs < 0 { + secs += 86400; + morerhssecs -= 86400; + } else if secs >= 86400 { + secs -= 86400; + morerhssecs += 86400; + } + debug_assert!(0 <= secs && secs < 86400); + + (NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs) + } + + /// Subtracts given `Duration` from the current time, + /// and also returns the number of *seconds* + /// in the integral number of days ignored from the subtraction. + /// (We cannot return `Duration` because it is subject to overflow or underflow.) + /// + /// # Example + /// + /// ~~~~ + /// use chrono::{NaiveTime, Duration}; + /// + /// let from_hms = NaiveTime::from_hms; + /// + /// assert_eq!(from_hms(3, 4, 5).overflowing_sub(Duration::hours(2)), + /// (from_hms(1, 4, 5), 0)); + /// assert_eq!(from_hms(3, 4, 5).overflowing_sub(Duration::hours(17)), + /// (from_hms(10, 4, 5), 86400)); + /// assert_eq!(from_hms(3, 4, 5).overflowing_sub(Duration::hours(-22)), + /// (from_hms(1, 4, 5), -86400)); + /// ~~~~ + #[inline] + pub fn overflowing_sub(&self, rhs: Duration) -> (NaiveTime, i64) { + let (time, rhs) = self.overflowing_add(-rhs); + (time, -rhs) // safe to negate, rhs is within +/- (2^63 / 1000) + } + /// Formats the time with the specified formatting items. /// Otherwise it is same to the ordinary [`format`](#method.format) method. /// @@ -841,65 +966,9 @@ impl hash::Hash for NaiveTime { impl Add for NaiveTime { type Output = NaiveTime; - fn add(self, mut rhs: Duration) -> NaiveTime { - let mut secs = self.secs; - let mut frac = self.frac; - - // check if `self` is a leap second and adding `rhs` would escape that leap second. - // if it's the case, update `self` and `rhs` to involve no leap second; - // otherwise the addition immediately finishes. - if frac >= 1_000_000_000 { - let rfrac = 2_000_000_000 - frac; - if rhs >= Duration::nanoseconds(rfrac as i64) { - rhs = rhs - Duration::nanoseconds(rfrac as i64); - secs += 1; - frac = 0; - } else if rhs < Duration::nanoseconds(-(frac as i64)) { - rhs = rhs + Duration::nanoseconds(frac as i64); - frac = 0; - } else { - frac = (frac as i64 + rhs.num_nanoseconds().unwrap()) as u32; - debug_assert!(frac < 2_000_000_000); - return NaiveTime { secs: secs, frac: frac }; - } - } - debug_assert!(secs <= 86400); - debug_assert!(frac < 1_000_000_000); - - let rhssecs = rhs.num_seconds(); - let rhsfrac = (rhs - Duration::seconds(rhssecs)).num_nanoseconds().unwrap(); - debug_assert!(Duration::seconds(rhssecs) + Duration::nanoseconds(rhsfrac) == rhs); - let rhssecs = (rhssecs % 86400) as i32; - let rhsfrac = rhsfrac as i32; - debug_assert!(-86400 < rhssecs && rhssecs < 86400); - debug_assert!(-1_000_000_000 < rhsfrac && rhsfrac < 1_000_000_000); - - let mut secs = secs as i32 + rhssecs; - let mut frac = frac as i32 + rhsfrac; - debug_assert!(-86400 < secs && secs <= 2 * 86400 + 1); - debug_assert!(-1_000_000_000 < frac && frac < 2_000_000_000); - - if frac < 0 { - frac += 1_000_000_000; - secs -= 1; - } else if frac >= 1_000_000_000 { - frac -= 1_000_000_000; - secs += 1; - } - debug_assert!(-86400 <= secs && secs <= 2 * 86400 + 1); - debug_assert!(0 <= frac && frac < 1_000_000_000); - - if secs < 0 { - secs += 86400 * 2; - } - if secs >= 86400 * 2 { - secs -= 86400 * 2; - } else if secs >= 86400 { - secs -= 86400; - } - debug_assert!(0 <= secs && secs < 86400); - - NaiveTime { secs: secs as u32, frac: frac as u32 } + #[inline] + fn add(self, rhs: Duration) -> NaiveTime { + self.overflowing_add(rhs).0 } } @@ -907,7 +976,7 @@ impl Add for NaiveTime { /// This does not overflow or underflow at all. /// /// As a part of Chrono's [leap second handling](./index.html#leap-second-handling), -/// the addition assumes that **there is no leap second ever**, +/// the subtraction assumes that **there is no leap second ever**, /// except when any of the `NaiveTime`s themselves represents a leap second /// in which case the assumption becomes that /// **there are exactly one (or two) leap second(s) ever**. @@ -1021,7 +1090,9 @@ impl Sub for NaiveTime { type Output = NaiveTime; #[inline] - fn sub(self, rhs: Duration) -> NaiveTime { self.add(-rhs) } + fn sub(self, rhs: Duration) -> NaiveTime { + self.overflowing_sub(rhs).0 + } } /// The `Debug` output of the naive time `t` is same to @@ -1417,6 +1488,10 @@ mod tests { check!(hmsm(3, 5, 7, 900), Duration::zero(), hmsm(3, 5, 7, 900)); check!(hmsm(3, 5, 7, 900), Duration::milliseconds(100), hmsm(3, 5, 8, 0)); + check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-1800), hmsm(3, 5, 6, 500)); + check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-800), hmsm(3, 5, 7, 500)); + check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(-100), hmsm(3, 5, 7, 1_200)); + check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(100), hmsm(3, 5, 7, 1_400)); check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(800), hmsm(3, 5, 8, 100)); check!(hmsm(3, 5, 7, 1_300), Duration::milliseconds(1800), hmsm(3, 5, 9, 100)); check!(hmsm(3, 5, 7, 900), Duration::seconds(86399), hmsm(3, 5, 6, 900)); // overwrap @@ -1430,6 +1505,24 @@ mod tests { check!(hmsm(0, 0, 0, 0), Duration::milliseconds(-9990), hmsm(23, 59, 50, 10)); } + #[test] + fn test_time_overflowing_add() { + let hmsm = NaiveTime::from_hms_milli; + + assert_eq!(hmsm(3, 4, 5, 678).overflowing_add(Duration::hours(11)), + (hmsm(14, 4, 5, 678), 0)); + assert_eq!(hmsm(3, 4, 5, 678).overflowing_add(Duration::hours(23)), + (hmsm(2, 4, 5, 678), 86400)); + assert_eq!(hmsm(3, 4, 5, 678).overflowing_add(Duration::hours(-7)), + (hmsm(20, 4, 5, 678), -86400)); + + // overflowing_add with leap seconds may be counter-intuitive + assert_eq!(hmsm(3, 4, 5, 1_678).overflowing_add(Duration::days(1)), + (hmsm(3, 4, 5, 678), 86400)); + assert_eq!(hmsm(3, 4, 5, 1_678).overflowing_add(Duration::days(-1)), + (hmsm(3, 4, 6, 678), -86400)); + } + #[test] fn test_time_sub() { macro_rules! check {