Coverage Report

Created: 2026-06-08 06:46

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