Skip to content

Commit

Permalink
change DateTimeFormatter (closes #13)
Browse files Browse the repository at this point in the history
  • Loading branch information
goekay committed Dec 3, 2023
1 parent 0db21a2 commit 1366703
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 1 deletion.
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,18 @@
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
63 changes: 62 additions & 1 deletion src/main/java/de/rwth/idsg/ocpp/jaxb/JodaDateTimeConverter.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package de.rwth.idsg.ocpp.jaxb;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;

import javax.xml.bind.annotation.adapters.XmlAdapter;

import static org.joda.time.format.ISODateTimeFormat.date;

/**
* Joda-Time and XSD represent data and time information according to ISO 8601.
*
Expand All @@ -12,12 +16,14 @@
*/
public class JodaDateTimeConverter extends XmlAdapter<String, DateTime> {

private static final DateTimeFormatter formatter = dateTimeParser();

@Override
public DateTime unmarshal(String v) throws Exception {
if (isNullOrEmpty(v)) {
return null;
} else {
return new DateTime(v);
return DateTime.parse(v, formatter);
}
}

Expand All @@ -36,4 +42,59 @@ public String marshal(DateTime v) throws Exception {
private static boolean isNullOrEmpty(String string) {
return string == null || string.isEmpty();
}

/**
* A custom DateTimeFormatter that follows the strictness and flexibility of XSD:dateTime (ISO 8601).
* This exact composition (with optional fields) is not present under {@link org.joda.time.format.ISODateTimeFormat}.
*/
private static DateTimeFormatter dateTimeParser() {
return new DateTimeFormatterBuilder()
.append(date())
.appendLiteral('T')
.append(hourElement())
.append(minuteElement())
.append(secondElement())
.appendOptional(fractionElement().getParser())
.appendOptional(offsetElement().getParser())
.toFormatter();
}

// -------------------------------------------------------------------------
// Copy-paste from "private" methods in ISODateTimeFormat
// -------------------------------------------------------------------------

private static DateTimeFormatter hourElement() {
return new DateTimeFormatterBuilder()
.appendHourOfDay(2)
.toFormatter();
}

private static DateTimeFormatter minuteElement() {
return new DateTimeFormatterBuilder()
.appendLiteral(':')
.appendMinuteOfHour(2)
.toFormatter();
}

private static DateTimeFormatter secondElement() {
return new DateTimeFormatterBuilder()
.appendLiteral(':')
.appendSecondOfMinute(2)
.toFormatter();
}

private static DateTimeFormatter fractionElement() {
return new DateTimeFormatterBuilder()
.appendLiteral('.')
// Support parsing up to nanosecond precision even though
// those extra digits will be dropped.
.appendFractionOfSecond(3, 9)
.toFormatter();
}

private static DateTimeFormatter offsetElement() {
return new DateTimeFormatterBuilder()
.appendTimeZoneOffset("Z", true, 2, 4)
.toFormatter();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package de.rwth.idsg.ocpp.jaxb;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.jupiter.api.Assertions;
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.util.TimeZone;
import java.util.stream.Stream;

public class JodaDateTimeConverterTest {

private final JodaDateTimeConverter converter = new JodaDateTimeConverter();

static {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
DateTimeZone.setDefault(DateTimeZone.forID("UTC"));
}

// -------------------------------------------------------------------------
// Marshal
// -------------------------------------------------------------------------

@Test
public void testMarshallNullInput() throws Exception {
String val = converter.marshal(null);
Assertions.assertNull(val);
}

@ParameterizedTest
@MethodSource("provideValidInput")
public void testMarshallEmptyInput(String val, String expected) throws Exception {
DateTime input = converter.unmarshal(val);
String output = converter.marshal(input);
Assertions.assertEquals(expected, output);
}

// -------------------------------------------------------------------------
// Unmarshal
// -------------------------------------------------------------------------

@Test
public void testUnmarshallNullInput() throws Exception {
converter.unmarshal(null);
}

@Test
public void testUnmarshallEmptyInput() throws Exception {
converter.unmarshal("");
}

@ParameterizedTest
@MethodSource("provideValidInput")
public void testUnmarshalValid(String val) throws Exception {
converter.unmarshal(val);
}

@ParameterizedTest
@MethodSource("provideInvalidInput")
public void testUnmarshalInvalid(String val) {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
converter.unmarshal(val);
});
}

/**
* First argument is used for marshaling only.
* Both arguments are used for unmarshaling: We use the second as the expected output of formatting.
*/
private static Stream<Arguments> provideValidInput() {
return Stream.of(
Arguments.of("2022-06-30T01:20:52", "2022-06-30T01:20:52.000Z"),
Arguments.of("2022-06-30T01:20:52+02:00", "2022-06-29T23:20:52.000Z"),
Arguments.of("2022-06-30T01:20:52Z", "2022-06-30T01:20:52.000Z"),
Arguments.of("2022-06-30T01:20:52+00:00", "2022-06-30T01:20:52.000Z"),
Arguments.of("2022-06-30T01:20:52.126", "2022-06-30T01:20:52.126Z"),
Arguments.of("2022-06-30T01:20:52.126+05:00", "2022-06-29T20:20:52.126Z"),
Arguments.of("2018-11-13T20:20:39+00:00", "2018-11-13T20:20:39.000Z"),
Arguments.of("-2022-06-30T01:20:52", "-2022-06-30T01:20:52.000Z")
);
}

private static Stream<Arguments> provideInvalidInput() {
return Stream.of(
Arguments.of("-1"),
Arguments.of("10000"), // https://github.com/steve-community/steve/issues/1292
Arguments.of("text"),
Arguments.of("2022-06-30"), // no time
Arguments.of("2022-06-30T01:20"), // seconds are required
Arguments.of("2022-06-30T25:20:34"), // hour out of range
Arguments.of("22-06-30T25:20:34") // year not YYYY-format
);
}
}

0 comments on commit 1366703

Please sign in to comment.