• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // This is a part of Chrono.
2 // See README.md and LICENSE.txt for details.
3 
4 //! Functionality for rounding or truncating a `DateTime` by a `TimeDelta`.
5 
6 use crate::{DateTime, NaiveDateTime, TimeDelta, TimeZone, Timelike};
7 use core::cmp::Ordering;
8 use core::fmt;
9 use core::ops::{Add, Sub};
10 
11 /// Extension trait for subsecond rounding or truncation to a maximum number
12 /// of digits. Rounding can be used to decrease the error variance when
13 /// serializing/persisting to lower precision. Truncation is the default
14 /// behavior in Chrono display formatting.  Either can be used to guarantee
15 /// equality (e.g. for testing) when round-tripping through a lower precision
16 /// format.
17 pub trait SubsecRound {
18     /// Return a copy rounded to the specified number of subsecond digits. With
19     /// 9 or more digits, self is returned unmodified. Halfway values are
20     /// rounded up (away from zero).
21     ///
22     /// # Example
23     /// ``` rust
24     /// # use chrono::{SubsecRound, Timelike, NaiveDate};
25     /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11)
26     ///     .unwrap()
27     ///     .and_hms_milli_opt(12, 0, 0, 154)
28     ///     .unwrap()
29     ///     .and_utc();
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::{SubsecRound, Timelike, NaiveDate};
41     /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11)
42     ///     .unwrap()
43     ///     .and_hms_milli_opt(12, 0, 0, 154)
44     ///     .unwrap()
45     ///     .and_utc();
46     /// assert_eq!(dt.trunc_subsecs(2).nanosecond(), 150_000_000);
47     /// assert_eq!(dt.trunc_subsecs(1).nanosecond(), 100_000_000);
48     /// ```
trunc_subsecs(self, digits: u16) -> Self49     fn trunc_subsecs(self, digits: u16) -> Self;
50 }
51 
52 impl<T> SubsecRound for T
53 where
54     T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
55 {
round_subsecs(self, digits: u16) -> T56     fn round_subsecs(self, digits: u16) -> T {
57         let span = span_for_digits(digits);
58         let delta_down = self.nanosecond() % span;
59         if delta_down > 0 {
60             let delta_up = span - delta_down;
61             if delta_up <= delta_down {
62                 self + TimeDelta::nanoseconds(delta_up.into())
63             } else {
64                 self - TimeDelta::nanoseconds(delta_down.into())
65             }
66         } else {
67             self // unchanged
68         }
69     }
70 
trunc_subsecs(self, digits: u16) -> T71     fn trunc_subsecs(self, digits: u16) -> T {
72         let span = span_for_digits(digits);
73         let delta_down = self.nanosecond() % span;
74         if delta_down > 0 {
75             self - TimeDelta::nanoseconds(delta_down.into())
76         } else {
77             self // unchanged
78         }
79     }
80 }
81 
82 // Return the maximum span in nanoseconds for the target number of digits.
span_for_digits(digits: u16) -> u3283 const fn span_for_digits(digits: u16) -> u32 {
84     // fast lookup form of: 10^(9-min(9,digits))
85     match digits {
86         0 => 1_000_000_000,
87         1 => 100_000_000,
88         2 => 10_000_000,
89         3 => 1_000_000,
90         4 => 100_000,
91         5 => 10_000,
92         6 => 1_000,
93         7 => 100,
94         8 => 10,
95         _ => 1,
96     }
97 }
98 
99 /// Extension trait for rounding or truncating a DateTime by a TimeDelta.
100 ///
101 /// # Limitations
102 /// Both rounding and truncating are done via [`TimeDelta::num_nanoseconds`] and
103 /// [`DateTime::timestamp_nanos_opt`]. This means that they will fail if either the
104 /// `TimeDelta` or the `DateTime` are too big to represented as nanoseconds. They
105 /// will also fail if the `TimeDelta` is bigger than the timestamp, negative or zero.
106 pub trait DurationRound: Sized {
107     /// Error that can occur in rounding or truncating
108     #[cfg(feature = "std")]
109     type Err: std::error::Error;
110 
111     /// Error that can occur in rounding or truncating
112     #[cfg(not(feature = "std"))]
113     type Err: fmt::Debug + fmt::Display;
114 
115     /// Return a copy rounded by TimeDelta.
116     ///
117     /// # Example
118     /// ``` rust
119     /// # use chrono::{DurationRound, TimeDelta, NaiveDate};
120     /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11)
121     ///     .unwrap()
122     ///     .and_hms_milli_opt(12, 0, 0, 154)
123     ///     .unwrap()
124     ///     .and_utc();
125     /// assert_eq!(
126     ///     dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
127     ///     "2018-01-11 12:00:00.150 UTC"
128     /// );
129     /// assert_eq!(
130     ///     dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
131     ///     "2018-01-12 00:00:00 UTC"
132     /// );
133     /// ```
duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err>134     fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err>;
135 
136     /// Return a copy truncated by TimeDelta.
137     ///
138     /// # Example
139     /// ``` rust
140     /// # use chrono::{DurationRound, TimeDelta, NaiveDate};
141     /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11)
142     ///     .unwrap()
143     ///     .and_hms_milli_opt(12, 0, 0, 154)
144     ///     .unwrap()
145     ///     .and_utc();
146     /// assert_eq!(
147     ///     dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
148     ///     "2018-01-11 12:00:00.150 UTC"
149     /// );
150     /// assert_eq!(
151     ///     dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
152     ///     "2018-01-11 00:00:00 UTC"
153     /// );
154     /// ```
duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err>155     fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err>;
156 }
157 
158 impl<Tz: TimeZone> DurationRound for DateTime<Tz> {
159     type Err = RoundingError;
160 
duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err>161     fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
162         duration_round(self.naive_local(), self, duration)
163     }
164 
duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err>165     fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
166         duration_trunc(self.naive_local(), self, duration)
167     }
168 }
169 
170 impl DurationRound for NaiveDateTime {
171     type Err = RoundingError;
172 
duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err>173     fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
174         duration_round(self, self, duration)
175     }
176 
duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err>177     fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
178         duration_trunc(self, self, duration)
179     }
180 }
181 
duration_round<T>( naive: NaiveDateTime, original: T, duration: TimeDelta, ) -> Result<T, RoundingError> where T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,182 fn duration_round<T>(
183     naive: NaiveDateTime,
184     original: T,
185     duration: TimeDelta,
186 ) -> Result<T, RoundingError>
187 where
188     T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
189 {
190     if let Some(span) = duration.num_nanoseconds() {
191         if span <= 0 {
192             return Err(RoundingError::DurationExceedsLimit);
193         }
194         let stamp =
195             naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
196         let delta_down = stamp % span;
197         if delta_down == 0 {
198             Ok(original)
199         } else {
200             let (delta_up, delta_down) = if delta_down < 0 {
201                 (delta_down.abs(), span - delta_down.abs())
202             } else {
203                 (span - delta_down, delta_down)
204             };
205             if delta_up <= delta_down {
206                 Ok(original + TimeDelta::nanoseconds(delta_up))
207             } else {
208                 Ok(original - TimeDelta::nanoseconds(delta_down))
209             }
210         }
211     } else {
212         Err(RoundingError::DurationExceedsLimit)
213     }
214 }
215 
duration_trunc<T>( naive: NaiveDateTime, original: T, duration: TimeDelta, ) -> Result<T, RoundingError> where T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,216 fn duration_trunc<T>(
217     naive: NaiveDateTime,
218     original: T,
219     duration: TimeDelta,
220 ) -> Result<T, RoundingError>
221 where
222     T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
223 {
224     if let Some(span) = duration.num_nanoseconds() {
225         if span <= 0 {
226             return Err(RoundingError::DurationExceedsLimit);
227         }
228         let stamp =
229             naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
230         let delta_down = stamp % span;
231         match delta_down.cmp(&0) {
232             Ordering::Equal => Ok(original),
233             Ordering::Greater => Ok(original - TimeDelta::nanoseconds(delta_down)),
234             Ordering::Less => Ok(original - TimeDelta::nanoseconds(span - delta_down.abs())),
235         }
236     } else {
237         Err(RoundingError::DurationExceedsLimit)
238     }
239 }
240 
241 /// An error from rounding by `TimeDelta`
242 ///
243 /// See: [`DurationRound`]
244 #[derive(Debug, Clone, PartialEq, Eq, Copy)]
245 pub enum RoundingError {
246     /// Error when the TimeDelta exceeds the TimeDelta from or until the Unix epoch.
247     ///
248     /// Note: this error is not produced anymore.
249     DurationExceedsTimestamp,
250 
251     /// Error when `TimeDelta.num_nanoseconds` exceeds the limit.
252     ///
253     /// ``` rust
254     /// # use chrono::{DurationRound, TimeDelta, RoundingError, NaiveDate};
255     /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31)
256     ///     .unwrap()
257     ///     .and_hms_nano_opt(23, 59, 59, 1_75_500_000)
258     ///     .unwrap()
259     ///     .and_utc();
260     ///
261     /// assert_eq!(
262     ///     dt.duration_round(TimeDelta::try_days(300 * 365).unwrap()),
263     ///     Err(RoundingError::DurationExceedsLimit)
264     /// );
265     /// ```
266     DurationExceedsLimit,
267 
268     /// Error when `DateTime.timestamp_nanos` exceeds the limit.
269     ///
270     /// ``` rust
271     /// # use chrono::{DurationRound, TimeDelta, RoundingError, TimeZone, Utc};
272     /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap();
273     ///
274     /// assert_eq!(
275     ///     dt.duration_round(TimeDelta::try_days(1).unwrap()),
276     ///     Err(RoundingError::TimestampExceedsLimit)
277     /// );
278     /// ```
279     TimestampExceedsLimit,
280 }
281 
282 impl fmt::Display for RoundingError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result283     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
284         match *self {
285             RoundingError::DurationExceedsTimestamp => {
286                 write!(f, "duration in nanoseconds exceeds timestamp")
287             }
288             RoundingError::DurationExceedsLimit => {
289                 write!(f, "duration exceeds num_nanoseconds limit")
290             }
291             RoundingError::TimestampExceedsLimit => {
292                 write!(f, "timestamp exceeds num_nanoseconds limit")
293             }
294         }
295     }
296 }
297 
298 #[cfg(feature = "std")]
299 impl std::error::Error for RoundingError {
300     #[allow(deprecated)]
description(&self) -> &str301     fn description(&self) -> &str {
302         "error from rounding or truncating with DurationRound"
303     }
304 }
305 
306 #[cfg(test)]
307 mod tests {
308     use super::{DurationRound, RoundingError, SubsecRound, TimeDelta};
309     use crate::offset::{FixedOffset, TimeZone, Utc};
310     use crate::Timelike;
311     use crate::{DateTime, NaiveDate};
312 
313     #[test]
test_round_subsecs()314     fn test_round_subsecs() {
315         let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
316         let dt = pst
317             .from_local_datetime(
318                 &NaiveDate::from_ymd_opt(2018, 1, 11)
319                     .unwrap()
320                     .and_hms_nano_opt(10, 5, 13, 84_660_684)
321                     .unwrap(),
322             )
323             .unwrap();
324 
325         assert_eq!(dt.round_subsecs(10), dt);
326         assert_eq!(dt.round_subsecs(9), dt);
327         assert_eq!(dt.round_subsecs(8).nanosecond(), 84_660_680);
328         assert_eq!(dt.round_subsecs(7).nanosecond(), 84_660_700);
329         assert_eq!(dt.round_subsecs(6).nanosecond(), 84_661_000);
330         assert_eq!(dt.round_subsecs(5).nanosecond(), 84_660_000);
331         assert_eq!(dt.round_subsecs(4).nanosecond(), 84_700_000);
332         assert_eq!(dt.round_subsecs(3).nanosecond(), 85_000_000);
333         assert_eq!(dt.round_subsecs(2).nanosecond(), 80_000_000);
334         assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000);
335 
336         assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
337         assert_eq!(dt.round_subsecs(0).second(), 13);
338 
339         let dt = Utc
340             .from_local_datetime(
341                 &NaiveDate::from_ymd_opt(2018, 1, 11)
342                     .unwrap()
343                     .and_hms_nano_opt(10, 5, 27, 750_500_000)
344                     .unwrap(),
345             )
346             .unwrap();
347         assert_eq!(dt.round_subsecs(9), dt);
348         assert_eq!(dt.round_subsecs(4), dt);
349         assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000);
350         assert_eq!(dt.round_subsecs(2).nanosecond(), 750_000_000);
351         assert_eq!(dt.round_subsecs(1).nanosecond(), 800_000_000);
352 
353         assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
354         assert_eq!(dt.round_subsecs(0).second(), 28);
355     }
356 
357     #[test]
test_round_leap_nanos()358     fn test_round_leap_nanos() {
359         let dt = Utc
360             .from_local_datetime(
361                 &NaiveDate::from_ymd_opt(2016, 12, 31)
362                     .unwrap()
363                     .and_hms_nano_opt(23, 59, 59, 1_750_500_000)
364                     .unwrap(),
365             )
366             .unwrap();
367         assert_eq!(dt.round_subsecs(9), dt);
368         assert_eq!(dt.round_subsecs(4), dt);
369         assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000);
370         assert_eq!(dt.round_subsecs(1).nanosecond(), 1_800_000_000);
371         assert_eq!(dt.round_subsecs(1).second(), 59);
372 
373         assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
374         assert_eq!(dt.round_subsecs(0).second(), 0);
375     }
376 
377     #[test]
test_trunc_subsecs()378     fn test_trunc_subsecs() {
379         let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
380         let dt = pst
381             .from_local_datetime(
382                 &NaiveDate::from_ymd_opt(2018, 1, 11)
383                     .unwrap()
384                     .and_hms_nano_opt(10, 5, 13, 84_660_684)
385                     .unwrap(),
386             )
387             .unwrap();
388 
389         assert_eq!(dt.trunc_subsecs(10), dt);
390         assert_eq!(dt.trunc_subsecs(9), dt);
391         assert_eq!(dt.trunc_subsecs(8).nanosecond(), 84_660_680);
392         assert_eq!(dt.trunc_subsecs(7).nanosecond(), 84_660_600);
393         assert_eq!(dt.trunc_subsecs(6).nanosecond(), 84_660_000);
394         assert_eq!(dt.trunc_subsecs(5).nanosecond(), 84_660_000);
395         assert_eq!(dt.trunc_subsecs(4).nanosecond(), 84_600_000);
396         assert_eq!(dt.trunc_subsecs(3).nanosecond(), 84_000_000);
397         assert_eq!(dt.trunc_subsecs(2).nanosecond(), 80_000_000);
398         assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0);
399 
400         assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
401         assert_eq!(dt.trunc_subsecs(0).second(), 13);
402 
403         let dt = pst
404             .from_local_datetime(
405                 &NaiveDate::from_ymd_opt(2018, 1, 11)
406                     .unwrap()
407                     .and_hms_nano_opt(10, 5, 27, 750_500_000)
408                     .unwrap(),
409             )
410             .unwrap();
411         assert_eq!(dt.trunc_subsecs(9), dt);
412         assert_eq!(dt.trunc_subsecs(4), dt);
413         assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000);
414         assert_eq!(dt.trunc_subsecs(2).nanosecond(), 750_000_000);
415         assert_eq!(dt.trunc_subsecs(1).nanosecond(), 700_000_000);
416 
417         assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
418         assert_eq!(dt.trunc_subsecs(0).second(), 27);
419     }
420 
421     #[test]
test_trunc_leap_nanos()422     fn test_trunc_leap_nanos() {
423         let dt = Utc
424             .from_local_datetime(
425                 &NaiveDate::from_ymd_opt(2016, 12, 31)
426                     .unwrap()
427                     .and_hms_nano_opt(23, 59, 59, 1_750_500_000)
428                     .unwrap(),
429             )
430             .unwrap();
431         assert_eq!(dt.trunc_subsecs(9), dt);
432         assert_eq!(dt.trunc_subsecs(4), dt);
433         assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000);
434         assert_eq!(dt.trunc_subsecs(1).nanosecond(), 1_700_000_000);
435         assert_eq!(dt.trunc_subsecs(1).second(), 59);
436 
437         assert_eq!(dt.trunc_subsecs(0).nanosecond(), 1_000_000_000);
438         assert_eq!(dt.trunc_subsecs(0).second(), 59);
439     }
440 
441     #[test]
test_duration_round()442     fn test_duration_round() {
443         let dt = Utc
444             .from_local_datetime(
445                 &NaiveDate::from_ymd_opt(2016, 12, 31)
446                     .unwrap()
447                     .and_hms_nano_opt(23, 59, 59, 175_500_000)
448                     .unwrap(),
449             )
450             .unwrap();
451 
452         assert_eq!(
453             dt.duration_round(TimeDelta::new(-1, 0).unwrap()),
454             Err(RoundingError::DurationExceedsLimit)
455         );
456         assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
457 
458         assert_eq!(
459             dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
460             "2016-12-31 23:59:59.180 UTC"
461         );
462 
463         // round up
464         let dt = Utc
465             .from_local_datetime(
466                 &NaiveDate::from_ymd_opt(2012, 12, 12)
467                     .unwrap()
468                     .and_hms_milli_opt(18, 22, 30, 0)
469                     .unwrap(),
470             )
471             .unwrap();
472         assert_eq!(
473             dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
474             "2012-12-12 18:25:00 UTC"
475         );
476         // round down
477         let dt = Utc
478             .from_local_datetime(
479                 &NaiveDate::from_ymd_opt(2012, 12, 12)
480                     .unwrap()
481                     .and_hms_milli_opt(18, 22, 29, 999)
482                     .unwrap(),
483             )
484             .unwrap();
485         assert_eq!(
486             dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
487             "2012-12-12 18:20:00 UTC"
488         );
489 
490         assert_eq!(
491             dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
492             "2012-12-12 18:20:00 UTC"
493         );
494         assert_eq!(
495             dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
496             "2012-12-12 18:30:00 UTC"
497         );
498         assert_eq!(
499             dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
500             "2012-12-12 18:00:00 UTC"
501         );
502         assert_eq!(
503             dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
504             "2012-12-13 00:00:00 UTC"
505         );
506 
507         // timezone east
508         let dt =
509             FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
510         assert_eq!(
511             dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
512             "2020-10-28 00:00:00 +01:00"
513         );
514         assert_eq!(
515             dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
516             "2020-10-29 00:00:00 +01:00"
517         );
518 
519         // timezone west
520         let dt =
521             FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
522         assert_eq!(
523             dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
524             "2020-10-28 00:00:00 -01:00"
525         );
526         assert_eq!(
527             dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
528             "2020-10-29 00:00:00 -01:00"
529         );
530     }
531 
532     #[test]
test_duration_round_naive()533     fn test_duration_round_naive() {
534         let dt = Utc
535             .from_local_datetime(
536                 &NaiveDate::from_ymd_opt(2016, 12, 31)
537                     .unwrap()
538                     .and_hms_nano_opt(23, 59, 59, 175_500_000)
539                     .unwrap(),
540             )
541             .unwrap()
542             .naive_utc();
543 
544         assert_eq!(
545             dt.duration_round(TimeDelta::new(-1, 0).unwrap()),
546             Err(RoundingError::DurationExceedsLimit)
547         );
548         assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
549 
550         assert_eq!(
551             dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
552             "2016-12-31 23:59:59.180"
553         );
554 
555         // round up
556         let dt = Utc
557             .from_local_datetime(
558                 &NaiveDate::from_ymd_opt(2012, 12, 12)
559                     .unwrap()
560                     .and_hms_milli_opt(18, 22, 30, 0)
561                     .unwrap(),
562             )
563             .unwrap()
564             .naive_utc();
565         assert_eq!(
566             dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
567             "2012-12-12 18:25:00"
568         );
569         // round down
570         let dt = Utc
571             .from_local_datetime(
572                 &NaiveDate::from_ymd_opt(2012, 12, 12)
573                     .unwrap()
574                     .and_hms_milli_opt(18, 22, 29, 999)
575                     .unwrap(),
576             )
577             .unwrap()
578             .naive_utc();
579         assert_eq!(
580             dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
581             "2012-12-12 18:20:00"
582         );
583 
584         assert_eq!(
585             dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
586             "2012-12-12 18:20:00"
587         );
588         assert_eq!(
589             dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
590             "2012-12-12 18:30:00"
591         );
592         assert_eq!(
593             dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
594             "2012-12-12 18:00:00"
595         );
596         assert_eq!(
597             dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
598             "2012-12-13 00:00:00"
599         );
600     }
601 
602     #[test]
test_duration_round_pre_epoch()603     fn test_duration_round_pre_epoch() {
604         let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap();
605         assert_eq!(
606             dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
607             "1969-12-12 12:10:00 UTC"
608         );
609     }
610 
611     #[test]
test_duration_trunc()612     fn test_duration_trunc() {
613         let dt = Utc
614             .from_local_datetime(
615                 &NaiveDate::from_ymd_opt(2016, 12, 31)
616                     .unwrap()
617                     .and_hms_nano_opt(23, 59, 59, 175_500_000)
618                     .unwrap(),
619             )
620             .unwrap();
621 
622         assert_eq!(
623             dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()),
624             Err(RoundingError::DurationExceedsLimit)
625         );
626         assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
627 
628         assert_eq!(
629             dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
630             "2016-12-31 23:59:59.170 UTC"
631         );
632 
633         // would round up
634         let dt = Utc
635             .from_local_datetime(
636                 &NaiveDate::from_ymd_opt(2012, 12, 12)
637                     .unwrap()
638                     .and_hms_milli_opt(18, 22, 30, 0)
639                     .unwrap(),
640             )
641             .unwrap();
642         assert_eq!(
643             dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
644             "2012-12-12 18:20:00 UTC"
645         );
646         // would round down
647         let dt = Utc
648             .from_local_datetime(
649                 &NaiveDate::from_ymd_opt(2012, 12, 12)
650                     .unwrap()
651                     .and_hms_milli_opt(18, 22, 29, 999)
652                     .unwrap(),
653             )
654             .unwrap();
655         assert_eq!(
656             dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
657             "2012-12-12 18:20:00 UTC"
658         );
659         assert_eq!(
660             dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
661             "2012-12-12 18:20:00 UTC"
662         );
663         assert_eq!(
664             dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
665             "2012-12-12 18:00:00 UTC"
666         );
667         assert_eq!(
668             dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
669             "2012-12-12 18:00:00 UTC"
670         );
671         assert_eq!(
672             dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
673             "2012-12-12 00:00:00 UTC"
674         );
675 
676         // timezone east
677         let dt =
678             FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
679         assert_eq!(
680             dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
681             "2020-10-27 00:00:00 +01:00"
682         );
683         assert_eq!(
684             dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
685             "2020-10-22 00:00:00 +01:00"
686         );
687 
688         // timezone west
689         let dt =
690             FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
691         assert_eq!(
692             dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
693             "2020-10-27 00:00:00 -01:00"
694         );
695         assert_eq!(
696             dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
697             "2020-10-22 00:00:00 -01:00"
698         );
699     }
700 
701     #[test]
test_duration_trunc_naive()702     fn test_duration_trunc_naive() {
703         let dt = Utc
704             .from_local_datetime(
705                 &NaiveDate::from_ymd_opt(2016, 12, 31)
706                     .unwrap()
707                     .and_hms_nano_opt(23, 59, 59, 175_500_000)
708                     .unwrap(),
709             )
710             .unwrap()
711             .naive_utc();
712 
713         assert_eq!(
714             dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()),
715             Err(RoundingError::DurationExceedsLimit)
716         );
717         assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
718 
719         assert_eq!(
720             dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
721             "2016-12-31 23:59:59.170"
722         );
723 
724         // would round up
725         let dt = Utc
726             .from_local_datetime(
727                 &NaiveDate::from_ymd_opt(2012, 12, 12)
728                     .unwrap()
729                     .and_hms_milli_opt(18, 22, 30, 0)
730                     .unwrap(),
731             )
732             .unwrap()
733             .naive_utc();
734         assert_eq!(
735             dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
736             "2012-12-12 18:20:00"
737         );
738         // would round down
739         let dt = Utc
740             .from_local_datetime(
741                 &NaiveDate::from_ymd_opt(2012, 12, 12)
742                     .unwrap()
743                     .and_hms_milli_opt(18, 22, 29, 999)
744                     .unwrap(),
745             )
746             .unwrap()
747             .naive_utc();
748         assert_eq!(
749             dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
750             "2012-12-12 18:20:00"
751         );
752         assert_eq!(
753             dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
754             "2012-12-12 18:20:00"
755         );
756         assert_eq!(
757             dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
758             "2012-12-12 18:00:00"
759         );
760         assert_eq!(
761             dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
762             "2012-12-12 18:00:00"
763         );
764         assert_eq!(
765             dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
766             "2012-12-12 00:00:00"
767         );
768     }
769 
770     #[test]
test_duration_trunc_pre_epoch()771     fn test_duration_trunc_pre_epoch() {
772         let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap();
773         assert_eq!(
774             dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
775             "1969-12-12 12:10:00 UTC"
776         );
777     }
778 
779     #[test]
issue1010()780     fn issue1010() {
781         let dt = DateTime::from_timestamp(-4_227_854_320, 678_774_288).unwrap();
782         let span = TimeDelta::microseconds(-7_019_067_213_869_040);
783         assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit));
784 
785         let dt = DateTime::from_timestamp(320_041_586, 920_103_021).unwrap();
786         let span = TimeDelta::nanoseconds(-8_923_838_508_697_114_584);
787         assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit));
788 
789         let dt = DateTime::from_timestamp(-2_621_440, 0).unwrap();
790         let span = TimeDelta::nanoseconds(-9_223_372_036_854_771_421);
791         assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit));
792     }
793 
794     #[test]
test_duration_trunc_close_to_epoch()795     fn test_duration_trunc_close_to_epoch() {
796         let span = TimeDelta::try_minutes(15).unwrap();
797 
798         let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap();
799         assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1970-01-01 00:00:00");
800 
801         let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap();
802         assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1969-12-31 23:45:00");
803     }
804 
805     #[test]
test_duration_round_close_to_epoch()806     fn test_duration_round_close_to_epoch() {
807         let span = TimeDelta::try_minutes(15).unwrap();
808 
809         let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap();
810         assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00");
811 
812         let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap();
813         assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00");
814     }
815 
816     #[test]
test_duration_round_close_to_min_max()817     fn test_duration_round_close_to_min_max() {
818         let span = TimeDelta::nanoseconds(i64::MAX);
819 
820         let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 - 1);
821         assert_eq!(
822             dt.duration_round(span).unwrap().to_string(),
823             "1677-09-21 00:12:43.145224193 UTC"
824         );
825 
826         let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 + 1);
827         assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC");
828 
829         let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 + 1);
830         assert_eq!(
831             dt.duration_round(span).unwrap().to_string(),
832             "2262-04-11 23:47:16.854775807 UTC"
833         );
834 
835         let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 - 1);
836         assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC");
837     }
838 }
839