Coverage Report

Created: 2025-11-24 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/chrono-0.4.42/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
pub enum RoundingError {
309
    /// Error when the TimeDelta exceeds the TimeDelta from or until the Unix epoch.
310
    ///
311
    /// Note: this error is not produced anymore.
312
    DurationExceedsTimestamp,
313
314
    /// Error when `TimeDelta.num_nanoseconds` exceeds the limit.
315
    ///
316
    /// ``` rust
317
    /// # use chrono::{DurationRound, TimeDelta, RoundingError, NaiveDate};
318
    /// let dt = NaiveDate::from_ymd_opt(2260, 12, 31)
319
    ///     .unwrap()
320
    ///     .and_hms_nano_opt(23, 59, 59, 1_75_500_000)
321
    ///     .unwrap()
322
    ///     .and_utc();
323
    ///
324
    /// assert_eq!(
325
    ///     dt.duration_round(TimeDelta::try_days(300 * 365).unwrap()),
326
    ///     Err(RoundingError::DurationExceedsLimit)
327
    /// );
328
    /// ```
329
    DurationExceedsLimit,
330
331
    /// Error when `DateTime.timestamp_nanos` exceeds the limit.
332
    ///
333
    /// ``` rust
334
    /// # use chrono::{DurationRound, TimeDelta, RoundingError, TimeZone, Utc};
335
    /// let dt = Utc.with_ymd_and_hms(2300, 12, 12, 0, 0, 0).unwrap();
336
    ///
337
    /// assert_eq!(
338
    ///     dt.duration_round(TimeDelta::try_days(1).unwrap()),
339
    ///     Err(RoundingError::TimestampExceedsLimit)
340
    /// );
341
    /// ```
342
    TimestampExceedsLimit,
343
}
344
345
impl fmt::Display for RoundingError {
346
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
347
0
        match *self {
348
            RoundingError::DurationExceedsTimestamp => {
349
0
                write!(f, "duration in nanoseconds exceeds timestamp")
350
            }
351
            RoundingError::DurationExceedsLimit => {
352
0
                write!(f, "duration exceeds num_nanoseconds limit")
353
            }
354
            RoundingError::TimestampExceedsLimit => {
355
0
                write!(f, "timestamp exceeds num_nanoseconds limit")
356
            }
357
        }
358
0
    }
359
}
360
361
#[cfg(feature = "std")]
362
impl std::error::Error for RoundingError {
363
    #[allow(deprecated)]
364
0
    fn description(&self) -> &str {
365
0
        "error from rounding or truncating with DurationRound"
366
0
    }
367
}
368
369
#[cfg(all(not(feature = "std"), feature = "core-error"))]
370
impl core::error::Error for RoundingError {
371
    #[allow(deprecated)]
372
    fn description(&self) -> &str {
373
        "error from rounding or truncating with DurationRound"
374
    }
375
}
376
377
#[cfg(test)]
378
mod tests {
379
    use super::{DurationRound, RoundingError, SubsecRound, TimeDelta};
380
    use crate::Timelike;
381
    use crate::offset::{FixedOffset, TimeZone, Utc};
382
    use crate::{DateTime, NaiveDate};
383
384
    #[test]
385
    fn test_round_subsecs() {
386
        let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
387
        let dt = pst
388
            .from_local_datetime(
389
                &NaiveDate::from_ymd_opt(2018, 1, 11)
390
                    .unwrap()
391
                    .and_hms_nano_opt(10, 5, 13, 84_660_684)
392
                    .unwrap(),
393
            )
394
            .unwrap();
395
396
        assert_eq!(dt.round_subsecs(10), dt);
397
        assert_eq!(dt.round_subsecs(9), dt);
398
        assert_eq!(dt.round_subsecs(8).nanosecond(), 84_660_680);
399
        assert_eq!(dt.round_subsecs(7).nanosecond(), 84_660_700);
400
        assert_eq!(dt.round_subsecs(6).nanosecond(), 84_661_000);
401
        assert_eq!(dt.round_subsecs(5).nanosecond(), 84_660_000);
402
        assert_eq!(dt.round_subsecs(4).nanosecond(), 84_700_000);
403
        assert_eq!(dt.round_subsecs(3).nanosecond(), 85_000_000);
404
        assert_eq!(dt.round_subsecs(2).nanosecond(), 80_000_000);
405
        assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000);
406
407
        assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
408
        assert_eq!(dt.round_subsecs(0).second(), 13);
409
410
        let dt = Utc
411
            .from_local_datetime(
412
                &NaiveDate::from_ymd_opt(2018, 1, 11)
413
                    .unwrap()
414
                    .and_hms_nano_opt(10, 5, 27, 750_500_000)
415
                    .unwrap(),
416
            )
417
            .unwrap();
418
        assert_eq!(dt.round_subsecs(9), dt);
419
        assert_eq!(dt.round_subsecs(4), dt);
420
        assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000);
