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