1#![allow(clippy::disallowed_types)]
5
6use std::{
7 cmp,
8 fmt::{self, Display, Formatter, Write},
9 hash::{Hash, Hasher},
10 ops,
11 str::FromStr,
12 time::Duration as StdDuration,
13};
14
15use serde::{Deserialize, Serialize};
16
17use crate::{
18 error::{Error, TemporalKind, TypeError},
19 fragment::Fragment,
20 reifydb_assertions,
21 value::temporal::parse::duration::parse_duration,
22};
23
24#[repr(C)]
25#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
26pub struct Duration {
27 months: i32,
28 days: i32,
29 nanos: i64,
30}
31
32const NANOS_PER_DAY: i64 = 86_400_000_000_000;
33const SECONDS_PER_DAY: i64 = 86_400;
34const DAYS_PER_MONTH: i64 = 30;
35const SECONDS_PER_YEAR: i64 = 31_557_600;
36
37impl Default for Duration {
38 fn default() -> Self {
39 Self::zero()
40 }
41}
42
43impl Duration {
44 fn overflow_err(message: impl Into<String>) -> TypeError {
45 TypeError::Temporal {
46 kind: TemporalKind::DurationOverflow {
47 message: message.into(),
48 },
49 message: "duration overflow".to_string(),
50 fragment: Fragment::None,
51 }
52 }
53
54 fn mixed_sign_err(days: i32, nanos: i64) -> TypeError {
55 TypeError::Temporal {
56 kind: TemporalKind::DurationMixedSign {
57 days,
58 nanos,
59 },
60 message: format!(
61 "duration days and nanos must share the same sign, got days={days}, nanos={nanos}"
62 ),
63 fragment: Fragment::None,
64 }
65 }
66
67 fn normalized(months: i32, days: i32, nanos: i64) -> Result<Self, Box<TypeError>> {
68 let extra_days = i32::try_from(nanos / NANOS_PER_DAY)
69 .map_err(|_| Box::new(Self::overflow_err("the days component overflows Duration range")))?;
70 let nanos = nanos % NANOS_PER_DAY;
71 let days = days
72 .checked_add(extra_days)
73 .ok_or_else(|| Box::new(Self::overflow_err("the days component overflows Duration range")))?;
74
75 if (days > 0 && nanos < 0) || (days < 0 && nanos > 0) {
76 return Err(Box::new(Self::mixed_sign_err(days, nanos)));
77 }
78
79 Ok(Self {
80 months,
81 days,
82 nanos,
83 })
84 }
85
86 pub fn new(months: i32, days: i32, nanos: i64) -> Result<Self, Box<TypeError>> {
87 Self::normalized(months, days, nanos)
88 }
89
90 pub fn from_seconds(seconds: i64) -> Result<Self, Box<TypeError>> {
91 Self::normalized(0, 0, seconds * 1_000_000_000)
92 }
93
94 pub fn from_milliseconds(milliseconds: i64) -> Result<Self, Box<TypeError>> {
95 Self::normalized(0, 0, milliseconds * 1_000_000)
96 }
97
98 pub fn from_microseconds(microseconds: i64) -> Result<Self, Box<TypeError>> {
99 Self::normalized(0, 0, microseconds * 1_000)
100 }
101
102 pub fn from_micros_infallible(microseconds: u64) -> Self {
103 const US_PER_DAY: u64 = 86_400_000_000;
104 let whole_days = microseconds / US_PER_DAY;
105 let remainder_us = microseconds % US_PER_DAY;
106 let days = if whole_days > i32::MAX as u64 {
107 i32::MAX
108 } else {
109 whole_days as i32
110 };
111 Self {
112 months: 0,
113 days,
114 nanos: (remainder_us * 1_000) as i64,
115 }
116 }
117
118 pub fn from_nanos_infallible(nanoseconds: u64) -> Self {
119 const NS_PER_DAY: u64 = 86_400_000_000_000;
120 let whole_days = nanoseconds / NS_PER_DAY;
121 let remainder_ns = nanoseconds % NS_PER_DAY;
122 let days = if whole_days > i32::MAX as u64 {
123 i32::MAX
124 } else {
125 whole_days as i32
126 };
127 Self {
128 months: 0,
129 days,
130 nanos: remainder_ns as i64,
131 }
132 }
133
134 pub const fn from_nanoseconds_const(nanoseconds: i64) -> Self {
135 reifydb_assertions! {
136 let day_count = nanoseconds / NANOS_PER_DAY;
137 assert!(
138 day_count >= i32::MIN as i64 && day_count <= i32::MAX as i64,
139 "nanoseconds overflows Duration range"
140 );
141 }
142 Self {
143 months: 0,
144 days: (nanoseconds / NANOS_PER_DAY) as i32,
145 nanos: nanoseconds % NANOS_PER_DAY,
146 }
147 }
148
149 pub const fn from_microseconds_const(microseconds: i64) -> Self {
150 reifydb_assertions! {
151 assert!(
152 microseconds.checked_mul(1_000).is_some(),
153 "microseconds overflows Duration range"
154 );
155 }
156 Self::from_nanoseconds_const(microseconds * 1_000)
157 }
158
159 pub const fn from_milliseconds_const(milliseconds: i64) -> Self {
160 reifydb_assertions! {
161 assert!(
162 milliseconds.checked_mul(1_000_000).is_some(),
163 "milliseconds overflows Duration range"
164 );
165 }
166 Self::from_nanoseconds_const(milliseconds * 1_000_000)
167 }
168
169 pub const fn from_seconds_const(seconds: i64) -> Self {
170 reifydb_assertions! {
171 assert!(
172 seconds.checked_mul(1_000_000_000).is_some(),
173 "seconds overflows Duration range"
174 );
175 }
176 Self::from_nanoseconds_const(seconds * 1_000_000_000)
177 }
178
179 pub const fn from_minutes_const(minutes: i64) -> Self {
180 reifydb_assertions! {
181 assert!(
182 minutes.checked_mul(60_000_000_000).is_some(),
183 "minutes overflows Duration range"
184 );
185 }
186 Self::from_nanoseconds_const(minutes * 60_000_000_000)
187 }
188
189 pub const fn from_hours_const(hours: i64) -> Self {
190 reifydb_assertions! {
191 assert!(
192 hours.checked_mul(3_600_000_000_000).is_some(),
193 "hours overflows Duration range"
194 );
195 }
196 Self::from_nanoseconds_const(hours * 3_600_000_000_000)
197 }
198
199 pub fn from_nanoseconds(nanoseconds: i64) -> Result<Self, Box<TypeError>> {
200 Self::normalized(0, 0, nanoseconds)
201 }
202
203 pub fn from_minutes(minutes: i64) -> Result<Self, Box<TypeError>> {
204 Self::normalized(0, 0, minutes * 60 * 1_000_000_000)
205 }
206
207 pub fn from_hours(hours: i64) -> Result<Self, Box<TypeError>> {
208 Self::normalized(0, 0, hours * 60 * 60 * 1_000_000_000)
209 }
210
211 pub fn from_days(days: i64) -> Result<Self, Box<TypeError>> {
212 let days = i32::try_from(days)
213 .map_err(|_| Box::new(Self::overflow_err(format!("{} days overflows Duration range", days))))?;
214 Self::normalized(0, days, 0)
215 }
216
217 pub fn from_weeks(weeks: i64) -> Result<Self, Box<TypeError>> {
218 let days = weeks.checked_mul(7).ok_or_else(|| {
219 Box::new(Self::overflow_err(format!("{} weeks overflows Duration range", weeks)))
220 })?;
221 let days = i32::try_from(days).map_err(|_| {
222 Box::new(Self::overflow_err(format!("{} weeks overflows Duration range", weeks)))
223 })?;
224 Self::normalized(0, days, 0)
225 }
226
227 pub fn from_months(months: i64) -> Result<Self, Box<TypeError>> {
228 let months = i32::try_from(months).map_err(|_| {
229 Box::new(Self::overflow_err(format!("{} months overflows Duration range", months)))
230 })?;
231 Self::normalized(months, 0, 0)
232 }
233
234 pub fn from_years(years: i64) -> Result<Self, Box<TypeError>> {
235 let months = years.checked_mul(12).ok_or_else(|| {
236 Box::new(Self::overflow_err(format!("{} years overflows Duration range", years)))
237 })?;
238 let months = i32::try_from(months).map_err(|_| {
239 Box::new(Self::overflow_err(format!("{} years overflows Duration range", years)))
240 })?;
241 Self::normalized(months, 0, 0)
242 }
243
244 pub fn zero() -> Self {
245 Self {
246 months: 0,
247 days: 0,
248 nanos: 0,
249 }
250 }
251
252 pub const MAX: Self = Self {
253 months: i32::MAX,
254 days: i32::MAX,
255 nanos: NANOS_PER_DAY - 1,
256 };
257
258 fn checked_total(
259 &self,
260 per_year: i64,
261 per_month: i64,
262 per_day: i64,
263 sub_day: i64,
264 unit: &str,
265 ) -> Result<i64, Box<TypeError>> {
266 let years = (self.months / 12) as i64;
267 let rem_months = (self.months % 12) as i64;
268 years.checked_mul(per_year)
269 .and_then(|a| rem_months.checked_mul(per_month).and_then(|b| a.checked_add(b)))
270 .and_then(|a| (self.days as i64).checked_mul(per_day).and_then(|b| a.checked_add(b)))
271 .and_then(|a| a.checked_add(sub_day))
272 .ok_or_else(|| Box::new(Self::overflow_err(format!("Duration overflows {} range", unit))))
273 }
274
275 pub fn seconds(&self) -> Result<i64, Box<TypeError>> {
276 self.checked_total(
277 SECONDS_PER_YEAR,
278 DAYS_PER_MONTH * SECONDS_PER_DAY,
279 SECONDS_PER_DAY,
280 self.nanos / 1_000_000_000,
281 "seconds",
282 )
283 }
284
285 pub fn milliseconds(&self) -> Result<i64, Box<TypeError>> {
286 self.checked_total(
287 SECONDS_PER_YEAR * 1_000,
288 DAYS_PER_MONTH * SECONDS_PER_DAY * 1_000,
289 SECONDS_PER_DAY * 1_000,
290 self.nanos / 1_000_000,
291 "milliseconds",
292 )
293 }
294
295 pub fn microseconds(&self) -> Result<i64, Box<TypeError>> {
296 self.checked_total(
297 SECONDS_PER_YEAR * 1_000_000,
298 DAYS_PER_MONTH * SECONDS_PER_DAY * 1_000_000,
299 SECONDS_PER_DAY * 1_000_000,
300 self.nanos / 1_000,
301 "microseconds",
302 )
303 }
304
305 pub fn nanoseconds(&self) -> Result<i64, Box<TypeError>> {
306 self.checked_total(
307 SECONDS_PER_YEAR * 1_000_000_000,
308 NANOS_PER_DAY * DAYS_PER_MONTH,
309 NANOS_PER_DAY,
310 self.nanos,
311 "nanoseconds",
312 )
313 }
314
315 pub fn get_months(&self) -> i32 {
316 self.months
317 }
318
319 pub fn get_days(&self) -> i32 {
320 self.days
321 }
322
323 pub fn get_nanos(&self) -> i64 {
324 self.nanos
325 }
326
327 pub fn as_nanos(&self) -> Result<i64, Box<TypeError>> {
328 self.nanoseconds()
329 }
330
331 pub fn is_positive(&self) -> bool {
332 self.months >= 0
333 && self.days >= 0 && self.nanos >= 0
334 && (self.months > 0 || self.days > 0 || self.nanos > 0)
335 }
336
337 pub fn is_negative(&self) -> bool {
338 self.months <= 0
339 && self.days <= 0 && self.nanos <= 0
340 && (self.months < 0 || self.days < 0 || self.nanos < 0)
341 }
342
343 pub fn abs(&self) -> Self {
344 Self {
345 months: self.months.abs(),
346 days: self.days.abs(),
347 nanos: self.nanos.abs(),
348 }
349 }
350
351 pub fn negate(&self) -> Self {
352 Self {
353 months: -self.months,
354 days: -self.days,
355 nanos: -self.nanos,
356 }
357 }
358
359 pub fn to_iso_string(&self) -> String {
360 if self.months == 0 && self.days == 0 && self.nanos == 0 {
361 return "PT0S".to_string();
362 }
363
364 let mut result = String::from("P");
365
366 let years = self.months / 12;
367 let months = self.months % 12;
368
369 if years != 0 {
370 write!(result, "{}Y", years).unwrap();
371 }
372 if months != 0 {
373 write!(result, "{}M", months).unwrap();
374 }
375
376 let total_seconds = self.nanos / 1_000_000_000;
377 let remaining_nanos = self.nanos % 1_000_000_000;
378
379 let extra_days = total_seconds / 86400;
380 let remaining_seconds = total_seconds % 86400;
381
382 let display_days = self.days + extra_days as i32;
383 let hours = remaining_seconds / 3600;
384 let minutes = (remaining_seconds % 3600) / 60;
385 let seconds = remaining_seconds % 60;
386
387 if display_days != 0 {
388 write!(result, "{}D", display_days).unwrap();
389 }
390
391 if hours != 0 || minutes != 0 || seconds != 0 || remaining_nanos != 0 {
392 result.push('T');
393
394 if hours != 0 {
395 write!(result, "{}H", hours).unwrap();
396 }
397 if minutes != 0 {
398 write!(result, "{}M", minutes).unwrap();
399 }
400 if seconds != 0 || remaining_nanos != 0 {
401 if remaining_nanos != 0 {
402 let fractional = remaining_nanos as f64 / 1_000_000_000.0;
403 let total_seconds_f = seconds as f64 + fractional;
404 let formatted_str = format!("{:.9}", total_seconds_f);
405 let formatted = formatted_str.trim_end_matches('0').trim_end_matches('.');
406 write!(result, "{}S", formatted).unwrap();
407 } else {
408 write!(result, "{}S", seconds).unwrap();
409 }
410 }
411 }
412
413 result
414 }
415}
416
417impl Duration {
418 fn total_nanos(&self) -> i128 {
419 self.months as i128 * DAYS_PER_MONTH as i128 * NANOS_PER_DAY as i128
420 + self.days as i128 * NANOS_PER_DAY as i128
421 + self.nanos as i128
422 }
423}
424
425impl PartialEq for Duration {
426 fn eq(&self, other: &Self) -> bool {
427 self.total_nanos() == other.total_nanos()
428 }
429}
430
431impl Eq for Duration {}
432
433impl Hash for Duration {
434 fn hash<H: Hasher>(&self, state: &mut H) {
435 self.total_nanos().hash(state);
436 }
437}
438
439impl PartialOrd for Duration {
440 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
441 Some(self.cmp(other))
442 }
443}
444
445impl Ord for Duration {
446 fn cmp(&self, other: &Self) -> cmp::Ordering {
447 self.total_nanos().cmp(&other.total_nanos())
448 }
449}
450
451impl Duration {
452 pub fn try_add(self, rhs: Self) -> Result<Self, Box<TypeError>> {
453 let months = self
454 .months
455 .checked_add(rhs.months)
456 .ok_or_else(|| Box::new(Self::overflow_err("the months component overflows Duration range")))?;
457 let days = self
458 .days
459 .checked_add(rhs.days)
460 .ok_or_else(|| Box::new(Self::overflow_err("the days component overflows Duration range")))?;
461 let nanos = self.nanos.checked_add(rhs.nanos).ok_or_else(|| {
462 Box::new(Self::overflow_err("the nanoseconds component overflows Duration range"))
463 })?;
464 Self::normalized(months, days, nanos)
465 }
466
467 pub fn try_sub(self, rhs: Self) -> Result<Self, Box<TypeError>> {
468 let months = self
469 .months
470 .checked_sub(rhs.months)
471 .ok_or_else(|| Box::new(Self::overflow_err("the months component overflows Duration range")))?;
472 let days = self
473 .days
474 .checked_sub(rhs.days)
475 .ok_or_else(|| Box::new(Self::overflow_err("the days component overflows Duration range")))?;
476 let nanos = self.nanos.checked_sub(rhs.nanos).ok_or_else(|| {
477 Box::new(Self::overflow_err("the nanoseconds component overflows Duration range"))
478 })?;
479 Self::normalized(months, days, nanos)
480 }
481
482 pub fn try_mul(self, rhs: i64) -> Result<Self, Box<TypeError>> {
483 let rhs_i32 = i32::try_from(rhs).map_err(|_| {
484 Box::new(Self::overflow_err(format!("multiplier {} overflows Duration range", rhs)))
485 })?;
486 let months = self
487 .months
488 .checked_mul(rhs_i32)
489 .ok_or_else(|| Box::new(Self::overflow_err("the months component overflows Duration range")))?;
490 let days = self
491 .days
492 .checked_mul(rhs_i32)
493 .ok_or_else(|| Box::new(Self::overflow_err("the days component overflows Duration range")))?;
494 let nanos = self.nanos.checked_mul(rhs).ok_or_else(|| {
495 Box::new(Self::overflow_err("the nanoseconds component overflows Duration range"))
496 })?;
497 Self::normalized(months, days, nanos)
498 }
499
500 pub fn div_duration(self, rhs: Self) -> Result<i64, Box<TypeError>> {
501 let divisor = rhs.as_nanos()?;
502 if divisor == 0 {
503 return Err(Box::new(Self::overflow_err("cannot divide a duration by a zero duration")));
504 }
505 Ok(self.as_nanos()? / divisor)
506 }
507
508 fn saturating_normalized(months: i32, days: i32, nanos: i64) -> Self {
509 let total = days as i128 * NANOS_PER_DAY as i128 + nanos as i128;
510 let new_days = (total / NANOS_PER_DAY as i128).clamp(i32::MIN as i128, i32::MAX as i128) as i32;
511 let new_nanos = (total % NANOS_PER_DAY as i128) as i64;
512 Self {
513 months,
514 days: new_days,
515 nanos: new_nanos,
516 }
517 }
518
519 pub fn saturating_add(self, rhs: Self) -> Self {
520 Self::saturating_normalized(
521 self.months.saturating_add(rhs.months),
522 self.days.saturating_add(rhs.days),
523 self.nanos.saturating_add(rhs.nanos),
524 )
525 }
526
527 pub fn saturating_sub(self, rhs: Self) -> Self {
528 Self::saturating_normalized(
529 self.months.saturating_sub(rhs.months),
530 self.days.saturating_sub(rhs.days),
531 self.nanos.saturating_sub(rhs.nanos),
532 )
533 }
534
535 pub fn saturating_mul(self, rhs: i64) -> Self {
536 let months = (self.months as i128 * rhs as i128).clamp(i32::MIN as i128, i32::MAX as i128) as i32;
537 let days = (self.days as i128 * rhs as i128).clamp(i32::MIN as i128, i32::MAX as i128) as i32;
538 Self::saturating_normalized(months, days, self.nanos.saturating_mul(rhs))
539 }
540}
541
542impl ops::Add for Duration {
543 type Output = Self;
544 fn add(self, rhs: Self) -> Self {
545 self.try_add(rhs).expect("duration add overflow")
546 }
547}
548
549impl ops::Sub for Duration {
550 type Output = Self;
551 fn sub(self, rhs: Self) -> Self {
552 self.try_sub(rhs).expect("duration sub overflow")
553 }
554}
555
556impl ops::Mul<i64> for Duration {
557 type Output = Self;
558 fn mul(self, rhs: i64) -> Self {
559 self.try_mul(rhs).expect("duration mul overflow")
560 }
561}
562
563impl Display for Duration {
564 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
565 if self.months == 0 && self.days == 0 && self.nanos == 0 {
566 return write!(f, "0s");
567 }
568
569 let years = self.months / 12;
570 let months = self.months % 12;
571
572 let total_seconds = self.nanos / 1_000_000_000;
573 let remaining_nanos = self.nanos % 1_000_000_000;
574
575 let extra_days = total_seconds / 86400;
576 let remaining_seconds = total_seconds % 86400;
577
578 let display_days = self.days + extra_days as i32;
579 let hours = remaining_seconds / 3600;
580 let minutes = (remaining_seconds % 3600) / 60;
581 let seconds = remaining_seconds % 60;
582
583 let abs_remaining = remaining_nanos.abs();
584 let ms = abs_remaining / 1_000_000;
585 let us = (abs_remaining % 1_000_000) / 1_000;
586 let ns = abs_remaining % 1_000;
587
588 if years != 0 {
589 write!(f, "{}y", years)?;
590 }
591 if months != 0 {
592 write!(f, "{}mo", months)?;
593 }
594 if display_days != 0 {
595 write!(f, "{}d", display_days)?;
596 }
597 if hours != 0 {
598 write!(f, "{}h", hours)?;
599 }
600 if minutes != 0 {
601 write!(f, "{}m", minutes)?;
602 }
603 if seconds != 0 {
604 write!(f, "{}s", seconds)?;
605 }
606
607 if ms != 0 || us != 0 || ns != 0 {
608 if remaining_nanos < 0
609 && seconds == 0 && hours == 0
610 && minutes == 0 && display_days == 0
611 && years == 0 && months == 0
612 {
613 write!(f, "-")?;
614 }
615 if ms != 0 {
616 write!(f, "{}ms", ms)?;
617 }
618 if us != 0 {
619 write!(f, "{}us", us)?;
620 }
621 if ns != 0 {
622 write!(f, "{}ns", ns)?;
623 }
624 }
625
626 Ok(())
627 }
628}
629
630impl Duration {
631 pub fn is_zero(&self) -> bool {
632 self.months == 0 && self.days == 0 && self.nanos == 0
633 }
634
635 pub fn to_std(&self) -> StdDuration {
636 let nanos = self.nanoseconds().map(|n| n.max(0)).unwrap_or(0);
637 StdDuration::from_nanos(nanos as u64)
638 }
639
640 pub fn from_std(duration: StdDuration) -> Self {
641 let nanos = i64::try_from(duration.as_nanos()).expect("std Duration exceeds i64 nanoseconds");
642 Self::from_nanoseconds(nanos).expect("std Duration nanoseconds within range")
643 }
644}
645
646impl From<Duration> for StdDuration {
647 fn from(duration: Duration) -> Self {
648 duration.to_std()
649 }
650}
651
652impl From<StdDuration> for Duration {
653 fn from(duration: StdDuration) -> Self {
654 Self::from_std(duration)
655 }
656}
657
658impl FromStr for Duration {
659 type Err = Error;
660
661 fn from_str(s: &str) -> Result<Self, Self::Err> {
662 parse_duration(Fragment::internal(s.trim()))
663 }
664}
665
666#[cfg(test)]
667pub mod tests {
668 use super::*;
669 use crate::error::TemporalKind;
670
671 mod max {
672 use super::*;
673
674 #[test]
675 fn adding_max_to_a_duration_saturates_instead_of_panicking() {
676 let saturated = Duration::from_hours_const(1).saturating_add(Duration::MAX);
679 assert_eq!(saturated.months, i32::MAX);
680 assert_eq!(saturated.days, i32::MAX);
681 }
682
683 #[test]
684 fn max_outranks_every_ordinary_window() {
685 assert_eq!(Duration::MAX, Duration::MAX);
688 for window in [
689 Duration::from_seconds_const(1),
690 Duration::from_hours_const(24),
691 Duration::from_seconds_const(365 * 86_400),
692 ] {
693 assert!(window < Duration::MAX, "{window:?} must be shorter than forever");
694 }
695 }
696
697 #[test]
698 fn max_is_normalised() {
699 assert!(Duration::MAX.nanos < NANOS_PER_DAY);
702 assert!(Duration::MAX.nanos >= 0);
703 }
704 }
705
706 mod ordering {
707 use std::{
708 cmp::Ordering,
709 collections::hash_map::DefaultHasher,
710 hash::{Hash, Hasher},
711 };
712
713 use super::*;
714
715 fn hash_of(duration: Duration) -> u64 {
716 let mut hasher = DefaultHasher::new();
717 duration.hash(&mut hasher);
718 hasher.finish()
719 }
720
721 #[test]
722 fn a_month_sorts_below_a_longer_span_expressed_in_days() {
723 let month = Duration::from_months(1).unwrap();
726 let thirty_one_days = Duration::from_days(31).unwrap();
727
728 assert_eq!(
729 month.cmp(&thirty_one_days),
730 Ordering::Less,
731 "one month is thirty days and must sort below thirty-one days"
732 );
733 assert!(month < thirty_one_days, "the comparison operators must agree with cmp");
734 }
735
736 #[test]
737 fn days_shorter_than_a_month_sort_below_it() {
738 let month = Duration::from_months(1).unwrap();
741 let twenty_nine_days = Duration::from_days(29).unwrap();
742
743 assert_eq!(
744 twenty_nine_days.cmp(&month),
745 Ordering::Less,
746 "twenty-nine days is shorter than a thirty day month and must sort below it"
747 );
748 }
749
750 #[test]
751 fn equal_spans_in_different_units_are_equal_and_hash_alike() {
752 let month = Duration::from_months(1).unwrap();
755 let thirty_days = Duration::from_days(30).unwrap();
756
757 assert_eq!(
758 month.cmp(&thirty_days),
759 Ordering::Equal,
760 "the same elapsed span must compare equal"
761 );
762 assert_eq!(month, thirty_days, "equality must agree with cmp or Ord's contract is broken");
763 assert_eq!(
764 hash_of(month),
765 hash_of(thirty_days),
766 "values that compare equal must hash alike or hash containers lose entries"
767 );
768 }
769
770 #[test]
771 fn sorting_orders_mixed_units_by_elapsed_time() {
772 let mut durations = vec![
775 Duration::from_months(1).unwrap(),
776 Duration::from_hours(1).unwrap(),
777 Duration::from_days(45).unwrap(),
778 Duration::from_seconds(30).unwrap(),
779 ];
780 durations.sort();
781
782 let ordered: Vec<i64> = durations.iter().map(|d| d.as_nanos().unwrap()).collect();
783 let mut expected = ordered.clone();
784 expected.sort();
785
786 assert_eq!(ordered, expected, "sorting must place durations in elapsed-time order");
787 }
788
789 #[test]
790 fn a_bound_expressed_in_seconds_rejects_shorter_spans_in_any_unit() {
791 let one_second = Duration::from_seconds(1).unwrap();
794
795 assert!(
796 Duration::from_milliseconds(500).unwrap() < one_second,
797 "500ms is below a one second bound"
798 );
799 assert!(Duration::from_days(1).unwrap() > one_second, "a day is above a one second bound");
800 assert!(Duration::from_months(1).unwrap() > one_second, "a month is above a one second bound");
801 }
802 }
803
804 fn assert_overflow(result: Result<Duration, Box<TypeError>>) {
805 let err = result.expect_err("expected DurationOverflow error");
806 match *err {
807 TypeError::Temporal {
808 kind: TemporalKind::DurationOverflow {
809 ..
810 },
811 ..
812 } => {}
813 other => panic!("expected DurationOverflow, got: {:?}", other),
814 }
815 }
816
817 fn assert_mixed_sign(result: Result<Duration, Box<TypeError>>, expected_days: i32, expected_nanos: i64) {
818 let err = result.expect_err("expected DurationMixedSign error");
819 match *err {
820 TypeError::Temporal {
821 kind: TemporalKind::DurationMixedSign {
822 days,
823 nanos,
824 },
825 ..
826 } => {
827 assert_eq!(days, expected_days, "days mismatch");
828 assert_eq!(nanos, expected_nanos, "nanos mismatch");
829 }
830 other => panic!("expected DurationMixedSign, got: {:?}", other),
831 }
832 }
833
834 #[test]
835 fn test_duration_iso_string_zero() {
836 assert_eq!(Duration::zero().to_iso_string(), "PT0S");
837 assert_eq!(Duration::from_seconds(0).unwrap().to_iso_string(), "PT0S");
838 assert_eq!(Duration::from_nanoseconds(0).unwrap().to_iso_string(), "PT0S");
839 assert_eq!(Duration::default().to_iso_string(), "PT0S");
840 }
841
842 #[test]
843 fn test_duration_iso_string_seconds() {
844 assert_eq!(Duration::from_seconds(1).unwrap().to_iso_string(), "PT1S");
845 assert_eq!(Duration::from_seconds(30).unwrap().to_iso_string(), "PT30S");
846 assert_eq!(Duration::from_seconds(59).unwrap().to_iso_string(), "PT59S");
847 }
848
849 #[test]
850 fn test_duration_iso_string_minutes() {
851 assert_eq!(Duration::from_minutes(1).unwrap().to_iso_string(), "PT1M");
852 assert_eq!(Duration::from_minutes(30).unwrap().to_iso_string(), "PT30M");
853 assert_eq!(Duration::from_minutes(59).unwrap().to_iso_string(), "PT59M");
854 }
855
856 #[test]
857 fn test_duration_iso_string_hours() {
858 assert_eq!(Duration::from_hours(1).unwrap().to_iso_string(), "PT1H");
859 assert_eq!(Duration::from_hours(12).unwrap().to_iso_string(), "PT12H");
860 assert_eq!(Duration::from_hours(23).unwrap().to_iso_string(), "PT23H");
861 }
862
863 #[test]
864 fn test_duration_iso_string_days() {
865 assert_eq!(Duration::from_days(1).unwrap().to_iso_string(), "P1D");
866 assert_eq!(Duration::from_days(7).unwrap().to_iso_string(), "P7D");
867 assert_eq!(Duration::from_days(365).unwrap().to_iso_string(), "P365D");
868 }
869
870 #[test]
871 fn test_duration_iso_string_weeks() {
872 assert_eq!(Duration::from_weeks(1).unwrap().to_iso_string(), "P7D");
873 assert_eq!(Duration::from_weeks(2).unwrap().to_iso_string(), "P14D");
874 assert_eq!(Duration::from_weeks(52).unwrap().to_iso_string(), "P364D");
875 }
876
877 #[test]
878 fn test_duration_iso_string_combined_time() {
879 let d = Duration::new(0, 0, (1 * 60 * 60 + 30 * 60) * 1_000_000_000).unwrap();
880 assert_eq!(d.to_iso_string(), "PT1H30M");
881
882 let d = Duration::new(0, 0, (5 * 60 + 45) * 1_000_000_000).unwrap();
883 assert_eq!(d.to_iso_string(), "PT5M45S");
884
885 let d = Duration::new(0, 0, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000).unwrap();
886 assert_eq!(d.to_iso_string(), "PT2H30M45S");
887 }
888
889 #[test]
890 fn test_duration_iso_string_combined_date_time() {
891 assert_eq!(Duration::new(0, 1, 2 * 60 * 60 * 1_000_000_000).unwrap().to_iso_string(), "P1DT2H");
892 assert_eq!(Duration::new(0, 1, 30 * 60 * 1_000_000_000).unwrap().to_iso_string(), "P1DT30M");
893 assert_eq!(
894 Duration::new(0, 1, (2 * 60 * 60 + 30 * 60) * 1_000_000_000).unwrap().to_iso_string(),
895 "P1DT2H30M"
896 );
897 assert_eq!(
898 Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000).unwrap().to_iso_string(),
899 "P1DT2H30M45S"
900 );
901 }
902
903 #[test]
904 fn test_duration_iso_string_milliseconds() {
905 assert_eq!(Duration::from_milliseconds(123).unwrap().to_iso_string(), "PT0.123S");
906 assert_eq!(Duration::from_milliseconds(1).unwrap().to_iso_string(), "PT0.001S");
907 assert_eq!(Duration::from_milliseconds(999).unwrap().to_iso_string(), "PT0.999S");
908 assert_eq!(Duration::from_milliseconds(1500).unwrap().to_iso_string(), "PT1.5S");
909 }
910
911 #[test]
912 fn test_duration_iso_string_microseconds() {
913 assert_eq!(Duration::from_microseconds(123456).unwrap().to_iso_string(), "PT0.123456S");
914 assert_eq!(Duration::from_microseconds(1).unwrap().to_iso_string(), "PT0.000001S");
915 assert_eq!(Duration::from_microseconds(999999).unwrap().to_iso_string(), "PT0.999999S");
916 assert_eq!(Duration::from_microseconds(1500000).unwrap().to_iso_string(), "PT1.5S");
917 }
918
919 #[test]
920 fn test_duration_iso_string_nanoseconds() {
921 assert_eq!(Duration::from_nanoseconds(123456789).unwrap().to_iso_string(), "PT0.123456789S");
922 assert_eq!(Duration::from_nanoseconds(1).unwrap().to_iso_string(), "PT0.000000001S");
923 assert_eq!(Duration::from_nanoseconds(999999999).unwrap().to_iso_string(), "PT0.999999999S");
924 assert_eq!(Duration::from_nanoseconds(1500000000).unwrap().to_iso_string(), "PT1.5S");
925 }
926
927 #[test]
928 fn test_duration_iso_string_fractional_seconds() {
929 let d = Duration::new(0, 0, 1 * 1_000_000_000 + 500 * 1_000_000).unwrap();
930 assert_eq!(d.to_iso_string(), "PT1.5S");
931
932 let d = Duration::new(0, 0, 2 * 1_000_000_000 + 123456 * 1_000).unwrap();
933 assert_eq!(d.to_iso_string(), "PT2.123456S");
934
935 let d = Duration::new(0, 0, 3 * 1_000_000_000 + 123456789).unwrap();
936 assert_eq!(d.to_iso_string(), "PT3.123456789S");
937 }
938
939 #[test]
940 fn test_duration_iso_string_complex() {
941 let d = Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000 + 123 * 1_000_000).unwrap();
942 assert_eq!(d.to_iso_string(), "P1DT2H30M45.123S");
943
944 let d = Duration::new(0, 7, (12 * 60 * 60 + 45 * 60 + 30) * 1_000_000_000 + 456789 * 1_000).unwrap();
945 assert_eq!(d.to_iso_string(), "P7DT12H45M30.456789S");
946 }
947
948 #[test]
949 fn test_duration_iso_string_trailing_zeros() {
950 assert_eq!(Duration::from_nanoseconds(100000000).unwrap().to_iso_string(), "PT0.1S");
951 assert_eq!(Duration::from_nanoseconds(120000000).unwrap().to_iso_string(), "PT0.12S");
952 assert_eq!(Duration::from_nanoseconds(123000000).unwrap().to_iso_string(), "PT0.123S");
953 assert_eq!(Duration::from_nanoseconds(123400000).unwrap().to_iso_string(), "PT0.1234S");
954 assert_eq!(Duration::from_nanoseconds(123450000).unwrap().to_iso_string(), "PT0.12345S");
955 assert_eq!(Duration::from_nanoseconds(123456000).unwrap().to_iso_string(), "PT0.123456S");
956 assert_eq!(Duration::from_nanoseconds(123456700).unwrap().to_iso_string(), "PT0.1234567S");
957 assert_eq!(Duration::from_nanoseconds(123456780).unwrap().to_iso_string(), "PT0.12345678S");
958 assert_eq!(Duration::from_nanoseconds(123456789).unwrap().to_iso_string(), "PT0.123456789S");
959 }
960
961 #[test]
962 fn test_duration_iso_string_negative() {
963 assert_eq!(Duration::from_seconds(-30).unwrap().to_iso_string(), "PT-30S");
964 assert_eq!(Duration::from_minutes(-5).unwrap().to_iso_string(), "PT-5M");
965 assert_eq!(Duration::from_hours(-2).unwrap().to_iso_string(), "PT-2H");
966 assert_eq!(Duration::from_days(-1).unwrap().to_iso_string(), "P-1D");
967 }
968
969 #[test]
970 fn test_duration_iso_string_large() {
971 assert_eq!(Duration::from_days(1000).unwrap().to_iso_string(), "P1000D");
972 assert_eq!(Duration::from_hours(25).unwrap().to_iso_string(), "P1DT1H");
973 assert_eq!(Duration::from_minutes(1500).unwrap().to_iso_string(), "P1DT1H");
974 assert_eq!(Duration::from_seconds(90000).unwrap().to_iso_string(), "P1DT1H");
975 }
976
977 #[test]
978 fn test_duration_iso_string_edge_cases() {
979 assert_eq!(Duration::from_nanoseconds(1).unwrap().to_iso_string(), "PT0.000000001S");
980 assert_eq!(Duration::from_nanoseconds(999999999).unwrap().to_iso_string(), "PT0.999999999S");
981 assert_eq!(Duration::from_nanoseconds(1000000000).unwrap().to_iso_string(), "PT1S");
982 assert_eq!(Duration::from_nanoseconds(60 * 1000000000).unwrap().to_iso_string(), "PT1M");
983 assert_eq!(Duration::from_nanoseconds(3600 * 1000000000).unwrap().to_iso_string(), "PT1H");
984 assert_eq!(Duration::from_nanoseconds(86400 * 1000000000).unwrap().to_iso_string(), "P1D");
985 }
986
987 #[test]
988 fn test_duration_iso_string_precision() {
989 assert_eq!(Duration::from_nanoseconds(100).unwrap().to_iso_string(), "PT0.0000001S");
990 assert_eq!(Duration::from_nanoseconds(10).unwrap().to_iso_string(), "PT0.00000001S");
991 assert_eq!(Duration::from_nanoseconds(1).unwrap().to_iso_string(), "PT0.000000001S");
992 }
993
994 #[test]
995 fn test_duration_display_zero() {
996 assert_eq!(format!("{}", Duration::zero()), "0s");
997 assert_eq!(format!("{}", Duration::from_seconds(0).unwrap()), "0s");
998 assert_eq!(format!("{}", Duration::from_nanoseconds(0).unwrap()), "0s");
999 assert_eq!(format!("{}", Duration::default()), "0s");
1000 }
1001
1002 #[test]
1003 fn test_duration_display_seconds_only() {
1004 assert_eq!(format!("{}", Duration::from_seconds(1).unwrap()), "1s");
1005 assert_eq!(format!("{}", Duration::from_seconds(30).unwrap()), "30s");
1006 assert_eq!(format!("{}", Duration::from_seconds(59).unwrap()), "59s");
1007 }
1008
1009 #[test]
1010 fn test_duration_display_minutes_only() {
1011 assert_eq!(format!("{}", Duration::from_minutes(1).unwrap()), "1m");
1012 assert_eq!(format!("{}", Duration::from_minutes(30).unwrap()), "30m");
1013 assert_eq!(format!("{}", Duration::from_minutes(59).unwrap()), "59m");
1014 }
1015
1016 #[test]
1017 fn test_duration_display_hours_only() {
1018 assert_eq!(format!("{}", Duration::from_hours(1).unwrap()), "1h");
1019 assert_eq!(format!("{}", Duration::from_hours(12).unwrap()), "12h");
1020 assert_eq!(format!("{}", Duration::from_hours(23).unwrap()), "23h");
1021 }
1022
1023 #[test]
1024 fn test_duration_display_days_only() {
1025 assert_eq!(format!("{}", Duration::from_days(1).unwrap()), "1d");
1026 assert_eq!(format!("{}", Duration::from_days(7).unwrap()), "7d");
1027 assert_eq!(format!("{}", Duration::from_days(365).unwrap()), "365d");
1028 }
1029
1030 #[test]
1031 fn test_duration_display_weeks_only() {
1032 assert_eq!(format!("{}", Duration::from_weeks(1).unwrap()), "7d");
1033 assert_eq!(format!("{}", Duration::from_weeks(2).unwrap()), "14d");
1034 assert_eq!(format!("{}", Duration::from_weeks(52).unwrap()), "364d");
1035 }
1036
1037 #[test]
1038 fn test_duration_display_months_only() {
1039 assert_eq!(format!("{}", Duration::from_months(1).unwrap()), "1mo");
1040 assert_eq!(format!("{}", Duration::from_months(6).unwrap()), "6mo");
1041 assert_eq!(format!("{}", Duration::from_months(11).unwrap()), "11mo");
1042 }
1043
1044 #[test]
1045 fn test_duration_display_years_only() {
1046 assert_eq!(format!("{}", Duration::from_years(1).unwrap()), "1y");
1047 assert_eq!(format!("{}", Duration::from_years(10).unwrap()), "10y");
1048 assert_eq!(format!("{}", Duration::from_years(100).unwrap()), "100y");
1049 }
1050
1051 #[test]
1052 fn test_duration_display_combined_time() {
1053 let d = Duration::new(0, 0, (1 * 60 * 60 + 30 * 60) * 1_000_000_000).unwrap();
1054 assert_eq!(format!("{}", d), "1h30m");
1055
1056 let d = Duration::new(0, 0, (5 * 60 + 45) * 1_000_000_000).unwrap();
1057 assert_eq!(format!("{}", d), "5m45s");
1058
1059 let d = Duration::new(0, 0, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000).unwrap();
1060 assert_eq!(format!("{}", d), "2h30m45s");
1061 }
1062
1063 #[test]
1064 fn test_duration_display_combined_date_time() {
1065 assert_eq!(format!("{}", Duration::new(0, 1, 2 * 60 * 60 * 1_000_000_000).unwrap()), "1d2h");
1066 assert_eq!(format!("{}", Duration::new(0, 1, 30 * 60 * 1_000_000_000).unwrap()), "1d30m");
1067 assert_eq!(
1068 format!("{}", Duration::new(0, 1, (2 * 60 * 60 + 30 * 60) * 1_000_000_000).unwrap()),
1069 "1d2h30m"
1070 );
1071 assert_eq!(
1072 format!("{}", Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000).unwrap()),
1073 "1d2h30m45s"
1074 );
1075 }
1076
1077 #[test]
1078 fn test_duration_display_years_months() {
1079 assert_eq!(format!("{}", Duration::new(13, 0, 0).unwrap()), "1y1mo");
1080 assert_eq!(format!("{}", Duration::new(27, 0, 0).unwrap()), "2y3mo");
1081 }
1082
1083 #[test]
1084 fn test_duration_display_full_components() {
1085 let nanos = (4 * 60 * 60 + 5 * 60 + 6) * 1_000_000_000i64;
1086 assert_eq!(format!("{}", Duration::new(14, 3, nanos).unwrap()), "1y2mo3d4h5m6s");
1087 }
1088
1089 #[test]
1090 fn test_duration_display_milliseconds() {
1091 assert_eq!(format!("{}", Duration::from_milliseconds(123).unwrap()), "123ms");
1092 assert_eq!(format!("{}", Duration::from_milliseconds(1).unwrap()), "1ms");
1093 assert_eq!(format!("{}", Duration::from_milliseconds(999).unwrap()), "999ms");
1094 assert_eq!(format!("{}", Duration::from_milliseconds(1500).unwrap()), "1s500ms");
1095 }
1096
1097 #[test]
1098 fn test_duration_display_microseconds() {
1099 assert_eq!(format!("{}", Duration::from_microseconds(123456).unwrap()), "123ms456us");
1100 assert_eq!(format!("{}", Duration::from_microseconds(1).unwrap()), "1us");
1101 assert_eq!(format!("{}", Duration::from_microseconds(999999).unwrap()), "999ms999us");
1102 assert_eq!(format!("{}", Duration::from_microseconds(1500000).unwrap()), "1s500ms");
1103 }
1104
1105 #[test]
1106 fn test_duration_display_nanoseconds() {
1107 assert_eq!(format!("{}", Duration::from_nanoseconds(123456789).unwrap()), "123ms456us789ns");
1108 assert_eq!(format!("{}", Duration::from_nanoseconds(1).unwrap()), "1ns");
1109 assert_eq!(format!("{}", Duration::from_nanoseconds(999999999).unwrap()), "999ms999us999ns");
1110 assert_eq!(format!("{}", Duration::from_nanoseconds(1500000000).unwrap()), "1s500ms");
1111 }
1112
1113 #[test]
1114 fn test_duration_display_sub_second_decomposition() {
1115 let d = Duration::new(0, 0, 1 * 1_000_000_000 + 500 * 1_000_000).unwrap();
1116 assert_eq!(format!("{}", d), "1s500ms");
1117
1118 let d = Duration::new(0, 0, 2 * 1_000_000_000 + 123456 * 1_000).unwrap();
1119 assert_eq!(format!("{}", d), "2s123ms456us");
1120
1121 let d = Duration::new(0, 0, 3 * 1_000_000_000 + 123456789).unwrap();
1122 assert_eq!(format!("{}", d), "3s123ms456us789ns");
1123 }
1124
1125 #[test]
1126 fn test_duration_display_complex() {
1127 let d = Duration::new(0, 1, (2 * 60 * 60 + 30 * 60 + 45) * 1_000_000_000 + 123 * 1_000_000).unwrap();
1128 assert_eq!(format!("{}", d), "1d2h30m45s123ms");
1129
1130 let d = Duration::new(0, 7, (12 * 60 * 60 + 45 * 60 + 30) * 1_000_000_000 + 456789 * 1_000).unwrap();
1131 assert_eq!(format!("{}", d), "7d12h45m30s456ms789us");
1132 }
1133
1134 #[test]
1135 fn test_duration_display_sub_second_only() {
1136 assert_eq!(format!("{}", Duration::from_nanoseconds(100000000).unwrap()), "100ms");
1137 assert_eq!(format!("{}", Duration::from_nanoseconds(120000000).unwrap()), "120ms");
1138 assert_eq!(format!("{}", Duration::from_nanoseconds(123000000).unwrap()), "123ms");
1139 assert_eq!(format!("{}", Duration::from_nanoseconds(100).unwrap()), "100ns");
1140 assert_eq!(format!("{}", Duration::from_nanoseconds(10).unwrap()), "10ns");
1141 assert_eq!(format!("{}", Duration::from_nanoseconds(1000).unwrap()), "1us");
1142 }
1143
1144 #[test]
1145 fn test_duration_display_negative() {
1146 assert_eq!(format!("{}", Duration::from_seconds(-30).unwrap()), "-30s");
1147 assert_eq!(format!("{}", Duration::from_minutes(-5).unwrap()), "-5m");
1148 assert_eq!(format!("{}", Duration::from_hours(-2).unwrap()), "-2h");
1149 assert_eq!(format!("{}", Duration::from_days(-1).unwrap()), "-1d");
1150 }
1151
1152 #[test]
1153 fn test_duration_display_negative_sub_second() {
1154 assert_eq!(format!("{}", Duration::from_milliseconds(-500).unwrap()), "-500ms");
1155 assert_eq!(format!("{}", Duration::from_microseconds(-100).unwrap()), "-100us");
1156 assert_eq!(format!("{}", Duration::from_nanoseconds(-50).unwrap()), "-50ns");
1157 }
1158
1159 #[test]
1160 fn test_duration_display_large() {
1161 assert_eq!(format!("{}", Duration::from_days(1000).unwrap()), "1000d");
1162 assert_eq!(format!("{}", Duration::from_hours(25).unwrap()), "1d1h");
1163 assert_eq!(format!("{}", Duration::from_minutes(1500).unwrap()), "1d1h");
1164 assert_eq!(format!("{}", Duration::from_seconds(90000).unwrap()), "1d1h");
1165 }
1166
1167 #[test]
1168 fn test_duration_display_edge_cases() {
1169 assert_eq!(format!("{}", Duration::from_nanoseconds(1).unwrap()), "1ns");
1170 assert_eq!(format!("{}", Duration::from_nanoseconds(999999999).unwrap()), "999ms999us999ns");
1171 assert_eq!(format!("{}", Duration::from_nanoseconds(1000000000).unwrap()), "1s");
1172 assert_eq!(format!("{}", Duration::from_nanoseconds(60 * 1000000000).unwrap()), "1m");
1173 assert_eq!(format!("{}", Duration::from_nanoseconds(3600 * 1000000000).unwrap()), "1h");
1174 assert_eq!(format!("{}", Duration::from_nanoseconds(86400 * 1000000000).unwrap()), "1d");
1175 }
1176
1177 #[test]
1178 fn test_duration_display_abs_and_negate() {
1179 let d = Duration::from_seconds(-30).unwrap();
1180 assert_eq!(format!("{}", d.abs()), "30s");
1181
1182 let d = Duration::from_seconds(30).unwrap();
1183 assert_eq!(format!("{}", d.negate()), "-30s");
1184 }
1185
1186 #[test]
1187 fn test_nanos_normalize_to_days() {
1188 let d = Duration::new(0, 0, 86_400_000_000_000).unwrap();
1189 assert_eq!(d.get_days(), 1);
1190 assert_eq!(d.get_nanos(), 0);
1191 }
1192
1193 #[test]
1194 fn test_nanos_normalize_to_days_with_remainder() {
1195 let d = Duration::new(0, 0, 86_400_000_000_000 + 1_000_000_000).unwrap();
1196 assert_eq!(d.get_days(), 1);
1197 assert_eq!(d.get_nanos(), 1_000_000_000);
1198 }
1199
1200 #[test]
1201 fn test_nanos_normalize_negative() {
1202 let d = Duration::new(0, 0, -86_400_000_000_000).unwrap();
1203 assert_eq!(d.get_days(), -1);
1204 assert_eq!(d.get_nanos(), 0);
1205 }
1206
1207 #[test]
1208 fn test_normalized_equality() {
1209 let d1 = Duration::new(0, 0, 86_400_000_000_000).unwrap();
1210 let d2 = Duration::new(0, 1, 0).unwrap();
1211 assert_eq!(d1, d2);
1212 }
1213
1214 #[test]
1215 fn test_normalized_ordering() {
1216 let d1 = Duration::new(0, 0, 86_400_000_000_000 + 1).unwrap();
1217 let d2 = Duration::new(0, 1, 0).unwrap();
1218 assert!(d1 > d2);
1219 }
1220
1221 #[test]
1222 fn test_mixed_sign_months_days_allowed() {
1223 let d = Duration::new(1, -15, 0).unwrap();
1226 assert_eq!(d.get_months(), 1);
1227 assert_eq!(d.get_days(), -15);
1228 }
1229
1230 #[test]
1231 fn test_mixed_sign_months_nanos_allowed() {
1232 let d = Duration::new(-1, 0, 1_000_000_000).unwrap();
1233 assert_eq!(d.get_months(), -1);
1234 assert_eq!(d.get_nanos(), 1_000_000_000);
1235 }
1236
1237 #[test]
1238 fn test_mixed_sign_days_positive_nanos_negative() {
1239 assert_mixed_sign(Duration::new(0, 1, -1), 1, -1);
1240 }
1241
1242 #[test]
1243 fn test_mixed_sign_days_negative_nanos_positive() {
1244 assert_mixed_sign(Duration::new(0, -1, 1), -1, 1);
1245 }
1246
1247 #[test]
1248 fn test_is_positive_negative_mutually_exclusive() {
1249 let durations = [
1250 Duration::new(1, 0, 0).unwrap(),
1251 Duration::new(0, 1, 0).unwrap(),
1252 Duration::new(0, 0, 1).unwrap(),
1253 Duration::new(-1, 0, 0).unwrap(),
1254 Duration::new(0, -1, 0).unwrap(),
1255 Duration::new(0, 0, -1).unwrap(),
1256 Duration::new(1, 1, 1).unwrap(),
1257 Duration::new(-1, -1, -1).unwrap(),
1258 Duration::new(1, -15, 0).unwrap(), Duration::new(-1, 15, 0).unwrap(), Duration::zero(),
1261 ];
1262 for d in durations {
1263 assert!(
1264 !(d.is_positive() && d.is_negative()),
1265 "Duration {:?} is both positive and negative",
1266 d
1267 );
1268 }
1269 }
1270
1271 #[test]
1272 fn test_mixed_months_days_is_neither_positive_nor_negative() {
1273 let d = Duration::new(1, -15, 0).unwrap();
1274 assert!(!d.is_positive());
1275 assert!(!d.is_negative());
1276 }
1277
1278 #[test]
1279 fn test_from_days_overflow() {
1280 assert_overflow(Duration::from_days(i32::MAX as i64 + 1));
1281 }
1282
1283 #[test]
1284 fn test_months_positive_days_negative_ok() {
1285 let d = Duration::new(1, -15, 0).unwrap();
1286 assert_eq!(d.get_months(), 1);
1287 assert_eq!(d.get_days(), -15);
1288 assert_eq!(d.get_nanos(), 0);
1289 }
1290
1291 #[test]
1292 fn test_months_negative_days_positive_ok() {
1293 let d = Duration::new(-1, 15, 0).unwrap();
1294 assert_eq!(d.get_months(), -1);
1295 assert_eq!(d.get_days(), 15);
1296 }
1297
1298 #[test]
1299 fn test_months_positive_nanos_negative_ok() {
1300 let d = Duration::new(1, 0, -1_000_000_000).unwrap();
1301 assert_eq!(d.get_months(), 1);
1302 assert_eq!(d.get_nanos(), -1_000_000_000);
1303 }
1304
1305 #[test]
1306 fn test_months_negative_nanos_positive_ok() {
1307 let d = Duration::new(-1, 0, 1_000_000_000).unwrap();
1308 assert_eq!(d.get_months(), -1);
1309 assert_eq!(d.get_nanos(), 1_000_000_000);
1310 }
1311
1312 #[test]
1313 fn test_months_positive_days_negative_nanos_negative_ok() {
1314 let d = Duration::new(2, -3, -1_000_000_000).unwrap();
1315 assert_eq!(d.get_months(), 2);
1316 assert_eq!(d.get_days(), -3);
1317 assert_eq!(d.get_nanos(), -1_000_000_000);
1318 }
1319
1320 #[test]
1321 fn test_months_negative_days_positive_nanos_positive_ok() {
1322 let d = Duration::new(-2, 3, 1_000_000_000).unwrap();
1323 assert_eq!(d.get_months(), -2);
1324 assert_eq!(d.get_days(), 3);
1325 assert_eq!(d.get_nanos(), 1_000_000_000);
1326 }
1327
1328 #[test]
1329 fn test_days_positive_nanos_negative_with_months_err() {
1330 assert_mixed_sign(Duration::new(5, 1, -1), 1, -1);
1331 }
1332
1333 #[test]
1334 fn test_days_negative_nanos_positive_with_months_err() {
1335 assert_mixed_sign(Duration::new(-5, -1, 1), -1, 1);
1336 }
1337
1338 #[test]
1339 fn test_nanos_normalization_causes_days_nanos_mixed_sign_err() {
1340 assert_mixed_sign(Duration::new(0, -3, 2 * 86_400_000_000_000 + 1), -1, 1);
1342 }
1343
1344 #[test]
1345 fn test_positive_months_negative_days_is_neither() {
1346 let d = Duration::new(1, -15, 0).unwrap();
1347 assert!(!d.is_positive());
1348 assert!(!d.is_negative());
1349 }
1350
1351 #[test]
1352 fn test_negative_months_positive_days_is_neither() {
1353 let d = Duration::new(-1, 15, 0).unwrap();
1354 assert!(!d.is_positive());
1355 assert!(!d.is_negative());
1356 }
1357
1358 #[test]
1359 fn test_positive_months_negative_days_negative_nanos_is_neither() {
1360 let d = Duration::new(2, -3, -1_000_000_000).unwrap();
1361 assert!(!d.is_positive());
1362 assert!(!d.is_negative());
1363 }
1364
1365 #[test]
1366 fn test_all_positive_is_positive() {
1367 let d = Duration::new(1, 2, 3).unwrap();
1368 assert!(d.is_positive());
1369 assert!(!d.is_negative());
1370 }
1371
1372 #[test]
1373 fn test_all_negative_is_negative() {
1374 let d = Duration::new(-1, -2, -3).unwrap();
1375 assert!(!d.is_positive());
1376 assert!(d.is_negative());
1377 }
1378
1379 #[test]
1380 fn test_zero_is_neither_positive_nor_negative() {
1381 assert!(!Duration::zero().is_positive());
1382 assert!(!Duration::zero().is_negative());
1383 }
1384
1385 #[test]
1386 fn test_only_months_positive() {
1387 let d = Duration::new(1, 0, 0).unwrap();
1388 assert!(d.is_positive());
1389 }
1390
1391 #[test]
1392 fn test_only_days_negative() {
1393 let d = Duration::new(0, -1, 0).unwrap();
1394 assert!(d.is_negative());
1395 }
1396
1397 #[test]
1398 fn test_normalization_nanos_into_negative_days() {
1399 let d = Duration::new(-5, 0, -2 * 86_400_000_000_000).unwrap();
1400 assert_eq!(d.get_months(), -5);
1401 assert_eq!(d.get_days(), -2);
1402 assert_eq!(d.get_nanos(), 0);
1403 }
1404
1405 #[test]
1406 fn test_normalization_nanos_into_days_with_mixed_months() {
1407 let d = Duration::new(3, 1, 86_400_000_000_000 + 500_000_000).unwrap();
1408 assert_eq!(d.get_months(), 3);
1409 assert_eq!(d.get_days(), 2);
1410 assert_eq!(d.get_nanos(), 500_000_000);
1411 }
1412
1413 #[test]
1414 fn test_try_sub_month_minus_days() {
1415 let a = Duration::new(1, 0, 0).unwrap();
1416 let b = Duration::new(0, 15, 0).unwrap();
1417 let result = a.try_sub(b).unwrap();
1418 assert_eq!(result.get_months(), 1);
1419 assert_eq!(result.get_days(), -15);
1420 }
1421
1422 #[test]
1423 fn test_try_sub_day_minus_month() {
1424 let a = Duration::new(0, 1, 0).unwrap();
1425 let b = Duration::new(1, 0, 0).unwrap();
1426 let result = a.try_sub(b).unwrap();
1427 assert_eq!(result.get_months(), -1);
1428 assert_eq!(result.get_days(), 1);
1429 }
1430
1431 #[test]
1432 fn test_try_add_mixed_months_days() {
1433 let a = Duration::new(2, -10, 0).unwrap();
1434 let b = Duration::new(-1, -5, 0).unwrap();
1435 let result = a.try_add(b).unwrap();
1436 assert_eq!(result.get_months(), 1);
1437 assert_eq!(result.get_days(), -15);
1438 }
1439
1440 #[test]
1441 fn test_try_sub_days_nanos_mixed_sign_err() {
1442 let a = Duration::new(0, 1, 0).unwrap();
1444 let b = Duration::new(0, 0, 1).unwrap();
1445 assert_mixed_sign(a.try_sub(b), 1, -1);
1446 }
1447
1448 #[test]
1449 fn test_try_mul_preserves_mixed_months() {
1450 let d = Duration::new(1, -3, 0).unwrap();
1451 let result = d.try_mul(2).unwrap();
1452 assert_eq!(result.get_months(), 2);
1453 assert_eq!(result.get_days(), -6);
1454 }
1455
1456 #[test]
1457 fn test_from_days_underflow() {
1458 assert_overflow(Duration::from_days(i32::MIN as i64 - 1));
1459 }
1460
1461 #[test]
1462 fn test_from_months_overflow() {
1463 assert_overflow(Duration::from_months(i32::MAX as i64 + 1));
1464 }
1465
1466 #[test]
1467 fn test_from_years_overflow() {
1468 assert_overflow(Duration::from_years(i32::MAX as i64 / 12 + 1));
1469 }
1470
1471 #[test]
1472 fn test_from_weeks_overflow() {
1473 assert_overflow(Duration::from_weeks(i32::MAX as i64 / 7 + 1));
1474 }
1475
1476 #[test]
1477 fn test_mul_months_truncation() {
1478 let d = Duration::from_months(1).unwrap();
1479 assert_overflow(d.try_mul(i32::MAX as i64 + 1));
1480 }
1481
1482 #[test]
1483 fn test_mul_days_truncation() {
1484 let d = Duration::from_days(1).unwrap();
1485 assert_overflow(d.try_mul(i32::MAX as i64 + 1));
1486 }
1487
1488 fn assert_total_overflow(result: Result<i64, Box<TypeError>>) {
1489 let err = result.expect_err("expected DurationOverflow error");
1490 match *err {
1491 TypeError::Temporal {
1492 kind: TemporalKind::DurationOverflow {
1493 ..
1494 },
1495 ..
1496 } => {}
1497 other => panic!("expected DurationOverflow, got: {:?}", other),
1498 }
1499 }
1500
1501 #[test]
1502 fn test_total_seconds_roundtrips_across_day_boundary() {
1503 let d = Duration::from_seconds(90_000).unwrap();
1506 assert_eq!(d.get_days(), 1);
1507 assert_eq!(d.seconds().unwrap(), 90_000);
1508 }
1509
1510 #[test]
1511 fn test_total_milliseconds_roundtrips_across_day_boundary() {
1512 let d = Duration::from_milliseconds(90_000_000).unwrap();
1513 assert_eq!(d.get_days(), 1);
1514 assert_eq!(d.milliseconds().unwrap(), 90_000_000);
1515 }
1516
1517 #[test]
1518 fn test_total_microseconds_roundtrips_across_day_boundary() {
1519 let d = Duration::from_microseconds(90_000_000_000).unwrap();
1520 assert_eq!(d.get_days(), 1);
1521 assert_eq!(d.microseconds().unwrap(), 90_000_000_000);
1522 }
1523
1524 #[test]
1525 fn test_total_nanoseconds_roundtrips_across_day_boundary() {
1526 let d = Duration::from_nanoseconds(90_000_000_000_000).unwrap();
1527 assert_eq!(d.get_days(), 1);
1528 assert_eq!(d.nanoseconds().unwrap(), 90_000_000_000_000);
1529 assert_eq!(d.as_nanos().unwrap(), 90_000_000_000_000);
1530 }
1531
1532 #[test]
1533 fn test_total_from_minutes_crossing_day() {
1534 let d = Duration::from_minutes(1_500).unwrap();
1535 assert_eq!(d.get_days(), 1);
1536 assert_eq!(d.seconds().unwrap(), 90_000);
1537 }
1538
1539 #[test]
1540 fn test_total_from_hours_crossing_day() {
1541 let d = Duration::from_hours(25).unwrap();
1542 assert_eq!(d.get_days(), 1);
1543 assert_eq!(d.seconds().unwrap(), 90_000);
1544 }
1545
1546 #[test]
1547 fn test_total_from_days_counts_days() {
1548 let d = Duration::from_days(3).unwrap();
1549 assert_eq!(d.seconds().unwrap(), 3 * 86_400);
1550 assert_eq!(d.nanoseconds().unwrap(), 3 * NANOS_PER_DAY);
1551 }
1552
1553 #[test]
1554 fn test_total_from_weeks_counts_days() {
1555 let d = Duration::from_weeks(2).unwrap();
1556 assert_eq!(d.get_days(), 14);
1557 assert_eq!(d.seconds().unwrap(), 14 * 86_400);
1558 }
1559
1560 #[test]
1561 fn test_total_from_months_uses_thirty_days() {
1562 let d = Duration::from_months(5).unwrap();
1564 assert_eq!(d.get_months(), 5);
1565 assert_eq!(d.seconds().unwrap(), 5 * 30 * 86_400);
1566 }
1567
1568 #[test]
1569 fn test_total_from_years_uses_three_six_five_quarter_days() {
1570 let one_year = Duration::from_years(1).unwrap();
1573 assert_eq!(one_year.get_months(), 12);
1574 assert_eq!(one_year.seconds().unwrap(), 31_557_600);
1575
1576 let three_sixty_five_days = Duration::from_days(365).unwrap();
1577 assert_eq!(three_sixty_five_days.seconds().unwrap(), 31_536_000);
1578 assert_ne!(one_year.seconds().unwrap(), three_sixty_five_days.seconds().unwrap());
1579
1580 assert_eq!(Duration::from_years(2).unwrap().seconds().unwrap(), 2 * 31_557_600);
1581 }
1582
1583 #[test]
1584 fn test_total_months_split_into_years_and_residual() {
1585 let d = Duration::from_months(13).unwrap();
1587 assert_eq!(d.seconds().unwrap(), 31_557_600 + 30 * 86_400);
1588 }
1589
1590 #[test]
1591 fn test_total_new_combined_mixed_sign() {
1592 let d = Duration::new(1, -5, 0).unwrap();
1595 assert_eq!(d.seconds().unwrap(), 30 * 86_400 - 5 * 86_400);
1596 }
1597
1598 #[test]
1599 fn test_total_from_micros_infallible_roundtrips() {
1600 let micros: u64 = 90_000_000_000;
1601 let d = Duration::from_micros_infallible(micros);
1602 assert_eq!(d.get_days(), 1);
1603 assert_eq!(d.microseconds().unwrap(), micros as i64);
1604 }
1605
1606 #[test]
1607 fn test_total_from_nanos_infallible_roundtrips() {
1608 let nanos: u64 = 7 * 86_400_000_000_000 + 1_500_000_000;
1611 let d = Duration::from_nanos_infallible(nanos);
1612 assert_eq!(d.get_days(), 7);
1613 assert_eq!(d.nanoseconds().unwrap(), nanos as i64);
1614 }
1615
1616 #[test]
1617 fn test_from_nanos_infallible_clamps_instead_of_wrapping() {
1618 let d = Duration::from_nanos_infallible(u64::MAX);
1620 assert!(d.get_days() > 0, "an unrepresentable span must clamp to a positive duration");
1621 }
1622
1623 #[test]
1624 fn test_total_zero_is_zero_in_every_unit() {
1625 let d = Duration::zero();
1626 assert_eq!(d.seconds().unwrap(), 0);
1627 assert_eq!(d.milliseconds().unwrap(), 0);
1628 assert_eq!(d.microseconds().unwrap(), 0);
1629 assert_eq!(d.nanoseconds().unwrap(), 0);
1630 assert_eq!(d.as_nanos().unwrap(), 0);
1631 }
1632
1633 #[test]
1634 fn test_total_overflow_fails_loud_per_unit() {
1635 let d = Duration::new(0, i32::MAX, 0).unwrap();
1638 assert_eq!(d.seconds().unwrap(), i32::MAX as i64 * 86_400);
1639 assert_total_overflow(d.nanoseconds());
1640 assert_total_overflow(d.as_nanos());
1641 }
1642
1643 #[test]
1644 fn saturating_add_matches_try_add_in_range() {
1645 let a = Duration::new(1, 2, 3_000_000_000).unwrap();
1647 let b = Duration::new(0, 1, 1_000_000_000).unwrap();
1648 assert_eq!(a.saturating_add(b), a.try_add(b).unwrap());
1649 assert_eq!(a.saturating_sub(b), a.try_sub(b).unwrap());
1650 }
1651
1652 #[test]
1653 fn saturating_add_clamps_months_overflow() {
1654 let d = Duration::new(i32::MAX, 0, 0).unwrap().saturating_add(Duration::new(1000, 0, 0).unwrap());
1655 assert_eq!(d.get_months(), i32::MAX);
1656 }
1657
1658 #[test]
1659 fn saturating_add_clamps_days_overflow() {
1660 let d = Duration::new(0, i32::MAX, 0).unwrap().saturating_add(Duration::new(0, 1000, 0).unwrap());
1661 assert_eq!(d.get_days(), i32::MAX);
1662 }
1663
1664 #[test]
1665 fn saturating_sub_carries_mixed_sign() {
1666 let d = Duration::new(0, 1, 0).unwrap().saturating_sub(Duration::new(0, 0, 1).unwrap());
1669 assert_eq!(d.get_days(), 0);
1670 assert_eq!(d.get_nanos(), 86_399_999_999_999);
1671 }
1672
1673 #[test]
1674 fn saturating_mul_scales_and_clamps() {
1675 assert_eq!(Duration::from_seconds(1).unwrap().saturating_mul(60), Duration::from_seconds(60).unwrap());
1676 assert_eq!(Duration::new(0, i32::MAX, 0).unwrap().saturating_mul(2).get_days(), i32::MAX);
1677 }
1678}
1679
1680#[cfg(test)]
1681mod conversion_tests {
1682 use std::str::FromStr;
1683
1684 use super::{Duration, StdDuration};
1685
1686 #[test]
1687 fn from_str_parses_human_units() {
1688 assert_eq!(Duration::from_str("10ms").unwrap().to_std(), StdDuration::from_millis(10));
1690 assert_eq!("5s".parse::<Duration>().unwrap().to_std(), StdDuration::from_secs(5));
1691 assert_eq!("2m".parse::<Duration>().unwrap().to_std(), StdDuration::from_secs(120));
1692 }
1693
1694 #[test]
1695 fn from_str_trims_whitespace() {
1696 assert_eq!(" 400ms ".parse::<Duration>().unwrap().to_std(), StdDuration::from_millis(400));
1697 }
1698
1699 #[test]
1700 fn from_str_rejects_bare_integer() {
1701 assert!("5000".parse::<Duration>().is_err());
1703 }
1704
1705 #[test]
1706 fn from_str_rejects_garbage() {
1707 assert!("soon".parse::<Duration>().is_err());
1708 assert!("".parse::<Duration>().is_err());
1709 }
1710
1711 #[test]
1712 fn to_std_and_from_std_round_trip() {
1713 let std = StdDuration::from_millis(1234);
1714 assert_eq!(Duration::from_std(std).to_std(), std);
1715 }
1716
1717 #[test]
1718 fn to_std_saturates_negative_to_zero() {
1719 let negative = Duration::from_seconds(-5).unwrap();
1721 assert_eq!(negative.to_std(), StdDuration::ZERO);
1722 }
1723
1724 #[test]
1725 fn dividing_a_span_counts_whole_steps_and_drops_the_remainder() {
1726 let second = Duration::from_seconds(1).unwrap();
1728
1729 assert_eq!(second.div_duration(Duration::from_milliseconds(250).unwrap()).unwrap(), 4);
1730 assert_eq!(second.div_duration(Duration::from_milliseconds(300).unwrap()).unwrap(), 3);
1731 assert_eq!(second.div_duration(Duration::from_seconds(2).unwrap()).unwrap(), 0);
1732 }
1733
1734 #[test]
1735 fn dividing_by_a_zero_span_is_refused_rather_than_panicking() {
1736 let err = Duration::from_seconds(1).unwrap().div_duration(Duration::zero());
1738
1739 assert!(err.is_err(), "a zero divisor must be an error, not a division by zero");
1740 }
1741
1742 #[test]
1743 fn division_reads_the_whole_duration_not_just_its_nanosecond_field() {
1744 let day = Duration::from_days(1).unwrap();
1746 let hour = Duration::from_hours(1).unwrap();
1747
1748 assert_eq!(day.div_duration(hour).unwrap(), 24);
1749 assert_eq!(day.get_nanos(), 0, "precondition: the dividend carries no nanoseconds of its own");
1750 }
1751}