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