Modify patch for #655 so that it fixes #604 without causing us to allow

invalid values
This commit is contained in:
James Agnew 2017-06-09 16:30:20 -04:00
parent e1cfc8212e
commit 7588a2ecff
16 changed files with 411 additions and 398 deletions

View File

@ -84,9 +84,7 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
*/ */
public BaseDateTimeDt(String theString) { public BaseDateTimeDt(String theString) {
setValueAsString(theString); setValueAsString(theString);
if (isPrecisionAllowed(getPrecision()) == false) { validatePrecisionAndThrowDataFormatException(theString, getPrecision());
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + getPrecision() + " precision): " + theString);
}
} }
private void clearTimeZone() { private void clearTimeZone() {
@ -158,11 +156,74 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
return b.toString(); return b.toString();
} }
/**
* Returns the month with 1-index, e.g. 1=the first day of the month
*/
public Integer getDay() {
return getFieldValue(Calendar.DAY_OF_MONTH);
}
/** /**
* Returns the default precision for the given datatype * Returns the default precision for the given datatype
*/ */
protected abstract TemporalPrecisionEnum getDefaultPrecisionForDatatype(); protected abstract TemporalPrecisionEnum getDefaultPrecisionForDatatype();
private Integer getFieldValue(int theField) {
if (getValue() == null) {
return null;
}
Calendar cal = getValueAsCalendar();
return cal.get(theField);
}
/**
* Returns the hour of the day in a 24h clock, e.g. 13=1pm
*/
public Integer getHour() {
return getFieldValue(Calendar.HOUR_OF_DAY);
}
/**
* Returns the milliseconds within the current second.
* <p>
* Note that this method returns the
* same value as {@link #getNanos()} but with less precision.
* </p>
*/
public Integer getMillis() {
return getFieldValue(Calendar.MILLISECOND);
}
/**
* Returns the minute of the hour in the range 0-59
*/
public Integer getMinute() {
return getFieldValue(Calendar.MINUTE);
}
/**
* Returns the month with 0-index, e.g. 0=January
*/
public Integer getMonth() {
return getFieldValue(Calendar.MONTH);
}
/**
* Returns the nanoseconds within the current second
* <p>
* Note that this method returns the
* same value as {@link #getMillis()} but with more precision.
* </p>
*/
public Long getNanos() {
if (isBlank(myFractionalSeconds)) {
return null;
}
String retVal = StringUtils.rightPad(myFractionalSeconds, 9, '0');
retVal = retVal.substring(0, 9);
return Long.parseLong(retVal);
}
private int getOffsetIndex(String theValueString) { private int getOffsetIndex(String theValueString) {
int plusIndex = theValueString.indexOf('+', 16); int plusIndex = theValueString.indexOf('+', 16);
int minusIndex = theValueString.indexOf('-', 16); int minusIndex = theValueString.indexOf('-', 16);
@ -189,6 +250,13 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
return myPrecision; return myPrecision;
} }
/**
* Returns the second of the minute in the range 0-59
*/
public Integer getSecond() {
return getFieldValue(Calendar.SECOND);
}
/** /**
* Returns the TimeZone associated with this dateTime's value. May return <code>null</code> if no timezone was * Returns the TimeZone associated with this dateTime's value. May return <code>null</code> if no timezone was
* supplied. * supplied.
@ -217,10 +285,17 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
return cal; return cal;
} }
/**
* Returns the year, e.g. 2015
*/
public Integer getYear() {
return getFieldValue(Calendar.YEAR);
}
/** /**
* To be implemented by subclasses to indicate whether the given precision is allowed by this type * To be implemented by subclasses to indicate whether the given precision is allowed by this type
*/ */
abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision); protected abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision);
/** /**
* Returns true if the timezone is set to GMT-0:00 (Z) * Returns true if the timezone is set to GMT-0:00 (Z)
@ -352,6 +427,10 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
myFractionalSeconds = ""; myFractionalSeconds = "";
} }
if (precision == TemporalPrecisionEnum.MINUTE) {
validatePrecisionAndThrowDataFormatException(value, precision);
}
setPrecision(precision); setPrecision(precision);
return cal.getTime(); return cal.getTime();
@ -372,6 +451,92 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
return retVal; return retVal;
} }
/**
* Sets the month with 1-index, e.g. 1=the first day of the month
*/
public BaseDateTimeDt setDay(int theDay) {
setFieldValue(Calendar.DAY_OF_MONTH, theDay, null, 0, 31);
return this;
}
private void setFieldValue(int theField, int theValue, String theFractionalSeconds, int theMinimum, int theMaximum) {
validateValueInRange(theValue, theMinimum, theMaximum);
Calendar cal;
if (getValue() == null) {
cal = new GregorianCalendar(0, 0, 0);
} else {
cal = getValueAsCalendar();
}
if (theField != -1) {
cal.set(theField, theValue);
}
if (theFractionalSeconds != null) {
myFractionalSeconds = theFractionalSeconds;
} else if (theField == Calendar.MILLISECOND) {
myFractionalSeconds = StringUtils.leftPad(Integer.toString(theValue), 3, '0');
}
super.setValue(cal.getTime());
}
/**
* Sets the hour of the day in a 24h clock, e.g. 13=1pm
*/
public BaseDateTimeDt setHour(int theHour) {
setFieldValue(Calendar.HOUR_OF_DAY, theHour, null, 0, 23);
return this;
}
/**
* Sets the milliseconds within the current second.
* <p>
* Note that this method sets the
* same value as {@link #setNanos(long)} but with less precision.
* </p>
*/
public BaseDateTimeDt setMillis(int theMillis) {
setFieldValue(Calendar.MILLISECOND, theMillis, null, 0, 999);
return this;
}
/**
* Sets the minute of the hour in the range 0-59
*/
public BaseDateTimeDt setMinute(int theMinute) {
setFieldValue(Calendar.MINUTE, theMinute, null, 0, 59);
return this;
}
/**
* Sets the month with 0-index, e.g. 0=January
*/
public BaseDateTimeDt setMonth(int theMonth) {
setFieldValue(Calendar.MONTH, theMonth, null, 0, 11);
return this;
}
/**
* Sets the nanoseconds within the current second
* <p>
* Note that this method sets the
* same value as {@link #setMillis(int)} but with more precision.
* </p>
*/
public BaseDateTimeDt setNanos(long theNanos) {
validateValueInRange(theNanos, 0, NANOS_PER_SECOND-1);
String fractionalSeconds = StringUtils.leftPad(Long.toString(theNanos), 9, '0');
// Strip trailing 0s
for (int i = fractionalSeconds.length(); i > 0; i--) {
if (fractionalSeconds.charAt(i-1) != '0') {
fractionalSeconds = fractionalSeconds.substring(0, i);
break;
}
}
int millis = (int)(theNanos / NANOS_PER_MILLIS);
setFieldValue(Calendar.MILLISECOND, millis, fractionalSeconds, 0, 999);
return this;
}
/** /**
* Sets the precision for this datatype * Sets the precision for this datatype
* *
@ -386,6 +551,14 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
return this; return this;
} }
/**
* Sets the second of the minute in the range 0-59
*/
public BaseDateTimeDt setSecond(int theSecond) {
setFieldValue(Calendar.SECOND, theSecond, null, 0, 59);
return this;
}
private BaseDateTimeDt setTimeZone(String theWholeValue, String theValue) { private BaseDateTimeDt setTimeZone(String theWholeValue, String theValue) {
if (isBlank(theValue)) { if (isBlank(theValue)) {
@ -465,6 +638,14 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
super.setValueAsString(theValue); super.setValueAsString(theValue);
} }
/**
* Sets the year, e.g. 2015
*/
public BaseDateTimeDt setYear(int theYear) {
setFieldValue(Calendar.YEAR, theYear, null, 0, 9999);
return this;
}
private void throwBadDateFormat(String theValue) { private void throwBadDateFormat(String theValue) {
throw new DataFormatException("Invalid date/time format: \"" + theValue + "\""); throw new DataFormatException("Invalid date/time format: \"" + theValue + "\"");
} }
@ -531,189 +712,16 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
} }
} }
/**
* Returns the year, e.g. 2015
*/
public Integer getYear() {
return getFieldValue(Calendar.YEAR);
}
/**
* Returns the month with 0-index, e.g. 0=January
*/
public Integer getMonth() {
return getFieldValue(Calendar.MONTH);
}
/**
* Returns the month with 1-index, e.g. 1=the first day of the month
*/
public Integer getDay() {
return getFieldValue(Calendar.DAY_OF_MONTH);
}
/**
* Returns the hour of the day in a 24h clock, e.g. 13=1pm
*/
public Integer getHour() {
return getFieldValue(Calendar.HOUR_OF_DAY);
}
/**
* Returns the minute of the hour in the range 0-59
*/
public Integer getMinute() {
return getFieldValue(Calendar.MINUTE);
}
/**
* Returns the second of the minute in the range 0-59
*/
public Integer getSecond() {
return getFieldValue(Calendar.SECOND);
}
/**
* Returns the milliseconds within the current second.
* <p>
* Note that this method returns the
* same value as {@link #getNanos()} but with less precision.
* </p>
*/
public Integer getMillis() {
return getFieldValue(Calendar.MILLISECOND);
}
/**
* Returns the nanoseconds within the current second
* <p>
* Note that this method returns the
* same value as {@link #getMillis()} but with more precision.
* </p>
*/
public Long getNanos() {
if (isBlank(myFractionalSeconds)) {
return null;
}
String retVal = StringUtils.rightPad(myFractionalSeconds, 9, '0');
retVal = retVal.substring(0, 9);
return Long.parseLong(retVal);
}
/**
* Sets the year, e.g. 2015
*/
public BaseDateTimeDt setYear(int theYear) {
setFieldValue(Calendar.YEAR, theYear, null, 0, 9999);
return this;
}
/**
* Sets the month with 0-index, e.g. 0=January
*/
public BaseDateTimeDt setMonth(int theMonth) {
setFieldValue(Calendar.MONTH, theMonth, null, 0, 11);
return this;
}
/**
* Sets the month with 1-index, e.g. 1=the first day of the month
*/
public BaseDateTimeDt setDay(int theDay) {
setFieldValue(Calendar.DAY_OF_MONTH, theDay, null, 0, 31);
return this;
}
/**
* Sets the hour of the day in a 24h clock, e.g. 13=1pm
*/
public BaseDateTimeDt setHour(int theHour) {
setFieldValue(Calendar.HOUR_OF_DAY, theHour, null, 0, 23);
return this;
}
/**
* Sets the minute of the hour in the range 0-59
*/
public BaseDateTimeDt setMinute(int theMinute) {
setFieldValue(Calendar.MINUTE, theMinute, null, 0, 59);
return this;
}
/**
* Sets the second of the minute in the range 0-59
*/
public BaseDateTimeDt setSecond(int theSecond) {
setFieldValue(Calendar.SECOND, theSecond, null, 0, 59);
return this;
}
/**
* Sets the milliseconds within the current second.
* <p>
* Note that this method sets the
* same value as {@link #setNanos(long)} but with less precision.
* </p>
*/
public BaseDateTimeDt setMillis(int theMillis) {
setFieldValue(Calendar.MILLISECOND, theMillis, null, 0, 999);
return this;
}
/**
* Sets the nanoseconds within the current second
* <p>
* Note that this method sets the
* same value as {@link #setMillis(int)} but with more precision.
* </p>
*/
public BaseDateTimeDt setNanos(long theNanos) {
validateValueInRange(theNanos, 0, NANOS_PER_SECOND-1);
String fractionalSeconds = StringUtils.leftPad(Long.toString(theNanos), 9, '0');
// Strip trailing 0s
for (int i = fractionalSeconds.length(); i > 0; i--) {
if (fractionalSeconds.charAt(i-1) != '0') {
fractionalSeconds = fractionalSeconds.substring(0, i);
break;
}
}
int millis = (int)(theNanos / NANOS_PER_MILLIS);
setFieldValue(Calendar.MILLISECOND, millis, fractionalSeconds, 0, 999);
return this;
}
private void setFieldValue(int theField, int theValue, String theFractionalSeconds, int theMinimum, int theMaximum) {
validateValueInRange(theValue, theMinimum, theMaximum);
Calendar cal;
if (getValue() == null) {
cal = new GregorianCalendar(0, 0, 0);
} else {
cal = getValueAsCalendar();
}
if (theField != -1) {
cal.set(theField, theValue);
}
if (theFractionalSeconds != null) {
myFractionalSeconds = theFractionalSeconds;
} else if (theField == Calendar.MILLISECOND) {
myFractionalSeconds = StringUtils.leftPad(Integer.toString(theValue), 3, '0');
}
super.setValue(cal.getTime());
}
private void validateValueInRange(long theValue, long theMinimum, long theMaximum) { private void validateValueInRange(long theValue, long theMinimum, long theMaximum) {
if (theValue < theMinimum || theValue > theMaximum) { if (theValue < theMinimum || theValue > theMaximum) {
throw new IllegalArgumentException("Value " + theValue + " is not between allowable range: " + theMinimum + " - " + theMaximum); throw new IllegalArgumentException("Value " + theValue + " is not between allowable range: " + theMinimum + " - " + theMaximum);
} }
} }
private Integer getFieldValue(int theField) { private void validatePrecisionAndThrowDataFormatException(String theValue, TemporalPrecisionEnum thePrecision) {
if (getValue() == null) { if (isPrecisionAllowed(thePrecision) == false) {
return null; throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theValue);
} }
Calendar cal = getValueAsCalendar();
return cal.get(theField);
} }
} }

