DateTime parser incorrectly parsed times where more than 3 digits of
precision were provided on the seconds after the decimal point
This commit is contained in:
parent
9a4612105c
commit
a5debc07a3
|
@ -35,6 +35,7 @@ import java.util.List;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.apache.commons.lang3.time.FastDateFormat;
|
import org.apache.commons.lang3.time.FastDateFormat;
|
||||||
|
@ -202,7 +203,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the TimeZone associated with this dateTime's value. May return <code>null</code> if no timezone was supplied.
|
* Returns the TimeZone associated with this dateTime's value. May return <code>null</code> if no timezone was
|
||||||
|
* supplied.
|
||||||
*/
|
*/
|
||||||
public TimeZone getTimeZone() {
|
public TimeZone getTimeZone() {
|
||||||
return myTimeZone;
|
return myTimeZone;
|
||||||
|
@ -304,18 +306,30 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
||||||
|
|
||||||
Date retVal;
|
Date retVal;
|
||||||
if (hasMillis) {
|
if (hasMillis) {
|
||||||
|
String value = theValue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have more than 3 digits of precision after the decimal point, we
|
||||||
|
* only parse the first 3 since Java Dates don't support more than that and
|
||||||
|
* FastDateFormat gets confused
|
||||||
|
*/
|
||||||
|
int offsetIndex = getOffsetIndex(theValue);
|
||||||
|
if (offsetIndex >= 24) {
|
||||||
|
value = theValue.substring(0, 23) + theValue.substring(offsetIndex);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (hasOffset(theValue)) {
|
if (hasOffset(value)) {
|
||||||
retVal = ourYearMonthDayTimeMilliZoneFormat.parse(theValue);
|
retVal = ourYearMonthDayTimeMilliZoneFormat.parse(value);
|
||||||
} else if (theValue.endsWith("Z")) {
|
} else if (value.endsWith("Z")) {
|
||||||
retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue);
|
retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(value);
|
||||||
} else {
|
} else {
|
||||||
retVal = ourYearMonthDayTimeMilliFormat.parse(theValue);
|
retVal = ourYearMonthDayTimeMilliFormat.parse(value);
|
||||||
}
|
}
|
||||||
} catch (ParseException p2) {
|
} catch (ParseException p2) {
|
||||||
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
|
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
|
||||||
}
|
}
|
||||||
setTimeZone(theValue, hasMillis);
|
setTimeZone(theValue);
|
||||||
setPrecision(TemporalPrecisionEnum.MILLI);
|
setPrecision(TemporalPrecisionEnum.MILLI);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -330,7 +344,7 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
||||||
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
|
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeZone(theValue, hasMillis);
|
setTimeZone(theValue);
|
||||||
setPrecision(TemporalPrecisionEnum.SECOND);
|
setPrecision(TemporalPrecisionEnum.SECOND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,18 +370,34 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
||||||
updateStringValue();
|
updateStringValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private BaseDateTimeDt setTimeZone(String theValueString, boolean hasMillis) {
|
private int getOffsetIndex(String theValueString) {
|
||||||
clearTimeZone();
|
int plusIndex = theValueString.indexOf('+', 19);
|
||||||
int timeZoneStart = 19;
|
int minusIndex = theValueString.indexOf('-', 19);
|
||||||
if (hasMillis)
|
int zIndex = theValueString.indexOf('Z', 19);
|
||||||
timeZoneStart += 4;
|
int retVal = Math.max(Math.max(plusIndex, minusIndex), zIndex);
|
||||||
if (theValueString.endsWith("Z")) {
|
if (retVal == -1) {
|
||||||
setTimeZoneZulu(true);
|
return -1;
|
||||||
} else if (theValueString.indexOf("GMT", timeZoneStart) != -1) {
|
|
||||||
setTimeZone(TimeZone.getTimeZone(theValueString.substring(timeZoneStart)));
|
|
||||||
} else if (theValueString.indexOf('+', timeZoneStart) != -1 || theValueString.indexOf('-', timeZoneStart) != -1) {
|
|
||||||
setTimeZone(TimeZone.getTimeZone("GMT" + theValueString.substring(timeZoneStart)));
|
|
||||||
}
|
}
|
||||||
|
if ((retVal - 2) != (plusIndex + minusIndex + zIndex)) {
|
||||||
|
// This means we have more than one separator
|
||||||
|
throw new DataFormatException("Invalid FHIR date/time string: " + theValueString);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseDateTimeDt setTimeZone(String theValueString) {
|
||||||
|
clearTimeZone();
|
||||||
|
|
||||||
|
int sepIndex = getOffsetIndex(theValueString);
|
||||||
|
if (sepIndex != -1) {
|
||||||
|
if (theValueString.charAt(sepIndex) == 'Z') {
|
||||||
|
setTimeZoneZulu(true);
|
||||||
|
} else {
|
||||||
|
String offsetString = theValueString.substring(sepIndex);
|
||||||
|
setTimeZone(TimeZone.getTimeZone("GMT" + offsetString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +414,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value for this type using the given Java Date object as the time, and using the default precision for this datatype, as well as the local timezone as determined by the local operating
|
* Sets the value for this type using the given Java Date object as the time, and using the default precision for
|
||||||
|
* this datatype, as well as the local timezone as determined by the local operating
|
||||||
* system. Both of these properties may be modified in subsequent calls if neccesary.
|
* system. Both of these properties may be modified in subsequent calls if neccesary.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -394,7 +425,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value for this type using the given Java Date object as the time, and using the specified precision, as well as the local timezone as determined by the local operating system. Both of
|
* Sets the value for this type using the given Java Date object as the time, and using the specified precision, as
|
||||||
|
* well as the local timezone as determined by the local operating system. Both of
|
||||||
* these properties may be modified in subsequent calls if neccesary.
|
* these properties may be modified in subsequent calls if neccesary.
|
||||||
*
|
*
|
||||||
* @param theValue
|
* @param theValue
|
||||||
|
@ -418,8 +450,10 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
||||||
/**
|
/**
|
||||||
* Returns a human readable version of this date/time using the system local format.
|
* Returns a human readable version of this date/time using the system local format.
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Note on time zones:</b> This method renders the value using the time zone that is contained within the value. For example, if this date object contains the value "2012-01-05T12:00:00-08:00",
|
* <b>Note on time zones:</b> This method renders the value using the time zone that is contained within the value.
|
||||||
* the human display will be rendered as "12:00:00" even if the application is being executed on a system in a different time zone. If this behaviour is not what you want, use
|
* For example, if this date object contains the value "2012-01-05T12:00:00-08:00",
|
||||||
|
* the human display will be rendered as "12:00:00" even if the application is being executed on a system in a
|
||||||
|
* different time zone. If this behaviour is not what you want, use
|
||||||
* {@link #toHumanDisplayLocalTimezone()} instead.
|
* {@link #toHumanDisplayLocalTimezone()} instead.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
|
@ -441,7 +475,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a human readable version of this date/time using the system local format, converted to the local timezone if neccesary.
|
* Returns a human readable version of this date/time using the system local format, converted to the local timezone
|
||||||
|
* if neccesary.
|
||||||
*
|
*
|
||||||
* @see #toHumanDisplay() for a method which does not convert the time to the local timezone before rendering it.
|
* @see #toHumanDisplay() for a method which does not convert the time to the local timezone before rendering it.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -124,7 +124,7 @@ public class TestRestfulServer extends RestfulServer {
|
||||||
myAppCtx = new AnnotationConfigWebApplicationContext();
|
myAppCtx = new AnnotationConfigWebApplicationContext();
|
||||||
myAppCtx.setServletConfig(getServletConfig());
|
myAppCtx.setServletConfig(getServletConfig());
|
||||||
myAppCtx.setParent(parentAppCtx);
|
myAppCtx.setParent(parentAppCtx);
|
||||||
if ("TDL2".equals(fhirVersionParam.trim().toUpperCase())) {
|
if ("TDL3".equals(fhirVersionParam.trim().toUpperCase())) {
|
||||||
myAppCtx.register(TdlDstu3Config.class);
|
myAppCtx.register(TdlDstu3Config.class);
|
||||||
baseUrlProperty = FHIR_BASEURL_TDL3;
|
baseUrlProperty = FHIR_BASEURL_TDL3;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||||
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
|
||||||
public class BaseDateTimeDtDstu2Test {
|
public class BaseDateTimeDtDstu2Test {
|
||||||
|
@ -53,6 +54,106 @@ public class BaseDateTimeDtDstu2Test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly0millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeDt dt = new DateTimeDt("2010-01-01T00:00:00-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.000", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly1millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeDt dt = new DateTimeDt("2010-01-01T00:00:00.1-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00.1-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.001", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00.001Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly2millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeDt dt = new DateTimeDt("2010-01-01T00:00:00.12-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00.12-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.012", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00.012Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly3millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeDt dt = new DateTimeDt("2010-01-01T00:00:00.123-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00.123-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.123", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00.123Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseInvalidZoneOffset() {
|
||||||
|
try {
|
||||||
|
new DateTimeDt("2010-01-01T00:00:00.1234-09:00Z");
|
||||||
|
fail();
|
||||||
|
} catch (DataFormatException e) {
|
||||||
|
assertEquals("Invalid FHIR date/time string: 2010-01-01T00:00:00.1234-09:00Z", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly4millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeDt dt = new DateTimeDt("2010-01-01T00:00:00.1234-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00.1234-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.123", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00.123Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly5millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeDt dt = new DateTimeDt("2010-01-01T00:00:00.12345-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00.12345-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.123", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00.123Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See HAPI #101 - https://github.com/jamesagnew/hapi-fhir/issues/101
|
* See HAPI #101 - https://github.com/jamesagnew/hapi-fhir/issues/101
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,6 +19,7 @@ import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
import org.apache.commons.lang3.time.FastDateFormat;
|
import org.apache.commons.lang3.time.FastDateFormat;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.primitive.BaseDateTimeDt;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
|
|
||||||
public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
||||||
|
@ -332,12 +333,12 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
||||||
} else if (theValue.length() >= 16) { // date and time with possible time zone
|
} else if (theValue.length() >= 16) { // date and time with possible time zone
|
||||||
char timeSeparator = theValue.charAt(10);
|
char timeSeparator = theValue.charAt(10);
|
||||||
if (timeSeparator != 'T') {
|
if (timeSeparator != 'T') {
|
||||||
throw new DataFormatException("Invalid date/time string: " + theValue);
|
throw new DataFormatException("Invalid date/time string (invalid length): " + theValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
int firstColonIndex = theValue.indexOf(':');
|
int firstColonIndex = theValue.indexOf(':');
|
||||||
if (firstColonIndex == -1) {
|
if (firstColonIndex == -1) {
|
||||||
throw new DataFormatException("Invalid date/time string: " + theValue);
|
throw new DataFormatException("Invalid date/time string (invalid length): " + theValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasSeconds = theValue.length() > firstColonIndex+3 ? theValue.charAt(firstColonIndex+3) == ':' : false;
|
boolean hasSeconds = theValue.length() > firstColonIndex+3 ? theValue.charAt(firstColonIndex+3) == ':' : false;
|
||||||
|
@ -355,18 +356,30 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
||||||
|
|
||||||
Date retVal;
|
Date retVal;
|
||||||
if (hasMillis) {
|
if (hasMillis) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have more than 3 digits of precision after the decimal point, we
|
||||||
|
* only parse the first 3 since Java Dates don't support more than that and
|
||||||
|
* FastDateFormat gets confused
|
||||||
|
*/
|
||||||
|
String value = theValue;
|
||||||
|
int offsetIndex = getOffsetIndex(theValue);
|
||||||
|
if (offsetIndex >= 24) {
|
||||||
|
value = theValue.substring(0, 23) + theValue.substring(offsetIndex);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (hasOffset(theValue)) {
|
if (hasOffset(value)) {
|
||||||
retVal = ourYearMonthDayTimeMilliZoneFormat.parse(theValue);
|
retVal = ourYearMonthDayTimeMilliZoneFormat.parse(value);
|
||||||
} else if (theValue.endsWith("Z")) {
|
} else if (value.endsWith("Z")) {
|
||||||
retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue);
|
retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(value);
|
||||||
} else {
|
} else {
|
||||||
retVal = ourYearMonthDayTimeMilliFormat.parse(theValue);
|
retVal = ourYearMonthDayTimeMilliFormat.parse(value);
|
||||||
}
|
}
|
||||||
} catch (ParseException p2) {
|
} catch (ParseException p2) {
|
||||||
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
|
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
|
||||||
}
|
}
|
||||||
setTimeZone(theValue, hasMillis);
|
setTimeZone(theValue);
|
||||||
setPrecision(TemporalPrecisionEnum.MILLI);
|
setPrecision(TemporalPrecisionEnum.MILLI);
|
||||||
} else if (hasSeconds) {
|
} else if (hasSeconds) {
|
||||||
try {
|
try {
|
||||||
|
@ -381,7 +394,7 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
||||||
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
|
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeZone(theValue, hasMillis);
|
setTimeZone(theValue);
|
||||||
setPrecision(TemporalPrecisionEnum.SECOND);
|
setPrecision(TemporalPrecisionEnum.SECOND);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -396,7 +409,7 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
||||||
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue, p2);
|
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue, p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeZone(theValue, hasMillis);
|
setTimeZone(theValue);
|
||||||
setPrecision(TemporalPrecisionEnum.MINUTE);
|
setPrecision(TemporalPrecisionEnum.MINUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,18 +457,35 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
||||||
updateStringValue();
|
updateStringValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTimeZone(String theValueString, boolean hasMillis) {
|
private int getOffsetIndex(String theValueString) {
|
||||||
clearTimeZone();
|
int plusIndex = theValueString.indexOf('+', 19);
|
||||||
int timeZoneStart = 19;
|
int minusIndex = theValueString.indexOf('-', 19);
|
||||||
if (hasMillis)
|
int zIndex = theValueString.indexOf('Z');
|
||||||
timeZoneStart += 4;
|
int retVal = Math.max(Math.max(plusIndex, minusIndex), zIndex);
|
||||||
if (theValueString.endsWith("Z")) {
|
if (retVal == -1) {
|
||||||
setTimeZoneZulu(true);
|
return -1;
|
||||||
} else if (theValueString.indexOf("GMT", timeZoneStart) != -1) {
|
|
||||||
setTimeZone(TimeZone.getTimeZone(theValueString.substring(timeZoneStart)));
|
|
||||||
} else if (theValueString.indexOf('+', timeZoneStart) != -1 || theValueString.indexOf('-', timeZoneStart) != -1) {
|
|
||||||
setTimeZone(TimeZone.getTimeZone("GMT" + theValueString.substring(timeZoneStart)));
|
|
||||||
}
|
}
|
||||||
|
if ((retVal - 2) != (plusIndex + minusIndex + zIndex)) {
|
||||||
|
// This means we have more than one separator
|
||||||
|
throw new DataFormatException("Invalid FHIR date/time string: " + theValueString);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseDateTimeType setTimeZone(String theValueString) {
|
||||||
|
clearTimeZone();
|
||||||
|
|
||||||
|
int sepIndex = getOffsetIndex(theValueString);
|
||||||
|
if (sepIndex != -1) {
|
||||||
|
if (theValueString.charAt(sepIndex) == 'Z') {
|
||||||
|
setTimeZoneZulu(true);
|
||||||
|
} else {
|
||||||
|
String offsetString = theValueString.substring(sepIndex);
|
||||||
|
setTimeZone(TimeZone.getTimeZone("GMT" + offsetString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTimeZone(TimeZone theTimeZone) {
|
public void setTimeZone(TimeZone theTimeZone) {
|
||||||
|
|
|
@ -332,6 +332,30 @@ public class FhirInstanceValidatorDstu3Test {
|
||||||
assertEquals(output.toString(), 0, output.getMessages().size());
|
assertEquals(output.toString(), 0, output.getMessages().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateRawXmlWithMissingRootNamespace() {
|
||||||
|
//@formatter:off
|
||||||
|
String input = ""
|
||||||
|
+ "<Patient>"
|
||||||
|
+ " <text>"
|
||||||
|
+ " <status value=\"generated\"/>"
|
||||||
|
+ " <div xmlns=\"http://www.w3.org/1999/xhtml\">Some narrative</div>"
|
||||||
|
+ " </text>"
|
||||||
|
+ " <name>"
|
||||||
|
+ " <use value=\"official\"/>"
|
||||||
|
+ " <family value=\"Doe\"/>"
|
||||||
|
+ " <given value=\"John\"/>"
|
||||||
|
+ " </name>"
|
||||||
|
+ " <gender value=\"male\"/>"
|
||||||
|
+ " <birthDate value=\"1974-12-25\"/>"
|
||||||
|
+ "</Patient>";
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
ValidationResult output = myVal.validateWithResult(input);
|
||||||
|
assertEquals(output.toString(), 1, output.getMessages().size());
|
||||||
|
ourLog.info(output.getMessages().get(0).getLocationString());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidateRawJsonResourceBadAttributes() {
|
public void testValidateRawJsonResourceBadAttributes() {
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
|
|
|
@ -0,0 +1,231 @@
|
||||||
|
package org.hl7.fhir.dstu3.model;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
|
||||||
|
public class BaseDateTimeTypeDstu3Test {
|
||||||
|
private static Locale ourDefaultLocale;
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseDateTimeTypeDstu3Test.class);
|
||||||
|
private SimpleDateFormat myDateInstantParser;
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClassClearContext() {
|
||||||
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
myDateInstantParser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseInvalidZoneOffset() {
|
||||||
|
try {
|
||||||
|
new DateTimeType("2010-01-01T00:00:00.1234-09:00Z");
|
||||||
|
fail();
|
||||||
|
} catch (DataFormatException e) {
|
||||||
|
assertEquals("Invalid FHIR date/time string: 2010-01-01T00:00:00.1234-09:00Z", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseInvalid() {
|
||||||
|
try {
|
||||||
|
DateTimeType dt = new DateTimeType();
|
||||||
|
dt.setValueAsString("1974-12-25+10:00");
|
||||||
|
fail();
|
||||||
|
} catch (ca.uhn.fhir.parser.DataFormatException e) {
|
||||||
|
assertEquals("Invalid date/time string (invalid length): 1974-12-25+10:00", e.getMessage());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DateTimeType dt = new DateTimeType();
|
||||||
|
dt.setValueAsString("1974-12-25Z");
|
||||||
|
fail();
|
||||||
|
} catch (ca.uhn.fhir.parser.DataFormatException e) {
|
||||||
|
assertEquals("Invalid date/time string (invalid length): 1974-12-25Z", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly0millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeType dt = new DateTimeType("2010-01-01T00:00:00-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.000", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly1millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeType dt = new DateTimeType("2010-01-01T00:00:00.1-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00.1-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.001", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00.001Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly2millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeType dt = new DateTimeType("2010-01-01T00:00:00.12-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00.12-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.012", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00.012Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly3millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeType dt = new DateTimeType("2010-01-01T00:00:00.123-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00.123-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.123", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00.123Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly4millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeType dt = new DateTimeType("2010-01-01T00:00:00.1234-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00.1234-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.123", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00.123Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseTimeZoneOffsetCorrectly5millis() {
|
||||||
|
myDateInstantParser.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
|
||||||
|
|
||||||
|
DateTimeType dt = new DateTimeType("2010-01-01T00:00:00.12345-09:00");
|
||||||
|
|
||||||
|
assertEquals("2010-01-01T00:00:00.12345-09:00", dt.getValueAsString());
|
||||||
|
assertEquals("2010-01-01 04:00:00.123", myDateInstantParser.format(dt.getValue()));
|
||||||
|
assertEquals("GMT-09:00", dt.getTimeZone().getID());
|
||||||
|
assertEquals(-32400000L, dt.getTimeZone().getRawOffset());
|
||||||
|
|
||||||
|
dt.setTimeZoneZulu(true);
|
||||||
|
assertEquals("2010-01-01T09:00:00.123Z", dt.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See HAPI #101 - https://github.com/jamesagnew/hapi-fhir/issues/101
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPrecisionRespectedForSetValue() throws Exception {
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
cal.setTime(myDateInstantParser.parse("2012-01-02 22:31:02.333"));
|
||||||
|
cal.setTimeZone(TimeZone.getTimeZone("EST"));
|
||||||
|
|
||||||
|
Date time = cal.getTime();
|
||||||
|
|
||||||
|
DateType date = new DateType();
|
||||||
|
date.setValue(time);
|
||||||
|
assertEquals("2012-01-02", date.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMinutePrecisionEncode() throws Exception {
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
cal.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
|
||||||
|
cal.set(1990, Calendar.JANUARY, 3, 3, 22, 11);
|
||||||
|
|
||||||
|
DateTimeType date = new DateTimeType();
|
||||||
|
date.setValue(cal.getTime(), TemporalPrecisionEnum.MINUTE);
|
||||||
|
date.setTimeZone(TimeZone.getTimeZone("EST"));
|
||||||
|
assertEquals("1990-01-02T21:22-05:00", date.getValueAsString());
|
||||||
|
|
||||||
|
date.setTimeZoneZulu(true);
|
||||||
|
assertEquals("1990-01-03T02:22Z", date.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See HAPI #101 - https://github.com/jamesagnew/hapi-fhir/issues/101
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPrecisionRespectedForSetValueWithPrecision() throws Exception {
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
cal.setTime(myDateInstantParser.parse("2012-01-02 22:31:02.333"));
|
||||||
|
cal.setTimeZone(TimeZone.getTimeZone("EST"));
|
||||||
|
|
||||||
|
Date time = cal.getTime();
|
||||||
|
|
||||||
|
DateType date = new DateType();
|
||||||
|
date.setValue(time, TemporalPrecisionEnum.DAY);
|
||||||
|
assertEquals("2012-01-02", date.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToHumanDisplay() {
|
||||||
|
DateTimeType dt = new DateTimeType("2012-01-05T12:00:00-08:00");
|
||||||
|
String human = dt.toHumanDisplay();
|
||||||
|
ourLog.info(human);
|
||||||
|
assertThat(human, containsString("2012"));
|
||||||
|
assertThat(human, containsString("12"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void afterClass() {
|
||||||
|
Locale.setDefault(ourDefaultLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() {
|
||||||
|
/*
|
||||||
|
* We cache the default locale, but temporarily set it to a random value during this test. This helps ensure
|
||||||
|
* that there are no language specific dependencies in the test.
|
||||||
|
*/
|
||||||
|
ourDefaultLocale = Locale.getDefault();
|
||||||
|
|
||||||
|
Locale[] available = { Locale.CANADA, Locale.GERMANY, Locale.TAIWAN };
|
||||||
|
Locale newLocale = available[(int) (Math.random() * available.length)];
|
||||||
|
Locale.setDefault(newLocale);
|
||||||
|
|
||||||
|
ourLog.info("Tests are running in locale: " + newLocale.getDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -240,6 +240,10 @@
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Web Testing UI was not able to correctly post an STU3 transaction
|
Web Testing UI was not able to correctly post an STU3 transaction
|
||||||
</action>
|
</action>
|
||||||
|
<action type="fix">
|
||||||
|
DateTime parser incorrectly parsed times where more than 3 digits of
|
||||||
|
precision were provided on the seconds after the decimal point
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="1.5" date="2016-04-20">
|
<release version="1.5" date="2016-04-20">
|
||||||
<action type="fix" issue="339">
|
<action type="fix" issue="339">
|
||||||
|
|
Loading…
Reference in New Issue