421
        assert_eq!(dt.round_subsecs(2).nanosecond(), 750_000_000);
422
        assert_eq!(dt.round_subsecs(1).nanosecond(), 800_000_000);
423
424
        assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
425
        assert_eq!(dt.round_subsecs(0).second(), 28);
426
    }
427
428
    #[test]
429
    fn test_round_leap_nanos() {
430
        let dt = Utc
431
            .from_local_datetime(
432
                &NaiveDate::from_ymd_opt(2016, 12, 31)
433
                    .unwrap()
434
                    .and_hms_nano_opt(23, 59, 59, 1_750_500_000)
435
                    .unwrap(),
436
            )
437
            .unwrap();
438
        assert_eq!(dt.round_subsecs(9), dt);
439
        assert_eq!(dt.round_subsecs(4), dt);
440
        assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000);
441
        assert_eq!(dt.round_subsecs(1).nanosecond(), 1_800_000_000);
442
        assert_eq!(dt.round_subsecs(1).second(), 59);
443
444
        assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
445
        assert_eq!(dt.round_subsecs(0).second(), 0);
446
    }
447
448
    #[test]
449
    fn test_trunc_subsecs() {
450
        let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
451
        let dt = pst
452
            .from_local_datetime(
453
                &NaiveDate::from_ymd_opt(2018, 1, 11)
454
                    .unwrap()
455
                    .and_hms_nano_opt(10, 5, 13, 84_660_684)
456
                    .unwrap(),
457
            )
458
            .unwrap();
459
460
        assert_eq!(dt.trunc_subsecs(10), dt);
461
        assert_eq!(dt.trunc_subsecs(9), dt);
462
        assert_eq!(dt.trunc_subsecs(8).nanosecond(), 84_660_680);
463
        assert_eq!(dt.trunc_subsecs(7).nanosecond(), 84_660_600);
464
        assert_eq!(dt.trunc_subsecs(6).nanosecond(), 84_660_000);
465
        assert_eq!(dt.trunc_subsecs(5).nanosecond(), 84_660_000);
466
        assert_eq!(dt.trunc_subsecs(4).nanosecond(), 84_600_000);
467
        assert_eq!(dt.trunc_subsecs(3).nanosecond(), 84_000_000);
468
        assert_eq!(dt.trunc_subsecs(2).nanosecond(), 80_000_000);
469
        assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0);
470
471
        assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
472
        assert_eq!(dt.trunc_subsecs(0).second(), 13);
473
474
        let dt = pst
475
            .from_local_datetime(
476
                &NaiveDate::from_ymd_opt(2018, 1, 11)
477
                    .unwrap()
478
                    .and_hms_nano_opt(10, 5, 27, 750_500_000)
479
                    .unwrap(),
480
            )
481
            .unwrap();
482
        assert_eq!(dt.trunc_subsecs(9), dt);
483
        assert_eq!(dt.trunc_subsecs(4), dt);
484
        assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000);
485
        assert_eq!(dt.trunc_subsecs(2).nanosecond(), 750_000_000);
486
        assert_eq!(dt.trunc_subsecs(1).nanosecond(), 700_000_000);
487
488
        assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
489
        assert_eq!(dt.trunc_subsecs(0).second(), 27);
490
    }
491
492
    #[test]
493
    fn test_trunc_leap_nanos() {
494
        let dt = Utc
495
            .from_local_datetime(
496
                &NaiveDate::from_ymd_opt(2016, 12, 31)
497
                    .unwrap()
498
                    .and_hms_nano_opt(23, 59, 59, 1_750_500_000)
499
                    .unwrap(),
500
            )
501
            .unwrap();
502
        assert_eq!(dt.trunc_subsecs(9), dt);
503
        assert_eq!(dt.trunc_subsecs(4), dt);
504
        assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000);
505
        assert_eq!(dt.trunc_subsecs(1).nanosecond(), 1_700_000_000);
506
        assert_eq!(dt.trunc_subsecs(1).second(), 59);
