chrono/
round.rs

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
6use crate::{DateTime, NaiveDateTime, TimeDelta, TimeZone, Timelike};
7use core::cmp::Ordering;
8use core::fmt;
9use 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.
17pub 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    /// ```
33    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    /// ```
49    fn trunc_subsecs(self, digits: u16) -> Self;
50}
51
52impl<T> SubsecRound for T
53where
54    T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
55{
56    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
71    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.
83const 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.
106pub 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    /// ```
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    /// ```
155    fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err>;
156
157    /// Return a copy rounded **up** by TimeDelta.
158    ///
159    /// # Example
160    /// ``` rust
161    /// # use chrono::{DurationRound, TimeDelta, NaiveDate};
162    /// let dt = NaiveDate::from_ymd_opt(2018, 1, 11)
163    ///     .unwrap()
164    ///     .and_hms_milli_opt(12, 0, 0, 154)
165    ///     .unwrap()
166    ///     .and_utc();
167    /// assert_eq!(
168    ///     dt.duration_round_up(TimeDelta::milliseconds(10)).unwrap().to_string(),
169    ///     "2018-01-11 12:00:00.160 UTC"
170    /// );
171    /// assert_eq!(
172    ///     dt.duration_round_up(TimeDelta::hours(1)).unwrap().to_string(),
173    ///     "2018-01-11 13:00:00 UTC"
174    /// );
175    ///
176    /// assert_eq!(
177    ///     dt.duration_round_up(TimeDelta::days(1)).unwrap().to_string(),
178    ///     "2018-01-12 00:00:00 UTC"
179    /// );
180    /// ```
181    fn duration_round_up(self, duration: TimeDelta) -> Result<Self, Self::Err>;
182}
183
184impl<Tz: TimeZone> DurationRound for DateTime<Tz> {
185    type Err = RoundingError;
186
187    fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
188        duration_round(self.naive_local(), self, duration)
189    }
190
191    fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
192        duration_trunc(self.naive_local(), self, duration)
193    }
194
195    fn duration_round_up(self, duration: TimeDelta) -> Result<Self, Self::Err> {
196        duration_round_up(self.naive_local(), self, duration)
197    }
198}
199
200impl DurationRound for NaiveDateTime {
201    type Err = RoundingError;
202
203    fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
204        duration_round(self, self, duration)
205    }
206
207    fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
208        duration_trunc(self, self, duration)
209    }
210
211    fn duration_round_up(self, duration: TimeDelta) -> Result<Self, Self::Err> {
212        duration_round_up(self, self, duration)
213    }
214}
215
216fn duration_round<T>(
217    naive: NaiveDateTime,
218    original: T,
219    duration: TimeDelta,
220) -> Result<T, RoundingError>
221where
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        if delta_down == 0 {
232            Ok(original)
233        } else {
234            let (delta_up, delta_down) = if delta_down < 0 {
235                (delta_down.abs(), span - delta_down.abs())
236            } else {
237                (span - delta_down, delta_down)
238            };
239            if delta_up <= delta_down {
240                Ok(original + TimeDelta::nanoseconds(delta_up))
241            } else {
242                Ok(original - TimeDelta::nanoseconds(delta_down))
243            }
244        }
245    } else {
246        Err(RoundingError::DurationExceedsLimit)
247    }
248}
249
250fn duration_trunc<T>(
251    naive: NaiveDateTime,
252    original: T,
253    duration: TimeDelta,
254) -> Result<T, RoundingError>
255where
256    T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
257{
258    if let Some(span) = duration.num_nanoseconds() {
259        if span <= 0 {
260            return Err(RoundingError::DurationExceedsLimit);
261        }
262        let stamp =
263            naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
264        let delta_down = stamp % span;
265        match delta_down.cmp(&0) {
266            Ordering::Equal => Ok(original),
267            Ordering::Greater => Ok(original - TimeDelta::nanoseconds(delta_down)),
268            Ordering::Less => Ok(original - TimeDelta::nanoseconds(span - delta_down.abs())),
269        }
270    } else {
271        Err(RoundingError::DurationExceedsLimit)
272    }
273}
274
275fn duration_round_up<T>(
276    naive: NaiveDateTime,
277    original: T,
278    duration: TimeDelta,
279) -> Result<T, RoundingError>
280where
281    T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
282{
283    if let Some(span) = duration.num_nanoseconds() {
284        if span <= 0 {
285            return Err(RoundingError::DurationExceedsLimit);
286        }
287        let stamp =
288            naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
289        let delta_down = stamp % span;
290        match delta_down.cmp(&0) {
291            Ordering::Equal => Ok(original),
292            Ordering::Greater => Ok(original + TimeDelta::nanoseconds(span - delta_down)),
293            Ordering::Less => Ok(original + TimeDelta::nanoseconds(delta_down.abs())),
294        }
295    } else {
296        Err(RoundingError::DurationExceedsLimit)
297    }
298}
299
300/// An error from rounding by `TimeDelta`
301///
302/// See: [`DurationRound`]
303#[derive(Debug, Clone, PartialEq, Eq, Copy)]
304pub enum RoundingError {
305    /// Error when the TimeDelta exceeds the TimeDelta from or until the Unix epoch.
306    ///
307    /// Note: this error is not produced anymore.
308    DurationExceedsTimestamp,
309
310    /// Error when `TimeDelta.num_nanoseconds` exceeds the limit.
311    ///
312    /// ``` rust
313    /// # use chrono::{DurationRound, TimeDelta, RoundingError, NaiveDate};
314    /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31)
315    ///     .unwrap()
316    ///     .and_hms_nano_opt(23, 59, 59, 1_75_500_000)
317    ///     .unwrap()
318    ///     .and_utc();
319    ///
320    /// assert_eq!(
321    ///     dt.duration_round(TimeDelta::try_days(300 * 365).unwrap()),
322    ///     Err(RoundingError::DurationExceedsLimit)
323    /// );
324    /// ```
325    DurationExceedsLimit,
326
327    /// Error when `DateTime.timestamp_nanos` exceeds the limit.
328    ///
329    /// ``` rust
330    /// # use chrono::{DurationRound, TimeDelta, RoundingError, TimeZone, Utc};
331    /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap();
332    ///
333    /// assert_eq!(
334    ///     dt.duration_round(TimeDelta::try_days(1).unwrap()),
335    ///     Err(RoundingError::TimestampExceedsLimit)
336    /// );
337    /// ```
338    TimestampExceedsLimit,
339}
340
341impl fmt::Display for RoundingError {
342    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
343        match *self {
344            RoundingError::DurationExceedsTimestamp => {
345                write!(f, "duration in nanoseconds exceeds timestamp")
346            }
347            RoundingError::DurationExceedsLimit => {
348                write!(f, "duration exceeds num_nanoseconds limit")
349            }
350            RoundingError::TimestampExceedsLimit => {
351                write!(f, "timestamp exceeds num_nanoseconds limit")
352            }
353        }
354    }
355}
356
357#[cfg(feature = "std")]
358impl std::error::Error for RoundingError {
359    #[allow(deprecated)]
360    fn description(&self) -> &str {
361        "error from rounding or truncating with DurationRound"
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use super::{DurationRound, RoundingError, SubsecRound, TimeDelta};
368    use crate::Timelike;
369    use crate::offset::{FixedOffset, TimeZone, Utc};
370    use crate::{DateTime, NaiveDate};
371
372    #[test]
373    fn test_round_subsecs() {
374        let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
375        let dt = pst
376            .from_local_datetime(
377                &NaiveDate::from_ymd_opt(2018, 1, 11)
378                    .unwrap()
379                    .and_hms_nano_opt(10, 5, 13, 84_660_684)
380                    .unwrap(),
381            )
382            .unwrap();
383
384        assert_eq!(dt.round_subsecs(10), dt);
385        assert_eq!(dt.round_subsecs(9), dt);
386        assert_eq!(dt.round_subsecs(8).nanosecond(), 84_660_680);
387        assert_eq!(dt.round_subsecs(7).nanosecond(), 84_660_700);
388        assert_eq!(dt.round_subsecs(6).nanosecond(), 84_661_000);
389        assert_eq!(dt.round_subsecs(5).nanosecond(), 84_660_000);
390        assert_eq!(dt.round_subsecs(4).nanosecond(), 84_700_000);
391        assert_eq!(dt.round_subsecs(3).nanosecond(), 85_000_000);
392        assert_eq!(dt.round_subsecs(2).nanosecond(), 80_000_000);
393        assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000);
394
395        assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
396        assert_eq!(dt.round_subsecs(0).second(), 13);
397
398        let dt = Utc
399            .from_local_datetime(
400                &NaiveDate::from_ymd_opt(2018, 1, 11)
401                    .unwrap()
402                    .and_hms_nano_opt(10, 5, 27, 750_500_000)
403                    .unwrap(),
404            )
405            .unwrap();
406        assert_eq!(dt.round_subsecs(9), dt);
407        assert_eq!(dt.round_subsecs(4), dt);
408        assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000);
409        assert_eq!(dt.round_subsecs(2).nanosecond(), 750_000_000);
410        assert_eq!(dt.round_subsecs(1).nanosecond(), 800_000_000);
411
412        assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
413        assert_eq!(dt.round_subsecs(0).second(), 28);
414    }
415
416    #[test]
417    fn test_round_leap_nanos() {
418        let dt = Utc
419            .from_local_datetime(
420                &NaiveDate::from_ymd_opt(2016, 12, 31)
421                    .unwrap()
422                    .and_hms_nano_opt(23, 59, 59, 1_750_500_000)
423                    .unwrap(),
424            )
425            .unwrap();
426        assert_eq!(dt.round_subsecs(9), dt);
427        assert_eq!(dt.round_subsecs(4), dt);
428        assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000);
429        assert_eq!(dt.round_subsecs(1).nanosecond(), 1_800_000_000);
430        assert_eq!(dt.round_subsecs(1).second(), 59);
431
432        assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
433        assert_eq!(dt.round_subsecs(0).second(), 0);
434    }
435
436    #[test]
437    fn test_trunc_subsecs() {
438        let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
439        let dt = pst
440            .from_local_datetime(
441                &NaiveDate::from_ymd_opt(2018, 1, 11)
442                    .unwrap()
443                    .and_hms_nano_opt(10, 5, 13, 84_660_684)
444                    .unwrap(),
445            )
446            .unwrap();
447
448        assert_eq!(dt.trunc_subsecs(10), dt);
449        assert_eq!(dt.trunc_subsecs(9), dt);
450        assert_eq!(dt.trunc_subsecs(8).nanosecond(), 84_660_680);
451        assert_eq!(dt.trunc_subsecs(7).nanosecond(), 84_660_600);
452        assert_eq!(dt.trunc_subsecs(6).nanosecond(), 84_660_000);
453        assert_eq!(dt.trunc_subsecs(5).nanosecond(), 84_660_000);
454        assert_eq!(dt.trunc_subsecs(4).nanosecond(), 84_600_000);
455        assert_eq!(dt.trunc_subsecs(3).nanosecond(), 84_000_000);
456        assert_eq!(dt.trunc_subsecs(2).nanosecond(), 80_000_000);
457        assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0);
458
459        assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
460        assert_eq!(dt.trunc_subsecs(0).second(), 13);
461
462        let dt = pst
463            .from_local_datetime(
464                &NaiveDate::from_ymd_opt(2018, 1, 11)
465                    .unwrap()
466                    .and_hms_nano_opt(10, 5, 27, 750_500_000)
467                    .unwrap(),
468            )
469            .unwrap();
470        assert_eq!(dt.trunc_subsecs(9), dt);
471        assert_eq!(dt.trunc_subsecs(4), dt);
472        assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000);
473        assert_eq!(dt.trunc_subsecs(2).nanosecond(), 750_000_000);
474        assert_eq!(dt.trunc_subsecs(1).nanosecond(), 700_000_000);
475
476        assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
477        assert_eq!(dt.trunc_subsecs(0).second(), 27);
478    }
479
480    #[test]
481    fn test_trunc_leap_nanos() {
482        let dt = Utc
483            .from_local_datetime(
484                &NaiveDate::from_ymd_opt(2016, 12, 31)
485                    .unwrap()
486                    .and_hms_nano_opt(23, 59, 59, 1_750_500_000)
487                    .unwrap(),
488            )
489            .unwrap();
490        assert_eq!(dt.trunc_subsecs(9), dt);
491        assert_eq!(dt.trunc_subsecs(4), dt);
492        assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000);
493        assert_eq!(dt.trunc_subsecs(1).nanosecond(), 1_700_000_000);
494        assert_eq!(dt.trunc_subsecs(1).second(), 59);
495
496        assert_eq!(dt.trunc_subsecs(0).nanosecond(), 1_000_000_000);
497        assert_eq!(dt.trunc_subsecs(0).second(), 59);
498    }
499
500    #[test]
501    fn test_duration_round() {
502        let dt = Utc
503            .from_local_datetime(
504                &NaiveDate::from_ymd_opt(2016, 12, 31)
505                    .unwrap()
506                    .and_hms_nano_opt(23, 59, 59, 175_500_000)
507                    .unwrap(),
508            )
509            .unwrap();
510
511        assert_eq!(
512            dt.duration_round(TimeDelta::new(-1, 0).unwrap()),
513            Err(RoundingError::DurationExceedsLimit)
514        );
515        assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
516
517        assert_eq!(
518            dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
519            "2016-12-31 23:59:59.180 UTC"
520        );
521
522        // round up
523        let dt = Utc
524            .from_local_datetime(
525                &NaiveDate::from_ymd_opt(2012, 12, 12)
526                    .unwrap()
527                    .and_hms_milli_opt(18, 22, 30, 0)
528                    .unwrap(),
529            )
530            .unwrap();
531        assert_eq!(
532            dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
533            "2012-12-12 18:25:00 UTC"
534        );
535        // round down
536        let dt = Utc
537            .from_local_datetime(
538                &NaiveDate::from_ymd_opt(2012, 12, 12)
539                    .unwrap()
540                    .and_hms_milli_opt(18, 22, 29, 999)
541                    .unwrap(),
542            )
543            .unwrap();
544        assert_eq!(
545            dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
546            "2012-12-12 18:20:00 UTC"
547        );
548
549        assert_eq!(
550            dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
551            "2012-12-12 18:20:00 UTC"
552        );
553        assert_eq!(
554            dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
555            "2012-12-12 18:30:00 UTC"
556        );
557        assert_eq!(
558            dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
559            "2012-12-12 18:00:00 UTC"
560        );
561        assert_eq!(
562            dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
563            "2012-12-13 00:00:00 UTC"
564        );
565
566        // timezone east
567        let dt =
568            FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
569        assert_eq!(
570            dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
571            "2020-10-28 00:00:00 +01:00"
572        );
573        assert_eq!(
574            dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
575            "2020-10-29 00:00:00 +01:00"
576        );
577
578        // timezone west
579        let dt =
580            FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
581        assert_eq!(
582            dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
583            "2020-10-28 00:00:00 -01:00"
584        );
585        assert_eq!(
586            dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
587            "2020-10-29 00:00:00 -01:00"
588        );
589    }
590
591    #[test]
592    fn test_duration_round_naive() {
593        let dt = Utc
594            .from_local_datetime(
595                &NaiveDate::from_ymd_opt(2016, 12, 31)
596                    .unwrap()
597                    .and_hms_nano_opt(23, 59, 59, 175_500_000)
598                    .unwrap(),
599            )
600            .unwrap()
601            .naive_utc();
602
603        assert_eq!(
604            dt.duration_round(TimeDelta::new(-1, 0).unwrap()),
605            Err(RoundingError::DurationExceedsLimit)
606        );
607        assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
608
609        assert_eq!(
610            dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
611            "2016-12-31 23:59:59.180"
612        );
613
614        // round up
615        let dt = Utc
616            .from_local_datetime(
617                &NaiveDate::from_ymd_opt(2012, 12, 12)
618                    .unwrap()
619                    .and_hms_milli_opt(18, 22, 30, 0)
620                    .unwrap(),
621            )
622            .unwrap()
623            .naive_utc();
624        assert_eq!(
625            dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
626            "2012-12-12 18:25:00"
627        );
628        // round down
629        let dt = Utc
630            .from_local_datetime(
631                &NaiveDate::from_ymd_opt(2012, 12, 12)
632                    .unwrap()
633                    .and_hms_milli_opt(18, 22, 29, 999)
634                    .unwrap(),
635            )
636            .unwrap()
637            .naive_utc();
638        assert_eq!(
639            dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
640            "2012-12-12 18:20:00"
641        );
642
643        assert_eq!(
644            dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
645            "2012-12-12 18:20:00"
646        );
647        assert_eq!(
648            dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
649            "2012-12-12 18:30:00"
650        );
651        assert_eq!(
652            dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
653            "2012-12-12 18:00:00"
654        );
655        assert_eq!(
656            dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
657            "2012-12-13 00:00:00"
658        );
659    }
660
661    #[test]
662    fn test_duration_round_pre_epoch() {
663        let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap();
664        assert_eq!(
665            dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
666            "1969-12-12 12:10:00 UTC"
667        );
668    }
669
670    #[test]
671    fn test_duration_trunc() {
672        let dt = Utc
673            .from_local_datetime(
674                &NaiveDate::from_ymd_opt(2016, 12, 31)
675                    .unwrap()
676                    .and_hms_nano_opt(23, 59, 59, 175_500_000)
677                    .unwrap(),
678            )
679            .unwrap();
680
681        assert_eq!(
682            dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()),
683            Err(RoundingError::DurationExceedsLimit)
684        );
685        assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
686
687        assert_eq!(
688            dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
689            "2016-12-31 23:59:59.170 UTC"
690        );
691
692        // would round up
693        let dt = Utc
694            .from_local_datetime(
695                &NaiveDate::from_ymd_opt(2012, 12, 12)
696                    .unwrap()
697                    .and_hms_milli_opt(18, 22, 30, 0)
698                    .unwrap(),
699            )
700            .unwrap();
701        assert_eq!(
702            dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
703            "2012-12-12 18:20:00 UTC"
704        );
705        // would round down
706        let dt = Utc
707            .from_local_datetime(
708                &NaiveDate::from_ymd_opt(2012, 12, 12)
709                    .unwrap()
710                    .and_hms_milli_opt(18, 22, 29, 999)
711                    .unwrap(),
712            )
713            .unwrap();
714        assert_eq!(
715            dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
716            "2012-12-12 18:20:00 UTC"
717        );
718        assert_eq!(
719            dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
720            "2012-12-12 18:20:00 UTC"
721        );
722        assert_eq!(
723            dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
724            "2012-12-12 18:00:00 UTC"
725        );
726        assert_eq!(
727            dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
728            "2012-12-12 18:00:00 UTC"
729        );
730        assert_eq!(
731            dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
732            "2012-12-12 00:00:00 UTC"
733        );
734
735        // timezone east
736        let dt =
737            FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
738        assert_eq!(
739            dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
740            "2020-10-27 00:00:00 +01:00"
741        );
742        assert_eq!(
743            dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
744            "2020-10-22 00:00:00 +01:00"
745        );
746
747        // timezone west
748        let dt =
749            FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
750        assert_eq!(
751            dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
752            "2020-10-27 00:00:00 -01:00"
753        );
754        assert_eq!(
755            dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
756            "2020-10-22 00:00:00 -01:00"
757        );
758    }
759
760    #[test]
761    fn test_duration_trunc_naive() {
762        let dt = Utc
763            .from_local_datetime(
764                &NaiveDate::from_ymd_opt(2016, 12, 31)
765                    .unwrap()
766                    .and_hms_nano_opt(23, 59, 59, 175_500_000)
767                    .unwrap(),
768            )
769            .unwrap()
770            .naive_utc();
771
772        assert_eq!(
773            dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()),
774            Err(RoundingError::DurationExceedsLimit)
775        );
776        assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
777
778        assert_eq!(
779            dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
780            "2016-12-31 23:59:59.170"
781        );
782
783        // would round up
784        let dt = Utc
785            .from_local_datetime(
786                &NaiveDate::from_ymd_opt(2012, 12, 12)
787                    .unwrap()
788                    .and_hms_milli_opt(18, 22, 30, 0)
789                    .unwrap(),
790            )
791            .unwrap()
792            .naive_utc();
793        assert_eq!(
794            dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
795            "2012-12-12 18:20:00"
796        );
797        // would round down
798        let dt = Utc
799            .from_local_datetime(
800                &NaiveDate::from_ymd_opt(2012, 12, 12)
801                    .unwrap()
802                    .and_hms_milli_opt(18, 22, 29, 999)
803                    .unwrap(),
804            )
805            .unwrap()
806            .naive_utc();
807        assert_eq!(
808            dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
809            "2012-12-12 18:20:00"
810        );
811        assert_eq!(
812            dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
813            "2012-12-12 18:20:00"
814        );
815        assert_eq!(
816            dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
817            "2012-12-12 18:00:00"
818        );
819        assert_eq!(
820            dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
821            "2012-12-12 18:00:00"
822        );
823        assert_eq!(
824            dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
825            "2012-12-12 00:00:00"
826        );
827    }
828
829    #[test]
830    fn test_duration_trunc_pre_epoch() {
831        let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap();
832        assert_eq!(
833            dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
834            "1969-12-12 12:10:00 UTC"
835        );
836    }
837
838    #[test]
839    fn issue1010() {
840        let dt = DateTime::from_timestamp(-4_227_854_320, 678_774_288).unwrap();
841        let span = TimeDelta::microseconds(-7_019_067_213_869_040);
842        assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit));
843
844        let dt = DateTime::from_timestamp(320_041_586, 920_103_021).unwrap();
845        let span = TimeDelta::nanoseconds(-8_923_838_508_697_114_584);
846        assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit));
847
848        let dt = DateTime::from_timestamp(-2_621_440, 0).unwrap();
849        let span = TimeDelta::nanoseconds(-9_223_372_036_854_771_421);
850        assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit));
851    }
852
853    #[test]
854    fn test_duration_trunc_close_to_epoch() {
855        let span = TimeDelta::try_minutes(15).unwrap();
856
857        let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap();
858        assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1970-01-01 00:00:00");
859
860        let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap();
861        assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1969-12-31 23:45:00");
862    }
863
864    #[test]
865    fn test_duration_round_close_to_epoch() {
866        let span = TimeDelta::try_minutes(15).unwrap();
867
868        let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap();
869        assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00");
870
871        let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap();
872        assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00");
873    }
874
875    #[test]
876    fn test_duration_round_close_to_min_max() {
877        let span = TimeDelta::nanoseconds(i64::MAX);
878
879        let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 - 1);
880        assert_eq!(
881            dt.duration_round(span).unwrap().to_string(),
882            "1677-09-21 00:12:43.145224193 UTC"
883        );
884
885        let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 + 1);
886        assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC");
887
888        let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 + 1);
889        assert_eq!(
890            dt.duration_round(span).unwrap().to_string(),
891            "2262-04-11 23:47:16.854775807 UTC"
892        );
893
894        let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 - 1);
895        assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC");
896    }
897
898    #[test]
899    fn test_duration_round_up() {
900        let dt = NaiveDate::from_ymd_opt(2016, 12, 31)
901            .unwrap()
902            .and_hms_nano_opt(23, 59, 59, 175_500_000)
903            .unwrap()
904            .and_utc();
905
906        assert_eq!(
907            dt.duration_round_up(TimeDelta::new(-1, 0).unwrap()),
908            Err(RoundingError::DurationExceedsLimit)
909        );
910
911        assert_eq!(
912            dt.duration_round_up(TimeDelta::zero()),
913            Err(RoundingError::DurationExceedsLimit)
914        );
915
916        assert_eq!(dt.duration_round_up(TimeDelta::MAX), Err(RoundingError::DurationExceedsLimit));
917
918        assert_eq!(
919            dt.duration_round_up(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
920            "2016-12-31 23:59:59.180 UTC"
921        );
922
923        // round up
924        let dt = NaiveDate::from_ymd_opt(2012, 12, 12)
925            .unwrap()
926            .and_hms_milli_opt(18, 22, 30, 0)
927            .unwrap()
928            .and_utc();
929
930        assert_eq!(
931            dt.duration_round_up(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
932            "2012-12-12 18:25:00 UTC"
933        );
934
935        assert_eq!(
936            dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
937            "2012-12-12 18:30:00 UTC"
938        );
939        assert_eq!(
940            dt.duration_round_up(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
941            "2012-12-12 18:30:00 UTC"
942        );
943        assert_eq!(
944            dt.duration_round_up(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
945            "2012-12-12 19:00:00 UTC"
946        );
947        assert_eq!(
948            dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
949            "2012-12-13 00:00:00 UTC"
950        );
951
952        // timezone east
953        let dt =
954            FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
955        assert_eq!(
956            dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
957            "2020-10-28 00:00:00 +01:00"
958        );
959        assert_eq!(
960            dt.duration_round_up(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
961            "2020-10-29 00:00:00 +01:00"
962        );
963
964        // timezone west
965        let dt =
966            FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
967        assert_eq!(
968            dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
969            "2020-10-28 00:00:00 -01:00"
970        );
971        assert_eq!(
972            dt.duration_round_up(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
973            "2020-10-29 00:00:00 -01:00"
974        );
975    }
976
977    #[test]
978    fn test_duration_round_up_naive() {
979        let dt = NaiveDate::from_ymd_opt(2016, 12, 31)
980            .unwrap()
981            .and_hms_nano_opt(23, 59, 59, 175_500_000)
982            .unwrap();
983
984        assert_eq!(
985            dt.duration_round_up(TimeDelta::new(-1, 0).unwrap()),
986            Err(RoundingError::DurationExceedsLimit)
987        );
988        assert_eq!(
989            dt.duration_round_up(TimeDelta::zero()),
990            Err(RoundingError::DurationExceedsLimit)
991        );
992
993        assert_eq!(dt.duration_round_up(TimeDelta::MAX), Err(RoundingError::DurationExceedsLimit));
994
995        assert_eq!(
996            dt.duration_round_up(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
997            "2016-12-31 23:59:59.180"
998        );
999
1000        let dt = Utc
1001            .from_local_datetime(
1002                &NaiveDate::from_ymd_opt(2012, 12, 12)
1003                    .unwrap()
1004                    .and_hms_milli_opt(18, 22, 30, 0)
1005                    .unwrap(),
1006            )
1007            .unwrap()
1008            .naive_utc();
1009        assert_eq!(
1010            dt.duration_round_up(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
1011            "2012-12-12 18:25:00"
1012        );
1013        assert_eq!(
1014            dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
1015            "2012-12-12 18:30:00"
1016        );
1017        assert_eq!(
1018            dt.duration_round_up(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
1019            "2012-12-12 18:30:00"
1020        );
1021        assert_eq!(
1022            dt.duration_round_up(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
1023            "2012-12-12 19:00:00"
1024        );
1025        assert_eq!(
1026            dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
1027            "2012-12-13 00:00:00"
1028        );
1029    }
1030
1031    #[test]
1032    fn test_duration_round_up_pre_epoch() {
1033        let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap();
1034        assert_eq!(
1035            dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
1036            "1969-12-12 12:20:00 UTC"
1037        );
1038
1039        let time_delta = TimeDelta::minutes(30);
1040        assert_eq!(
1041            DateTime::UNIX_EPOCH.duration_round_up(time_delta).unwrap().to_string(),
1042            "1970-01-01 00:00:00 UTC"
1043        )
1044    }
1045
1046    #[test]
1047    fn test_duration_round_up_close_to_min_max() {
1048        let mut dt = NaiveDate::from_ymd_opt(2012, 12, 12)
1049            .unwrap()
1050            .and_hms_milli_opt(18, 22, 30, 0)
1051            .unwrap()
1052            .and_utc();
1053
1054        let span = TimeDelta::nanoseconds(i64::MAX);
1055
1056        assert_eq!(
1057            dt.duration_round_up(span).unwrap().to_string(),
1058            DateTime::from_timestamp_nanos(i64::MAX).to_string()
1059        );
1060
1061        dt = DateTime::UNIX_EPOCH + TimeDelta::nanoseconds(1);
1062        assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::from_timestamp_nanos(i64::MAX));
1063
1064        let dt = DateTime::from_timestamp_nanos(1);
1065        assert_eq!(
1066            dt.duration_round_up(span).unwrap().to_string(),
1067            "2262-04-11 23:47:16.854775807 UTC"
1068        );
1069
1070        let dt = DateTime::from_timestamp_nanos(-1);
1071        assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH);
1072
1073        // Rounds to 1677-09-21 00:12:43.145224193 UTC if at i64::MIN.
1074        // because i64::MIN is 1677-09-21 00:12:43.145224192 UTC.
1075        //
1076        //                                                v
1077        // We add 2 to get to 1677-09-21 00:12:43.145224194 UTC
1078        // this issue is because abs(i64::MIN) == i64::MAX + 1
1079        let dt = DateTime::from_timestamp_nanos(i64::MIN + 2);
1080        assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH);
1081    }
1082}