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