507
508
        assert_eq!(dt.trunc_subsecs(0).nanosecond(), 1_000_000_000);
509
        assert_eq!(dt.trunc_subsecs(0).second(), 59);
510
    }
511
512
    #[test]
513
    fn test_duration_round() {
514
        let dt = Utc
515
            .from_local_datetime(
516
                &NaiveDate::from_ymd_opt(2016, 12, 31)
517
                    .unwrap()
518
                    .and_hms_nano_opt(23, 59, 59, 175_500_000)
519
                    .unwrap(),
520
            )
521
            .unwrap();
522
523
        assert_eq!(
524
            dt.duration_round(TimeDelta::new(-1, 0).unwrap()),
525
            Err(RoundingError::DurationExceedsLimit)
526
        );
527
        assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
528
529
        assert_eq!(
530
            dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
531
            "2016-12-31 23:59:59.180 UTC"
532
        );
533
534
        // round up
535
        let dt = Utc
536
            .from_local_datetime(
537
                &NaiveDate::from_ymd_opt(2012, 12, 12)
538
                    .unwrap()
539
                    .and_hms_milli_opt(18, 22, 30, 0)
540
                    .unwrap(),
541
            )
542
            .unwrap();
543
        assert_eq!(
544
            dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
545
            "2012-12-12 18:25:00 UTC"
546
        );
547
        // round down
548
        let dt = Utc
549
            .from_local_datetime(
550
                &NaiveDate::from_ymd_opt(2012, 12, 12)
551
                    .unwrap()
552
                    .and_hms_milli_opt(18, 22, 29, 999)
553
                    .unwrap(),
554
            )
555
            .unwrap();
556
        assert_eq!(
557
            dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
558
            "2012-12-12 18:20:00 UTC"
559
        );
560
561
        assert_eq!(
562
            dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
563
            "2012-12-12 18:20:00 UTC"
564
        );
565
        assert_eq!(
566
            dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
567
            "2012-12-12 18:30:00 UTC"
568
        );
569
        assert_eq!(
570
            dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
571
            "2012-12-12 18:00:00 UTC"
572
        );
573
        assert_eq!(
574
            dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
575
            "2012-12-13 00:00:00 UTC"
576
        );
577
578
        // timezone east
579
        let dt =
580
            FixedOffset::east_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
        // timezone west
591
        let dt =
592
            FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
593
        assert_eq!(
594
            dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
595
            "2020-10-28 00:00:00 -01:00"
596
        );
597
        assert_eq!(
598
            dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
599
            "2020-10-29 00:00:00 -01:00"
600
        );
601
    }
602
603
    #[test]
604
    fn test_duration_round_naive() {
605
        let dt = Utc
606
            .from_local_datetime(
607
                &NaiveDate::from_ymd_opt(2016, 12, 31)
608
                    .unwrap()
609
                    .and_hms_nano_opt(23, 59, 59, 175_500_000)
610
                    .unwrap(),
611
            )
612
            .unwrap()
613
            .naive_utc();
614
615
        assert_eq!(
616
            dt.duration_round(TimeDelta::new(-1, 0).unwrap()),
617
            Err(RoundingError::DurationExceedsLimit)
618
        );
619
        assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
620
621
        assert_eq!(
622
            dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
623
            "2016-12-31 23:59:59.180"
624
        );
625
626
        // round up
627
        let dt = Utc
628
            .from_local_datetime(
629
                &NaiveDate::from_ymd_opt(2012, 12, 12)
630
                    .unwrap()
631
                    .and_hms_milli_opt(18, 22, 30, 0)
632
                    .unwrap(),
633
            )
634
            .unwrap()
635
            .naive_utc();
636
        assert_eq!(
637
            dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
638
            "2012-12-12 18:25:00"
639
        );
640
        // round down
641
        let dt = Utc
642
            .from_local_datetime(
643
                &NaiveDate::from_ymd_opt(2012, 12, 12)
644
                    .unwrap()
645
                    .and_hms_milli_opt(18, 22, 29, 999)
646
                    .unwrap(),
647
            )
648
            .unwrap()
649
            .naive_utc();
650
        assert_eq!(
651
            dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
652
            "2012-12-12 18:20:00"
653
        );
654
655
        assert_eq!(
656
            dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
657
            "2012-12-12 18:20:00"
658
        );
659
        assert_eq!(
660
            dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
661
            "2012-12-12 18:30:00"
662
        );
663
        assert_eq!(
664
            dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
665
            "2012-12-12 18:00:00"
666
        );
667
        assert_eq!(
668
            dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
669
            "2012-12-13 00:00:00"
670
        );
671
    }
