Skip to content

Commit

Permalink
Explicitly retain "Z" and "+00:00" as timezones when provided.
Browse files Browse the repository at this point in the history
  • Loading branch information
sundberg committed Jul 10, 2018
1 parent 24b7411 commit 4769574
Show file tree
Hide file tree
Showing 72 changed files with 638 additions and 520 deletions.
26 changes: 18 additions & 8 deletions java/src/main/java/com/google/fhir/stu3/DateTimeWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,20 @@ private static DateTime parseAndValidate(String input, ZoneId defaultTimeZone) {

// DateTime, with timezone offset.
try {
return buildDateTime(OffsetDateTime.parse(input, SECOND_WITH_TZ), DateTime.Precision.SECOND);
OffsetDateTime offsetDateTime = OffsetDateTime.parse(input, SECOND_WITH_TZ);
String timezone = extractFhirTimezone(input, offsetDateTime);
return buildDateTime(
offsetDateTime.toInstant().toEpochMilli() * 1000L, timezone, DateTime.Precision.SECOND);
} catch (DateTimeParseException e) {
// Fall through.
}
try {
return buildDateTime(OffsetDateTime.parse(input), DateTime.Precision.MILLISECOND);
OffsetDateTime offsetDateTime = OffsetDateTime.parse(input);
String timezone = extractFhirTimezone(input, offsetDateTime);
return buildDateTime(
offsetDateTime.toInstant().toEpochMilli() * 1000L,
timezone,
DateTime.Precision.MILLISECOND);
} catch (DateTimeParseException e) {
// Fall through.
}
Expand All @@ -144,17 +152,17 @@ private static DateTime buildDateTime(
String timezone = defaultTimeZone.toString();
return DateTime.newBuilder()
.setValueUs(dateTime.atZone(defaultTimeZone).toInstant().toEpochMilli() * 1000L)
.setTimezone("Z".equals(timezone) ? "UTC" : timezone) // Prefer "UTC" over "Z" for GMT
.setTimezone(timezone)
.setPrecision(precision)
.build();
}

private static DateTime buildDateTime(OffsetDateTime dateTime, DateTime.Precision precision) {
String timezone = dateTime.getOffset().toString();
private static DateTime buildDateTime(
long valueUs, String timezone, DateTime.Precision precision) {
return DateTime.newBuilder()
.setValueUs(dateTime.toInstant().toEpochMilli() * 1000L)
.setValueUs(valueUs)
.setPrecision(precision)
.setTimezone("Z".equals(timezone) ? "UTC" : timezone) // Prefer "UTC" over "Z" for GMT
.setTimezone(timezone)
.build();
}

Expand All @@ -175,6 +183,8 @@ protected String printValue() {
if (formatter == null) {
throw new IllegalArgumentException("Invalid precision: " + dateTime.getPrecision());
}
return Instant.ofEpochMilli(dateTime.getValueUs() / 1000L).atZone(zoneId).format(formatter);
return withOriginalTimezone(
Instant.ofEpochMilli(dateTime.getValueUs() / 1000L).atZone(zoneId).format(formatter),
dateTime.getTimezone());
}
}
2 changes: 1 addition & 1 deletion java/src/main/java/com/google/fhir/stu3/DateWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private static Date buildDate(LocalDate date, ZoneId defaultTimeZone, Date.Preci
return Date.newBuilder()
.setValueUs(date.atStartOfDay().atZone(defaultTimeZone).toInstant().toEpochMilli() * 1000L)
.setPrecision(precision)
.setTimezone("Z".equals(timezone) ? "UTC" : timezone) // Prefer "UTC" over "Z" for GMT
.setTimezone(timezone)
.build();
}

Expand Down
27 changes: 18 additions & 9 deletions java/src/main/java/com/google/fhir/stu3/InstantWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,32 @@ public InstantWrapper(String input) {
private static Instant parseAndValidate(String input) {
validateUsingPattern(INSTANT_PATTERN, input);
try {
return buildInstant(OffsetDateTime.parse(input, SECOND_WITH_TZ), Instant.Precision.SECOND);
OffsetDateTime offsetDateTime = OffsetDateTime.parse(input, SECOND_WITH_TZ);
String timezone = extractFhirTimezone(input, offsetDateTime);
return buildInstant(
offsetDateTime.toInstant().toEpochMilli() * 1000L, timezone, Instant.Precision.SECOND);
} catch (DateTimeParseException e) {
// Fall through.
}
try {
// The default parser for OffsetDateTime handles fractional seconds.
return buildInstant(OffsetDateTime.parse(input), Instant.Precision.MILLISECOND);
OffsetDateTime offsetDateTime = OffsetDateTime.parse(input);
String timezone = extractFhirTimezone(input, offsetDateTime);
return buildInstant(
offsetDateTime.toInstant().toEpochMilli() * 1000L,
timezone,
Instant.Precision.MILLISECOND);
} catch (DateTimeParseException e) {
// Fall through.
}
throw new IllegalArgumentException("Invalid Instant: " + input);
}

private static Instant buildInstant(OffsetDateTime dateTime, Instant.Precision precision) {
String timezone = dateTime.getOffset().toString();
private static Instant buildInstant(long valueUs, String timezone, Instant.Precision precision) {
return Instant.newBuilder()
.setValueUs(dateTime.toInstant().toEpochMilli() * 1000L)
.setValueUs(valueUs)
.setPrecision(precision)
.setTimezone("Z".equals(timezone) ? "UTC" : timezone)
.setTimezone(timezone)
.build();
}

Expand All @@ -81,8 +88,10 @@ protected String printValue() {
if (formatter == null) {
throw new IllegalArgumentException("Invalid precision: " + getWrapped().getPrecision());
}
return java.time.Instant.ofEpochMilli(getWrapped().getValueUs() / 1000L)
.atZone(ZoneId.of(getWrapped().getTimezone()))
.format(formatter);
return withOriginalTimezone(
java.time.Instant.ofEpochMilli(getWrapped().getValueUs() / 1000L)
.atZone(ZoneId.of(getWrapped().getTimezone()))
.format(formatter),
getWrapped().getTimezone());
}
}
17 changes: 17 additions & 0 deletions java/src/main/java/com/google/fhir/stu3/PrimitiveWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -116,4 +117,20 @@ public Element getElement() {
}
return builder.build();
}

protected static String withOriginalTimezone(String timeString, String originalTimezone) {
// Restore [+-]00:00 if necessary.
return timeString.endsWith("Z")
&& (originalTimezone.startsWith("+") || originalTimezone.startsWith("-"))
? timeString.replace("Z", originalTimezone)
: timeString;
}

// Extracts a valid fhir timezone from a time string, maintaining original. This is necessary
// because OffsetDateTime will convert +00:00, -00:00, and Z to Z, which is not reversible.
protected static String extractFhirTimezone(String timeString, OffsetDateTime offsetDateTime) {
return timeString.endsWith("+00:00")
? "+00:00"
: (timeString.endsWith("-00:00") ? "-00:00" : offsetDateTime.getOffset().toString());
}
}
63 changes: 56 additions & 7 deletions java/src/test/java/com/google/fhir/stu3/DateTimeWrapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void parseYear() {
DateTime.newBuilder()
.setValueUs(31536000000000L)
.setPrecision(DateTime.Precision.YEAR)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(parsed).isEqualTo(expected);

Expand All @@ -55,7 +55,7 @@ public void parseYearMonth() {
DateTime.newBuilder()
.setValueUs(2678400000000L)
.setPrecision(DateTime.Precision.MONTH)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(parsed).isEqualTo(expected);

Expand All @@ -76,7 +76,7 @@ public void parseYearMonthDay() {
DateTime.newBuilder()
.setValueUs(0)
.setPrecision(DateTime.Precision.DAY)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(parsed).isEqualTo(expected);

Expand All @@ -98,7 +98,29 @@ public void parseDateTime() {
DateTime.newBuilder()
.setValueUs(43200123000L)
.setPrecision(DateTime.Precision.MILLISECOND)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(parsed).isEqualTo(expected);

parsed =
new DateTimeWrapper("1970-01-01T12:00:00.123+00:00", ZoneId.of("Australia/Sydney"))
.getWrapped();
expected =
DateTime.newBuilder()
.setValueUs(43200123000L)
.setPrecision(DateTime.Precision.MILLISECOND)
.setTimezone("+00:00")
.build();
assertThat(parsed).isEqualTo(expected);

parsed =
new DateTimeWrapper("1970-01-01T12:00:00.123-00:00", ZoneId.of("Australia/Sydney"))
.getWrapped();
expected =
DateTime.newBuilder()
.setValueUs(43200123000L)
.setPrecision(DateTime.Precision.MILLISECOND)
.setTimezone("-00:00")
.build();
assertThat(parsed).isEqualTo(expected);

Expand All @@ -120,7 +142,7 @@ public void printYear() {
DateTime.newBuilder()
.setValueUs(0)
.setPrecision(DateTime.Precision.YEAR)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(new DateTimeWrapper(input).toString()).isEqualTo("1970");
assertThat(new DateTimeWrapper(input, ZoneId.of("Australia/Sydney")).toString())
Expand All @@ -138,7 +160,7 @@ public void printYearMonth() {
DateTime.newBuilder()
.setValueUs(0)
.setPrecision(DateTime.Precision.MONTH)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(new DateTimeWrapper(input).toString()).isEqualTo("1970-01");
assertThat(new DateTimeWrapper(input, ZoneId.of("Australia/Sydney")).toString())
Expand All @@ -156,7 +178,7 @@ public void printYearMonthDay() {
DateTime.newBuilder()
.setValueUs(0)
.setPrecision(DateTime.Precision.DAY)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(new DateTimeWrapper(input).toString()).isEqualTo("1970-01-01");
assertThat(new DateTimeWrapper(input, ZoneId.of("Australia/Sydney")).toString())
Expand All @@ -171,6 +193,15 @@ public void printYearMonthDay() {
@Test
public void printDateTime() {
DateTime input =
DateTime.newBuilder()
.setValueUs(43200123000L)
.setPrecision(DateTime.Precision.MILLISECOND)
.setTimezone("Z")
.build();
assertThat(new DateTimeWrapper(input, ZoneId.of("Europe/London")).toString())
.isEqualTo("1970-01-01T12:00:00.123Z");

input =
DateTime.newBuilder()
.setValueUs(43200123000L)
.setPrecision(DateTime.Precision.MILLISECOND)
Expand All @@ -196,5 +227,23 @@ public void printDateTime() {
.build();
assertThat(new DateTimeWrapper(input, ZoneId.of("Australia/Sydney")).toString())
.isEqualTo("2014-10-09T14:58:00");

input =
DateTime.newBuilder()
.setValueUs(43200123000L)
.setPrecision(DateTime.Precision.MILLISECOND)
.setTimezone("+00:00")
.build();
assertThat(new DateTimeWrapper(input, ZoneId.of("Europe/London")).toString())
.isEqualTo("1970-01-01T12:00:00.123+00:00");

input =
DateTime.newBuilder()
.setValueUs(43200123000L)
.setPrecision(DateTime.Precision.MILLISECOND)
.setTimezone("-00:00")
.build();
assertThat(new DateTimeWrapper(input, ZoneId.of("Europe/London")).toString())
.isEqualTo("1970-01-01T12:00:00.123-00:00");
}
}
12 changes: 6 additions & 6 deletions java/src/test/java/com/google/fhir/stu3/DateWrapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void parseYear() {
Date.newBuilder()
.setValueUs(31536000000000L)
.setPrecision(Date.Precision.YEAR)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(parsed).isEqualTo(expected);

Expand All @@ -55,7 +55,7 @@ public void parseYearMonth() {
Date.newBuilder()
.setValueUs(2678400000000L)
.setPrecision(Date.Precision.MONTH)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(parsed).isEqualTo(expected);

Expand All @@ -73,7 +73,7 @@ public void parseYearMonth() {
public void parseYearMonthDay() {
Date parsed = new DateWrapper("1970-01-01", ZoneOffset.UTC).getWrapped();
Date expected =
Date.newBuilder().setValueUs(0).setPrecision(Date.Precision.DAY).setTimezone("UTC").build();
Date.newBuilder().setValueUs(0).setPrecision(Date.Precision.DAY).setTimezone("Z").build();
assertThat(parsed).isEqualTo(expected);

parsed = new DateWrapper("1970-01-01", ZoneId.of("Australia/Sydney")).getWrapped();
Expand All @@ -92,7 +92,7 @@ public void printYear() {
Date.newBuilder()
.setValueUs(0)
.setPrecision(Date.Precision.YEAR)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(new DateWrapper(input).toString()).isEqualTo("1970");
assertThat(new DateWrapper(input, ZoneId.of("Australia/Sydney")).toString()).isEqualTo("1970");
Expand All @@ -108,7 +108,7 @@ public void printYearMonth() {
Date.newBuilder()
.setValueUs(0)
.setPrecision(Date.Precision.MONTH)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(new DateWrapper(input).toString()).isEqualTo("1970-01");
assertThat(new DateWrapper(input, ZoneId.of("Australia/Sydney")).toString())
Expand All @@ -123,7 +123,7 @@ public void printYearMonth() {
@Test
public void printYearMonthDay() {
Date input =
Date.newBuilder().setValueUs(0).setPrecision(Date.Precision.DAY).setTimezone("UTC").build();
Date.newBuilder().setValueUs(0).setPrecision(Date.Precision.DAY).setTimezone("Z").build();
assertThat(new DateWrapper(input).toString()).isEqualTo("1970-01-01");
assertThat(new DateWrapper(input, ZoneId.of("Australia/Sydney")).toString())
.isEqualTo("1970-01-01");
Expand Down
37 changes: 35 additions & 2 deletions java/src/test/java/com/google/fhir/stu3/InstantWrapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void parseInstant() {
Instant.newBuilder()
.setValueUs(0)
.setPrecision(Instant.Precision.SECOND)
.setTimezone("UTC")
.setTimezone("+00:00")
.build();
assertThat(parsed).isEqualTo(expected);

Expand All @@ -44,7 +44,16 @@ public void parseInstant() {
Instant.newBuilder()
.setValueUs(0)
.setPrecision(Instant.Precision.SECOND)
.setTimezone("UTC")
.setTimezone("Z")
.build();
assertThat(parsed).isEqualTo(expected);

parsed = new InstantWrapper("1970-01-01T00:00:00-00:00").getWrapped();
expected =
Instant.newBuilder()
.setValueUs(0)
.setPrecision(Instant.Precision.SECOND)
.setTimezone("-00:00")
.build();
assertThat(parsed).isEqualTo(expected);
}
Expand All @@ -53,6 +62,14 @@ public void parseInstant() {
public void printInstant() {
Instant input;

input =
Instant.newBuilder()
.setValueUs(0)
.setPrecision(Instant.Precision.SECOND)
.setTimezone("Z")
.build();
assertThat(new InstantWrapper(input).toString()).isEqualTo("1970-01-01T00:00:00Z");

input =
Instant.newBuilder()
.setValueUs(0)
Expand All @@ -61,6 +78,22 @@ public void printInstant() {
.build();
assertThat(new InstantWrapper(input).toString()).isEqualTo("1970-01-01T00:00:00Z");

input =
Instant.newBuilder()
.setValueUs(0)
.setPrecision(Instant.Precision.SECOND)
.setTimezone("+00:00")
.build();
assertThat(new InstantWrapper(input).toString()).isEqualTo("1970-01-01T00:00:00+00:00");

input =
Instant.newBuilder()
.setValueUs(0)
.setPrecision(Instant.Precision.SECOND)
.setTimezone("-00:00")
.build();
assertThat(new InstantWrapper(input).toString()).isEqualTo("1970-01-01T00:00:00-00:00");

input =
Instant.newBuilder()
.setValueUs(1412827080000000L)
Expand Down
2 changes: 1 addition & 1 deletion testdata/stu3/examples/activitydefinition-example.prototxt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ experimental {
}
date {
value_us: 1488549960000000
timezone: "UTC"
timezone: "Z"
precision: SECOND
}
publisher {
Expand Down
Loading

0 comments on commit 4769574

Please sign in to comment.