From 2f380704de013d146785f28df4ce9106aac4d2a5 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Fri, 18 Oct 2019 10:47:20 -0400
Subject: [PATCH] Fix precision and validator issues
---
.../fhir/dstu2/model/BaseDateTimeType.java | 69 +++++++------------
.../dstu2016may/model/BaseDateTimeType.java | 27 +-------
.../fhir/dstu3/model/BaseDateTimeType.java | 27 +-------
.../hl7/fhir/r4/model/BaseDateTimeType.java | 67 +++++++-----------
.../hl7/fhir/r5/elementmodel/JsonParser.java | 7 +-
.../hl7/fhir/r5/model/BaseDateTimeType.java | 27 +-------
.../fhir/r5/model/BaseDateTimeTypeTest.java | 15 ++++
.../org/hl7/fhir/utilities/DateTimeUtil.java | 44 ++++++++++++
.../org/hl7/fhir/utilities/Utilities.java | 5 ++
.../validation/tests/ValidationTestSuite.java | 2 +-
.../validation-examples/manifest.json | 6 ++
.../observation-with-trailing-dot.json | 28 ++++++++
12 files changed, 160 insertions(+), 164 deletions(-)
create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/DateTimeUtil.java
create mode 100644 org.hl7.fhir.validation/src/test/resources/validation-examples/observation-with-trailing-dot.json
diff --git a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/model/BaseDateTimeType.java b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/model/BaseDateTimeType.java
index 95e0777d5..20537690c 100644
--- a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/model/BaseDateTimeType.java
+++ b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/model/BaseDateTimeType.java
@@ -25,6 +25,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;
+import org.hl7.fhir.utilities.DateTimeUtil;
import java.text.ParseException;
import java.util.*;
@@ -472,53 +473,29 @@ public abstract class BaseDateTimeType extends PrimitiveType {
return getValue().after(theDateTimeType.getValue());
}
- /**
- * 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 {@link #toHumanDisplayLocalTimezone()}
- * instead.
- *
- */
- public String toHumanDisplay() {
- TimeZone tz = getTimeZone();
- Calendar value = tz != null ? Calendar.getInstance(tz) : Calendar.getInstance();
- value.setTime(getValue());
+ /**
+ * 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
+ * {@link #toHumanDisplayLocalTimezone()} instead.
+ *
+ */
+ public String toHumanDisplay() {
+ return DateTimeUtil.toHumanDisplay(getTimeZone(), getPrecision(), getValue(), getValueAsString());
+ }
- switch (getPrecision()) {
- case YEAR:
- case MONTH:
- case DAY:
- return ourHumanDateFormat.format(value);
- case MILLI:
- case SECOND:
- default:
- return ourHumanDateTimeFormat.format(value);
- }
- }
-
- /**
- * 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.
- */
- public String toHumanDisplayLocalTimezone() {
- switch (getPrecision()) {
- case YEAR:
- case MONTH:
- case DAY:
- return ourHumanDateFormat.format(getValue());
- case MILLI:
- case SECOND:
- default:
- return ourHumanDateTimeFormat.format(getValue());
- }
- }
+ /**
+ * 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.
+ */
+ public String toHumanDisplayLocalTimezone() {
+ return DateTimeUtil.toHumanDisplayLocalTimezone(getPrecision(), getValue(), getValueAsString());
+ }
/**
diff --git a/org.hl7.fhir.dstu2016may/src/main/java/org/hl7/fhir/dstu2016may/model/BaseDateTimeType.java b/org.hl7.fhir.dstu2016may/src/main/java/org/hl7/fhir/dstu2016may/model/BaseDateTimeType.java
index 46feca76f..d3029abcc 100644
--- a/org.hl7.fhir.dstu2016may/src/main/java/org/hl7/fhir/dstu2016may/model/BaseDateTimeType.java
+++ b/org.hl7.fhir.dstu2016may/src/main/java/org/hl7/fhir/dstu2016may/model/BaseDateTimeType.java
@@ -34,6 +34,7 @@ import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import ca.uhn.fhir.parser.DataFormatException;
+import org.hl7.fhir.utilities.DateTimeUtil;
public abstract class BaseDateTimeType extends PrimitiveType {
@@ -488,20 +489,7 @@ public abstract class BaseDateTimeType extends PrimitiveType {
*
*/
public String toHumanDisplay() {
- TimeZone tz = getTimeZone();
- Calendar value = tz != null ? Calendar.getInstance(tz) : Calendar.getInstance();
- value.setTime(getValue());
-
- switch (getPrecision()) {
- case YEAR:
- case MONTH:
- case DAY:
- return ourHumanDateFormat.format(value);
- case MILLI:
- case SECOND:
- default:
- return ourHumanDateTimeFormat.format(value);
- }
+ return DateTimeUtil.toHumanDisplay(getTimeZone(), getPrecision(), getValue(), getValueAsString());
}
/**
@@ -511,16 +499,7 @@ public abstract class BaseDateTimeType extends PrimitiveType {
* @see #toHumanDisplay() for a method which does not convert the time to the local timezone before rendering it.
*/
public String toHumanDisplayLocalTimezone() {
- switch (getPrecision()) {
- case YEAR:
- case MONTH:
- case DAY:
- return ourHumanDateFormat.format(getValue());
- case MILLI:
- case SECOND:
- default:
- return ourHumanDateTimeFormat.format(getValue());
- }
+ return DateTimeUtil.toHumanDisplayLocalTimezone(getPrecision(), getValue(), getValueAsString());
}
private void validateCharAtIndexIs(String theValue, int theIndex, char theChar) {
diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java
index 2e23ce8c8..eda2edd0a 100644
--- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java
+++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java
@@ -34,6 +34,7 @@ import org.apache.commons.lang3.time.FastDateFormat;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.parser.DataFormatException;
+import org.hl7.fhir.utilities.DateTimeUtil;
public abstract class BaseDateTimeType extends PrimitiveType {
@@ -781,20 +782,7 @@ public abstract class BaseDateTimeType extends PrimitiveType {
*
*/
public String toHumanDisplay() {
- TimeZone tz = getTimeZone();
- Calendar value = tz != null ? Calendar.getInstance(tz) : Calendar.getInstance();
- value.setTime(getValue());
-
- switch (getPrecision()) {
- case YEAR:
- case MONTH:
- case DAY:
- return ourHumanDateFormat.format(value);
- case MILLI:
- case SECOND:
- default:
- return ourHumanDateTimeFormat.format(value);
- }
+ return DateTimeUtil.toHumanDisplay(getTimeZone(), getPrecision(), getValue(), getValueAsString());
}
/**
@@ -804,16 +792,7 @@ public abstract class BaseDateTimeType extends PrimitiveType {
* @see #toHumanDisplay() for a method which does not convert the time to the local timezone before rendering it.
*/
public String toHumanDisplayLocalTimezone() {
- switch (getPrecision()) {
- case YEAR:
- case MONTH:
- case DAY:
- return ourHumanDateFormat.format(getValue());
- case MILLI:
- case SECOND:
- default:
- return ourHumanDateTimeFormat.format(getValue());
- }
+ return DateTimeUtil.toHumanDisplayLocalTimezone(getPrecision(), getValue(), getValueAsString());
}
private void validateBeforeOrAfter(DateTimeType theDateTimeType) {
diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java
index 715ac7720..b71c2b13c 100644
--- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java
+++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/BaseDateTimeType.java
@@ -34,6 +34,7 @@ import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import ca.uhn.fhir.parser.DataFormatException;
+import org.hl7.fhir.utilities.DateTimeUtil;
public abstract class BaseDateTimeType extends PrimitiveType {
@@ -771,51 +772,29 @@ public abstract class BaseDateTimeType extends PrimitiveType {
return retVal;
}
- /**
- * 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
- * {@link #toHumanDisplayLocalTimezone()} instead.
- *
- */
- public String toHumanDisplay() {
- TimeZone tz = getTimeZone();
- Calendar value = tz != null ? Calendar.getInstance(tz) : Calendar.getInstance();
- value.setTime(getValue());
+ /**
+ * 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
+ * {@link #toHumanDisplayLocalTimezone()} instead.
+ *
+ */
+ public String toHumanDisplay() {
+ return DateTimeUtil.toHumanDisplay(getTimeZone(), getPrecision(), getValue(), getValueAsString());
+ }
- switch (getPrecision()) {
- case YEAR:
- case MONTH:
- case DAY:
- return ourHumanDateFormat.format(value);
- case MILLI:
- case SECOND:
- default:
- return ourHumanDateTimeFormat.format(value);
- }
- }
-
- /**
- * 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.
- */
- public String toHumanDisplayLocalTimezone() {
- switch (getPrecision()) {
- case YEAR:
- case MONTH:
- case DAY:
- return ourHumanDateFormat.format(getValue());
- case MILLI:
- case SECOND:
- default:
- return ourHumanDateTimeFormat.format(getValue());
- }
- }
+ /**
+ * 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.
+ */
+ public String toHumanDisplayLocalTimezone() {
+ return DateTimeUtil.toHumanDisplayLocalTimezone(getPrecision(), getValue(), getValueAsString());
+ }
private void validateBeforeOrAfter(DateTimeType theDateTimeType) {
if (getValue() == null) {
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java
index 4fb4b1433..32215c6f4 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java
@@ -273,7 +273,12 @@ public class JsonParser extends ParserBase {
context.getChildren().add(n);
if (main != null) {
JsonPrimitive p = (JsonPrimitive) main;
- n.setValue(p.getAsString());
+ if (p.isNumber() && p.getAsNumber() instanceof JsonTrackingParser.PresentedBigDecimal) {
+ String rawValue = ((JsonTrackingParser.PresentedBigDecimal) p.getAsNumber()).getPresentation();
+ n.setValue(rawValue);
+ } else {
+ n.setValue(p.getAsString());
+ }
if (!n.getProperty().isChoice() && n.getType().equals("xhtml")) {
try {
n.setXhtml(new XhtmlParser().setValidatorMode(policy == ValidationPolicy.EVERYTHING).parse(n.getValue(), null).getDocumentElement());
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/BaseDateTimeType.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/BaseDateTimeType.java
index ff691cf83..25698ace9 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/BaseDateTimeType.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/BaseDateTimeType.java
@@ -26,6 +26,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;
+import org.hl7.fhir.utilities.DateTimeUtil;
import java.util.Calendar;
import java.util.Date;
@@ -781,20 +782,7 @@ public abstract class BaseDateTimeType extends PrimitiveType {
*
*/
public String toHumanDisplay() {
- TimeZone tz = getTimeZone();
- Calendar value = tz != null ? Calendar.getInstance(tz) : Calendar.getInstance();
- value.setTime(getValue());
-
- switch (getPrecision()) {
- case YEAR:
- case MONTH:
- case DAY:
- return ourHumanDateFormat.format(value);
- case MILLI:
- case SECOND:
- default:
- return ourHumanDateTimeFormat.format(value);
- }
+ return DateTimeUtil.toHumanDisplay(getTimeZone(), getPrecision(), getValue(), getValueAsString());
}
/**
@@ -804,16 +792,7 @@ public abstract class BaseDateTimeType extends PrimitiveType {
* @see #toHumanDisplay() for a method which does not convert the time to the local timezone before rendering it.
*/
public String toHumanDisplayLocalTimezone() {
- switch (getPrecision()) {
- case YEAR:
- case MONTH:
- case DAY:
- return ourHumanDateFormat.format(getValue());
- case MILLI:
- case SECOND:
- default:
- return ourHumanDateTimeFormat.format(getValue());
- }
+ return DateTimeUtil.toHumanDisplayLocalTimezone(getPrecision(), getValue(), getValueAsString());
}
private void validateBeforeOrAfter(DateTimeType theDateTimeType) {
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/model/BaseDateTimeTypeTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/model/BaseDateTimeTypeTest.java
index 6fc50b080..9b24998d0 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/model/BaseDateTimeTypeTest.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/model/BaseDateTimeTypeTest.java
@@ -78,6 +78,21 @@ public class BaseDateTimeTypeTest {
assertFalse(compareDateTimes("2016-12-02T13:00:00Z", "2016-12-02T10:00:00")); // no timezone, might be the same time
}
+ @Test
+ public void testToHumanDisplayForDateOnlyPrecisions() {
+ assertEquals("2019-01-02", new DateTimeType("2019-01-02").toHumanDisplay());
+ assertEquals("2019-01", new DateTimeType("2019-01").toHumanDisplay());
+ assertEquals("2019", new DateTimeType("2019").toHumanDisplay());
+ }
+
+ @Test
+ public void testToHumanDisplayLocalTimezoneForDateOnlyPrecisions() {
+ assertEquals("2019-01-02", new DateTimeType("2019-01-02").toHumanDisplayLocalTimezone());
+ assertEquals("2019-01", new DateTimeType("2019-01").toHumanDisplayLocalTimezone());
+ assertEquals("2019", new DateTimeType("2019").toHumanDisplayLocalTimezone());
+ }
+
+
private Boolean compareDateTimes(String theLeft, String theRight) {
System.out.println("Compare "+theLeft+" to "+theRight);
DateTimeType leftDt = new DateTimeType(theLeft);
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/DateTimeUtil.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/DateTimeUtil.java
new file mode 100644
index 000000000..fbc513049
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/DateTimeUtil.java
@@ -0,0 +1,44 @@
+package org.hl7.fhir.utilities;
+
+import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
+import org.apache.commons.lang3.time.FastDateFormat;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class DateTimeUtil {
+ private static final FastDateFormat ourHumanDateFormat = FastDateFormat.getDateInstance(FastDateFormat.MEDIUM);
+ private static final FastDateFormat ourHumanDateTimeFormat = FastDateFormat.getDateTimeInstance(FastDateFormat.MEDIUM, FastDateFormat.MEDIUM);
+
+
+ public static String toHumanDisplay(TimeZone theTimeZone, TemporalPrecisionEnum thePrecision, Date theValue, String theValueAsString) {
+ Calendar value = theTimeZone != null ? Calendar.getInstance(theTimeZone) : Calendar.getInstance();
+ value.setTime(theValue);
+
+ switch (thePrecision) {
+ case YEAR:
+ case MONTH:
+ case DAY:
+ return theValueAsString;
+ case MILLI:
+ case SECOND:
+ default:
+ return ourHumanDateTimeFormat.format(value);
+ }
+
+ }
+
+ public static String toHumanDisplayLocalTimezone(TemporalPrecisionEnum thePrecision, Date theValue, String theValueAsString) {
+ switch (thePrecision) {
+ case YEAR:
+ case MONTH:
+ case DAY:
+ return theValueAsString;
+ case MILLI:
+ case SECOND:
+ default:
+ return ourHumanDateTimeFormat.format(theValue);
+ }
+ }
+}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java
index 3c5ee8019..019eced4d 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java
@@ -179,6 +179,11 @@ public class Utilities {
if (value.startsWith("+0") && !"+0".equals(value) && !value.startsWith("+0."))
return DecimalStatus.SYNTAX;
}
+
+ // check for trailing dot
+ if (value.endsWith(".")) {
+ return DecimalStatus.SYNTAX;
+ }
boolean havePeriod = false;
boolean haveExponent = false;
diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java
index db6d0d100..732eff94c 100644
--- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java
+++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java
@@ -67,7 +67,7 @@ import com.google.gson.JsonObject;
public class ValidationTestSuite implements IEvaluationContext, IValidatorResourceFetcher {
@Parameters(name = "{index}: id {0}")
- public static Iterable