672
673
    #[test]
674
    fn test_duration_round_pre_epoch() {
675
        let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap();
676
        assert_eq!(
677
            dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
678
            "1969-12-12 12:10:00 UTC"
679
        );
680
    }
681
682
    #[test]
683
    fn test_duration_trunc() {
684
        let dt = Utc
685
            .from_local_datetime(
686
                &NaiveDate::from_ymd_opt(2016, 12, 31)
687
                    .unwrap()
688
                    .and_hms_nano_opt(23, 59, 59, 175_500_000)
689
                    .unwrap(),
690
            )
691
            .unwrap();
692
693
        assert_eq!(
694
            dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()),
695
            Err(RoundingError::DurationExceedsLimit)
696
        );
697
        assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
698
699
        assert_eq!(
700
            dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
701
            "2016-12-31 23:59:59.170 UTC"
702
        );
703
704
        // would round up
705
        let dt = Utc
706
            .from_local_datetime(
707
                &NaiveDate::from_ymd_opt(2012, 12, 12)
708
                    .unwrap()
709
                    .and_hms_milli_opt(18, 22, 30, 0)
710
                    .unwrap(),
711
            )
712
            .unwrap();
713
        assert_eq!(
714
            dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
715
            "2012-12-12 18:20:00 UTC"
716
        );
717
        // would round down
718
        let dt = Utc
719
            .from_local_datetime(
720
                &NaiveDate::from_ymd_opt(2012, 12, 12)
721
                    .unwrap()
722
                    .and_hms_milli_opt(18, 22, 29, 999)
723
                    .unwrap(),
724
            )
725
            .unwrap();
726
        assert_eq!(
727
            dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
728
            "2012-12-12 18:20:00 UTC"
729
        );
730
        assert_eq!(
731
            dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
732
            "2012-12-12 18:20:00 UTC"
733
        );
734
        assert_eq!(
735
            dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
736
            "2012-12-12 18:00:00 UTC"
737
        );
738
        assert_eq!(
739
            dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
740
            "2012-12-12 18:00:00 UTC"
741
        );
742
        assert_eq!(
743
            dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
744
            "2012-12-12 00:00:00 UTC"
745
        );
746
747
        // timezone east
748
        let dt =
749
            FixedOffset::east_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
        // timezone west
760
        let dt =
761
            FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
762
        assert_eq!(
763
            dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
764
            "2020-10-27 00:00:00 -01:00"
765
        );
766
        assert_eq!(
767
            dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
768
            "2020-10-22 00:00:00 -01:00"
769
        );
770
    }
771
772
    #[test]
773
    fn test_duration_trunc_naive() {
774
        let dt = Utc
775
            .from_local_datetime(
776
                &NaiveDate::from_ymd_opt(2016, 12, 31)
777
                    .unwrap()
778
                    .and_hms_nano_opt(23, 59, 59, 175_500_000)
779
                    .unwrap(),
780
            )
781
            .unwrap()
782
            .naive_utc();
783
784
        assert_eq!(
785
            dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()),
786
            Err(RoundingError::DurationExceedsLimit)
787
        );
788
        assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
789
790
        assert_eq!(
791
            dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
792
            "2016-12-31 23:59:59.170"
793
        );
794
795
        // would round up
796
        let dt = Utc
797
            .from_local_datetime(
798
                &NaiveDate::from_ymd_opt(2012, 12, 12)
799
                    .unwrap()
800
                    .and_hms_milli_opt(18, 22, 30, 0)
801
                    .unwrap(),
802
            )
803
            .unwrap()
804
            .naive_utc();
805
        assert_eq!(
806
            dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
807
            "2012-12-12 18:20:00"
808
        );
809
        // would round down
810
        let dt = Utc
811
            .from_local_datetime(
812
                &NaiveDate::from_ymd_opt(2012, 12, 12)
813
                    .unwrap()
814
                    .and_hms_milli_opt(18, 22, 29, 999)
815
                    .unwrap(),
816
            )
817
            .unwrap()
818
            .naive_utc();
819
        assert_eq!(
820
            dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
821
            "2012-12-12 18:20:00"
822
        );
823
        assert_eq!(
824
            dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
825
            "2012-12-12 18:20:00"
826
        );
