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