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 #[cfg(feature = "std")]
109 type Err: std::error::Error;
110
111 #[cfg(all(not(feature = "std"), feature = "core-error"))]
113 type Err: core::error::Error;
114
115 #[cfg(all(not(feature = "std"), not(feature = "core-error")))]
117 type Err: fmt::Debug + fmt::Display;
118
119 fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err>;
139
140 fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err>;
160
161 fn duration_round_up(self, duration: TimeDelta) -> Result<Self, Self::Err>;
186}
187
188impl<Tz: TimeZone> DurationRound for DateTime<Tz> {
189 type Err = RoundingError;
190
191 fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
192 duration_round(self.naive_local(), self, duration)
193 }
194
195 fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
196 duration_trunc(self.naive_local(), self, duration)
197 }
198
199 fn duration_round_up(self, duration: TimeDelta) -> Result<Self, Self::Err> {
200 duration_round_up(self.naive_local(), self, duration)
201 }
202}
203
204impl DurationRound for NaiveDateTime {
205 type Err = RoundingError;
206
207 fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
208 duration_round(self, self, duration)
209 }
210
211 fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
212 duration_trunc(self, self, duration)
213 }
214
215 fn duration_round_up(self, duration: TimeDelta) -> Result<Self, Self::Err> {
216 duration_round_up(self, self, duration)
217 }
218}
219
220fn duration_round<T>(
221 naive: NaiveDateTime,
222 original: T,
223 duration: TimeDelta,
224) -> Result<T, RoundingError>
225where
226 T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
227{
228 if let Some(span) = duration.num_nanoseconds() {
229 if span <= 0 {
230 return Err(RoundingError::DurationExceedsLimit);
231 }
232 let stamp =
233 naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
234 let delta_down = stamp % span;
235 if delta_down == 0 {
236 Ok(original)
237 } else {
238 let (delta_up, delta_down) = if delta_down < 0 {
239 (delta_down.abs(), span - delta_down.abs())
240 } else {
241 (span - delta_down, delta_down)
242 };
243 if delta_up <= delta_down {
244 Ok(original + TimeDelta::nanoseconds(delta_up))
245 } else {
246 Ok(original - TimeDelta::nanoseconds(delta_down))
247 }
248 }
249 } else {
250 Err(RoundingError::DurationExceedsLimit)
251 }
252}
253
254fn duration_trunc<T>(
255 naive: NaiveDateTime,
256 original: T,
257 duration: TimeDelta,
258) -> Result<T, RoundingError>
259where
260 T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
261{
262 if let Some(span) = duration.num_nanoseconds() {
263 if span <= 0 {
264 return Err(RoundingError::DurationExceedsLimit);
265 }
266 let stamp =
267 naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
268 let delta_down = stamp % span;
269 match delta_down.cmp(&0) {
270 Ordering::Equal => Ok(original),
271 Ordering::Greater => Ok(original - TimeDelta::nanoseconds(delta_down)),
272 Ordering::Less => Ok(original - TimeDelta::nanoseconds(span - delta_down.abs())),
273 }
274 } else {
275 Err(RoundingError::DurationExceedsLimit)
276 }
277}
278
279fn duration_round_up<T>(
280 naive: NaiveDateTime,
281 original: T,
282 duration: TimeDelta,
283) -> Result<T, RoundingError>
284where
285 T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
286{
287 if let Some(span) = duration.num_nanoseconds() {
288 if span <= 0 {
289 return Err(RoundingError::DurationExceedsLimit);
290 }
291 let stamp =
292 naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
293 let delta_down = stamp % span;
294 match delta_down.cmp(&0) {
295 Ordering::Equal => Ok(original),
296 Ordering::Greater => Ok(original + TimeDelta::nanoseconds(span - delta_down)),
297 Ordering::Less => Ok(original + TimeDelta::nanoseconds(delta_down.abs())),
298 }
299 } else {
300 Err(RoundingError::DurationExceedsLimit)
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, Copy)]
308pub enum RoundingError {
309 DurationExceedsTimestamp,
313
314 DurationExceedsLimit,
330
331 TimestampExceedsLimit,
343}
344
345impl fmt::Display for RoundingError {
346 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
347 match *self {
348 RoundingError::DurationExceedsTimestamp => {
349 write!(f, "duration in nanoseconds exceeds timestamp")
350 }
351 RoundingError::DurationExceedsLimit => {
352 write!(f, "duration exceeds num_nanoseconds limit")
353 }
354 RoundingError::TimestampExceedsLimit => {
355 write!(f, "timestamp exceeds num_nanoseconds limit")
356 }
357 }
358 }
359}
360
361#[cfg(feature = "std")]
362impl std::error::Error for RoundingError {
363 #[allow(deprecated)]
364 fn description(&self) -> &str {
365 "error from rounding or truncating with DurationRound"
366 }
367}
368
369#[cfg(all(not(feature = "std"), feature = "core-error"))]
370impl 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)]
378mod 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let dt = DateTime::from_timestamp_nanos(i64::MIN + 2);
1092 assert_eq!(dt.duration_round_up(span).unwrap(), DateTime::UNIX_EPOCH);
1093 }
1094}