View File

@ -133,7 +133,7 @@ public class DateDt extends BaseDateTimeDt {
} }
@Override @Override
boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { protected boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) {
switch (thePrecision) { switch (thePrecision) {
case YEAR: case YEAR:
case MONTH: case MONTH:

View File

@ -105,7 +105,7 @@ public class DateTimeDt extends BaseDateTimeDt {
} }
@Override @Override
boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { protected boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) {
switch (thePrecision) { switch (thePrecision) {
case YEAR: case YEAR:
case MONTH: case MONTH:

View File

@ -157,7 +157,7 @@ public class InstantDt extends BaseDateTimeDt {
} }
@Override @Override
boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { protected boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) {
switch (thePrecision) { switch (thePrecision) {
case SECOND: case SECOND:
case MILLI: case MILLI:

View File

@ -34,17 +34,18 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.primitive.DateDt; import ca.uhn.fhir.model.primitive.*;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.method.QualifiedParamList;
import ca.uhn.fhir.rest.param.DateParam.DateParamDateTimeHolder;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQueryParameterType , */IQueryParameterOr<DateParam> { public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQueryParameterType , */IQueryParameterOr<DateParam> {
private final DateTimeDt myValue = new DateTimeDt(); private static final long serialVersionUID = 1L;
private final DateParamDateTimeHolder myValue = new DateParamDateTimeHolder();
/** /**
* Constructor * Constructor
@ -223,6 +224,11 @@ public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQuer
return null; return null;
} }
@Override
public List<DateParam> getValuesAsQueryTokens() {
return Collections.singletonList(this);
}
/** /**
* Returns <code>true</code> if no date/time is specified. Note that this method does not check the comparator, so a * Returns <code>true</code> if no date/time is specified. Note that this method does not check the comparator, so a
* QualifiedDateParam with only a comparator and no date/time is considered empty. * QualifiedDateParam with only a comparator and no date/time is considered empty.
@ -267,6 +273,20 @@ public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQuer
} }
} }
@Override
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters) {
setMissing(null);
setPrefix(null);
setValueAsString(null);
if (theParameters.size() == 1) {
setValueAsString(theParameters.get(0));
} else if (theParameters.size() > 1) {
throw new InvalidRequestException("This server does not support multi-valued dates for this paramater: " + theParameters);
}
}
private ParamPrefixEnum toPrefix(QuantityCompararatorEnum theComparator) { private ParamPrefixEnum toPrefix(QuantityCompararatorEnum theComparator) {
if (theComparator != null) { if (theComparator != null) {
return ParamPrefixEnum.forDstu1Value(theComparator.getCode()); return ParamPrefixEnum.forDstu1Value(theComparator.getCode());
@ -282,24 +302,18 @@ public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQuer
return b.build(); return b.build();
} }
@Override public class DateParamDateTimeHolder extends BaseDateTimeDt {
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters) { @Override
setMissing(null); protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() {
setPrefix(null); return TemporalPrecisionEnum.SECOND;
setValueAsString(null); }
if (theParameters.size() == 1) { @Override
setValueAsString(theParameters.get(0)); protected boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) {
} else if (theParameters.size() > 1) { return true;
throw new InvalidRequestException("This server does not support multi-valued dates for this paramater: " + theParameters);
} }
} }
@Override
public List<DateParam> getValuesAsQueryTokens() {
return Collections.singletonList(this);
}
} }

View File

@ -44,9 +44,9 @@ public class BaseDateTimeDtDstu2Test {
InstantDt type = (InstantDt) new InstantDt(from).setTimeZoneZulu(true); InstantDt type = (InstantDt) new InstantDt(from).setTimeZoneZulu(true);
String encoded = type.getValueAsString(); String encoded = type.getValueAsString();
ourLog.info("LDT: " + ldt.toString()); ourLog.info("LDT: "+ ldt.toString());
ourLog.info("Expected: " + "1960-09-07T00:44:25.012"); ourLog.info("Expected: "+"1960-09-07T00:44:25.012");
ourLog.info("Actual: " + encoded); ourLog.info("Actual: "+encoded);
assertEquals("1960-09-07T00:44:25.012Z", encoded); assertEquals("1960-09-07T00:44:25.012Z", encoded);
@ -84,6 +84,7 @@ public class BaseDateTimeDtDstu2Test {
} }
private void validate(long millis, String expected) { private void validate(long millis, String expected) {
InstantDt dt; InstantDt dt;
dt = new InstantDt(new Date(millis)); dt = new InstantDt(new Date(millis));
@ -99,6 +100,7 @@ public class BaseDateTimeDtDstu2Test {
assertEquals(expected.replace("Z", "+00:00"), dt.getValueAsString()); assertEquals(expected.replace("Z", "+00:00"), dt.getValueAsString());
} }
@Test @Test
public void testSetPartialsYearFromExisting() { public void testSetPartialsYearFromExisting() {
InstantDt dt = new InstantDt("2011-03-11T15:44:13.27564757855254768473697463986328969635-08:00"); InstantDt dt = new InstantDt("2011-03-11T15:44:13.27564757855254768473697463986328969635-08:00");
@ -260,6 +262,7 @@ public class BaseDateTimeDtDstu2Test {
verifyFails("1974-A2-25"); verifyFails("1974-A2-25");
verifyFails("1974-12-A5"); verifyFails("1974-12-A5");
// Date shouldn't have a time zone // Date shouldn't have a time zone
verifyFails("1974-12-25Z"); verifyFails("1974-12-25Z");
verifyFails("1974-12-25+10:00"); verifyFails("1974-12-25+10:00");
@ -567,26 +570,25 @@ public class BaseDateTimeDtDstu2Test {
} }
@Test @Test
public void testParseMinute() throws DataFormatException { public void testParseMinuteShouldFail() throws DataFormatException {
DateTimeDt dt = new DateTimeDt(); DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013-02-03T11:22"); try {
dt.setValueAsString("2013-02-03T11:22");
assertEquals("2013-02-03 11:22", myDateInstantParser.format(dt.getValue()).substring(0, 16)); fail();
assertEquals("2013-02-03T11:22", dt.getValueAsString()); } catch (DataFormatException e) {
assertEquals(false, dt.isTimeZoneZulu()); assertEquals(e.getMessage(), "Invalid date/time string (datatype DateTimeDt does not support MINUTE precision): 2013-02-03T11:22");
assertNull(dt.getTimeZone()); }
assertEquals(TemporalPrecisionEnum.MINUTE, dt.getPrecision());
} }
@Test @Test
public void testParseMinuteZulu() throws DataFormatException { public void testParseMinuteZuluShouldFail() throws DataFormatException {
DateTimeDt dt = new DateTimeDt(); DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013-02-03T11:22Z"); try {
dt.setValueAsString("2013-02-03T11:22Z");
assertEquals("2013-02-03T11:22Z", dt.getValueAsString()); fail();
assertEquals(true, dt.isTimeZoneZulu()); } catch (DataFormatException e) {
assertEquals("GMT", dt.getTimeZone().getID()); assertEquals(e.getMessage(), "Invalid date/time string (datatype DateTimeDt does not support MINUTE precision): 2013-02-03T11:22Z");
assertEquals(TemporalPrecisionEnum.MINUTE, dt.getPrecision()); }
} }
@Test @Test

View File

@ -1,6 +1,10 @@
package ca.uhn.fhir.rest.param; package ca.uhn.fhir.rest.param;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.Date; import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
@ -19,15 +23,15 @@ public class DateParamTest {
public void testConstructors() { public void testConstructors() {
new DateParam(); new DateParam();
new DateParam("2011-01-02"); new DateParam("2011-01-02");
new DateParam(ParamPrefixEnum.GREATERTHAN,new Date()); new DateParam(ParamPrefixEnum.GREATERTHAN, new Date());
new DateParam(ParamPrefixEnum.GREATERTHAN,new DateTimeDt("2011-01-02")); new DateParam(ParamPrefixEnum.GREATERTHAN, new DateTimeDt("2011-01-02"));
new DateParam(ParamPrefixEnum.GREATERTHAN,InstantDt.withCurrentTime()); new DateParam(ParamPrefixEnum.GREATERTHAN, InstantDt.withCurrentTime());
new DateParam(ParamPrefixEnum.GREATERTHAN,"2011-01-02"); new DateParam(ParamPrefixEnum.GREATERTHAN, "2011-01-02");
new DateParam(QuantityCompararatorEnum.GREATERTHAN,new Date()); new DateParam(QuantityCompararatorEnum.GREATERTHAN, new Date());
new DateParam(QuantityCompararatorEnum.GREATERTHAN,new DateTimeDt("2011-01-02")); new DateParam(QuantityCompararatorEnum.GREATERTHAN, new DateTimeDt("2011-01-02"));
new DateParam(QuantityCompararatorEnum.GREATERTHAN,InstantDt.withCurrentTime()); new DateParam(QuantityCompararatorEnum.GREATERTHAN, InstantDt.withCurrentTime());
new DateParam(QuantityCompararatorEnum.GREATERTHAN,"2011-01-02"); new DateParam(QuantityCompararatorEnum.GREATERTHAN, "2011-01-02");
} }
@Test @Test
@ -48,4 +52,56 @@ public class DateParamTest {
assertEquals("2016-06-09T21:38:14.591-04:00", dt.getValueAsString()); assertEquals("2016-06-09T21:38:14.591-04:00", dt.getValueAsString());
} }
@Test
public void testParseMinutePrecision() {
DateParam param = new DateParam();
param.setValueAsString("2016-06-09T20:38Z");
assertEquals(null, param.getPrefix());
assertEquals("2016-06-09T20:38Z", param.getValueAsString());
ourLog.info("PRE: " + param.getValue());
ourLog.info("PRE: " + param.getValue().getTime());
InstantDt dt = new InstantDt(new Date(param.getValue().getTime()));
dt.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
ourLog.info("POST: " + dt.getValue());
assertEquals("2016-06-09T16:38:00.000-04:00", dt.getValueAsString());
}
@Test
public void testParseMinutePrecisionWithoutTimezone() {
DateParam param = new DateParam();
param.setValueAsString("2016-06-09T20:38");
assertEquals(null, param.getPrefix());
assertEquals("2016-06-09T20:38", param.getValueAsString());
ourLog.info("PRE: " + param.getValue());
ourLog.info("PRE: " + param.getValue().getTime());
InstantDt dt = new InstantDt(new Date(param.getValue().getTime()));
dt.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
ourLog.info("POST: " + dt.getValue());
assertThat(dt.getValueAsString(), startsWith("2016-06-09T"));
assertThat(dt.getValueAsString(), endsWith(":38:00.000-04:00"));
}
@Test
public void testParseMinutePrecisionWithPrefix() {
DateParam param = new DateParam();
param.setValueAsString("gt2016-06-09T20:38Z");
assertEquals(ParamPrefixEnum.GREATERTHAN, param.getPrefix());
assertEquals("2016-06-09T20:38Z", param.getValueAsString());
ourLog.info("PRE: " + param.getValue());
ourLog.info("PRE: " + param.getValue().getTime());
InstantDt dt = new InstantDt(new Date(param.getValue().getTime()));
dt.setTimeZone(TimeZone.getTimeZone("America/Toronto"));
ourLog.info("POST: " + dt.getValue());
assertEquals("2016-06-09T16:38:00.000-04:00", dt.getValueAsString());
}
} }

View File

@ -12,6 +12,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.api.TemporalPrecisionEnum;
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> {
@ -44,9 +45,7 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
*/ */
public BaseDateTimeType(Date theDate, TemporalPrecisionEnum thePrecision) { public BaseDateTimeType(Date theDate, TemporalPrecisionEnum thePrecision) {
setValue(theDate, thePrecision); setValue(theDate, thePrecision);
if (isPrecisionAllowed(thePrecision) == false) { validatePrecisionAndThrowDataFormatException(getValueAsString(), getPrecision());
throw new IllegalArgumentException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theDate);
}
} }
/** /**
@ -55,6 +54,7 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
public BaseDateTimeType(Date theDate, TemporalPrecisionEnum thePrecision, TimeZone theTimeZone) { public BaseDateTimeType(Date theDate, TemporalPrecisionEnum thePrecision, TimeZone theTimeZone) {
this(theDate, thePrecision); this(theDate, thePrecision);
setTimeZone(theTimeZone); setTimeZone(theTimeZone);
validatePrecisionAndThrowDataFormatException(getValueAsString(), getPrecision());
} }
/** /**
@ -65,12 +65,10 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
*/ */
public BaseDateTimeType(String theString) { public BaseDateTimeType(String theString) {
setValueAsString(theString); setValueAsString(theString);
if (isPrecisionAllowed(getPrecision()) == false) { validatePrecisionAndThrowDataFormatException(theString, getPrecision());
throw new IllegalArgumentException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + getPrecision() + " precision): " + theString);
}
} }
/** /**
* Adds the given amount to the field specified by theField * Adds the given amount to the field specified by theField
* *
* @param theField * @param theField
@ -307,7 +305,7 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
*/ */
public TimeZone getTimeZone() { public TimeZone getTimeZone() {
if (myTimeZoneZulu) { if (myTimeZoneZulu) {
return TimeZone.getTimeZone("GMT"); return TimeZone.getTimeZone("Z");
} }
return myTimeZone; return myTimeZone;
} }
@ -471,6 +469,10 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
myFractionalSeconds = ""; myFractionalSeconds = "";
} }
if (precision == TemporalPrecisionEnum.MINUTE) {
validatePrecisionAndThrowDataFormatException(value, precision);
}
myPrecision = precision; myPrecision = precision;
return cal.getTime(); return cal.getTime();
@ -818,6 +820,12 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
} }
} }
private void validatePrecisionAndThrowDataFormatException(String theValue, TemporalPrecisionEnum thePrecision) {
if (isPrecisionAllowed(thePrecision) == false) {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theValue);
}
}
private void validateValueInRange(long theValue, long theMinimum, long theMaximum) { private void validateValueInRange(long theValue, long theMinimum, long theMaximum) {
if (theValue < theMinimum || theValue > theMaximum) { if (theValue < theMinimum || theValue > theMaximum) {
throw new IllegalArgumentException("Value " + theValue + " is not between allowable range: " + theMinimum + " - " + theMaximum); throw new IllegalArgumentException("Value " + theValue + " is not between allowable range: " + theMinimum + " - " + theMaximum);

View File

@ -36,6 +36,7 @@ import java.util.zip.DataFormatException;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.DatatypeDef;
/** /**

View File

@ -29,18 +29,14 @@ POSSIBILITY OF SUCH DAMAGE.
package org.hl7.fhir.dstu3.model; package org.hl7.fhir.dstu3.model;
import java.util.Calendar;
/** /**
* Primitive type "date" in FHIR: any day in a gregorian calendar * Primitive type "date" in FHIR: any day in a gregorian calendar
*/ */
import java.util.*;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.DatatypeDef;
/** /**

View File

@ -31,11 +31,10 @@ POSSIBILITY OF SUCH DAMAGE.
*/ */
package org.hl7.fhir.dstu3.model; package org.hl7.fhir.dstu3.model;
import java.util.Calendar; import java.util.*;
import java.util.Date;
import java.util.TimeZone;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.DatatypeDef;
/** /**

View File

@ -30,16 +30,14 @@ package org.hl7.fhir.dstu3.model;
*/ */
// Generated on Mon, Apr 17, 2017 17:38-0400 for FHIR v3.0.1 // Generated on Mon, Apr 17, 2017 17:38-0400 for FHIR v3.0.1
import java.util.Date;
import java.util.List;
import java.util.*;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.Block;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.ICompositeType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.api.annotation.*;
/** /**
* A time period defined by a start and end date and optionally time. * A time period defined by a start and end date and optionally time.
*/ */

View File

@ -1,62 +0,0 @@
package org.hl7.fhir.dstu3.model;
import java.util.Calendar;
import java.util.Date;
import org.apache.commons.lang3.time.DateUtils;
public enum TemporalPrecisionEnum {
YEAR(Calendar.YEAR) {
@Override
public Date add(Date theInput, int theAmount) {
return DateUtils.addYears(theInput, theAmount);
}
},
MONTH(Calendar.MONTH) {
@Override
public Date add(Date theInput, int theAmount) {
return DateUtils.addMonths(theInput, theAmount);
}
},
DAY(Calendar.DATE) {
@Override
public Date add(Date theInput, int theAmount) {
return DateUtils.addDays(theInput, theAmount);
}
},
MINUTE(Calendar.MINUTE) {
@Override
public Date add(Date theInput, int theAmount) {
return DateUtils.addMinutes(theInput, theAmount);
}
},
SECOND(Calendar.SECOND) {
@Override
public Date add(Date theInput, int theAmount) {
return DateUtils.addSeconds(theInput, theAmount);
}
},
MILLI(Calendar.MILLISECOND) {
@Override
public Date add(Date theInput, int theAmount) {
return DateUtils.addMilliseconds(theInput, theAmount);
}
},
;
private int myCalendarConstant;
TemporalPrecisionEnum(int theCalendarConstant) {
myCalendarConstant = theCalendarConstant;
}
public abstract Date add(Date theInput, int theAmount);
public int getCalendarConstant() {
return myCalendarConstant;
}
}

View File

@ -1,49 +1,22 @@
package org.hl7.fhir.dstu3.utils; package org.hl7.fhir.dstu3.utils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.*;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.dstu3.context.IWorkerContext; import org.hl7.fhir.dstu3.context.IWorkerContext;
import org.hl7.fhir.dstu3.model.Base; import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.DecimalType;
import org.hl7.fhir.dstu3.model.ElementDefinition;
import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.dstu3.model.ExpressionNode; import org.hl7.fhir.dstu3.model.ExpressionNode.*;
import org.hl7.fhir.dstu3.model.ExpressionNode.CollectionStatus;
import org.hl7.fhir.dstu3.model.ExpressionNode.Function;
import org.hl7.fhir.dstu3.model.ExpressionNode.Kind;
import org.hl7.fhir.dstu3.model.ExpressionNode.Operation;
import org.hl7.fhir.dstu3.model.ExpressionNode.SourceLocation;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.Property;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule; import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.dstu3.model.TemporalPrecisionEnum;
import org.hl7.fhir.dstu3.model.TimeType;
import org.hl7.fhir.dstu3.model.TypeDetails;
import org.hl7.fhir.dstu3.model.TypeDetails.ProfiledType; import org.hl7.fhir.dstu3.model.TypeDetails.ProfiledType;
import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException; import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException;
import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails;
import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.exceptions.UcumException;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.ucum.Decimal; import org.hl7.fhir.utilities.ucum.Decimal;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.ElementUtil;

View File

@ -1,28 +1,30 @@
package org.hl7.fhir.dstu3.model; package org.hl7.fhir.dstu3.model;
import ca.uhn.fhir.context.FhirContext; import static org.hamcrest.Matchers.containsString;
import ca.uhn.fhir.parser.DataFormatException; import static org.hamcrest.Matchers.either;
import ca.uhn.fhir.util.TestUtil; import static org.hamcrest.Matchers.endsWith;
import ca.uhn.fhir.validation.ValidationResult; import static org.junit.Assert.assertEquals;
import org.apache.commons.lang3.time.FastDateFormat; import static org.junit.Assert.assertFalse;
import org.hamcrest.Matchers; import static org.junit.Assert.assertNotNull;
import org.junit.AfterClass; import static org.junit.Assert.assertNull;
import org.junit.Assert; import static org.junit.Assert.assertThat;
import org.junit.Before; import static org.junit.Assert.assertTrue;
import org.junit.BeforeClass; import static org.junit.Assert.fail;
import org.junit.Test;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.Calendar; import java.util.*;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import static org.hamcrest.Matchers.*; import org.apache.commons.lang3.time.FastDateFormat;
import static org.junit.Assert.*; import org.hamcrest.Matchers;
import org.junit.*;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.ValidationResult;
public class BaseDateTimeTypeDstu3Test { public class BaseDateTimeTypeDstu3Test {
private static FhirContext ourCtx = FhirContext.forDstu3(); private static FhirContext ourCtx = FhirContext.forDstu3();
@ -60,6 +62,29 @@ public class BaseDateTimeTypeDstu3Test {
assertFalse(new DateTimeType("2011-01-01T12:12:12Z").before(new DateTimeType("2011-01-01T12:12:12Z"))); assertFalse(new DateTimeType("2011-01-01T12:12:12Z").before(new DateTimeType("2011-01-01T12:12:12Z")));
} }
@Test
public void testParseMinuteShouldFail() throws DataFormatException {
DateTimeType dt = new DateTimeType();
try {
dt.setValueAsString("2013-02-03T11:22");
fail();
} catch (DataFormatException e) {
assertEquals(e.getMessage(), "Invalid date/time string (datatype DateTimeType does not support MINUTE precision): 2013-02-03T11:22");
}
}
@Test
public void testParseMinuteZuluShouldFail() throws DataFormatException {
DateTimeType dt = new DateTimeType();
try {
dt.setValueAsString("2013-02-03T11:22Z");
fail();
} catch (DataFormatException e) {
assertEquals(e.getMessage(), "Invalid date/time string (datatype DateTimeType does not support MINUTE precision): 2013-02-03T11:22Z");
}
}
@Test() @Test()
public void testAfterNull() { public void testAfterNull() {
try { try {
@ -107,19 +132,18 @@ public class BaseDateTimeTypeDstu3Test {
/** /**
* Test for #57 * Test for #57
*/ */
@SuppressWarnings("unused")
@Test @Test
public void testConstructorRejectsInvalidPrecision() { public void testConstructorRejectsInvalidPrecision() {
try { try {
new DateType("2001-01-02T11:13:33"); new DateType("2001-01-02T11:13:33");
fail(); fail();
} catch (IllegalArgumentException e) { } catch (DataFormatException e) {
assertThat(e.getMessage(), containsString("precision")); assertThat(e.getMessage(), containsString("precision"));
} }
try { try {
new InstantType("2001-01-02"); new InstantType("2001-01-02");
fail(); fail();
} catch (IllegalArgumentException e) { } catch (DataFormatException e) {
assertThat(e.getMessage(), containsString("precision")); assertThat(e.getMessage(), containsString("precision"));
} }
} }
@ -330,7 +354,7 @@ public class BaseDateTimeTypeDstu3Test {
cal.set(1990, Calendar.JANUARY, 3, 3, 22, 11); cal.set(1990, Calendar.JANUARY, 3, 3, 22, 11);
DateTimeType date = new DateTimeType(); DateTimeType date = new DateTimeType();
date.setValue(cal.getTime(), TemporalPrecisionEnum.MINUTE); date.setValue(cal.getTime(), ca.uhn.fhir.model.api.TemporalPrecisionEnum.MINUTE);
date.setTimeZone(TimeZone.getTimeZone("EST")); date.setTimeZone(TimeZone.getTimeZone("EST"));
assertEquals("1990-01-02T21:22-05:00", date.getValueAsString()); assertEquals("1990-01-02T21:22-05:00", date.getValueAsString());
@ -538,24 +562,21 @@ public class BaseDateTimeTypeDstu3Test {
@Test @Test
public void testParseMinute() throws DataFormatException { public void testParseMinute() throws DataFormatException {
DateTimeType dt = new DateTimeType(); DateTimeType dt = new DateTimeType();
dt.setValueAsString("2013-02-03T11:22"); try {
dt.setValueAsString("2013-02-03T11:22");
assertEquals("2013-02-03 11:22", myDateInstantParser.format(dt.getValue()).substring(0, 16)); } catch (DataFormatException e) {
assertEquals("2013-02-03T11:22", dt.getValueAsString()); assertEquals("Invalid date/time string (datatype DateTimeType does not support MINUTE precision): 2013-02-03T11:22", e.getMessage());
assertEquals(false, dt.isTimeZoneZulu()); }
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.MINUTE, dt.getPrecision());
} }
@Test @Test
public void testParseMinuteZulu() throws DataFormatException { public void testParseMinuteZulu() throws DataFormatException {
DateTimeType dt = new DateTimeType(); DateTimeType dt = new DateTimeType();
dt.setValueAsString("2013-02-03T11:22Z"); try {
dt.setValueAsString("2013-02-03T11:22Z");
assertEquals("2013-02-03T11:22Z", dt.getValueAsString()); } catch (Exception e) {
assertEquals(true, dt.isTimeZoneZulu()); assertEquals("Invalid date/time string (datatype DateTimeType does not support MINUTE precision): 2013-02-03T11:22Z", e.getMessage());
assertEquals("GMT", dt.getTimeZone().getID()); }
assertEquals(TemporalPrecisionEnum.MINUTE, dt.getPrecision());
} }
@Test @Test
@ -739,7 +760,7 @@ public class BaseDateTimeTypeDstu3Test {
Date time = cal.getTime(); Date time = cal.getTime();
DateType date = new DateType(); DateType date = new DateType();
date.setValue(time, TemporalPrecisionEnum.DAY); date.setValue(time, ca.uhn.fhir.model.api.TemporalPrecisionEnum.DAY);
assertEquals("2012-01-02", date.getValueAsString()); assertEquals("2012-01-02", date.getValueAsString());
} }

View File

@ -24,6 +24,10 @@
looked like before the update. This change was made to support the change above, but looked like before the update. This change was made to support the change above, but
seems like a useful feature all around. seems like a useful feature all around.
</action> </action>
<action type="fix" issue="604">
Allow DateParam (used in servers) to handle values with MINUTE precision. Thanks to
Christian Ohr for the pull request!
</action>
</release> </release>
<release version="2.5" date="2017-06-08"> <release version="2.5" date="2017-06-08">
<action type="fix"> <action type="fix">
@ -191,11 +195,6 @@
was preventing the CLI from uploading definitions correctly. Thanks to was preventing the CLI from uploading definitions correctly. Thanks to
Joel Schneider for the Pull Request! Joel Schneider for the Pull Request!
</action> </action>
<action type="fix" issue="655">
DateTime datatypes (both DSTU1/2 and RI STU3) did not properly parse
values with MINUTE precision. Thanks to Christian Ohr for the pull
request!
</action>
<action type="add" issue="656"> <action type="add" issue="656">
Improve handling in JPA server when doing code:above and code:below Improve handling in JPA server when doing code:above and code:below
searches to use a disjunction of AND and IN in order to avoid failures searches to use a disjunction of AND and IN in order to avoid failures