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