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"
+}