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
158impl<Tz: TimeZone> DurationRound for DateTime<Tz> {
159 type Err = RoundingError;
160
161 fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
162 duration_round(self.naive_local(), self, duration)
163 }
164
165 fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
166 duration_trunc(self.naive_local(), self, duration)
167 }
168}
169
170impl DurationRound for NaiveDateTime {
171 type Err = RoundingError;
172
173 fn duration_round(self, duration: TimeDelta) -> Result<Self, Self::Err> {
174 duration_round(self, self, duration)
175 }
176
177 fn duration_trunc(self, duration: TimeDelta) -> Result<Self, Self::Err> {
178 duration_trunc(self, self, duration)
179 }
180}
181
182fn duration_round<T>(
183 naive: NaiveDateTime,
184 original: T,
185 duration: TimeDelta,
186) -> Result<T, RoundingError>
187where
188 T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
189{
190 if let Some(span) = duration.num_nanoseconds() {
191 if span <= 0 {
192 return Err(RoundingError::DurationExceedsLimit);
193 }
194 let stamp =
195 naive.and_utc().timestamp_nanos_opt().ok_or(RoundingError::TimestampExceedsLimit)?;
196 let delta_down = stamp % span;
197 if delta_down == 0 {
198 Ok(original)
199 } else {
200 let (delta_up, delta_down) = if delta_down < 0 {
201 (delta_down.abs(), span - delta_down.abs())
202 } else {
203 (span - delta_down, delta_down)
204 };
205 if delta_up <= delta_down {
206 Ok(original + TimeDelta::nanoseconds(delta_up))
207 } else {
208 Ok(original - TimeDelta::nanoseconds(delta_down))
209 }
210 }
211 } else {
212 Err(RoundingError::DurationExceedsLimit)
213 }
214}
215
216fn duration_trunc<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 match delta_down.cmp(&0) {
232 Ordering::Equal => Ok(original),
233 Ordering::Greater => Ok(original - TimeDelta::nanoseconds(delta_down)),
234 Ordering::Less => Ok(original - TimeDelta::nanoseconds(span - delta_down.abs())),
235 }
236 } else {
237 Err(RoundingError::DurationExceedsLimit)
238 }
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Copy)]
245pub enum RoundingError {
246 DurationExceedsTimestamp,
250
251 DurationExceedsLimit,
267
268 TimestampExceedsLimit,
280}
281
282impl fmt::Display for RoundingError {
283 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
284 match *self {
285 RoundingError::DurationExceedsTimestamp => {
286 write!(f, "duration in nanoseconds exceeds timestamp")
287 }
288 RoundingError::DurationExceedsLimit => {
289 write!(f, "duration exceeds num_nanoseconds limit")
290 }
291 RoundingError::TimestampExceedsLimit => {
292 write!(f, "timestamp exceeds num_nanoseconds limit")
293 }
294 }
295 }
296}
297
298#[cfg(feature = "std")]
299impl std::error::Error for RoundingError {
300 #[allow(deprecated)]
301 fn description(&self) -> &str {
302 "error from rounding or truncating with DurationRound"
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::{DurationRound, RoundingError, SubsecRound, TimeDelta};
309 use crate::offset::{FixedOffset, TimeZone, Utc};
310 use crate::Timelike;
311 use crate::{DateTime, NaiveDate};
312
313 #[test]
314 fn test_round_subsecs() {
315 let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
316 let dt = pst
317 .from_local_datetime(
318 &NaiveDate::from_ymd_opt(2018, 1, 11)
319 .unwrap()
320 .and_hms_nano_opt(10, 5, 13, 84_660_684)
321 .unwrap(),
322 )
323 .unwrap();
324
325 assert_eq!(dt.round_subsecs(10), dt);
326 assert_eq!(dt.round_subsecs(9), dt);
327 assert_eq!(dt.round_subsecs(8).nanosecond(), 84_660_680);
328 assert_eq!(dt.round_subsecs(7).nanosecond(), 84_660_700);
329 assert_eq!(dt.round_subsecs(6).nanosecond(), 84_661_000);
330 assert_eq!(dt.round_subsecs(5).nanosecond(), 84_660_000);
331 assert_eq!(dt.round_subsecs(4).nanosecond(), 84_700_000);
332 assert_eq!(dt.round_subsecs(3).nanosecond(), 85_000_000);
333 assert_eq!(dt.round_subsecs(2).nanosecond(), 80_000_000);
334 assert_eq!(dt.round_subsecs(1).nanosecond(), 100_000_000);
335
336 assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
337 assert_eq!(dt.round_subsecs(0).second(), 13);
338
339 let dt = Utc
340 .from_local_datetime(
341 &NaiveDate::from_ymd_opt(2018, 1, 11)
342 .unwrap()
343 .and_hms_nano_opt(10, 5, 27, 750_500_000)
344 .unwrap(),
345 )
346 .unwrap();
347 assert_eq!(dt.round_subsecs(9), dt);
348 assert_eq!(dt.round_subsecs(4), dt);
349 assert_eq!(dt.round_subsecs(3).nanosecond(), 751_000_000);
350 assert_eq!(dt.round_subsecs(2).nanosecond(), 750_000_000);
351 assert_eq!(dt.round_subsecs(1).nanosecond(), 800_000_000);
352
353 assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
354 assert_eq!(dt.round_subsecs(0).second(), 28);
355 }
356
357 #[test]
358 fn test_round_leap_nanos() {
359 let dt = Utc
360 .from_local_datetime(
361 &NaiveDate::from_ymd_opt(2016, 12, 31)
362 .unwrap()
363 .and_hms_nano_opt(23, 59, 59, 1_750_500_000)
364 .unwrap(),
365 )
366 .unwrap();
367 assert_eq!(dt.round_subsecs(9), dt);
368 assert_eq!(dt.round_subsecs(4), dt);
369 assert_eq!(dt.round_subsecs(2).nanosecond(), 1_750_000_000);
370 assert_eq!(dt.round_subsecs(1).nanosecond(), 1_800_000_000);
371 assert_eq!(dt.round_subsecs(1).second(), 59);
372
373 assert_eq!(dt.round_subsecs(0).nanosecond(), 0);
374 assert_eq!(dt.round_subsecs(0).second(), 0);
375 }
376
377 #[test]
378 fn test_trunc_subsecs() {
379 let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap();
380 let dt = pst
381 .from_local_datetime(
382 &NaiveDate::from_ymd_opt(2018, 1, 11)
383 .unwrap()
384 .and_hms_nano_opt(10, 5, 13, 84_660_684)
385 .unwrap(),
386 )
387 .unwrap();
388
389 assert_eq!(dt.trunc_subsecs(10), dt);
390 assert_eq!(dt.trunc_subsecs(9), dt);
391 assert_eq!(dt.trunc_subsecs(8).nanosecond(), 84_660_680);
392 assert_eq!(dt.trunc_subsecs(7).nanosecond(), 84_660_600);
393 assert_eq!(dt.trunc_subsecs(6).nanosecond(), 84_660_000);
394 assert_eq!(dt.trunc_subsecs(5).nanosecond(), 84_660_000);
395 assert_eq!(dt.trunc_subsecs(4).nanosecond(), 84_600_000);
396 assert_eq!(dt.trunc_subsecs(3).nanosecond(), 84_000_000);
397 assert_eq!(dt.trunc_subsecs(2).nanosecond(), 80_000_000);
398 assert_eq!(dt.trunc_subsecs(1).nanosecond(), 0);
399
400 assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
401 assert_eq!(dt.trunc_subsecs(0).second(), 13);
402
403 let dt = pst
404 .from_local_datetime(
405 &NaiveDate::from_ymd_opt(2018, 1, 11)
406 .unwrap()
407 .and_hms_nano_opt(10, 5, 27, 750_500_000)
408 .unwrap(),
409 )
410 .unwrap();
411 assert_eq!(dt.trunc_subsecs(9), dt);
412 assert_eq!(dt.trunc_subsecs(4), dt);
413 assert_eq!(dt.trunc_subsecs(3).nanosecond(), 750_000_000);
414 assert_eq!(dt.trunc_subsecs(2).nanosecond(), 750_000_000);
415 assert_eq!(dt.trunc_subsecs(1).nanosecond(), 700_000_000);
416
417 assert_eq!(dt.trunc_subsecs(0).nanosecond(), 0);
418 assert_eq!(dt.trunc_subsecs(0).second(), 27);
419 }
420
421 #[test]
422 fn test_trunc_leap_nanos() {
423 let dt = Utc
424 .from_local_datetime(
425 &NaiveDate::from_ymd_opt(2016, 12, 31)
426 .unwrap()
427 .and_hms_nano_opt(23, 59, 59, 1_750_500_000)
428 .unwrap(),
429 )
430 .unwrap();
431 assert_eq!(dt.trunc_subsecs(9), dt);
432 assert_eq!(dt.trunc_subsecs(4), dt);
433 assert_eq!(dt.trunc_subsecs(2).nanosecond(), 1_750_000_000);
434 assert_eq!(dt.trunc_subsecs(1).nanosecond(), 1_700_000_000);
435 assert_eq!(dt.trunc_subsecs(1).second(), 59);
436
437 assert_eq!(dt.trunc_subsecs(0).nanosecond(), 1_000_000_000);
438 assert_eq!(dt.trunc_subsecs(0).second(), 59);
439 }
440
441 #[test]
442 fn test_duration_round() {
443 let dt = Utc
444 .from_local_datetime(
445 &NaiveDate::from_ymd_opt(2016, 12, 31)
446 .unwrap()
447 .and_hms_nano_opt(23, 59, 59, 175_500_000)
448 .unwrap(),
449 )
450 .unwrap();
451
452 assert_eq!(
453 dt.duration_round(TimeDelta::new(-1, 0).unwrap()),
454 Err(RoundingError::DurationExceedsLimit)
455 );
456 assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
457
458 assert_eq!(
459 dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
460 "2016-12-31 23:59:59.180 UTC"
461 );
462
463 let dt = Utc
465 .from_local_datetime(
466 &NaiveDate::from_ymd_opt(2012, 12, 12)
467 .unwrap()
468 .and_hms_milli_opt(18, 22, 30, 0)
469 .unwrap(),
470 )
471 .unwrap();
472 assert_eq!(
473 dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
474 "2012-12-12 18:25:00 UTC"
475 );
476 let dt = Utc
478 .from_local_datetime(
479 &NaiveDate::from_ymd_opt(2012, 12, 12)
480 .unwrap()
481 .and_hms_milli_opt(18, 22, 29, 999)
482 .unwrap(),
483 )
484 .unwrap();
485 assert_eq!(
486 dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
487 "2012-12-12 18:20:00 UTC"
488 );
489
490 assert_eq!(
491 dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
492 "2012-12-12 18:20:00 UTC"
493 );
494 assert_eq!(
495 dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
496 "2012-12-12 18:30:00 UTC"
497 );
498 assert_eq!(
499 dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
500 "2012-12-12 18:00:00 UTC"
501 );
502 assert_eq!(
503 dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
504 "2012-12-13 00:00:00 UTC"
505 );
506
507 let dt =
509 FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
510 assert_eq!(
511 dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
512 "2020-10-28 00:00:00 +01:00"
513 );
514 assert_eq!(
515 dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
516 "2020-10-29 00:00:00 +01:00"
517 );
518
519 let dt =
521 FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
522 assert_eq!(
523 dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
524 "2020-10-28 00:00:00 -01:00"
525 );
526 assert_eq!(
527 dt.duration_round(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
528 "2020-10-29 00:00:00 -01:00"
529 );
530 }
531
532 #[test]
533 fn test_duration_round_naive() {
534 let dt = Utc
535 .from_local_datetime(
536 &NaiveDate::from_ymd_opt(2016, 12, 31)
537 .unwrap()
538 .and_hms_nano_opt(23, 59, 59, 175_500_000)
539 .unwrap(),
540 )
541 .unwrap()
542 .naive_utc();
543
544 assert_eq!(
545 dt.duration_round(TimeDelta::new(-1, 0).unwrap()),
546 Err(RoundingError::DurationExceedsLimit)
547 );
548 assert_eq!(dt.duration_round(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
549
550 assert_eq!(
551 dt.duration_round(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
552 "2016-12-31 23:59:59.180"
553 );
554
555 let dt = Utc
557 .from_local_datetime(
558 &NaiveDate::from_ymd_opt(2012, 12, 12)
559 .unwrap()
560 .and_hms_milli_opt(18, 22, 30, 0)
561 .unwrap(),
562 )
563 .unwrap()
564 .naive_utc();
565 assert_eq!(
566 dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
567 "2012-12-12 18:25:00"
568 );
569 let dt = Utc
571 .from_local_datetime(
572 &NaiveDate::from_ymd_opt(2012, 12, 12)
573 .unwrap()
574 .and_hms_milli_opt(18, 22, 29, 999)
575 .unwrap(),
576 )
577 .unwrap()
578 .naive_utc();
579 assert_eq!(
580 dt.duration_round(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
581 "2012-12-12 18:20:00"
582 );
583
584 assert_eq!(
585 dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
586 "2012-12-12 18:20:00"
587 );
588 assert_eq!(
589 dt.duration_round(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
590 "2012-12-12 18:30:00"
591 );
592 assert_eq!(
593 dt.duration_round(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
594 "2012-12-12 18:00:00"
595 );
596 assert_eq!(
597 dt.duration_round(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
598 "2012-12-13 00:00:00"
599 );
600 }
601
602 #[test]
603 fn test_duration_round_pre_epoch() {
604 let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap();
605 assert_eq!(
606 dt.duration_round(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
607 "1969-12-12 12:10:00 UTC"
608 );
609 }
610
611 #[test]
612 fn test_duration_trunc() {
613 let dt = Utc
614 .from_local_datetime(
615 &NaiveDate::from_ymd_opt(2016, 12, 31)
616 .unwrap()
617 .and_hms_nano_opt(23, 59, 59, 175_500_000)
618 .unwrap(),
619 )
620 .unwrap();
621
622 assert_eq!(
623 dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()),
624 Err(RoundingError::DurationExceedsLimit)
625 );
626 assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
627
628 assert_eq!(
629 dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
630 "2016-12-31 23:59:59.170 UTC"
631 );
632
633 let dt = Utc
635 .from_local_datetime(
636 &NaiveDate::from_ymd_opt(2012, 12, 12)
637 .unwrap()
638 .and_hms_milli_opt(18, 22, 30, 0)
639 .unwrap(),
640 )
641 .unwrap();
642 assert_eq!(
643 dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
644 "2012-12-12 18:20:00 UTC"
645 );
646 let dt = Utc
648 .from_local_datetime(
649 &NaiveDate::from_ymd_opt(2012, 12, 12)
650 .unwrap()
651 .and_hms_milli_opt(18, 22, 29, 999)
652 .unwrap(),
653 )
654 .unwrap();
655 assert_eq!(
656 dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
657 "2012-12-12 18:20:00 UTC"
658 );
659 assert_eq!(
660 dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
661 "2012-12-12 18:20:00 UTC"
662 );
663 assert_eq!(
664 dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
665 "2012-12-12 18:00:00 UTC"
666 );
667 assert_eq!(
668 dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
669 "2012-12-12 18:00:00 UTC"
670 );
671 assert_eq!(
672 dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
673 "2012-12-12 00:00:00 UTC"
674 );
675
676 let dt =
678 FixedOffset::east_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
679 assert_eq!(
680 dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
681 "2020-10-27 00:00:00 +01:00"
682 );
683 assert_eq!(
684 dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
685 "2020-10-22 00:00:00 +01:00"
686 );
687
688 let dt =
690 FixedOffset::west_opt(3600).unwrap().with_ymd_and_hms(2020, 10, 27, 15, 0, 0).unwrap();
691 assert_eq!(
692 dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
693 "2020-10-27 00:00:00 -01:00"
694 );
695 assert_eq!(
696 dt.duration_trunc(TimeDelta::try_weeks(1).unwrap()).unwrap().to_string(),
697 "2020-10-22 00:00:00 -01:00"
698 );
699 }
700
701 #[test]
702 fn test_duration_trunc_naive() {
703 let dt = Utc
704 .from_local_datetime(
705 &NaiveDate::from_ymd_opt(2016, 12, 31)
706 .unwrap()
707 .and_hms_nano_opt(23, 59, 59, 175_500_000)
708 .unwrap(),
709 )
710 .unwrap()
711 .naive_utc();
712
713 assert_eq!(
714 dt.duration_trunc(TimeDelta::new(-1, 0).unwrap()),
715 Err(RoundingError::DurationExceedsLimit)
716 );
717 assert_eq!(dt.duration_trunc(TimeDelta::zero()), Err(RoundingError::DurationExceedsLimit));
718
719 assert_eq!(
720 dt.duration_trunc(TimeDelta::try_milliseconds(10).unwrap()).unwrap().to_string(),
721 "2016-12-31 23:59:59.170"
722 );
723
724 let dt = Utc
726 .from_local_datetime(
727 &NaiveDate::from_ymd_opt(2012, 12, 12)
728 .unwrap()
729 .and_hms_milli_opt(18, 22, 30, 0)
730 .unwrap(),
731 )
732 .unwrap()
733 .naive_utc();
734 assert_eq!(
735 dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
736 "2012-12-12 18:20:00"
737 );
738 let dt = Utc
740 .from_local_datetime(
741 &NaiveDate::from_ymd_opt(2012, 12, 12)
742 .unwrap()
743 .and_hms_milli_opt(18, 22, 29, 999)
744 .unwrap(),
745 )
746 .unwrap()
747 .naive_utc();
748 assert_eq!(
749 dt.duration_trunc(TimeDelta::try_minutes(5).unwrap()).unwrap().to_string(),
750 "2012-12-12 18:20:00"
751 );
752 assert_eq!(
753 dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
754 "2012-12-12 18:20:00"
755 );
756 assert_eq!(
757 dt.duration_trunc(TimeDelta::try_minutes(30).unwrap()).unwrap().to_string(),
758 "2012-12-12 18:00:00"
759 );
760 assert_eq!(
761 dt.duration_trunc(TimeDelta::try_hours(1).unwrap()).unwrap().to_string(),
762 "2012-12-12 18:00:00"
763 );
764 assert_eq!(
765 dt.duration_trunc(TimeDelta::try_days(1).unwrap()).unwrap().to_string(),
766 "2012-12-12 00:00:00"
767 );
768 }
769
770 #[test]
771 fn test_duration_trunc_pre_epoch() {
772 let dt = Utc.with_ymd_and_hms(1969, 12, 12, 12, 12, 12).unwrap();
773 assert_eq!(
774 dt.duration_trunc(TimeDelta::try_minutes(10).unwrap()).unwrap().to_string(),
775 "1969-12-12 12:10:00 UTC"
776 );
777 }
778
779 #[test]
780 fn issue1010() {
781 let dt = DateTime::from_timestamp(-4_227_854_320, 678_774_288).unwrap();
782 let span = TimeDelta::microseconds(-7_019_067_213_869_040);
783 assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit));
784
785 let dt = DateTime::from_timestamp(320_041_586, 920_103_021).unwrap();
786 let span = TimeDelta::nanoseconds(-8_923_838_508_697_114_584);
787 assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit));
788
789 let dt = DateTime::from_timestamp(-2_621_440, 0).unwrap();
790 let span = TimeDelta::nanoseconds(-9_223_372_036_854_771_421);
791 assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit));
792 }
793
794 #[test]
795 fn test_duration_trunc_close_to_epoch() {
796 let span = TimeDelta::try_minutes(15).unwrap();
797
798 let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap();
799 assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1970-01-01 00:00:00");
800
801 let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap();
802 assert_eq!(dt.duration_trunc(span).unwrap().to_string(), "1969-12-31 23:45:00");
803 }
804
805 #[test]
806 fn test_duration_round_close_to_epoch() {
807 let span = TimeDelta::try_minutes(15).unwrap();
808
809 let dt = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(0, 0, 15).unwrap();
810 assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00");
811
812 let dt = NaiveDate::from_ymd_opt(1969, 12, 31).unwrap().and_hms_opt(23, 59, 45).unwrap();
813 assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00");
814 }
815
816 #[test]
817 fn test_duration_round_close_to_min_max() {
818 let span = TimeDelta::nanoseconds(i64::MAX);
819
820 let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 - 1);
821 assert_eq!(
822 dt.duration_round(span).unwrap().to_string(),
823 "1677-09-21 00:12:43.145224193 UTC"
824 );
825
826 let dt = DateTime::from_timestamp_nanos(i64::MIN / 2 + 1);
827 assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC");
828
829 let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 + 1);
830 assert_eq!(
831 dt.duration_round(span).unwrap().to_string(),
832 "2262-04-11 23:47:16.854775807 UTC"
833 );
834
835 let dt = DateTime::from_timestamp_nanos(i64::MAX / 2 - 1);
836 assert_eq!(dt.duration_round(span).unwrap().to_string(), "1970-01-01 00:00:00 UTC");
837 }
838}