1use crate::value::partial::{
3 check_component, DateComponent, DicomDate, DicomDateTime, DicomTime,
4 Error as PartialValuesError,
5};
6use chrono::{FixedOffset, NaiveDate, NaiveTime};
7use snafu::{Backtrace, OptionExt, ResultExt, Snafu};
8use std::convert::TryFrom;
9use std::ops::{Add, Mul, Sub};
10
11#[derive(Debug, Snafu)]
12#[non_exhaustive]
13pub enum Error {
14 #[snafu(display("Unexpected end of element"))]
15 UnexpectedEndOfElement { backtrace: Backtrace },
16 #[snafu(display("Invalid date"))]
17 InvalidDate { backtrace: Backtrace },
18 #[snafu(display("Invalid time"))]
19 InvalidTime { backtrace: Backtrace },
20 #[snafu(display("Invalid DateTime"))]
21 InvalidDateTime {
22 #[snafu(backtrace)]
23 source: PartialValuesError,
24 },
25 #[snafu(display("Invalid date-time zone component"))]
26 InvalidDateTimeZone { backtrace: Backtrace },
27 #[snafu(display("Expected fraction delimiter '.', got '{}'", *value as char))]
28 FractionDelimiter { value: u8, backtrace: Backtrace },
29 #[snafu(display("Invalid number length: it is {}, but must be between 1 and 9", len))]
30 InvalidNumberLength { len: usize, backtrace: Backtrace },
31 #[snafu(display("Invalid number token: got '{}', but must be a digit in '0'..='9'", *value as char))]
32 InvalidNumberToken { value: u8, backtrace: Backtrace },
33 #[snafu(display("Invalid time zone sign token: got '{}', but must be '+' or '-'", *value as char))]
34 InvalidTimeZoneSignToken { value: u8, backtrace: Backtrace },
35 #[snafu(display(
36 "Could not parse incomplete value: first missing component: {:?}",
37 component
38 ))]
39 IncompleteValue {
40 component: DateComponent,
41 backtrace: Backtrace,
42 },
43 #[snafu(display("Component is invalid"))]
44 InvalidComponent {
45 #[snafu(backtrace)]
46 source: PartialValuesError,
47 },
48 #[snafu(display("Failed to construct partial value"))]
49 PartialValue {
50 #[snafu(backtrace)]
51 source: PartialValuesError,
52 },
53 #[snafu(display("Seconds '{secs}' out of bounds when constructing FixedOffset"))]
54 SecsOutOfBounds { secs: i32, backtrace: Backtrace },
55}
56
57type Result<T, E = Error> = std::result::Result<T, E>;
58
59pub fn parse_date(buf: &[u8]) -> Result<NaiveDate> {
63 match buf.len() {
64 4 => IncompleteValueSnafu {
65 component: DateComponent::Month,
66 }
67 .fail(),
68 6 => IncompleteValueSnafu {
69 component: DateComponent::Day,
70 }
71 .fail(),
72 len if len >= 8 => {
73 let year = read_number(&buf[0..4])?;
74 let month: u32 = read_number(&buf[4..6])?;
75 check_component(DateComponent::Month, &month).context(InvalidComponentSnafu)?;
76
77 let day: u32 = read_number(&buf[6..8])?;
78 check_component(DateComponent::Day, &day).context(InvalidComponentSnafu)?;
79
80 NaiveDate::from_ymd_opt(year, month, day).context(InvalidDateSnafu)
81 }
82 _ => UnexpectedEndOfElementSnafu.fail(),
83 }
84}
85
86pub fn parse_date_partial(buf: &[u8]) -> Result<(DicomDate, &[u8])> {
91 if buf.len() < 4 {
92 UnexpectedEndOfElementSnafu.fail()
93 } else {
94 let year: u16 = read_number(&buf[0..4])?;
95 let buf = &buf[4..];
96 if buf.len() < 2 {
97 Ok((DicomDate::from_y(year).context(PartialValueSnafu)?, buf))
98 } else {
99 match read_number::<u8>(&buf[0..2]) {
100 Err(_) => Ok((DicomDate::from_y(year).context(PartialValueSnafu)?, buf)),
101 Ok(month) => {
102 let buf = &buf[2..];
103 if buf.len() < 2 {
104 Ok((
105 DicomDate::from_ym(year, month).context(PartialValueSnafu)?,
106 buf,
107 ))
108 } else {
109 match read_number::<u8>(&buf[0..2]) {
110 Err(_) => Ok((
111 DicomDate::from_ym(year, month).context(PartialValueSnafu)?,
112 buf,
113 )),
114 Ok(day) => {
115 let buf = &buf[2..];
116 Ok((
117 DicomDate::from_ymd(year, month, day)
118 .context(PartialValueSnafu)?,
119 buf,
120 ))
121 }
122 }
123 }
124 }
125 }
126 }
127 }
128}
129
130pub fn parse_time_partial(buf: &[u8]) -> Result<(DicomTime, &[u8])> {
135 if buf.len() < 2 {
136 UnexpectedEndOfElementSnafu.fail()
137 } else {
138 let hour: u8 = read_number(&buf[0..2])?;
139 let buf = &buf[2..];
140 if buf.len() < 2 {
141 Ok((DicomTime::from_h(hour).context(PartialValueSnafu)?, buf))
142 } else {
143 match read_number::<u8>(&buf[0..2]) {
144 Err(_) => Ok((DicomTime::from_h(hour).context(PartialValueSnafu)?, buf)),
145 Ok(minute) => {
146 let buf = &buf[2..];
147 if buf.len() < 2 {
148 Ok((
149 DicomTime::from_hm(hour, minute).context(PartialValueSnafu)?,
150 buf,
151 ))
152 } else {
153 match read_number::<u8>(&buf[0..2]) {
154 Err(_) => Ok((
155 DicomTime::from_hm(hour, minute).context(PartialValueSnafu)?,
156 buf,
157 )),
158 Ok(second) => {
159 let buf = &buf[2..];
160 if buf.len() > 1 && buf[0] == b'.' {
162 let buf = &buf[1..];
163 let no_digits_index =
164 buf.iter().position(|b| !b.is_ascii_digit());
165 let max = no_digits_index.unwrap_or(buf.len());
166 let n = usize::min(6, max);
167 let fraction: u32 = read_number(&buf[0..n])?;
168 let buf = &buf[n..];
169 let fp = u8::try_from(n).unwrap();
170 Ok((
171 DicomTime::from_hmsf(hour, minute, second, fraction, fp)
172 .context(PartialValueSnafu)?,
173 buf,
174 ))
175 } else {
176 Ok((
177 DicomTime::from_hms(hour, minute, second)
178 .context(PartialValueSnafu)?,
179 buf,
180 ))
181 }
182 }
183 }
184 }
185 }
186 }
187 }
188 }
189}
190
191pub fn parse_time(buf: &[u8]) -> Result<(NaiveTime, &[u8])> {
199 match buf.len() {
201 2 => IncompleteValueSnafu {
202 component: DateComponent::Minute,
203 }
204 .fail(),
205 4 => IncompleteValueSnafu {
206 component: DateComponent::Second,
207 }
208 .fail(),
209 6 => {
210 let hour: u32 = read_number(&buf[0..2])?;
211 check_component(DateComponent::Hour, &hour).context(InvalidComponentSnafu)?;
212 let minute: u32 = read_number(&buf[2..4])?;
213 check_component(DateComponent::Minute, &minute).context(InvalidComponentSnafu)?;
214 let second: u32 = read_number(&buf[4..6])?;
215 check_component(DateComponent::Second, &second).context(InvalidComponentSnafu)?;
216 Ok((
217 NaiveTime::from_hms_opt(hour, minute, second).context(InvalidTimeSnafu)?,
218 &buf[6..],
219 ))
220 }
221 len if len >= 8 => {
222 let hour: u32 = read_number(&buf[0..2])?;
223 check_component(DateComponent::Hour, &hour).context(InvalidComponentSnafu)?;
224 let minute: u32 = read_number(&buf[2..4])?;
225 check_component(DateComponent::Minute, &minute).context(InvalidComponentSnafu)?;
226 let second: u32 = read_number(&buf[4..6])?;
227 check_component(DateComponent::Second, &second).context(InvalidComponentSnafu)?;
228 let buf = &buf[6..];
229 if buf[0] != b'.' {
230 FractionDelimiterSnafu { value: buf[0] }.fail()
231 } else {
232 let buf = &buf[1..];
233 let no_digits_index = buf.iter().position(|b| !b.is_ascii_digit());
234 let max = no_digits_index.unwrap_or(buf.len());
235 let n = usize::min(6, max);
236 let mut fraction: u32 = read_number(&buf[0..n])?;
237 let mut acc = n;
238 while acc < 6 {
239 fraction *= 10;
240 acc += 1;
241 }
242 let buf = &buf[n..];
243 check_component(DateComponent::Fraction, &fraction)
244 .context(InvalidComponentSnafu)?;
245 Ok((
246 NaiveTime::from_hms_micro_opt(hour, minute, second, fraction)
247 .context(InvalidTimeSnafu)?,
248 buf,
249 ))
250 }
251 }
252 _ => UnexpectedEndOfElementSnafu.fail(),
253 }
254}
255
256pub trait Ten {
258 fn ten() -> Self;
261}
262
263macro_rules! impl_integral_ten {
264 ($t:ty) => {
265 impl Ten for $t {
266 fn ten() -> Self {
267 10
268 }
269 }
270 };
271}
272
273macro_rules! impl_floating_ten {
274 ($t:ty) => {
275 impl Ten for $t {
276 fn ten() -> Self {
277 10.
278 }
279 }
280 };
281}
282
283impl_integral_ten!(i16);
284impl_integral_ten!(u16);
285impl_integral_ten!(u8);
286impl_integral_ten!(i32);
287impl_integral_ten!(u32);
288impl_integral_ten!(i64);
289impl_integral_ten!(u64);
290impl_integral_ten!(isize);
291impl_integral_ten!(usize);
292impl_floating_ten!(f32);
293impl_floating_ten!(f64);
294
295pub fn read_number<T>(text: &[u8]) -> Result<T>
300where
301 T: Ten,
302 T: From<u8>,
303 T: Add<T, Output = T>,
304 T: Mul<T, Output = T>,
305 T: Sub<T, Output = T>,
306{
307 if text.is_empty() || text.len() > 9 {
308 return InvalidNumberLengthSnafu { len: text.len() }.fail();
309 }
310 if let Some(c) = text.iter().cloned().find(|b| !b.is_ascii_digit()) {
311 return InvalidNumberTokenSnafu { value: c }.fail();
312 }
313
314 Ok(read_number_unchecked(text))
315}
316
317#[inline]
318fn read_number_unchecked<T>(buf: &[u8]) -> T
319where
320 T: Ten,
321 T: From<u8>,
322 T: Add<T, Output = T>,
323 T: Mul<T, Output = T>,
324{
325 debug_assert!(!buf.is_empty());
326 debug_assert!(buf.len() < 10);
327 buf[1..].iter().fold((buf[0] - b'0').into(), |acc, v| {
328 acc * T::ten() + (*v - b'0').into()
329 })
330}
331
332pub fn parse_datetime_partial(buf: &[u8]) -> Result<DicomDateTime> {
362 let (date, rest) = parse_date_partial(buf)?;
363
364 let (time, buf) = match parse_time_partial(rest) {
365 Ok((time, buf)) => (Some(time), buf),
366 Err(_) => (None, rest),
367 };
368
369 let time_zone = match buf.len() {
370 0 => None,
371 len if len > 4 => {
372 let tz_sign = buf[0];
373 let buf = &buf[1..];
374 let tz_h: u32 = read_number(&buf[0..2])?;
375 let tz_m: u32 = read_number(&buf[2..4])?;
376 let s = (tz_h * 60 + tz_m) * 60;
377 match tz_sign {
378 b'+' => {
379 check_component(DateComponent::UtcEast, &s).context(InvalidComponentSnafu)?;
380 Some(
381 FixedOffset::east_opt(s as i32)
382 .context(SecsOutOfBoundsSnafu { secs: s as i32 })?,
383 )
384 }
385 b'-' => {
386 check_component(DateComponent::UtcWest, &s).context(InvalidComponentSnafu)?;
387 Some(
388 FixedOffset::west_opt(s as i32)
389 .context(SecsOutOfBoundsSnafu { secs: s as i32 })?,
390 )
391 }
392 c => return InvalidTimeZoneSignTokenSnafu { value: c }.fail(),
393 }
394 }
395 _ => return UnexpectedEndOfElementSnafu.fail(),
396 };
397
398 match time_zone {
399 Some(time_zone) => match time {
400 Some(tm) => DicomDateTime::from_date_and_time_with_time_zone(date, tm, time_zone)
401 .context(InvalidDateTimeSnafu),
402 None => Ok(DicomDateTime::from_date_with_time_zone(date, time_zone)),
403 },
404 None => match time {
405 Some(tm) => DicomDateTime::from_date_and_time(date, tm).context(InvalidDateTimeSnafu),
406 None => Ok(DicomDateTime::from_date(date)),
407 },
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn test_parse_date() {
417 assert_eq!(
418 parse_date(b"20180101").unwrap(),
419 NaiveDate::from_ymd_opt(2018, 1, 1).unwrap()
420 );
421 assert_eq!(
422 parse_date(b"19711231").unwrap(),
423 NaiveDate::from_ymd_opt(1971, 12, 31).unwrap()
424 );
425 assert_eq!(
426 parse_date(b"20140426").unwrap(),
427 NaiveDate::from_ymd_opt(2014, 4, 26).unwrap()
428 );
429 assert_eq!(
430 parse_date(b"20180101xxxx").unwrap(),
431 NaiveDate::from_ymd_opt(2018, 1, 1).unwrap()
432 );
433 assert_eq!(
434 parse_date(b"19000101").unwrap(),
435 NaiveDate::from_ymd_opt(1900, 1, 1).unwrap()
436 );
437 assert_eq!(
438 parse_date(b"19620728").unwrap(),
439 NaiveDate::from_ymd_opt(1962, 7, 28).unwrap()
440 );
441 assert_eq!(
442 parse_date(b"19020404-0101").unwrap(),
443 NaiveDate::from_ymd_opt(1902, 4, 4).unwrap()
444 );
445
446 assert!(matches!(
447 parse_date(b"1902"),
448 Err(Error::IncompleteValue {
449 component: DateComponent::Month,
450 ..
451 })
452 ));
453
454 assert!(matches!(
455 parse_date(b"190208"),
456 Err(Error::IncompleteValue {
457 component: DateComponent::Day,
458 ..
459 })
460 ));
461
462 assert!(matches!(
463 parse_date(b"19021515"),
464 Err(Error::InvalidComponent {
465 source: PartialValuesError::InvalidComponent {
466 component: DateComponent::Month,
467 value: 15,
468 ..
469 },
470 ..
471 })
472 ));
473
474 assert!(matches!(
475 parse_date(b"19021200"),
476 Err(Error::InvalidComponent {
477 source: PartialValuesError::InvalidComponent {
478 component: DateComponent::Day,
479 value: 0,
480 ..
481 },
482 ..
483 })
484 ));
485
486 assert!(matches!(
487 parse_date(b"19021232"),
488 Err(Error::InvalidComponent {
489 source: PartialValuesError::InvalidComponent {
490 component: DateComponent::Day,
491 value: 32,
492 ..
493 },
494 ..
495 })
496 ));
497
498 assert!(matches!(
500 parse_date(b"20210229"),
501 Err(Error::InvalidDate { .. })
502 ));
503
504 assert!(parse_date(b"").is_err());
505 assert!(parse_date(b" ").is_err());
506 assert!(parse_date(b"--------").is_err());
507 assert!(parse_date(&[0x00_u8; 8]).is_err());
508 assert!(parse_date(&[0xFF_u8; 8]).is_err());
509 assert!(parse_date(&[b'0'; 8]).is_err());
510 assert!(parse_date(b"nothing!").is_err());
511 assert!(parse_date(b"2012dec").is_err());
512 }
513
514 #[test]
515 fn test_parse_date_partial() {
516 assert_eq!(
517 parse_date_partial(b"20180101").unwrap(),
518 (DicomDate::from_ymd(2018, 1, 1).unwrap(), &[][..])
519 );
520 assert_eq!(
521 parse_date_partial(b"19711231").unwrap(),
522 (DicomDate::from_ymd(1971, 12, 31).unwrap(), &[][..])
523 );
524 assert_eq!(
525 parse_date_partial(b"20180101xxxx").unwrap(),
526 (DicomDate::from_ymd(2018, 1, 1).unwrap(), &b"xxxx"[..])
527 );
528 assert_eq!(
529 parse_date_partial(b"201801xxxx").unwrap(),
530 (DicomDate::from_ym(2018, 1).unwrap(), &b"xxxx"[..])
531 );
532 assert_eq!(
533 parse_date_partial(b"2018xxxx").unwrap(),
534 (DicomDate::from_y(2018).unwrap(), &b"xxxx"[..])
535 );
536 assert_eq!(
537 parse_date_partial(b"19020404-0101").unwrap(),
538 (DicomDate::from_ymd(1902, 4, 4).unwrap(), &b"-0101"[..][..])
539 );
540 assert_eq!(
541 parse_date_partial(b"201811").unwrap(),
542 (DicomDate::from_ym(2018, 11).unwrap(), &[][..])
543 );
544 assert_eq!(
545 parse_date_partial(b"1914").unwrap(),
546 (DicomDate::from_y(1914).unwrap(), &[][..])
547 );
548
549 assert_eq!(
550 parse_date_partial(b"19140").unwrap(),
551 (DicomDate::from_y(1914).unwrap(), &b"0"[..])
552 );
553
554 assert_eq!(
555 parse_date_partial(b"1914121").unwrap(),
556 (DicomDate::from_ym(1914, 12).unwrap(), &b"1"[..])
557 );
558
559 assert_eq!(
561 parse_date_partial(b"20210229").unwrap(),
562 (DicomDate::from_ymd(2021, 2, 29).unwrap(), &[][..])
563 );
564
565 assert!(matches!(
566 parse_date_partial(b"19021515"),
567 Err(Error::PartialValue {
568 source: PartialValuesError::InvalidComponent {
569 component: DateComponent::Month,
570 value: 15,
571 ..
572 },
573 ..
574 })
575 ));
576
577 assert!(matches!(
578 parse_date_partial(b"19021200"),
579 Err(Error::PartialValue {
580 source: PartialValuesError::InvalidComponent {
581 component: DateComponent::Day,
582 value: 0,
583 ..
584 },
585 ..
586 })
587 ));
588
589 assert!(matches!(
590 parse_date_partial(b"19021232"),
591 Err(Error::PartialValue {
592 source: PartialValuesError::InvalidComponent {
593 component: DateComponent::Day,
594 value: 32,
595 ..
596 },
597 ..
598 })
599 ));
600 }
601
602 #[test]
603 fn test_parse_time() {
604 assert_eq!(
605 parse_time(b"100000.1").unwrap(),
606 (
607 NaiveTime::from_hms_micro_opt(10, 0, 0, 100_000).unwrap(),
608 &[][..]
609 )
610 );
611 assert_eq!(
612 parse_time(b"235959.0123").unwrap(),
613 (
614 NaiveTime::from_hms_micro_opt(23, 59, 59, 12_300).unwrap(),
615 &[][..]
616 )
617 );
618 assert_eq!(
620 parse_time(b"235959.1234567").unwrap(),
621 (
622 NaiveTime::from_hms_micro_opt(23, 59, 59, 123_456).unwrap(),
623 &b"7"[..]
624 )
625 );
626 assert_eq!(
627 parse_time(b"235959.123456+0100").unwrap(),
628 (
629 NaiveTime::from_hms_micro_opt(23, 59, 59, 123_456).unwrap(),
630 &b"+0100"[..]
631 )
632 );
633 assert_eq!(
634 parse_time(b"235959.1-0100").unwrap(),
635 (
636 NaiveTime::from_hms_micro_opt(23, 59, 59, 100_000).unwrap(),
637 &b"-0100"[..]
638 )
639 );
640 assert_eq!(
641 parse_time(b"235959.12345+0100").unwrap(),
642 (
643 NaiveTime::from_hms_micro_opt(23, 59, 59, 123_450).unwrap(),
644 &b"+0100"[..]
645 )
646 );
647 assert_eq!(
648 parse_time(b"153011").unwrap(),
649 (NaiveTime::from_hms_opt(15, 30, 11).unwrap(), &b""[..])
650 );
651 assert_eq!(
652 parse_time(b"000000.000000").unwrap(),
653 (NaiveTime::from_hms_opt(0, 0, 0).unwrap(), &[][..])
654 );
655 assert!(matches!(
656 parse_time(b"23"),
657 Err(Error::IncompleteValue {
658 component: DateComponent::Minute,
659 ..
660 })
661 ));
662 assert!(matches!(
663 parse_time(b"1530"),
664 Err(Error::IncompleteValue {
665 component: DateComponent::Second,
666 ..
667 })
668 ));
669 assert!(matches!(
670 parse_time(b"153011x0110"),
671 Err(Error::FractionDelimiter { value: 0x78_u8, .. })
672 ));
673 assert!(parse_date(&[0x00_u8; 6]).is_err());
674 assert!(parse_date(&[0xFF_u8; 6]).is_err());
675 assert!(parse_date(b"075501.----").is_err());
676 assert!(parse_date(b"nope").is_err());
677 assert!(parse_date(b"235800.0a").is_err());
678 }
679 #[test]
680 fn test_parse_time_partial() {
681 assert_eq!(
682 parse_time_partial(b"10").unwrap(),
683 (DicomTime::from_h(10).unwrap(), &[][..])
684 );
685 assert_eq!(
686 parse_time_partial(b"101").unwrap(),
687 (DicomTime::from_h(10).unwrap(), &b"1"[..])
688 );
689 assert_eq!(
690 parse_time_partial(b"0755").unwrap(),
691 (DicomTime::from_hm(7, 55).unwrap(), &[][..])
692 );
693 assert_eq!(
694 parse_time_partial(b"075500").unwrap(),
695 (DicomTime::from_hms(7, 55, 0).unwrap(), &[][..])
696 );
697 assert_eq!(
698 parse_time_partial(b"065003").unwrap(),
699 (DicomTime::from_hms(6, 50, 3).unwrap(), &[][..])
700 );
701 assert_eq!(
702 parse_time_partial(b"075501.5").unwrap(),
703 (DicomTime::from_hmsf(7, 55, 1, 5, 1).unwrap(), &[][..])
704 );
705 assert_eq!(
706 parse_time_partial(b"075501.123").unwrap(),
707 (DicomTime::from_hmsf(7, 55, 1, 123, 3).unwrap(), &[][..])
708 );
709 assert_eq!(
710 parse_time_partial(b"10+0101").unwrap(),
711 (DicomTime::from_h(10).unwrap(), &b"+0101"[..])
712 );
713 assert_eq!(
714 parse_time_partial(b"1030+0101").unwrap(),
715 (DicomTime::from_hm(10, 30).unwrap(), &b"+0101"[..])
716 );
717 assert_eq!(
718 parse_time_partial(b"075501.123+0101").unwrap(),
719 (
720 DicomTime::from_hmsf(7, 55, 1, 123, 3).unwrap(),
721 &b"+0101"[..]
722 )
723 );
724 assert_eq!(
725 parse_time_partial(b"075501+0101").unwrap(),
726 (DicomTime::from_hms(7, 55, 1).unwrap(), &b"+0101"[..])
727 );
728 assert_eq!(
729 parse_time_partial(b"075501.999999").unwrap(),
730 (DicomTime::from_hmsf(7, 55, 1, 999_999, 6).unwrap(), &[][..])
731 );
732 assert_eq!(
733 parse_time_partial(b"075501.9999994").unwrap(),
734 (
735 DicomTime::from_hmsf(7, 55, 1, 999_999, 6).unwrap(),
736 &b"4"[..]
737 )
738 );
739 assert_eq!(
741 parse_time_partial(b"105960").unwrap(),
742 (DicomTime::from_hms(10, 59, 60).unwrap(), &[][..])
743 );
744 assert!(matches!(
745 parse_time_partial(b"24"),
746 Err(Error::PartialValue {
747 source: PartialValuesError::InvalidComponent {
748 component: DateComponent::Hour,
749 value: 24,
750 ..
751 },
752 ..
753 })
754 ));
755 assert!(matches!(
756 parse_time_partial(b"1060"),
757 Err(Error::PartialValue {
758 source: PartialValuesError::InvalidComponent {
759 component: DateComponent::Minute,
760 value: 60,
761 ..
762 },
763 ..
764 })
765 ));
766 }
767
768 #[test]
769 fn test_parse_datetime_partial() {
770 assert_eq!(
771 parse_datetime_partial(b"20171130101010.204").unwrap(),
772 DicomDateTime::from_date_and_time(
773 DicomDate::from_ymd(2017, 11, 30).unwrap(),
774 DicomTime::from_hmsf(10, 10, 10, 204, 3).unwrap(),
775 )
776 .unwrap()
777 );
778 assert_eq!(
779 parse_datetime_partial(b"20171130101010").unwrap(),
780 DicomDateTime::from_date_and_time(
781 DicomDate::from_ymd(2017, 11, 30).unwrap(),
782 DicomTime::from_hms(10, 10, 10).unwrap()
783 )
784 .unwrap()
785 );
786 assert_eq!(
787 parse_datetime_partial(b"2017113023").unwrap(),
788 DicomDateTime::from_date_and_time(
789 DicomDate::from_ymd(2017, 11, 30).unwrap(),
790 DicomTime::from_h(23).unwrap()
791 )
792 .unwrap()
793 );
794 assert_eq!(
795 parse_datetime_partial(b"201711").unwrap(),
796 DicomDateTime::from_date(DicomDate::from_ym(2017, 11).unwrap())
797 );
798 assert_eq!(
799 parse_datetime_partial(b"20171130101010.204+0535").unwrap(),
800 DicomDateTime::from_date_and_time_with_time_zone(
801 DicomDate::from_ymd(2017, 11, 30).unwrap(),
802 DicomTime::from_hmsf(10, 10, 10, 204, 3).unwrap(),
803 FixedOffset::east_opt(5 * 3600 + 35 * 60).unwrap()
804 )
805 .unwrap()
806 );
807 assert_eq!(
808 parse_datetime_partial(b"20171130101010+0535").unwrap(),
809 DicomDateTime::from_date_and_time_with_time_zone(
810 DicomDate::from_ymd(2017, 11, 30).unwrap(),
811 DicomTime::from_hms(10, 10, 10).unwrap(),
812 FixedOffset::east_opt(5 * 3600 + 35 * 60).unwrap()
813 )
814 .unwrap()
815 );
816 assert_eq!(
817 parse_datetime_partial(b"2017113010+0535").unwrap(),
818 DicomDateTime::from_date_and_time_with_time_zone(
819 DicomDate::from_ymd(2017, 11, 30).unwrap(),
820 DicomTime::from_h(10).unwrap(),
821 FixedOffset::east_opt(5 * 3600 + 35 * 60).unwrap()
822 )
823 .unwrap()
824 );
825 assert_eq!(
826 parse_datetime_partial(b"20171130-0135").unwrap(),
827 DicomDateTime::from_date_with_time_zone(
828 DicomDate::from_ymd(2017, 11, 30).unwrap(),
829 FixedOffset::west_opt(3600 + 35 * 60).unwrap()
830 )
831 );
832 assert_eq!(
833 parse_datetime_partial(b"201711-0135").unwrap(),
834 DicomDateTime::from_date_with_time_zone(
835 DicomDate::from_ym(2017, 11).unwrap(),
836 FixedOffset::west_opt(3600 + 35 * 60).unwrap()
837 )
838 );
839 assert_eq!(
840 parse_datetime_partial(b"2017-0135").unwrap(),
841 DicomDateTime::from_date_with_time_zone(
842 DicomDate::from_y(2017).unwrap(),
843 FixedOffset::west_opt(3600 + 35 * 60).unwrap()
844 )
845 );
846
847 assert!(matches!(
849 parse_datetime_partial(b"20200101-1201"),
850 Err(Error::InvalidComponent { .. })
851 ));
852
853 assert!(matches!(
855 parse_datetime_partial(b"20200101+1401"),
856 Err(Error::InvalidComponent { .. })
857 ));
858
859 assert!(matches!(
860 parse_datetime_partial(b"xxxx0229101010.204"),
861 Err(Error::InvalidNumberToken { .. })
862 ));
863
864 assert!(parse_datetime_partial(b"").is_err());
865 assert!(parse_datetime_partial(&[0x00_u8; 8]).is_err());
866 assert!(parse_datetime_partial(&[0xFF_u8; 8]).is_err());
867 assert!(parse_datetime_partial(&[b'0'; 8]).is_err());
868 assert!(parse_datetime_partial(&[b' '; 8]).is_err());
869 assert!(parse_datetime_partial(b"nope").is_err());
870 assert!(parse_datetime_partial(b"2015dec").is_err());
871 assert!(parse_datetime_partial(b"20151231162945.").is_err());
872 assert!(parse_datetime_partial(b"20151130161445+").is_err());
873 assert!(parse_datetime_partial(b"20151130161445+----").is_err());
874 assert!(parse_datetime_partial(b"20151130161445. ").is_err());
875 assert!(parse_datetime_partial(b"20151130161445. +0000").is_err());
876 assert!(parse_datetime_partial(b"20100423164000.001+3").is_err());
877 assert!(parse_datetime_partial(b"200809112945*1000").is_err());
878 assert!(parse_datetime_partial(b"20171130101010.204+1").is_err());
879 assert!(parse_datetime_partial(b"20171130101010.204+01").is_err());
880 assert!(parse_datetime_partial(b"20171130101010.204+011").is_err());
881 }
882}