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