Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support modern java.time types for getters (first draft) #1351

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 219 additions & 7 deletions dcm4che-core/src/main/java/org/dcm4che3/data/Attributes.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.util.*;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -1241,6 +1248,174 @@ public double[] getDoubles(String privateCreator, int tag, VR vr) {
}
}

/**
* Gets the most accurate temporal type for the given tag.
* <p>
* An instance of {@link ZonedDateTime} will be returned for:
* <ul>
* <li>A tag with {@link VR#DT} which has a timezone offset defined within its value.</li>
* <li>A tag with {@link VR#DT} without a timezone offset within its value,
* but a {@link Tag#TimezoneOffsetFromUTC} is defined within this or any parent Attributes,
* or a default TimeZone (see {@link #setDefaultTimeZone(TimeZone)}) has been set for this or any parent.</li>
* <li>A tag with {@link VR#DA} or {@link VR#TM}, in case the corresponding {@link VR#TM} or {@link VR#DA} tag
* value is also available (e.g. given {@link Tag#StudyDate}, also {@link Tag#StudyTime} is available), and
* {@link Tag#TimezoneOffsetFromUTC} or a default TimeZone is available.</li>
* </ul>
*
* If no timezone information is available, then an instance of {@link LocalDateTime} will be returned for
* {@link VR#DT} tags, and for {@link VR#DA} or {@link VR#TM} tags if the corresponding {@link VR#TM} or
* {@link VR#DA} tag value is also available.
*
* If the corresponding {@link VR#TM} or {@link VR#DA} tag value is not available then an instance of
* {@link LocalDate} (for VR#DA tags) or {@link LocalTime} (for VR#TM tags) will be returned.
*
* In case the value for the given tag itself is not set (or empty), then <code>null</code> (or the supplied
* <code>defVal</code> for other variants of this method) will be returned.
*
* @param tag tag number
* @return an instance of {@link ZonedDateTime}, {@link LocalDateTime}, {@link LocalDate} or {@link LocalTime}, or null
*/
public Temporal getTemporal(int tag) {
return getTemporal(null, tag, null, 0, null, new DatePrecision());
}

// TODO variants of getTemporal missing here

/**
* See {@link #getTemporal(int)}.
*
* @param privateCreator private creator
* @param tag tag number
* @param vr VR
* @param valueIndex value index
* @param defVal default value, if the tag value is not set or empty
* @param precision used as a return value: contains information about the contained date/time precision and
* whether the tag value itself contained timezone information (only for {@link VR#DT} tags).
* @return
*/
public Temporal getTemporal(String privateCreator, int tag, VR vr, int valueIndex, Temporal defVal,
DatePrecision precision) {
int index = indexOf(privateCreator, tag);
if (index < 0)
return defVal;

Object value = values[index];
if (value == Value.NULL)
return defVal;

vr = updateVR(index, vr);

if (!vr.isTemporalType()) {
LOG.info("Attempt to access {} {} as date/time", TagUtils.toString(tag), vr);
return defVal;
}

value = decodeStringValue(index);
if (value == Value.NULL) {
return defVal;
}

Temporal t;
try {
t = vr.toTemporal(value, valueIndex, defVal, precision);
} catch (IllegalArgumentException e) {
LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
return defVal;
}

if (t instanceof OffsetDateTime) {
return ((OffsetDateTime) t).toZonedDateTime();
} else if (t instanceof LocalDate) {
int tmTag = ElementDictionary.getElementDictionary(privateCreator).tmTagOf(tag);
if (tmTag != 0) {
LocalTime localTime = getLocalTime(privateCreator, tmTag, VR.TM, valueIndex, null, precision);
if (localTime != null) {
t = ((LocalDate) t).atTime(localTime);
}
}
} else if (t instanceof LocalTime) {
int daTag = ElementDictionary.getElementDictionary(privateCreator).daTagOf(tag);
if (daTag != 0) {
LocalDate localDate = getLocalDate(privateCreator, daTag, VR.DA, valueIndex, null, new DatePrecision());
if (localDate != null) {
t = localDate.atTime((LocalTime) t);
}
}
}

if (t instanceof LocalDateTime) {
ZoneId zoneId = getZoneId();
if (zoneId != null) {
return ((LocalDateTime) t).atZone(zoneId);
} else {
return t;
}
}

return t;
}


private LocalDate getLocalDate(String privateCreator, int tag, VR vr, int valueIndex, LocalDate defVal,
DatePrecision precision) {
int index = indexOf(privateCreator, tag);
if (index < 0)
return defVal;

Object value = values[index];
if (value == Value.NULL)
return defVal;

vr = updateVR(index, vr);

if (vr != VR.DA) {
LOG.info("Attempt to access {} {} as local date", TagUtils.toString(tag), vr);
return defVal;
}

value = decodeStringValue(index);
if (value == Value.NULL) {
return defVal;
}

try {
return (LocalDate)vr.toTemporal(value, valueIndex, defVal, precision);
} catch (IllegalArgumentException e) {
LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
return defVal;
}
}

private LocalTime getLocalTime(String privateCreator, int tag, VR vr, int valueIndex, LocalTime defVal,
DatePrecision precision) {
int index = indexOf(privateCreator, tag);
if (index < 0)
return defVal;

Object value = values[index];
if (value == Value.NULL)
return defVal;

vr = updateVR(index, vr);

if (vr != VR.TM) {
LOG.info("Attempt to access {} {} as local time", TagUtils.toString(tag), vr);
return defVal;
}

value = decodeStringValue(index);
if (value == Value.NULL) {
return defVal;
}

try {
return (LocalTime)vr.toTemporal(value, valueIndex, defVal, precision);
} catch (IllegalArgumentException e) {
LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
return defVal;
}
}

