| // This is a part of Chrono. |
| // See README.md and LICENSE.txt for details. |
| |
| use Timelike; |
| use core::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 SubsecRound { |
| /// 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). |
| /// |
| /// # Example |
| /// ``` rust |
| /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; |
| /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); |
| /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000); |
| /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000); |
| /// ``` |
| fn round_subsecs(self, digits: u16) -> Self; |
| |
| /// Return a copy truncated to the specified number of subsecond |
| /// digits. With 9 or more digits, self is returned unmodified. |
| /// |
| /// # Example |
| /// ``` rust |
| /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc}; |
| /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154); |
| /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000); |
| /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000); |
| /// ``` |
| fn trunc_subsecs(self, digits: u16) -> Self; |
| } |
| |
| impl<T> SubsecRound for T |
| where T: Timelike + Add<Duration, Output=T> + Sub<Duration, Output=T> |
| { |
| fn round_subsecs(self, digits: u16) -> T { |
| let span = span_for_digits(digits); |
| let delta_down = self.nanosecond() % span; |
| if delta_down > 0 { |
| let delta_up = span - delta_down; |
| if delta_up <= delta_down { |
| self + Duration::nanoseconds(delta_up.into()) |
| } else { |
| self - Duration::nanoseconds(delta_down.into()) |
| } |
| } else { |
| self // unchanged |
| } |
| } |
| |
| fn trunc_subsecs(self, digits: u16) -> T { |
| let span = span_for_digits(digits); |
| let delta_down = self.nanosecond() % span; |
| if delta_down > 0 { |
| self - Duration::nanoseconds(delta_down.into()) |
| } 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::SubsecRound; |
| |
| #[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_subsecs(10), dt); |
| assert_eq!(dt.round_subsecs(9), dt); |
| assert_eq!(dt.round_subsecs(8).nanosecond(), 084_660_680); |
| assert_eq!(dt.round_subsecs(7).nanosecond(), 084_660_700); |
| assert_eq!(dt.round_subsecs(6).nanosecond(), 084_661_000); |
| assert_eq!(dt.round_subsecs(5).nanosecond(), 084_660_000); |
| assert_eq!(dt.round_subsecs(4).nanosecond(), 084_700_000); |
| assert_eq!(dt.round_subsecs(3).nanosecond(), 085_000_000); |
| assert_eq!(dt.round_subsecs(2).nanosecond(), 080_000_000); |
| assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000); |
| |
| assert_eq!(dt.round_subsecs(0).nanosecond(), 0); |
| assert_eq!(dt.round_subsecs(0).second(), 13); |
| |
| let dt = Utc.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000); |
| assert_eq!(dt.round_subsecs(9), dt); |
| assert_eq!(dt.round_subsecs(4), dt); |
| assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000); |
| assert_eq!(dt.round_subsecs(2).nanosecond(), 750_000_000); |
| assert_eq!(dt.round_subsecs(1).nanosecond(), 800_000_000); |
| |
| assert_eq!(dt.round_subsecs(0).nanosecond(), 0); |
| assert_eq!(dt.round_subsecs(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_subsecs(9), dt); |
| assert_eq!(dt.round_subsecs(4), dt); |
| assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000); |
| assert_eq!(dt.round_subsecs(1).nanosecond(), 1_800_000_000); |
| assert_eq!(dt.round_subsecs(1).second(), 59); |
| |
| assert_eq!(dt.round_subsecs(0).nanosecond(), 0); |
| assert_eq!(dt.round_subsecs(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_subsecs(10), dt); |
| assert_eq!(dt.trunc_subsecs(9), dt); |
| assert_eq!(dt.trunc_subsecs(8).nanosecond(), 084_660_680); |
| assert_eq!(dt.trunc_subsecs(7).nanosecond(), 084_660_600); |
| assert_eq!(dt.trunc_subsecs(6).nanosecond(), 084_660_000); |
| assert_eq!(dt.trunc_subsecs(5).nanosecond(), 084_660_000); |
| assert_eq!(dt.trunc_subsecs(4).nanosecond(), 084_600_000); |
| assert_eq!(dt.trunc_subsecs(3).nanosecond(), 084_000_000); |
| assert_eq!(dt.trunc_subsecs(2).nanosecond(), 080_000_000); |
| assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0); |
| |
| assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); |
| assert_eq!(dt.trunc_subsecs(0).second(), 13); |
| |
| let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000); |
| assert_eq!(dt.trunc_subsecs(9), dt); |
| assert_eq!(dt.trunc_subsecs(4), dt); |
| assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000); |
| assert_eq!(dt.trunc_subsecs(2).nanosecond(), 750_000_000); |
| assert_eq!(dt.trunc_subsecs(1).nanosecond(), 700_000_000); |
| |
| assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0); |
| assert_eq!(dt.trunc_subsecs(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_subsecs(9), dt); |
| assert_eq!(dt.trunc_subsecs(4), dt); |
| assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000); |
| assert_eq!(dt.trunc_subsecs(1).nanosecond(), 1_700_000_000); |
| assert_eq!(dt.trunc_subsecs(1).second(), 59); |
| |
| assert_eq!(dt.trunc_subsecs(0).nanosecond(), 1_000_000_000); |
| assert_eq!(dt.trunc_subsecs(0).second(), 59); |
| } |
| } |