Fix #444 - Correct handling of parsing milliseconds in dates before 1970

This commit is contained in:
James Agnew 2016-09-09 18:18:28 -04:00
parent ed7e6929a7
commit a2ffc6af05
7 changed files with 201 additions and 73 deletions

View File

@ -449,7 +449,12 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
myPrecision = thePrecision;
myFractionalSeconds = "";
if (theValue != null) {
String fractionalSeconds = Integer.toString((int) (theValue.getTime() % 1000));
long millis = theValue.getTime() % 1000;
if (millis < 0) {
// This is for times before 1970 (see bug #444)
millis = 1000 + millis;
}
String fractionalSeconds = Integer.toString((int) millis);
myFractionalSeconds = StringUtils.leftPad(fractionalSeconds, 3, '0');
}
super.setValue(theValue);

View File

@ -3,22 +3,21 @@ package ca.uhn.fhir.model.primitive;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.endsWith;
import static org.junit.Assert.*;
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 java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import org.apache.commons.lang3.time.FastDateFormat;
import org.hamcrest.Matchers;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.*;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
@ -35,6 +34,33 @@ public class BaseDateTimeDtDstu2Test {
private SimpleDateFormat myDateInstantParser;
private FastDateFormat myDateInstantZoneParser;
/**
* See #444
*/
@Test
public void testParseAndEncodeDateBefore1970() {
LocalDateTime ldt = LocalDateTime.of(1960, 9, 7, 0, 44, 25, 12387401);
Date from = Date.from(ldt.toInstant(ZoneOffset.UTC));
InstantDt type = (InstantDt) new InstantDt(from).setTimeZoneZulu(true);
String encoded = type.getValueAsString();
ourLog.info("LDT: "+ ldt.toString());
ourLog.info("Expected: "+"1960-09-07T00:44:25.012");
ourLog.info("Actual: "+encoded);
assertEquals("1960-09-07T00:44:25.012Z", encoded);
type = new InstantDt(encoded);
assertEquals(1960, type.getYear().intValue());
assertEquals(8, type.getMonth().intValue()); // 0-indexed unlike LocalDateTime.of
assertEquals(7, type.getDay().intValue());
assertEquals(0, type.getHour().intValue());
assertEquals(44, type.getMinute().intValue());
assertEquals(25, type.getSecond().intValue());
assertEquals(12, type.getMillis().intValue());
}
@Test
public void testFromTime() {
long millis;

View File

@ -429,7 +429,12 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
myPrecision = thePrecision;
myFractionalSeconds = "";
if (theValue != null) {
String fractionalSeconds = Integer.toString((int) (theValue.getTime() % 1000));
long millis = theValue.getTime() % 1000;
if (millis < 0) {
// This is for times before 1970 (see bug #444)
millis = 1000 + millis;
}
String fractionalSeconds = Integer.toString((int) millis);
myFractionalSeconds = StringUtils.leftPad(fractionalSeconds, 3, '0');
}
super.setValue(theValue);

View File

@ -23,6 +23,8 @@ import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import org.apache.commons.io.IOUtils;
@ -103,7 +105,7 @@ public class XmlParserDstu3Test {
parent = (Organization) child.getPartOf().getResource();
assertEquals("parent", parent.getName());
gp = (Organization)parent.getPartOf().getResource();
gp = (Organization) parent.getPartOf().getResource();
assertEquals("grandparent", gp.getName());
}
@ -127,7 +129,7 @@ public class XmlParserDstu3Test {
assertEquals("#3", performer.getId());
}
@Test(expected=DataFormatException.class)
@Test(expected = DataFormatException.class)
public void testContainedResourceWithNoId() throws IOException {
String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_contained_with_no_id.xml"), StandardCharsets.UTF_8);
@ -136,7 +138,6 @@ public class XmlParserDstu3Test {
parser.parseResource(Bundle.class, string);
}
@Test()
public void testContainedResourceWithNoIdLenient() throws IOException {
String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_contained_with_no_id.xml"), StandardCharsets.UTF_8);
@ -146,7 +147,7 @@ public class XmlParserDstu3Test {
parser.parseResource(Bundle.class, string);
}
@Test(expected=DataFormatException.class)
@Test(expected = DataFormatException.class)
public void testParseWithInvalidLocalRef() throws IOException {
String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_invalid_contained_ref.xml"), StandardCharsets.UTF_8);
@ -409,7 +410,6 @@ public class XmlParserDstu3Test {
ourCtx = null;
}
@Test
public void testEncodeAndParseContainedNonCustomTypes() {
ourCtx = FhirContext.forDstu3();
@ -1435,12 +1435,12 @@ public class XmlParserDstu3Test {
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2\"/>"));
parser.setDontStripVersionsFromReferencesAtPaths((String[])null);
parser.setDontStripVersionsFromReferencesAtPaths((String[]) null);
enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2\"/>"));
parser.setDontStripVersionsFromReferencesAtPaths((List<String>)null);
parser.setDontStripVersionsFromReferencesAtPaths((List<String>) null);
enc = parser.setPrettyPrint(true).encodeResourceToString(auditEvent);
ourLog.info(enc);
assertThat(enc, containsString("<reference value=\"http://foo.com/Organization/2\"/>"));

View File

@ -68,6 +68,62 @@ public class CreateDstu3Test {
}
@Test
public void testCreateWithIncorrectContent1() throws Exception {
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity("{\"foo\":\"bar\"}", ContentType.parse("application/xml+fhir; charset=utf-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("<OperationOutcome xmlns=\"http://hl7.org/fhir\"><issue><severity value=\"error\"/><code value=\"processing\"/><diagnostics value=\""));
assertThat(responseContent, containsString("Failed to parse request body as XML resource."));
}
@Test
public void testCreateWithIncorrectContent2() throws Exception {
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity("{\"foo\":\"bar\"}", ContentType.parse("application/fhir+xml; charset=utf-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("<OperationOutcome xmlns=\"http://hl7.org/fhir\"><issue><severity value=\"error\"/><code value=\"processing\"/><diagnostics value=\""));
assertThat(responseContent, containsString("Failed to parse request body as XML resource."));
}
@Test
public void testCreateWithIncorrectContent3() throws Exception {
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity("{\"foo\":\"bar\"}", ContentType.parse("application/fhir+json; charset=utf-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("Failed to parse request body as JSON resource."));
}
@Test
public void testSearch() throws Exception {

View File

@ -11,6 +11,8 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@ -19,6 +21,7 @@ import java.util.TimeZone;
import org.apache.commons.lang3.time.FastDateFormat;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
@ -27,6 +30,7 @@ import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.ValidationResult;
@ -54,6 +58,7 @@ public class BaseDateTimeTypeDstu3Test {
/**
* Test for #57
*/
@SuppressWarnings("unused")
@Test
public void testConstructorRejectsInvalidPrecision() {
try {
@ -283,8 +288,6 @@ public class BaseDateTimeTypeDstu3Test {
assertEquals("1990-01-03T02:22Z", date.getValueAsString());
}
@Test
public void testNewInstance() throws InterruptedException {
InstantType now = InstantType.withCurrentTime();
@ -293,6 +296,35 @@ public class BaseDateTimeTypeDstu3Test {
assertTrue(now.getValue().before(then.getValue()));
}
/**
* See #444
*/
@Test
public void testParseAndEncodeDateBefore1970() {
LocalDateTime ldt = LocalDateTime.of(1960, 9, 7, 0, 44, 25, 12387401);
Date from = Date.from(ldt.toInstant(ZoneOffset.UTC));
InstantType type = (InstantType) new InstantType(from).setTimeZoneZulu(true);
String encoded = type.asStringValue();
ourLog.info("LDT: "+ ldt.toString());
ourLog.info("Expected: "+"1960-09-07T00:44:25.012");
ourLog.info("Actual: "+encoded);
assertEquals("1960-09-07T00:44:25.012Z", encoded);
type = new InstantType(encoded);
assertEquals(1960, type.getYear().intValue());
assertEquals(8, type.getMonth().intValue()); // 0-indexed unlike LocalDateTime.of
assertEquals(7, type.getDay().intValue());
assertEquals(0, type.getHour().intValue());
assertEquals(44, type.getMinute().intValue());
assertEquals(25, type.getSecond().intValue());
assertEquals(12, type.getMillis().intValue());
}
@Test
public void testParseDate() {
new DateType("2012-03-31");

View File

@ -32,6 +32,10 @@
Remove Maven dependency on Saxon library, as it is not actually used. Thanks
to Lem Edmondson for the suggestion!
</action>
<action type="fix" issue="444">
Times before 1970 with fractional milliseconds were parsed incorrectly. Thanks
to GitHub user @CarthageKing for reporting!
</action>
</release>
<release version="2.0" date="2016-08-30">
<action type="fix">