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!(
848 "{:02}{:02}{:02}.{}",
849 h,
850 m,
851 s,
852 match f {
853 0 => "0",
854 _ => sfrac.get(1..).unwrap(),
855 }
856 )
857 }
858 }
859 }
860}
861
862impl DicomDateTime {
863 pub fn to_encoded(&self) -> String {
867 match self.time {
868 Some(time) => match self.time_zone {
869 Some(offset) => format!(
870 "{}{}{}",
871 self.date.to_encoded(),
872 time.to_encoded(),
873 offset.to_string().replace(':', "")
874 ),
875 None => format!("{}{}", self.date.to_encoded(), time.to_encoded()),
876 },
877 None => match self.time_zone {
878 Some(offset) => format!(
879 "{}{}",
880 self.date.to_encoded(),
881 offset.to_string().replace(':', "")
882 ),
883 None => self.date.to_encoded().to_string(),
884 },
885 }
886 }
887}
888
889#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
896pub enum PreciseDateTime {
897 Naive(NaiveDateTime),
899 TimeZone(DateTime<FixedOffset>),
901}
902
903impl PreciseDateTime {
904 pub fn as_datetime(&self) -> Option<&DateTime<FixedOffset>> {
907 match self {
908 PreciseDateTime::Naive(..) => None,
909 PreciseDateTime::TimeZone(value) => Some(value),
910 }
911 }
912
913 pub fn as_naive_datetime(&self) -> Option<&NaiveDateTime> {
916 match self {
917 PreciseDateTime::Naive(value) => Some(value),
918 PreciseDateTime::TimeZone(..) => None,
919 }
920 }
921
922 pub fn into_datetime(self) -> Option<DateTime<FixedOffset>> {
925 match self {
926 PreciseDateTime::Naive(..) => None,
927 PreciseDateTime::TimeZone(value) => Some(value),
928 }
929 }
930
931 pub fn into_naive_datetime(self) -> Option<NaiveDateTime> {
934 match self {
935 PreciseDateTime::Naive(value) => Some(value),
936 PreciseDateTime::TimeZone(..) => None,
937 }
938 }
939
940 pub fn to_naive_date(&self) -> NaiveDate {
950 match self {
951 PreciseDateTime::Naive(value) => value.date(),
952 PreciseDateTime::TimeZone(value) => value.date_naive(),
953 }
954 }
955
956 pub fn to_naive_time(&self) -> NaiveTime {
958 match self {
959 PreciseDateTime::Naive(value) => value.time(),
960 PreciseDateTime::TimeZone(value) => value.time(),
961 }
962 }
963
964 #[inline]
966 pub fn has_time_zone(&self) -> bool {
967 matches!(self, PreciseDateTime::TimeZone(..))
968 }
969}
970
971impl PartialOrd for PreciseDateTime {
978 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
979 match (self, other) {
980 (PreciseDateTime::Naive(a), PreciseDateTime::Naive(b)) => a.partial_cmp(b),
981 (PreciseDateTime::TimeZone(a), PreciseDateTime::TimeZone(b)) => a.partial_cmp(b),
982 _ => None,
983 }
984 }
985}
986
987#[cfg(test)]
988mod tests {
989 use super::*;
990 use chrono::TimeZone;
991
992 #[test]
993 fn test_dicom_date() {
994 assert_eq!(
995 DicomDate::from_ymd(1944, 2, 29).unwrap(),
996 DicomDate(DicomDateImpl::Day(1944, 2, 29))
997 );
998
999 assert!(DicomDate::from_ymd(1945, 2, 29).unwrap().is_precise());
1001 assert_eq!(
1002 DicomDate::from_ym(1944, 2).unwrap(),
1003 DicomDate(DicomDateImpl::Month(1944, 2))
1004 );
1005 assert_eq!(
1006 DicomDate::from_y(1944).unwrap(),
1007 DicomDate(DicomDateImpl::Year(1944))
1008 );
1009
1010 assert_eq!(DicomDate::from_ymd(1944, 2, 29).unwrap().is_precise(), true);
1011 assert_eq!(DicomDate::from_ym(1944, 2).unwrap().is_precise(), false);
1012 assert_eq!(DicomDate::from_y(1944).unwrap().is_precise(), false);
1013 assert_eq!(
1014 DicomDate::from_ymd(1944, 2, 29)
1015 .unwrap()
1016 .earliest()
1017 .unwrap(),
1018 NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
1019 );
1020 assert_eq!(
1021 DicomDate::from_ymd(1944, 2, 29).unwrap().latest().unwrap(),
1022 NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
1023 );
1024
1025 assert_eq!(
1026 DicomDate::from_y(1944).unwrap().earliest().unwrap(),
1027 NaiveDate::from_ymd_opt(1944, 1, 1).unwrap()
1028 );
1029 assert_eq!(
1031 DicomDate::from_ym(1944, 2).unwrap().latest().unwrap(),
1032 NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
1033 );
1034 assert_eq!(
1035 DicomDate::from_ym(1945, 2).unwrap().latest().unwrap(),
1036 NaiveDate::from_ymd_opt(1945, 2, 28).unwrap()
1037 );
1038
1039 assert_eq!(
1040 DicomDate::try_from(&NaiveDate::from_ymd_opt(1945, 2, 28).unwrap()).unwrap(),
1041 DicomDate(DicomDateImpl::Day(1945, 2, 28))
1042 );
1043
1044 assert!(matches!(
1045 DicomDate::try_from(&NaiveDate::from_ymd_opt(-2000, 2, 28).unwrap()),
1046 Err(Error::Conversion { .. })
1047 ));
1048
1049 assert!(matches!(
1050 DicomDate::try_from(&NaiveDate::from_ymd_opt(10_000, 2, 28).unwrap()),
1051 Err(Error::InvalidComponent {
1052 component: DateComponent::Year,
1053 ..
1054 })
1055 ));
1056 }
1057
1058 #[test]
1059 fn test_dicom_time() {
1060 assert_eq!(
1061 DicomTime::from_hms_micro(9, 1, 1, 123456).unwrap(),
1062 DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 123456, 6))
1063 );
1064 assert_eq!(
1065 DicomTime::from_hms_micro(9, 1, 1, 1).unwrap(),
1066 DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 1, 6))
1067 );
1068 assert_eq!(
1069 DicomTime::from_hms(9, 0, 0).unwrap(),
1070 DicomTime(DicomTimeImpl::Second(9, 0, 0))
1071 );
1072 assert_eq!(
1073 DicomTime::from_hm(23, 59).unwrap(),
1074 DicomTime(DicomTimeImpl::Minute(23, 59))
1075 );
1076 assert_eq!(
1077 DicomTime::from_h(1).unwrap(),
1078 DicomTime(DicomTimeImpl::Hour(1))
1079 );
1080 assert!(DicomTime::from_hms_micro(9, 1, 1, 123456)
1082 .unwrap()
1083 .is_precise());
1084 assert!(!DicomTime::from_hms_milli(9, 1, 1, 123)
1085 .unwrap()
1086 .is_precise());
1087
1088 assert_eq!(
1089 DicomTime::from_hms_milli(9, 1, 1, 123)
1090 .unwrap()
1091 .earliest()
1092 .unwrap(),
1093 NaiveTime::from_hms_micro_opt(9, 1, 1, 123_000).unwrap()
1094 );
1095 assert_eq!(
1096 DicomTime::from_hms_milli(9, 1, 1, 123)
1097 .unwrap()
1098 .latest()
1099 .unwrap(),
1100 NaiveTime::from_hms_micro_opt(9, 1, 1, 123_999).unwrap()
1101 );
1102
1103 assert_eq!(
1104 DicomTime::from_hms_milli(9, 1, 1, 2)
1105 .unwrap()
1106 .earliest()
1107 .unwrap(),
1108 NaiveTime::from_hms_micro_opt(9, 1, 1, 002000).unwrap()
1109 );
1110 assert_eq!(
1111 DicomTime::from_hms_milli(9, 1, 1, 2)
1112 .unwrap()
1113 .latest()
1114 .unwrap(),
1115 NaiveTime::from_hms_micro_opt(9, 1, 1, 002999).unwrap()
1116 );
1117
1118 assert_eq!(
1119 DicomTime::from_hms_micro(9, 1, 1, 123456)
1120 .unwrap()
1121 .is_precise(),
1122 true
1123 );
1124
1125 assert_eq!(
1126 DicomTime::from_hms_milli(9, 1, 1, 1).unwrap(),
1127 DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 1, 3))
1128 );
1129
1130 assert_eq!(
1131 DicomTime::try_from(&NaiveTime::from_hms_milli_opt(16, 31, 28, 123).unwrap()).unwrap(),
1132 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 123_000, 6))
1133 );
1134
1135 assert_eq!(
1136 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 123).unwrap()).unwrap(),
1137 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 000123, 6))
1138 );
1139
1140 assert_eq!(
1141 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 1234).unwrap()).unwrap(),
1142 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 001234, 6))
1143 );
1144
1145 assert_eq!(
1146 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 0).unwrap()).unwrap(),
1147 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 0, 6))
1148 );
1149
1150 assert_eq!(
1151 DicomTime::from_hmsf(9, 1, 1, 1, 4).unwrap().to_string(),
1152 "09:01:01.0001"
1153 );
1154 assert_eq!(
1155 DicomTime::from_hmsf(9, 1, 1, 0, 1).unwrap().to_string(),
1156 "09:01:01.0"
1157 );
1158 assert_eq!(
1159 DicomTime::from_hmsf(7, 55, 1, 1, 5).unwrap().to_encoded(),
1160 "075501.00001"
1161 );
1162 assert_eq!(
1164 DicomTime::from_hmsf(9, 1, 1, 0, 6).unwrap().to_encoded(),
1165 "090101.0"
1166 );
1167
1168 assert_eq!(
1170 DicomTime::from_hmsf(23, 59, 60, 123, 3)
1171 .unwrap()
1172 .to_encoded(),
1173 "235960.123",
1174 );
1175
1176 assert_eq!(
1178 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 59, 1_000_000).unwrap())
1179 .unwrap()
1180 .to_encoded(),
1181 "163160.0",
1182 );
1183
1184 assert_eq!(
1186 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 59, 1_012_345).unwrap())
1187 .unwrap()
1188 .to_encoded(),
1189 "163160.012345",
1190 );
1191
1192 assert!(matches!(
1193 DicomTime::from_hmsf(9, 1, 1, 1, 7),
1194 Err(Error::FractionPrecisionRange { value: 7, .. })
1195 ));
1196
1197 assert!(matches!(
1198 DicomTime::from_hms_milli(9, 1, 1, 1000),
1199 Err(Error::InvalidComponent {
1200 component: DateComponent::Millisecond,
1201 ..
1202 })
1203 ));
1204
1205 assert!(matches!(
1206 DicomTime::from_hmsf(9, 1, 1, 123456, 3),
1207 Err(Error::FractionPrecisionMismatch {
1208 fraction: 123456,
1209 precision: 3,
1210 ..
1211 })
1212 ));
1213
1214 assert!(matches!(
1216 DicomTime::from_hmsf(9, 1, 1, 1_000_000, 6),
1217 Err(Error::InvalidComponent {
1218 component: DateComponent::Fraction,
1219 ..
1220 })
1221 ));
1222
1223 assert!(matches!(
1224 DicomTime::from_hmsf(9, 1, 1, 12345, 5).unwrap().exact(),
1225 Err(crate::value::range::Error::ImpreciseValue { .. })
1226 ));
1227 }
1228
1229 #[test]
1230 fn test_dicom_datetime() {
1231 let default_offset = FixedOffset::east_opt(0).unwrap();
1232 assert_eq!(
1233 DicomDateTime::from_date_with_time_zone(
1234 DicomDate::from_ymd(2020, 2, 29).unwrap(),
1235 default_offset
1236 ),
1237 DicomDateTime {
1238 date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
1239 time: None,
1240 time_zone: Some(default_offset)
1241 }
1242 );
1243
1244 assert_eq!(
1245 DicomDateTime::from_date(DicomDate::from_ym(2020, 2).unwrap())
1246 .earliest()
1247 .unwrap(),
1248 PreciseDateTime::Naive(NaiveDateTime::new(
1249 NaiveDate::from_ymd_opt(2020, 2, 1).unwrap(),
1250 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1251 ))
1252 );
1253
1254 assert_eq!(
1255 DicomDateTime::from_date_with_time_zone(
1256 DicomDate::from_ym(2020, 2).unwrap(),
1257 default_offset
1258 )
1259 .latest()
1260 .unwrap(),
1261 PreciseDateTime::TimeZone(
1262 FixedOffset::east_opt(0)
1263 .unwrap()
1264 .from_local_datetime(&NaiveDateTime::new(
1265 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1266 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1267 ))
1268 .unwrap()
1269 )
1270 );
1271
1272 assert_eq!(
1273 DicomDateTime::from_date_and_time_with_time_zone(
1274 DicomDate::from_ymd(2020, 2, 29).unwrap(),
1275 DicomTime::from_hmsf(23, 59, 59, 10, 2).unwrap(),
1276 default_offset
1277 )
1278 .unwrap()
1279 .earliest()
1280 .unwrap(),
1281 PreciseDateTime::TimeZone(
1282 FixedOffset::east_opt(0)
1283 .unwrap()
1284 .from_local_datetime(&NaiveDateTime::new(
1285 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1286 NaiveTime::from_hms_micro_opt(23, 59, 59, 100_000).unwrap()
1287 ))
1288 .unwrap()
1289 )
1290 );
1291 assert_eq!(
1292 DicomDateTime::from_date_and_time_with_time_zone(
1293 DicomDate::from_ymd(2020, 2, 29).unwrap(),
1294 DicomTime::from_hmsf(23, 59, 59, 10, 2).unwrap(),
1295 default_offset
1296 )
1297 .unwrap()
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, 109_999).unwrap()
1306 ))
1307 .unwrap()
1308 )
1309 );
1310
1311 assert_eq!(
1312 DicomDateTime::try_from(
1313 &FixedOffset::east_opt(0)
1314 .unwrap()
1315 .from_local_datetime(&NaiveDateTime::new(
1316 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1317 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1318 ))
1319 .unwrap()
1320 )
1321 .unwrap(),
1322 DicomDateTime {
1323 date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
1324 time: Some(DicomTime::from_hms_micro(23, 59, 59, 999_999).unwrap()),
1325 time_zone: Some(default_offset)
1326 }
1327 );
1328
1329 assert_eq!(
1330 DicomDateTime::try_from(
1331 &FixedOffset::east_opt(0)
1332 .unwrap()
1333 .from_local_datetime(&NaiveDateTime::new(
1334 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1335 NaiveTime::from_hms_micro_opt(23, 59, 59, 0).unwrap()
1336 ))
1337 .unwrap()
1338 )
1339 .unwrap(),
1340 DicomDateTime {
1341 date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
1342 time: Some(DicomTime::from_hms_micro(23, 59, 59, 0).unwrap()),
1343 time_zone: Some(default_offset)
1344 }
1345 );
1346
1347 assert_eq!(
1349 DicomDateTime::try_from(
1350 &FixedOffset::east_opt(0)
1351 .unwrap()
1352 .from_local_datetime(&NaiveDateTime::new(
1353 NaiveDate::from_ymd_opt(2023, 12, 31).unwrap(),
1354 NaiveTime::from_hms_micro_opt(23, 59, 59, 1_000_000).unwrap()
1355 ))
1356 .unwrap()
1357 )
1358 .unwrap(),
1359 DicomDateTime {
1360 date: DicomDate::from_ymd(2023, 12, 31).unwrap(),
1361 time: Some(DicomTime::from_hms_micro(23, 59, 60, 0).unwrap()),
1362 time_zone: Some(default_offset)
1363 }
1364 );
1365
1366 assert!(matches!(
1367 DicomDateTime::from_date_with_time_zone(
1368 DicomDate::from_ymd(2021, 2, 29).unwrap(),
1369 default_offset
1370 )
1371 .earliest(),
1372 Err(crate::value::range::Error::InvalidDate { .. })
1373 ));
1374
1375 assert!(matches!(
1376 DicomDateTime::from_date_and_time_with_time_zone(
1377 DicomDate::from_ym(2020, 2).unwrap(),
1378 DicomTime::from_hms_milli(23, 59, 59, 999).unwrap(),
1379 default_offset
1380 ),
1381 Err(Error::DateTimeFromPartials {
1382 value: DateComponent::Month,
1383 ..
1384 })
1385 ));
1386 assert!(matches!(
1387 DicomDateTime::from_date_and_time_with_time_zone(
1388 DicomDate::from_y(1).unwrap(),
1389 DicomTime::from_hms_micro(23, 59, 59, 10).unwrap(),
1390 default_offset
1391 ),
1392 Err(Error::DateTimeFromPartials {
1393 value: DateComponent::Year,
1394 ..
1395 })
1396 ));
1397
1398 assert!(matches!(
1399 DicomDateTime::from_date_and_time_with_time_zone(
1400 DicomDate::from_ymd(2000, 1, 1).unwrap(),
1401 DicomTime::from_hms_milli(23, 59, 59, 10).unwrap(),
1402 default_offset
1403 )
1404 .unwrap()
1405 .exact(),
1406 Err(crate::value::range::Error::ImpreciseValue { .. })
1407 ));
1408
1409 assert!(
1411 DicomDateTime::from_date_and_time(
1412 DicomDate::from_ymd(2000, 1, 1).unwrap(),
1413 DicomTime::from_hms_milli(23, 59, 59, 10).unwrap()
1414 )
1415 .unwrap()
1416 .is_precise()
1417 == false
1418 );
1419
1420 assert!(DicomDateTime::from_date_and_time(
1421 DicomDate::from_ymd(2000, 1, 1).unwrap(),
1422 DicomTime::from_hms_micro(23, 59, 59, 654_321).unwrap()
1423 )
1424 .unwrap()
1425 .is_precise());
1426 }
1427}