From b983240ef137c5c38a5554413381fb26d1c2e0f9 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Sat, 19 Nov 2016 19:06:13 -0700 Subject: Add TimeSpec, a Newtype around libc::timespec Also, add trait TimeValLike, so some code can be shared between TimeSpec and TimeVal. --- src/sys/time.rs | 356 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 310 insertions(+), 46 deletions(-) (limited to 'src/sys/time.rs') diff --git a/src/sys/time.rs b/src/sys/time.rs index 8340c9d0..21eb87bb 100644 --- a/src/sys/time.rs +++ b/src/sys/time.rs @@ -1,21 +1,270 @@ use std::{cmp, fmt, ops}; -use libc::{time_t, suseconds_t, timeval}; +use libc::{c_long, time_t, suseconds_t, timespec, timeval}; + +pub trait TimeValLike: Sized { + #[inline] + fn zero() -> Self { + Self::seconds(0) + } + + #[inline] + fn hours(hours: i64) -> Self { + let secs = hours.checked_mul(SECS_PER_HOUR) + .expect("TimeVal::hours ouf of bounds"); + Self::seconds(secs) + } + + #[inline] + fn minutes(minutes: i64) -> Self { + let secs = minutes.checked_mul(SECS_PER_MINUTE) + .expect("TimeVal::minutes out of bounds"); + Self::seconds(secs) + } + + fn seconds(seconds: i64) -> Self; + fn milliseconds(milliseconds: i64) -> Self; + fn microseconds(microseconds: i64) -> Self; + fn nanoseconds(nanoseconds: i64) -> Self; + + #[inline] + fn num_hours(&self) -> i64 { + self.num_seconds() / 3600 + } + + #[inline] + fn num_minutes(&self) -> i64 { + self.num_seconds() / 60 + } + + fn num_seconds(&self) -> i64; + fn num_milliseconds(&self) -> i64; + fn num_microseconds(&self) -> i64; + fn num_nanoseconds(&self) -> i64; +} #[repr(C)] #[derive(Clone, Copy)] -pub struct TimeVal(timeval); +pub struct TimeSpec(timespec); -const MICROS_PER_SEC: i64 = 1_000_000; +const NANOS_PER_SEC: i64 = 1_000_000_000; const SECS_PER_MINUTE: i64 = 60; const SECS_PER_HOUR: i64 = 3600; #[cfg(target_pointer_width = "64")] -const MAX_SECONDS: i64 = (::std::i64::MAX / MICROS_PER_SEC) - 1; +const TS_MAX_SECONDS: i64 = (::std::i64::MAX / NANOS_PER_SEC) - 1; #[cfg(target_pointer_width = "32")] -const MAX_SECONDS: i64 = ::std::isize::MAX as i64; +const TS_MAX_SECONDS: i64 = ::std::isize::MAX as i64; + +const TS_MIN_SECONDS: i64 = -TS_MAX_SECONDS; + + +impl fmt::Debug for TimeSpec { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("TimeSpec") + .field("tv_sec", &self.tv_sec()) + .field("tv_nsec", &self.tv_nsec()) + .finish() + } +} + +impl cmp::PartialEq for TimeSpec { + // The implementation of cmp is simplified by assuming that the struct is + // normalized. That is, tv_nsec must always be within [0, 1_000_000_000) + fn eq(&self, other: &TimeSpec) -> bool { + self.tv_sec() == other.tv_sec() && self.tv_nsec() == other.tv_nsec() + } +} + +impl cmp::Eq for TimeSpec {} + +impl cmp::Ord for TimeSpec { + // The implementation of cmp is simplified by assuming that the struct is + // normalized. That is, tv_nsec must always be within [0, 1_000_000_000) + fn cmp(&self, other: &TimeSpec) -> cmp::Ordering { + if self.tv_sec() == other.tv_sec() { + self.tv_sec().cmp(&other.tv_sec()) + } else { + self.tv_nsec().cmp(&other.tv_nsec()) + } + } +} -const MIN_SECONDS: i64 = -MAX_SECONDS; +impl cmp::PartialOrd for TimeSpec { + fn partial_cmp(&self, other: &TimeSpec) -> Option { + Some(self.cmp(other)) + } +} + +impl TimeValLike for TimeSpec { + #[inline] + fn seconds(seconds: i64) -> TimeSpec { + assert!(seconds >= TS_MIN_SECONDS && seconds <= TS_MAX_SECONDS, "TimeSpec out of bounds; seconds={}", seconds); + TimeSpec(timespec {tv_sec: seconds as time_t, tv_nsec: 0 }) + } + + #[inline] + fn milliseconds(milliseconds: i64) -> TimeSpec { + let nanoseconds = milliseconds.checked_mul(1_000_000) + .expect("TimeSpec::milliseconds out of bounds"); + + TimeSpec::nanoseconds(nanoseconds) + } + + /// Makes a new `TimeSpec` with given number of microseconds. + #[inline] + fn microseconds(microseconds: i64) -> TimeSpec { + let nanoseconds = microseconds.checked_mul(1_000) + .expect("TimeSpec::milliseconds out of bounds"); + + TimeSpec::nanoseconds(nanoseconds) + } + + /// Makes a new `TimeSpec` with given number of nanoseconds. + #[inline] + fn nanoseconds(nanoseconds: i64) -> TimeSpec { + let (secs, nanos) = div_mod_floor_64(nanoseconds, NANOS_PER_SEC); + assert!(secs >= TS_MIN_SECONDS && secs <= TS_MAX_SECONDS, "TimeSpec out of bounds"); + TimeSpec(timespec {tv_sec: secs as time_t, + tv_nsec: nanos as c_long }) + } + + fn num_seconds(&self) -> i64 { + if self.tv_sec() < 0 && self.tv_nsec() > 0 { + (self.tv_sec() + 1) as i64 + } else { + self.tv_sec() as i64 + } + } + + fn num_milliseconds(&self) -> i64 { + self.num_nanoseconds() / 1_000_000 + } + + fn num_microseconds(&self) -> i64 { + self.num_nanoseconds() / 1_000_000_000 + } + + fn num_nanoseconds(&self) -> i64 { + let secs = self.num_seconds() * 1_000_000_000; + let nsec = self.nanos_mod_sec(); + secs + nsec as i64 + } +} + +impl TimeSpec { + fn nanos_mod_sec(&self) -> c_long { + if self.tv_sec() < 0 && self.tv_nsec() > 0 { + self.tv_nsec() - NANOS_PER_SEC as c_long + } else { + self.tv_nsec() + } + } + + pub fn timespec(&self) -> timespec{ + self.0 + } + + pub fn tv_sec(&self) -> time_t { + self.0.tv_sec + } + + pub fn tv_nsec(&self) -> c_long { + self.0.tv_nsec + } +} + +impl ops::Neg for TimeSpec { + type Output = TimeSpec; + + fn neg(self) -> TimeSpec { + TimeSpec::nanoseconds(-self.num_nanoseconds()) + } +} + +impl ops::Add for TimeSpec { + type Output = TimeSpec; + + fn add(self, rhs: TimeSpec) -> TimeSpec { + TimeSpec::nanoseconds( + self.num_nanoseconds() + rhs.num_nanoseconds()) + } +} + +impl ops::Sub for TimeSpec { + type Output = TimeSpec; + + fn sub(self, rhs: TimeSpec) -> TimeSpec { + TimeSpec::nanoseconds( + self.num_nanoseconds() - rhs.num_nanoseconds()) + } +} + +impl ops::Mul for TimeSpec { + type Output = TimeSpec; + + fn mul(self, rhs: i32) -> TimeSpec { + let usec = self.num_nanoseconds().checked_mul(rhs as i64) + .expect("TimeSpec multiply out of bounds"); + + TimeSpec::nanoseconds(usec) + } +} + +impl ops::Div for TimeSpec { + type Output = TimeSpec; + + fn div(self, rhs: i32) -> TimeSpec { + let usec = self.num_nanoseconds() / rhs as i64; + TimeSpec::nanoseconds(usec) + } +} + +impl fmt::Display for TimeSpec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (abs, sign) = if self.tv_sec() < 0 { + (-*self, "-") + } else { + (*self, "") + }; + + let sec = abs.tv_sec(); + + try!(write!(f, "{}", sign)); + + if abs.tv_nsec() == 0 { + if abs.tv_sec() == 1 { + try!(write!(f, "{} second", sec)); + } else { + try!(write!(f, "{} seconds", sec)); + } + } else if abs.tv_nsec() % 1_000_000 == 0 { + try!(write!(f, "{}.{:03} seconds", sec, abs.tv_nsec() / 1_000_000)); + } else if abs.tv_nsec() % 1_000 == 0 { + try!(write!(f, "{}.{:06} seconds", sec, abs.tv_nsec() / 1_000)); + } else { + try!(write!(f, "{}.{:09} seconds", sec, abs.tv_nsec())); + } + + Ok(()) + } +} + + + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct TimeVal(timeval); + +const MICROS_PER_SEC: i64 = 1_000_000; + +#[cfg(target_pointer_width = "64")] +const TV_MAX_SECONDS: i64 = (::std::i64::MAX / MICROS_PER_SEC) - 1; + +#[cfg(target_pointer_width = "32")] +const TV_MAX_SECONDS: i64 = ::std::isize::MAX as i64; + +const TV_MIN_SECONDS: i64 = -TV_MAX_SECONDS; impl fmt::Debug for TimeVal { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { @@ -54,36 +303,15 @@ impl cmp::PartialOrd for TimeVal { } } -impl TimeVal { - #[inline] - pub fn zero() -> TimeVal { - TimeVal::microseconds(0) - } - - #[inline] - pub fn hours(hours: i64) -> TimeVal { - let secs = hours.checked_mul(SECS_PER_HOUR) - .expect("TimeVal::hours ouf of bounds"); - - TimeVal::seconds(secs) - } - - #[inline] - pub fn minutes(minutes: i64) -> TimeVal { - let secs = minutes.checked_mul(SECS_PER_MINUTE) - .expect("TimeVal::minutes out of bounds"); - - TimeVal::seconds(secs) - } - +impl TimeValLike for TimeVal { #[inline] - pub fn seconds(seconds: i64) -> TimeVal { - assert!(seconds >= MIN_SECONDS && seconds <= MAX_SECONDS, "TimeVal out of bounds; seconds={}", seconds); + fn seconds(seconds: i64) -> TimeVal { + assert!(seconds >= TV_MIN_SECONDS && seconds <= TV_MAX_SECONDS, "TimeVal out of bounds; seconds={}", seconds); TimeVal(timeval {tv_sec: seconds as time_t, tv_usec: 0 }) } #[inline] - pub fn milliseconds(milliseconds: i64) -> TimeVal { + fn milliseconds(milliseconds: i64) -> TimeVal { let microseconds = milliseconds.checked_mul(1_000) .expect("TimeVal::milliseconds out of bounds"); @@ -92,22 +320,25 @@ impl TimeVal { /// Makes a new `TimeVal` with given number of microseconds. #[inline] - pub fn microseconds(microseconds: i64) -> TimeVal { + fn microseconds(microseconds: i64) -> TimeVal { let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); - assert!(secs >= MIN_SECONDS && secs <= MAX_SECONDS, "TimeVal out of bounds"); + assert!(secs >= TV_MIN_SECONDS && secs <= TV_MAX_SECONDS, "TimeVal out of bounds"); TimeVal(timeval {tv_sec: secs as time_t, tv_usec: micros as suseconds_t }) } - pub fn num_hours(&self) -> i64 { - self.num_seconds() / 3600 - } - - pub fn num_minutes(&self) -> i64 { - self.num_seconds() / 60 + /// Makes a new `TimeVal` with given number of nanoseconds. Some precision + /// will be lost + #[inline] + fn nanoseconds(nanoseconds: i64) -> TimeVal { + let microseconds = nanoseconds / 1000; + let (secs, micros) = div_mod_floor_64(microseconds, MICROS_PER_SEC); + assert!(secs >= TV_MIN_SECONDS && secs <= TV_MAX_SECONDS, "TimeVal out of bounds"); + TimeVal(timeval {tv_sec: secs as time_t, + tv_usec: micros as suseconds_t }) } - pub fn num_seconds(&self) -> i64 { + fn num_seconds(&self) -> i64 { if self.tv_sec() < 0 && self.tv_usec() > 0 { (self.tv_sec() + 1) as i64 } else { @@ -115,16 +346,22 @@ impl TimeVal { } } - pub fn num_milliseconds(&self) -> i64 { + fn num_milliseconds(&self) -> i64 { self.num_microseconds() / 1_000 } - pub fn num_microseconds(&self) -> i64 { + fn num_microseconds(&self) -> i64 { let secs = self.num_seconds() * 1_000_000; let usec = self.micros_mod_sec(); secs + usec as i64 } + fn num_nanoseconds(&self) -> i64 { + self.num_microseconds() * 1_000 + } +} + +impl TimeVal { fn micros_mod_sec(&self) -> suseconds_t { if self.tv_sec() < 0 && self.tv_usec() > 0 { self.tv_usec() - MICROS_PER_SEC as suseconds_t @@ -250,10 +487,36 @@ fn div_rem_64(this: i64, other: i64) -> (i64, i64) { #[cfg(test)] mod test { - use super::TimeVal; + use super::{TimeSpec, TimeVal, TimeValLike}; + + #[test] + pub fn test_timespec() { + assert!(TimeSpec::seconds(1) != TimeSpec::zero()); + assert_eq!(TimeSpec::seconds(1) + TimeSpec::seconds(2), TimeSpec::seconds(3)); + assert_eq!(TimeSpec::minutes(3) + TimeSpec::seconds(2), + TimeSpec::seconds(182)); + } + + #[test] + pub fn test_timespec_neg() { + let a = TimeSpec::seconds(1) + TimeSpec::nanoseconds(123); + let b = TimeSpec::seconds(-1) + TimeSpec::nanoseconds(-123); + + assert_eq!(a, -b); + } + + #[test] + pub fn test_timespec_fmt() { + assert_eq!(TimeSpec::zero().to_string(), "0 seconds"); + assert_eq!(TimeSpec::seconds(42).to_string(), "42 seconds"); + assert_eq!(TimeSpec::milliseconds(42).to_string(), "0.042 seconds"); + assert_eq!(TimeSpec::microseconds(42).to_string(), "0.000042 seconds"); + assert_eq!(TimeSpec::nanoseconds(42).to_string(), "0.000000042 seconds"); + assert_eq!(TimeSpec::seconds(-86401).to_string(), "-86401 seconds"); + } #[test] - pub fn test_time_val() { + pub fn test_timeval() { assert!(TimeVal::seconds(1) != TimeVal::zero()); assert_eq!(TimeVal::seconds(1) + TimeVal::seconds(2), TimeVal::seconds(3)); assert_eq!(TimeVal::minutes(3) + TimeVal::seconds(2), @@ -261,7 +524,7 @@ mod test { } #[test] - pub fn test_time_val_neg() { + pub fn test_timeval_neg() { let a = TimeVal::seconds(1) + TimeVal::microseconds(123); let b = TimeVal::seconds(-1) + TimeVal::microseconds(-123); @@ -269,11 +532,12 @@ mod test { } #[test] - pub fn test_time_val_fmt() { + pub fn test_timeval_fmt() { assert_eq!(TimeVal::zero().to_string(), "0 seconds"); assert_eq!(TimeVal::seconds(42).to_string(), "42 seconds"); assert_eq!(TimeVal::milliseconds(42).to_string(), "0.042 seconds"); assert_eq!(TimeVal::microseconds(42).to_string(), "0.000042 seconds"); + assert_eq!(TimeVal::nanoseconds(1402).to_string(), "0.000001 seconds"); assert_eq!(TimeVal::seconds(-86401).to_string(), "-86401 seconds"); } } -- cgit v1.2.3