From 39133efebf49823002977f0ad0ac12edf27b4d06 Mon Sep 17 00:00:00 2001 From: Ruud van Asseldonk Date: Tue, 19 Aug 2014 14:32:20 +0200 Subject: [PATCH] libstd: Refactor Duration. This changes the internal representation of `Duration` from days: i32, secs: i32, nanos: u32 to secs: i64, nanos: i32 This resolves #16466. Some methods now take `i64` instead of `i32` due to the increased range. Some methods, like `num_milliseconds`, now return an `Option` instead of `i64`, because the range of `Duration` is now larger than e.g. 2^63 milliseconds. --- src/libstd/io/net/tcp.rs | 12 +- src/libstd/io/net/unix.rs | 10 +- src/libstd/io/timer.rs | 2 +- src/libstd/time/duration.rs | 468 +++++++++++++++--------------------- 4 files changed, 206 insertions(+), 286 deletions(-) diff --git a/src/libstd/io/net/tcp.rs b/src/libstd/io/net/tcp.rs index 7055b9d7a47..52d3a04432a 100644 --- a/src/libstd/io/net/tcp.rs +++ b/src/libstd/io/net/tcp.rs @@ -97,25 +97,31 @@ impl TcpStream { /// the specified duration. /// /// This is the same as the `connect` method, except that if the timeout - /// specified (in milliseconds) elapses before a connection is made an error - /// will be returned. The error's kind will be `TimedOut`. + /// specified elapses before a connection is made an error will be + /// returned. The error's kind will be `TimedOut`. /// /// Note that the `addr` argument may one day be split into a separate host /// and port, similar to the API seen in `connect`. /// /// If a `timeout` with zero or negative duration is specified then /// the function returns `Err`, with the error kind set to `TimedOut`. + /// If the timeout is larger than 2^63 milliseconds, the function also + /// returns `Err` with the error kind set to `TimedOut`. #[experimental = "the timeout argument may eventually change types"] pub fn connect_timeout(addr: SocketAddr, timeout: Duration) -> IoResult { if timeout <= Duration::milliseconds(0) { return Err(standard_error(TimedOut)); } + let timeout_ms = timeout.num_milliseconds().map(|x| { x as u64 }); + if timeout_ms.is_none() { + return Err(standard_error(TimedOut)); + } let SocketAddr { ip, port } = addr; let addr = rtio::SocketAddr { ip: super::to_rtio(ip), port: port }; LocalIo::maybe_raise(|io| { - io.tcp_connect(addr, Some(timeout.num_milliseconds() as u64)).map(TcpStream::new) + io.tcp_connect(addr, timeout_ms).map(TcpStream::new) }).map_err(IoError::from_rtio_error) } diff --git a/src/libstd/io/net/unix.rs b/src/libstd/io/net/unix.rs index eb251075418..179855003f9 100644 --- a/src/libstd/io/net/unix.rs +++ b/src/libstd/io/net/unix.rs @@ -61,20 +61,26 @@ impl UnixStream { /// Connect to a pipe named by `path`, timing out if the specified number of /// milliseconds. /// - /// This function is similar to `connect`, except that if `timeout_ms` + /// This function is similar to `connect`, except that if `timeout` /// elapses the function will return an error of kind `TimedOut`. /// /// If a `timeout` with zero or negative duration is specified then /// the function returns `Err`, with the error kind set to `TimedOut`. + /// If the timeout is larger than 2^63 milliseconds, the function also + /// returns `Err` with the error kind set to `TimedOut`. #[experimental = "the timeout argument is likely to change types"] pub fn connect_timeout(path: &P, timeout: Duration) -> IoResult { if timeout <= Duration::milliseconds(0) { return Err(standard_error(TimedOut)); } + let timeout_ms = timeout.num_milliseconds().map(|x| { x as u64 }); + if timeout_ms.is_none() { + return Err(standard_error(TimedOut)); + } LocalIo::maybe_raise(|io| { - let s = io.unix_connect(&path.to_c_str(), Some(timeout.num_milliseconds() as u64)); + let s = io.unix_connect(&path.to_c_str(), timeout_ms); s.map(|p| UnixStream { obj: p }) }).map_err(IoError::from_rtio_error) } diff --git a/src/libstd/io/timer.rs b/src/libstd/io/timer.rs index 39c6c74e45e..205132aca1d 100644 --- a/src/libstd/io/timer.rs +++ b/src/libstd/io/timer.rs @@ -225,7 +225,7 @@ impl Callback for TimerCallback { } fn in_ms_u64(d: Duration) -> u64 { - let ms = d.num_milliseconds(); + let ms = d.num_milliseconds().unwrap_or(0); if ms < 0 { return 0 }; return ms as u64; } diff --git a/src/libstd/time/duration.rs b/src/libstd/time/duration.rs index 155dfbc66c0..c2030ccceb0 100644 --- a/src/libstd/time/duration.rs +++ b/src/libstd/time/duration.rs @@ -12,227 +12,182 @@ #![experimental] -use {fmt, i32}; +use {fmt, i64}; use ops::{Add, Sub, Mul, Div, Neg}; use option::{Option, Some, None}; use num; use num::{CheckedAdd, CheckedMul}; use result::{Result, Ok, Err}; - -/// `Duration`'s `days` component should have no more than this value. -static MIN_DAYS: i32 = i32::MIN; -/// `Duration`'s `days` component should have no less than this value. -static MAX_DAYS: i32 = i32::MAX; - +/// The number of nanoseconds in a microsecond. +static NANOS_PER_MICRO: i32 = 1000; +/// The number of nanosecdons in a millisecond. +static NANOS_PER_MILLI: i32 = 1000_000; /// The number of nanoseconds in seconds. static NANOS_PER_SEC: i32 = 1_000_000_000; +/// The number of microseconds per second. +static MICROS_PER_SEC: i64 = 1000_000; +/// The number of milliseconds per second. +static MILLIS_PER_SEC: i64 = 1000; +/// The number of seconds in a minute. +static SECS_PER_MINUTE: i64 = 60; +/// The number of seconds in an hour. +static SECS_PER_HOUR: i64 = 3600; /// The number of (non-leap) seconds in days. -static SECS_PER_DAY: i32 = 86400; +static SECS_PER_DAY: i64 = 86400; +/// The number of (non-leap) seconds in a week. +static SECS_PER_WEEK: i64 = 604800; macro_rules! try_opt( ($e:expr) => (match $e { Some(v) => v, None => return None }) ) -// FIXME #16466: This could be represented as (i64 seconds, u32 nanos) /// 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)] pub struct Duration { - days: i32, - secs: u32, // Always < SECS_PER_DAY - nanos: u32, // Always < NANOS_PR_SECOND + secs: i64, + nanos: i32, // Always 0 <= nanos < NANOS_PER_SEC } /// The minimum possible `Duration`. -pub static MIN: Duration = Duration { days: MIN_DAYS, secs: 0, nanos: 0 }; +pub static MIN: Duration = Duration { secs: i64::MIN, nanos: 0 }; /// The maximum possible `Duration`. -pub static MAX: Duration = Duration { days: MAX_DAYS, secs: SECS_PER_DAY as u32 - 1, - nanos: NANOS_PER_SEC as u32 - 1 }; +pub static MAX: Duration = Duration { secs: i64::MAX, nanos: NANOS_PER_SEC - 1 }; impl Duration { /// Makes a new `Duration` with given number of weeks. - /// Equivalent to `Duration::new(weeks * 7, 0, 0)` with overflow checks. - /// + /// Equivalent to `Duration::seconds(weeks * 7 * 24 * 60 * 60), with overflow checks. /// Fails when the duration is out of bounds. #[inline] - pub fn weeks(weeks: i32) -> Duration { - let days = weeks.checked_mul(&7).expect("Duration::weeks out of bounds"); - Duration::days(days) + pub fn weeks(weeks: i64) -> Duration { + let secs = weeks.checked_mul(&SECS_PER_WEEK).expect("Duration::weeks out of bounds"); + Duration::seconds(secs) } /// Makes a new `Duration` with given number of days. - /// Equivalent to `Duration::new(days, 0, 0)`. + /// Equivalent to `Duration::seconds(days * 24 * 60 * 60)` with overflow checks. + /// Fails when the duration is out of bounds. #[inline] - pub fn days(days: i32) -> Duration { - Duration { days: days, secs: 0, nanos: 0 } + pub fn days(days: i64) -> Duration { + let secs = days.checked_mul(&SECS_PER_DAY).expect("Duration::days out of bounds"); + Duration::seconds(secs) } /// Makes a new `Duration` with given number of hours. - /// Equivalent to `Duration::new(0, hours * 3600, 0)` with overflow checks. + /// Equivalent to `Duration::seconds(hours * 60 * 60)` with overflow checks. + /// Fails when the duration is out of bounds. #[inline] - pub fn hours(hours: i32) -> Duration { - let (days, hours) = div_mod_floor(hours, (SECS_PER_DAY / 3600)); - let secs = hours * 3600; - Duration { secs: secs as u32, ..Duration::days(days) } + pub fn hours(hours: i64) -> Duration { + let secs = hours.checked_mul(&SECS_PER_HOUR).expect("Duration::hours ouf of bounds"); + Duration::seconds(secs) } /// Makes a new `Duration` with given number of minutes. - /// Equivalent to `Duration::new(0, mins * 60, 0)` with overflow checks. + /// Equivalent to `Duration::seconds(minutes * 60)` with overflow checks. + /// Fails when the duration is out of bounds. #[inline] - pub fn minutes(mins: i32) -> Duration { - let (days, mins) = div_mod_floor(mins, (SECS_PER_DAY / 60)); - let secs = mins * 60; - Duration { secs: secs as u32, ..Duration::days(days) } + pub fn minutes(minutes: i64) -> Duration { + let secs = minutes.checked_mul(&SECS_PER_MINUTE).expect("Duration::minutes out of bounds"); + Duration::seconds(secs) } /// Makes a new `Duration` with given number of seconds. - /// Equivalent to `Duration::new(0, secs, 0)`. #[inline] - pub fn seconds(secs: i32) -> Duration { - let (days, secs) = div_mod_floor(secs, SECS_PER_DAY); - Duration { secs: secs as u32, ..Duration::days(days) } + pub fn seconds(seconds: i64) -> Duration { + Duration { secs: seconds, nanos: 0 } } /// Makes a new `Duration` with given number of milliseconds. - /// Equivalent to `Duration::new(0, 0, millis * 1_000_000)` with overflow checks. #[inline] - pub fn milliseconds(millis: i32) -> Duration { - let (secs, millis) = div_mod_floor(millis, (NANOS_PER_SEC / 1_000_000)); - let nanos = millis * 1_000_000; - Duration { nanos: nanos as u32, ..Duration::seconds(secs) } + 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 } } /// Makes a new `Duration` with given number of microseconds. - /// Equivalent to `Duration::new(0, 0, micros * 1_000)` with overflow checks. #[inline] - pub fn microseconds(micros: i32) -> Duration { - let (secs, micros) = div_mod_floor(micros, (NANOS_PER_SEC / 1_000)); - let nanos = micros * 1_000; - Duration { nanos: nanos as u32, ..Duration::seconds(secs) } + 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 } } /// Makes a new `Duration` with given number of nanoseconds. - /// Equivalent to `Duration::new(0, 0, nanos)`. #[inline] - pub fn nanoseconds(nanos: i32) -> Duration { - let (secs, nanos) = div_mod_floor(nanos, NANOS_PER_SEC); - Duration { nanos: nanos as u32, ..Duration::seconds(secs) } - } - - /// Returns a tuple of the number of days, (non-leap) seconds and - /// nanoseconds in the duration. Note that the number of seconds - /// and nanoseconds are always positive, so that for example - /// `-Duration::seconds(3)` has -1 days and 86,397 seconds. - #[inline] - fn to_tuple_64(&self) -> (i64, u32, u32) { - (self.days as i64, self.secs, self.nanos) - } - - /// Negates the duration and returns a tuple like `to_tuple`. - /// This does not overflow and thus is internally used for several methods. - fn to_negated_tuple_64(&self) -> (i64, u32, u32) { - let mut days = -(self.days as i64); - let mut secs = -(self.secs as i32); - let mut nanos = -(self.nanos as i32); - if nanos < 0 { - nanos += NANOS_PER_SEC; - secs -= 1; - } - if secs < 0 { - secs += SECS_PER_DAY; - days -= 1; - } - (days, secs as u32, nanos as u32) + 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 } } /// Returns the total number of whole weeks in the duration. #[inline] - pub fn num_weeks(&self) -> i32 { + pub fn num_weeks(&self) -> i64 { self.num_days() / 7 } /// Returns the total number of whole days in the duration. - pub fn num_days(&self) -> i32 { - if self.days < 0 { - let negated = -*self; - -negated.days - } else { - self.days - } + pub fn num_days(&self) -> i64 { + self.num_seconds() / SECS_PER_DAY } /// Returns the total number of whole hours in the duration. #[inline] pub fn num_hours(&self) -> i64 { - self.num_seconds() / 3600 + self.num_seconds() / SECS_PER_HOUR } /// Returns the total number of whole minutes in the duration. #[inline] pub fn num_minutes(&self) -> i64 { - self.num_seconds() / 60 + self.num_seconds() / SECS_PER_MINUTE } /// Returns the total number of whole seconds in the duration. pub fn num_seconds(&self) -> i64 { - // cannot overflow, 2^32 * 86400 < 2^64 - fn secs((days, secs, _): (i64, u32, u32)) -> i64 { - days as i64 * SECS_PER_DAY as i64 + secs as 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 } - if self.days < 0 {-secs(self.to_negated_tuple_64())} else {secs(self.to_tuple_64())} } - /// Returns the total number of whole milliseconds in the duration. - pub fn num_milliseconds(&self) -> i64 { - // cannot overflow, 2^32 * 86400 * 1000 < 2^64 - fn millis((days, secs, nanos): (i64, u32, u32)) -> i64 { - static MILLIS_PER_SEC: i64 = 1_000; - static NANOS_PER_MILLI: i64 = 1_000_000; - (days as i64 * MILLIS_PER_SEC * SECS_PER_DAY as i64 + - secs as i64 * MILLIS_PER_SEC + - nanos as i64 / NANOS_PER_MILLI) + /// 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 } - if self.days < 0 {-millis(self.to_negated_tuple_64())} else {millis(self.to_tuple_64())} + } + + /// Returns the total number of whole milliseconds in the duration, + /// or `None` on overflow (exceeding 2^63 milliseconds in either direction). + pub fn num_milliseconds(&self) -> Option { + let secs_part = try_opt!(self.num_seconds().checked_mul(&MILLIS_PER_SEC)); + let nanos_part = self.nanos_mod_sec() / NANOS_PER_MILLI; + secs_part.checked_add(&(nanos_part as i64)) } /// Returns the total number of whole microseconds in the duration, - /// or `None` on the overflow (exceeding 2^63 microseconds in either directions). + /// or `None` on overflow (exceeding 2^63 microseconds in either direction). pub fn num_microseconds(&self) -> Option { - fn micros((days, secs, nanos): (i64, u32, u32)) -> Option { - static MICROS_PER_SEC: i64 = 1_000_000; - static MICROS_PER_DAY: i64 = MICROS_PER_SEC * SECS_PER_DAY as i64; - static NANOS_PER_MICRO: i64 = 1_000; - let nmicros = try_opt!((days as i64).checked_mul(&MICROS_PER_DAY)); - let nmicros = try_opt!(nmicros.checked_add(&(secs as i64 * MICROS_PER_SEC))); - let nmicros = try_opt!(nmicros.checked_add(&(nanos as i64 / NANOS_PER_MICRO as i64))); - Some(nmicros) - } - if self.days < 0 { - // the final negation won't overflow since we start with positive numbers. - micros(self.to_negated_tuple_64()).map(|micros| -micros) - } else { - micros(self.to_tuple_64()) - } + 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)) } /// Returns the total number of whole nanoseconds in the duration, - /// or `None` on the overflow (exceeding 2^63 nanoseconds in either directions). + /// or `None` on overflow (exceeding 2^63 nanoseconds in either direction). pub fn num_nanoseconds(&self) -> Option { - fn nanos((days, secs, nanos): (i64, u32, u32)) -> Option { - static NANOS_PER_DAY: i64 = NANOS_PER_SEC as i64 * SECS_PER_DAY as i64; - let nnanos = try_opt!((days as i64).checked_mul(&NANOS_PER_DAY)); - let nnanos = try_opt!(nnanos.checked_add(&(secs as i64 * NANOS_PER_SEC as i64))); - let nnanos = try_opt!(nnanos.checked_add(&(nanos as i64))); - Some(nnanos) - } - if self.days < 0 { - // the final negation won't overflow since we start with positive numbers. - nanos(self.to_negated_tuple_64()).map(|micros| -micros) - } else { - nanos(self.to_tuple_64()) - } + 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)) } } @@ -244,156 +199,130 @@ impl num::Bounded for Duration { impl num::Zero for Duration { #[inline] fn zero() -> Duration { - Duration { days: 0, secs: 0, nanos: 0 } + Duration { secs: 0, nanos: 0 } } #[inline] fn is_zero(&self) -> bool { - self.days == 0 && self.secs == 0 && self.nanos == 0 + self.secs == 0 && self.nanos == 0 } } impl Neg for Duration { #[inline] fn neg(&self) -> Duration { - let (days, secs, nanos) = self.to_negated_tuple_64(); - Duration { days: days as i32, secs: secs, nanos: nanos } // FIXME can overflow + if self.secs == i64::MIN && self.nanos == 0 { + // The minimum value cannot be negated due to overflow. Use the + // maximum value, which is one nanosecond less than the negated minimum. + MAX + } else if self.secs == i64::MIN { + Duration { secs: i64::MAX, nanos: NANOS_PER_SEC - self.nanos } + } else if self.nanos == 0 { + Duration { secs: -self.secs, nanos: 0 } + } else { + Duration { secs: -self.secs - 1, nanos: NANOS_PER_SEC - self.nanos } + } } } impl Add for Duration { fn add(&self, rhs: &Duration) -> Duration { - let mut days = self.days + rhs.days; let mut secs = self.secs + rhs.secs; let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC as u32 { - nanos -= NANOS_PER_SEC as u32; + if nanos >= NANOS_PER_SEC { + nanos -= NANOS_PER_SEC; secs += 1; } - if secs >= SECS_PER_DAY as u32 { - secs -= SECS_PER_DAY as u32; - days += 1; - } - Duration { days: days, secs: secs, nanos: nanos } + Duration { secs: secs, nanos: nanos } } } impl num::CheckedAdd for Duration { fn checked_add(&self, rhs: &Duration) -> Option { - let mut days = try_opt!(self.days.checked_add(&rhs.days)); - let mut secs = self.secs + rhs.secs; + let mut secs = try_opt!(self.secs.checked_add(&rhs.secs)); let mut nanos = self.nanos + rhs.nanos; - if nanos >= NANOS_PER_SEC as u32 { - nanos -= NANOS_PER_SEC as u32; - secs += 1; + if nanos >= NANOS_PER_SEC { + nanos -= NANOS_PER_SEC; + secs = try_opt!(secs.checked_add(&1)); } - if secs >= SECS_PER_DAY as u32 { - secs -= SECS_PER_DAY as u32; - days = try_opt!(days.checked_add(&1)); - } - Some(Duration { days: days, secs: secs, nanos: nanos }) + Some(Duration { secs: secs, nanos: nanos }) } } impl Sub for Duration { fn sub(&self, rhs: &Duration) -> Duration { - let mut days = self.days - rhs.days; - let mut secs = self.secs as i32 - rhs.secs as i32; - let mut nanos = self.nanos as i32 - rhs.nanos as i32; + let mut secs = self.secs - rhs.secs; + let mut nanos = self.nanos - rhs.nanos; if nanos < 0 { nanos += NANOS_PER_SEC; secs -= 1; } - if secs < 0 { - secs += SECS_PER_DAY; - days -= 1; - } - Duration { days: days, secs: secs as u32, nanos: nanos as u32 } + Duration { secs: secs, nanos: nanos } } } impl num::CheckedSub for Duration { fn checked_sub(&self, rhs: &Duration) -> Option { - let mut days = try_opt!(self.days.checked_sub(&rhs.days)); - let mut secs = self.secs as i32 - rhs.secs as i32; - let mut nanos = self.nanos as i32 - rhs.nanos as i32; + 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 -= 1; + secs = try_opt!(secs.checked_sub(&1)); } - if secs < 0 { - secs += SECS_PER_DAY; - days = try_opt!(days.checked_sub(&1)); - } - Some(Duration { days: days, secs: secs as u32, nanos: nanos as u32 }) + Some(Duration { secs: secs, nanos: nanos }) } } impl Mul for Duration { fn mul(&self, rhs: &i32) -> Duration { - /// Given `0 <= y < limit <= 2^30`, - /// returns `(h,l)` such that `x * y = h * limit + l` where `0 <= l < limit`. - fn mul_i64_u32_limit(x: i64, y: u32, limit: u32) -> (i64,u32) { - let y = y as i64; - let limit = limit as i64; - let (xh, xl) = div_mod_floor_64(x, limit); - let (h, l) = (xh * y, xl * y); - let (h_, l) = div_rem_64(l, limit); - (h + h_, l as u32) - } - - let rhs = *rhs as i64; - let (secs1, nanos) = mul_i64_u32_limit(rhs, self.nanos, NANOS_PER_SEC as u32); - let (days1, secs1) = div_mod_floor_64(secs1, (SECS_PER_DAY as i64)); - let (days2, secs2) = mul_i64_u32_limit(rhs, self.secs, SECS_PER_DAY as u32); - let mut days = self.days as i64 * rhs + days1 + days2; - let mut secs = secs1 as u32 + secs2; - if secs >= SECS_PER_DAY as u32 { - secs -= 1; - days += 1; - } - Duration { days: days as i32, secs: secs, nanos: nanos } + // 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 } } } impl Div for Duration { fn div(&self, rhs: &i32) -> Duration { - let (rhs, days, secs, nanos) = if *rhs < 0 { - let (days, secs, nanos) = self.to_negated_tuple_64(); - (-(*rhs as i64), days, secs as i64, nanos as i64) - } else { - (*rhs as i64, self.days as i64, self.secs as i64, self.nanos as i64) - }; - - let (days, carry) = div_mod_floor_64(days, rhs); - let secs = secs + carry * SECS_PER_DAY as i64; - let (secs, carry) = div_mod_floor_64(secs, rhs); - let nanos = nanos + carry * NANOS_PER_SEC as i64; - let nanos = nanos / rhs; - Duration { days: days as i32, secs: secs as u32, nanos: nanos as u32 } + 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 } } } impl fmt::Show for Duration { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let hasdate = self.days != 0; - let hastime = (self.secs != 0 || self.nanos != 0) || !hasdate; + let days = self.num_days(); + let secs = self.secs - days * SECS_PER_DAY; + let hasdate = days != 0; + let hastime = (secs != 0 || self.nanos != 0) || !hasdate; try!(write!(f, "P")); if hasdate { // technically speaking the negative part is not the valid ISO 8601, // but we need to print it anyway. - try!(write!(f, "{}D", self.days)); + try!(write!(f, "{}D", days)); } if hastime { if self.nanos == 0 { - try!(write!(f, "T{}S", self.secs)); - } else if self.nanos % 1_000_000 == 0 { - try!(write!(f, "T{}.{:03}S", self.secs, self.nanos / 1_000_000)); - } else if self.nanos % 1_000 == 0 { - try!(write!(f, "T{}.{:06}S", self.secs, self.nanos / 1_000)); + try!(write!(f, "T{}S", secs)); + } else if self.nanos % NANOS_PER_MILLI == 0 { + try!(write!(f, "T{}.{:03}S", secs, self.nanos / NANOS_PER_MILLI)); + } else if self.nanos % NANOS_PER_MICRO == 0 { + try!(write!(f, "T{}.{:06}S", secs, self.nanos / NANOS_PER_MICRO)); } else { - try!(write!(f, "T{}.{:09}S", self.secs, self.nanos)); + try!(write!(f, "T{}.{:09}S", secs, self.nanos)); } } Ok(()) @@ -401,34 +330,6 @@ impl fmt::Show for Duration { } // Copied from libnum -#[inline] -fn div_mod_floor(this: i32, other: i32) -> (i32, i32) { - (div_floor(this, other), mod_floor(this, other)) -} - -#[inline] -fn div_floor(this: i32, other: i32) -> i32 { - match div_rem(this, other) { - (d, r) if (r > 0 && other < 0) - || (r < 0 && other > 0) => d - 1, - (d, _) => d, - } -} - -#[inline] -fn mod_floor(this: i32, other: i32) -> i32 { - match this % other { - r if (r > 0 && other < 0) - || (r < 0 && other > 0) => r + other, - r => r, - } -} - -#[inline] -fn div_rem(this: i32, other: i32) -> (i32, i32) { - (this / other, this % other) -} - #[inline] fn div_mod_floor_64(this: i64, other: i64) -> (i64, i64) { (div_floor_64(this, other), mod_floor_64(this, other)) @@ -459,7 +360,7 @@ fn div_rem_64(this: i64, other: i64) -> (i64, i64) { #[cfg(test)] mod tests { - use super::{Duration, MIN_DAYS, MAX_DAYS, MIN, MAX}; + use super::{Duration, MIN, MAX}; use {i32, i64}; use num::{Zero, CheckedAdd, CheckedSub}; use option::{Some, None}; @@ -492,10 +393,8 @@ mod tests { assert_eq!(Duration::seconds(86401).num_days(), 1); assert_eq!(Duration::seconds(-86399).num_days(), 0); assert_eq!(Duration::seconds(-86401).num_days(), -1); - assert_eq!(Duration::days(i32::MAX).num_days(), i32::MAX); - assert_eq!(Duration::days(i32::MIN).num_days(), i32::MIN); - assert_eq!(MAX.num_days(), MAX_DAYS); - assert_eq!(MIN.num_days(), MIN_DAYS); + assert_eq!(Duration::days(i32::MAX as i64).num_days(), i32::MAX as i64); + assert_eq!(Duration::days(i32::MIN as i64).num_days(), i32::MIN as i64); } #[test] @@ -508,26 +407,26 @@ mod tests { assert_eq!(Duration::milliseconds(1001).num_seconds(), 1); assert_eq!(Duration::milliseconds(-999).num_seconds(), 0); assert_eq!(Duration::milliseconds(-1001).num_seconds(), -1); - assert_eq!(Duration::seconds(i32::MAX).num_seconds(), i32::MAX as i64); - assert_eq!(Duration::seconds(i32::MIN).num_seconds(), i32::MIN as i64); - assert_eq!(MAX.num_seconds(), (MAX_DAYS as i64 + 1) * 86400 - 1); - assert_eq!(MIN.num_seconds(), MIN_DAYS as i64 * 86400); + assert_eq!(Duration::seconds(i64::MAX).num_seconds(), i64::MAX); + assert_eq!(Duration::seconds(i64::MIN).num_seconds(), i64::MIN); + assert_eq!(MAX.num_seconds(), i64::MAX); + assert_eq!(MIN.num_seconds(), i64::MIN); } #[test] fn test_duration_num_milliseconds() { let d: Duration = Zero::zero(); - assert_eq!(d.num_milliseconds(), 0); - assert_eq!(Duration::milliseconds(1).num_milliseconds(), 1); - assert_eq!(Duration::milliseconds(-1).num_milliseconds(), -1); - assert_eq!(Duration::microseconds(999).num_milliseconds(), 0); - assert_eq!(Duration::microseconds(1001).num_milliseconds(), 1); - assert_eq!(Duration::microseconds(-999).num_milliseconds(), 0); - assert_eq!(Duration::microseconds(-1001).num_milliseconds(), -1); - assert_eq!(Duration::milliseconds(i32::MAX).num_milliseconds(), i32::MAX as i64); - assert_eq!(Duration::milliseconds(i32::MIN).num_milliseconds(), i32::MIN as i64); - assert_eq!(MAX.num_milliseconds(), (MAX_DAYS as i64 + 1) * 86400_000 - 1); - assert_eq!(MIN.num_milliseconds(), MIN_DAYS as i64 * 86400_000); + assert_eq!(d.num_milliseconds(), Some(0)); + assert_eq!(Duration::milliseconds(1).num_milliseconds(), Some(1)); + assert_eq!(Duration::milliseconds(-1).num_milliseconds(), Some(-1)); + assert_eq!(Duration::microseconds(999).num_milliseconds(), Some(0)); + assert_eq!(Duration::microseconds(1001).num_milliseconds(), Some(1)); + assert_eq!(Duration::microseconds(-999).num_milliseconds(), Some(0)); + assert_eq!(Duration::microseconds(-1001).num_milliseconds(), Some(-1)); + assert_eq!(Duration::milliseconds(i64::MAX).num_milliseconds(), Some(i64::MAX)); + assert_eq!(Duration::milliseconds(i64::MIN).num_milliseconds(), Some(i64::MIN)); + assert_eq!(MAX.num_milliseconds(), None); + assert_eq!(MIN.num_milliseconds(), None); } #[test] @@ -540,19 +439,19 @@ mod tests { assert_eq!(Duration::nanoseconds(1001).num_microseconds(), Some(1)); assert_eq!(Duration::nanoseconds(-999).num_microseconds(), Some(0)); assert_eq!(Duration::nanoseconds(-1001).num_microseconds(), Some(-1)); - assert_eq!(Duration::microseconds(i32::MAX).num_microseconds(), Some(i32::MAX as i64)); - assert_eq!(Duration::microseconds(i32::MIN).num_microseconds(), Some(i32::MIN as i64)); + assert_eq!(Duration::microseconds(i64::MAX).num_microseconds(), Some(i64::MAX)); + assert_eq!(Duration::microseconds(i64::MIN).num_microseconds(), Some(i64::MIN)); assert_eq!(MAX.num_microseconds(), None); assert_eq!(MIN.num_microseconds(), None); // overflow checks static MICROS_PER_DAY: i64 = 86400_000_000; - assert_eq!(Duration::days((i64::MAX / MICROS_PER_DAY) as i32).num_microseconds(), + assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY).num_microseconds(), Some(i64::MAX / MICROS_PER_DAY * MICROS_PER_DAY)); - assert_eq!(Duration::days((i64::MIN / MICROS_PER_DAY) as i32).num_microseconds(), + assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY).num_microseconds(), Some(i64::MIN / MICROS_PER_DAY * MICROS_PER_DAY)); - assert_eq!(Duration::days((i64::MAX / MICROS_PER_DAY + 1) as i32).num_microseconds(), None); - assert_eq!(Duration::days((i64::MIN / MICROS_PER_DAY - 1) as i32).num_microseconds(), None); + assert_eq!(Duration::days(i64::MAX / MICROS_PER_DAY + 1).num_microseconds(), None); + assert_eq!(Duration::days(i64::MIN / MICROS_PER_DAY - 1).num_microseconds(), None); } #[test] @@ -561,30 +460,30 @@ mod tests { assert_eq!(d.num_nanoseconds(), Some(0)); assert_eq!(Duration::nanoseconds(1).num_nanoseconds(), Some(1)); assert_eq!(Duration::nanoseconds(-1).num_nanoseconds(), Some(-1)); - assert_eq!(Duration::nanoseconds(i32::MAX).num_nanoseconds(), Some(i32::MAX as i64)); - assert_eq!(Duration::nanoseconds(i32::MIN).num_nanoseconds(), Some(i32::MIN as i64)); + assert_eq!(Duration::nanoseconds(i64::MAX).num_nanoseconds(), Some(i64::MAX)); + assert_eq!(Duration::nanoseconds(i64::MIN).num_nanoseconds(), Some(i64::MIN)); assert_eq!(MAX.num_nanoseconds(), None); assert_eq!(MIN.num_nanoseconds(), None); // overflow checks static NANOS_PER_DAY: i64 = 86400_000_000_000; - assert_eq!(Duration::days((i64::MAX / NANOS_PER_DAY) as i32).num_nanoseconds(), + assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY).num_nanoseconds(), Some(i64::MAX / NANOS_PER_DAY * NANOS_PER_DAY)); - assert_eq!(Duration::days((i64::MIN / NANOS_PER_DAY) as i32).num_nanoseconds(), + assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY).num_nanoseconds(), Some(i64::MIN / NANOS_PER_DAY * NANOS_PER_DAY)); - assert_eq!(Duration::days((i64::MAX / NANOS_PER_DAY + 1) as i32).num_nanoseconds(), None); - assert_eq!(Duration::days((i64::MIN / NANOS_PER_DAY - 1) as i32).num_nanoseconds(), None); + assert_eq!(Duration::days(i64::MAX / NANOS_PER_DAY + 1).num_nanoseconds(), None); + assert_eq!(Duration::days(i64::MIN / NANOS_PER_DAY - 1).num_nanoseconds(), None); } #[test] fn test_duration_checked_ops() { - assert_eq!(Duration::days(MAX_DAYS).checked_add(&Duration::seconds(86399)), - Some(Duration::days(MAX_DAYS - 1) + Duration::seconds(86400+86399))); - assert!(Duration::days(MAX_DAYS).checked_add(&Duration::seconds(86400)).is_none()); + assert_eq!(Duration::seconds(i64::MAX).checked_add(&Duration::milliseconds(999)), + Some(Duration::seconds(i64::MAX - 1) + Duration::milliseconds(1999))); + assert!(Duration::seconds(i64::MAX).checked_add(&Duration::milliseconds(1000)).is_none()); - assert_eq!(Duration::days(MIN_DAYS).checked_sub(&Duration::seconds(0)), - Some(Duration::days(MIN_DAYS))); - assert!(Duration::days(MIN_DAYS).checked_sub(&Duration::seconds(1)).is_none()); + assert_eq!(Duration::seconds(i64::MIN).checked_sub(&Duration::seconds(0)), + Some(Duration::seconds(i64::MIN))); + assert!(Duration::seconds(i64::MIN).checked_sub(&Duration::seconds(1)).is_none()); } #[test] @@ -601,6 +500,8 @@ mod tests { Duration::seconds(10) - Duration::nanoseconds(10)); assert_eq!((Duration::nanoseconds(1) + Duration::seconds(1) + Duration::days(1)) * 3, Duration::nanoseconds(3) + Duration::seconds(3) + Duration::days(3)); + assert_eq!(Duration::milliseconds(1500) * -2, Duration::seconds(-3)); + assert_eq!(Duration::milliseconds(-1500) * 2, Duration::seconds(-3)); } #[test] @@ -612,6 +513,13 @@ mod tests { assert_eq!(Duration::nanoseconds(123_456_789) / -1, -Duration::nanoseconds(123_456_789)); assert_eq!(-Duration::nanoseconds(123_456_789) / -1, Duration::nanoseconds(123_456_789)); assert_eq!(-Duration::nanoseconds(123_456_789) / 1, -Duration::nanoseconds(123_456_789)); + assert_eq!(Duration::seconds(1) / 3, Duration::nanoseconds(333_333_333)); + assert_eq!(Duration::seconds(4) / 3, Duration::nanoseconds(1_333_333_333)); + assert_eq!(Duration::seconds(-1) / 2, Duration::milliseconds(-500)); + assert_eq!(Duration::seconds(1) / -2, Duration::milliseconds(-500)); + assert_eq!(Duration::seconds(-1) / -2, Duration::milliseconds(500)); + assert_eq!(Duration::seconds(-4) / 3, Duration::nanoseconds(-1_333_333_333)); + assert_eq!(Duration::seconds(-4) / -3, Duration::nanoseconds(1_333_333_333)); } #[test]