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