1use crate::value::AsRange;
4use chrono::{
5 DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc,
6};
7use snafu::{Backtrace, ResultExt, Snafu};
8use std::convert::{TryFrom, TryInto};
9use std::fmt;
10use std::ops::RangeInclusive;
11
12#[derive(Debug, Snafu)]
13#[non_exhaustive]
14pub enum Error {
15 #[snafu(display("To combine a DicomDate with a DicomTime value, the DicomDate has to be precise. Precision is: '{:?}'", value))]
16 DateTimeFromPartials {
17 value: DateComponent,
18 backtrace: Backtrace,
19 },
20 #[snafu(display(
21 "'{:?}' has invalid value: '{}', must be in {:?}",
22 component,
23 value,
24 range
25 ))]
26 InvalidComponent {
27 component: DateComponent,
28 value: u32,
29 range: RangeInclusive<u32>,
30 backtrace: Backtrace,
31 },
32 #[snafu(display(
33 "Second fraction precision '{}' is out of range, must be in 0..=6",
34 value
35 ))]
36 FractionPrecisionRange { value: u32, backtrace: Backtrace },
37 #[snafu(display(
38 "Number of digits in decimal representation of fraction '{}' does not match it's precision '{}'",
39 fraction,
40 precision
41 ))]
42 FractionPrecisionMismatch {
43 fraction: u32,
44 precision: u32,
45 backtrace: Backtrace,
46 },
47 #[snafu(display("Conversion of value '{}' into {:?} failed", value, component))]
48 Conversion {
49 value: String,
50 component: DateComponent,
51 source: std::num::TryFromIntError,
52 },
53 #[snafu(display(
54 "Cannot convert from an imprecise value. This value represents a date / time range"
55 ))]
56 ImpreciseValue { backtrace: Backtrace },
57}
58
59type Result<T, E = Error> = std::result::Result<T, E>;
60
61#[derive(Debug, PartialEq, Copy, Clone, Eq, Hash, PartialOrd, Ord)]
63pub enum DateComponent {
64 Year,
66 Month,
68 Day,
70 Hour,
72 Minute,
74 Second,
76 Millisecond,
78 Fraction,
80 UtcWest,
82 UtcEast,
84}
85
86#[derive(Clone, Copy, PartialEq)]
118pub struct DicomDate(DicomDateImpl);
119
120#[derive(Clone, Copy, PartialEq)]
165pub struct DicomTime(DicomTimeImpl);
166
167#[derive(Debug, Clone, Copy, PartialEq)]
170enum DicomDateImpl {
171 Year(u16),
172 Month(u16, u8),
173 Day(u16, u8, u8),
174}
175
176#[derive(Debug, Clone, Copy, PartialEq)]
181enum DicomTimeImpl {
182 Hour(u8),
183 Minute(u8, u8),
184 Second(u8, u8, u8),
185 Fraction(u8, u8, u8, u32, u8),
186}
187
188#[derive(PartialEq, Clone, Copy)]
244pub struct DicomDateTime {
245 date: DicomDate,
246 time: Option<DicomTime>,
247 time_zone: Option<FixedOffset>,
248}
249
250pub fn check_component<T>(component: DateComponent, value: &T) -> Result<()>
254where
255 T: Into<u32> + Copy,
256{
257 let range = match component {
258 DateComponent::Year => 0..=9_999,
259 DateComponent::Month => 1..=12,
260 DateComponent::Day => 1..=31,
261 DateComponent::Hour => 0..=23,
262 DateComponent::Minute => 0..=59,
263 DateComponent::Second => 0..=60,
264 DateComponent::Millisecond => 0..=999,
265 DateComponent::Fraction => 0..=999_999,
266 DateComponent::UtcWest => 0..=(12 * 3600),
267 DateComponent::UtcEast => 0..=(14 * 3600),
268 };
269
270 let value: u32 = (*value).into();
271 if range.contains(&value) {
272 Ok(())
273 } else {
274 InvalidComponentSnafu {
275 component,
276 value,
277 range,
278 }
279 .fail()
280 }
281}
282
283impl DicomDate {
284 pub fn from_y(year: u16) -> Result<DicomDate> {
289 check_component(DateComponent::Year, &year)?;
290 Ok(DicomDate(DicomDateImpl::Year(year)))
291 }
292 pub fn from_ym(year: u16, month: u8) -> Result<DicomDate> {
297 check_component(DateComponent::Year, &year)?;
298 check_component(DateComponent::Month, &month)?;
299 Ok(DicomDate(DicomDateImpl::Month(year, month)))
300 }
301 pub fn from_ymd(year: u16, month: u8, day: u8) -> Result<DicomDate> {
306 check_component(DateComponent::Year, &year)?;
307 check_component(DateComponent::Month, &month)?;
308 check_component(DateComponent::Day, &day)?;
309 Ok(DicomDate(DicomDateImpl::Day(year, month, day)))
310 }
311
312 pub fn year(&self) -> &u16 {
314 match self {
315 DicomDate(DicomDateImpl::Year(y)) => y,
316 DicomDate(DicomDateImpl::Month(y, _)) => y,
317 DicomDate(DicomDateImpl::Day(y, _, _)) => y,
318 }
319 }
320 pub fn month(&self) -> Option<&u8> {
322 match self {
323 DicomDate(DicomDateImpl::Year(_)) => None,
324 DicomDate(DicomDateImpl::Month(_, m)) => Some(m),
325 DicomDate(DicomDateImpl::Day(_, m, _)) => Some(m),
326 }
327 }
328 pub fn day(&self) -> Option<&u8> {
330 match self {
331 DicomDate(DicomDateImpl::Year(_)) => None,
332 DicomDate(DicomDateImpl::Month(_, _)) => None,
333 DicomDate(DicomDateImpl::Day(_, _, d)) => Some(d),
334 }
335 }
336
337 pub(crate) fn precision(&self) -> DateComponent {
339 match self {
340 DicomDate(DicomDateImpl::Year(..)) => DateComponent::Year,
341 DicomDate(DicomDateImpl::Month(..)) => DateComponent::Month,
342 DicomDate(DicomDateImpl::Day(..)) => DateComponent::Day,
343 }
344 }
345}
346
347impl std::str::FromStr for DicomDate {
348 type Err = crate::value::DeserializeError;
349
350 fn from_str(s: &str) -> Result<Self, Self::Err> {
351 let (date, _) = crate::value::deserialize::parse_date_partial(s.as_bytes())?;
352 Ok(date)
353 }
354}
355
356impl TryFrom<&NaiveDate> for DicomDate {
357 type Error = Error;
358 fn try_from(date: &NaiveDate) -> Result<Self> {
359 let year: u16 = date.year().try_into().with_context(|_| ConversionSnafu {
360 value: date.year().to_string(),
361 component: DateComponent::Year,
362 })?;
363 let month: u8 = date.month().try_into().with_context(|_| ConversionSnafu {
364 value: date.month().to_string(),
365 component: DateComponent::Month,
366 })?;
367 let day: u8 = date.day().try_into().with_context(|_| ConversionSnafu {
368 value: date.day().to_string(),
369 component: DateComponent::Day,
370 })?;
371 DicomDate::from_ymd(year, month, day)
372 }
373}
374
375impl fmt::Display for DicomDate {
376 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377 match self {
378 DicomDate(DicomDateImpl::Year(y)) => write!(f, "{y:04}"),
379 DicomDate(DicomDateImpl::Month(y, m)) => write!(f, "{y:04}-{m:02}"),
380 DicomDate(DicomDateImpl::Day(y, m, d)) => write!(f, "{y:04}-{m:02}-{d:02}"),
381 }
382 }
383}
384
385impl fmt::Debug for DicomDate {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 match self {
388 DicomDate(DicomDateImpl::Year(y)) => write!(f, "{y:04}-MM-DD"),
389 DicomDate(DicomDateImpl::Month(y, m)) => write!(f, "{y:04}-{m:02}-DD"),
390 DicomDate(DicomDateImpl::Day(y, m, d)) => write!(f, "{y:04}-{m:02}-{d:02}"),
391 }
392 }
393}
394
395impl DicomTime {
396 pub fn from_h(hour: u8) -> Result<DicomTime> {
401 check_component(DateComponent::Hour, &hour)?;
402 Ok(DicomTime(DicomTimeImpl::Hour(hour)))
403 }
404
405 pub fn from_hm(hour: u8, minute: u8) -> Result<DicomTime> {
410 check_component(DateComponent::Hour, &hour)?;
411 check_component(DateComponent::Minute, &minute)?;
412 Ok(DicomTime(DicomTimeImpl::Minute(hour, minute)))
413 }
414
415 pub fn from_hms(hour: u8, minute: u8, second: u8) -> Result<DicomTime> {
420 check_component(DateComponent::Hour, &hour)?;
421 check_component(DateComponent::Minute, &minute)?;
422 check_component(DateComponent::Second, &second)?;
423 Ok(DicomTime(DicomTimeImpl::Second(hour, minute, second)))
424 }
425 pub fn from_hms_milli(hour: u8, minute: u8, second: u8, millisecond: u32) -> Result<DicomTime> {
430 check_component(DateComponent::Millisecond, &millisecond)?;
431 Ok(DicomTime(DicomTimeImpl::Fraction(
432 hour,
433 minute,
434 second,
435 millisecond,
436 3,
437 )))
438 }
439
440 pub fn from_hms_micro(hour: u8, minute: u8, second: u8, microsecond: u32) -> Result<DicomTime> {
446 check_component(DateComponent::Fraction, µsecond)?;
447 Ok(DicomTime(DicomTimeImpl::Fraction(
448 hour,
449 minute,
450 second,
451 microsecond,
452 6,
453 )))
454 }
455
456 pub fn hour(&self) -> &u8 {
458 match self {
459 DicomTime(DicomTimeImpl::Hour(h)) => h,
460 DicomTime(DicomTimeImpl::Minute(h, _)) => h,
461 DicomTime(DicomTimeImpl::Second(h, _, _)) => h,
462 DicomTime(DicomTimeImpl::Fraction(h, _, _, _, _)) => h,
463 }
464 }
465 pub fn minute(&self) -> Option<&u8> {
467 match self {
468 DicomTime(DicomTimeImpl::Hour(_)) => None,
469 DicomTime(DicomTimeImpl::Minute(_, m)) => Some(m),
470 DicomTime(DicomTimeImpl::Second(_, m, _)) => Some(m),
471 DicomTime(DicomTimeImpl::Fraction(_, m, _, _, _)) => Some(m),
472 }
473 }
474 pub fn second(&self) -> Option<&u8> {
476 match self {
477 DicomTime(DicomTimeImpl::Hour(_)) => None,
478 DicomTime(DicomTimeImpl::Minute(_, _)) => None,
479 DicomTime(DicomTimeImpl::Second(_, _, s)) => Some(s),
480 DicomTime(DicomTimeImpl::Fraction(_, _, s, _, _)) => Some(s),
481 }
482 }
483
484 pub fn millisecond(&self) -> Option<u32> {
489 self.fraction_and_precision().and_then(|(f, fp)| match fp {
490 0..=2 => None,
491 3 => Some(f),
492 4 => Some(f / 10),
493 5 => Some(f / 100),
494 6 => Some(f / 1_000),
495 _ => unreachable!("fp outside expected range 0..=6"),
496 })
497 }
498
499 pub fn fraction_micro(&self) -> Option<u32> {
515 match self.fraction_and_precision() {
516 None => None,
517 Some((f, 1)) => Some(f * 100_000),
518 Some((f, 2)) => Some(f * 10_000),
519 Some((f, 3)) => Some(f * 1_000),
520 Some((f, 4)) => Some(f * 100),
521 Some((f, 5)) => Some(f * 10),
522 Some((f, 6)) => Some(f),
523 Some((_, _)) => unreachable!("fp outside expected range 0..=6"),
524 }
525 }
526
527 pub fn fraction_ms(&self) -> Option<u32> {
546 match self.fraction_and_precision() {
547 None => None,
548 Some((f, 1)) => Some(f * 100),
549 Some((f, 2)) => Some(f * 10),
550 Some((f, 3)) => Some(f),
551 Some((f, 4)) => Some(f / 10),
552 Some((f, 5)) => Some(f / 100),
553 Some((f, 6)) => Some(f / 1_000),
554 Some((_, _)) => unreachable!("fp outside expected range 0..=6"),
555 }
556 }
557
558 pub fn fraction_precision(&self) -> u8 {
561 match self.fraction_and_precision() {
562 None => 0,
563 Some((_, fp)) => fp,
564 }
565 }
566
567 pub(crate) fn fraction_and_precision(&self) -> Option<(u32, u8)> {
572 match self {
573 DicomTime(DicomTimeImpl::Hour(_)) => None,
574 DicomTime(DicomTimeImpl::Minute(_, _)) => None,
575 DicomTime(DicomTimeImpl::Second(_, _, _)) => None,
576 DicomTime(DicomTimeImpl::Fraction(_, _, _, f, fp)) => Some((*f, *fp)),
577 }
578 }
579
580 pub fn fraction_str(&self) -> String {
597 match self.fraction_and_precision() {
598 None | Some((_, 0)) => String::new(),
599 Some((f, 1)) => format!("{:01}", f),
600 Some((f, 2)) => format!("{:02}", f),
601 Some((f, 3)) => format!("{:03}", f),
602 Some((f, 4)) => format!("{:04}", f),
603 Some((f, 5)) => format!("{:05}", f),
604 Some((f, 6)) => format!("{:06}", f),
605 Some((_, _)) => unreachable!("fp outside expected range 0..=6"),
606 }
607 }
608
609 pub(crate) fn from_hmsf(
614 hour: u8,
615 minute: u8,
616 second: u8,
617 fraction: u32,
618 frac_precision: u8,
619 ) -> Result<DicomTime> {
620 if !(1..=6).contains(&frac_precision) {
621 return FractionPrecisionRangeSnafu {
622 value: frac_precision,
623 }
624 .fail();
625 }
626 if u32::pow(10, frac_precision as u32) < fraction {
627 return FractionPrecisionMismatchSnafu {
628 fraction,
629 precision: frac_precision,
630 }
631 .fail();
632 }
633
634 check_component(DateComponent::Hour, &hour)?;
635 check_component(DateComponent::Minute, &minute)?;
636 check_component(DateComponent::Second, &second)?;
637 let f: u32 = fraction * u32::pow(10, 6 - frac_precision as u32);
638 check_component(DateComponent::Fraction, &f)?;
639 Ok(DicomTime(DicomTimeImpl::Fraction(
640 hour,
641 minute,
642 second,
643 fraction,
644 frac_precision,
645 )))
646 }
647
648 pub(crate) fn precision(&self) -> DateComponent {
650 match self {
651 DicomTime(DicomTimeImpl::Hour(..)) => DateComponent::Hour,
652 DicomTime(DicomTimeImpl::Minute(..)) => DateComponent::Minute,
653 DicomTime(DicomTimeImpl::Second(..)) => DateComponent::Second,
654 DicomTime(DicomTimeImpl::Fraction(..)) => DateComponent::Fraction,
655 }
656 }
657}
658
659impl TryFrom<&NaiveTime> for DicomTime {
660 type Error = Error;
661 fn try_from(time: &NaiveTime) -> Result<Self> {
662 let hour: u8 = time.hour().try_into().with_context(|_| ConversionSnafu {
663 value: time.hour().to_string(),
664 component: DateComponent::Hour,
665 })?;
666 let minute: u8 = time.minute().try_into().with_context(|_| ConversionSnafu {
667 value: time.minute().to_string(),
668 component: DateComponent::Minute,
669 })?;
670 let second: u8 = time.second().try_into().with_context(|_| ConversionSnafu {
671 value: time.second().to_string(),
672 component: DateComponent::Second,
673 })?;
674 let microsecond = time.nanosecond() / 1000;
675 let (second, microsecond) = if microsecond >= 1_000_000 && second == 59 {
677 (60, microsecond - 1_000_000)
678 } else {
679 (second, microsecond)
680 };
681
682 DicomTime::from_hms_micro(hour, minute, second, microsecond)
683 }
684}
685
686impl std::str::FromStr for DicomTime {
687 type Err = crate::value::DeserializeError;
688
689 fn from_str(s: &str) -> Result<Self, Self::Err> {
690 let (time, _) = crate::value::deserialize::parse_time_partial(s.as_bytes())?;
691 Ok(time)
692 }
693}
694
695impl fmt::Display for DicomTime {
696 fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
697 match self {
698 DicomTime(DicomTimeImpl::Hour(h)) => write!(frm, "{h:02}"),
699 DicomTime(DicomTimeImpl::Minute(h, m)) => write!(frm, "{h:02}:{m:02}"),
700 DicomTime(DicomTimeImpl::Second(h, m, s)) => {
701 write!(frm, "{h:02}:{m:02}:{s:02}")
702 }
703 DicomTime(DicomTimeImpl::Fraction(h, m, s, f, fp)) => {
704 let sfrac = (u32::pow(10, *fp as u32) + f).to_string();
705 write!(
706 frm,
707 "{h:02}:{m:02}:{s:02}.{}",
708 match f {
709 0 => "0",
710 _ => sfrac.get(1..).unwrap(),
711 }
712 )
713 }
714 }
715 }
716}
717
718impl fmt::Debug for DicomTime {
719 fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
720 match self {
721 DicomTime(DicomTimeImpl::Hour(h)) => write!(frm, "{h:02}:mm:ss.FFFFFF"),
722 DicomTime(DicomTimeImpl::Minute(h, m)) => write!(frm, "{h:02}:{m:02}:ss.FFFFFF"),
723 DicomTime(DicomTimeImpl::Second(h, m, s)) => {
724 write!(frm, "{h:02}:{m:02}:{s:02}.FFFFFF")
725 }
726 DicomTime(DicomTimeImpl::Fraction(h, m, s, f, _fp)) => {
727 write!(frm, "{h:02}:{m:02}:{s:02}.{f:F<6}")
728 }
729 }
730 }
731}
732
733impl DicomDateTime {
734 pub fn from_date_with_time_zone(date: DicomDate, time_zone: FixedOffset) -> DicomDateTime {
738 DicomDateTime {
739 date,
740 time: None,
741 time_zone: Some(time_zone),
742 }
743 }
744
745 pub fn from_date(date: DicomDate) -> DicomDateTime {
749 DicomDateTime {
750 date,
751 time: None,
752 time_zone: None,
753 }
754 }
755
756 pub fn from_date_and_time(date: DicomDate, time: DicomTime) -> Result<DicomDateTime> {
761 if date.is_precise() {
762 Ok(DicomDateTime {
763 date,
764 time: Some(time),
765 time_zone: None,
766 })
767 } else {
768 DateTimeFromPartialsSnafu {
769 value: date.precision(),
770 }
771 .fail()
772 }
773 }
774
775 pub fn from_date_and_time_with_time_zone(
780 date: DicomDate,
781 time: DicomTime,
782 time_zone: FixedOffset,
783 ) -> Result<DicomDateTime> {
784 if date.is_precise() {
785 Ok(DicomDateTime {
786 date,
787 time: Some(time),
788 time_zone: Some(time_zone),
789 })
790 } else {
791 DateTimeFromPartialsSnafu {
792 value: date.precision(),
793 }
794 .fail()
795 }
796 }
797
798 pub fn now_local() -> Result<DicomDateTime> {
803 DicomDateTime::try_from(&Local::now().naive_local())
804 }
805
806 pub fn now_utc() -> Result<DicomDateTime> {
811 DicomDateTime::try_from(&Utc::now().naive_utc())
812 }
813
814 pub fn into_parts(self) -> (DicomDate, Option<DicomTime>, Option<FixedOffset>) {
818 (self.date, self.time, self.time_zone)
819 }
820
821 pub fn date(&self) -> &DicomDate {
823 &self.date
824 }
825
826 pub fn time(&self) -> Option<&DicomTime> {
828 self.time.as_ref()
829 }
830
831 pub fn time_zone(&self) -> Option<&FixedOffset> {
833 self.time_zone.as_ref()
834 }
835
836 pub fn has_time_zone(&self) -> bool {
838 self.time_zone.is_some()
839 }
840}
841
842impl TryFrom<&DateTime<FixedOffset>> for DicomDateTime {
843 type Error = Error;
844 fn try_from(dt: &DateTime<FixedOffset>) -> Result<Self> {
845 let year: u16 = dt.year().try_into().with_context(|_| ConversionSnafu {
846 value: dt.year().to_string(),
847 component: DateComponent::Year,
848 })?;
849 let month: u8 = dt.month().try_into().with_context(|_| ConversionSnafu {
850 value: dt.month().to_string(),
851 component: DateComponent::Month,
852 })?;
853 let day: u8 = dt.day().try_into().with_context(|_| ConversionSnafu {
854 value: dt.day().to_string(),
855 component: DateComponent::Day,
856 })?;
857 let hour: u8 = dt.hour().try_into().with_context(|_| ConversionSnafu {
858 value: dt.hour().to_string(),
859 component: DateComponent::Hour,
860 })?;
861 let minute: u8 = dt.minute().try_into().with_context(|_| ConversionSnafu {
862 value: dt.minute().to_string(),
863 component: DateComponent::Minute,
864 })?;
865 let second: u8 = dt.second().try_into().with_context(|_| ConversionSnafu {
866 value: dt.second().to_string(),
867 component: DateComponent::Second,
868 })?;
869 let microsecond = dt.nanosecond() / 1000;
870 let (second, microsecond) = if microsecond >= 1_000_000 && second == 59 {
872 (60, microsecond - 1_000_000)
873 } else {
874 (second, microsecond)
875 };
876
877 DicomDateTime::from_date_and_time_with_time_zone(
878 DicomDate::from_ymd(year, month, day)?,
879 DicomTime::from_hms_micro(hour, minute, second, microsecond)?,
880 *dt.offset(),
881 )
882 }
883}
884
885impl TryFrom<&NaiveDateTime> for DicomDateTime {
886 type Error = Error;
887 fn try_from(dt: &NaiveDateTime) -> Result<Self> {
888 let year: u16 = dt.year().try_into().with_context(|_| ConversionSnafu {
889 value: dt.year().to_string(),
890 component: DateComponent::Year,
891 })?;
892 let month: u8 = dt.month().try_into().with_context(|_| ConversionSnafu {
893 value: dt.month().to_string(),
894 component: DateComponent::Month,
895 })?;
896 let day: u8 = dt.day().try_into().with_context(|_| ConversionSnafu {
897 value: dt.day().to_string(),
898 component: DateComponent::Day,
899 })?;
900 let hour: u8 = dt.hour().try_into().with_context(|_| ConversionSnafu {
901 value: dt.hour().to_string(),
902 component: DateComponent::Hour,
903 })?;
904 let minute: u8 = dt.minute().try_into().with_context(|_| ConversionSnafu {
905 value: dt.minute().to_string(),
906 component: DateComponent::Minute,
907 })?;
908 let second: u8 = dt.second().try_into().with_context(|_| ConversionSnafu {
909 value: dt.second().to_string(),
910 component: DateComponent::Second,
911 })?;
912 let microsecond = dt.nanosecond() / 1000;
913 let (second, microsecond) = if microsecond >= 1_000_000 && second == 59 {
915 (60, microsecond - 1_000_000)
916 } else {
917 (second, microsecond)
918 };
919
920 DicomDateTime::from_date_and_time(
921 DicomDate::from_ymd(year, month, day)?,
922 DicomTime::from_hms_micro(hour, minute, second, microsecond)?,
923 )
924 }
925}
926
927impl fmt::Display for DicomDateTime {
928 fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
929 match self.time {
930 None => match self.time_zone {
931 Some(offset) => write!(frm, "{} {}", self.date, offset),
932 None => write!(frm, "{}", self.date),
933 },
934 Some(time) => match self.time_zone {
935 Some(offset) => write!(frm, "{} {} {}", self.date, time, offset),
936 None => write!(frm, "{} {}", self.date, time),
937 },
938 }
939 }
940}
941
942impl fmt::Debug for DicomDateTime {
943 fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
944 match self.time {
945 None => match self.time_zone {
946 Some(offset) => write!(frm, "{:?} {}", self.date, offset),
947 None => write!(frm, "{:?}", self.date),
948 },
949 Some(time) => match self.time_zone {
950 Some(offset) => write!(frm, "{:?} {:?} {}", self.date, time, offset),
951 None => write!(frm, "{:?} {:?}", self.date, time),
952 },
953 }
954 }
955}
956
957impl std::str::FromStr for DicomDateTime {
958 type Err = crate::value::DeserializeError;
959
960 fn from_str(s: &str) -> Result<Self, Self::Err> {
961 crate::value::deserialize::parse_datetime_partial(s.as_bytes())
962 }
963}
964
965impl DicomDate {
966 pub fn to_encoded(&self) -> String {
970 match self {
971 DicomDate(DicomDateImpl::Year(y)) => format!("{y:04}"),
972 DicomDate(DicomDateImpl::Month(y, m)) => format!("{y:04}{m:02}"),
973 DicomDate(DicomDateImpl::Day(y, m, d)) => format!("{y:04}{m:02}{d:02}"),
974 }
975 }
976}
977
978impl DicomTime {
979 pub fn to_encoded(&self) -> String {
983 match self {
984 DicomTime(DicomTimeImpl::Hour(h)) => format!("{h:02}"),
985 DicomTime(DicomTimeImpl::Minute(h, m)) => format!("{h:02}{m:02}"),
986 DicomTime(DicomTimeImpl::Second(h, m, s)) => format!("{h:02}{m:02}{s:02}"),
987 DicomTime(DicomTimeImpl::Fraction(h, m, s, f, fp)) => {
988 let sfrac = (u32::pow(10, *fp as u32) + f).to_string();
989 format!("{h:02}{m:02}{s:02}.{}", sfrac.get(1..).unwrap())
990 }
991 }
992 }
993}
994
995impl DicomDateTime {
996 pub fn to_encoded(&self) -> String {
1000 match self.time {
1001 Some(time) => match self.time_zone {
1002 Some(offset) => format!(
1003 "{}{}{}",
1004 self.date.to_encoded(),
1005 time.to_encoded(),
1006 offset.to_string().replace(':', "")
1007 ),
1008 None => format!("{}{}", self.date.to_encoded(), time.to_encoded()),
1009 },
1010 None => match self.time_zone {
1011 Some(offset) => format!(
1012 "{}{}",
1013 self.date.to_encoded(),
1014 offset.to_string().replace(':', "")
1015 ),
1016 None => self.date.to_encoded().to_string(),
1017 },
1018 }
1019 }
1020}
1021
1022#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
1029pub enum PreciseDateTime {
1030 Naive(NaiveDateTime),
1032 TimeZone(DateTime<FixedOffset>),
1034}
1035
1036impl PreciseDateTime {
1037 pub fn as_datetime(&self) -> Option<&DateTime<FixedOffset>> {
1040 match self {
1041 PreciseDateTime::Naive(..) => None,
1042 PreciseDateTime::TimeZone(value) => Some(value),
1043 }
1044 }
1045
1046 pub fn as_naive_datetime(&self) -> Option<&NaiveDateTime> {
1049 match self {
1050 PreciseDateTime::Naive(value) => Some(value),
1051 PreciseDateTime::TimeZone(..) => None,
1052 }
1053 }
1054
1055 pub fn into_datetime(self) -> Option<DateTime<FixedOffset>> {
1058 match self {
1059 PreciseDateTime::Naive(..) => None,
1060 PreciseDateTime::TimeZone(value) => Some(value),
1061 }
1062 }
1063
1064 pub fn into_naive_datetime(self) -> Option<NaiveDateTime> {
1067 match self {
1068 PreciseDateTime::Naive(value) => Some(value),
1069 PreciseDateTime::TimeZone(..) => None,
1070 }
1071 }
1072
1073 pub fn to_naive_date(&self) -> NaiveDate {
1083 match self {
1084 PreciseDateTime::Naive(value) => value.date(),
1085 PreciseDateTime::TimeZone(value) => value.date_naive(),
1086 }
1087 }
1088
1089 pub fn to_naive_time(&self) -> NaiveTime {
1091 match self {
1092 PreciseDateTime::Naive(value) => value.time(),
1093 PreciseDateTime::TimeZone(value) => value.time(),
1094 }
1095 }
1096
1097 #[inline]
1099 pub fn has_time_zone(&self) -> bool {
1100 matches!(self, PreciseDateTime::TimeZone(..))
1101 }
1102}
1103
1104impl PartialOrd for PreciseDateTime {
1111 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1112 match (self, other) {
1113 (PreciseDateTime::Naive(a), PreciseDateTime::Naive(b)) => a.partial_cmp(b),
1114 (PreciseDateTime::TimeZone(a), PreciseDateTime::TimeZone(b)) => a.partial_cmp(b),
1115 _ => None,
1116 }
1117 }
1118}
1119
1120#[cfg(test)]
1121mod tests {
1122 use super::*;
1123 use chrono::{Duration, TimeZone};
1124
1125 #[test]
1126 fn test_dicom_date() {
1127 assert_eq!(
1128 DicomDate::from_ymd(1944, 2, 29).unwrap(),
1129 DicomDate(DicomDateImpl::Day(1944, 2, 29))
1130 );
1131
1132 assert!(DicomDate::from_ymd(1945, 2, 29).unwrap().is_precise());
1134 assert_eq!(
1135 DicomDate::from_ym(1944, 2).unwrap(),
1136 DicomDate(DicomDateImpl::Month(1944, 2))
1137 );
1138 assert_eq!(
1139 DicomDate::from_y(1944).unwrap(),
1140 DicomDate(DicomDateImpl::Year(1944))
1141 );
1142
1143 assert!(DicomDate::from_ymd(1944, 2, 29).unwrap().is_precise());
1144 assert!(!DicomDate::from_ym(1944, 2).unwrap().is_precise());
1145 assert!(!DicomDate::from_y(1944).unwrap().is_precise());
1146 assert_eq!(
1147 DicomDate::from_ymd(1944, 2, 29)
1148 .unwrap()
1149 .earliest()
1150 .unwrap(),
1151 NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
1152 );
1153 assert_eq!(
1154 DicomDate::from_ymd(1944, 2, 29).unwrap().latest().unwrap(),
1155 NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
1156 );
1157
1158 assert_eq!(
1159 DicomDate::from_y(1944).unwrap().earliest().unwrap(),
1160 NaiveDate::from_ymd_opt(1944, 1, 1).unwrap()
1161 );
1162 assert_eq!(
1164 DicomDate::from_ym(1944, 2).unwrap().latest().unwrap(),
1165 NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
1166 );
1167 assert_eq!(
1168 DicomDate::from_ym(1945, 2).unwrap().latest().unwrap(),
1169 NaiveDate::from_ymd_opt(1945, 2, 28).unwrap()
1170 );
1171
1172 assert_eq!(
1173 DicomDate::try_from(&NaiveDate::from_ymd_opt(1945, 2, 28).unwrap()).unwrap(),
1174 DicomDate(DicomDateImpl::Day(1945, 2, 28))
1175 );
1176
1177 let date: DicomDate = "20240229".parse().unwrap();
1180 assert_eq!(date, DicomDate(DicomDateImpl::Day(2024, 2, 29)));
1181 assert!(date.is_precise());
1182
1183 assert!(matches!(
1186 DicomDate::try_from(&NaiveDate::from_ymd_opt(-2000, 2, 28).unwrap()),
1187 Err(Error::Conversion { .. })
1188 ));
1189
1190 assert!(matches!(
1191 DicomDate::try_from(&NaiveDate::from_ymd_opt(10_000, 2, 28).unwrap()),
1192 Err(Error::InvalidComponent {
1193 component: DateComponent::Year,
1194 ..
1195 })
1196 ));
1197 }
1198
1199 #[test]
1200 fn test_dicom_time() {
1201 assert_eq!(
1202 DicomTime::from_hms_micro(9, 1, 1, 123456).unwrap(),
1203 DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 123456, 6))
1204 );
1205 assert_eq!(
1206 DicomTime::from_hms_micro(9, 1, 1, 1).unwrap(),
1207 DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 1, 6))
1208 );
1209 assert_eq!(
1210 DicomTime::from_hms(9, 0, 0).unwrap(),
1211 DicomTime(DicomTimeImpl::Second(9, 0, 0))
1212 );
1213 assert_eq!(
1214 DicomTime::from_hm(23, 59).unwrap(),
1215 DicomTime(DicomTimeImpl::Minute(23, 59))
1216 );
1217 assert_eq!(
1218 DicomTime::from_h(1).unwrap(),
1219 DicomTime(DicomTimeImpl::Hour(1))
1220 );
1221 assert!(DicomTime::from_hms_micro(9, 1, 1, 123456)
1223 .unwrap()
1224 .is_precise());
1225 assert!(!DicomTime::from_hms_milli(9, 1, 1, 123)
1226 .unwrap()
1227 .is_precise());
1228
1229 assert_eq!(
1230 DicomTime::from_hms_milli(9, 1, 1, 123)
1231 .unwrap()
1232 .earliest()
1233 .unwrap(),
1234 NaiveTime::from_hms_micro_opt(9, 1, 1, 123_000).unwrap()
1235 );
1236 assert_eq!(
1237 DicomTime::from_hms_milli(9, 1, 1, 123)
1238 .unwrap()
1239 .latest()
1240 .unwrap(),
1241 NaiveTime::from_hms_micro_opt(9, 1, 1, 123_999).unwrap()
1242 );
1243
1244 assert_eq!(
1245 DicomTime::from_hms_milli(9, 1, 1, 2)
1246 .unwrap()
1247 .earliest()
1248 .unwrap(),
1249 NaiveTime::from_hms_micro_opt(9, 1, 1, 2000).unwrap()
1250 );
1251 assert_eq!(
1252 DicomTime::from_hms_milli(9, 1, 1, 2)
1253 .unwrap()
1254 .latest()
1255 .unwrap(),
1256 NaiveTime::from_hms_micro_opt(9, 1, 1, 2999).unwrap()
1257 );
1258
1259 assert!(DicomTime::from_hms_micro(9, 1, 1, 123456)
1260 .unwrap()
1261 .is_precise());
1262
1263 assert_eq!(
1264 DicomTime::from_hms_milli(9, 1, 1, 1).unwrap(),
1265 DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 1, 3))
1266 );
1267
1268 assert_eq!(
1269 DicomTime::try_from(&NaiveTime::from_hms_milli_opt(16, 31, 28, 123).unwrap()).unwrap(),
1270 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 123_000, 6))
1271 );
1272
1273 assert_eq!(
1274 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 123).unwrap()).unwrap(),
1275 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 123, 6))
1276 );
1277
1278 assert_eq!(
1279 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 1234).unwrap()).unwrap(),
1280 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 1234, 6))
1281 );
1282
1283 assert_eq!(
1284 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 0).unwrap()).unwrap(),
1285 DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 0, 6))
1286 );
1287
1288 assert_eq!(
1289 DicomTime::from_hmsf(9, 1, 1, 1, 4).unwrap().to_string(),
1290 "09:01:01.0001"
1291 );
1292 assert_eq!(
1293 DicomTime::from_hmsf(9, 1, 1, 0, 1).unwrap().to_string(),
1294 "09:01:01.0"
1295 );
1296 assert_eq!(
1297 DicomTime::from_hmsf(7, 55, 1, 1, 5).unwrap().to_encoded(),
1298 "075501.00001"
1299 );
1300 assert_eq!(
1302 DicomTime::from_hmsf(9, 1, 1, 0, 2).unwrap().to_encoded(),
1303 "090101.00"
1304 );
1305 assert_eq!(
1306 DicomTime::from_hmsf(9, 1, 1, 0, 3).unwrap().to_encoded(),
1307 "090101.000"
1308 );
1309 assert_eq!(
1310 DicomTime::from_hmsf(9, 1, 1, 0, 4).unwrap().to_encoded(),
1311 "090101.0000"
1312 );
1313 assert_eq!(
1314 DicomTime::from_hmsf(9, 1, 1, 0, 5).unwrap().to_encoded(),
1315 "090101.00000"
1316 );
1317 assert_eq!(
1318 DicomTime::from_hmsf(9, 1, 1, 0, 6).unwrap().to_encoded(),
1319 "090101.000000"
1320 );
1321
1322 assert_eq!(
1324 DicomTime::from_hmsf(23, 59, 60, 123, 3)
1325 .unwrap()
1326 .to_encoded(),
1327 "235960.123",
1328 );
1329
1330 assert_eq!(
1332 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 59, 1_000_000).unwrap())
1333 .unwrap()
1334 .to_encoded(),
1335 "163160.000000",
1336 );
1337
1338 {
1340 let time =
1341 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 59, 1_012_345).unwrap())
1342 .unwrap();
1343 assert_eq!(time.to_encoded(), "163160.012345");
1344
1345 assert_eq!(time.fraction_micro(), Some(12_345));
1346 assert_eq!(time.fraction_ms(), Some(12));
1347 assert_eq!(time.millisecond(), Some(12));
1348 }
1349
1350 assert_eq!(
1352 DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 59, 0).unwrap())
1353 .unwrap()
1354 .to_encoded(),
1355 "163159.000000",
1356 );
1357
1358 let date_time: DateTime<_> = DateTime::<Utc>::from_naive_utc_and_offset(
1360 NaiveDateTime::new(
1361 NaiveDate::from_ymd_opt(2024, 8, 9).unwrap(),
1362 NaiveTime::from_hms_opt(9, 9, 39).unwrap(),
1363 ),
1364 Utc,
1365 )
1366 .with_timezone(&FixedOffset::east_opt(0).unwrap());
1367 let dicom_date_time = DicomDateTime::try_from(&date_time).unwrap();
1368 assert!(dicom_date_time.has_time_zone());
1369 assert!(dicom_date_time.is_precise());
1370 let dicom_time = dicom_date_time.time().unwrap();
1371 assert_eq!(dicom_time.fraction_and_precision(), Some((0, 6)),);
1372 assert_eq!(dicom_date_time.to_encoded(), "20240809090939.000000+0000");
1373
1374 assert_eq!(
1375 dicom_date_time.time().map(|t| t.millisecond()),
1376 Some(Some(0)),
1377 );
1378
1379 let time: DicomTime = "211133.7651".parse().unwrap();
1382 assert_eq!(
1383 time,
1384 DicomTime(DicomTimeImpl::Fraction(21, 11, 33, 7651, 4))
1385 );
1386 assert_eq!(time.fraction_ms(), Some(765));
1387 assert_eq!(time.fraction_micro(), Some(765_100));
1388 assert_eq!(time.fraction_precision(), 4);
1389 assert_eq!(&time.fraction_str(), "7651");
1390
1391 assert!(matches!(
1394 DicomTime::from_hmsf(9, 1, 1, 1, 7),
1395 Err(Error::FractionPrecisionRange { value: 7, .. })
1396 ));
1397
1398 assert!(matches!(
1399 DicomTime::from_hms_milli(9, 1, 1, 1000),
1400 Err(Error::InvalidComponent {
1401 component: DateComponent::Millisecond,
1402 ..
1403 })
1404 ));
1405
1406 assert!(matches!(
1407 DicomTime::from_hmsf(9, 1, 1, 123456, 3),
1408 Err(Error::FractionPrecisionMismatch {
1409 fraction: 123456,
1410 precision: 3,
1411 ..
1412 })
1413 ));
1414
1415 assert!(matches!(
1417 DicomTime::from_hmsf(9, 1, 1, 1_000_000, 6),
1418 Err(Error::InvalidComponent {
1419 component: DateComponent::Fraction,
1420 ..
1421 })
1422 ));
1423
1424 assert!(matches!(
1425 DicomTime::from_hmsf(9, 1, 1, 12345, 5).unwrap().exact(),
1426 Err(crate::value::range::Error::ImpreciseValue { .. })
1427 ));
1428
1429 for (frac, frac_precision, microseconds) in [
1431 (1, 1, 100_000),
1432 (12, 2, 120_000),
1433 (123, 3, 123_000),
1434 (1234, 4, 123_400),
1435 (12345, 5, 123_450),
1436 (123456, 6, 123_456),
1437 ] {
1438 let time = DicomTime::from_hmsf(9, 1, 1, frac, frac_precision).unwrap();
1439 assert_eq!(time.fraction_micro(), Some(microseconds));
1440 assert_eq!(time.fraction_precision(), frac_precision);
1441 assert_eq!(time.fraction_str(), frac.to_string());
1442 }
1443 assert_eq!(DicomTime::from_hms(9, 1, 1).unwrap().fraction_micro(), None);
1445 }
1446
1447 #[test]
1448 fn test_dicom_date_time_now_local() {
1449 let dicom_datetime_local = DicomDateTime::now_local()
1450 .expect("Failed to get current local datetime from DicomDateTime::now_local()");
1451 let dicom_datetime_local_time = dicom_datetime_local
1452 .time()
1453 .expect("Failed to get time from DicomDateTime");
1454 let dicom_datetime_local_date = dicom_datetime_local.date();
1455
1456 let system_time_local = Local::now().naive_local().time();
1457 let dicom_naive_time_local = dicom_datetime_local_time
1458 .to_naive_time()
1459 .expect("Failed to convert DicomDateTime time component to NaiveTime");
1460 let time_difference_local = system_time_local - dicom_naive_time_local;
1461 assert!(
1462 time_difference_local.abs() < Duration::seconds(1),
1463 "Time component difference between system and DicomDateTime local exceeds 1 second: {:?}",
1464 time_difference_local
1465 );
1466
1467 let system_date_local = Local::now().naive_local().date();
1468 let dicom_naive_date_local = dicom_datetime_local_date
1469 .to_naive_date()
1470 .expect("Failed to convert DicomDateTime date component to NaiveDate");
1471 assert_eq!(
1472 dicom_naive_date_local, system_date_local,
1473 "Date component mismatch between DicomDateTime and system"
1474 );
1475 }
1476
1477 #[test]
1478 fn test_dicom_date_time_now_utc() {
1479 let dicom_datetime_utc = DicomDateTime::now_utc()
1480 .expect("Failed to get current UTC datetime from DicomDateTime::now_utc()");
1481 let dicom_datetime_utc_time = dicom_datetime_utc
1482 .time()
1483 .expect("Failed to get time from DicomDateTime (UTC)");
1484 let dicom_datetime_utc_date = dicom_datetime_utc.date();
1485
1486 let system_time_utc = Utc::now().naive_utc().time();
1487 let dicom_naive_time_utc = dicom_datetime_utc_time
1488 .to_naive_time()
1489 .expect("Failed to convert DicomDateTime UTC time component to NaiveTime");
1490 let time_difference_utc = system_time_utc - dicom_naive_time_utc;
1491 assert!(
1492 time_difference_utc.abs() < Duration::seconds(1),
1493 "Time component difference between system and DicomDateTime UTC exceeds 1 second: {:?}",
1494 time_difference_utc
1495 );
1496
1497 let system_date_utc = Utc::now().naive_utc().date();
1498 let dicom_naive_date_utc = dicom_datetime_utc_date
1499 .to_naive_date()
1500 .expect("Failed to convert DicomDateTime UTC date component to NaiveDate");
1501 assert_eq!(
1502 dicom_naive_date_utc, system_date_utc,
1503 "Date component mismatch between DicomDateTime UTC and system"
1504 );
1505 }
1506
1507 #[test]
1508 fn test_dicom_datetime() {
1509 let default_offset = FixedOffset::east_opt(0).unwrap();
1510 assert_eq!(
1511 DicomDateTime::from_date_with_time_zone(
1512 DicomDate::from_ymd(2020, 2, 29).unwrap(),
1513 default_offset
1514 ),
1515 DicomDateTime {
1516 date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
1517 time: None,
1518 time_zone: Some(default_offset)
1519 }
1520 );
1521
1522 assert_eq!(
1523 DicomDateTime::from_date(DicomDate::from_ym(2020, 2).unwrap())
1524 .earliest()
1525 .unwrap(),
1526 PreciseDateTime::Naive(NaiveDateTime::new(
1527 NaiveDate::from_ymd_opt(2020, 2, 1).unwrap(),
1528 NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
1529 ))
1530 );
1531
1532 assert_eq!(
1533 DicomDateTime::from_date_with_time_zone(
1534 DicomDate::from_ym(2020, 2).unwrap(),
1535 default_offset
1536 )
1537 .latest()
1538 .unwrap(),
1539 PreciseDateTime::TimeZone(
1540 FixedOffset::east_opt(0)
1541 .unwrap()
1542 .from_local_datetime(&NaiveDateTime::new(
1543 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1544 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1545 ))
1546 .unwrap()
1547 )
1548 );
1549
1550 assert_eq!(
1551 DicomDateTime::from_date_and_time_with_time_zone(
1552 DicomDate::from_ymd(2020, 2, 29).unwrap(),
1553 DicomTime::from_hmsf(23, 59, 59, 10, 2).unwrap(),
1554 default_offset
1555 )
1556 .unwrap()
1557 .earliest()
1558 .unwrap(),
1559 PreciseDateTime::TimeZone(
1560 FixedOffset::east_opt(0)
1561 .unwrap()
1562 .from_local_datetime(&NaiveDateTime::new(
1563 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1564 NaiveTime::from_hms_micro_opt(23, 59, 59, 100_000).unwrap()
1565 ))
1566 .unwrap()
1567 )
1568 );
1569 assert_eq!(
1570 DicomDateTime::from_date_and_time_with_time_zone(
1571 DicomDate::from_ymd(2020, 2, 29).unwrap(),
1572 DicomTime::from_hmsf(23, 59, 59, 10, 2).unwrap(),
1573 default_offset
1574 )
1575 .unwrap()
1576 .latest()
1577 .unwrap(),
1578 PreciseDateTime::TimeZone(
1579 FixedOffset::east_opt(0)
1580 .unwrap()
1581 .from_local_datetime(&NaiveDateTime::new(
1582 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1583 NaiveTime::from_hms_micro_opt(23, 59, 59, 109_999).unwrap()
1584 ))
1585 .unwrap()
1586 )
1587 );
1588
1589 assert_eq!(
1590 DicomDateTime::try_from(
1591 &FixedOffset::east_opt(0)
1592 .unwrap()
1593 .from_local_datetime(&NaiveDateTime::new(
1594 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1595 NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1596 ))
1597 .unwrap()
1598 )
1599 .unwrap(),
1600 DicomDateTime {
1601 date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
1602 time: Some(DicomTime::from_hms_micro(23, 59, 59, 999_999).unwrap()),
1603 time_zone: Some(default_offset)
1604 }
1605 );
1606
1607 assert_eq!(
1608 DicomDateTime::try_from(
1609 &FixedOffset::east_opt(0)
1610 .unwrap()
1611 .from_local_datetime(&NaiveDateTime::new(
1612 NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
1613 NaiveTime::from_hms_micro_opt(23, 59, 59, 0).unwrap()
1614 ))
1615 .unwrap()
1616 )
1617 .unwrap(),
1618 DicomDateTime {
1619 date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
1620 time: Some(DicomTime::from_hms_micro(23, 59, 59, 0).unwrap()),
1621 time_zone: Some(default_offset)
1622 }
1623 );
1624
1625 assert_eq!(
1627 DicomDateTime::try_from(
1628 &FixedOffset::east_opt(0)
1629 .unwrap()
1630 .from_local_datetime(&NaiveDateTime::new(
1631 NaiveDate::from_ymd_opt(2023, 12, 31).unwrap(),
1632 NaiveTime::from_hms_micro_opt(23, 59, 59, 1_000_000).unwrap()
1633 ))
1634 .unwrap()
1635 )
1636 .unwrap(),
1637 DicomDateTime {
1638 date: DicomDate::from_ymd(2023, 12, 31).unwrap(),
1639 time: Some(DicomTime::from_hms_micro(23, 59, 60, 0).unwrap()),
1640 time_zone: Some(default_offset)
1641 }
1642 );
1643
1644 let dt: DicomDateTime = "20240229235959.123456+0000".parse().unwrap();
1647 assert_eq!(
1648 dt,
1649 DicomDateTime {
1650 date: DicomDate::from_ymd(2024, 2, 29).unwrap(),
1651 time: Some(DicomTime::from_hms_micro(23, 59, 59, 123_456).unwrap()),
1652 time_zone: Some(default_offset)
1653 }
1654 );
1655 assert!(dt.is_precise());
1656
1657 assert!(matches!(
1660 DicomDateTime::from_date_with_time_zone(
1661 DicomDate::from_ymd(2021, 2, 29).unwrap(),
1662 default_offset
1663 )
1664 .earliest(),
1665 Err(crate::value::range::Error::InvalidDate { .. })
1666 ));
1667
1668 assert!(matches!(
1669 DicomDateTime::from_date_and_time_with_time_zone(
1670 DicomDate::from_ym(2020, 2).unwrap(),
1671 DicomTime::from_hms_milli(23, 59, 59, 999).unwrap(),
1672 default_offset
1673 ),
1674 Err(Error::DateTimeFromPartials {
1675 value: DateComponent::Month,
1676 ..
1677 })
1678 ));
1679 assert!(matches!(
1680 DicomDateTime::from_date_and_time_with_time_zone(
1681 DicomDate::from_y(1).unwrap(),
1682 DicomTime::from_hms_micro(23, 59, 59, 10).unwrap(),
1683 default_offset
1684 ),
1685 Err(Error::DateTimeFromPartials {
1686 value: DateComponent::Year,
1687 ..
1688 })
1689 ));
1690
1691 assert!(matches!(
1692 DicomDateTime::from_date_and_time_with_time_zone(
1693 DicomDate::from_ymd(2000, 1, 1).unwrap(),
1694 DicomTime::from_hms_milli(23, 59, 59, 10).unwrap(),
1695 default_offset
1696 )
1697 .unwrap()
1698 .exact(),
1699 Err(crate::value::range::Error::ImpreciseValue { .. })
1700 ));
1701
1702 assert!(!DicomDateTime::from_date_and_time(
1704 DicomDate::from_ymd(2000, 1, 1).unwrap(),
1705 DicomTime::from_hms_milli(23, 59, 59, 10).unwrap()
1706 )
1707 .unwrap()
1708 .is_precise());
1709
1710 assert!(DicomDateTime::from_date_and_time(
1711 DicomDate::from_ymd(2000, 1, 1).unwrap(),
1712 DicomTime::from_hms_micro(23, 59, 59, 654_321).unwrap()
1713 )
1714 .unwrap()
1715 .is_precise());
1716 }
1717}