use crate::value::range::AsRange;
use chrono::{DateTime, Datelike, FixedOffset, NaiveDate, NaiveTime, Timelike};
use snafu::{Backtrace, ResultExt, Snafu};
use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::ops::RangeInclusive;
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum Error {
#[snafu(display("To combine a DicomDate with a DicomTime value, the DicomDate has to be precise. Precision is: '{:?}'", value))]
DateTimeFromPartials {
value: DateComponent,
backtrace: Backtrace,
},
#[snafu(display(
"'{:?}' has invalid value: '{}', must be in {:?}",
component,
value,
range
))]
InvalidComponent {
component: DateComponent,
value: u32,
range: RangeInclusive<u32>,
backtrace: Backtrace,
},
#[snafu(display(
"Second fraction precision '{}' is out of range, must be in 0..=6",
value
))]
FractionPrecisionRange { value: u32, backtrace: Backtrace },
#[snafu(display(
"Number of digits in decimal representation of fraction '{}' does not match it's precision '{}'",
fraction,
precision
))]
FractionPrecisionMismatch {
fraction: u32,
precision: u32,
backtrace: Backtrace,
},
#[snafu(display("Conversion of value '{}' into {:?} failed", value, component))]
Conversion {
value: String,
component: DateComponent,
source: std::num::TryFromIntError,
},
#[snafu(display(
"Cannot convert from an imprecise value. This value represents a date / time range"
))]
ImpreciseValue { backtrace: Backtrace },
}
type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, PartialEq, Copy, Clone, Eq, Hash, PartialOrd, Ord)]
pub enum DateComponent {
Year,
Month,
Day,
Hour,
Minute,
Second,
Millisecond,
Fraction,
UtcWest,
UtcEast,
}
#[derive(Clone, Copy, PartialEq)]
pub struct DicomDate(DicomDateImpl);
#[derive(Clone, Copy, PartialEq)]
pub struct DicomTime(DicomTimeImpl);
#[derive(Debug, Clone, Copy, PartialEq)]
enum DicomDateImpl {
Year(u16),
Month(u16, u8),
Day(u16, u8, u8),
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum DicomTimeImpl {
Hour(u8),
Minute(u8, u8),
Second(u8, u8, u8),
Fraction(u8, u8, u8, u32, u8),
}
#[derive(PartialEq, Clone, Copy)]
pub struct DicomDateTime {
date: DicomDate,
time: Option<DicomTime>,
offset: FixedOffset,
}
pub fn check_component<T>(component: DateComponent, value: &T) -> Result<()>
where
T: Into<u32> + Copy,
{
let range = match component {
DateComponent::Year => 0..=9_999,
DateComponent::Month => 1..=12,
DateComponent::Day => 1..=31,
DateComponent::Hour => 0..=23,
DateComponent::Minute => 0..=59,
DateComponent::Second => 0..=60,
DateComponent::Millisecond => 0..=999,
DateComponent::Fraction => 0..=999_999,
DateComponent::UtcWest => 0..=(12 * 3600),
DateComponent::UtcEast => 0..=(14 * 3600),
};
let value: u32 = (*value).into();
if range.contains(&value) {
Ok(())
} else {
InvalidComponentSnafu {
component,
value,
range,
}
.fail()
}
}
impl DicomDate {
pub fn from_y(year: u16) -> Result<DicomDate> {
check_component(DateComponent::Year, &year)?;
Ok(DicomDate(DicomDateImpl::Year(year)))
}
pub fn from_ym(year: u16, month: u8) -> Result<DicomDate> {
check_component(DateComponent::Year, &year)?;
check_component(DateComponent::Month, &month)?;
Ok(DicomDate(DicomDateImpl::Month(year, month)))
}
pub fn from_ymd(year: u16, month: u8, day: u8) -> Result<DicomDate> {
check_component(DateComponent::Year, &year)?;
check_component(DateComponent::Month, &month)?;
check_component(DateComponent::Day, &day)?;
Ok(DicomDate(DicomDateImpl::Day(year, month, day)))
}
pub fn year(&self) -> &u16 {
match self {
DicomDate(DicomDateImpl::Year(y)) => y,
DicomDate(DicomDateImpl::Month(y, _)) => y,
DicomDate(DicomDateImpl::Day(y, _, _)) => y,
}
}
pub fn month(&self) -> Option<&u8> {
match self {
DicomDate(DicomDateImpl::Year(_)) => None,
DicomDate(DicomDateImpl::Month(_, m)) => Some(m),
DicomDate(DicomDateImpl::Day(_, m, _)) => Some(m),
}
}
pub fn day(&self) -> Option<&u8> {
match self {
DicomDate(DicomDateImpl::Year(_)) => None,
DicomDate(DicomDateImpl::Month(_, _)) => None,
DicomDate(DicomDateImpl::Day(_, _, d)) => Some(d),
}
}
}
impl TryFrom<&NaiveDate> for DicomDate {
type Error = Error;
fn try_from(date: &NaiveDate) -> Result<Self> {
let year: u16 = date.year().try_into().context(ConversionSnafu {
value: date.year().to_string(),
component: DateComponent::Year,
})?;
let month: u8 = date.month().try_into().context(ConversionSnafu {
value: date.month().to_string(),
component: DateComponent::Month,
})?;
let day: u8 = date.day().try_into().context(ConversionSnafu {
value: date.day().to_string(),
component: DateComponent::Day,
})?;
DicomDate::from_ymd(year, month, day)
}
}
impl fmt::Display for DicomDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DicomDate(DicomDateImpl::Year(y)) => write!(f, "{:04}", y),
DicomDate(DicomDateImpl::Month(y, m)) => write!(f, "{:04}-{:02}", y, m),
DicomDate(DicomDateImpl::Day(y, m, d)) => write!(f, "{:04}-{:02}-{:02}", y, m, d),
}
}
}
impl fmt::Debug for DicomDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DicomDate(DicomDateImpl::Year(y)) => write!(f, "{:04}-MM-DD", y),
DicomDate(DicomDateImpl::Month(y, m)) => write!(f, "{:04}-{:02}-DD", y, m),
DicomDate(DicomDateImpl::Day(y, m, d)) => write!(f, "{:04}-{:02}-{:02}", y, m, d),
}
}
}
impl DicomTime {
pub fn from_h(hour: u8) -> Result<DicomTime> {
check_component(DateComponent::Hour, &hour)?;
Ok(DicomTime(DicomTimeImpl::Hour(hour)))
}
pub fn from_hm(hour: u8, minute: u8) -> Result<DicomTime> {
check_component(DateComponent::Hour, &hour)?;
check_component(DateComponent::Minute, &minute)?;
Ok(DicomTime(DicomTimeImpl::Minute(hour, minute)))
}
pub fn from_hms(hour: u8, minute: u8, second: u8) -> Result<DicomTime> {
check_component(DateComponent::Hour, &hour)?;
check_component(DateComponent::Minute, &minute)?;
check_component(DateComponent::Second, &second)?;
Ok(DicomTime(DicomTimeImpl::Second(hour, minute, second)))
}
pub fn from_hms_milli(hour: u8, minute: u8, second: u8, millisecond: u32) -> Result<DicomTime> {
check_component(DateComponent::Millisecond, &millisecond)?;
Ok(DicomTime(DicomTimeImpl::Fraction(
hour,
minute,
second,
millisecond,
3,
)))
}
pub fn from_hms_micro(hour: u8, minute: u8, second: u8, microsecond: u32) -> Result<DicomTime> {
check_component(DateComponent::Fraction, µsecond)?;
Ok(DicomTime(DicomTimeImpl::Fraction(
hour,
minute,
second,
microsecond,
6,
)))
}
pub fn hour(&self) -> &u8 {
match self {
DicomTime(DicomTimeImpl::Hour(h)) => h,
DicomTime(DicomTimeImpl::Minute(h, _)) => h,
DicomTime(DicomTimeImpl::Second(h, _, _)) => h,
DicomTime(DicomTimeImpl::Fraction(h, _, _, _, _)) => h,
}
}
pub fn minute(&self) -> Option<&u8> {
match self {
DicomTime(DicomTimeImpl::Hour(_)) => None,
DicomTime(DicomTimeImpl::Minute(_, m)) => Some(m),
DicomTime(DicomTimeImpl::Second(_, m, _)) => Some(m),
DicomTime(DicomTimeImpl::Fraction(_, m, _, _, _)) => Some(m),
}
}
pub fn second(&self) -> Option<&u8> {
match self {
DicomTime(DicomTimeImpl::Hour(_)) => None,
DicomTime(DicomTimeImpl::Minute(_, _)) => None,
DicomTime(DicomTimeImpl::Second(_, _, s)) => Some(s),
DicomTime(DicomTimeImpl::Fraction(_, _, s, _, _)) => Some(s),
}
}
pub fn fraction(&self) -> Option<&u32> {
match self {
DicomTime(DicomTimeImpl::Hour(_)) => None,
DicomTime(DicomTimeImpl::Minute(_, _)) => None,
DicomTime(DicomTimeImpl::Second(_, _, _)) => None,
DicomTime(DicomTimeImpl::Fraction(_, _, _, f, fp)) => match fp {
6 => Some(f),
_ => None,
},
}
}
pub(crate) fn fraction_and_precision(&self) -> Option<(&u32, &u8)> {
match self {
DicomTime(DicomTimeImpl::Hour(_)) => None,
DicomTime(DicomTimeImpl::Minute(_, _)) => None,
DicomTime(DicomTimeImpl::Second(_, _, _)) => None,
DicomTime(DicomTimeImpl::Fraction(_, _, _, f, fp)) => Some((f, fp)),
}
}
pub(crate) fn from_hmsf(
hour: u8,
minute: u8,
second: u8,
fraction: u32,
frac_precision: u8,
) -> Result<DicomTime> {
if !(1..=6).contains(&frac_precision) {
return FractionPrecisionRangeSnafu {
value: frac_precision,
}
.fail();
}
if u32::pow(10, frac_precision as u32) < fraction {
return FractionPrecisionMismatchSnafu {
fraction,
precision: frac_precision,
}
.fail();
}
check_component(DateComponent::Hour, &hour)?;
check_component(DateComponent::Minute, &minute)?;
check_component(DateComponent::Second, &second)?;
let f: u32 = fraction * u32::pow(10, 6 - frac_precision as u32);
check_component(DateComponent::Fraction, &f)?;
Ok(DicomTime(DicomTimeImpl::Fraction(
hour,
minute,
second,
fraction,
frac_precision,
)))
}
}
impl TryFrom<&NaiveTime> for DicomTime {
type Error = Error;
fn try_from(time: &NaiveTime) -> Result<Self> {
let hour: u8 = time.hour().try_into().context(ConversionSnafu {
value: time.hour().to_string(),
component: DateComponent::Hour,
})?;
let minute: u8 = time.minute().try_into().context(ConversionSnafu {
value: time.minute().to_string(),
component: DateComponent::Minute,
})?;
let second: u8 = time.second().try_into().context(ConversionSnafu {
value: time.second().to_string(),
component: DateComponent::Second,
})?;
DicomTime::from_hms_micro(hour, minute, second, time.nanosecond() / 1000)
}
}
impl fmt::Display for DicomTime {
fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DicomTime(DicomTimeImpl::Hour(h)) => write!(frm, "{:02}", h),
DicomTime(DicomTimeImpl::Minute(h, m)) => write!(frm, "{:02}:{:02}", h, m),
DicomTime(DicomTimeImpl::Second(h, m, s)) => {
write!(frm, "{:02}:{:02}:{:02}", h, m, s)
}
DicomTime(DicomTimeImpl::Fraction(h, m, s, f, fp)) => {
let sfrac = (u32::pow(10, *fp as u32) + f).to_string();
write!(
frm,
"{:02}:{:02}:{:02}.{}",
h,
m,
s,
match f {
0 => "0",
_ => sfrac.get(1..).unwrap(),
}
)
}
}
}
}
impl fmt::Debug for DicomTime {
fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DicomTime(DicomTimeImpl::Hour(h)) => write!(frm, "{:02}:mm:ss.FFFFFF", h),
DicomTime(DicomTimeImpl::Minute(h, m)) => write!(frm, "{:02}:{:02}:ss.FFFFFF", h, m),
DicomTime(DicomTimeImpl::Second(h, m, s)) => {
write!(frm, "{:02}:{:02}:{:02}.FFFFFF", h, m, s)
}
DicomTime(DicomTimeImpl::Fraction(h, m, s, f, _fp)) => {
write!(frm, "{:02}:{:02}:{:02}.{:F<6}", h, m, s, f)
}
}
}
}
impl DicomDateTime {
pub fn from_date(date: DicomDate, offset: FixedOffset) -> DicomDateTime {
DicomDateTime {
date,
time: None,
offset,
}
}
pub fn from_date_and_time(
date: DicomDate,
time: DicomTime,
offset: FixedOffset,
) -> Result<DicomDateTime> {
if date.is_precise() {
Ok(DicomDateTime {
date,
time: Some(time),
offset,
})
} else {
DateTimeFromPartialsSnafu {
value: date.precision(),
}
.fail()
}
}
pub fn date(&self) -> &DicomDate {
&self.date
}
pub fn time(&self) -> Option<&DicomTime> {
self.time.as_ref()
}
pub fn offset(&self) -> &FixedOffset {
&self.offset
}
}
impl TryFrom<&DateTime<FixedOffset>> for DicomDateTime {
type Error = Error;
fn try_from(dt: &DateTime<FixedOffset>) -> Result<Self> {
let year: u16 = dt.year().try_into().context(ConversionSnafu {
value: dt.year().to_string(),
component: DateComponent::Year,
})?;
let month: u8 = dt.month().try_into().context(ConversionSnafu {
value: dt.month().to_string(),
component: DateComponent::Month,
})?;
let day: u8 = dt.day().try_into().context(ConversionSnafu {
value: dt.day().to_string(),
component: DateComponent::Day,
})?;
let hour: u8 = dt.hour().try_into().context(ConversionSnafu {
value: dt.hour().to_string(),
component: DateComponent::Hour,
})?;
let minute: u8 = dt.minute().try_into().context(ConversionSnafu {
value: dt.minute().to_string(),
component: DateComponent::Minute,
})?;
let second: u8 = dt.second().try_into().context(ConversionSnafu {
value: dt.second().to_string(),
component: DateComponent::Second,
})?;
DicomDateTime::from_date_and_time(
DicomDate::from_ymd(year, month, day)?,
DicomTime::from_hms_micro(hour, minute, second, dt.nanosecond() / 1000)?,
*dt.offset(),
)
}
}
impl fmt::Display for DicomDateTime {
fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.time {
None => write!(frm, "{} {}", self.date, self.offset),
Some(time) => write!(frm, "{} {} {}", self.date, time, self.offset),
}
}
}
impl fmt::Debug for DicomDateTime {
fn fmt(&self, frm: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.time {
None => write!(frm, "{:?} {:?}", self.date, self.offset),
Some(time) => write!(frm, "{:?} {:?} {}", self.date, time, self.offset),
}
}
}
pub trait Precision {
fn precision(&self) -> DateComponent;
}
impl Precision for DicomDate {
fn precision(&self) -> DateComponent {
match self {
DicomDate(DicomDateImpl::Year(..)) => DateComponent::Year,
DicomDate(DicomDateImpl::Month(..)) => DateComponent::Month,
DicomDate(DicomDateImpl::Day(..)) => DateComponent::Day,
}
}
}
impl Precision for DicomTime {
fn precision(&self) -> DateComponent {
match self {
DicomTime(DicomTimeImpl::Hour(..)) => DateComponent::Hour,
DicomTime(DicomTimeImpl::Minute(..)) => DateComponent::Minute,
DicomTime(DicomTimeImpl::Second(..)) => DateComponent::Second,
DicomTime(DicomTimeImpl::Fraction(..)) => DateComponent::Fraction,
}
}
}
impl Precision for DicomDateTime {
fn precision(&self) -> DateComponent {
match self.time {
Some(time) => time.precision(),
None => self.date.precision(),
}
}
}
impl DicomDate {
pub fn to_encoded(&self) -> String {
match self {
DicomDate(DicomDateImpl::Year(y)) => format!("{:04}", y),
DicomDate(DicomDateImpl::Month(y, m)) => format!("{:04}{:02}", y, m),
DicomDate(DicomDateImpl::Day(y, m, d)) => format!("{:04}{:02}{:02}", y, m, d),
}
}
}
impl DicomTime {
pub fn to_encoded(&self) -> String {
match self {
DicomTime(DicomTimeImpl::Hour(h)) => format!("{:02}", h),
DicomTime(DicomTimeImpl::Minute(h, m)) => format!("{:02}{:02}", h, m),
DicomTime(DicomTimeImpl::Second(h, m, s)) => format!("{:02}{:02}{:02}", h, m, s),
DicomTime(DicomTimeImpl::Fraction(h, m, s, f, fp)) => {
let sfrac = (u32::pow(10, *fp as u32) + f).to_string();
format!(
"{:02}{:02}{:02}.{}",
h,
m,
s,
match f {
0 => "0",
_ => sfrac.get(1..).unwrap(),
}
)
}
}
}
}
impl DicomDateTime {
pub fn to_encoded(&self) -> String {
match self.time {
Some(time) => format!(
"{}{}{}",
self.date.to_encoded(),
time.to_encoded(),
self.offset.to_string().replace(':', "")
),
None => format!(
"{}{}",
self.date.to_encoded(),
self.offset.to_string().replace(':', "")
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{NaiveDateTime, TimeZone};
#[test]
fn test_dicom_date() {
assert_eq!(
DicomDate::from_ymd(1944, 2, 29).unwrap(),
DicomDate(DicomDateImpl::Day(1944, 2, 29))
);
assert_eq!(
DicomDate::from_ym(1944, 2).unwrap(),
DicomDate(DicomDateImpl::Month(1944, 2))
);
assert_eq!(
DicomDate::from_y(1944).unwrap(),
DicomDate(DicomDateImpl::Year(1944))
);
assert_eq!(DicomDate::from_ymd(1944, 2, 29).unwrap().is_precise(), true);
assert_eq!(DicomDate::from_ym(1944, 2).unwrap().is_precise(), false);
assert_eq!(DicomDate::from_y(1944).unwrap().is_precise(), false);
assert_eq!(
DicomDate::from_ymd(1944, 2, 29)
.unwrap()
.earliest()
.unwrap(),
NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
);
assert_eq!(
DicomDate::from_ymd(1944, 2, 29).unwrap().latest().unwrap(),
NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
);
assert_eq!(
DicomDate::from_y(1944).unwrap().earliest().unwrap(),
NaiveDate::from_ymd_opt(1944, 1, 1).unwrap()
);
assert_eq!(
DicomDate::from_ym(1944, 2).unwrap().latest().unwrap(),
NaiveDate::from_ymd_opt(1944, 2, 29).unwrap()
);
assert_eq!(
DicomDate::from_ym(1945, 2).unwrap().latest().unwrap(),
NaiveDate::from_ymd_opt(1945, 2, 28).unwrap()
);
assert_eq!(
DicomDate::try_from(&NaiveDate::from_ymd_opt(1945, 2, 28).unwrap()).unwrap(),
DicomDate(DicomDateImpl::Day(1945, 2, 28))
);
assert!(matches!(
DicomDate::try_from(&NaiveDate::from_ymd_opt(-2000, 2, 28).unwrap()),
Err(Error::Conversion { .. })
));
assert!(matches!(
DicomDate::try_from(&NaiveDate::from_ymd_opt(10_000, 2, 28).unwrap()),
Err(Error::InvalidComponent {
component: DateComponent::Year,
..
})
));
}
#[test]
fn test_dicom_time() {
assert_eq!(
DicomTime::from_hms_micro(9, 1, 1, 123456).unwrap(),
DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 123456, 6))
);
assert_eq!(
DicomTime::from_hms_micro(9, 1, 1, 1).unwrap(),
DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 1, 6))
);
assert_eq!(
DicomTime::from_hms(9, 0, 0).unwrap(),
DicomTime(DicomTimeImpl::Second(9, 0, 0))
);
assert_eq!(
DicomTime::from_hm(23, 59).unwrap(),
DicomTime(DicomTimeImpl::Minute(23, 59))
);
assert_eq!(
DicomTime::from_h(1).unwrap(),
DicomTime(DicomTimeImpl::Hour(1))
);
assert_eq!(
DicomTime::from_hms_milli(9, 1, 1, 123)
.unwrap()
.earliest()
.unwrap(),
NaiveTime::from_hms_micro_opt(9, 1, 1, 123_000).unwrap()
);
assert_eq!(
DicomTime::from_hms_milli(9, 1, 1, 123)
.unwrap()
.latest()
.unwrap(),
NaiveTime::from_hms_micro_opt(9, 1, 1, 123_999).unwrap()
);
assert_eq!(
DicomTime::from_hms_milli(9, 1, 1, 2)
.unwrap()
.earliest()
.unwrap(),
NaiveTime::from_hms_micro_opt(9, 1, 1, 002000).unwrap()
);
assert_eq!(
DicomTime::from_hms_milli(9, 1, 1, 2)
.unwrap()
.latest()
.unwrap(),
NaiveTime::from_hms_micro_opt(9, 1, 1, 002999).unwrap()
);
assert_eq!(
DicomTime::from_hms_micro(9, 1, 1, 123456)
.unwrap()
.is_precise(),
true
);
assert_eq!(
DicomTime::from_hms_milli(9, 1, 1, 1).unwrap(),
DicomTime(DicomTimeImpl::Fraction(9, 1, 1, 1, 3))
);
assert_eq!(
DicomTime::try_from(&NaiveTime::from_hms_milli_opt(16, 31, 28, 123).unwrap()).unwrap(),
DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 123_000, 6))
);
assert_eq!(
DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 123).unwrap()).unwrap(),
DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 000123, 6))
);
assert_eq!(
DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 1234).unwrap()).unwrap(),
DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 001234, 6))
);
assert_eq!(
DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 0).unwrap()).unwrap(),
DicomTime(DicomTimeImpl::Fraction(16, 31, 28, 0, 6))
);
assert_eq!(
DicomTime::from_hmsf(9, 1, 1, 1, 4).unwrap().to_string(),
"09:01:01.0001"
);
assert_eq!(
DicomTime::from_hmsf(9, 1, 1, 0, 1).unwrap().to_string(),
"09:01:01.0"
);
assert_eq!(
DicomTime::from_hmsf(7, 55, 1, 1, 5).unwrap().to_encoded(),
"075501.00001"
);
assert_eq!(
DicomTime::from_hmsf(9, 1, 1, 0, 6).unwrap().to_encoded(),
"090101.0"
);
assert!(matches!(
DicomTime::from_hmsf(9, 1, 1, 1, 7),
Err(Error::FractionPrecisionRange { value: 7, .. })
));
assert!(matches!(
DicomTime::from_hms_milli(9, 1, 1, 1000),
Err(Error::InvalidComponent {
component: DateComponent::Millisecond,
..
})
));
assert!(matches!(
DicomTime::from_hmsf(9, 1, 1, 123456, 3),
Err(Error::FractionPrecisionMismatch {
fraction: 123456,
precision: 3,
..
})
));
assert!(matches!(
DicomTime::try_from(&NaiveTime::from_hms_micro_opt(16, 31, 28, 1_000_000).unwrap()),
Err(Error::InvalidComponent {
component: DateComponent::Fraction,
..
})
));
assert!(matches!(
DicomTime::from_hmsf(9, 1, 1, 12345, 5).unwrap().exact(),
Err(crate::value::range::Error::ImpreciseValue { .. })
));
}
#[test]
fn test_dicom_datetime() {
let default_offset = FixedOffset::east_opt(0).unwrap();
assert_eq!(
DicomDateTime::from_date(DicomDate::from_ymd(2020, 2, 29).unwrap(), default_offset),
DicomDateTime {
date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
time: None,
offset: default_offset
}
);
assert_eq!(
DicomDateTime::from_date(DicomDate::from_ym(2020, 2).unwrap(), default_offset)
.earliest()
.unwrap(),
FixedOffset::east_opt(0)
.unwrap()
.from_local_datetime(&NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 2, 1).unwrap(),
NaiveTime::from_hms_micro_opt(0, 0, 0, 0).unwrap()
))
.unwrap()
);
assert_eq!(
DicomDateTime::from_date(DicomDate::from_ym(2020, 2).unwrap(), default_offset)
.latest()
.unwrap(),
FixedOffset::east_opt(0)
.unwrap()
.from_local_datetime(&NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
))
.unwrap()
);
assert_eq!(
DicomDateTime::from_date_and_time(
DicomDate::from_ymd(2020, 2, 29).unwrap(),
DicomTime::from_hmsf(23, 59, 59, 10, 2).unwrap(),
default_offset
)
.unwrap()
.earliest()
.unwrap(),
FixedOffset::east_opt(0)
.unwrap()
.from_local_datetime(&NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
NaiveTime::from_hms_micro_opt(23, 59, 59, 100_000).unwrap()
))
.unwrap()
);
assert_eq!(
DicomDateTime::from_date_and_time(
DicomDate::from_ymd(2020, 2, 29).unwrap(),
DicomTime::from_hmsf(23, 59, 59, 10, 2).unwrap(),
default_offset
)
.unwrap()
.latest()
.unwrap(),
FixedOffset::east_opt(0)
.unwrap()
.from_local_datetime(&NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
NaiveTime::from_hms_micro_opt(23, 59, 59, 109_999).unwrap()
))
.unwrap()
);
assert_eq!(
DicomDateTime::try_from(
&FixedOffset::east_opt(0)
.unwrap()
.from_local_datetime(&NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
))
.unwrap()
)
.unwrap(),
DicomDateTime {
date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
time: Some(DicomTime::from_hms_micro(23, 59, 59, 999_999).unwrap()),
offset: default_offset
}
);
assert_eq!(
DicomDateTime::try_from(
&FixedOffset::east_opt(0)
.unwrap()
.from_local_datetime(&NaiveDateTime::new(
NaiveDate::from_ymd_opt(2020, 2, 29).unwrap(),
NaiveTime::from_hms_micro_opt(23, 59, 59, 0).unwrap()
))
.unwrap()
)
.unwrap(),
DicomDateTime {
date: DicomDate::from_ymd(2020, 2, 29).unwrap(),
time: Some(DicomTime::from_hms_micro(23, 59, 59, 0).unwrap()),
offset: default_offset
}
);
assert!(matches!(
DicomDateTime::from_date(DicomDate::from_ymd(2021, 2, 29).unwrap(), default_offset)
.earliest(),
Err(crate::value::range::Error::InvalidDate { .. })
));
assert!(matches!(
DicomDateTime::from_date_and_time(
DicomDate::from_ym(2020, 2).unwrap(),
DicomTime::from_hms_milli(23, 59, 59, 999).unwrap(),
default_offset
),
Err(Error::DateTimeFromPartials {
value: DateComponent::Month,
..
})
));
assert!(matches!(
DicomDateTime::from_date_and_time(
DicomDate::from_y(1).unwrap(),
DicomTime::from_hms_micro(23, 59, 59, 10).unwrap(),
default_offset
),
Err(Error::DateTimeFromPartials {
value: DateComponent::Year,
..
})
));
assert!(matches!(
DicomDateTime::from_date_and_time(
DicomDate::from_ymd(2000, 1, 1).unwrap(),
DicomTime::from_hms_milli(23, 59, 59, 10).unwrap(),
default_offset
)
.unwrap()
.exact(),
Err(crate::value::range::Error::ImpreciseValue { .. })
));
}
}