Skip to content

Commit 6012785

Browse files
TomMarkuskerobert-s-ubi
authored andcommitted
Ensure millis precision in timestamps of outgoing messages
ISO8601 does not specify that fractional seconds shall be omitted if given. Enforce that an on full seconds, the millis resolution is retained for outgoing messages and that incoming messages of any precision can be parsed. On incoming messages with... * ...seconds precision, assume that millis are zero. * ...millis precision, assume that micros are zero. * ...micros precision, assume that nanos are zero. This retains proper timestamp comparison.
1 parent 32903d4 commit 6012785

2 files changed

Lines changed: 70 additions & 1 deletion

File tree

OCPP-J/src/main/java/eu/chargetime/ocpp/JSONCommunicator.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.lang.reflect.Type;
1010
import java.time.ZonedDateTime;
1111
import java.time.format.DateTimeFormatter;
12+
import java.time.format.DateTimeFormatterBuilder;
13+
import java.time.format.ResolverStyle;
1214
import org.slf4j.Logger;
1315
import org.slf4j.LoggerFactory;
1416

@@ -46,6 +48,16 @@ public class JSONCommunicator extends Communicator {
4648

4749
private static final Logger logger = LoggerFactory.getLogger(JSONCommunicator.class);
4850

51+
/**
52+
* a derivation of ISO_INSTANT that always serializes to three millisecond digits, even if zero
53+
*/
54+
public static final DateTimeFormatter ISO_INSTANT_WITH_MILLIS_PRECISION =
55+
new DateTimeFormatterBuilder()
56+
.parseCaseInsensitive()
57+
.appendInstant(3)
58+
.toFormatter()
59+
.withResolverStyle(ResolverStyle.STRICT);
60+
4961
private static final int INDEX_MESSAGEID = 0;
5062
private static final int TYPENUMBER_CALL = 2;
5163
private static final int INDEX_CALL_ACTION = 2;
@@ -94,7 +106,7 @@ private static class ZonedDateTimeSerializer
94106
@Override
95107
public JsonElement serialize(
96108
ZonedDateTime zonedDateTime, Type type, JsonSerializationContext jsonSerializationContext) {
97-
return new JsonPrimitive(zonedDateTime.format(DateTimeFormatter.ISO_INSTANT));
109+
return new JsonPrimitive(zonedDateTime.format(ISO_INSTANT_WITH_MILLIS_PRECISION));
98110
}
99111

100112
@Override

ocpp-v1_6/src/test/java/eu/chargetime/ocpp/test/JSONCommunicatorTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,22 @@ public void unpackPayload_aCalendarPayload_returnsTestModelWithACalendar() throw
102102
assertThat(model.getCalendarTest().compareTo(someDate), is(0));
103103
}
104104

105+
@Test
106+
public void unpackPayload_aCalendarPayload_parsesNoFractionalAsZeroFractional() throws Exception {
107+
// Given
108+
String aCalendar = "2016-04-28T07:16:11Z";
109+
String payload = "{\"calendarTest\":\"%s\"}";
110+
111+
ZonedDateTime someDate = ZonedDateTime.parse("2016-04-28T07:16:11.000Z");
112+
113+
// When
114+
TestModel model =
115+
communicator.unpackPayload(String.format(payload, aCalendar), TestModel.class);
116+
117+
// Then
118+
assertThat(model.getCalendarTest().compareTo(someDate), is(0));
119+
}
120+
105121
@Test
106122
public void unpackPayload_anIntegerPayload_returnsTestModelWithAnInteger() throws Exception {
107123
// Given
@@ -276,6 +292,24 @@ public void pack_bootNotificationRequest_returnsBootNotificationRequestPayload()
276292
assertThat(payload, equalTo(expected));
277293
}
278294

295+
@Test
296+
public void pack_bootNotificationConfirmation_limitsTimeFractionalDigitsToThree()
297+
throws Exception {
298+
// Given
299+
String expected =
300+
"{\"currentTime\":\"2016-04-28T06:41:13.123Z\",\"interval\":300,\"status\":\"Accepted\"}";
301+
BootNotificationConfirmation confirmation = new BootNotificationConfirmation();
302+
confirmation.setCurrentTime(createDateTimeInNanos(1461825673123456789L)); // will be truncated
303+
confirmation.setInterval(300);
304+
confirmation.setStatus(RegistrationStatus.Accepted);
305+
306+
// When
307+
Object payload = communicator.packPayload(confirmation);
308+
309+
// Then
310+
assertThat(payload, equalTo(expected));
311+
}
312+
279313
@Test
280314
public void pack_bootNotificationConfirmation_returnsBootNotificationConfirmationPayload()
281315
throws Exception {
@@ -294,6 +328,24 @@ public void pack_bootNotificationConfirmation_returnsBootNotificationConfirmatio
294328
assertThat(payload, equalTo(expected));
295329
}
296330

331+
@Test
332+
public void pack_bootNotificationConfirmation_alwaysFormatsTimeWithThreeFractionalDigits()
333+
throws Exception {
334+
// Given
335+
String expected =
336+
"{\"currentTime\":\"2016-04-28T06:41:13.000Z\",\"interval\":300,\"status\":\"Accepted\"}";
337+
BootNotificationConfirmation confirmation = new BootNotificationConfirmation();
338+
confirmation.setCurrentTime(createDateTimeInMillis(1461825673000L)); // will not be truncated
339+
confirmation.setInterval(300);
340+
confirmation.setStatus(RegistrationStatus.Accepted);
341+
342+
// When
343+
Object payload = communicator.packPayload(confirmation);
344+
345+
// Then
346+
assertThat(payload, equalTo(expected));
347+
}
348+
297349
@Test
298350
public void disconnect_disconnects() {
299351
// When
@@ -319,4 +371,9 @@ public void sendError_transmitsError() throws Exception {
319371
private ZonedDateTime createDateTimeInMillis(long dateInMillis) {
320372
return Instant.ofEpochMilli(dateInMillis).atOffset(ZoneOffset.UTC).toZonedDateTime();
321373
}
374+
375+
private ZonedDateTime createDateTimeInNanos(long dateInNanos) {
376+
return Instant.ofEpochSecond(dateInNanos / 1000000000, dateInNanos % 1000000000)
377+
.atOffset(ZoneOffset.UTC).toZonedDateTime();
378+
}
322379
}

0 commit comments

Comments
 (0)