From 876f976294a04a86cda6c7fd5b3da5753566363d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20R=C3=A1tkai?= Date: Tue, 27 Jun 2023 10:09:52 +0100 Subject: [PATCH 1/3] Add XapiTimestamp utility class --- .../jackson/StrictTimestampDeserializer.java | 34 ++---------- .../jackson/model/strict/XapiTimestamp.java | 54 +++++++++++++++++++ 2 files changed, 59 insertions(+), 29 deletions(-) create mode 100644 xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java diff --git a/xapi-model/src/main/java/dev/learning/xapi/jackson/StrictTimestampDeserializer.java b/xapi-model/src/main/java/dev/learning/xapi/jackson/StrictTimestampDeserializer.java index 8c6ab469..4a43a221 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/jackson/StrictTimestampDeserializer.java +++ b/xapi-model/src/main/java/dev/learning/xapi/jackson/StrictTimestampDeserializer.java @@ -7,13 +7,9 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import dev.learning.xapi.jackson.model.strict.XapiTimestamp; import java.io.IOException; import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; /** * Strict Timestamp deserializer. @@ -33,34 +29,14 @@ protected StrictTimestampDeserializer(Class vc) { } /** - * {@inheritDoc} + * {@inheritDoc} Converts text to {@link java.time.Instant} using {@link XapiTimestamp}. + * + * @return {@link java.time.Instant} of text input */ @Override public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - final var text = p.getText(); - - // Negative zero offset is not permitted in ISO 8601 however it is permitted in RFC 3339 which - // considers the negative zero to indicate that the offset is unknown. - // The xAPI specification states that timestamps SHOULD be formatted per RFC 3339 however the - // conformance tests fail if negative zero offsets are accepted. - if (text.endsWith("-00") || text.endsWith("-0000") || text.endsWith("-00:00")) { - throw ctxt.instantiationException(handledType(), "Negative timezone offset can not be zero"); - } - - // Permit +00:00, +0000 and +00 - final var formatter = - new DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - .appendPattern("[XXXXX][XXXX][X]").toFormatter(); - - final var dt = formatter.parseBest(text, Instant::from, LocalDateTime::from); - - if (dt instanceof final Instant instant) { - return instant; - } else { - return Instant.from(ZonedDateTime.of((LocalDateTime) dt, ZoneOffset.UTC)); - } - + return XapiTimestamp.parse(p.getText()); } } diff --git a/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java b/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java new file mode 100644 index 00000000..a16d6602 --- /dev/null +++ b/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016-2023 Berry Cloud Ltd. All rights reserved. + */ + +package dev.learning.xapi.jackson.model.strict; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; + +/** + * Utility class for parsing ISO 8601 timestamps according to the strict xAPI rules. + * + * @author István Rátkai (Selindek) + */ +public class XapiTimestamp { + + private XapiTimestamp() { + // Should not be instantiated + } + + /** + * {@inheritDoc} Converts ISO 8601 string to {@link java.time.Instant}. + * + * @return {@link java.time.Instant} of text input + */ + public static Instant parse(String text) { + // Negative zero offset is not permitted in ISO 8601 however it is permitted in RFC 3339 which + // considers the negative zero to indicate that the offset is unknown. + // The xAPI specification states that timestamps should be formatted per RFC 3339 however the + // conformance tests fail if negative zero offsets are accepted. + if (text.endsWith("-00") || text.endsWith("-0000") || text.endsWith("-00:00")) { + throw new DateTimeException("Negative timezone offset can not be zero"); + } + + // Permit +00:00, +0000 and +00 + final var formatter = + new DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .appendPattern("[XXXXX][XXXX][X]").toFormatter(); + + final var dt = formatter.parseBest(text, Instant::from, LocalDateTime::from); + + if (dt instanceof final Instant instant) { + return instant; + } else { + return Instant.from(ZonedDateTime.of((LocalDateTime) dt, ZoneOffset.UTC)); + } + } + +} From d68d31a2511e1932479389a317585191d6b0da3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20R=C3=A1tkai?= Date: Tue, 27 Jun 2023 10:23:44 +0100 Subject: [PATCH 2/3] fixup --- .../xapi/jackson/StrictTimestampDeserializer.java | 7 ++++++- .../xapi/jackson/model/strict/XapiTimestamp.java | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/xapi-model/src/main/java/dev/learning/xapi/jackson/StrictTimestampDeserializer.java b/xapi-model/src/main/java/dev/learning/xapi/jackson/StrictTimestampDeserializer.java index 4a43a221..a8928979 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/jackson/StrictTimestampDeserializer.java +++ b/xapi-model/src/main/java/dev/learning/xapi/jackson/StrictTimestampDeserializer.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import dev.learning.xapi.jackson.model.strict.XapiTimestamp; +import dev.learning.xapi.jackson.model.strict.XapiTimestamp.XapiTimestampParseException; import java.io.IOException; import java.time.Instant; @@ -36,7 +37,11 @@ protected StrictTimestampDeserializer(Class vc) { @Override public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return XapiTimestamp.parse(p.getText()); + try { + return XapiTimestamp.parse(p.getText()); + } catch (final XapiTimestampParseException ex) { + throw ctxt.instantiationException(handledType(), ex.getMessage()); + } } } diff --git a/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java b/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java index a16d6602..11192b96 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java +++ b/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java @@ -4,7 +4,6 @@ package dev.learning.xapi.jackson.model.strict; -import java.time.DateTimeException; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -34,7 +33,7 @@ public static Instant parse(String text) { // The xAPI specification states that timestamps should be formatted per RFC 3339 however the // conformance tests fail if negative zero offsets are accepted. if (text.endsWith("-00") || text.endsWith("-0000") || text.endsWith("-00:00")) { - throw new DateTimeException("Negative timezone offset can not be zero"); + throw new XapiTimestampParseException("Negative timezone offset can not be zero"); } // Permit +00:00, +0000 and +00 @@ -51,4 +50,16 @@ public static Instant parse(String text) { } } + public static class XapiTimestampParseException extends RuntimeException { + + private static final long serialVersionUID = -3097189442067704841L; + + XapiTimestampParseException(String message) { + + super(message); + } + + } + + } From 323ce95007459fdc1d5c3c7c6bbc2e88f97e36bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20R=C3=A1tkai?= Date: Tue, 27 Jun 2023 10:26:33 +0100 Subject: [PATCH 3/3] fcs --- .../learning/xapi/jackson/model/strict/XapiTimestamp.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java b/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java index 11192b96..afc4120b 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java +++ b/xapi-model/src/main/java/dev/learning/xapi/jackson/model/strict/XapiTimestamp.java @@ -50,6 +50,11 @@ public static Instant parse(String text) { } } + /** + * Exception used to indicate a problem while parsing a timestamp in strict xAPI mode. + * + * @author István Rátkai (Selindek) + */ public static class XapiTimestampParseException extends RuntimeException { private static final long serialVersionUID = -3097189442067704841L;