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