1use crate::value::AsRange;
4use chrono::{DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
5use snafu::{Backtrace, ResultExt, Snafu};
6use std::convert::{TryFrom, TryInto};
7use std::fmt;
8use std::ops::RangeInclusive;
9
10#[derive(Debug, Snafu)]
11#[non_exhaustive]
12pub enum Error {
13 #[snafu(display("To combine a DicomDate with a DicomTime value, the DicomDate has to be precise. Precision is: '{:?}'", value))]
14 DateTimeFromPartials {
15 value: DateComponent,
16 backtrace: Backtrace,
17 },
18 #[snafu(display(
19 "'{:?}' has invalid value: '{}', must be in {:?}",
20 component,
21 value,
22 range
23 ))]
24 InvalidComponent {
25 component: DateComponent,
26 value: u32,
27 range: RangeInclusive<u32>,
28 backtrace: Backtrace,
29 },
30 #[snafu(display(
31 "Second fraction precision '{}' is out of range, must be in 0..=6",
32 value
33 ))]
34 FractionPrecisionRange { value: u32, backtrace: Backtrace },
35 #[snafu(display(
36 "Number of digits in decimal representation of fraction '{}' does not match it's precision '{}'",
37 fraction,
38 precision
39 ))]
40 FractionPrecisionMismatch {
41 fraction: u32,
42 precision: u32,
43 backtrace: Backtrace,
44 },
45 #[snafu(display("Conversion of value '{}' into {:?} failed", value, component))]
46 Conversion {
47 value: String,
48 component: DateComponent,
49 source: std::num::TryFromIntError,
50 },
51 #[snafu(display(
52 "Cannot convert from an imprecise value. This value represents a date / time range"
53 ))]
54 ImpreciseValue { backtrace: Backtrace },
55}
56
57type Result<T, E = Error> = std::result::Result<T, E>;
58
59#[derive(Debug, PartialEq, Copy, Clone, Eq, Hash, PartialOrd, Ord)]
61pub enum DateComponent {
62 Year,
64 Month,
66 Day,
68 Hour,
70 Minute,
72 Second,
74 Millisecond,
76 Fraction,
78 UtcWest,
80 UtcEast,
82}
83
84#[derive(Clone, Copy, PartialEq)]
116pub struct DicomDate(DicomDateImpl);
117
118#[derive(Clone, Copy, PartialEq)]
163pub struct DicomTime(DicomTimeImpl);
164
165#[derive(Debug, Clone, Copy, PartialEq)]
168enum DicomDateImpl {
169 Year(u16),
170 Month(u16, u8),
171 Day(u16, u8, u8),
172}
173
174#[derive(Debug, Clone, Copy, PartialEq)]
179enum DicomTimeImpl {
180 Hour(u8),
181 Minute(u8, u8),
182 Second(u8, u8, u8),
183 Fraction(u8, u8, u8, u32, u8),
184}
185
186#[derive(PartialEq, Clone, Copy)]
242pub struct DicomDateTime {
243 date: DicomDate,
244 time: Option<DicomTime>,
245 time_zone: Option<FixedOffset>,
246}
247
248pub fn check_component<T>(component: DateComponent, value: &T) -> Result<()>
252where
253 T: Into<u32> + Copy,
254{
255 let range = match component {
256 DateComponent::Year => 0..=9_999,
257 DateComponent::Month => 1..=12,
258 DateComponent::Day => 1..=31,
259 DateComponent::Hour => 0..=23,
260 DateComponent::Minute => 0..=59,
261 DateComponent::Second => 0..=60,
262 DateComponent::Millisecond => 0..=999,
263 DateComponent::Fraction => 0..=999_999,
264 DateComponent::UtcWest => 0..=(12 * 3600),
265 DateComponent::UtcEast => 0..=(14 * 3600),
266 };
267
268 let value: u32 = (*value).into();
269 if range.contains(&value) {
270 Ok(())
271 } else {
272 InvalidComponentSnafu {
273 component,
274 value,
275 range,
276 }
277 .fail()
278 }
279}
280
281impl DicomDate {
282 pub fn from_y(year: u16) -> Result<DicomDate> {
287 check_component(DateComponent::Year, &year)?;
288 Ok(DicomDate(DicomDateImpl::Year(year)))
289 }
290 pub fn from_ym(year: u16, month: u8) -> Result<DicomDate> {
295 check_component(DateComponent::Year, &year)?;
296 check_component(DateComponent::Month, &month)?;
297 Ok(DicomDate(DicomDateImpl::Month(year, month)))
298 }
299 pub fn from_ymd(year: u16, month: u8, day: u8) -> Result<DicomDate> {
304 check_component(DateComponent::Year, &year)?;
305 check_component(DateComponent::Month, &month)?;
306 check_component(DateComponent::Day, &day)?;
307 Ok(DicomDate(DicomDateImpl::Day(year, month, day)))
308 }
309
310 pub fn year(&self) -> &u16 {
312 match self {
313 DicomDate(DicomDateImpl::Year(y)) => y,
314 DicomDate(DicomDateImpl::Month(y, _)) => y,
315 DicomDate(DicomDateImpl::Day(y, _, _)) => y,
316 }
317 }
318 pub fn month(&self) -> Option<&u8> {
320 match self {
321 DicomDate(DicomDateImpl::Year(_)) => None,
322 DicomDate(DicomDateImpl::Month(_, m)) => Some(m),
323 DicomDate(DicomDateImpl::Day(_, m, _)) => Some(m),
324 }
325 }
326 pub fn day(&self) -> Option<&u8> {
328 match self {
329 DicomDate(DicomDateImpl::Year(_)) => None,
330 DicomDate(DicomDateImpl::Month(_, _)) => None,
331 DicomDate(DicomDateImpl::Day(_, _, d)) => Some(d),
332 }
333 }
334
335 pub(crate) fn precision(&self) -> DateComponent {
337 match self {
338 DicomDate(DicomDateImpl::Year(..)) => DateComponent::Year,
339 DicomDate(DicomDateImpl::Month(..)) => DateComponent::Month,
340 DicomDate(DicomDateImpl::Day(..)) => DateComponent::Day,
341 }
342 }
343}
344
345impl TryFrom<&NaiveDate> for DicomDate {
346 type Error = Error;
347 fn try_from(date: &NaiveDate) -> Result<Self> {
348 let year: u16 = date.year().try_into().with_context(|_| ConversionSnafu {
349 value: date.year().to_string(),
350 component: DateComponent::Year,
351 })?;
352 let month: u8 = date.month().try_into().with_context(|_| ConversionSnafu {
353 value: date.month().to_string(),
354 component: DateComponent::Month,
355 })?;
356 let day: u8 = date.day().try_into().with_context(|_| ConversionSnafu {
357 value: date.day().to_string(),
358 component: DateComponent::Day,
359 })?;
360 DicomDate::from_ymd(year, month, day)
361 }
362}
363
364impl fmt::Display for DicomDate {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 match self {
367 DicomDate(DicomDateImpl::Year(y)) => write!(f, "{:04}", y),
368 DicomDate(DicomDateImpl::Month(y, m)) => write!(f, "{:04}-{:02}", y, m),
369 DicomDate(DicomDateImpl::Day(y, m, d)) => write!(f, "{:04}-{:02}-{:02}", y, m, d),
370 }
371 }
372}
373
374impl fmt::Debug for DicomDate {
375 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376 match self {
377 DicomDate(DicomDateImpl::Year(y)) => write!(f, "{:04}-MM-DD", y),
378 DicomDate(DicomDateImpl::Month(y, m)) => write!(f, "{:04}-{:02}-DD", y, m),
379 DicomDate(DicomDateImpl::Day(y, m, d)) => write!(f, "{:04}-{:02}-{:02}", y, m, d),
380 }
381 }
382}
383
384impl DicomTime {
385 pub fn from_h(hour: u8) -> Result<DicomTime> {
390 check_component(DateComponent::Hour, &hour)?;
391 Ok(DicomTime(DicomTimeImpl::Hour(hour)))
392 }
393
394 pub fn from_hm(hour: u8, minute: u8) -> Result<DicomTime> {
399 check_component(DateComponent::Hour, &hour)?;
400 check_component(DateComponent::Minute, &minute)?;
401 Ok(DicomTime(DicomTimeImpl::Minute(hour, minute)))
402 }
403
404 pub fn from_hms(hour: u8, minute: u8, second: u8) -> Result<DicomTime> {
409 check_component(DateComponent::Hour, &hour)?;
410 check_component(DateComponent::Minute, &minute)?;
411 check_component(DateComponent::Second, &second)?;
412 Ok(DicomTime(DicomTimeImpl::Second(hour, minute, second)))
413 }
414 pub fn from_hms_milli(hour: u8, minute: u8, second: u8, millisecond: u32) -> Result<DicomTime> {
419 check_component(DateComponent::Millisecond, &millisecond)?;
420 Ok(DicomTime(DicomTimeImpl::Fraction(
421 hour,
422 minute,
423 second,
424 millisecond,
425 3,
426 )))
427 }
428
429 pub fn from_hms_micro(hour: u8, minute: u8, second: u8, microsecond: u32) -> Result<DicomTime> {
435 check_component(DateComponent::Fraction, µsecond)?;
436 Ok(DicomTime(DicomTimeImpl::Fraction(
437 hour,
438 minute,
439 second,
440 microsecond,
441 6,
442 )))
443 }
444 pub fn hour(&self) -> &u8 {
446 match self {
447 DicomTime(DicomTimeImpl::Hour(h)) => h,
448 DicomTime(DicomTimeImpl::Minute(h, _)) => h,
449 DicomTime(DicomTimeImpl::Second(h, _, _)) => h,
450 DicomTime(DicomTimeImpl::Fraction(h, _, _, _, _)) => h,
451 }
452 }
453 pub fn minute(&self) -> Option<&u8> {
455 match self {
456 DicomTime(DicomTimeImpl::Hour(_)) => None,
457 DicomTime(DicomTimeImpl::Minute(_, m)) => Some(m),
458 DicomTime(DicomTimeImpl::Second(_, m, _)) => Some(m),
459 DicomTime(DicomTimeImpl::Fraction(_, m, _, _, _)) => Some(m),
460 }
461 }
462 pub fn second(&self) -> Option<&u8> {
464 match self {
465 DicomTime(DicomTimeImpl::Hour(_)) => None,
466 DicomTime(DicomTimeImpl::Minute(_, _)) => None,
467 DicomTime(DicomTimeImpl::Second(_, _, s)) => Some(s),
468 DicomTime(DicomTimeImpl::Fraction(_, _, s, _, _)) => Some(s),
469 }
470 }
471 pub fn fraction(&self) -> Option<&u32> {
473 match self {
474 DicomTime(DicomTimeImpl::Hour(_)) => None,
475 DicomTime(DicomTimeImpl::Minute(_, _)) => None,
476 DicomTime(DicomTimeImpl::Second(_, _, _)) => None,
477 DicomTime(DicomTimeImpl::Fraction(_, _, _, f, fp)) => match fp {
478 6 => Some(f),
479 _ => None,
480 },
481 }
482 }
483 pub(crate) fn fraction_and_precision(&self) -> Option<(&u32, &u8)> {
485 match self {
486 DicomTime(DicomTimeImpl::Hour(_)) => None,
487 DicomTime(DicomTimeImpl::Minute(_, _)) => None,
488 DicomTime(DicomTimeImpl::Second(_, _, _)) => None,
489 DicomTime(DicomTimeImpl::Fraction(_, _, _, f, fp)) => Some((f, fp)),
490 }
491 }
492 pub(crate) fn from_hmsf(
497 hour: u8,
498 minute: u8,
499 second: u8,
500 fraction: u32,
501 frac_precision: u8,
502 ) -> Result<DicomTime> {
503 if !(1..=6).contains(&frac_precision) {
504 return FractionPrecisionRangeSnafu {
505 value: frac_precision,
506 }
507 .fail();
508 }
509 if u32::pow(10, frac_precision as u32) < fraction {
510 return FractionPrecisionMismatchSnafu {
511 fraction,
512 precision: frac_precision,
513 }
514 .fail();
515 }
516
517 check_component(DateComponent::Hour, &hour)?;
518 check_component(DateComponent::Minute, &minute)?;
519 check_component(DateComponent::Second, &second)?;
520 let f: u32 = fraction * u32::pow(10, 6 - frac_precision as u32);
521 check_component(DateComponent::Fraction, &f)?;
522 Ok(DicomTime(DicomTimeImpl::Fraction(
523 hour,
524 minute,
525 second,
526 fraction,
527 frac_precision,
528 )))
529 }
530
531 pub(crate) fn precision(&self) -> DateComponent {
533 match self {
534 DicomTime(DicomTimeImpl::Hour(..)) => DateComponent::Hour,
535 DicomTime(DicomTimeImpl::Minute(..)) => DateComponent::Minute,
536 DicomTime(DicomTimeImpl::Second(..)) => DateComponent::Second,
537 DicomTime(DicomTimeImpl::Fraction(..)) => DateComponent::Fraction,
538 }
539 }
540}
541
542impl TryFrom<&NaiveTime> for DicomTime {
543 type Error = Error;
544 fn try_from(time: &NaiveTime) -> Result<Self> {
545 let hour: u8 = time.hour().try_into().with_context(|_| ConversionSnafu {
546 value: time.hour().to_string(),
547 component: DateComponent::Hour,
548 })?;
549 let minute: u8 = time.minute().try_into().with_context(|_| ConversionSnafu {
550 value: time.minute().to_string(),
551 component: DateComponent::Minute,
552 })?;
553 let second: u8 = time.second().try_into().with_context(|_| ConversionSnafu {
554 value: time.second().to_string(),
555 component: DateComponent::Second,
556 })?;
557 let microsecond = time.nanosecond() / 1000;
558 let (second, microsecond) = if microsecond >= 1_000_000 && second == 59 {
560 (60, microsecond - 1_000_000)
561 } else {
562 (second, microsecond)
563 };
564
565 DicomTime::from_hms_micro(hour, minute, second, microsecond)
566 }
567}
568
569impl fmt::Display for DicomTime {
570 fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
571 match self {
572 DicomTime(DicomTimeImpl::Hour(h)) => write!(frm, "{:02}", h),
573 DicomTime(DicomTimeImpl::Minute(h, m)) => write!(frm, "{:02}:{:02}", h, m),
574 DicomTime(DicomTimeImpl::Second(h, m, s)) => {
575 write!(frm, "{:02}:{:02}:{:02}", h, m, s)
576 }
577 DicomTime(DicomTimeImpl::Fraction(h, m, s, f, fp)) => {
578 let sfrac = (u32::pow(10, *fp as u32) + f).to_string();
579 write!(
580 frm,
581 "{:02}:{:02}:{:02}.{}",
582 h,
583 m,
584 s,
585 match f {
586 0 => "0",
587 _ => sfrac.get(1..).unwrap(),
588 }
589 )
590 }
591 }
592 }
593}
594
595impl fmt::Debug for DicomTime {
596 fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
597 match self {
598 DicomTime(DicomTimeImpl::Hour(h)) => write!(frm, "{:02}:mm:ss.FFFFFF", h),
599 DicomTime(DicomTimeImpl::Minute(h, m)) => write!(frm, "{:02}:{:02}:ss.FFFFFF", h, m),
600 DicomTime(DicomTimeImpl::Second(h, m, s)) => {
601 write!(frm, "{:02}:{:02}:{:02}.FFFFFF", h, m, s)
602 }
603 DicomTime(DicomTimeImpl::Fraction(h, m, s, f, _fp)) => {
604 write!(frm, "{:02}:{:02}:{:02}.{:F<6}", h, m, s, f)
605 }
606 }
607 }
608}
609
610impl DicomDateTime {
611 pub fn from_date_with_time_zone(date: DicomDate, time_zone: FixedOffset) -> DicomDateTime {
615 DicomDateTime {
616 date,
617 time: None,
618 time_zone: Some(time_zone),
619 }
620 }
621
622 pub fn from_date(date: DicomDate) -> DicomDateTime {
626 DicomDateTime {
627 date,
628 time: None,
629 time_zone: None,
630 }
631 }
632
633 pub fn from_date_and_time(date: DicomDate, time: DicomTime) -> Result<DicomDateTime> {
638 if date.is_precise() {
639 Ok(DicomDateTime {
640 date,
641 time: Some(time),
642 time_zone: None,
643 })
644 } else {
645 DateTimeFromPartialsSnafu {
646 value: date.precision(),
647 }
648 .fail()
649 }
650 }
651
652 pub fn from_date_and_time_with_time_zone(
657 date: DicomDate,
658 time: DicomTime,
659 time_zone: FixedOffset,
660 ) -> Result<DicomDateTime> {
661 if date.is_precise() {
662 Ok(DicomDateTime {
663 date,
664 time: Some(time),
665 time_zone: Some(time_zone),
666 })
667 } else {
668 DateTimeFromPartialsSnafu {
669 value: date.precision(),
670 }
671 .fail()
672 }
673 }
674
675 pub fn date(&self) -> &DicomDate {
677 &self.date
678 }
679
680 pub fn time(&self) -> Option<&DicomTime> {
682 self.time.as_ref()
683 }
684
685 pub fn time_zone(&self) -> Option<&FixedOffset> {
687 self.time_zone.as_ref()
688 }
689
690 pub fn has_time_zone(&self) -> bool {
692 self.time_zone.is_some()
693 }
694
695 #[deprecated(since = "0.7.0", note = "Use `time_zone` instead")]
697 pub fn offset(&self) {}
698}
699
700impl TryFrom<&DateTime<FixedOffset>> for DicomDateTime {
701 type Error = Error;
702 fn try_from(dt: &DateTime<FixedOffset>) -> Result<Self> {
703 let year: u16 = dt.year().try_into().with_context(|_| ConversionSnafu {
704 value: dt.year().to_string(),
705 component: DateComponent::Year,
706 })?;
707 let month: u8 = dt.month().try_into().with_context(|_| ConversionSnafu {
708 value: dt.month().to_string(),
709 component: DateComponent::Month,
710 })?;
711 let day: u8 = dt.day().try_into().with_context(|_| ConversionSnafu {
712 value: dt.day().to_string(),
713 component: DateComponent::Day,
714 })?;
715 let hour: u8 = dt.hour().try_into().with_context(|_| ConversionSnafu {
716 value: dt.hour().to_string(),
717 component: DateComponent::Hour,
718 })?;
719 let minute: u8 = dt.minute().try_into().with_context(|_| ConversionSnafu {
720 value: dt.minute().to_string(),
721 component: DateComponent::Minute,
722 })?;
723 let second: u8 = dt.second().try_into().with_context(|_| ConversionSnafu {
724 value: dt.second().to_string(),
725 component: DateComponent::Second,
726 })?;
727 let microsecond = dt.nanosecond() / 1000;
728 let (second, microsecond) = if microsecond >= 1_000_000 && second == 59 {
730 (60, microsecond - 1_000_000)
731 } else {
732 (second, microsecond)
733 };
734
735 DicomDateTime::from_date_and_time_with_time_zone(
736 DicomDate::from_ymd(year, month, day)?,
737 DicomTime::from_hms_micro(hour, minute, second, microsecond)?,
738 *dt.offset(),
739 )
740 }
741}
742
743impl TryFrom<&NaiveDateTime> for DicomDateTime {
744 type Error = Error;
745 fn try_from(dt: &NaiveDateTime) -> Result<Self> {
746 let year: u16 = dt.year().try_into().with_context(|_| ConversionSnafu {
747 value: dt.year().to_string(),
748 component: DateComponent::Year,
749 })?;
750 let month: u8 = dt.month().try_into().with_context(|_| ConversionSnafu {
751 value: dt.month().to_string(),
752 component: DateComponent::Month,
753 })?;
754 let day: u8 = dt.day().try_into().with_context(|_| ConversionSnafu {
755 value: dt.day().to_string(),
756 component: DateComponent::Day,
757 })?;
758 let hour: u8 = dt.hour().try_into().with_context(|_| ConversionSnafu {
759 value: dt.hour().to_string(),
760 component: DateComponent::Hour,
761 })?;
762 let minute: u8 = dt.minute().try_into().with_context(|_| ConversionSnafu {
763 value: dt.minute().to_string(),
764 component: DateComponent::Minute,
765 })?;
766 let second: u8 = dt.second().try_into().with_context(|_| ConversionSnafu {
767 value: dt.second().to_string(),
768 component: DateComponent::Second,
769 })?;
770 let microsecond = dt.nanosecond() / 1000;
771 let (second, microsecond) = if microsecond >= 1_000_000 && second == 59 {
773 (60, microsecond - 1_000_000)
774 } else {
775 (second, microsecond)
776 };
777
778 DicomDateTime::from_date_and_time(
779 DicomDate::from_ymd(year, month, day)?,
780 DicomTime::from_hms_micro(hour, minute, second, microsecond)?,
781 )
782 }
783}
784
785impl fmt::Display for DicomDateTime {
786 fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
787 match self.time {
788 None => match self.time_zone {
789 Some(offset) => write!(frm, "{} {}", self.date, offset),
790 None => write!(frm, "{}", self.date),
791 },
792 Some(time) => match self.time_zone {
793 Some(offset) => write!(frm, "{} {} {}", self.date, time, offset),
794 None => write!(frm, "{} {}", self.date, time),
795 },
796 }
797 }
798}
799
800impl fmt::Debug for DicomDateTime {
801 fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
802 match self.time {
803 None => match self.time_zone {
804 Some(offset) => write!(frm, "{:?} {}", self.date, offset),
805 None => write!(frm, "{:?}", self.date),
806 },
807 Some(time) => match self.time_zone {
808 Some(offset) => write!(frm, "{:?} {:?} {}", self.date, time, offset),
809 None => write!(frm, "{:?} {:?}", self.date, time),
810 },
811 }
812 }
813}
814
815impl std::str::FromStr for DicomDateTime {
816 type Err = crate::value::DeserializeError;
817
818 fn from_str(s: &str) -> Result<Self, Self::Err> {
819 crate::value::deserialize::parse_datetime_partial(s.as_bytes())
820 }
821}
822
823impl DicomDate {
824 pub fn to_encoded(&self) -> String {
828 match self {
829 DicomDate(DicomDateImpl::Year(y)) => format!("{:04}", y),
830 DicomDate(DicomDateImpl::Month(y, m)) => format!("{:04}{:02}", y, m),
831 DicomDate(DicomDateImpl::Day(y, m, d)) => format!("{:04}{:02}{:02}", y, m, d),
832 }
833 }
834}
835
836impl DicomTime {
837 pub fn to_encoded(&self) -> String {
841 match self {
842 DicomTime(DicomTimeImpl::Hour(h)) => format!("{:02}", h),
843 DicomTime(DicomTimeImpl::Minute(h, m)) => format!("{:02}{:02}", h, m),
844 DicomTime(DicomTimeImpl::Second(h, m, s)) => format!("{:02}{:02}{:02}", h, m, s),
845 DicomTime(DicomTimeImpl::Fraction(h, m, s, f, fp)) => {
846 let sfrac = (u32::pow(10, *fp as u32) + f).to_string();
847 format!("{:02}{:02}{:02}.{}", h, m, s, sfrac.get(1..).unwrap())
848 }
849 }
850 }
851}
852
853impl DicomDateTime {
854 pub fn to_encoded(&self) -> String {
858 match self.time {
859 Some(time) => match self.time_zone {
860 Some(offset) => format!(
861 "{}{}{}",
862 self.date.to_encoded(),
863 time.to_encoded(),
864 offset.to_string().replace(':', "")
865 ),
866 None => format!("{}{}", self.date.to_encoded(), time.to_encoded()),
867 },
868 None => match self.time_zone {
869 Some(offset) => format!(
870 "{}{}",
871 self.date.to_encoded(),
872 offset.to_string().replace(':', "")
873 ),
874 None => self.date.to_encoded().to_string(),
875 },
876 }
877 }
878}
879
880#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
887pub enum PreciseDateTime {
888 Naive(NaiveDateTime),
890 TimeZone(DateTime<FixedOffset>),
892}
893
894impl PreciseDateTime {
895 pub fn as_datetime(&self) -> Option<&DateTime<FixedOffset>> {
898 match self {
899 PreciseDateTime::Naive(..) => None,
900 PreciseDateTime::TimeZone(value) => Some(value),
901 }
902 }
903
904 pub fn as_naive_datetime(&self) -> Option<&NaiveDateTime> {
907 match self {
908 PreciseDateTime::Naive(value) => Some(value),
909 PreciseDateTime::TimeZone(..) => None,
910 }
911 }
912
913 pub fn into_datetime(self) -> Option<DateTime<FixedOffset>> {
916 match self {
917 PreciseDateTime::Naive(..) => None,
918 PreciseDateTime::TimeZone(value) => Some(value),
919 }
920 }
921
922 pub fn into_naive_datetime(self) -> Option<NaiveDateTime> {
925 match self {
926 PreciseDateTime::Naive(value) => Some(value),
927 PreciseDateTime::TimeZone(..) => None,
928 }
929 }
930
931 pub fn to_naive_date(&self) -> NaiveDate {
941 match self {
942 PreciseDateTime::Naive(value) => value.date(),
943 PreciseDateTime::TimeZone(value) => value.date_naive(),
944 }
945 }
946
947 pub fn to_naive_time(&self) -> NaiveTime {
949 match self {
950 PreciseDateTime::Naive(value) => value.time(),
951 PreciseDateTime::TimeZone(value) => value.time(),
952 }
953 }
954
955 #[inline]
957 pub fn has_time_zone(&self) -> bool {
958 matches!(self, PreciseDateTime::TimeZone(..))
959 }
960}
961
962impl PartialOrd for PreciseDateTime {
969 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
970 match (self, other) {
971 (PreciseDateTime::Naive(a), PreciseDateTime::Naive(b)) => a.partial_cmp(b),
972 (PreciseDateTime::TimeZone(a), PreciseDateTime::TimeZone(b)) => a.partial_cmp(b),
973 _ => None,
974 }
975 }
976}
977
978#[cfg(test)]
979mod tests {
980 use super::*;
981 use chrono::TimeZone;
982
983 #[test]
984 fn test_dicom_date() {
985 assert_eq!(
986 DicomDate::from_ymd(1944, 2, 29).unwrap(),
987 DicomDate(DicomDateImpl::Day(1944, 2, 29))
988 );
989
990 assert!(DicomDate::from_ymd(1945, 2, 29).unwrap().is_precise());
992 assert_eq!(
993 DicomDate::from_ym(1944, 2).unwrap(),
994 DicomDate(DicomDateImpl::Month(1944, 2))
995 );
996 assert_eq!(
997 DicomDate::from_y(1944).unwrap(),
998 DicomDate(DicomDateImpl::Year(1944))
999 );
1000
1001 assert_eq!(DicomDate::from_ymd(1944, 2, 29).unwrap().is_precise(), true);
1002 assert_eq!(DicomDate::from_ym(1944, 2).unwrap().is_precise(), false);
1003 assert_eq!(DicomDate::from_y(1944).unwrap().is_precise(), false);
1004 assert_eq!(
1005 DicomDate::from_ymd(1944, 2, 29)
1006 .unwrap()
1007 .earliest()
1008 .unwrap(),
1009 NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
1010 );
1011 assert_eq!(
1012 DicomDate::from_ymd(1944, 2, 29).unwrap().latest().unwrap(),
1013 NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
1014 );
1015
1016 assert_eq!(
1017 DicomDate::from_y(1944).unwrap().earliest().unwrap(),
1018 NaiveDate::from_ymd_opt(1944, 1, 1).unwrap()
1019 );
1020 assert_eq!(
1022 DicomDate::from_ym(1944, 2).unwrap().latest().unwrap(),
1023 NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
1024 );
1025 assert_eq!(
1026 DicomDate::from_ym(1945, 2).unwrap().latest().unwrap(),
1027 NaiveDate::from_ymd_opt(1945, 2, 28).unwrap()
1028 );
1029
1030 assert_eq!(
1031 DicomDate::try_from(&NaiveDate::from_ymd_opt(1945, 2, 28).unwrap()).unwrap(),
1032 DicomDate(DicomDateImpl::Day(1945, 2, 28))
1033 );
1034
1035 assert!(matches!(
1036 DicomDate::try_from(&NaiveDate::from_ymd_opt(-2000, 2, 28).unwrap()),
1037 Err(Error::Conversion { .. })
1038 ));
1039
1040 assert!(matches!(
1041 DicomDate::try_from(&NaiveDate::from_ymd_opt(10_000, 2, 28).unwrap()),
1042 Err(Error::InvalidComponent {
1043 component: DateComponent::Year,
1044 ..
1045 })
1046 ));
1047 }
1048
1049 #[test]
1050 fn test_dicom_time() {
1051 assert_eq!(
1052 DicomTime::from_hms_micro(9, 1, 1, 123456).unwrap(),
1053 DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 123456, 6))
1054 );
1055 assert_eq!(
1056 DicomTime::from_hms_micro(9, 1, 1, 1).unwrap(),
1057 DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 1, 6))
1058 );
1059 assert_eq!(
1060 DicomTime::from_hms(9, 0, 0).unwrap(),
1061 DicomTime(DicomTimeImpl::Second(9, 0, 0))
1062 );
1063 assert_eq!(
1064 DicomTime::from_hm(23, 59).unwrap(),
1065 DicomTime(DicomTimeImpl::Minute(23, 59))
1066 );
1067 assert_eq!(
1068 DicomTime::from_h(1).unwrap(),
1069 DicomTime(DicomTimeImpl::Hour(1))
1070 );
1071 assert!(DicomTime::from_hms_micro(9, 1, 1, 123456)
1073 .unwrap()
1074 .is_precise());
1075 assert!(!DicomTime::from_hms_milli(9, 1, 1, 123)
1076 .unwrap()
1077 .is_precise());
1078
1079 assert_eq!(
1080 DicomTime::from_hms_milli(9, 1, 1, 123)
1081 .unwrap()
1082 .earliest()
1083 .unwrap(),
1084 NaiveTime::from_hms_micro_opt(9, 1, 1, 123_000).unwrap()
1085 );
1086 assert_eq!(
1087 DicomTime::from_hms_milli(9, 1, 1, 123)
1088 .unwrap()
1089 .latest()
1090 .unwrap(),
1091 NaiveTime::from_hms_micro_opt(9, 1, 1, 123_999).unwrap()
1092 );
1093
1094 assert_eq!(
1095 DicomTime::from_hms_milli(9, 1, 1, 2)
1096 .unwrap()
1097 .earliest()
1098 .unwrap(),
1099 NaiveTime::from_hms_micro_opt(9, 1, 1, 002000).unwrap()
1100 );
1101 assert_eq!(
1102 DicomTime::from_hms_milli(9, 1, 1, 2)
1103 .unwrap()
1104 .latest()
1105 .unwrap(),
1106 NaiveTime::from_hms_micro_opt(9, 1, 1, 002999).unwrap()
1107 );
1108
1109 assert_eq!(
1110 DicomTime::from_hms_micro(9, 1, 1, 123456)
1111 .unwrap()
1112 .is_precise(),
1113 true
1114 );
1115
1116 assert_eq!(
1117 DicomTime::from_hms_milli(9, 1, 1, 1).unwrap(),
1118 DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 1, 3))
1119 );
1120
1121 assert_eq!(
1122 DicomTime::try_from(&NaiveTime::from_hms_milli_opt(16, 31, 28, 123).unwrap()).unwrap(),
1123 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 123_000, 6))
1124 );
1125
1126 assert_eq!(
1127 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 123).unwrap()).unwrap(),
1128 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 000123, 6))
1129 );
1130
1131 assert_eq!(
1132 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 1234).unwrap()).unwrap(),
1133 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 001234, 6))
1134 );
1135
1136 assert_eq!(
1137 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 0).unwrap()).unwrap(),
1138 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 0, 6))
1139 );
1140
1141 assert_eq!(
1142 DicomTime::from_hmsf(9, 1, 1, 1, 4).unwrap().to_string(),
1143 "09:01:01.0001"
1144 );
1145 assert_eq!(
1146 DicomTime::from_hmsf(9, 1, 1, 0, 1).unwrap().to_string(),
1147 "09:01:01.0"
1148 );
1149 assert_eq!(
1150 DicomTime::from_hmsf(7, 55, 1, 1, 5).unwrap().to_encoded(),
1151 "075501.00001"
1152 );
1153 assert_eq!(
1155 DicomTime::from_hmsf(9, 1, 1, 0, 2).unwrap().to_encoded(),
1156 "090101.00"
1157 );
1158 assert_eq!(
1159 DicomTime::from_hmsf(9, 1, 1, 0, 3).unwrap().to_encoded(),
1160 "090101.000"
1161 );
1162 assert_eq!(
1163 DicomTime::from_hmsf(9, 1, 1, 0, 4).unwrap().to_encoded(),
1164 "090101.0000"
1165 );
1166 assert_eq!(
1167 DicomTime::from_hmsf(9, 1, 1, 0, 5).unwrap().to_encoded(),
1168 "090101.00000"
1169 );
1170 assert_eq!(
1171 DicomTime::from_hmsf(9, 1, 1, 0, 6).unwrap().to_encoded(),
1172 "090101.000000"
1173 );
1174
1175 assert_eq!(
1177 DicomTime::from_hmsf(23, 59, 60, 123, 3)
1178 .unwrap()
1179 .to_encoded(),
1180 "235960.123",
1181 );
1182
1183 assert_eq!(
1185 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 59, 1_000_000).unwrap())
1186 .unwrap()
1187 .to_encoded(),
1188 "163160.000000",
1189 );
1190
1191 assert_eq!(
1193 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 59, 1_012_345).unwrap())
1194 .unwrap()
1195 .to_encoded(),
1196 "163160.012345",
1197 );
1198
1199
1200 assert_eq!(
1202 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 59, 0).unwrap())
1203 .unwrap()
1204 .to_encoded(),
1205 "163159.000000",
1206 );
1207
1208 let date_time: DateTime<_> = DateTime::<chrono::Utc>::from_naive_utc_and_offset(
1210 NaiveDateTime::new(
1211 NaiveDate::from_ymd_opt(2024, 8, 9).unwrap(),
1212 NaiveTime::from_hms_opt(9, 9, 39).unwrap(),
1213 ),
1214 chrono::Utc,
1215 ).with_timezone(&FixedOffset::east_opt(0).unwrap());
1216 let dicom_date_time = DicomDateTime::try_from(&date_time).unwrap();
1217 assert!(dicom_date_time.has_time_zone());
1218 assert!(dicom_date_time.is_precise());
1219 let dicom_time = dicom_date_time.time().unwrap();
1220 assert_eq!(
1221 dicom_time.fraction_and_precision(),
1222 Some((&0, &6)),
1223 );
1224 assert_eq!(
1225 dicom_date_time.to_encoded(),
1226 "20240809090939.000000+0000"
1227 );
1228
1229 assert!(matches!(
1232 DicomTime::from_hmsf(9, 1, 1, 1, 7),
1233 Err(Error::FractionPrecisionRange { value: 7, .. })
1234 ));
1235
1236 assert!(matches!(
1237 DicomTime::from_hms_milli(9, 1, 1, 1000),
1238 Err(Error::InvalidComponent {
1239 component: DateComponent::Millisecond,
1240 ..
1241 })
1242 ));
1243
1244 assert!(matches!(
1245 DicomTime::from_hmsf(9, 1, 1, 123456, 3),
1246 Err(Error::FractionPrecisionMismatch {
1247 fraction: 123456,
1248 precision: 3,
1249 ..
1250 })
1251 ));
1252
1253 assert!(matches!(
1255 DicomTime::from_hmsf(9, 1, 1, 1_000_000, 6),
1256 Err(Error::InvalidComponent {
1257 component: DateComponent::Fraction,
1258 ..
1259 })
1260 ));
1261
1262 assert!(matches!(
1263 DicomTime::from_hmsf(9, 1, 1, 12345, 5).unwrap().exact(),
1264 Err(crate::value::range::Error::ImpreciseValue { .. })
1265 ));
1266 }
1267
1268 #[test]
1269 fn test_dicom_datetime() {
1270 let default_offset = FixedOffset::east_opt(0).unwrap();
1271 assert_eq!(
1272 DicomDateTime::from_date_with_time_zone(
1273 DicomDate::from_ymd(2020, 2, 29).unwrap(),
1274 default_offset
1275 ),
1276 DicomDateTime {
1277 date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
1278 time: None,
1279 time_zone: Some(default_offset)
1280 }
1281 );
1282
1283 assert_eq!(
1284 DicomDateTime::from_date(DicomDate::from_ym(2020, 2).unwrap())
1285 .earliest()
1286 .unwrap(),
1287 PreciseDateTime::Naive(NaiveDateTime::new(
1288 NaiveDate::from_ymd_opt(2020, 2, 1).unwrap(),
1289 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1290 ))
1291 );
1292
1293 assert_eq!(
1294 DicomDateTime::from_date_with_time_zone(
1295 DicomDate::from_ym(2020, 2).unwrap(),
1296 default_offset
1297 )
1298 .latest()
1299 .unwrap(),
1300 PreciseDateTime::TimeZone(
1301 FixedOffset::east_opt(0)
1302 .unwrap()
1303 .from_local_datetime(&NaiveDateTime::new(
1304 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1305 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1306 ))
1307 .unwrap()
1308 )
1309 );
1310
1311 assert_eq!(
1312 DicomDateTime::from_date_and_time_with_time_zone(
1313 DicomDate::from_ymd(2020, 2, 29).unwrap(),
1314 DicomTime::from_hmsf(23, 59, 59, 10, 2).unwrap(),
1315 default_offset
1316 )
1317 .unwrap()
1318 .earliest()
1319 .unwrap(),
1320 PreciseDateTime::TimeZone(
1321 FixedOffset::east_opt(0)
1322 .unwrap()
1323 .from_local_datetime(&NaiveDateTime::new(
1324 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1325 NaiveTime::from_hms_micro_opt(23, 59, 59, 100_000).unwrap()
1326 ))
1327 .unwrap()
1328 )
1329 );
1330 assert_eq!(
1331 DicomDateTime::from_date_and_time_with_time_zone(
1332 DicomDate::from_ymd(2020, 2, 29).unwrap(),
1333 DicomTime::from_hmsf(23, 59, 59, 10, 2).unwrap(),
1334 default_offset
1335 )
1336 .unwrap()
1337 .latest()
1338 .unwrap(),
1339 PreciseDateTime::TimeZone(
1340 FixedOffset::east_opt(0)
1341 .unwrap()
1342 .from_local_datetime(&NaiveDateTime::new(
1343 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1344 NaiveTime::from_hms_micro_opt(23, 59, 59, 109_999).unwrap()
1345 ))
1346 .unwrap()
1347 )
1348 );
1349
1350 assert_eq!(
1351 DicomDateTime::try_from(
1352 &FixedOffset::east_opt(0)
1353 .unwrap()
1354 .from_local_datetime(&NaiveDateTime::new(
1355 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1356 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1357 ))
1358 .unwrap()
1359 )
1360 .unwrap(),
1361 DicomDateTime {
1362 date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
1363 time: Some(DicomTime::from_hms_micro(23, 59, 59, 999_999).unwrap()),
1364 time_zone: Some(default_offset)
1365 }
1366 );
1367
1368 assert_eq!(
1369 DicomDateTime::try_from(
1370 &FixedOffset::east_opt(0)
1371 .unwrap()
1372 .from_local_datetime(&NaiveDateTime::new(
1373 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1374 NaiveTime::from_hms_micro_opt(23, 59, 59, 0).unwrap()
1375 ))
1376 .unwrap()
1377 )
1378 .unwrap(),
1379 DicomDateTime {
1380 date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
1381 time: Some(DicomTime::from_hms_micro(23, 59, 59, 0).unwrap()),
1382 time_zone: Some(default_offset)
1383 }
1384 );
1385
1386 assert_eq!(
1388 DicomDateTime::try_from(
1389 &FixedOffset::east_opt(0)
1390 .unwrap()
1391 .from_local_datetime(&NaiveDateTime::new(
1392 NaiveDate::from_ymd_opt(2023, 12, 31).unwrap(),
1393 NaiveTime::from_hms_micro_opt(23, 59, 59, 1_000_000).unwrap()
1394 ))
1395 .unwrap()
1396 )
1397 .unwrap(),
1398 DicomDateTime {
1399 date: DicomDate::from_ymd(2023, 12, 31).unwrap(),
1400 time: Some(DicomTime::from_hms_micro(23, 59, 60, 0).unwrap()),
1401 time_zone: Some(default_offset)
1402 }
1403 );
1404
1405 assert!(matches!(
1406 DicomDateTime::from_date_with_time_zone(
1407 DicomDate::from_ymd(2021, 2, 29).unwrap(),
1408 default_offset
1409 )
1410 .earliest(),
1411 Err(crate::value::range::Error::InvalidDate { .. })
1412 ));
1413
1414 assert!(matches!(
1415 DicomDateTime::from_date_and_time_with_time_zone(
1416 DicomDate::from_ym(2020, 2).unwrap(),
1417 DicomTime::from_hms_milli(23, 59, 59, 999).unwrap(),
1418 default_offset
1419 ),
1420 Err(Error::DateTimeFromPartials {
1421 value: DateComponent::Month,
1422 ..
1423 })
1424 ));
1425 assert!(matches!(
1426 DicomDateTime::from_date_and_time_with_time_zone(
1427 DicomDate::from_y(1).unwrap(),
1428 DicomTime::from_hms_micro(23, 59, 59, 10).unwrap(),
1429 default_offset
1430 ),
1431 Err(Error::DateTimeFromPartials {
1432 value: DateComponent::Year,
1433 ..
1434 })
1435 ));
1436
1437 assert!(matches!(
1438 DicomDateTime::from_date_and_time_with_time_zone(
1439 DicomDate::from_ymd(2000, 1, 1).unwrap(),
1440 DicomTime::from_hms_milli(23, 59, 59, 10).unwrap(),
1441 default_offset
1442 )
1443 .unwrap()
1444 .exact(),
1445 Err(crate::value::range::Error::ImpreciseValue { .. })
1446 ));
1447
1448 assert!(
1450 DicomDateTime::from_date_and_time(
1451 DicomDate::from_ymd(2000, 1, 1).unwrap(),
1452 DicomTime::from_hms_milli(23, 59, 59, 10).unwrap()
1453 )
1454 .unwrap()
1455 .is_precise()
1456 == false
1457 );
1458
1459 assert!(DicomDateTime::from_date_and_time(
1460 DicomDate::from_ymd(2000, 1, 1).unwrap(),
1461 DicomTime::from_hms_micro(23, 59, 59, 654_321).unwrap()
1462 )
1463 .unwrap()
1464 .is_precise());
1465 }
1466}