diff --git a/CHANGELOG.md b/CHANGELOG.md index b5762b6..334df09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,16 @@ This project adheres to [Semantic Versioning](https://semver.org). ## [Unreleased] +- Add `Duration::{as_secs_f64, as_secs_f32, from_secs_f64, from_secs_f32, + mul_f64, mul_f32, div_f64, div_f32}` methods. + They are based on [`duration_float`](https://github.com/rust-lang/rust/issues/54361) + feature of the standard library that stabilized on Rust 1.38. + - Make `Duration::{as_secs, subsec_millis, subsec_micros, subsec_nanos, as_millis, as_micros, as_nanos, is_some, is_none, unwrap_or}` const function on rustc 1.46+. -- Make `Instant::{is_some, is_none, unwrap_or}` const function on rustc 1.46+. +- Make `Instant::{is_some, is_none, unwrap_or}` const function on Rust 1.46+. - Implement `TryFrom` for `easytime::Instant` and `easytime::Duration`. With this change, the minimum required version of `easytime` with `--no-default-features` goes up to Rust 1.34 (the minimum required version of the default feature has not changed). diff --git a/README.md b/README.md index ac0f6fe..2b682d0 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ fn foo(secs: u64, nanos: u32, instant: Instant) -> Option { let now = Instant::now(); let secs = Duration::from_secs(secs); - let nanos = Duration::from_nanos(u64::from(nanos)); + let nanos = Duration::from_nanos(nanos as u64); let dur = secs.checked_add(nanos)?; now.checked_duration_since(instant)?.checked_sub(dur) diff --git a/src/duration.rs b/src/duration.rs index 51b53ec..f30eb29 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -8,6 +8,8 @@ use core::{ use super::{pair_and_then, TryFromTimeError}; +const NANOS_PER_SEC: u32 = 1_000_000_000; + /// A `Duration` type to represent a span of time, typically used for system /// timeouts. /// @@ -28,7 +30,6 @@ pub struct Duration(pub(crate) Option); impl Duration { // TODO: duration_constants https://github.com/rust-lang/rust/issues/57391 // TODO: duration_zero https://github.com/rust-lang/rust/issues/73544 - // TODO: duration_float https://github.com/rust-lang/rust/issues/54361 // TODO: div_duration https://github.com/rust-lang/rust/issues/63139 /// Creates a new `Duration` from the specified number of whole seconds and @@ -39,7 +40,7 @@ impl Duration { #[inline] pub fn new(secs: u64, nanos: u32) -> Self { let secs = time::Duration::from_secs(secs); - let nanos = time::Duration::from_nanos(u64::from(nanos)); + let nanos = time::Duration::from_nanos(nanos as u64); Self(secs.checked_add(nanos)) } @@ -154,6 +155,174 @@ impl Duration { } } + /// Returns the number of seconds contained by this `Duration` as `f64`. + /// + /// The returned value does include the fractional (nanosecond) part of the duration. + /// + /// # Examples + /// + /// ``` + /// use easytime::Duration; + /// + /// let dur = Duration::new(2, 700_000_000); + /// assert_eq!(dur.as_secs_f64(), Some(2.7)); + /// ``` + #[inline] + pub fn as_secs_f64(&self) -> Option { + // TODO: replace with `self.0.as_ref().map(time::Duration::as_secs_f64)` on Rust 1.38+. + self.0.map(|this| { + (this.as_secs() as f64) + (this.subsec_nanos() as f64) / (NANOS_PER_SEC as f64) + }) + } + + /// Returns the number of seconds contained by this `Duration` as `f32`. + /// + /// The returned value does include the fractional (nanosecond) part of the duration. + /// + /// # Examples + /// + /// ``` + /// use easytime::Duration; + /// + /// let dur = Duration::new(2, 700_000_000); + /// assert_eq!(dur.as_secs_f32(), Some(2.7)); + /// ``` + #[inline] + pub fn as_secs_f32(&self) -> Option { + // TODO: replace with `self.0.as_ref().map(time::Duration::as_secs_f32)` on Rust 1.38+. + self.0.map(|this| { + (this.as_secs() as f32) + (this.subsec_nanos() as f32) / (NANOS_PER_SEC as f32) + }) + } + + /// Creates a new `Duration` from the specified number of seconds represented + /// as `f64`. + /// + /// # Examples + /// + /// ``` + /// use easytime::Duration; + /// + /// let dur = Duration::from_secs_f64(2.7); + /// assert_eq!(dur, Duration::new(2, 700_000_000)); + /// ``` + #[inline] + pub fn from_secs_f64(secs: f64) -> Self { + const MAX_NANOS_F64: f64 = + ((u64::max_value() as u128 + 1) * (NANOS_PER_SEC as u128)) as f64; + let nanos = secs * (NANOS_PER_SEC as f64); + if !nanos.is_finite() || nanos >= MAX_NANOS_F64 || nanos < 0.0 { + return Self(None); + } + let nanos = nanos as u128; + Self::new( + (nanos / (NANOS_PER_SEC as u128)) as u64, + (nanos % (NANOS_PER_SEC as u128)) as u32, + ) + } + + /// Creates a new `Duration` from the specified number of seconds represented + /// as `f32`. + /// + /// # Examples + /// + /// ``` + /// use easytime::Duration; + /// + /// let dur = Duration::from_secs_f32(2.7); + /// assert_eq!(dur, Duration::new(2, 700_000_000)); + /// ``` + #[inline] + pub fn from_secs_f32(secs: f32) -> Duration { + const MAX_NANOS_F32: f32 = + ((u64::max_value() as u128 + 1) * (NANOS_PER_SEC as u128)) as f32; + let nanos = secs * (NANOS_PER_SEC as f32); + if !nanos.is_finite() || nanos >= MAX_NANOS_F32 || nanos < 0.0 { + return Self(None); + } + let nanos = nanos as u128; + Self::new( + (nanos / (NANOS_PER_SEC as u128)) as u64, + (nanos % (NANOS_PER_SEC as u128)) as u32, + ) + } + + /// Multiplies `Duration` by `f64`. + /// + /// # Examples + /// + /// ``` + /// use easytime::Duration; + /// + /// let dur = Duration::new(2, 700_000_000); + /// assert_eq!(dur.mul_f64(3.14), Duration::new(8, 478_000_000)); + /// assert_eq!(dur.mul_f64(3.14e5), Duration::new(847_800, 0)); + /// ``` + #[inline] + pub fn mul_f64(self, rhs: f64) -> Duration { + self.as_secs_f64() + .map(|secs| Duration::from_secs_f64(rhs * secs)) + .unwrap_or_else(|| Self(None)) + } + + /// Multiplies `Duration` by `f32`. + /// + /// # Examples + /// + /// ``` + /// use easytime::Duration; + /// + /// let dur = Duration::new(2, 700_000_000); + /// // note that due to rounding errors result is slightly different + /// // from 8.478 and 847800.0 + /// assert_eq!(dur.mul_f32(3.14), Duration::new(8, 478_000_640)); + /// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847799, 969_120_256)); + /// ``` + #[inline] + pub fn mul_f32(self, rhs: f32) -> Duration { + self.as_secs_f32() + .map(|secs| Duration::from_secs_f32(rhs * secs)) + .unwrap_or_else(|| Self(None)) + } + + /// Divide `Duration` by `f64`. + /// + /// # Examples + /// + /// ``` + /// use easytime::Duration; + /// + /// let dur = Duration::new(2, 700_000_000); + /// assert_eq!(dur.div_f64(3.14), Duration::new(0, 859_872_611)); + /// // note that truncation is used, not rounding + /// assert_eq!(dur.div_f64(3.14e5), Duration::new(0, 8_598)); + /// ``` + #[inline] + pub fn div_f64(self, rhs: f64) -> Duration { + self.as_secs_f64() + .map(|secs| Duration::from_secs_f64(secs / rhs)) + .unwrap_or_else(|| Self(None)) + } + + /// Divide `Duration` by `f32`. + /// + /// # Examples + /// + /// ``` + /// use easytime::Duration; + /// + /// let dur = Duration::new(2, 700_000_000); + /// assert_eq!(dur.div_f64(3.14), Duration::new(0, 859_872_611)); + /// // note that truncation is used, not rounding + /// assert_eq!(dur.div_f64(3.14e5), Duration::new(0, 8_598)); + /// ``` + #[inline] + pub fn div_f32(self, rhs: f32) -> Duration { + self.as_secs_f32() + .map(|secs| Duration::from_secs_f32(secs / rhs)) + .unwrap_or_else(|| Self(None)) + } + // ============================================================================= // Option based method implementations diff --git a/src/lib.rs b/src/lib.rs index 0b849fd..ebe97f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ //! let now = Instant::now(); //! //! let secs = Duration::from_secs(secs); -//! let nanos = Duration::from_nanos(u64::from(nanos)); +//! let nanos = Duration::from_nanos(nanos as u64); //! //! let dur = secs.checked_add(nanos)?; //! now.checked_duration_since(instant)?.checked_sub(dur)