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