Skip to content

Commit

Permalink
Implement fallible Time/Duration conversions.
Browse files Browse the repository at this point in the history
Resolves #131 and #150.
  • Loading branch information
Aehmlo authored and iliekturtles committed Sep 4, 2019
1 parent aadfa3e commit 97a27b5
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 6 deletions.
10 changes: 5 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ matrix:
rust: stable
script: |
set -e
cargo test --all --verbose --features "use_serde"
cargo test --manifest-path tests/edition_check/Cargo.toml --verbose --features "use_serde"
cargo test --all --verbose --features "use_serde try-from"
cargo test --manifest-path tests/edition_check/Cargo.toml --verbose --features "use_serde try-from"
cargo test --verbose --no-default-features --features "f32 si"
cargo test --verbose --no-default-features --features "autoconvert f32 si use_serde"
cargo test --verbose --no-run --no-default-features --features "autoconvert usize isize bigint bigrational si std use_serde"
cargo test --verbose --no-run --no-default-features --features "autoconvert usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 bigint biguint rational rational32 rational64 bigrational f32 f64 std use_serde"
cargo test --verbose --no-default-features --features "autoconvert f32 si use_serde try-from"
cargo test --verbose --no-run --no-default-features --features "autoconvert usize isize bigint bigrational si std use_serde try-from"
cargo test --verbose --no-run --no-default-features --features "autoconvert usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 bigint biguint rational rational32 rational64 bigrational f32 f64 std use_serde try-from"
- name: Rustfmt
rust: 1.35.0
install:
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ f32 = []
f64 = []
si = []
std = ["num-traits/std"]
# The `try-from` feature provides `impl TryFrom<Time> for Duration` and `impl TryFrom<Duration> for
# Time`. The `TryFrom` trait was stabilized in Rust 1.34.0 and will cause uom to fail to compile if
# enabled with an earlier version of Rust.
try-from = []
# The `use_serde` feature exists so that, in the future, other dependency features like num/serde
# can be added. However, num/serde is currently left out because it has not yet been updated to
# Serde 1.0. It is also necessary to name the feature something other than `serde` because of a
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ uom = {
"rational", "rational32", "rational64", "bigrational", # Integer ratio storage types.
"f32", "f64", # Floating point storage types.
"si", "std", # Built-in SI system and std library support.
"try-from", # `TryFrom` support between `Time` and `Duration`. Requires `rustc` 1.34.0.
"use_serde", # Serde support.
]
}
Expand All @@ -105,6 +106,8 @@ uom = {
default.
* `std` -- Feature to compile with standard library support. Disabling this feature compiles `uom`
with `no_std`. Enabled by default.
* `try-from` -- Feature to enable `TryFrom` support between `Time` and `Duration`. Requires `rustc`
1.34.0.
* `use_serde` -- Feature to enable support for serialization and deserialization of quantities
with the [Serde][serde] crate. Disabled by default.

Expand Down
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
//! "rational", "rational32", "rational64", "bigrational", # Integer ratio storage types.
//! "f32", "f64", # Floating point storage types.
//! "si", "std", # Built-in SI system and std library support.
//! "try-from", # `TryFrom` support between `Time` and `Duration`. Requires `rustc` 1.34.0.
//! "use_serde", # Serde support.
//! ]
//! }
Expand All @@ -95,6 +96,8 @@
//! default.
//! * `std` -- Feature to compile with standard library support. Disabling this feature compiles
//! `uom` with `no_std`. Enabled by default.
//! * `try-from` -- Feature to enable `TryFrom` support between `Time` and `Duration`. Requires
//! `rustc` 1.34.0.
//! * `use_serde` -- Feature to enable support for serialization and deserialization of quantities
//! with the [Serde][serde] crate. Disabled by default.
//!
Expand Down Expand Up @@ -248,7 +251,7 @@ pub mod num {
#[cfg(not(feature = "std"))]
pub use num_traits::float::FloatCore as Float;

pub use num_traits::{pow, FromPrimitive, Num, One, Saturating, Signed, Zero};
pub use num_traits::{pow, AsPrimitive, FromPrimitive, Num, One, Saturating, Signed, Zero};

#[cfg(feature = "bigint-support")]
pub use num_bigint::{BigInt, BigUint};
Expand Down
153 changes: 153 additions & 0 deletions src/si/time.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
//! Time (base unit second, s).
#[cfg(feature = "try-from")]
use lib::time::Duration;
#[cfg(feature = "try-from")]
use num::{AsPrimitive, FromPrimitive, Zero};

quantity! {
/// Time (base unit second, s).
quantity: Time; "time";
Expand Down Expand Up @@ -46,3 +51,151 @@ quantity! {
@year: 3.1536_E7; "a", "year", "years";
}
}

/// An error encountered converting between `Time` and `Duration`.
#[cfg(feature = "try-from")]
#[derive(Debug, Clone, Copy)]
pub enum TryFromError {
/// The given time interval was negative, making conversion to a duration nonsensical.
///
/// To convert a negative time interval to a duration, first use `abs` to make it positive.
NegativeDuration,

/// The given time interval exceeded the maximum size of a `Duration`.
Overflow,
}

/// Attempt to convert the given `Time` to a `Duration`.
///
/// For possible failure modes see [`TryFromError`][TryFromError].
///
/// ## Notes
///
/// The `Duration` to `Time` conversion is tested to be accurate to within 1 nanosecond (to allow
/// for floating point rounding error). If greater precision is needed, consider using a different
/// underlying storage type or avoiding the conversion altogether.
///
/// [TryFromError]: enum.TryFromError.html
#[cfg(feature = "try-from")]
impl<U, V> ::lib::convert::TryFrom<Time<U, V>> for Duration
where
U: ::si::Units<V> + ?Sized,
V: ::num::Num + ::Conversion<V> + ::lib::cmp::PartialOrd + AsPrimitive<u64> + AsPrimitive<u32>,
second: ::Conversion<V, T = V::T>,
nanosecond: ::Conversion<V, T = V::T>,
{
type Error = TryFromError;

fn try_from(time: Time<U, V>) -> Result<Self, Self::Error> {
if time < Time::<U, V>::zero() {
return Err(TryFromError::NegativeDuration);
}

let secs = time.get::<second>().as_();
let nanos = (time % Time::<U, V>::new::<second>(V::one())).get::<nanosecond>().as_();

Ok(Duration::new(secs, nanos))
}
}

/// Attempt to convert the given `Duration` to a `Time`.
///
/// For possible failure modes, see [`TryFromError`][TryFromError].
///
/// ## Notes
///
/// The `Duration` to `Time` conversion is tested to be accurate to within 100 nanoseconds (to
/// allow for floating point rounding error). If greater precision is needed, consider using a
/// different underlying storage type or avoiding the conversion altogether.
///
/// [TryFromError]: enum.TryFromError.html
#[cfg(feature = "try-from")]
impl<U, V> ::lib::convert::TryFrom<Duration> for Time<U, V>
where
U: ::si::Units<V> + ?Sized,
V: ::num::Num + ::Conversion<V> + FromPrimitive,
second: ::Conversion<V, T = V::T>,
nanosecond: ::Conversion<V, T = V::T>,
{
type Error = TryFromError;

fn try_from(duration: Duration) -> Result<Self, Self::Error> {
let secs = V::from_u64(duration.as_secs());
let nanos = V::from_u32(duration.subsec_micros());

match (secs, nanos) {
(Some(secs), Some(nanos)) => {
Ok(Time::<U, V>::new::<second>(secs) + Time::<U, V>::new::<nanosecond>(nanos))
}
_ => Err(TryFromError::Overflow),
}
}
}

#[cfg(all(test, feature = "try-from"))]
mod tests {
storage_types! {
use lib::convert::{TryFrom, TryInto};
use lib::time::Duration;
use num::{AsPrimitive, FromPrimitive, Zero};
use tests::*;
use si::quantities::*;
use si::time::nanosecond;
use si::time::convert::TryFromError;
use quickcheck::TestResult;

quickcheck! {
fn duration_try_from(v: A<V>) -> TestResult {
test_try_from(Duration::try_from(Time::new::<nanosecond>(*v)), v)
}

fn time_try_into(v: A<V>) -> TestResult {
test_try_from(Time::new::<nanosecond>(*v).try_into(), v)
}

fn time_try_from(v: A<V>) -> TestResult {
if *v < V::zero() {
return TestResult::discard();
}

test_try_into(Time::try_from(Duration::from_nanos(v.as_())), v)
}

fn duration_try_into(v: A<V>) -> TestResult {
if *v < V::zero() {
return TestResult::discard();
}

test_try_into(Duration::from_nanos(v.as_()).try_into(), v)
}
}

fn test_try_from(t: Result<Duration, TryFromError>, v: A<V>) -> TestResult {
let ok = match (t, *v >= V::zero()) {
(Ok(t), true) => {
let d = Duration::from_nanos(v.as_());
let r = if d > t { d - t } else { t - d };

Duration::from_nanos(1) >= r
},
(Err(_), false) => true,
_ => false,
};

TestResult::from_bool(ok)
}

fn test_try_into(t: Result<Time<V>, TryFromError>, v: A<V>) -> TestResult {
let ok = if let Ok(t) = t {
let b = Time::new::<nanosecond>(v.as_());
let d = if t > b { t - b } else { b - t };

d <= Time::new::<nanosecond>(V::from_u8(100).unwrap())
} else {
false
};

TestResult::from_bool(ok)
}
}
}
1 change: 1 addition & 0 deletions tests/edition_check/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ uom = { path = "../.." }
[features]
default = []
use_serde = ["uom/use_serde"]
try-from = ["uom/try-from"]
1 change: 1 addition & 0 deletions tests/feature_check/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ uom = { path = "../.." }
[features]
default = []
use_serde = ["uom/use_serde"]
try-from = ["uom/try-from"]

0 comments on commit 97a27b5

Please sign in to comment.