public Date getDate(int tag) {
return getDate(null, tag, null, 0, null, new DatePrecision());
}
Expand Down Expand Up @@ -1357,11 +1532,12 @@ public Date getDate(String privateCreator, int tag, VR vr, int valueIndex,
LOG.info("Attempt to access {} {} as date", TagUtils.toString(tag), vr);
return defVal;
}
try {

value = decodeStringValue(index);
if (value == Value.NULL)
if (value == Value.NULL)
return defVal;

try {
return vr.toDate(value, getTimeZone(), valueIndex, false, defVal, precision);
} catch (IllegalArgumentException e) {
LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
Expand Down Expand Up @@ -1456,11 +1632,12 @@ public Date[] getDates(String privateCreator, int tag, VR vr,
LOG.info("Attempt to access {} {} as date", TagUtils.toString(tag), vr);
return DateUtils.EMPTY_DATES;
}
try {
value = decodeStringValue(index);
if (value == Value.NULL)
return DateUtils.EMPTY_DATES;

value = decodeStringValue(index);
if (value == Value.NULL)
return DateUtils.EMPTY_DATES;

try {
return vr.toDates(value, getTimeZone(), false, precisions);
} catch (IllegalArgumentException e) {
LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
Expand Down Expand Up @@ -1496,8 +1673,8 @@ public Date[] getDates(String privateCreator, long tag,
Date[] dates = new Date[da.length];
precisions.precisions = new DatePrecision[da.length];
int i = 0;
TimeZone tz = getTimeZone();
try {
TimeZone tz = getTimeZone();
while (i < tm.length)
dates[i++] = VR.DT.toDate(da[i] + tm[i], tz, 0, false, null,
precisions.precisions[i] = new DatePrecision());
Expand Down Expand Up @@ -1671,6 +1848,16 @@ public void setDefaultTimeZone(TimeZone tz) {
defaultTimeZone = tz;
}

public ZoneId getDefaultZoneId() {
if (defaultTimeZone != null)
return defaultTimeZone.toZoneId();

if (parent != null)
return parent.getDefaultZoneId();

return null;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the different behavior here compared to getDefaultTimezone: It returns null in case nothing is defined and does not fall back to the system default.

}

public TimeZone getDefaultTimeZone() {
if (defaultTimeZone != null)
return defaultTimeZone;
Expand Down Expand Up @@ -1701,6 +1888,31 @@ public TimeZone getTimeZone() {
return tz;
}

public ZoneId getZoneId() {
// TODO we might want to store the ZoneId, instead of converting every time here

if (tz != null)
return tz.toZoneId();

if (containsTimezoneOffsetFromUTC) {
String s = getString(Tag.TimezoneOffsetFromUTC);
if (s == null) {
return null;
}
try {
tz = DateUtils.timeZone(s);
} catch (IllegalArgumentException e) {
LOG.info(e.getMessage());
return null;
}
return tz.toZoneId();
} else if (parent != null) {
return parent.getZoneId();
} else {
return getDefaultZoneId();
}
}

/**
* Set Timezone Offset From UTC (0008,0201) to specified value and
* adjust contained DA, DT and TM attributs accordingly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@

package org.dcm4che3.data;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.TimeZone;

Expand Down Expand Up @@ -609,7 +612,11 @@ public double[] toDoubles(Object val, boolean bigEndian) {
for (int i = 0, off = 0; i < ds.length; i++, off += numBytes)
ds[i] = toDouble(b, off, bigEndian);
return ds;
}
}

@Override public Temporal toTemporal(Object val, int valueIndex, Temporal defVal, DatePrecision precision) {
throw new UnsupportedOperationException();
}

@Override
public Date toDate(Object val, TimeZone tz, int valueIndex, boolean ceil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@

package org.dcm4che3.data;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.TimeZone;

Expand Down Expand Up @@ -135,7 +138,11 @@ public double toDouble(Object val, boolean bigEndian, int valueIndex,
@Override
public double[] toDoubles(Object val, boolean bigEndian) {
throw new UnsupportedOperationException();
}
}

@Override public Temporal toTemporal(Object val, int valueIndex, Temporal defVal, DatePrecision precision) {
throw new UnsupportedOperationException();
}

@Override
public Date toDate(Object val, TimeZone tz, int valueIndex, boolean ceil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@

package org.dcm4che3.data;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.TimeZone;

Expand Down Expand Up @@ -448,8 +451,25 @@ public double toDouble(Object val, boolean bigEndian, int valueIndex,
@Override
public double[] toDoubles(Object val, boolean bigEndian) {
throw new UnsupportedOperationException();
}
}

@Override public Temporal toTemporal(Object val, int valueIndex, Temporal defVal, DatePrecision precision) {
if (temporalType == null)
throw new UnsupportedOperationException();

if (val instanceof String) {
return valueIndex == 0
? temporalType.parseTemporal((String) val, precision)
: defVal;
}
if (val instanceof String[]) {
String[] ss = (String[]) val;
return (valueIndex < ss.length && ss[valueIndex] != null)
? temporalType.parseTemporal(ss[valueIndex], precision)
: defVal;
}
throw new UnsupportedOperationException();
}

@Override
public Date toDate(Object val, TimeZone tz, int valueIndex, boolean ceil,
Expand Down
Loading
Loading