diff --git a/OCPP-J/src/main/java/eu/chargetime/ocpp/JSONCommunicator.java b/OCPP-J/src/main/java/eu/chargetime/ocpp/JSONCommunicator.java index 15e937d0..01f3147f 100644 --- a/OCPP-J/src/main/java/eu/chargetime/ocpp/JSONCommunicator.java +++ b/OCPP-J/src/main/java/eu/chargetime/ocpp/JSONCommunicator.java @@ -9,6 +9,8 @@ import java.lang.reflect.Type; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.ResolverStyle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,6 +48,16 @@ public class JSONCommunicator extends Communicator { private static final Logger logger = LoggerFactory.getLogger(JSONCommunicator.class); + /** + * a derivation of ISO_INSTANT that always serializes to three millisecond digits, even if zero + */ + public static final DateTimeFormatter ISO_INSTANT_WITH_MILLIS_PRECISION = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendInstant(3) + .toFormatter() + .withResolverStyle(ResolverStyle.STRICT); + private static final int INDEX_MESSAGEID = 0; private static final int TYPENUMBER_CALL = 2; private static final int INDEX_CALL_ACTION = 2; @@ -94,7 +106,7 @@ private static class ZonedDateTimeSerializer @Override public JsonElement serialize( ZonedDateTime zonedDateTime, Type type, JsonSerializationContext jsonSerializationContext) { - return new JsonPrimitive(zonedDateTime.format(DateTimeFormatter.ISO_INSTANT)); + return new JsonPrimitive(zonedDateTime.format(ISO_INSTANT_WITH_MILLIS_PRECISION)); } @Override diff --git a/ocpp-v1_6/src/test/java/eu/chargetime/ocpp/test/JSONCommunicatorTest.java b/ocpp-v1_6/src/test/java/eu/chargetime/ocpp/test/JSONCommunicatorTest.java index 0436cf22..da2b4b70 100644 --- a/ocpp-v1_6/src/test/java/eu/chargetime/ocpp/test/JSONCommunicatorTest.java +++ b/ocpp-v1_6/src/test/java/eu/chargetime/ocpp/test/JSONCommunicatorTest.java @@ -102,6 +102,22 @@ public void unpackPayload_aCalendarPayload_returnsTestModelWithACalendar() throw assertThat(model.getCalendarTest().compareTo(someDate), is(0)); } + @Test + public void unpackPayload_aCalendarPayload_parsesNoFractionalAsZeroFractional() throws Exception { + // Given + String aCalendar = "2016-04-28T07:16:11Z"; + String payload = "{\"calendarTest\":\"%s\"}"; + + ZonedDateTime someDate = ZonedDateTime.parse("2016-04-28T07:16:11.000Z"); + + // When + TestModel model = + communicator.unpackPayload(String.format(payload, aCalendar), TestModel.class); + + // Then + assertThat(model.getCalendarTest().compareTo(someDate), is(0)); + } + @Test public void unpackPayload_anIntegerPayload_returnsTestModelWithAnInteger() throws Exception { // Given @@ -276,6 +292,24 @@ public void pack_bootNotificationRequest_returnsBootNotificationRequestPayload() assertThat(payload, equalTo(expected)); } + @Test + public void pack_bootNotificationConfirmation_limitsTimeFractionalDigitsToThree() + throws Exception { + // Given + String expected = + "{\"currentTime\":\"2016-04-28T06:41:13.123Z\",\"interval\":300,\"status\":\"Accepted\"}"; + BootNotificationConfirmation confirmation = new BootNotificationConfirmation(); + confirmation.setCurrentTime(createDateTimeInNanos(1461825673123456789L)); // will be truncated + confirmation.setInterval(300); + confirmation.setStatus(RegistrationStatus.Accepted); + + // When + Object payload = communicator.packPayload(confirmation); + + // Then + assertThat(payload, equalTo(expected)); + } + @Test public void pack_bootNotificationConfirmation_returnsBootNotificationConfirmationPayload() throws Exception { @@ -294,6 +328,24 @@ public void pack_bootNotificationConfirmation_returnsBootNotificationConfirmatio assertThat(payload, equalTo(expected)); } + @Test + public void pack_bootNotificationConfirmation_alwaysFormatsTimeWithThreeFractionalDigits() + throws Exception { + // Given + String expected = + "{\"currentTime\":\"2016-04-28T06:41:13.000Z\",\"interval\":300,\"status\":\"Accepted\"}"; + BootNotificationConfirmation confirmation = new BootNotificationConfirmation(); + confirmation.setCurrentTime(createDateTimeInMillis(1461825673000L)); // will not be truncated + confirmation.setInterval(300); + confirmation.setStatus(RegistrationStatus.Accepted); + + // When + Object payload = communicator.packPayload(confirmation); + + // Then + assertThat(payload, equalTo(expected)); + } + @Test public void disconnect_disconnects() { // When @@ -319,4 +371,9 @@ public void sendError_transmitsError() throws Exception { private ZonedDateTime createDateTimeInMillis(long dateInMillis) { return Instant.ofEpochMilli(dateInMillis).atOffset(ZoneOffset.UTC).toZonedDateTime(); } + + private ZonedDateTime createDateTimeInNanos(long dateInNanos) { + return Instant.ofEpochSecond(dateInNanos / 1000000000, dateInNanos % 1000000000) + .atOffset(ZoneOffset.UTC).toZonedDateTime(); + } }