827
        assert_eq!(
828
            dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
829
            "2012-12-12 18:00:00"
830
        );
831
        assert_eq!(
832
            dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
833
            "2012-12-12 18:00:00"
834
        );
835
        assert_eq!(
836
            dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
837
            "2012-12-12 00:00:00"
838
        );
839
    }
840
841
    #[test]
842
    fn test_duration_trunc_pre_epoch() {
843
        let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap();
844
        assert_eq!(
845
            dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
846
            "1969-12-12 12:10:00 UTC"
847
        );
848
    }
849
850
    #[test]
851
    fn issue1010() {
852
        let dt = DateTime::from_timestamp(-4_227_854_320, 678_774_288).unwrap();
853
        let span = TimeDelta::microseconds(-7_019_067_213_869_040);
854
        assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit));
855
856
        let dt = DateTime::from_timestamp(320_041_586, 920_103_021).unwrap();
857
        let span = TimeDelta::nanoseconds(-8_923_838_508_697_114_584);
858
        assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit));
859
860
        let dt = DateTime::from_timestamp(-2_621_440, 0).unwrap();
861
        let span = TimeDelta::nanoseconds(-9_223_372_036_854_771_421);
862
        assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit));
863
    }
864
865
    #[test]
866
    fn test_duration_trunc_close_to_epoch() {
867
        let span = TimeDelta::try_minutes(15).unwrap();
868
869
        let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap();
870
        assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1970-01-01 00:00:00");
871
872
        let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap();
873
        assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1969-12-31 23:45:00");
874
    }
875
876
    #[test]
877
    fn test_duration_round_close_to_epoch() {
878
        let span = TimeDelta::try_minutes(15).unwrap();
879
880
        let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap();
881
        assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00");
882
883
        let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap();
884
        assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00");
885
    }
886
887
    #[test]
888
    fn test_duration_round_close_to_min_max() {
889
        let span = TimeDelta::nanoseconds(i64::MAX);
890
891
        let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 - 1);
892
        assert_eq!(
893
            dt.duration_round(span).unwrap().to_string(),
894
            "1677-09-21 00:12:43.145224193 UTC"
895
        );
896
897
        let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 + 1);
898
        assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC");
899
900
        let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 + 1);
901
        assert_eq!(
902
            dt.duration_round(span).unwrap().to_string(),
903
            "2262-04-11 23:47:16.854775807 UTC"
904
        );
905
906
        let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 - 1);
907
        assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC");
908
    }
909
910
    #[test]
