1use std::collections;
5
6use crate::{
7 error::{Error, TemporalKind, TypeError},
8 fragment::Fragment,
9 value::Duration,
10};
11
12fn validate_component_order(
13 component: char,
14 seen: &mut collections::HashSet<char>,
15 last_order: &mut u8,
16 current_order: u8,
17 fragment: Fragment,
18 position: usize,
19) -> Result<(), Error> {
20 let key = if component == 'M' {
21 'M'
22 } else {
23 component
24 };
25
26 if seen.contains(&key) {
27 let frag = fragment.sub_fragment(position, 1);
28 return Err(TypeError::Temporal {
29 kind: TemporalKind::DuplicateDurationComponent {
30 component,
31 },
32 message: format!("duplicate duration component '{}'", component),
33 fragment: frag,
34 }
35 .into());
36 }
37
38 if current_order <= *last_order {
39 let frag = fragment.sub_fragment(position, 1);
40 return Err(TypeError::Temporal {
41 kind: TemporalKind::OutOfOrderDurationComponent {
42 component,
43 },
44 message: format!("duration component '{}' is out of order", component),
45 fragment: frag,
46 }
47 .into());
48 }
49
50 seen.insert(key);
51 *last_order = current_order;
52 Ok(())
53}
54
55pub fn parse_duration(fragment: Fragment) -> Result<Duration, Error> {
56 let fragment_value = fragment.text();
57
58 if fragment_value.starts_with('P') {
59 return parse_iso_duration(fragment);
60 }
61
62 parse_human_duration(fragment)
63}
64
65fn parse_human_duration(fragment: Fragment) -> Result<Duration, Error> {
66 let input = fragment.text();
67 let bytes = input.as_bytes();
68 let len = bytes.len();
69
70 if len == 0 {
71 return Err(TypeError::Temporal {
72 kind: TemporalKind::InvalidDurationFormat,
73 message: "invalid duration format".into(),
74 fragment,
75 }
76 .into());
77 }
78
79 let mut months = 0i32;
80 let mut days = 0i32;
81 let mut nanos = 0i64;
82 let mut pos = 0;
83 let mut found_any = false;
84
85 let mut last_order = 0u8;
86
87 while pos < len {
88 let num_start = pos;
89 while pos < len && bytes[pos].is_ascii_digit() {
90 pos += 1;
91 }
92
93 if pos == num_start || pos >= len {
94 return Err(TypeError::Temporal {
95 kind: TemporalKind::InvalidDurationFormat,
96 message: "invalid duration format".into(),
97 fragment,
98 }
99 .into());
100 }
101
102 let num_str = &input[num_start..pos];
103 let value: i64 = num_str.parse().map_err(|_| {
104 let frag = fragment.sub_fragment(num_start, num_str.len());
105 let err: Error = TypeError::Temporal {
106 kind: TemporalKind::InvalidDurationFormat,
107 message: "invalid duration format".into(),
108 fragment: frag,
109 }
110 .into();
111 err
112 })?;
113
114 let (order, advance) = if pos + 1 < len && bytes[pos] == b'n' && bytes[pos + 1] == b's' {
115 nanos += value;
116 (9u8, 2)
117 } else if pos + 1 < len && bytes[pos] == b'u' && bytes[pos + 1] == b's' {
118 nanos += value * 1_000;
119 (8u8, 2)
120 } else if pos + 1 < len && bytes[pos] == b'm' && bytes[pos + 1] == b's' {
121 nanos += value * 1_000_000;
122 (7u8, 2)
123 } else if pos + 1 < len && bytes[pos] == b'm' && bytes[pos + 1] == b'o' {
124 months += value as i32;
125 (2u8, 2)
126 } else if bytes[pos] == b'y' {
127 months += value as i32 * 12;
128 (1u8, 1)
129 } else if bytes[pos] == b'd' {
130 days += value as i32;
131 (3u8, 1)
132 } else if bytes[pos] == b'h' {
133 nanos += value * 60 * 60 * 1_000_000_000;
134 (4u8, 1)
135 } else if bytes[pos] == b'm' {
136 nanos += value * 60 * 1_000_000_000;
137 (5u8, 1)
138 } else if bytes[pos] == b's' {
139 nanos += value * 1_000_000_000;
140 (6u8, 1)
141 } else {
142 let char_frag = fragment.sub_fragment(pos, 1);
143 return Err(TypeError::Temporal {
144 kind: TemporalKind::InvalidDurationCharacter,
145 message: format!("invalid character in duration '{}'", char_frag.text()),
146 fragment: char_frag,
147 }
148 .into());
149 };
150
151 if order <= last_order {
152 let frag = fragment.sub_fragment(pos, advance);
153 return Err(TypeError::Temporal {
154 kind: TemporalKind::OutOfOrderDurationComponent {
155 component: bytes[pos] as char,
156 },
157 message: format!("duration component '{}' is out of order", &input[pos..pos + advance]),
158 fragment: frag,
159 }
160 .into());
161 }
162
163 last_order = order;
164 pos += advance;
165 found_any = true;
166 }
167
168 if !found_any {
169 return Err(TypeError::Temporal {
170 kind: TemporalKind::InvalidDurationFormat,
171 message: "invalid duration format".into(),
172 fragment,
173 }
174 .into());
175 }
176
177 Ok(Duration::new(months, days, nanos)?)
178}
179
180fn parse_iso_duration(fragment: Fragment) -> Result<Duration, Error> {
181 let fragment_value = fragment.text();
182
183 if fragment_value.len() == 1 || fragment_value == "PT" {
184 return Err(TypeError::Temporal {
185 kind: TemporalKind::InvalidDurationFormat,
186 message: "invalid duration format".into(),
187 fragment,
188 }
189 .into());
190 }
191
192 let chars = fragment_value.chars().skip(1);
193 let mut months = 0i32;
194 let mut days = 0i32;
195 let mut nanos = 0i64;
196 let mut current_number = String::new();
197 let mut in_time_part = false;
198 let mut current_position = 1;
199
200 let mut seen_date_components = collections::HashSet::new();
201 let mut seen_time_components = collections::HashSet::new();
202 let mut last_date_component_order = 0u8;
203 let mut last_time_component_order = 0u8;
204
205 for c in chars {
206 match c {
207 'T' => {
208 in_time_part = true;
209 current_position += 1;
210 }
211 '0'..='9' | '.' => {
212 current_number.push(c);
213 current_position += 1;
214 }
215 'Y' => {
216 if in_time_part {
217 let unit_frag = fragment.sub_fragment(current_position, 1);
218 return Err(TypeError::Temporal {
219 kind: TemporalKind::InvalidUnitInContext {
220 unit: 'Y',
221 in_time_part: true,
222 },
223 message: format!("invalid unit '{}' in {}", 'Y', "time part (after T)"),
224 fragment: unit_frag,
225 }
226 .into());
227 }
228 if current_number.is_empty() {
229 let unit_frag = fragment.sub_fragment(current_position, 1);
230 return Err(TypeError::Temporal {
231 kind: TemporalKind::IncompleteDurationSpecification,
232 message: "incomplete duration specification".into(),
233 fragment: unit_frag,
234 }
235 .into());
236 }
237 if current_number.contains('.') {
238 let start = current_position - current_number.len();
239 let dot_pos = start + current_number.find('.').unwrap();
240 let char_frag = fragment.sub_fragment(dot_pos, 1);
241 return Err(TypeError::Temporal {
242 kind: TemporalKind::InvalidDurationCharacter,
243 message: format!(
244 "invalid character in duration '{}'",
245 char_frag.text()
246 ),
247 fragment: char_frag,
248 }
249 .into());
250 }
251
252 validate_component_order(
253 'Y',
254 &mut seen_date_components,
255 &mut last_date_component_order,
256 1,
257 fragment.clone(),
258 current_position,
259 )?;
260
261 let years: i32 = current_number.parse().map_err(|_| {
262 let start = current_position - current_number.len();
263 let number_frag = fragment.sub_fragment(start, current_number.len());
264 let err: Error = TypeError::Temporal {
265 kind: TemporalKind::InvalidDurationComponentValue {
266 unit: 'Y',
267 },
268 message: format!("invalid year value '{}'", number_frag.text()),
269 fragment: number_frag,
270 }
271 .into();
272 err
273 })?;
274 months += years * 12;
275 current_number.clear();
276 current_position += 1;
277 }
278 'M' => {
279 if current_number.is_empty() {
280 let unit_frag = fragment.sub_fragment(current_position, 1);
281 return Err(TypeError::Temporal {
282 kind: TemporalKind::IncompleteDurationSpecification,
283 message: "incomplete duration specification".into(),
284 fragment: unit_frag,
285 }
286 .into());
287 }
288 if current_number.contains('.') {
289 let start = current_position - current_number.len();
290 let dot_pos = start + current_number.find('.').unwrap();
291 let char_frag = fragment.sub_fragment(dot_pos, 1);
292 return Err(TypeError::Temporal {
293 kind: TemporalKind::InvalidDurationCharacter,
294 message: format!(
295 "invalid character in duration '{}'",
296 char_frag.text()
297 ),
298 fragment: char_frag,
299 }
300 .into());
301 }
302
303 if in_time_part {
304 validate_component_order(
305 'M',
306 &mut seen_time_components,
307 &mut last_time_component_order,
308 2,
309 fragment.clone(),
310 current_position,
311 )?;
312 } else {
313 validate_component_order(
314 'M',
315 &mut seen_date_components,
316 &mut last_date_component_order,
317 2,
318 fragment.clone(),
319 current_position,
320 )?;
321 }
322
323 let value: i64 = current_number.parse().map_err(|_| {
324 let start = current_position - current_number.len();
325 let number_frag = fragment.sub_fragment(start, current_number.len());
326 let err: Error = TypeError::Temporal {
327 kind: TemporalKind::InvalidDurationComponentValue {
328 unit: 'M',
329 },
330 message: format!("invalid month/minute value '{}'", number_frag.text()),
331 fragment: number_frag,
332 }
333 .into();
334 err
335 })?;
336 if in_time_part {
337 nanos += value * 60 * 1_000_000_000;
338 } else {
339 months += value as i32;
340 }
341 current_number.clear();
342 current_position += 1;
343 }
344 'W' => {
345 if in_time_part {
346 let unit_frag = fragment.sub_fragment(current_position, 1);
347 return Err(TypeError::Temporal {
348 kind: TemporalKind::InvalidUnitInContext {
349 unit: 'W',
350 in_time_part: true,
351 },
352 message: format!("invalid unit '{}' in {}", 'W', "time part (after T)"),
353 fragment: unit_frag,
354 }
355 .into());
356 }
357 if current_number.is_empty() {
358 let unit_frag = fragment.sub_fragment(current_position, 1);
359 return Err(TypeError::Temporal {
360 kind: TemporalKind::IncompleteDurationSpecification,
361 message: "incomplete duration specification".into(),
362 fragment: unit_frag,
363 }
364 .into());
365 }
366 if current_number.contains('.') {
367 let start = current_position - current_number.len();
368 let dot_pos = start + current_number.find('.').unwrap();
369 let char_frag = fragment.sub_fragment(dot_pos, 1);
370 return Err(TypeError::Temporal {
371 kind: TemporalKind::InvalidDurationCharacter,
372 message: format!(
373 "invalid character in duration '{}'",
374 char_frag.text()
375 ),
376 fragment: char_frag,
377 }
378 .into());
379 }
380
381 validate_component_order(
382 'W',
383 &mut seen_date_components,
384 &mut last_date_component_order,
385 3,
386 fragment.clone(),
387 current_position,
388 )?;
389
390 let weeks: i32 = current_number.parse().map_err(|_| {
391 let start = current_position - current_number.len();
392 let number_frag = fragment.sub_fragment(start, current_number.len());
393 let err: Error = TypeError::Temporal {
394 kind: TemporalKind::InvalidDurationComponentValue {
395 unit: 'W',
396 },
397 message: format!("invalid week value '{}'", number_frag.text()),
398 fragment: number_frag,
399 }
400 .into();
401 err
402 })?;
403 days += weeks * 7;
404 current_number.clear();
405 current_position += 1;
406 }
407 'D' => {
408 if in_time_part {
409 let unit_frag = fragment.sub_fragment(current_position, 1);
410 return Err(TypeError::Temporal {
411 kind: TemporalKind::InvalidUnitInContext {
412 unit: 'D',
413 in_time_part: true,
414 },
415 message: format!("invalid unit '{}' in {}", 'D', "time part (after T)"),
416 fragment: unit_frag,
417 }
418 .into());
419 }
420 if current_number.is_empty() {
421 let unit_frag = fragment.sub_fragment(current_position, 1);
422 return Err(TypeError::Temporal {
423 kind: TemporalKind::IncompleteDurationSpecification,
424 message: "incomplete duration specification".into(),
425 fragment: unit_frag,
426 }
427 .into());
428 }
429 if current_number.contains('.') {
430 let start = current_position - current_number.len();
431 let dot_pos = start + current_number.find('.').unwrap();
432 let char_frag = fragment.sub_fragment(dot_pos, 1);
433 return Err(TypeError::Temporal {
434 kind: TemporalKind::InvalidDurationCharacter,
435 message: format!(
436 "invalid character in duration '{}'",
437 char_frag.text()
438 ),
439 fragment: char_frag,
440 }
441 .into());
442 }
443
444 validate_component_order(
445 'D',
446 &mut seen_date_components,
447 &mut last_date_component_order,
448 4,
449 fragment.clone(),
450 current_position,
451 )?;
452
453 let day_value: i32 = current_number.parse().map_err(|_| {
454 let start = current_position - current_number.len();
455 let number_frag = fragment.sub_fragment(start, current_number.len());
456 let err: Error = TypeError::Temporal {
457 kind: TemporalKind::InvalidDurationComponentValue {
458 unit: 'D',
459 },
460 message: format!("invalid day value '{}'", number_frag.text()),
461 fragment: number_frag,
462 }
463 .into();
464 err
465 })?;
466 days += day_value;
467 current_number.clear();
468 current_position += 1;
469 }
470 'H' => {
471 if !in_time_part {
472 let unit_frag = fragment.sub_fragment(current_position, 1);
473 return Err(TypeError::Temporal {
474 kind: TemporalKind::InvalidUnitInContext {
475 unit: 'H',
476 in_time_part: false,
477 },
478 message: format!(
479 "invalid unit '{}' in {}",
480 'H', "date part (before T)"
481 ),
482 fragment: unit_frag,
483 }
484 .into());
485 }
486 if current_number.is_empty() {
487 let unit_frag = fragment.sub_fragment(current_position, 1);
488 return Err(TypeError::Temporal {
489 kind: TemporalKind::IncompleteDurationSpecification,
490 message: "incomplete duration specification".into(),
491 fragment: unit_frag,
492 }
493 .into());
494 }
495 if current_number.contains('.') {
496 let start = current_position - current_number.len();
497 let dot_pos = start + current_number.find('.').unwrap();
498 let char_frag = fragment.sub_fragment(dot_pos, 1);
499 return Err(TypeError::Temporal {
500 kind: TemporalKind::InvalidDurationCharacter,
501 message: format!(
502 "invalid character in duration '{}'",
503 char_frag.text()
504 ),
505 fragment: char_frag,
506 }
507 .into());
508 }
509
510 validate_component_order(
511 'H',
512 &mut seen_time_components,
513 &mut last_time_component_order,
514 1,
515 fragment.clone(),
516 current_position,
517 )?;
518
519 let hours: i64 = current_number.parse().map_err(|_| {
520 let start = current_position - current_number.len();
521 let number_frag = fragment.sub_fragment(start, current_number.len());
522 let err: Error = TypeError::Temporal {
523 kind: TemporalKind::InvalidDurationComponentValue {
524 unit: 'H',
525 },
526 message: format!("invalid hour value '{}'", number_frag.text()),
527 fragment: number_frag,
528 }
529 .into();
530 err
531 })?;
532 nanos += hours * 60 * 60 * 1_000_000_000;
533 current_number.clear();
534 current_position += 1;
535 }
536 'S' => {
537 if !in_time_part {
538 let unit_frag = fragment.sub_fragment(current_position, 1);
539 return Err(TypeError::Temporal {
540 kind: TemporalKind::InvalidUnitInContext {
541 unit: 'S',
542 in_time_part: false,
543 },
544 message: format!(
545 "invalid unit '{}' in {}",
546 'S', "date part (before T)"
547 ),
548 fragment: unit_frag,
549 }
550 .into());
551 }
552 if current_number.is_empty() {
553 let unit_frag = fragment.sub_fragment(current_position, 1);
554 return Err(TypeError::Temporal {
555 kind: TemporalKind::IncompleteDurationSpecification,
556 message: "incomplete duration specification".into(),
557 fragment: unit_frag,
558 }
559 .into());
560 }
561
562 validate_component_order(
563 'S',
564 &mut seen_time_components,
565 &mut last_time_component_order,
566 3,
567 fragment.clone(),
568 current_position,
569 )?;
570
571 if current_number.contains('.') {
572 let seconds_float: f64 = current_number.parse().map_err(|_| {
573 let start = current_position - current_number.len();
574 let number_frag = fragment.sub_fragment(start, current_number.len());
575 let err: Error = TypeError::Temporal {
576 kind: TemporalKind::InvalidDurationComponentValue {
577 unit: 'S',
578 },
579 message: format!(
580 "invalid second value '{}'",
581 number_frag.text()
582 ),
583 fragment: number_frag,
584 }
585 .into();
586 err
587 })?;
588 nanos += (seconds_float * 1_000_000_000.0) as i64;
589 } else {
590 let seconds: i64 = current_number.parse().map_err(|_| {
591 let start = current_position - current_number.len();
592 let number_frag = fragment.sub_fragment(start, current_number.len());
593 let err: Error = TypeError::Temporal {
594 kind: TemporalKind::InvalidDurationComponentValue {
595 unit: 'S',
596 },
597 message: format!(
598 "invalid second value '{}'",
599 number_frag.text()
600 ),
601 fragment: number_frag,
602 }
603 .into();
604 err
605 })?;
606 nanos += seconds * 1_000_000_000;
607 }
608
609 current_number.clear();
610 current_position += 1;
611 }
612 _ => {
613 let char_frag = fragment.sub_fragment(current_position, 1);
614 return Err(TypeError::Temporal {
615 kind: TemporalKind::InvalidDurationCharacter,
616 message: format!("invalid character in duration '{}'", char_frag.text()),
617 fragment: char_frag,
618 }
619 .into());
620 }
621 }
622 }
623
624 if !current_number.is_empty() {
625 let start = current_position - current_number.len();
626 let number_frag = fragment.sub_fragment(start, current_number.len());
627 return Err(TypeError::Temporal {
628 kind: TemporalKind::IncompleteDurationSpecification,
629 message: "incomplete duration specification".into(),
630 fragment: number_frag,
631 }
632 .into());
633 }
634
635 Ok(Duration::new(months, days, nanos)?)
636}
637
638#[cfg(test)]
639pub mod tests {
640 use super::parse_duration;
641 use crate::fragment::Fragment;
642
643 #[test]
644 fn test_days() {
645 let fragment = Fragment::testing("P1D");
646 let duration = parse_duration(fragment).unwrap();
647 assert_eq!(duration.get_days(), 1);
649 assert_eq!(duration.get_nanos(), 0);
650 }
651
652 #[test]
653 fn test_time_hours_minutes() {
654 let fragment = Fragment::testing("PT2H30M");
655 let duration = parse_duration(fragment).unwrap();
656 assert_eq!(duration.get_nanos(), (2 * 60 * 60 + 30 * 60) * 1_000_000_000);
659 }
660
661 #[test]
662 fn test_comptokenize() {
663 let fragment = Fragment::testing("P1DT2H30M");
664 let duration = parse_duration(fragment).unwrap();
665 let expected_nanos = (2 * 60 * 60 + 30 * 60) * 1_000_000_000;
667 assert_eq!(duration.get_days(), 1);
668 assert_eq!(duration.get_nanos(), expected_nanos);
669 }
670
671 #[test]
672 fn test_seconds_only() {
673 let fragment = Fragment::testing("PT45S");
674 let duration = parse_duration(fragment).unwrap();
675 assert_eq!(duration.get_nanos(), 45 * 1_000_000_000);
676 }
677
678 #[test]
679 fn test_minutes_only() {
680 let fragment = Fragment::testing("PT5M");
681 let duration = parse_duration(fragment).unwrap();
682 assert_eq!(duration.get_nanos(), 5 * 60 * 1_000_000_000);
683 }
684
685 #[test]
686 fn test_hours_only() {
687 let fragment = Fragment::testing("PT1H");
688 let duration = parse_duration(fragment).unwrap();
689 assert_eq!(duration.get_nanos(), 60 * 60 * 1_000_000_000);
690 }
691
692 #[test]
693 fn test_weeks() {
694 let fragment = Fragment::testing("P1W");
695 let duration = parse_duration(fragment).unwrap();
696 assert_eq!(duration.get_days(), 7);
697 assert_eq!(duration.get_nanos(), 0);
698 }
699
700 #[test]
701 fn test_years() {
702 let fragment = Fragment::testing("P1Y");
703 let duration = parse_duration(fragment).unwrap();
704 assert_eq!(duration.get_months(), 12);
705 assert_eq!(duration.get_days(), 0);
706 assert_eq!(duration.get_nanos(), 0);
707 }
708
709 #[test]
710 fn test_months() {
711 let fragment = Fragment::testing("P1M");
712 let duration = parse_duration(fragment).unwrap();
713 assert_eq!(duration.get_months(), 1);
714 assert_eq!(duration.get_days(), 0);
715 assert_eq!(duration.get_nanos(), 0);
716 }
717
718 #[test]
719 fn test_full_format() {
720 let fragment = Fragment::testing("P1Y2M3DT4H5M6S");
721 let duration = parse_duration(fragment).unwrap();
722 let expected_months = 12 + 2; let expected_days = 3;
724 let expected_nanos = 4 * 60 * 60 * 1_000_000_000 + 5 * 60 * 1_000_000_000 + 6 * 1_000_000_000; assert_eq!(duration.get_months(), expected_months);
728 assert_eq!(duration.get_days(), expected_days);
729 assert_eq!(duration.get_nanos(), expected_nanos);
730 }
731
732 #[test]
733 fn test_invalid_format() {
734 let fragment = Fragment::testing("invalid");
735 let err = parse_duration(fragment).unwrap_err();
736 assert_eq!(err.0.code, "TEMPORAL_004");
737 }
738
739 #[test]
740 fn test_invalid_character() {
741 let fragment = Fragment::testing("P1X");
742 let err = parse_duration(fragment).unwrap_err();
743 assert_eq!(err.0.code, "TEMPORAL_014");
744 }
745
746 #[test]
747 fn test_years_in_time_part() {
748 let fragment = Fragment::testing("PTY");
749 let err = parse_duration(fragment).unwrap_err();
750 assert_eq!(err.0.code, "TEMPORAL_016");
751 }
752
753 #[test]
754 fn test_weeks_in_time_part() {
755 let fragment = Fragment::testing("PTW");
756 let err = parse_duration(fragment).unwrap_err();
757 assert_eq!(err.0.code, "TEMPORAL_016");
758 }
759
760 #[test]
761 fn test_days_in_time_part() {
762 let fragment = Fragment::testing("PTD");
763 let err = parse_duration(fragment).unwrap_err();
764 assert_eq!(err.0.code, "TEMPORAL_016");
765 }
766
767 #[test]
768 fn test_hours_in_date_part() {
769 let fragment = Fragment::testing("P1H");
770 let err = parse_duration(fragment).unwrap_err();
771 assert_eq!(err.0.code, "TEMPORAL_016");
772 }
773
774 #[test]
775 fn test_seconds_in_date_part() {
776 let fragment = Fragment::testing("P1S");
777 let err = parse_duration(fragment).unwrap_err();
778 assert_eq!(err.0.code, "TEMPORAL_016");
779 }
780
781 #[test]
782 fn test_incomplete_specification() {
783 let fragment = Fragment::testing("P1");
784 let err = parse_duration(fragment).unwrap_err();
785 assert_eq!(err.0.code, "TEMPORAL_015");
786 }
787
788 #[test]
789 fn test_human_seconds() {
790 let d = parse_duration(Fragment::testing("30s")).unwrap();
791 assert_eq!(d.get_nanos(), 30 * 1_000_000_000);
792 }
793
794 #[test]
795 fn test_human_minutes() {
796 let d = parse_duration(Fragment::testing("5m")).unwrap();
797 assert_eq!(d.get_nanos(), 5 * 60 * 1_000_000_000);
798 }
799
800 #[test]
801 fn test_human_hours() {
802 let d = parse_duration(Fragment::testing("1h")).unwrap();
803 assert_eq!(d.get_nanos(), 60 * 60 * 1_000_000_000);
804 }
805
806 #[test]
807 fn test_human_days() {
808 let d = parse_duration(Fragment::testing("1d")).unwrap();
809 assert_eq!(d.get_days(), 1);
810 assert_eq!(d.get_nanos(), 0);
811 }
812
813 #[test]
814 fn test_human_months() {
815 let d = parse_duration(Fragment::testing("3mo")).unwrap();
816 assert_eq!(d.get_months(), 3);
817 }
818
819 #[test]
820 fn test_human_years() {
821 let d = parse_duration(Fragment::testing("2y")).unwrap();
822 assert_eq!(d.get_months(), 24);
823 }
824
825 #[test]
826 fn test_human_hours_minutes() {
827 let d = parse_duration(Fragment::testing("2h30m")).unwrap();
828 assert_eq!(d.get_nanos(), (2 * 60 * 60 + 30 * 60) * 1_000_000_000);
829 }
830
831 #[test]
832 fn test_human_days_hours_minutes() {
833 let d = parse_duration(Fragment::testing("1d2h30m")).unwrap();
834 assert_eq!(d.get_days(), 1);
835 assert_eq!(d.get_nanos(), (2 * 60 * 60 + 30 * 60) * 1_000_000_000);
836 }
837
838 #[test]
839 fn test_human_full_format() {
840 let d = parse_duration(Fragment::testing("1y2mo3d4h5m6s")).unwrap();
841 assert_eq!(d.get_months(), 14); assert_eq!(d.get_days(), 3);
843 let expected_nanos = (4 * 60 * 60 + 5 * 60 + 6) * 1_000_000_000;
844 assert_eq!(d.get_nanos(), expected_nanos);
845 }
846
847 #[test]
848 fn test_human_milliseconds() {
849 let d = parse_duration(Fragment::testing("500ms")).unwrap();
850 assert_eq!(d.get_nanos(), 500 * 1_000_000);
851 }
852
853 #[test]
854 fn test_human_microseconds() {
855 let d = parse_duration(Fragment::testing("100us")).unwrap();
856 assert_eq!(d.get_nanos(), 100 * 1_000);
857 }
858
859 #[test]
860 fn test_human_nanoseconds() {
861 let d = parse_duration(Fragment::testing("50ns")).unwrap();
862 assert_eq!(d.get_nanos(), 50);
863 }
864
865 #[test]
866 fn test_human_seconds_with_milliseconds() {
867 let d = parse_duration(Fragment::testing("1s500ms")).unwrap();
868 assert_eq!(d.get_nanos(), 1 * 1_000_000_000 + 500 * 1_000_000);
869 }
870
871 #[test]
872 fn test_human_zero() {
873 let d = parse_duration(Fragment::testing("0s")).unwrap();
874 assert_eq!(d.get_months(), 0);
875 assert_eq!(d.get_days(), 0);
876 assert_eq!(d.get_nanos(), 0);
877 }
878
879 #[test]
880 fn test_human_all_sub_second() {
881 let d = parse_duration(Fragment::testing("123ms456us789ns")).unwrap();
882 assert_eq!(d.get_nanos(), 123 * 1_000_000 + 456 * 1_000 + 789);
883 }
884}