Add SubSecondRound extension trait with impl for Timelike

This commit is contained in:
David Kellum 2018-01-30 12:12:16 -08:00
parent ff962d452c
commit b5acb0ed95
2 changed files with 165 additions and 0 deletions

View File

@ -409,6 +409,7 @@ pub use date::{Date, MIN_DATE, MAX_DATE};
pub use datetime::{DateTime, SecondsFormat};
#[cfg(feature = "rustc-serialize")] pub use datetime::rustc_serialize::TsSeconds;
pub use format::{ParseError, ParseResult};
pub use round::SubSecondRound;
/// A convenience module appropriate for glob imports (`use chrono::prelude::*;`).
pub mod prelude {
@ -418,6 +419,7 @@ pub mod prelude {
#[doc(no_inline)] pub use {NaiveDate, NaiveTime, NaiveDateTime};
#[doc(no_inline)] pub use Date;
#[doc(no_inline)] pub use {DateTime, SecondsFormat};
#[doc(no_inline)] pub use {SubSecondRound};
}
// useful throughout the codebase
@ -464,6 +466,7 @@ pub mod naive {
mod date;
mod datetime;
pub mod format;
mod round;
/// Serialization/Deserialization in alternate formats
///

162
src/round.rs Normal file
View File

@ -0,0 +1,162 @@
// This is a part of Chrono.
// See README.md and LICENSE.txt for details.
use Timelike;
use std::ops::{Add, Sub};
use oldtime::Duration;
/// Extension trait for subsecond rounding or truncation to a maximum number
/// of digits. Rounding can be used to decrease the error variance when
/// serializing/persisting to lower precision. Truncation is the default
/// behavior in Chrono display formatting. Either can be used to guarantee
/// equality (e.g. for testing) when round-tripping through a lower precision
/// format.
pub trait SubSecondRound {
/// Return a copy rounded to the specified number of subsecond digits. With
/// 9 or more digits, self is returned unmodified. Halfway values are
/// rounded up (away from zero).
fn round(self, digits: u16) -> Self;
/// Return a copy truncated to the specified number of subsecond
/// digits. With 9 or more digits, self is returned unmodified.
fn trunc(self, digits: u16) -> Self;
}
impl<T> SubSecondRound for T
where T: Timelike + Add<Duration, Output=T> + Sub<Duration, Output=T>
{
fn round(self, digits: u16) -> T {
let span = span_for_digits(digits);
let rem = self.nanosecond() % span;
if rem > 0 {
let rev = span - rem;
if rev <= rem {
self + Duration::nanoseconds(rev.into()) // up
} else {
self - Duration::nanoseconds(rem.into()) // down
}
} else {
self // unchanged
}
}
fn trunc(self, digits: u16) -> T {
let span = span_for_digits(digits);
let rem = self.nanosecond() % span;
if rem > 0 {
self - Duration::nanoseconds(rem.into()) // truncate
} else {
self // unchanged
}
}
}
// Return the maximum span in nanoseconds for the target number of digits.
fn span_for_digits(digits: u16) -> u32 {
// fast lookup form of: 10^(9-min(9,digits))
match digits {
0 => 1_000_000_000,
1 => 100_000_000,
2 => 10_000_000,
3 => 1_000_000,
4 => 100_000,
5 => 10_000,
6 => 1_000,
7 => 100,
8 => 10,
_ => 1
}
}
#[cfg(test)]
mod tests {
use Timelike;
use offset::{FixedOffset, TimeZone, Utc};
use super::SubSecondRound;
#[test]
fn test_round() {
let pst = FixedOffset::east(8 * 60 * 60);
let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_684);
assert_eq!(dt.round(10), dt);
assert_eq!(dt.round(9), dt);
assert_eq!(dt.round(8).nanosecond(), 084_660_680);
assert_eq!(dt.round(7).nanosecond(), 084_660_700);
assert_eq!(dt.round(6).nanosecond(), 084_661_000);
assert_eq!(dt.round(5).nanosecond(), 084_660_000);
assert_eq!(dt.round(4).nanosecond(), 084_700_000);
assert_eq!(dt.round(3).nanosecond(), 085_000_000);
assert_eq!(dt.round(2).nanosecond(), 080_000_000);
assert_eq!(dt.round(1).nanosecond(), 100_000_000);
assert_eq!(dt.round(0).nanosecond(), 0);
assert_eq!(dt.round(0).second(), 13);
let dt = Utc.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000);
assert_eq!(dt.round(9), dt);
assert_eq!(dt.round(4), dt);
assert_eq!(dt.round(3).nanosecond(), 751_000_000);
assert_eq!(dt.round(2).nanosecond(), 750_000_000);
assert_eq!(dt.round(1).nanosecond(), 800_000_000);
assert_eq!(dt.round(0).nanosecond(), 0);
assert_eq!(dt.round(0).second(), 28);
}
#[test]
fn test_round_leap_nanos() {
let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000);
assert_eq!(dt.round(9), dt);
assert_eq!(dt.round(4), dt);
assert_eq!(dt.round(2).nanosecond(), 1_750_000_000);
assert_eq!(dt.round(1).nanosecond(), 1_800_000_000);
assert_eq!(dt.round(1).second(), 59);
assert_eq!(dt.round(0).nanosecond(), 0);
assert_eq!(dt.round(0).second(), 0);
}
#[test]
fn test_trunc() {
let pst = FixedOffset::east(8 * 60 * 60);
let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_684);
assert_eq!(dt.trunc(10), dt);
assert_eq!(dt.trunc(9), dt);
assert_eq!(dt.trunc(8).nanosecond(), 084_660_680);
assert_eq!(dt.trunc(7).nanosecond(), 084_660_600);
assert_eq!(dt.trunc(6).nanosecond(), 084_660_000);
assert_eq!(dt.trunc(5).nanosecond(), 084_660_000);
assert_eq!(dt.trunc(4).nanosecond(), 084_600_000);
assert_eq!(dt.trunc(3).nanosecond(), 084_000_000);
assert_eq!(dt.trunc(2).nanosecond(), 080_000_000);
assert_eq!(dt.trunc(1).nanosecond(), 0);
assert_eq!(dt.trunc(0).nanosecond(), 0);
assert_eq!(dt.trunc(0).second(), 13);
let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000);
assert_eq!(dt.trunc(9), dt);
assert_eq!(dt.trunc(4), dt);
assert_eq!(dt.trunc(3).nanosecond(), 750_000_000);
assert_eq!(dt.trunc(2).nanosecond(), 750_000_000);
assert_eq!(dt.trunc(1).nanosecond(), 700_000_000);
assert_eq!(dt.trunc(0).nanosecond(), 0);
assert_eq!(dt.trunc(0).second(), 27);
}
#[test]
fn test_trunc_leap_nanos() {
let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000);
assert_eq!(dt.trunc(9), dt);
assert_eq!(dt.trunc(4), dt);
assert_eq!(dt.trunc(2).nanosecond(), 1_750_000_000);
assert_eq!(dt.trunc(1).nanosecond(), 1_700_000_000);
assert_eq!(dt.trunc(1).second(), 59);
assert_eq!(dt.trunc(0).nanosecond(), 1_000_000_000);
assert_eq!(dt.trunc(0).second(), 59);
}
}