911
    fn test_duration_round_up() {
912
        let dt = NaiveDate::from_ymd_opt(2016, 12, 31)
913
            .unwrap()
914
            .and_hms_nano_opt(23, 59, 59, 175_500_000)
915
            .unwrap()
916
            .and_utc();
917
918
        assert_eq!(
919
            dt.duration_round_up(TimeDelta::new(-1, 0).unwrap()),
920
            Err(RoundingError::DurationExceedsLimit)
921
        );
922
923
        assert_eq!(
924
            dt.duration_round_up(TimeDelta::zero()),
925
            Err(RoundingError::DurationExceedsLimit)
926
        );
927
928
        assert_eq!(dt.duration_round_up(TimeDelta::MAX), Err(RoundingError::DurationExceedsLimit));
929
930
        assert_eq!(
931
            dt.duration_round_up(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
932
            "2016-12-31 23:59:59.180 UTC"
933
        );
934
935
        // round up
936
        let dt = NaiveDate::from_ymd_opt(2012, 12, 12)
937
            .unwrap()
938
            .and_hms_milli_opt(18, 22, 30, 0)
939
            .unwrap()
940
            .and_utc();
941
942
        assert_eq!(
943
            dt.duration_round_up(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
944
            "2012-12-12 18:25:00 UTC"
945
        );
946
947
        assert_eq!(
948
            dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
949
            "2012-12-12 18:30:00 UTC"
950
        );
951
        assert_eq!(
952
            dt.duration_round_up(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
953
            "2012-12-12 18:30:00 UTC"
954
        );
955
        assert_eq!(
956
            dt.duration_round_up(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
957
            "2012-12-12 19:00:00 UTC"
958
        );
959
        assert_eq!(
960
            dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
961
            "2012-12-13 00:00:00 UTC"
962
        );
963
964
        // timezone east
965
        let dt =
966
            FixedOffset::east_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
        // timezone west
977
        let dt =
978
            FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
979
        assert_eq!(
980
            dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
981
            "2020-10-28 00:00:00 -01:00"
982
        );
983
        assert_eq!(
984
            dt.duration_round_up(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
985
            "2020-10-29 00:00:00 -01:00"
986
        );
987
    }
988
989
    #[test]
990
    fn test_duration_round_up_naive() {
991
        let dt = NaiveDate::from_ymd_opt(2016, 12, 31)
992
            .unwrap()
993
            .and_hms_nano_opt(23, 59, 59, 175_500_000)
994
            .unwrap();
995
996
        assert_eq!(
997
            dt.duration_round_up(TimeDelta::new(-1, 0).unwrap()),
998
            Err(RoundingError::DurationExceedsLimit)
999
        );
1000
        assert_eq!(
1001
            dt.duration_round_up(TimeDelta::zero()),
1002
            Err(RoundingError::DurationExceedsLimit)
1003
        );
1004
1005
        assert_eq!(dt.duration_round_up(TimeDelta::MAX), Err(RoundingError::DurationExceedsLimit));
1006
1007
        assert_eq!(
1008
            dt.duration_round_up(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
1009
            "2016-12-31 23:59:59.180"
1010
        );
1011
1012
        let dt = Utc
1013
            .from_local_datetime(
1014
                &NaiveDate::from_ymd_opt(2012, 12, 12)
1015
                    .unwrap()
1016
                    .and_hms_milli_opt(18, 22, 30, 0)
1017
                    .unwrap(),
1018
            )
1019
            .unwrap()
1020
            .naive_utc();
1021
        assert_eq!(
1022
            dt.duration_round_up(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
1023
            "2012-12-12 18:25:00"
1024
        );
1025
        assert_eq!(
1026
            dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
1027
            "2012-12-12 18:30:00"
1028
        );
1029
        assert_eq!(
1030
            dt.duration_round_up(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
1031
            "2012-12-12 18:30:00"
1032
        );
1033
        assert_eq!(
1034
            dt.duration_round_up(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
1035
            "2012-12-12 19:00:00"
1036
        );
1037
        assert_eq!(
1038
            dt.duration_round_up(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
1039
            "2012-12-13 00:00:00"
1040
        );
1041
    }
1042
1043
    #[test]
1044
    fn test_duration_round_up_pre_epoch() {
1045
        let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap();
1046
        assert_eq!(
1047
            dt.duration_round_up(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
1048
            "1969-12-12 12:20:00 UTC"
1049
        );
1050
1051
        let time_delta = TimeDelta::minutes(30);
1052
        assert_eq!(
1053
            DateTime::UNIX_EPOCH.duration_round_up(time_delta).unwrap().to_string(),
1054
            "1970-01-01 00:00:00 UTC"
1055
        )
1056
    }
1057
1058
    #[test]
1059
    fn test_duration_round_up_close_to_min_max() {
1060
        let mut dt = NaiveDate::from_ymd_opt(2012, 12, 12)
1061
            .unwrap()
1062
            .and_hms_milli_opt(18, 22, 30, 0)
1063
            .unwrap()
1064
            .and_utc();
1065
1066
        let span = TimeDelta::nanoseconds(i64::MAX);
1067
1068
        assert_eq!(
1069
            dt.duration_round_up(span).unwrap().to_string(),
1070
            DateTime::from_timestamp_nanos(i64::MAX).to_string()
1071
        );
1072
1073
        dt = DateTime::UNIX_EPOCH + TimeDelta::nanoseconds(1);
1074
        assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::from_timestamp_nanos(i64::MAX));
1075
1076
        let dt = DateTime::from_timestamp_nanos(1);
1077
        assert_eq!(
1078
            dt.duration_round_up(span).unwrap().to_string(),
1079
            "2262-04-11 23:47:16.854775807 UTC"
1080
        );
1081
1082
        let dt = DateTime::from_timestamp_nanos(-1);
1083
        assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH);
1084
1085
        // Rounds to 1677-09-21 00:12:43.145224193 UTC if at i64::MIN.
1086
        // because i64::MIN is 1677-09-21 00:12:43.145224192 UTC.
1087
        //
1088
        //                                                v
1089
        // We add 2 to get to 1677-09-21 00:12:43.145224194 UTC
1090
        // this issue is because abs(i64::MIN) == i64::MAX + 1
1091
        let dt = DateTime::from_timestamp_nanos(i64::MIN + 2);
1092
        assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH);
1093
    }
1094
}