Coverage Report

Created: 2025-06-02 07:01

/rust/registry/src/index.crates.io-6f17d22bba15001f/chrono-0.4.41/src/round.rs
Line
Count
Source (jump to first uncovered line)
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
0
    // 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(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
184
impl<Tz: TimeZone> DurationRound for DateTime<Tz> {
185
    type Err = RoundingError;
186
187
0
    fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
188
0
        duration_round(self.naive_local(), self, duration)
189
0
    }
190
191
0
    fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
192
0
        duration_trunc(self.naive_local(), self, duration)
193
0
    }
194
195
0
    fn duration_round_up(self, duration: TimeDelta) -> Result<Self, Self::Err> {
196
0
        duration_round_up(self.naive_local(), self, duration)
197
0
    }
198
}
199
200
impl DurationRound for NaiveDateTime {
201
    type Err = RoundingError;
202
203
0
    fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
204
0
        duration_round(self, self, duration)
205
0
    }
206
207
0
    fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
208
0
        duration_trunc(self, self, duration)
209
0
    }
210
211
0
    fn duration_round_up(self, duration: TimeDelta) -> Result<Self, Self::Err> {
212
0
        duration_round_up(self, self, duration)
213
0
    }
214
}
215
216
0
fn duration_round<T>(
217
0
    naive: NaiveDateTime,
218
0
    original: T,
219
0
    duration: TimeDelta,
220
0
) -> Result<T, RoundingError>
221
0
where
222
0
    T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
223
0
{
224
0
    if let Some(span) = duration.num_nanoseconds() {
225
0
        if span <= 0 {
226
0
            return Err(RoundingError::DurationExceedsLimit);
227
0
        }
228
0
        let stamp =
229
0
            naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
230
0
        let delta_down = stamp % span;
231
0
        if delta_down == 0 {
232
0
            Ok(original)
233
        } else {
234
0
            let (delta_up, delta_down) = if delta_down < 0 {
235
0
                (delta_down.abs(), span - delta_down.abs())
236
            } else {
237
0
                (span - delta_down, delta_down)
238
            };
239
0
            if delta_up <= delta_down {
240
0
                Ok(original + TimeDelta::nanoseconds(delta_up))
241
            } else {
242
0
                Ok(original - TimeDelta::nanoseconds(delta_down))
243
            }
244
        }
245
    } else {
246
0
        Err(RoundingError::DurationExceedsLimit)
247
    }
248
0
}
249
250
0
fn duration_trunc<T>(
251
0
    naive: NaiveDateTime,
252
0
    original: T,
253
0
    duration: TimeDelta,
254
0
) -> Result<T, RoundingError>
255
0
where
256
0
    T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
257
0
{
258
0
    if let Some(span) = duration.num_nanoseconds() {
259
0
        if span <= 0 {
260
0
            return Err(RoundingError::DurationExceedsLimit);
261
0
        }
262
0
        let stamp =
263
0
            naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
264
0
        let delta_down = stamp % span;
265
0
        match delta_down.cmp(&0) {
266
0
            Ordering::Equal => Ok(original),
267
0
            Ordering::Greater => Ok(original - TimeDelta::nanoseconds(delta_down)),
268
0
            Ordering::Less => Ok(original - TimeDelta::nanoseconds(span - delta_down.abs())),
269
        }
270
    } else {
271
0
        Err(RoundingError::DurationExceedsLimit)
272
    }
273
0
}
274
275
0
fn duration_round_up<T>(
276
0
    naive: NaiveDateTime,
277
0
    original: T,
278
0
    duration: TimeDelta,
279
0
) -> Result<T, RoundingError>
280
0
where
281
0
    T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
282
0
{
283
0
    if let Some(span) = duration.num_nanoseconds() {
284
0
        if span <= 0 {
285
0
            return Err(RoundingError::DurationExceedsLimit);
286
0
        }
287
0
        let stamp =
288
0
            naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
289
0
        let delta_down = stamp % span;
290
0
        match delta_down.cmp(&0) {
291
0
            Ordering::Equal => Ok(original),
292
0
            Ordering::Greater => Ok(original + TimeDelta::nanoseconds(span - delta_down)),
293
0
            Ordering::Less => Ok(original + TimeDelta::nanoseconds(delta_down.abs())),
294
        }
295
    } else {
296
0
        Err(RoundingError::DurationExceedsLimit)
297
    }
298
0
}
299
300
/// An error from rounding by `TimeDelta`
301
///
302
/// See: [`DurationRound`]
303
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
304
pub 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
341
impl fmt::Display for RoundingError {
342
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
343
0
        match *self {
344
            RoundingError::DurationExceedsTimestamp => {
345
0
                write!(f, "duration in nanoseconds exceeds timestamp")
346
            }
347
            RoundingError::DurationExceedsLimit => {
348
0
                write!(f, "duration exceeds num_nanoseconds limit")
349
            }
350
            RoundingError::TimestampExceedsLimit => {
351
0
                write!(f, "timestamp exceeds num_nanoseconds limit")
352
            }
353
        }
354
0
    }
355
}
356
357
#[cfg(feature = "std")]
358
impl std::error::Error for RoundingError {
359
    #[allow(deprecated)]
360
0
    fn description(&self) -> &str {
361
0
        "error from rounding or truncating with DurationRound"
362
0
    }
363
}
364
365
#[cfg(test)]
366
mod 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
}