diff --git a/src/libstd/time/duration.rs b/src/libstd/time/duration.rs index c3adae8cff839..1642dc9737e87 100644 --- a/src/libstd/time/duration.rs +++ b/src/libstd/time/duration.rs @@ -13,195 +13,174 @@ #![experimental] use {fmt, i64}; +use from_str::FromStr; use ops::{Add, Sub, Mul, Div, Neg}; use option::{Option, Some, None}; use num; use num::{CheckedAdd, CheckedMul}; use result::{Result, Ok, Err}; +/// The number of milliseconds in a second. +const MILLIS_PER_SECOND: i64 = 1000; +/// The number of milliseconds in a minute. +const MILLIS_PER_MINUTE: i64 = 60 * MILLIS_PER_SECOND; +/// The number of milliseconds in an hour. +const MILLIS_PER_HOUR: i64 = 60 * MILLIS_PER_MINUTE; +/// The number of milliseconds in a day. +const MILLIS_PER_DAY: i64 = 24 * MILLIS_PER_HOUR; +/// The number of milliseconds in a week. +const MILLIS_PER_WEEK: i64 = 7 * MILLIS_PER_DAY; + +/// The number of microseconds in a millisecond. +const MICROS_PER_MILLI: i64 = 1000; /// The number of nanoseconds in a microsecond. -const NANOS_PER_MICRO: i32 = 1000; +const NANOS_PER_MICRO: i64 = 1000; /// The number of nanoseconds in a millisecond. -const NANOS_PER_MILLI: i32 = 1000_000; -/// The number of nanoseconds in seconds. -const NANOS_PER_SEC: i32 = 1_000_000_000; -/// The number of microseconds per second. -const MICROS_PER_SEC: i64 = 1000_000; -/// The number of milliseconds per second. -const MILLIS_PER_SEC: i64 = 1000; -/// The number of seconds in a minute. -const SECS_PER_MINUTE: i64 = 60; -/// The number of seconds in an hour. -const SECS_PER_HOUR: i64 = 3600; -/// The number of (non-leap) seconds in days. -const SECS_PER_DAY: i64 = 86400; -/// The number of (non-leap) seconds in a week. -const SECS_PER_WEEK: i64 = 604800; - -macro_rules! try_opt( - ($e:expr) => (match $e { Some(v) => v, None => return None }) -) +const NANOS_PER_MILLI: i64 = 1000_000; +const OUT_OF_BOUNDS: &'static str = "Duration out of bounds"; -/// ISO 8601 time duration with nanosecond precision. -/// This also allows for the negative duration; see individual methods for details. -#[deriving(Clone, PartialEq, Eq, PartialOrd, Ord)] +/// An absolute amount of time, independent of time zones and calendars with nanosecond precision. +/// A duration can express the positive or negative difference between two instants in time +/// according to a particular clock. +#[deriving(Clone, PartialEq, Eq, PartialOrd, Ord, Zero, Default, Hash)] pub struct Duration { - secs: i64, - nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC + millis: i64, // Milliseconds + nanos: i32, // Nanoseconds, |nanos| < NANOS_PER_MILLI } -/// The minimum possible `Duration`: `i64::MIN` milliseconds. -pub const MIN: Duration = Duration { - secs: i64::MIN / MILLIS_PER_SEC - 1, - nanos: NANOS_PER_SEC + (i64::MIN % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI -}; +macro_rules! try_opt( + ($e:expr) => (match $e { Some(v) => v, None => return None }) +) +macro_rules! duration( + ($millis:expr, $nanos:expr) => (Duration { millis: $millis, nanos: $nanos }) +) +macro_rules! carry( + ($millis:expr, $nanos:expr) => ( + if $millis > 0 && $nanos < 0 { + (-1, NANOS_PER_MILLI) + } + else if $millis < 0 && $nanos > 0 { + (1, -NANOS_PER_MILLI) + } + else { + (0, 0) + } + ) +) -/// The maximum possible `Duration`: `i64::MAX` milliseconds. -pub const MAX: Duration = Duration { - secs: i64::MAX / MILLIS_PER_SEC, - nanos: (i64::MAX % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI -}; +/// The minimum possible `Duration` (-P106751991167DT7H12M55.808999999S). +pub const MIN: Duration = duration!(i64::MIN, -NANOS_PER_MILLI as i32 + 1); +/// The maximum possible `Duration` (P106751991167DT7H12M55.807999999S). +pub const MAX: Duration = duration!(i64::MAX, NANOS_PER_MILLI as i32 - 1); impl Duration { - /// Makes a new `Duration` with given number of weeks. - /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60), with overflow checks. + /// Makes a new `Duration` with given number of weeks with overflow checks. /// Fails when the duration is out of bounds. #[inline] pub fn weeks(weeks: i64) -> Duration { - let secs = weeks.checked_mul(&SECS_PER_WEEK).expect("Duration::weeks out of bounds"); - Duration::seconds(secs) + duration!(weeks.checked_mul(&MILLIS_PER_WEEK).expect(OUT_OF_BOUNDS), 0) } - /// Makes a new `Duration` with given number of days. - /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. + /// Makes a new `Duration` with given number of days with overflow checks. /// Fails when the duration is out of bounds. #[inline] pub fn days(days: i64) -> Duration { - let secs = days.checked_mul(&SECS_PER_DAY).expect("Duration::days out of bounds"); - Duration::seconds(secs) + duration!(days.checked_mul(&MILLIS_PER_DAY).expect(OUT_OF_BOUNDS), 0) } - /// Makes a new `Duration` with given number of hours. - /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// Makes a new `Duration` with given number of hours with overflow checks. /// Fails when the duration is out of bounds. #[inline] pub fn hours(hours: i64) -> Duration { - let secs = hours.checked_mul(&SECS_PER_HOUR).expect("Duration::hours ouf of bounds"); - Duration::seconds(secs) + duration!(hours.checked_mul(&MILLIS_PER_HOUR).expect(OUT_OF_BOUNDS), 0) } - /// Makes a new `Duration` with given number of minutes. - /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// Makes a new `Duration` with given number of minutes with overflow checks. /// Fails when the duration is out of bounds. #[inline] pub fn minutes(minutes: i64) -> Duration { - let secs = minutes.checked_mul(&SECS_PER_MINUTE).expect("Duration::minutes out of bounds"); - Duration::seconds(secs) + duration!(minutes.checked_mul(&MILLIS_PER_MINUTE).expect(OUT_OF_BOUNDS), 0) } - /// Makes a new `Duration` with given number of seconds. - /// Fails when the duration is more than `i64::MAX` milliseconds - /// or less than `i64::MIN` milliseconds. + /// Makes a new `Duration` with given number of seconds with overflow checks. + /// Fails when the duration is out of bounds. #[inline] pub fn seconds(seconds: i64) -> Duration { - let d = Duration { secs: seconds, nanos: 0 }; - if d < MIN || d > MAX { - panic!("Duration::seconds out of bounds"); - } - d + duration!(seconds.checked_mul(&MILLIS_PER_SECOND).expect(OUT_OF_BOUNDS), 0) } /// Makes a new `Duration` with given number of milliseconds. #[inline] pub fn milliseconds(milliseconds: i64) -> Duration { - let (secs, millis) = div_mod_floor_64(milliseconds, MILLIS_PER_SEC); - let nanos = millis as i32 * NANOS_PER_MILLI; - Duration { secs: secs, nanos: nanos } + duration!(milliseconds, 0) } /// Makes a new `Duration` with given number of microseconds. #[inline] pub fn microseconds(microseconds: i64) -> Duration { - let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); - let nanos = micros as i32 * NANOS_PER_MICRO; - Duration { secs: secs, nanos: nanos } + duration!( + microseconds / MICROS_PER_MILLI, + (NANOS_PER_MICRO * (microseconds % MICROS_PER_MILLI)) as i32 + ) } /// Makes a new `Duration` with given number of nanoseconds. #[inline] - pub fn nanoseconds(nanos: i64) -> Duration { - let (secs, nanos) = div_mod_floor_64(nanos, NANOS_PER_SEC as i64); - Duration { secs: secs, nanos: nanos as i32 } + pub fn nanoseconds(nanoseconds: i64) -> Duration { + duration!(nanoseconds / NANOS_PER_MILLI, (nanoseconds % NANOS_PER_MILLI) as i32) } /// Returns the total number of whole weeks in the duration. #[inline] pub fn num_weeks(&self) -> i64 { - self.num_days() / 7 + self.millis / MILLIS_PER_WEEK } /// Returns the total number of whole days in the duration. + #[inline] pub fn num_days(&self) -> i64 { - self.num_seconds() / SECS_PER_DAY + self.millis / MILLIS_PER_DAY } /// Returns the total number of whole hours in the duration. #[inline] pub fn num_hours(&self) -> i64 { - self.num_seconds() / SECS_PER_HOUR + self.millis / MILLIS_PER_HOUR } /// Returns the total number of whole minutes in the duration. #[inline] pub fn num_minutes(&self) -> i64 { - self.num_seconds() / SECS_PER_MINUTE + self.millis / MILLIS_PER_MINUTE } /// Returns the total number of whole seconds in the duration. + #[inline] pub fn num_seconds(&self) -> i64 { - // If secs is negative, nanos should be subtracted from the duration. - if self.secs < 0 && self.nanos > 0 { - self.secs + 1 - } else { - self.secs - } + self.millis / MILLIS_PER_SECOND } - /// Returns the number of nanoseconds such that - /// `nanos_mod_sec() + num_seconds() * NANOS_PER_SEC` is the total number of - /// nanoseconds in the duration. - fn nanos_mod_sec(&self) -> i32 { - if self.secs < 0 && self.nanos > 0 { - self.nanos - NANOS_PER_SEC - } else { - self.nanos - } - } - - /// Returns the total number of whole milliseconds in the duration, + /// Returns the total number of milliseconds in the duration. + #[inline] pub fn num_milliseconds(&self) -> i64 { - // A proper Duration will not overflow, because MIN and MAX are defined - // such that the range is exactly i64 milliseconds. - let secs_part = self.num_seconds() * MILLIS_PER_SEC; - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MILLI; - secs_part + nanos_part as i64 + self.millis } - /// Returns the total number of whole microseconds in the duration, + /// Returns the total number of microseconds in the duration. /// or `None` on overflow (exceeding 2^63 microseconds in either direction). + #[inline] pub fn num_microseconds(&self) -> Option { - let secs_part = try_opt!(self.num_seconds().checked_mul(&MICROS_PER_SEC)); - let nanos_part = self.nanos_mod_sec() / NANOS_PER_MICRO; - secs_part.checked_add(&(nanos_part as i64)) + let micros = try_opt!(self.millis.checked_mul(&MICROS_PER_MILLI)); + micros.checked_add(&(self.nanos as i64/ NANOS_PER_MICRO)) } - /// Returns the total number of whole nanoseconds in the duration, + /// Returns the total number of nanoseconds in the duration. /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). + #[inline] pub fn num_nanoseconds(&self) -> Option { - let secs_part = try_opt!(self.num_seconds().checked_mul(&(NANOS_PER_SEC as i64))); - let nanos_part = self.nanos_mod_sec(); - secs_part.checked_add(&(nanos_part as i64)) + let nanos = try_opt!(self.millis.checked_mul(&NANOS_PER_MILLI)); + nanos.checked_add(&(self.nanos as i64)) } } @@ -210,169 +189,291 @@ impl num::Bounded for Duration { #[inline] fn max_value() -> Duration { MAX } } -impl num::Zero for Duration { - #[inline] - fn zero() -> Duration { - Duration { secs: 0, nanos: 0 } - } - - #[inline] - fn is_zero(&self) -> bool { - self.secs == 0 && self.nanos == 0 - } -} - impl Neg for Duration { #[inline] fn neg(&self) -> Duration { - if self.nanos == 0 { - Duration { secs: -self.secs, nanos: 0 } - } else { - Duration { secs: -self.secs - 1, nanos: NANOS_PER_SEC - self.nanos } - } + duration!(-self.millis, -self.nanos) } } impl Add for Duration { fn add(&self, rhs: &Duration) -> Duration { - let mut secs = self.secs + rhs.secs; - let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs += 1; - } - Duration { secs: secs, nanos: nanos } - } -} + let nanos_sum = self.nanos + rhs.nanos; -impl num::CheckedAdd for Duration { - fn checked_add(&self, rhs: &Duration) -> Option { - let mut secs = try_opt!(self.secs.checked_add(&rhs.secs)); - let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs = try_opt!(secs.checked_add(&1)); - } - let d = Duration { secs: secs, nanos: nanos }; - // Even if d is within the bounds of i64 seconds, - // it might still overflow i64 milliseconds. - if d < MIN || d > MAX { None } else { Some(d) } + let millis = self.millis + rhs.millis + (nanos_sum as i64) / NANOS_PER_MILLI; + let nanos = nanos_sum % (NANOS_PER_MILLI as i32); + + let (m, n) = carry!(millis, nanos); + + duration!(millis + m, nanos + n as i32) } } impl Sub for Duration { fn sub(&self, rhs: &Duration) -> Duration { - let mut secs = self.secs - rhs.secs; - let mut nanos = self.nanos - rhs.nanos; - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - Duration { secs: secs, nanos: nanos } + let nanos_sub = self.nanos - rhs.nanos; + + let millis = self.millis - rhs.millis + (nanos_sub as i64) / NANOS_PER_MILLI; + let nanos = nanos_sub % (NANOS_PER_MILLI as i32); + + let (m, n) = carry!(millis, nanos); + + duration!(millis + m, nanos + n as i32) } } +impl num::CheckedAdd for Duration { + fn checked_add(&self, rhs: &Duration) -> Option { + let nanos_sum = self.nanos + rhs.nanos; // cannot overflow + let millis_sum = try_opt!(self.millis.checked_add(&rhs.millis)); + + let millis = try_opt!(millis_sum.checked_add(&((nanos_sum as i64) / NANOS_PER_MILLI))); + let nanos = nanos_sum % (NANOS_PER_MILLI as i32); + + let (m, n) = carry!(millis, nanos); + + Some(duration!(try_opt!(millis.checked_add(&m)), nanos + n as i32)) + } +} + + impl num::CheckedSub for Duration { fn checked_sub(&self, rhs: &Duration) -> Option { - let mut secs = try_opt!(self.secs.checked_sub(&rhs.secs)); - let mut nanos = self.nanos - rhs.nanos; - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs = try_opt!(secs.checked_sub(&1)); - } - let d = Duration { secs: secs, nanos: nanos }; - // Even if d is within the bounds of i64 seconds, - // it might still overflow i64 milliseconds. - if d < MIN || d > MAX { None } else { Some(d) } + let nanos_sub = self.nanos - rhs.nanos; // cannot overflow + let millis_sub = try_opt!(self.millis.checked_sub(&rhs.millis)); + + let millis = try_opt!(millis_sub.checked_sub(&((nanos_sub as i64) / NANOS_PER_MILLI))); + let nanos = nanos_sub % (NANOS_PER_MILLI as i32); + + let (m, n) = carry!(millis, nanos); + + Some(duration!(try_opt!(millis.checked_add(&m)), nanos + n as i32)) } } impl Mul for Duration { fn mul(&self, rhs: &i32) -> Duration { - // Multiply nanoseconds as i64, because it cannot overflow that way. - let total_nanos = self.nanos as i64 * *rhs as i64; - let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64); - let secs = self.secs * *rhs as i64 + extra_secs; - Duration { secs: secs, nanos: nanos as i32 } + let nanos_mul = self.nanos as i64 * *rhs as i64; + + let millis = self.millis * *rhs as i64 + nanos_mul / NANOS_PER_MILLI; + let nanos = (nanos_mul % NANOS_PER_MILLI) as i32; + + duration!(millis, nanos) } } impl Div for Duration { fn div(&self, rhs: &i32) -> Duration { - let mut secs = self.secs / *rhs as i64; - let carry = self.secs - secs * *rhs as i64; - let extra_nanos = carry * NANOS_PER_SEC as i64 / *rhs as i64; - let mut nanos = self.nanos / *rhs + extra_nanos as i32; - if nanos >= NANOS_PER_SEC { - nanos -= NANOS_PER_SEC; - secs += 1; - } - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - Duration { secs: secs, nanos: nanos } + let millis = self.millis / *rhs as i64; + let nanos = ((self.millis % *rhs as i64)*NANOS_PER_MILLI + self.nanos as i64)/ *rhs as i64; + + duration!(millis + nanos / NANOS_PER_MILLI, (nanos % NANOS_PER_MILLI) as i32) } } impl fmt::Show for Duration { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // technically speaking, negative duration is not valid ISO 8601, - // but we need to print it anyway. - let (abs, sign) = if self.secs < 0 { (-*self, "-") } else { (*self, "") }; + try!(write!(f, "{}P", if self.millis < 0 || self.nanos < 0 { "-" } else { "" })); - let days = abs.secs / SECS_PER_DAY; - let secs = abs.secs - days * SECS_PER_DAY; - let hasdate = days != 0; - let hastime = (secs != 0 || abs.nanos != 0) || !hasdate; + let days = self.num_days(); + let mut rem = (self.millis - days * MILLIS_PER_DAY).abs(); + + let hours = rem / MILLIS_PER_HOUR; + rem -= hours * MILLIS_PER_HOUR; - try!(write!(f, "{}P", sign)); + let minutes = rem / MILLIS_PER_MINUTE; + rem -= minutes * MILLIS_PER_MINUTE; + + let seconds = rem / MILLIS_PER_SECOND; + rem = (rem - seconds * MILLIS_PER_SECOND) * NANOS_PER_MILLI + (self.nanos as i64).abs(); + + let hasdate = days != 0; + let hastime = (hours != 0 || minutes != 0 || seconds != 0 || rem != 0) || !hasdate; if hasdate { - try!(write!(f, "{}D", days)); + try!(write!(f, "{}D", days.abs())); } + if hastime { - if abs.nanos == 0 { - try!(write!(f, "T{}S", secs)); - } else if abs.nanos % NANOS_PER_MILLI == 0 { - try!(write!(f, "T{}.{:03}S", secs, abs.nanos / NANOS_PER_MILLI)); - } else if abs.nanos % NANOS_PER_MICRO == 0 { - try!(write!(f, "T{}.{:06}S", secs, abs.nanos / NANOS_PER_MICRO)); - } else { - try!(write!(f, "T{}.{:09}S", secs, abs.nanos)); + try!(write!(f, "T")); + + if hours != 0 { + try!(write!(f, "{}H", hours)); + } + + if minutes != 0 { + try!(write!(f, "{}M", minutes)); + } + + if rem == 0 { + try!(write!(f, "{}S", seconds)); + } + else if rem % NANOS_PER_MILLI == 0 { + try!(write!(f, "{}.{:03}S", seconds, rem / NANOS_PER_MILLI)); + } + else if rem % NANOS_PER_MICRO == 0 { + try!(write!(f, "{}.{:06}S", seconds, rem / NANOS_PER_MICRO)); + } + else { + try!(write!(f, "{}.{:09}S", seconds, rem)); } } + Ok(()) } } -// Copied from libnum -#[inline] -fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { - (div_floor_64(this, other), mod_floor_64(this, other)) -} +impl FromStr for Duration { + /// Parse a `Duration` from a string. + /// + /// Durations are represented by the format ±P[n]DT[n]H[n]M[n]S, where [n] is a decimal positive + /// number (last one possibly using a comma), and the remaining characters are case-insensitive. + /// + /// # Examples + /// + /// ```rust + /// assert_eq!(from_str::("-P106751991167DT7H12M55.808999999S"), Some(Duration::MIN)); + /// assert_eq!(from_str::("P106751991167DT7H12M55.807999999S"), Some(Duration::MAX)); + /// assert_eq!(from_str::("P31D"), Some(Duration::days(31))); + /// assert_eq!(from_str::("not even a Duration"), None); + /// ``` + #[inline] + fn from_str(source: &str) -> Option { + fn atoi(source: &str) -> (Option, i64, &str) { + let mut value: Option = None; + let mut scale = 1i64; + let mut s = source; + + while !s.is_empty() { + s = match s.slice_shift_char() { + (Some(c), rem) if '0' <= c && c <= '9' => { + let digit = (c as u8 - b'0') as i64; + + value = value.unwrap_or(0).checked_mul(&10); + + if value.is_none() { + break; + } + + value = value.unwrap().checked_add(&digit); + + if value.is_none() { + break; + } + + scale *= 10; + rem + }, + _ => break + } + } -#[inline] -fn div_floor_64(this: i64, other: i64) -> i64 { - match div_rem_64(this, other) { - (d, r) if (r > 0 && other < 0) - || (r < 0 && other > 0) => d - 1, - (d, _) => d, - } -} + (value, scale, s) + } -#[inline] -fn mod_floor_64(this: i64, other: i64) -> i64 { - match this % other { - r if (r > 0 && other < 0) - || (r < 0 && other > 0) => r + other, - r => r, - } -} + let source = source.trim_left(); + + // Sign + let (mut s, sign) = match source.slice_shift_char() { + (Some(c), rem) if c == '-' || c == '+' => (rem, c), + _ => (source, '+'), + }; + + // P - period + s = match s.slice_shift_char() { + (Some(c), rem) if c == 'P' || c == 'p' => rem, + _ => return None + }; + + // D - days + let (days, _, next) = atoi(s); + + if days.is_some() { + s = match next.slice_shift_char() { + (Some(c), rem) if c == 'D' || c == 'd' => rem, + _ => return None + }; + } + + // T - time + s = match s.slice_shift_char() { + (Some(c), rem) if c == 'T' || c == 't' => rem, + (None, _) if days.is_some() => s, + _ => return None + }; + + let (mut hours, mut minutes): (Option, Option) = (None, None); + let (mut seconds, mut nanos): (Option, Option) = (None, None); + + while !s.is_empty() { + let (value, scale, next) = atoi(s); + + if value.is_none() || next.is_empty() { + return None; + } -#[inline] -fn div_rem_64(this: i64, other: i64) -> (i64, i64) { - (this / other, this % other) + s = match next.slice_shift_char() { + (Some(c), rem) if (c == 'H' || c == 'h') + && hours.is_none() && minutes.is_none() + && seconds.is_none() && nanos.is_none() => { + hours = value; + rem + }, + (Some(c), rem) if (c == 'M' || c == 'm') + && minutes.is_none() && seconds.is_none() + && nanos.is_none() => { + minutes = value; + rem + }, + (Some(c), rem) if (c == '.') && seconds.is_none() && nanos.is_none() => { + seconds = value; + rem + }, + (Some(c), rem) if (c == 'S' || c == 's') + && (seconds.is_none() || nanos.is_none()) => { + if seconds.is_none() { + seconds = value; + } + else { + nanos = value.unwrap().checked_mul(&(MILLIS_PER_SECOND * NANOS_PER_MILLI)); + + if nanos.is_none() { + return None; + } + + nanos = Some(nanos.unwrap() / scale); + } + + rem + }, + _ => return None + } + }; + + let days_millis = try_opt!(days.unwrap_or(0).checked_mul(&MILLIS_PER_DAY)); + let hours_millis = try_opt!(hours.unwrap_or(0).checked_mul(&MILLIS_PER_HOUR)); + let minutes_millis = try_opt!(minutes.unwrap_or(0).checked_mul(&MILLIS_PER_MINUTE)); + let seconds_millis = try_opt!(seconds.unwrap_or(0).checked_mul(&MILLIS_PER_SECOND)); + + let mut millis = try_opt!(days_millis.checked_add(&hours_millis)); + + millis = try_opt!(millis.checked_add(&minutes_millis)); + millis = try_opt!(millis.checked_add(&seconds_millis)); + + let mut nanos = nanos.unwrap_or(0); + + if sign == '-' { + millis = -millis; + nanos = -nanos; + } + + Some( + duration!( + try_opt!(millis.checked_add(&(nanos / NANOS_PER_MILLI))), + (nanos % NANOS_PER_MILLI) as i32 + ) + ) + } } #[cfg(test)] @@ -552,9 +653,48 @@ mod tests { "P7DT6.543S".to_string()); assert_eq!(Duration::seconds(-86401).to_string(), "-P1DT1S".to_string()); assert_eq!(Duration::nanoseconds(-1).to_string(), "-PT0.000000001S".to_string()); + assert_eq!(MIN.to_string(), "-P106751991167DT7H12M55.808999999S".to_string()); + assert_eq!(MAX.to_string(), "P106751991167DT7H12M55.807999999S".to_string()); // the format specifier should have no effect on `Duration` assert_eq!(format!("{:30}", Duration::days(1) + Duration::milliseconds(2345)), "P1DT2.345S".to_string()); } + + #[test] + fn test_duration_from_str() { + assert_eq!(from_str::("not even a Duration"), None); + assert_eq!(from_str::("DT755S"), None); + assert_eq!(from_str::("P123T9S"), None); + assert_eq!(from_str::("PDT7S"), None); + assert_eq!(from_str::("P1D7S"), None); + assert_eq!(from_str::("P1D"), Some(Duration::days(1))); + assert_eq!(from_str::("+P2D"), Some(Duration::days(2))); + assert_eq!(from_str::("-P3D"), Some(Duration::days(-3))); + assert_eq!(from_str::("-PT5H"), Some(Duration::hours(-5))); + assert_eq!(from_str::("PT5H30M"), Some(Duration::seconds(19800))); + assert_eq!(from_str::("PT30M"), Some(Duration::minutes(30))); + assert_eq!(from_str::("PT30M5H"), None); + assert_eq!(from_str::("PT1M5S"), Some(Duration::seconds(65))); + assert_eq!(from_str::("PT5S1M"), None); + assert_eq!(from_str::("PT5S1H"), None); + assert_eq!(from_str::("PT1H2M5S"), Some(Duration::seconds(3725))); + assert_eq!(from_str::("PT1H1H2M5S"), None); + assert_eq!(from_str::("PT1H2M1H5S"), None); + assert_eq!(from_str::("PT1H2M5S1H"), None); + assert_eq!(from_str::("PT1H2M5.S"), None); + assert_eq!(from_str::("PT1H2M5.5S"), Some(Duration::milliseconds(3725500))); + assert_eq!(from_str::("PT1H2M5.05S"), Some(Duration::milliseconds(3725050))); + assert_eq!(from_str::("PT1H2M5.005S"), Some(Duration::milliseconds(3725005))); + assert_eq!(from_str::("PT1H2M5.0005S"), Some(duration!(3725000, 500000))); + assert_eq!(from_str::("PT1H2M5.00005S"), Some(duration!(3725000, 50000))); + assert_eq!(from_str::("PT1H2M5.000005S"), Some(duration!(3725000, 5000))); + assert_eq!(from_str::("PT1H2M5.0000005S"), Some(duration!(3725000, 500))); + assert_eq!(from_str::("PT1H2M5.00000005S"), Some(duration!(3725000, 50))); + assert_eq!(from_str::("PT1H2M5.000000005S"), Some(duration!(3725000, 5))); + assert_eq!(from_str::("PT1H2M5.500000005S"), Some(duration!(3725500, 5))); + assert_eq!(from_str::("-P106751991167DT7H12M55.808999999S"), Some(MIN)); + assert_eq!(from_str::("P106751991167DT7H12M55.807999999S"), Some(MAX)); + } } +