Port in changes from RI

This commit is contained in:
James 2016-12-12 07:32:53 -05:00
parent 826c91087b
commit 88117f8d6e
2 changed files with 375 additions and 237 deletions

View File

@ -16,13 +16,13 @@ import ca.uhn.fhir.parser.DataFormatException;
public abstract class BaseDateTimeType extends PrimitiveType<Date> {
private static final long serialVersionUID = 1L;
static final long NANOS_PER_MILLIS = 1000000L;
static final long NANOS_PER_SECOND = 1000000000L;
static final long NANOS_PER_SECOND = 1000000000L;
private static final FastDateFormat ourHumanDateFormat = FastDateFormat.getDateInstance(FastDateFormat.MEDIUM);
private static final FastDateFormat ourHumanDateTimeFormat = FastDateFormat.getDateTimeInstance(FastDateFormat.MEDIUM, FastDateFormat.MEDIUM);
private static final long serialVersionUID = 1L;
private String myFractionalSeconds;
private TemporalPrecisionEnum myPrecision = null;
@ -39,13 +39,13 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
/**
* Constructor
*
* @throws DataFormatException
* @throws IllegalArgumentException
* If the specified precision is not allowed for this type
*/
public BaseDateTimeType(Date theDate, TemporalPrecisionEnum thePrecision) {
setValue(theDate, thePrecision);
if (isPrecisionAllowed(thePrecision) == false) {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theDate);
throw new IllegalArgumentException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theDate);
}
}
@ -60,16 +60,76 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
/**
* Constructor
*
* @throws DataFormatException
* @throws IllegalArgumentException
* If the specified precision is not allowed for this type
*/
public BaseDateTimeType(String theString) {
setValueAsString(theString);
if (isPrecisionAllowed(getPrecision()) == false) {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + getPrecision() + " precision): " + theString);
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
*
* @param theField
* The field, uses constants from {@link Calendar} such as {@link Calendar#YEAR}
* @param theValue
* The number to add (or subtract for a negative number)
*/
public void add(int theField, int theValue) {
switch (theField) {
case Calendar.YEAR:
setValue(DateUtils.addYears(getValue(), theValue), getPrecision());
break;
case Calendar.MONTH:
setValue(DateUtils.addMonths(getValue(), theValue), getPrecision());
break;
case Calendar.DATE:
setValue(DateUtils.addDays(getValue(), theValue), getPrecision());
break;
case Calendar.HOUR:
setValue(DateUtils.addHours(getValue(), theValue), getPrecision());
break;
case Calendar.MINUTE:
setValue(DateUtils.addMinutes(getValue(), theValue), getPrecision());
break;
case Calendar.SECOND:
setValue(DateUtils.addSeconds(getValue(), theValue), getPrecision());
break;
case Calendar.MILLISECOND:
setValue(DateUtils.addMilliseconds(getValue(), theValue), getPrecision());
break;
default:
throw new DataFormatException("Unknown field constant: " + theField);
}
}
/**
* Returns <code>true</code> if the given object represents a date/time before <code>this</code> object
*
* @throws NullPointerException
* If <code>this.getValue()</code> or <code>theDateTimeType.getValue()</code>
* return <code>null</code>
*/
public boolean after(DateTimeType theDateTimeType) {
validateBeforeOrAfter(theDateTimeType);
return getValue().after(theDateTimeType.getValue());
}
/**
* Returns <code>true</code> if the given object represents a date/time before <code>this</code> object
*
* @throws NullPointerException
* If <code>this.getValue()</code> or <code>theDateTimeType.getValue()</code>
* return <code>null</code>
*/
public boolean before(DateTimeType theDateTimeType) {
validateBeforeOrAfter(theDateTimeType);
return getValue().before(theDateTimeType.getValue());
}
private void clearTimeZone() {
myTimeZone = null;
myTimeZoneZulu = false;
@ -140,11 +200,74 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
}
}
/**
* 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
*/
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) {
int plusIndex = theValueString.indexOf('+', 16);
int minusIndex = theValueString.indexOf('-', 16);
@ -171,6 +294,13 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
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
* supplied.
@ -199,6 +329,13 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
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
*/
@ -272,7 +409,7 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
int offsetIdx = getOffsetIndex(value);
String time;
if (offsetIdx == -1) {
//throwBadDateFormat(theValue);
// throwBadDateFormat(theValue);
// No offset - should this be an error?
time = value.substring(11);
} else {
@ -354,6 +491,92 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
return retVal;
}
/**
* Sets the month with 1-index, e.g. 1=the first day of the month
*/
public BaseDateTimeType 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 BaseDateTimeType 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 BaseDateTimeType setMillis(int theMillis) {
setFieldValue(Calendar.MILLISECOND, theMillis, null, 0, 999);
return this;
}
/**
* Sets the minute of the hour in the range 0-59
*/
public BaseDateTimeType setMinute(int theMinute) {
setFieldValue(Calendar.MINUTE, theMinute, null, 0, 59);
return this;
}
/**
* Sets the month with 0-index, e.g. 0=January
*/
public BaseDateTimeType 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 BaseDateTimeType 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
*
@ -367,6 +590,14 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
updateStringValue();
}
/**
* Sets the second of the minute in the range 0-59
*/
public BaseDateTimeType setSecond(int theSecond) {
setFieldValue(Calendar.SECOND, theSecond, null, 0, 59);
return this;
}
private BaseDateTimeType setTimeZone(String theWholeValue, String theValue) {
if (isBlank(theValue)) {
@ -448,6 +679,55 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
super.setValueAsString(theValue);
}
protected void setValueAsV3String(String theV3String) {
if (StringUtils.isBlank(theV3String)) {
setValue(null);
} else {
StringBuilder b = new StringBuilder();
String timeZone = null;
for (int i = 0; i < theV3String.length(); i++) {
char nextChar = theV3String.charAt(i);
if (nextChar == '+' || nextChar == '-' || nextChar == 'Z') {
timeZone = (theV3String.substring(i));
break;
}
// assertEquals("2013-02-02T20:13:03-05:00", DateAndTime.parseV3("20130202201303-0500").toString());
if (i == 4 || i == 6) {
b.append('-');
} else if (i == 8) {
b.append('T');
} else if (i == 10 || i == 12) {
b.append(':');
}
b.append(nextChar);
}
if (b.length() == 16)
b.append(":00"); // schema rule, must have seconds
if (timeZone != null && b.length() > 10) {
if (timeZone.length() == 5) {
b.append(timeZone.substring(0, 3));
b.append(':');
b.append(timeZone.substring(3));
} else {
b.append(timeZone);
}
}
setValueAsString(b.toString());
}
}
/**
* Sets the year, e.g. 2015
*/
public BaseDateTimeType setYear(int theYear) {
setFieldValue(Calendar.YEAR, theYear, null, 0, 9999);
return this;
}
private void throwBadDateFormat(String theValue) {
throw new DataFormatException("Invalid date/time format: \"" + theValue + "\"");
}
@ -456,6 +736,18 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
throw new DataFormatException("Invalid date/time format: \"" + theValue + "\": " + theMesssage);
}
/**
* Returns a view of this date/time as a Calendar object. Note that the returned
* Calendar object is entirely independent from <code>this</code> object. Changes to the
* calendar will not affect <code>this</code>.
*/
public Calendar toCalendar() {
Calendar retVal = Calendar.getInstance();
retVal.setTime(getValue());
retVal.setTimeZone(getTimeZone());
return retVal;
}
/**
* Returns a human readable version of this date/time using the system local format.
* <p>
@ -502,6 +794,18 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
}
}
private void validateBeforeOrAfter(DateTimeType theDateTimeType) {
if (getValue() == null) {
throw new NullPointerException("This BaseDateTimeType does not contain a value (getValue() returns null)");
}
if (theDateTimeType == null) {
throw new NullPointerException("theDateTimeType must not be null");
}
if (theDateTimeType.getValue() == null) {
throw new NullPointerException("The given BaseDateTimeType does not contain a value (theDateTimeType.getValue() returns null)");
}
}
private void validateCharAtIndexIs(String theValue, int theIndex, char theChar) {
if (theValue.charAt(theIndex) != theChar) {
throwBadDateFormat(theValue, "Expected character '" + theChar + "' at index " + theIndex + " but found " + theValue.charAt(theIndex));
@ -514,230 +818,10 @@ public abstract class BaseDateTimeType extends PrimitiveType<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 BaseDateTimeType setYear(int theYear) {
setFieldValue(Calendar.YEAR, theYear, null, 0, 9999);
return this;
}
/**
* Sets the month with 0-index, e.g. 0=January
*/
public BaseDateTimeType 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 BaseDateTimeType 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 BaseDateTimeType 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 BaseDateTimeType setMinute(int theMinute) {
setFieldValue(Calendar.MINUTE, theMinute, null, 0, 59);
return this;
}
/**
* Sets the second of the minute in the range 0-59
*/
public BaseDateTimeType 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 BaseDateTimeType 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 BaseDateTimeType 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) {
if (theValue < theMinimum || theValue > theMaximum) {
throw new IllegalArgumentException("Value " + theValue + " is not between allowable range: " + theMinimum + " - " + theMaximum);
}
}
private Integer getFieldValue(int theField) {
if (getValue() == null) {
return null;
}
Calendar cal = getValueAsCalendar();
return cal.get(theField);
}
protected void setValueAsV3String(String theV3String) {
if (StringUtils.isBlank(theV3String)) {
setValue(null);
} else {
StringBuilder b = new StringBuilder();
String timeZone = null;
for (int i = 0; i < theV3String.length(); i++) {
char nextChar = theV3String.charAt(i);
if (nextChar == '+' || nextChar == '-' || nextChar == 'Z') {
timeZone = (theV3String.substring(i));
break;
}
// assertEquals("2013-02-02T20:13:03-05:00", DateAndTime.parseV3("20130202201303-0500").toString());
if (i == 4 || i == 6) {
b.append('-');
} else if (i == 8) {
b.append('T');
} else if (i == 10 || i == 12) {
b.append(':');
}
b.append(nextChar);
}
if (b.length() == 16)
b.append(":00"); // schema rule, must have seconds
if (timeZone != null && b.length() > 10) {
if (timeZone.length() ==5) {
b.append(timeZone.substring(0, 3));
b.append(':');
b.append(timeZone.substring(3));
}else {
b.append(timeZone);
}
}
setValueAsString(b.toString());
}
}
}

