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