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