From 7d85d19f1a6bed2beec36daba09f5ce3009003f3 Mon Sep 17 00:00:00 2001 From: Matej Vasek Date: Thu, 18 Jun 2020 16:59:12 +0200 Subject: [PATCH] fix: sub-second resolution `time` attribute (#176) * fix: sub-second resolution `time` attribute Fixes parsing of dates like: 2018-04-26T14:48:09.1234Z Signed-off-by: Matej Vasek * test: added test for datetime parsing formatting Signed-off-by: Matej Vasek --- api/pom.xml | 12 +++++ .../main/java/io/cloudevents/types/Time.java | 8 ++- .../java/io/cloudevents/types/TimeTest.java | 53 +++++++++++++++++++ .../java/io/cloudevents/core/test/Data.java | 9 ++++ .../cloudevents/jackson/JsonFormatTest.java | 1 + .../v1/json_data_with_fractional_time.json | 11 ++++ 6 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 api/src/test/java/io/cloudevents/types/TimeTest.java create mode 100644 formats/json-jackson/src/test/resources/v1/json_data_with_fractional_time.json diff --git a/api/pom.xml b/api/pom.xml index a93b2f30..a919f104 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -40,6 +40,18 @@ provided true + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + test + + + org.assertj + assertj-core + ${assertj-core.version} + test + diff --git a/api/src/main/java/io/cloudevents/types/Time.java b/api/src/main/java/io/cloudevents/types/Time.java index dbf2a8f6..6633b3b8 100644 --- a/api/src/main/java/io/cloudevents/types/Time.java +++ b/api/src/main/java/io/cloudevents/types/Time.java @@ -19,10 +19,16 @@ package io.cloudevents.types; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; public final class Time { - public static final DateTimeFormatter RFC3339_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"); + public static final DateTimeFormatter RFC3339_DATE_FORMAT = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd'T'HH:mm:ss") + .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) + .appendZoneOrOffsetId() + .toFormatter(); public static ZonedDateTime parseTime(String time) throws DateTimeParseException { return ZonedDateTime.parse(time, RFC3339_DATE_FORMAT); diff --git a/api/src/test/java/io/cloudevents/types/TimeTest.java b/api/src/test/java/io/cloudevents/types/TimeTest.java new file mode 100644 index 00000000..4f08ea5f --- /dev/null +++ b/api/src/test/java/io/cloudevents/types/TimeTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018-Present The CloudEvents Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.cloudevents.types; + + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.time.ZonedDateTime; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TimeTest { + + @ParameterizedTest + @MethodSource("parseDateArguments") + void testParseAndFormatDate(String ts) { + ZonedDateTime zonedDateTime = Time.parseTime(ts); + assertThat(ts).isEqualTo(zonedDateTime.format(Time.RFC3339_DATE_FORMAT)); + } + + @Test + void testParseDateOffset() { + assertThat(Time.parseTime("1937-01-01T12:20:27.87+00:20")) + .isEqualTo("1937-01-01T12:00:27.87Z"); + } + + public static Stream parseDateArguments() { + return Stream.of( + Arguments.of("1985-04-12T23:20:50.52Z"), + Arguments.of("1990-12-31T23:59:00Z"), + Arguments.of("1990-12-31T15:59:00-08:00"), + Arguments.of("1937-01-01T12:00:27.87+00:20") + ); + } +} diff --git a/core/src/test/java/io/cloudevents/core/test/Data.java b/core/src/test/java/io/cloudevents/core/test/Data.java index 7dfc6442..f579155c 100644 --- a/core/src/test/java/io/cloudevents/core/test/Data.java +++ b/core/src/test/java/io/cloudevents/core/test/Data.java @@ -57,6 +57,15 @@ public class Data { .withTime(TIME) .build(); + public static final CloudEvent V1_WITH_JSON_DATA_WITH_FRACTIONAL_TIME = CloudEventBuilder.v1() + .withId(ID) + .withType(TYPE) + .withSource(SOURCE) + .withData(DATACONTENTTYPE_JSON, DATASCHEMA, DATA_JSON_SERIALIZED) + .withSubject(SUBJECT) + .withTime(Time.parseTime("2018-04-26T14:48:09.1234Z")) + .build(); + public static final CloudEvent V1_WITH_JSON_DATA_WITH_EXT = CloudEventBuilder.v1() .withId(ID) .withType(TYPE) diff --git a/formats/json-jackson/src/test/java/io/cloudevents/jackson/JsonFormatTest.java b/formats/json-jackson/src/test/java/io/cloudevents/jackson/JsonFormatTest.java index 8c96aa2b..c2257edb 100644 --- a/formats/json-jackson/src/test/java/io/cloudevents/jackson/JsonFormatTest.java +++ b/formats/json-jackson/src/test/java/io/cloudevents/jackson/JsonFormatTest.java @@ -115,6 +115,7 @@ class JsonFormatTest { Arguments.of(V03_WITH_TEXT_DATA, "v03/base64_text_data.json"), Arguments.of(V1_MIN, "v1/min.json"), Arguments.of(V1_WITH_JSON_DATA, "v1/json_data.json"), + Arguments.of(V1_WITH_JSON_DATA_WITH_FRACTIONAL_TIME, "v1/json_data_with_fractional_time.json"), Arguments.of(V1_WITH_JSON_DATA_WITH_EXT, "v1/json_data_with_ext.json"), Arguments.of(V1_WITH_XML_DATA, "v1/base64_xml_data.json"), Arguments.of(V1_WITH_TEXT_DATA, "v1/base64_text_data.json") diff --git a/formats/json-jackson/src/test/resources/v1/json_data_with_fractional_time.json b/formats/json-jackson/src/test/resources/v1/json_data_with_fractional_time.json new file mode 100644 index 00000000..23a8fcb4 --- /dev/null +++ b/formats/json-jackson/src/test/resources/v1/json_data_with_fractional_time.json @@ -0,0 +1,11 @@ +{ + "specversion": "1.0", + "id": "1", + "type": "mock.test", + "source": "http://localhost/source", + "dataschema": "http://localhost/schema", + "datacontenttype": "application/json", + "data": {}, + "subject": "sub", + "time": "2018-04-26T14:48:09.1234Z" +}