1 // This is a part of Chrono.
2 // See README.md and LICENSE.txt for details.
3 
4 use Timelike;
5 use core::ops::{Add, Sub};
6 use oldtime::Duration;
7 
8 /// Extension trait for subsecond rounding or truncation to a maximum number
9 /// of digits. Rounding can be used to decrease the error variance when
10 /// serializing/persisting to lower precision. Truncation is the default
11 /// behavior in Chrono display formatting.  Either can be used to guarantee
12 /// equality (e.g. for testing) when round-tripping through a lower precision
13 /// format.
14 pub trait SubsecRound {
15     /// Return a copy rounded to the specified number of subsecond digits. With
16     /// 9 or more digits, self is returned unmodified. Halfway values are
17     /// rounded up (away from zero).
18     ///
19     /// # Example
20     /// ``` rust
21     /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc};
22     /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154);
23     /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000);
24     /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000);
25     /// ```
round_subsecs(self, digits: u16) -> Self26     fn round_subsecs(self, digits: u16) -> Self;
27 
28     /// Return a copy truncated to the specified number of subsecond
29     /// digits. With 9 or more digits, self is returned unmodified.
30     ///
31     /// # Example
32     /// ``` rust
33     /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc};
34     /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154);
35     /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000);
36     /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000);
37     /// ```
trunc_subsecs(self, digits: u16) -> Self38     fn trunc_subsecs(self, digits: u16) -> Self;
39 }
40 
41 impl<T> SubsecRound for T
42 where T: Timelike + Add<Duration, Output=T> + Sub<Duration, Output=T>
43 {
round_subsecs(self, digits: u16) -> T44     fn round_subsecs(self, digits: u16) -> T {
45         let span = span_for_digits(digits);
46         let delta_down = self.nanosecond() % span;
47         if delta_down > 0 {
48             let delta_up = span - delta_down;
49             if delta_up <= delta_down {
50                 self + Duration::nanoseconds(delta_up.into())
51             } else {
52                 self - Duration::nanoseconds(delta_down.into())
53             }
54         } else {
55             self // unchanged
56         }
57     }
58 
trunc_subsecs(self, digits: u16) -> T59     fn trunc_subsecs(self, digits: u16) -> T {
60         let span = span_for_digits(digits);
61         let delta_down = self.nanosecond() % span;
62         if delta_down > 0 {
63             self - Duration::nanoseconds(delta_down.into())
64         } else {
65             self // unchanged
66         }
67     }
68 }
69 
70 // Return the maximum span in nanoseconds for the target number of digits.
span_for_digits(digits: u16) -> u3271 fn span_for_digits(digits: u16) -> u32 {
72     // fast lookup form of: 10^(9-min(9,digits))
73     match digits {
74         0 => 1_000_000_000,
75         1 =>   100_000_000,
76         2 =>    10_000_000,
77         3 =>     1_000_000,
78         4 =>       100_000,
79         5 =>        10_000,
80         6 =>         1_000,
81         7 =>           100,
82         8 =>            10,
83         _ =>             1
84     }
85 }
86 
87 #[cfg(test)]
88 mod tests {
89     use Timelike;
90     use offset::{FixedOffset, TimeZone, Utc};
91     use super::SubsecRound;
92 
93     #[test]
test_round()94     fn test_round() {
95         let pst = FixedOffset::east(8 * 60 * 60);
96         let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_684);
97 
98         assert_eq!(dt.round_subsecs(10), dt);
99         assert_eq!(dt.round_subsecs(9), dt);
100         assert_eq!(dt.round_subsecs(8).nanosecond(), 084_660_680);
101         assert_eq!(dt.round_subsecs(7).nanosecond(), 084_660_700);
102         assert_eq!(dt.round_subsecs(6).nanosecond(), 084_661_000);
103         assert_eq!(dt.round_subsecs(5).nanosecond(), 084_660_000);
104         assert_eq!(dt.round_subsecs(4).nanosecond(), 084_700_000);
105         assert_eq!(dt.round_subsecs(3).nanosecond(), 085_000_000);
106         assert_eq!(dt.round_subsecs(2).nanosecond(), 080_000_000);
107         assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000);
108 
109         assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
110         assert_eq!(dt.round_subsecs(0).second(), 13);
111 
112         let dt = Utc.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000);
113         assert_eq!(dt.round_subsecs(9), dt);
114         assert_eq!(dt.round_subsecs(4), dt);
115         assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000);
116         assert_eq!(dt.round_subsecs(2).nanosecond(), 750_000_000);
117         assert_eq!(dt.round_subsecs(1).nanosecond(), 800_000_000);
118 
119         assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
120         assert_eq!(dt.round_subsecs(0).second(), 28);
121     }
122 
123     #[test]
test_round_leap_nanos()124     fn test_round_leap_nanos() {
125         let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000);
126         assert_eq!(dt.round_subsecs(9), dt);
127         assert_eq!(dt.round_subsecs(4), dt);
128         assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000);
129         assert_eq!(dt.round_subsecs(1).nanosecond(), 1_800_000_000);
130         assert_eq!(dt.round_subsecs(1).second(), 59);
131 
132         assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
133         assert_eq!(dt.round_subsecs(0).second(), 0);
134     }
135 
136     #[test]
test_trunc()137     fn test_trunc() {
138         let pst = FixedOffset::east(8 * 60 * 60);
139         let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_684);
140 
141         assert_eq!(dt.trunc_subsecs(10), dt);
142         assert_eq!(dt.trunc_subsecs(9), dt);
143         assert_eq!(dt.trunc_subsecs(8).nanosecond(), 084_660_680);
144         assert_eq!(dt.trunc_subsecs(7).nanosecond(), 084_660_600);
145         assert_eq!(dt.trunc_subsecs(6).nanosecond(), 084_660_000);
146         assert_eq!(dt.trunc_subsecs(5).nanosecond(), 084_660_000);
147         assert_eq!(dt.trunc_subsecs(4).nanosecond(), 084_600_000);
148         assert_eq!(dt.trunc_subsecs(3).nanosecond(), 084_000_000);
149         assert_eq!(dt.trunc_subsecs(2).nanosecond(), 080_000_000);
150         assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0);
151 
152         assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
153         assert_eq!(dt.trunc_subsecs(0).second(), 13);
154 
155         let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000);
156         assert_eq!(dt.trunc_subsecs(9), dt);
157         assert_eq!(dt.trunc_subsecs(4), dt);
158         assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000);
159         assert_eq!(dt.trunc_subsecs(2).nanosecond(), 750_000_000);
160         assert_eq!(dt.trunc_subsecs(1).nanosecond(), 700_000_000);
161 
162         assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
163         assert_eq!(dt.trunc_subsecs(0).second(), 27);
164     }
165 
166     #[test]
test_trunc_leap_nanos()167     fn test_trunc_leap_nanos() {
168         let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000);
169         assert_eq!(dt.trunc_subsecs(9), dt);
170         assert_eq!(dt.trunc_subsecs(4), dt);
171         assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000);
172         assert_eq!(dt.trunc_subsecs(1).nanosecond(), 1_700_000_000);
173         assert_eq!(dt.trunc_subsecs(1).second(), 59);
174 
175         assert_eq!(dt.trunc_subsecs(0).nanosecond(), 1_000_000_000);
176         assert_eq!(dt.trunc_subsecs(0).second(), 59);
177     }
178 }
179