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