View File

@ -3,12 +3,7 @@ package org.hl7.fhir.dstu3.model;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.endsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
@ -55,6 +50,65 @@ public class BaseDateTimeTypeDstu3Test {
assertEquals("1995-11-15T04:58:08Z", dt.getValueAsString());
}
@Test
public void testAfter() {
assertTrue(new DateTimeType("2011-01-01T12:12:12Z").after(new DateTimeType("2011-01-01T12:12:11Z")));
assertFalse(new DateTimeType("2011-01-01T12:12:11Z").after(new DateTimeType("2011-01-01T12:12:12Z")));
assertFalse(new DateTimeType("2011-01-01T12:12:12Z").after(new DateTimeType("2011-01-01T12:12:12Z")));
}
@Test
public void testBefore() {
assertFalse(new DateTimeType("2011-01-01T12:12:12Z").before(new DateTimeType("2011-01-01T12:12:11Z")));
assertTrue(new DateTimeType("2011-01-01T12:12:11Z").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 testAfterNull() {
try {
assertTrue(new DateTimeType().after(new DateTimeType("2011-01-01T12:12:11Z")));
fail();
} catch (NullPointerException e) {
assertEquals("This BaseDateTimeType does not contain a value (getValue() returns null)", e.getMessage());
}
try {
assertTrue(new DateTimeType("2011-01-01T12:12:11Z").after(new DateTimeType()));
fail();
} catch (NullPointerException e) {
assertEquals("The given BaseDateTimeType does not contain a value (theDateTimeType.getValue() returns null)", e.getMessage());
}
try {
assertTrue(new DateTimeType("2011-01-01T12:12:11Z").after(null));
fail();
} catch (NullPointerException e) {
assertEquals("theDateTimeType must not be null", e.getMessage());
}
}
@Test()
public void testBeforeNull1() {
try {
assertTrue(new DateTimeType().before(new DateTimeType("2011-01-01T12:12:11Z")));
fail();
} catch (NullPointerException e) {
assertEquals("This BaseDateTimeType does not contain a value (getValue() returns null)", e.getMessage());
}
try {
assertTrue(new DateTimeType("2011-01-01T12:12:11Z").before(new DateTimeType()));
fail();
} catch (NullPointerException e) {
assertEquals("The given BaseDateTimeType does not contain a value (theDateTimeType.getValue() returns null)", e.getMessage());
}
try {
assertTrue(new DateTimeType("2011-01-01T12:12:11Z").before(null));
fail();
} catch (NullPointerException e) {
assertEquals("theDateTimeType must not be null", e.getMessage());
}
}
/**
* Test for #57
*/
@ -64,13 +118,13 @@ public class BaseDateTimeTypeDstu3Test {
try {
new DateType("2001-01-02T11:13:33");
fail();
} catch (DataFormatException e) {
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("precision"));
}
try {
new InstantType("2001-01-02");
fail();
} catch (DataFormatException e) {
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("precision"));
}
}