From a5debc07a3e152628f2b9f4f905c924ffa9d6b5d Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 25 May 2016 19:20:51 -0400 Subject: [PATCH 1/4] DateTime parser incorrectly parsed times where more than 3 digits of precision were provided on the seconds after the decimal point --- .../fhir/model/primitive/BaseDateTimeDt.java | 83 +++++-- .../ca/uhn/fhirtest/TestRestfulServer.java | 2 +- .../primitive/BaseDateTimeDtDstu2Test.java | 101 ++++++++ .../fhir/dstu3/model/BaseDateTimeType.java | 72 ++++-- .../FhirInstanceValidatorDstu3Test.java | 24 ++ .../model/BaseDateTimeTypeDstu3Test.java | 231 ++++++++++++++++++ src/changes/changes.xml | 4 + 7 files changed, 471 insertions(+), 46 deletions(-) create mode 100644 hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/model/BaseDateTimeTypeDstu3Test.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java index e9ad33e3120..b926fccf496 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.TimeZone; import java.util.regex.Pattern; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.FastDateFormat; @@ -202,7 +203,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive { } /** - * Returns the TimeZone associated with this dateTime's value. May return null if no timezone was supplied. + * Returns the TimeZone associated with this dateTime's value. May return null if no timezone was + * supplied. */ public TimeZone getTimeZone() { return myTimeZone; @@ -304,18 +306,30 @@ public abstract class BaseDateTimeDt extends BasePrimitive { Date retVal; 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 { - if (hasOffset(theValue)) { - retVal = ourYearMonthDayTimeMilliZoneFormat.parse(theValue); - } else if (theValue.endsWith("Z")) { - retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue); + if (hasOffset(value)) { + retVal = ourYearMonthDayTimeMilliZoneFormat.parse(value); + } else if (value.endsWith("Z")) { + retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(value); } else { - retVal = ourYearMonthDayTimeMilliFormat.parse(theValue); + retVal = ourYearMonthDayTimeMilliFormat.parse(value); } } catch (ParseException p2) { throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue); } - setTimeZone(theValue, hasMillis); + setTimeZone(theValue); setPrecision(TemporalPrecisionEnum.MILLI); } else { try { @@ -330,7 +344,7 @@ public abstract class BaseDateTimeDt extends BasePrimitive { throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue); } - setTimeZone(theValue, hasMillis); + setTimeZone(theValue); setPrecision(TemporalPrecisionEnum.SECOND); } @@ -356,18 +370,34 @@ public abstract class BaseDateTimeDt extends BasePrimitive { updateStringValue(); } - private BaseDateTimeDt setTimeZone(String theValueString, boolean hasMillis) { - clearTimeZone(); - int timeZoneStart = 19; - if (hasMillis) - timeZoneStart += 4; - if (theValueString.endsWith("Z")) { - setTimeZoneZulu(true); - } 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))); + private int getOffsetIndex(String theValueString) { + int plusIndex = theValueString.indexOf('+', 19); + int minusIndex = theValueString.indexOf('-', 19); + int zIndex = theValueString.indexOf('Z', 19); + int retVal = Math.max(Math.max(plusIndex, minusIndex), zIndex); + if (retVal == -1) { + return -1; } + 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; } @@ -384,7 +414,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive { } /** - * 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. */ @Override @@ -394,7 +425,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive { } /** - * 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. * * @param theValue @@ -418,8 +450,10 @@ public abstract class BaseDateTimeDt extends BasePrimitive { /** * Returns a human readable version of this date/time using the system local format. *

- * Note on time zones: 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", - * 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 + * Note on time zones: 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", + * 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. *

*/ @@ -441,7 +475,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive { } /** - * 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. */ diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java index 0288ce9143c..e4f8fffce0b 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java @@ -124,7 +124,7 @@ public class TestRestfulServer extends RestfulServer { myAppCtx = new AnnotationConfigWebApplicationContext(); myAppCtx.setServletConfig(getServletConfig()); myAppCtx.setParent(parentAppCtx); - if ("TDL2".equals(fhirVersionParam.trim().toUpperCase())) { + if ("TDL3".equals(fhirVersionParam.trim().toUpperCase())) { myAppCtx.register(TdlDstu3Config.class); baseUrlProperty = FHIR_BASEURL_TDL3; } else { diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtDstu2Test.java index 372725b9953..134a2af210b 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtDstu2Test.java @@ -17,6 +17,7 @@ import org.junit.BeforeClass; import org.junit.Test; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.util.TestUtil; 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 */ diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java index 0d0531cd122..313a951d9c5 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java @@ -19,6 +19,7 @@ import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.FastDateFormat; +import ca.uhn.fhir.model.primitive.BaseDateTimeDt; import ca.uhn.fhir.parser.DataFormatException; public abstract class BaseDateTimeType extends PrimitiveType { @@ -332,12 +333,12 @@ public abstract class BaseDateTimeType extends PrimitiveType { } else if (theValue.length() >= 16) { // date and time with possible time zone char timeSeparator = theValue.charAt(10); 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(':'); 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; @@ -355,18 +356,30 @@ public abstract class BaseDateTimeType extends PrimitiveType { Date retVal; 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 { - if (hasOffset(theValue)) { - retVal = ourYearMonthDayTimeMilliZoneFormat.parse(theValue); - } else if (theValue.endsWith("Z")) { - retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue); + if (hasOffset(value)) { + retVal = ourYearMonthDayTimeMilliZoneFormat.parse(value); + } else if (value.endsWith("Z")) { + retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(value); } else { - retVal = ourYearMonthDayTimeMilliFormat.parse(theValue); + retVal = ourYearMonthDayTimeMilliFormat.parse(value); } } catch (ParseException p2) { throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue); } - setTimeZone(theValue, hasMillis); + setTimeZone(theValue); setPrecision(TemporalPrecisionEnum.MILLI); } else if (hasSeconds) { try { @@ -381,7 +394,7 @@ public abstract class BaseDateTimeType extends PrimitiveType { throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue); } - setTimeZone(theValue, hasMillis); + setTimeZone(theValue); setPrecision(TemporalPrecisionEnum.SECOND); } else { try { @@ -396,7 +409,7 @@ public abstract class BaseDateTimeType extends PrimitiveType { throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue, p2); } - setTimeZone(theValue, hasMillis); + setTimeZone(theValue); setPrecision(TemporalPrecisionEnum.MINUTE); } @@ -444,18 +457,35 @@ public abstract class BaseDateTimeType extends PrimitiveType { updateStringValue(); } - private void setTimeZone(String theValueString, boolean hasMillis) { - clearTimeZone(); - int timeZoneStart = 19; - if (hasMillis) - timeZoneStart += 4; - if (theValueString.endsWith("Z")) { - setTimeZoneZulu(true); - } 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))); + private int getOffsetIndex(String theValueString) { + int plusIndex = theValueString.indexOf('+', 19); + int minusIndex = theValueString.indexOf('-', 19); + int zIndex = theValueString.indexOf('Z'); + int retVal = Math.max(Math.max(plusIndex, minusIndex), zIndex); + if (retVal == -1) { + return -1; } + 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) { diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java index e3af8833f65..9ed2f2cf77f 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java @@ -332,6 +332,30 @@ public class FhirInstanceValidatorDstu3Test { assertEquals(output.toString(), 0, output.getMessages().size()); } + @Test + public void testValidateRawXmlWithMissingRootNamespace() { + //@formatter:off + String input = "" + + "" + + " " + + " " + + "
Some narrative
" + + "
" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + "
"; + //@formatter:on + + ValidationResult output = myVal.validateWithResult(input); + assertEquals(output.toString(), 1, output.getMessages().size()); + ourLog.info(output.getMessages().get(0).getLocationString()); + } + @Test public void testValidateRawJsonResourceBadAttributes() { //@formatter:off diff --git a/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/model/BaseDateTimeTypeDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/model/BaseDateTimeTypeDstu3Test.java new file mode 100644 index 00000000000..937b51e3840 --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/model/BaseDateTimeTypeDstu3Test.java @@ -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()); + } + +} diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 2fc1c955789..f3e3cd56aff 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -240,6 +240,10 @@ Web Testing UI was not able to correctly post an STU3 transaction + + DateTime parser incorrectly parsed times where more than 3 digits of + precision were provided on the seconds after the decimal point + From fd7f9a9b98403d5ced4125113382fcee34acb4bc Mon Sep 17 00:00:00 2001 From: James Agnew Date: Fri, 27 May 2016 10:27:32 -0400 Subject: [PATCH 2/4] Auto close contextx in JPA tests --- .../fhir/jpa/z/ZContextCloserDstu2Test.java | 20 +++++++++++++++++++ .../fhir/jpa/z/ZContextCloserDstu3Test.java | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/z/ZContextCloserDstu2Test.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/z/ZContextCloserDstu3Test.java diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/z/ZContextCloserDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/z/ZContextCloserDstu2Test.java new file mode 100644 index 00000000000..075a2cdb4af --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/z/ZContextCloserDstu2Test.java @@ -0,0 +1,20 @@ +package ca.uhn.fhir.jpa.z; + +import org.junit.Test; +import org.springframework.test.annotation.DirtiesContext; + +import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test; + +public class ZContextCloserDstu2Test extends BaseJpaDstu2Test { + + /** + * this is just here to close the context when this package's test are done + */ + @Test + @DirtiesContext() + public void testCloseContext() { + // nothing + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/z/ZContextCloserDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/z/ZContextCloserDstu3Test.java new file mode 100644 index 00000000000..3e364345517 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/z/ZContextCloserDstu3Test.java @@ -0,0 +1,20 @@ +package ca.uhn.fhir.jpa.z; + +import org.junit.Test; +import org.springframework.test.annotation.DirtiesContext; + +import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; + +public class ZContextCloserDstu3Test extends BaseJpaDstu3Test { + + /** + * this is just here to close the context when this package's test are done + */ + @Test + @DirtiesContext() + public void testCloseContext() { + // nothing + } + + +} From 6fcf2df8b4c4bacbfac9d9773eedbbd7277d5b7c Mon Sep 17 00:00:00 2001 From: James Agnew Date: Fri, 27 May 2016 10:38:35 -0400 Subject: [PATCH 3/4] Fix test --- .../src/test/java/ca/uhn/fhir/rest/server/ETagServerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ETagServerTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ETagServerTest.java index df061e18d91..091c5136364 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ETagServerTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ETagServerTest.java @@ -104,7 +104,7 @@ public class ETagServerTest { @Test public void testLastModifiedHeader() throws Exception { - ourLastModifiedDate = new InstantDt("2012-11-25T02:34:45.2222Z").getValue(); + ourLastModifiedDate = new InstantDt("2012-11-25T02:34:45.222Z").getValue(); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2/_history/3"); HttpResponse status = ourClient.execute(httpGet); @@ -118,7 +118,7 @@ public class ETagServerTest { Header cl = status.getFirstHeader(Constants.HEADER_LAST_MODIFIED_LOWERCASE); assertNotNull(cl); - assertEquals("Sun, 25 Nov 2012 02:34:47 GMT", cl.getValue()); + assertEquals("Sun, 25 Nov 2012 02:34:45 GMT", cl.getValue()); } finally { if (status.getEntity() != null) { IOUtils.closeQuietly(status.getEntity().getContent()); From 236dfe586fb0e4fdb667e6d3b9b57d755ec8bcd9 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Fri, 27 May 2016 10:50:31 -0400 Subject: [PATCH 4/4] Fix tests --- .../test/java/ca/uhn/fhir/rest/server/ETagServerHl7OrgTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ETagServerHl7OrgTest.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ETagServerHl7OrgTest.java index a40b1e4f29c..5d9e07a9404 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ETagServerHl7OrgTest.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ETagServerHl7OrgTest.java @@ -93,7 +93,7 @@ public class ETagServerHl7OrgTest { Header cl = status.getFirstHeader(Constants.HEADER_LAST_MODIFIED_LOWERCASE); assertNotNull(cl); - assertEquals("Sun, 25 Nov 2012 02:34:47 GMT", cl.getValue()); + assertEquals("Sun, 25 Nov 2012 02:34:45 GMT", cl.getValue()); } @Before