1use crate::value::partial::{
3 check_component, DateComponent, DicomDate, DicomDateTime, DicomTime,
4 Error as PartialValuesError,
5};
6use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone};
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
332#[deprecated(
343 since = "0.7.0",
344 note = "Use `parse_datetime_partial()` then `to_precise_datetime()`"
345)]
346pub fn parse_datetime(buf: &[u8], dt_utc_offset: FixedOffset) -> Result<DateTime<FixedOffset>> {
347 let date = parse_date(buf)?;
348 let buf = &buf[8..];
349 let (time, buf) = parse_time(buf)?;
350 let offset = match buf.len() {
351 0 => {
352 let dt: Result<_> = dt_utc_offset
356 .from_local_datetime(&NaiveDateTime::new(date, time))
357 .single()
358 .context(InvalidDateTimeZoneSnafu);
359
360 return dt;
361 }
362 len if len > 4 => {
363 let tz_sign = buf[0];
364 let buf = &buf[1..];
365 let tz_h: i32 = read_number(&buf[0..2])?;
366 let tz_m: i32 = read_number(&buf[2..4])?;
367 let s = (tz_h * 60 + tz_m) * 60;
368 match tz_sign {
369 b'+' => FixedOffset::east_opt(s).context(SecsOutOfBoundsSnafu { secs: s })?,
370 b'-' => FixedOffset::west_opt(s).context(SecsOutOfBoundsSnafu { secs: s })?,
371 c => return InvalidTimeZoneSignTokenSnafu { value: c }.fail(),
372 }
373 }
374 _ => return UnexpectedEndOfElementSnafu.fail(),
375 };
376
377 offset
378 .from_local_datetime(&NaiveDateTime::new(date, time))
379 .single()
380 .context(InvalidDateTimeZoneSnafu)
381}
382
383pub fn parse_datetime_partial(buf: &[u8]) -> Result<DicomDateTime> {
413 let (date, rest) = parse_date_partial(buf)?;
414
415 let (time, buf) = match parse_time_partial(rest) {
416 Ok((time, buf)) => (Some(time), buf),
417 Err(_) => (None, rest),
418 };
419
420 let time_zone = match buf.len() {
421 0 => None,
422 len if len > 4 => {
423 let tz_sign = buf[0];
424 let buf = &buf[1..];
425 let tz_h: u32 = read_number(&buf[0..2])?;
426 let tz_m: u32 = read_number(&buf[2..4])?;
427 let s = (tz_h * 60 + tz_m) * 60;
428 match tz_sign {
429 b'+' => {
430 check_component(DateComponent::UtcEast, &s).context(InvalidComponentSnafu)?;
431 Some(
432 FixedOffset::east_opt(s as i32)
433 .context(SecsOutOfBoundsSnafu { secs: s as i32 })?,
434 )
435 }
436 b'-' => {
437 check_component(DateComponent::UtcWest, &s).context(InvalidComponentSnafu)?;
438 Some(
439 FixedOffset::west_opt(s as i32)
440 .context(SecsOutOfBoundsSnafu { secs: s as i32 })?,
441 )
442 }
443 c => return InvalidTimeZoneSignTokenSnafu { value: c }.fail(),
444 }
445 }
446 _ => return UnexpectedEndOfElementSnafu.fail(),
447 };
448
449 match time_zone {
450 Some(time_zone) => match time {
451 Some(tm) => DicomDateTime::from_date_and_time_with_time_zone(date, tm, time_zone)
452 .context(InvalidDateTimeSnafu),
453 None => Ok(DicomDateTime::from_date_with_time_zone(date, time_zone)),
454 },
455 None => match time {
456 Some(tm) => DicomDateTime::from_date_and_time(date, tm).context(InvalidDateTimeSnafu),
457 None => Ok(DicomDateTime::from_date(date)),
458 },
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 #[test]
467 fn test_parse_date() {
468 assert_eq!(
469 parse_date(b"20180101").unwrap(),
470 NaiveDate::from_ymd_opt(2018, 1, 1).unwrap()
471 );
472 assert_eq!(
473 parse_date(b"19711231").unwrap(),
474 NaiveDate::from_ymd_opt(1971, 12, 31).unwrap()
475 );
476 assert_eq!(
477 parse_date(b"20140426").unwrap(),
478 NaiveDate::from_ymd_opt(2014, 4, 26).unwrap()
479 );
480 assert_eq!(
481 parse_date(b"20180101xxxx").unwrap(),
482 NaiveDate::from_ymd_opt(2018, 1, 1).unwrap()
483 );
484 assert_eq!(
485 parse_date(b"19000101").unwrap(),
486 NaiveDate::from_ymd_opt(1900, 1, 1).unwrap()
487 );
488 assert_eq!(
489 parse_date(b"19620728").unwrap(),
490 NaiveDate::from_ymd_opt(1962, 7, 28).unwrap()
491 );
492 assert_eq!(
493 parse_date(b"19020404-0101").unwrap(),
494 NaiveDate::from_ymd_opt(1902, 4, 4).unwrap()
495 );
496
497 assert!(matches!(
498 parse_date(b"1902"),
499 Err(Error::IncompleteValue {
500 component: DateComponent::Month,
501 ..
502 })
503 ));
504
505 assert!(matches!(
506 parse_date(b"190208"),
507 Err(Error::IncompleteValue {
508 component: DateComponent::Day,
509 ..
510 })
511 ));
512
513 assert!(matches!(
514 parse_date(b"19021515"),
515 Err(Error::InvalidComponent {
516 source: PartialValuesError::InvalidComponent {
517 component: DateComponent::Month,
518 value: 15,
519 ..
520 },
521 ..
522 })
523 ));
524
525 assert!(matches!(
526 parse_date(b"19021200"),
527 Err(Error::InvalidComponent {
528 source: PartialValuesError::InvalidComponent {
529 component: DateComponent::Day,
530 value: 0,
531 ..
532 },
533 ..
534 })
535 ));
536
537 assert!(matches!(
538 parse_date(b"19021232"),
539 Err(Error::InvalidComponent {
540 source: PartialValuesError::InvalidComponent {
541 component: DateComponent::Day,
542 value: 32,
543 ..
544 },
545 ..
546 })
547 ));
548
549 assert!(matches!(
551 parse_date(b"20210229"),
552 Err(Error::InvalidDate { .. })
553 ));
554
555 assert!(parse_date(b"").is_err());
556 assert!(parse_date(b" ").is_err());
557 assert!(parse_date(b"--------").is_err());
558 assert!(parse_date(&[0x00_u8; 8]).is_err());
559 assert!(parse_date(&[0xFF_u8; 8]).is_err());
560 assert!(parse_date(&[b'0'; 8]).is_err());
561 assert!(parse_date(b"nothing!").is_err());
562 assert!(parse_date(b"2012dec").is_err());
563 }
564
565 #[test]
566 fn test_parse_date_partial() {
567 assert_eq!(
568 parse_date_partial(b"20180101").unwrap(),
569 (DicomDate::from_ymd(2018, 1, 1).unwrap(), &[][..])
570 );
571 assert_eq!(
572 parse_date_partial(b"19711231").unwrap(),
573 (DicomDate::from_ymd(1971, 12, 31).unwrap(), &[][..])
574 );
575 assert_eq!(
576 parse_date_partial(b"20180101xxxx").unwrap(),
577 (DicomDate::from_ymd(2018, 1, 1).unwrap(), &b"xxxx"[..])
578 );
579 assert_eq!(
580 parse_date_partial(b"201801xxxx").unwrap(),
581 (DicomDate::from_ym(2018, 1).unwrap(), &b"xxxx"[..])
582 );
583 assert_eq!(
584 parse_date_partial(b"2018xxxx").unwrap(),
585 (DicomDate::from_y(2018).unwrap(), &b"xxxx"[..])
586 );
587 assert_eq!(
588 parse_date_partial(b"19020404-0101").unwrap(),
589 (DicomDate::from_ymd(1902, 4, 4).unwrap(), &b"-0101"[..][..])
590 );
591 assert_eq!(
592 parse_date_partial(b"201811").unwrap(),
593 (DicomDate::from_ym(2018, 11).unwrap(), &[][..])
594 );
595 assert_eq!(
596 parse_date_partial(b"1914").unwrap(),
597 (DicomDate::from_y(1914).unwrap(), &[][..])
598 );
599
600 assert_eq!(
601 parse_date_partial(b"19140").unwrap(),
602 (DicomDate::from_y(1914).unwrap(), &b"0"[..])
603 );
604
605 assert_eq!(
606 parse_date_partial(b"1914121").unwrap(),
607 (DicomDate::from_ym(1914, 12).unwrap(), &b"1"[..])
608 );
609
610 assert_eq!(
612 parse_date_partial(b"20210229").unwrap(),
613 (DicomDate::from_ymd(2021, 2, 29).unwrap(), &[][..])
614 );
615
616 assert!(matches!(
617 parse_date_partial(b"19021515"),
618 Err(Error::PartialValue {
619 source: PartialValuesError::InvalidComponent {
620 component: DateComponent::Month,
621 value: 15,
622 ..
623 },
624 ..
625 })
626 ));
627
628 assert!(matches!(
629 parse_date_partial(b"19021200"),
630 Err(Error::PartialValue {
631 source: PartialValuesError::InvalidComponent {
632 component: DateComponent::Day,
633 value: 0,
634 ..
635 },
636 ..
637 })
638 ));
639
640 assert!(matches!(
641 parse_date_partial(b"19021232"),
642 Err(Error::PartialValue {
643 source: PartialValuesError::InvalidComponent {
644 component: DateComponent::Day,
645 value: 32,
646 ..
647 },
648 ..
649 })
650 ));
651 }
652
653 #[test]
654 fn test_parse_time() {
655 assert_eq!(
656 parse_time(b"100000.1").unwrap(),
657 (
658 NaiveTime::from_hms_micro_opt(10, 0, 0, 100_000).unwrap(),
659 &[][..]
660 )
661 );
662 assert_eq!(
663 parse_time(b"235959.0123").unwrap(),
664 (
665 NaiveTime::from_hms_micro_opt(23, 59, 59, 12_300).unwrap(),
666 &[][..]
667 )
668 );
669 assert_eq!(
671 parse_time(b"235959.1234567").unwrap(),
672 (
673 NaiveTime::from_hms_micro_opt(23, 59, 59, 123_456).unwrap(),
674 &b"7"[..]
675 )
676 );
677 assert_eq!(
678 parse_time(b"235959.123456+0100").unwrap(),
679 (
680 NaiveTime::from_hms_micro_opt(23, 59, 59, 123_456).unwrap(),
681 &b"+0100"[..]
682 )
683 );
684 assert_eq!(
685 parse_time(b"235959.1-0100").unwrap(),
686 (
687 NaiveTime::from_hms_micro_opt(23, 59, 59, 100_000).unwrap(),
688 &b"-0100"[..]
689 )
690 );
691 assert_eq!(
692 parse_time(b"235959.12345+0100").unwrap(),
693 (
694 NaiveTime::from_hms_micro_opt(23, 59, 59, 123_450).unwrap(),
695 &b"+0100"[..]
696 )
697 );
698 assert_eq!(
699 parse_time(b"153011").unwrap(),
700 (NaiveTime::from_hms_opt(15, 30, 11).unwrap(), &b""[..])
701 );
702 assert_eq!(
703 parse_time(b"000000.000000").unwrap(),
704 (NaiveTime::from_hms_opt(0, 0, 0).unwrap(), &[][..])
705 );
706 assert!(matches!(
707 parse_time(b"23"),
708 Err(Error::IncompleteValue {
709 component: DateComponent::Minute,
710 ..
711 })
712 ));
713 assert!(matches!(
714 parse_time(b"1530"),
715 Err(Error::IncompleteValue {
716 component: DateComponent::Second,
717 ..
718 })
719 ));
720 assert!(matches!(
721 parse_time(b"153011x0110"),
722 Err(Error::FractionDelimiter { value: 0x78_u8, .. })
723 ));
724 assert!(parse_date(&[0x00_u8; 6]).is_err());
725 assert!(parse_date(&[0xFF_u8; 6]).is_err());
726 assert!(parse_date(b"075501.----").is_err());
727 assert!(parse_date(b"nope").is_err());
728 assert!(parse_date(b"235800.0a").is_err());
729 }
730 #[test]
731 fn test_parse_time_partial() {
732 assert_eq!(
733 parse_time_partial(b"10").unwrap(),
734 (DicomTime::from_h(10).unwrap(), &[][..])
735 );
736 assert_eq!(
737 parse_time_partial(b"101").unwrap(),
738 (DicomTime::from_h(10).unwrap(), &b"1"[..])
739 );
740 assert_eq!(
741 parse_time_partial(b"0755").unwrap(),
742 (DicomTime::from_hm(7, 55).unwrap(), &[][..])
743 );
744 assert_eq!(
745 parse_time_partial(b"075500").unwrap(),
746 (DicomTime::from_hms(7, 55, 0).unwrap(), &[][..])
747 );
748 assert_eq!(
749 parse_time_partial(b"065003").unwrap(),
750 (DicomTime::from_hms(6, 50, 3).unwrap(), &[][..])
751 );
752 assert_eq!(
753 parse_time_partial(b"075501.5").unwrap(),
754 (DicomTime::from_hmsf(7, 55, 1, 5, 1).unwrap(), &[][..])
755 );
756 assert_eq!(
757 parse_time_partial(b"075501.123").unwrap(),
758 (DicomTime::from_hmsf(7, 55, 1, 123, 3).unwrap(), &[][..])
759 );
760 assert_eq!(
761 parse_time_partial(b"10+0101").unwrap(),
762 (DicomTime::from_h(10).unwrap(), &b"+0101"[..])
763 );
764 assert_eq!(
765 parse_time_partial(b"1030+0101").unwrap(),
766 (DicomTime::from_hm(10, 30).unwrap(), &b"+0101"[..])
767 );
768 assert_eq!(
769 parse_time_partial(b"075501.123+0101").unwrap(),
770 (
771 DicomTime::from_hmsf(7, 55, 1, 123, 3).unwrap(),
772 &b"+0101"[..]
773 )
774 );
775 assert_eq!(
776 parse_time_partial(b"075501+0101").unwrap(),
777 (DicomTime::from_hms(7, 55, 1).unwrap(), &b"+0101"[..])
778 );
779 assert_eq!(
780 parse_time_partial(b"075501.999999").unwrap(),
781 (DicomTime::from_hmsf(7, 55, 1, 999_999, 6).unwrap(), &[][..])
782 );
783 assert_eq!(
784 parse_time_partial(b"075501.9999994").unwrap(),
785 (
786 DicomTime::from_hmsf(7, 55, 1, 999_999, 6).unwrap(),
787 &b"4"[..]
788 )
789 );
790 assert_eq!(
792 parse_time_partial(b"105960").unwrap(),
793 (DicomTime::from_hms(10, 59, 60).unwrap(), &[][..])
794 );
795 assert!(matches!(
796 parse_time_partial(b"24"),
797 Err(Error::PartialValue {
798 source: PartialValuesError::InvalidComponent {
799 component: DateComponent::Hour,
800 value: 24,
801 ..
802 },
803 ..
804 })
805 ));
806 assert!(matches!(
807 parse_time_partial(b"1060"),
808 Err(Error::PartialValue {
809 source: PartialValuesError::InvalidComponent {
810 component: DateComponent::Minute,
811 value: 60,
812 ..
813 },
814 ..
815 })
816 ));
817 }
818
819 #[test]
820 fn test_parse_datetime_partial() {
821 assert_eq!(
822 parse_datetime_partial(b"20171130101010.204").unwrap(),
823 DicomDateTime::from_date_and_time(
824 DicomDate::from_ymd(2017, 11, 30).unwrap(),
825 DicomTime::from_hmsf(10, 10, 10, 204, 3).unwrap(),
826 )
827 .unwrap()
828 );
829 assert_eq!(
830 parse_datetime_partial(b"20171130101010").unwrap(),
831 DicomDateTime::from_date_and_time(
832 DicomDate::from_ymd(2017, 11, 30).unwrap(),
833 DicomTime::from_hms(10, 10, 10).unwrap()
834 )
835 .unwrap()
836 );
837 assert_eq!(
838 parse_datetime_partial(b"2017113023").unwrap(),
839 DicomDateTime::from_date_and_time(
840 DicomDate::from_ymd(2017, 11, 30).unwrap(),
841 DicomTime::from_h(23).unwrap()
842 )
843 .unwrap()
844 );
845 assert_eq!(
846 parse_datetime_partial(b"201711").unwrap(),
847 DicomDateTime::from_date(DicomDate::from_ym(2017, 11).unwrap())
848 );
849 assert_eq!(
850 parse_datetime_partial(b"20171130101010.204+0535").unwrap(),
851 DicomDateTime::from_date_and_time_with_time_zone(
852 DicomDate::from_ymd(2017, 11, 30).unwrap(),
853 DicomTime::from_hmsf(10, 10, 10, 204, 3).unwrap(),
854 FixedOffset::east_opt(5 * 3600 + 35 * 60).unwrap()
855 )
856 .unwrap()
857 );
858 assert_eq!(
859 parse_datetime_partial(b"20171130101010+0535").unwrap(),
860 DicomDateTime::from_date_and_time_with_time_zone(
861 DicomDate::from_ymd(2017, 11, 30).unwrap(),
862 DicomTime::from_hms(10, 10, 10).unwrap(),
863 FixedOffset::east_opt(5 * 3600 + 35 * 60).unwrap()
864 )
865 .unwrap()
866 );
867 assert_eq!(
868 parse_datetime_partial(b"2017113010+0535").unwrap(),
869 DicomDateTime::from_date_and_time_with_time_zone(
870 DicomDate::from_ymd(2017, 11, 30).unwrap(),
871 DicomTime::from_h(10).unwrap(),
872 FixedOffset::east_opt(5 * 3600 + 35 * 60).unwrap()
873 )
874 .unwrap()
875 );
876 assert_eq!(
877 parse_datetime_partial(b"20171130-0135").unwrap(),
878 DicomDateTime::from_date_with_time_zone(
879 DicomDate::from_ymd(2017, 11, 30).unwrap(),
880 FixedOffset::west_opt(1 * 3600 + 35 * 60).unwrap()
881 )
882 );
883 assert_eq!(
884 parse_datetime_partial(b"201711-0135").unwrap(),
885 DicomDateTime::from_date_with_time_zone(
886 DicomDate::from_ym(2017, 11).unwrap(),
887 FixedOffset::west_opt(1 * 3600 + 35 * 60).unwrap()
888 )
889 );
890 assert_eq!(
891 parse_datetime_partial(b"2017-0135").unwrap(),
892 DicomDateTime::from_date_with_time_zone(
893 DicomDate::from_y(2017).unwrap(),
894 FixedOffset::west_opt(1 * 3600 + 35 * 60).unwrap()
895 )
896 );
897
898 assert!(matches!(
900 parse_datetime_partial(b"20200101-1201"),
901 Err(Error::InvalidComponent { .. })
902 ));
903
904 assert!(matches!(
906 parse_datetime_partial(b"20200101+1401"),
907 Err(Error::InvalidComponent { .. })
908 ));
909
910 assert!(matches!(
911 parse_datetime_partial(b"xxxx0229101010.204"),
912 Err(Error::InvalidNumberToken { .. })
913 ));
914
915 assert!(parse_datetime_partial(b"").is_err());
916 assert!(parse_datetime_partial(&[0x00_u8; 8]).is_err());
917 assert!(parse_datetime_partial(&[0xFF_u8; 8]).is_err());
918 assert!(parse_datetime_partial(&[b'0'; 8]).is_err());
919 assert!(parse_datetime_partial(&[b' '; 8]).is_err());
920 assert!(parse_datetime_partial(b"nope").is_err());
921 assert!(parse_datetime_partial(b"2015dec").is_err());
922 assert!(parse_datetime_partial(b"20151231162945.").is_err());
923 assert!(parse_datetime_partial(b"20151130161445+").is_err());
924 assert!(parse_datetime_partial(b"20151130161445+----").is_err());
925 assert!(parse_datetime_partial(b"20151130161445. ").is_err());
926 assert!(parse_datetime_partial(b"20151130161445. +0000").is_err());
927 assert!(parse_datetime_partial(b"20100423164000.001+3").is_err());
928 assert!(parse_datetime_partial(b"200809112945*1000").is_err());
929 assert!(parse_datetime_partial(b"20171130101010.204+1").is_err());
930 assert!(parse_datetime_partial(b"20171130101010.204+01").is_err());
931 assert!(parse_datetime_partial(b"20171130101010.204+011").is_err());
932 }
933}