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