• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // This is a part of Chrono.
2 // See README.md and LICENSE.txt for details.
3 
4 use core::cmp::Ordering;
5 use core::fmt;
6 use core::marker::Sized;
7 use core::ops::{Add, Sub};
8 use datetime::DateTime;
9 use oldtime::Duration;
10 #[cfg(any(feature = "std", test))]
11 use std;
12 use TimeZone;
13 use Timelike;
14 
15 /// Extension trait for subsecond rounding or truncation to a maximum number
16 /// of digits. Rounding can be used to decrease the error variance when
17 /// serializing/persisting to lower precision. Truncation is the default
18 /// behavior in Chrono display formatting.  Either can be used to guarantee
19 /// equality (e.g. for testing) when round-tripping through a lower precision
20 /// format.
21 pub trait SubsecRound {
22     /// Return a copy rounded to the specified number of subsecond digits. With
23     /// 9 or more digits, self is returned unmodified. Halfway values are
24     /// rounded up (away from zero).
25     ///
26     /// # Example
27     /// ``` rust
28     /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc};
29     /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154);
30     /// assert_eq!(dt.round_subsecs(2).nanosecond(), 150_000_000);
31     /// assert_eq!(dt.round_subsecs(1).nanosecond(), 200_000_000);
32     /// ```
round_subsecs(self, digits: u16) -> Self33     fn round_subsecs(self, digits: u16) -> Self;
34 
35     /// Return a copy truncated to the specified number of subsecond
36     /// digits. With 9 or more digits, self is returned unmodified.
37     ///
38     /// # Example
39     /// ``` rust
40     /// # use chrono::{DateTime, SubsecRound, Timelike, TimeZone, Utc};
41     /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154);
42     /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000);
43     /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000);
44     /// ```
trunc_subsecs(self, digits: u16) -> Self45     fn trunc_subsecs(self, digits: u16) -> Self;
46 }
47 
48 impl<T> SubsecRound for T
49 where
50     T: Timelike + Add<Duration, Output = T> + Sub<Duration, Output = T>,
51 {
round_subsecs(self, digits: u16) -> T52     fn round_subsecs(self, digits: u16) -> T {
53         let span = span_for_digits(digits);
54         let delta_down = self.nanosecond() % span;
55         if delta_down > 0 {
56             let delta_up = span - delta_down;
57             if delta_up <= delta_down {
58                 self + Duration::nanoseconds(delta_up.into())
59             } else {
60                 self - Duration::nanoseconds(delta_down.into())
61             }
62         } else {
63             self // unchanged
64         }
65     }
66 
trunc_subsecs(self, digits: u16) -> T67     fn trunc_subsecs(self, digits: u16) -> T {
68         let span = span_for_digits(digits);
69         let delta_down = self.nanosecond() % span;
70         if delta_down > 0 {
71             self - Duration::nanoseconds(delta_down.into())
72         } else {
73             self // unchanged
74         }
75     }
76 }
77 
78 // Return the maximum span in nanoseconds for the target number of digits.
span_for_digits(digits: u16) -> u3279 fn span_for_digits(digits: u16) -> u32 {
80     // fast lookup form of: 10^(9-min(9,digits))
81     match digits {
82         0 => 1_000_000_000,
83         1 => 100_000_000,
84         2 => 10_000_000,
85         3 => 1_000_000,
86         4 => 100_000,
87         5 => 10_000,
88         6 => 1_000,
89         7 => 100,
90         8 => 10,
91         _ => 1,
92     }
93 }
94 
95 /// Extension trait for rounding or truncating a DateTime by a Duration.
96 ///
97 /// # Limitations
98 /// Both rounding and truncating are done via [`Duration::num_nanoseconds`] and
99 /// [`DateTime::timestamp_nanos`]. This means that they will fail if either the
100 /// `Duration` or the `DateTime` are too big to represented as nanoseconds. They
101 /// will also fail if the `Duration` is bigger than the timestamp.
102 pub trait DurationRound: Sized {
103     /// Error that can occur in rounding or truncating
104     #[cfg(any(feature = "std", test))]
105     type Err: std::error::Error;
106 
107     /// Error that can occur in rounding or truncating
108     #[cfg(not(any(feature = "std", test)))]
109     type Err: fmt::Debug + fmt::Display;
110 
111     /// Return a copy rounded by Duration.
112     ///
113     /// # Example
114     /// ``` rust
115     /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc};
116     /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154);
117     /// assert_eq!(
118     ///     dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(),
119     ///     "2018-01-11 12:00:00.150 UTC"
120     /// );
121     /// assert_eq!(
122     ///     dt.duration_round(Duration::days(1)).unwrap().to_string(),
123     ///     "2018-01-12 00:00:00 UTC"
124     /// );
125     /// ```
duration_round(self, duration: Duration) -> Result<Self, Self::Err>126     fn duration_round(self, duration: Duration) -> Result<Self, Self::Err>;
127 
128     /// Return a copy truncated by Duration.
129     ///
130     /// # Example
131     /// ``` rust
132     /// # use chrono::{DateTime, DurationRound, Duration, TimeZone, Utc};
133     /// let dt = Utc.ymd(2018, 1, 11).and_hms_milli(12, 0, 0, 154);
134     /// assert_eq!(
135     ///     dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(),
136     ///     "2018-01-11 12:00:00.150 UTC"
137     /// );
138     /// assert_eq!(
139     ///     dt.duration_trunc(Duration::days(1)).unwrap().to_string(),
140     ///     "2018-01-11 00:00:00 UTC"
141     /// );
142     /// ```
duration_trunc(self, duration: Duration) -> Result<Self, Self::Err>143     fn duration_trunc(self, duration: Duration) -> Result<Self, Self::Err>;
144 }
145 
146 /// The maximum number of seconds a DateTime can be to be represented as nanoseconds
147 const MAX_SECONDS_TIMESTAMP_FOR_NANOS: i64 = 9_223_372_036;
148 
149 impl<Tz: TimeZone> DurationRound for DateTime<Tz> {
150     type Err = RoundingError;
151 
duration_round(self, duration: Duration) -> Result<Self, Self::Err>152     fn duration_round(self, duration: Duration) -> Result<Self, Self::Err> {
153         if let Some(span) = duration.num_nanoseconds() {
154             if self.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS {
155                 return Err(RoundingError::TimestampExceedsLimit);
156             }
157             let stamp = self.timestamp_nanos();
158             if span > stamp.abs() {
159                 return Err(RoundingError::DurationExceedsTimestamp);
160             }
161             let delta_down = stamp % span;
162             if delta_down == 0 {
163                 Ok(self)
164             } else {
165                 let (delta_up, delta_down) = if delta_down < 0 {
166                     (delta_down.abs(), span - delta_down.abs())
167                 } else {
168                     (span - delta_down, delta_down)
169                 };
170                 if delta_up <= delta_down {
171                     Ok(self + Duration::nanoseconds(delta_up))
172                 } else {
173                     Ok(self - Duration::nanoseconds(delta_down))
174                 }
175             }
176         } else {
177             Err(RoundingError::DurationExceedsLimit)
178         }
179     }
180 
duration_trunc(self, duration: Duration) -> Result<Self, Self::Err>181     fn duration_trunc(self, duration: Duration) -> Result<Self, Self::Err> {
182         if let Some(span) = duration.num_nanoseconds() {
183             if self.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS {
184                 return Err(RoundingError::TimestampExceedsLimit);
185             }
186             let stamp = self.timestamp_nanos();
187             if span > stamp.abs() {
188                 return Err(RoundingError::DurationExceedsTimestamp);
189             }
190             let delta_down = stamp % span;
191             match delta_down.cmp(&0) {
192                 Ordering::Equal => Ok(self),
193                 Ordering::Greater => Ok(self - Duration::nanoseconds(delta_down)),
194                 Ordering::Less => Ok(self - Duration::nanoseconds(span - delta_down.abs())),
195             }
196         } else {
197             Err(RoundingError::DurationExceedsLimit)
198         }
199     }
200 }
201 
202 /// An error from rounding by `Duration`
203 ///
204 /// See: [`DurationRound`]
205 #[derive(Debug, Clone, PartialEq, Eq, Copy)]
206 pub enum RoundingError {
207     /// Error when the Duration exceeds the Duration from or until the Unix epoch.
208     ///
209     /// ``` rust
210     /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc};
211     /// let dt = Utc.ymd(1970, 12, 12).and_hms(0, 0, 0);
212     ///
213     /// assert_eq!(
214     ///     dt.duration_round(Duration::days(365)),
215     ///     Err(RoundingError::DurationExceedsTimestamp),
216     /// );
217     /// ```
218     DurationExceedsTimestamp,
219 
220     /// Error when `Duration.num_nanoseconds` exceeds the limit.
221     ///
222     /// ``` rust
223     /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc};
224     /// let dt = Utc.ymd(2260, 12, 31).and_hms_nano(23, 59, 59, 1_75_500_000);
225     ///
226     /// assert_eq!(
227     ///     dt.duration_round(Duration::days(300 * 365)),
228     ///     Err(RoundingError::DurationExceedsLimit)
229     /// );
230     /// ```
231     DurationExceedsLimit,
232 
233     /// Error when `DateTime.timestamp_nanos` exceeds the limit.
234     ///
235     /// ``` rust
236     /// # use chrono::{DateTime, DurationRound, Duration, RoundingError, TimeZone, Utc};
237     /// let dt = Utc.ymd(2300, 12, 12).and_hms(0, 0, 0);
238     ///
239     /// assert_eq!(dt.duration_round(Duration::days(1)), Err(RoundingError::TimestampExceedsLimit),);
240     /// ```
241     TimestampExceedsLimit,
242 }
243 
244 impl fmt::Display for RoundingError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result245     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246         match *self {
247             RoundingError::DurationExceedsTimestamp => {
248                 write!(f, "duration in nanoseconds exceeds timestamp")
249             }
250             RoundingError::DurationExceedsLimit => {
251                 write!(f, "duration exceeds num_nanoseconds limit")
252             }
253             RoundingError::TimestampExceedsLimit => {
254                 write!(f, "timestamp exceeds num_nanoseconds limit")
255             }
256         }
257     }
258 }
259 
260 #[cfg(any(feature = "std", test))]
261 impl std::error::Error for RoundingError {
262     #[allow(deprecated)]
description(&self) -> &str263     fn description(&self) -> &str {
264         "error from rounding or truncating with DurationRound"
265     }
266 }
267 
268 #[cfg(test)]
269 mod tests {
270     use super::{Duration, DurationRound, SubsecRound};
271     use offset::{FixedOffset, TimeZone, Utc};
272     use Timelike;
273 
274     #[test]
test_round_subsecs()275     fn test_round_subsecs() {
276         let pst = FixedOffset::east(8 * 60 * 60);
277         let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_684);
278 
279         assert_eq!(dt.round_subsecs(10), dt);
280         assert_eq!(dt.round_subsecs(9), dt);
281         assert_eq!(dt.round_subsecs(8).nanosecond(), 084_660_680);
282         assert_eq!(dt.round_subsecs(7).nanosecond(), 084_660_700);
283         assert_eq!(dt.round_subsecs(6).nanosecond(), 084_661_000);
284         assert_eq!(dt.round_subsecs(5).nanosecond(), 084_660_000);
285         assert_eq!(dt.round_subsecs(4).nanosecond(), 084_700_000);
286         assert_eq!(dt.round_subsecs(3).nanosecond(), 085_000_000);
287         assert_eq!(dt.round_subsecs(2).nanosecond(), 080_000_000);
288         assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000);
289 
290         assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
291         assert_eq!(dt.round_subsecs(0).second(), 13);
292 
293         let dt = Utc.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000);
294         assert_eq!(dt.round_subsecs(9), dt);
295         assert_eq!(dt.round_subsecs(4), dt);
296         assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000);
297         assert_eq!(dt.round_subsecs(2).nanosecond(), 750_000_000);
298         assert_eq!(dt.round_subsecs(1).nanosecond(), 800_000_000);
299 
300         assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
301         assert_eq!(dt.round_subsecs(0).second(), 28);
302     }
303 
304     #[test]
test_round_leap_nanos()305     fn test_round_leap_nanos() {
306         let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000);
307         assert_eq!(dt.round_subsecs(9), dt);
308         assert_eq!(dt.round_subsecs(4), dt);
309         assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000);
310         assert_eq!(dt.round_subsecs(1).nanosecond(), 1_800_000_000);
311         assert_eq!(dt.round_subsecs(1).second(), 59);
312 
313         assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
314         assert_eq!(dt.round_subsecs(0).second(), 0);
315     }
316 
317     #[test]
test_trunc_subsecs()318     fn test_trunc_subsecs() {
319         let pst = FixedOffset::east(8 * 60 * 60);
320         let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_684);
321 
322         assert_eq!(dt.trunc_subsecs(10), dt);
323         assert_eq!(dt.trunc_subsecs(9), dt);
324         assert_eq!(dt.trunc_subsecs(8).nanosecond(), 084_660_680);
325         assert_eq!(dt.trunc_subsecs(7).nanosecond(), 084_660_600);
326         assert_eq!(dt.trunc_subsecs(6).nanosecond(), 084_660_000);
327         assert_eq!(dt.trunc_subsecs(5).nanosecond(), 084_660_000);
328         assert_eq!(dt.trunc_subsecs(4).nanosecond(), 084_600_000);
329         assert_eq!(dt.trunc_subsecs(3).nanosecond(), 084_000_000);
330         assert_eq!(dt.trunc_subsecs(2).nanosecond(), 080_000_000);
331         assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0);
332 
333         assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
334         assert_eq!(dt.trunc_subsecs(0).second(), 13);
335 
336         let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 27, 750_500_000);
337         assert_eq!(dt.trunc_subsecs(9), dt);
338         assert_eq!(dt.trunc_subsecs(4), dt);
339         assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000);
340         assert_eq!(dt.trunc_subsecs(2).nanosecond(), 750_000_000);
341         assert_eq!(dt.trunc_subsecs(1).nanosecond(), 700_000_000);
342 
343         assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
344         assert_eq!(dt.trunc_subsecs(0).second(), 27);
345     }
346 
347     #[test]
test_trunc_leap_nanos()348     fn test_trunc_leap_nanos() {
349         let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_750_500_000);
350         assert_eq!(dt.trunc_subsecs(9), dt);
351         assert_eq!(dt.trunc_subsecs(4), dt);
352         assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000);
353         assert_eq!(dt.trunc_subsecs(1).nanosecond(), 1_700_000_000);
354         assert_eq!(dt.trunc_subsecs(1).second(), 59);
355 
356         assert_eq!(dt.trunc_subsecs(0).nanosecond(), 1_000_000_000);
357         assert_eq!(dt.trunc_subsecs(0).second(), 59);
358     }
359 
360     #[test]
test_duration_round()361     fn test_duration_round() {
362         let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 175_500_000);
363 
364         assert_eq!(
365             dt.duration_round(Duration::milliseconds(10)).unwrap().to_string(),
366             "2016-12-31 23:59:59.180 UTC"
367         );
368 
369         // round up
370         let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0);
371         assert_eq!(
372             dt.duration_round(Duration::minutes(5)).unwrap().to_string(),
373             "2012-12-12 18:25:00 UTC"
374         );
375         // round down
376         let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999);
377         assert_eq!(
378             dt.duration_round(Duration::minutes(5)).unwrap().to_string(),
379             "2012-12-12 18:20:00 UTC"
380         );
381 
382         assert_eq!(
383             dt.duration_round(Duration::minutes(10)).unwrap().to_string(),
384             "2012-12-12 18:20:00 UTC"
385         );
386         assert_eq!(
387             dt.duration_round(Duration::minutes(30)).unwrap().to_string(),
388             "2012-12-12 18:30:00 UTC"
389         );
390         assert_eq!(
391             dt.duration_round(Duration::hours(1)).unwrap().to_string(),
392             "2012-12-12 18:00:00 UTC"
393         );
394         assert_eq!(
395             dt.duration_round(Duration::days(1)).unwrap().to_string(),
396             "2012-12-13 00:00:00 UTC"
397         );
398     }
399 
400     #[test]
test_duration_round_pre_epoch()401     fn test_duration_round_pre_epoch() {
402         let dt = Utc.ymd(1969, 12, 12).and_hms(12, 12, 12);
403         assert_eq!(
404             dt.duration_round(Duration::minutes(10)).unwrap().to_string(),
405             "1969-12-12 12:10:00 UTC"
406         );
407     }
408 
409     #[test]
test_duration_trunc()410     fn test_duration_trunc() {
411         let dt = Utc.ymd(2016, 12, 31).and_hms_nano(23, 59, 59, 1_75_500_000);
412 
413         assert_eq!(
414             dt.duration_trunc(Duration::milliseconds(10)).unwrap().to_string(),
415             "2016-12-31 23:59:59.170 UTC"
416         );
417 
418         // would round up
419         let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 30, 0);
420         assert_eq!(
421             dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(),
422             "2012-12-12 18:20:00 UTC"
423         );
424         // would round down
425         let dt = Utc.ymd(2012, 12, 12).and_hms_milli(18, 22, 29, 999);
426         assert_eq!(
427             dt.duration_trunc(Duration::minutes(5)).unwrap().to_string(),
428             "2012-12-12 18:20:00 UTC"
429         );
430         assert_eq!(
431             dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(),
432             "2012-12-12 18:20:00 UTC"
433         );
434         assert_eq!(
435             dt.duration_trunc(Duration::minutes(30)).unwrap().to_string(),
436             "2012-12-12 18:00:00 UTC"
437         );
438         assert_eq!(
439             dt.duration_trunc(Duration::hours(1)).unwrap().to_string(),
440             "2012-12-12 18:00:00 UTC"
441         );
442         assert_eq!(
443             dt.duration_trunc(Duration::days(1)).unwrap().to_string(),
444             "2012-12-12 00:00:00 UTC"
445         );
446     }
447 
448     #[test]
test_duration_trunc_pre_epoch()449     fn test_duration_trunc_pre_epoch() {
450         let dt = Utc.ymd(1969, 12, 12).and_hms(12, 12, 12);
451         assert_eq!(
452             dt.duration_trunc(Duration::minutes(10)).unwrap().to_string(),
453             "1969-12-12 12:10:00 UTC"
454         );
455     }
456 }
457