Add better constructors for date types

This commit is contained in:
jamesagnew 2016-01-20 07:40:57 -05:00
parent f9960b22d5
commit 944afc2785
11 changed files with 322 additions and 53 deletions

View File

@ -46,6 +46,13 @@ public enum TemporalPrecisionEnum {
return DateUtils.addDays(theInput, 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) { SECOND(Calendar.SECOND) {
@Override @Override
public Date add(Date theInput, int theAmount) { public Date add(Date theInput, int theAmount) {
@ -58,7 +65,7 @@ public enum TemporalPrecisionEnum {
public Date add(Date theInput, int theAmount) { public Date add(Date theInput, int theAmount) {
return DateUtils.addMilliseconds(theInput, theAmount); return DateUtils.addMilliseconds(theInput, theAmount);
} }
}, },
; ;

View File

@ -68,6 +68,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
private static final FastDateFormat ourHumanDateFormat = FastDateFormat.getDateInstance(FastDateFormat.MEDIUM); private static final FastDateFormat ourHumanDateFormat = FastDateFormat.getDateInstance(FastDateFormat.MEDIUM);
private static final Pattern ourYearMonthPattern = Pattern.compile("[0-9]{4}[0-9]{2}"); private static final Pattern ourYearMonthPattern = Pattern.compile("[0-9]{4}[0-9]{2}");
private static final Pattern ourYearPattern = Pattern.compile("[0-9]{4}"); private static final Pattern ourYearPattern = Pattern.compile("[0-9]{4}");
private static final FastDateFormat ourYearMonthDayTimeMinsFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm");
private static final FastDateFormat ourYearMonthDayTimeMinsZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mmZZ");
static { static {
ArrayList<FastDateFormat> formatters = new ArrayList<FastDateFormat>(); ArrayList<FastDateFormat> formatters = new ArrayList<FastDateFormat>();
@ -185,36 +187,40 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
if (theValue == null) { if (theValue == null) {
return null; return null;
} else { } else {
GregorianCalendar cal;
if (myTimeZoneZulu) {
cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
} else if (myTimeZone != null) {
cal = new GregorianCalendar(myTimeZone);
} else {
cal = new GregorianCalendar();
}
cal.setTime(theValue);
switch (myPrecision) { switch (myPrecision) {
case DAY: case DAY:
return ourYearMonthDayFormat.format(theValue); return ourYearMonthDayFormat.format(cal);
case MONTH: case MONTH:
return ourYearMonthFormat.format(theValue); return ourYearMonthFormat.format(cal);
case YEAR: case YEAR:
return ourYearFormat.format(theValue); return ourYearFormat.format(cal);
case MINUTE:
if (myTimeZoneZulu) {
return ourYearMonthDayTimeMinsFormat.format(cal) + "Z";
} else {
return ourYearMonthDayTimeMinsZoneFormat.format(cal);
}
case SECOND: case SECOND:
if (myTimeZoneZulu) { if (myTimeZoneZulu) {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(theValue);
return ourYearMonthDayTimeFormat.format(cal) + "Z"; return ourYearMonthDayTimeFormat.format(cal) + "Z";
} else if (myTimeZone != null) {
GregorianCalendar cal = new GregorianCalendar(myTimeZone);
cal.setTime(theValue);
return ourYearMonthDayTimeZoneFormat.format(cal);
} else { } else {
return ourYearMonthDayTimeFormat.format(theValue); return ourYearMonthDayTimeZoneFormat.format(cal);
} }
case MILLI: case MILLI:
if (myTimeZoneZulu) { if (myTimeZoneZulu) {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(theValue);
return ourYearMonthDayTimeMilliFormat.format(cal) + "Z"; return ourYearMonthDayTimeMilliFormat.format(cal) + "Z";
} else if (myTimeZone != null) {
GregorianCalendar cal = new GregorianCalendar(myTimeZone);
cal.setTime(theValue);
return ourYearMonthDayTimeMilliZoneFormat.format(cal);
} else { } else {
return ourYearMonthDayTimeMilliFormat.format(theValue); return ourYearMonthDayTimeMilliZoneFormat.format(cal);
} }
} }
throw new IllegalStateException("Invalid precision (this is a HAPI bug, shouldn't happen): " + myPrecision); throw new IllegalStateException("Invalid precision (this is a HAPI bug, shouldn't happen): " + myPrecision);

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.model.primitive; package ca.uhn.fhir.model.primitive;
import java.util.Calendar;
/* /*
* #%L * #%L
* HAPI FHIR - Core Library * HAPI FHIR - Core Library
@ -21,6 +23,8 @@ package ca.uhn.fhir.model.primitive;
*/ */
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.DatatypeDef;
@ -34,6 +38,17 @@ import ca.uhn.fhir.parser.DataFormatException;
* <li>{@link TemporalPrecisionEnum#MONTH} * <li>{@link TemporalPrecisionEnum#MONTH}
* <li>{@link TemporalPrecisionEnum#DAY} * <li>{@link TemporalPrecisionEnum#DAY}
* </ul> * </ul>
*
* <p>
* <b>Note on using Java Date objects:</b> This type stores the date as a Java Date. Note that
* the Java Date has more precision (millisecond precision), and does not store a timezone. As such,
* it could potentially cause issues. For example, if a Date contains the number of milliseconds at
* midnight in a timezone across the date line from your location, it might refer to a different date than
* intended.
* </p>
* <p>
* As such, it is recommended to use the <code>Calendar<code> or <code>int,int,int</code> constructors
* </p>
*/ */
@DatatypeDef(name = "date") @DatatypeDef(name = "date")
public class DateDt extends BaseDateTimeDt { public class DateDt extends BaseDateTimeDt {
@ -51,7 +66,17 @@ public class DateDt extends BaseDateTimeDt {
} }
/** /**
* Constructor which accepts a date value and uses the {@link #DEFAULT_PRECISION} for this type * Constructor which accepts a date value and uses the {@link #DEFAULT_PRECISION} for this type.
*/
public DateDt(Calendar theCalendar) {
super(theCalendar.getTime(), DEFAULT_PRECISION);
setTimeZone(theCalendar.getTimeZone());
}
/**
* Constructor which accepts a date value and uses the {@link #DEFAULT_PRECISION} for this type.
* <b>Please see the note on timezones</b> on the {@link DateDt class documentation} for considerations
* when using this constructor!
*/ */
@SimpleSetter(suffix = "WithDayPrecision") @SimpleSetter(suffix = "WithDayPrecision")
public DateDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) { public DateDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) {
@ -65,6 +90,8 @@ public class DateDt extends BaseDateTimeDt {
* <li>{@link TemporalPrecisionEnum#MONTH} * <li>{@link TemporalPrecisionEnum#MONTH}
* <li>{@link TemporalPrecisionEnum#DAY} * <li>{@link TemporalPrecisionEnum#DAY}
* </ul> * </ul>
* <b>Please see the note on timezones</b> on the {@link DateDt class documentation} for considerations
* when using this constructor!
* *
* @throws DataFormatException * @throws DataFormatException
* If the specified precision is not allowed for this type * If the specified precision is not allowed for this type
@ -74,6 +101,17 @@ public class DateDt extends BaseDateTimeDt {
super(theDate, thePrecision); super(theDate, thePrecision);
} }
/**
* Constructor which accepts a date value and uses the {@link #DEFAULT_PRECISION} for this type.
*
* @param theYear The year, e.g. 2015
* @param theMonth The month, e.g. 0 for January
* @param theDay The day (1 indexed) e.g. 1 for the first day of the month
*/
public DateDt(int theYear, int theMonth, int theDay) {
this(toCalendarZulu(theYear, theMonth, theDay));
}
/** /**
* Constructor which accepts a date as a string in FHIR format * Constructor which accepts a date as a string in FHIR format
* *
@ -84,6 +122,16 @@ public class DateDt extends BaseDateTimeDt {
super(theDate); super(theDate);
} }
/**
* Returns the default precision for this datatype
*
* @see #DEFAULT_PRECISION
*/
@Override
protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() {
return DEFAULT_PRECISION;
}
@Override @Override
boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) {
switch (thePrecision) { switch (thePrecision) {
@ -96,14 +144,12 @@ public class DateDt extends BaseDateTimeDt {
} }
} }
/** private static GregorianCalendar toCalendarZulu(int theYear, int theMonth, int theDay) {
* Returns the default precision for this datatype GregorianCalendar retVal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
* retVal.set(Calendar.YEAR, theYear);
* @see #DEFAULT_PRECISION retVal.set(Calendar.MONTH, theMonth);
*/ retVal.set(Calendar.DATE, theDay);
@Override return retVal;
protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() {
return DEFAULT_PRECISION;
} }
} }

View File

@ -62,6 +62,7 @@ public class TestDstu21Config extends BaseJavaConfigDstu21 {
extraProperties.put("hibernate.search.default.directory_provider" ,"filesystem"); extraProperties.put("hibernate.search.default.directory_provider" ,"filesystem");
extraProperties.put("hibernate.search.default.indexBase", "target/lucene_index_dstu21"); extraProperties.put("hibernate.search.default.indexBase", "target/lucene_index_dstu21");
extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT"); extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT");
extraProperties.put("hibernate.search.autoregister_listeners", "true");
return extraProperties; return extraProperties;
} }

View File

@ -126,51 +126,43 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
if (theValue == null) { if (theValue == null) {
return null; return null;
} else { } else {
GregorianCalendar cal;
if (myTimeZoneZulu) {
cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
} else if (myTimeZone != null) {
cal = new GregorianCalendar(myTimeZone);
} else {
cal = new GregorianCalendar();
}
cal.setTime(theValue);
switch (myPrecision) { switch (myPrecision) {
case DAY: case DAY:
return ourYearMonthDayFormat.format(theValue); return ourYearMonthDayFormat.format(cal);
case MONTH: case MONTH:
return ourYearMonthFormat.format(theValue); return ourYearMonthFormat.format(cal);
case YEAR: case YEAR:
return ourYearFormat.format(theValue); return ourYearFormat.format(cal);
case MINUTE: case MINUTE:
if (myTimeZoneZulu) { if (myTimeZoneZulu) {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(theValue);
return ourYearMonthDayTimeMinsFormat.format(cal) + "Z"; return ourYearMonthDayTimeMinsFormat.format(cal) + "Z";
} else if (myTimeZone != null) {
GregorianCalendar cal = new GregorianCalendar(myTimeZone);
cal.setTime(theValue);
return (ourYearMonthDayTimeMinsZoneFormat.format(cal));
} else { } else {
return ourYearMonthDayTimeMinsFormat.format(theValue); return ourYearMonthDayTimeMinsZoneFormat.format(cal);
} }
case SECOND: case SECOND:
if (myTimeZoneZulu) { if (myTimeZoneZulu) {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(theValue);
return ourYearMonthDayTimeFormat.format(cal) + "Z"; return ourYearMonthDayTimeFormat.format(cal) + "Z";
} else if (myTimeZone != null) {
GregorianCalendar cal = new GregorianCalendar(myTimeZone);
cal.setTime(theValue);
return (ourYearMonthDayTimeZoneFormat.format(cal));
} else { } else {
return ourYearMonthDayTimeFormat.format(theValue); return ourYearMonthDayTimeZoneFormat.format(cal);
} }
case MILLI: case MILLI:
if (myTimeZoneZulu) { if (myTimeZoneZulu) {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(theValue);
return ourYearMonthDayTimeMilliFormat.format(cal) + "Z"; return ourYearMonthDayTimeMilliFormat.format(cal) + "Z";
} else if (myTimeZone != null) {
GregorianCalendar cal = new GregorianCalendar(myTimeZone);
cal.setTime(theValue);
return (ourYearMonthDayTimeMilliZoneFormat.format(cal));
} else { } else {
return ourYearMonthDayTimeMilliFormat.format(theValue); return ourYearMonthDayTimeMilliZoneFormat.format(cal);
} }
} }
throw new IllegalStateException("Invalid precision (this is a bug, shouldn't happen): " + myPrecision); throw new IllegalStateException("Invalid precision (this is a HAPI bug, shouldn't happen): " + myPrecision);
} }
} }

View File

@ -29,11 +29,14 @@ POSSIBILITY OF SUCH DAMAGE.
package org.hl7.fhir.dstu21.model; package org.hl7.fhir.dstu21.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.Date; import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone; import java.util.TimeZone;
import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.DatatypeDef;
@ -95,6 +98,33 @@ public class DateType extends BaseDateTimeType {
super(theDate); super(theDate);
} }
/**
* Constructor which accepts a date value and uses the {@link #DEFAULT_PRECISION} for this type.
*/
public DateType(Calendar theCalendar) {
super(theCalendar.getTime(), DEFAULT_PRECISION);
setTimeZone(theCalendar.getTimeZone());
}
/**
* Constructor which accepts a date value and uses the {@link #DEFAULT_PRECISION} for this type.
*
* @param theYear The year, e.g. 2015
* @param theMonth The month, e.g. 0 for January
* @param theDay The day (1 indexed) e.g. 1 for the first day of the month
*/
public DateType(int theYear, int theMonth, int theDay) {
this(toCalendarZulu(theYear, theMonth, theDay));
}
private static GregorianCalendar toCalendarZulu(int theYear, int theMonth, int theDay) {
GregorianCalendar retVal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
retVal.set(Calendar.YEAR, theYear);
retVal.set(Calendar.MONTH, theMonth);
retVal.set(Calendar.DATE, theDay);
return retVal;
}
@Override @Override
boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) {
switch (thePrecision) { switch (thePrecision) {

View File

@ -0,0 +1,105 @@
package ca.uhn.fhir.model;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.hl7.fhir.dstu21.model.DateTimeType;
import org.hl7.fhir.dstu21.model.DateType;
import org.hl7.fhir.dstu21.model.TemporalPrecisionEnum;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class BaseDateTimeTypeDstu21Test {
private static Locale ourDefaultLocale;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseDateTimeTypeDstu21Test.class);
private SimpleDateFormat myDateInstantParser;
@Before
public void before() {
myDateInstantParser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
}
/**
* 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());
}
}

View File

@ -0,0 +1,52 @@
package ca.uhn.fhir.model;
import static org.junit.Assert.assertEquals;
import java.util.Calendar;
import java.util.TimeZone;
import org.hl7.fhir.dstu21.model.DateType;
import org.junit.Test;
public class DateTypeTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DateTypeTest.class);
@Test
public void testPrecision() {
// ourLog.info(""+ new TreeSet<String>(Arrays.asList(TimeZone.getAvailableIDs())));
final Calendar cal = Calendar.getInstance();
cal.setTimeZone(TimeZone.getTimeZone("GMT"));
cal.set(1990, Calendar.JANUARY, 1, 0, 0, 0);
ourLog.info("Time: {}", cal); // 631152000775
DateType dateDt = new DateType(cal.getTime());
long time = dateDt.getValue().getTime();
ourLog.info("Time: {}", time); // 631152000775
ourLog.info("Time: {}", dateDt.getValue()); // 631152000775
dateDt.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
time = dateDt.getValue().getTime();
ourLog.info("Time: {}", time); // 631152000775
ourLog.info("Time: {}", dateDt.getValue()); // 631152000775
String valueAsString = dateDt.getValueAsString();
ourLog.info(valueAsString);
// is 631152000030
}
@Test
public void testConstructors() {
final Calendar cal = Calendar.getInstance();
cal.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
cal.set(1990, Calendar.JANUARY, 5, 0, 0, 0);
DateType dateDt = new DateType(cal);
assertEquals("1990-01-05", dateDt.getValueAsString());
dateDt = new DateType(1990, 0, 5);
assertEquals("1990-01-05", dateDt.getValueAsString());
}
}

View File

@ -41,6 +41,21 @@ public class BaseDateTimeDtDstu2Test {
assertEquals("2012-01-02", date.getValueAsString()); 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);
DateTimeDt date = new DateTimeDt();
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 * See HAPI #101 - https://github.com/jamesagnew/hapi-fhir/issues/101
*/ */

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.model.primitive; package ca.uhn.fhir.model.primitive;
import static org.junit.Assert.assertEquals;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.TimeZone; import java.util.TimeZone;
@ -36,4 +38,18 @@ public class DateDtTest {
} }
@Test
public void testConstructors() {
final Calendar cal = Calendar.getInstance();
cal.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
cal.set(1990, Calendar.JANUARY, 5, 0, 0, 0);
DateDt dateDt = new DateDt(cal);
assertEquals("1990-01-05", dateDt.getValueAsString());
dateDt = new DateDt(1990, 0, 5);
assertEquals("1990-01-05", dateDt.getValueAsString());
}
} }

View File

@ -109,7 +109,6 @@ public class Controller extends BaseController {
return "result"; return "result";
} }
@SuppressWarnings("unchecked")
@RequestMapping(value = { "/delete" }) @RequestMapping(value = { "/delete" })
public String actionDelete(HttpServletRequest theReq, HomeRequest theRequest, BindingResult theBindingResult, ModelMap theModel) { public String actionDelete(HttpServletRequest theReq, HomeRequest theRequest, BindingResult theBindingResult, ModelMap theModel) {
addCommonParams(theReq, theRequest, theModel); addCommonParams(theReq, theRequest, theModel);