diff --git a/pom.xml b/pom.xml
index 90e864e9f..e14286055 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,6 +71,7 @@
2.7.21
2.2
2.2.14.Final
+ 1.3.0
@@ -94,6 +95,11 @@
true
${version.joni}
+
+ com.ethlo.time
+ itu
+ ${version.itu}
+
ch.qos.logback
logback-classic
diff --git a/src/main/java/com/networknt/schema/DateTimeValidator.java b/src/main/java/com/networknt/schema/DateTimeValidator.java
index 38f98731d..db3d4bae3 100644
--- a/src/main/java/com/networknt/schema/DateTimeValidator.java
+++ b/src/main/java/com/networknt/schema/DateTimeValidator.java
@@ -16,18 +16,16 @@
package com.networknt.schema;
+import com.ethlo.time.ITU;
+import com.ethlo.time.LeapSecondException;
import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.text.ParsePosition;
-import java.text.SimpleDateFormat;
+import java.time.LocalDate;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
-import java.util.TimeZone;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
public class DateTimeValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(DateTimeValidator.class);
@@ -38,11 +36,6 @@ public class DateTimeValidator extends BaseJsonValidator implements JsonValidato
private final String DATE = "date";
private final String DATETIME = "date-time";
- private static final Pattern RFC3339_PATTERN = Pattern.compile(
- "^(\\d{4})-(\\d{2})-(\\d{2})" // yyyy-MM-dd
- + "([Tt](\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?" // 'T'HH:mm:ss.milliseconds
- + "(([Zz])|([+-])(\\d{2}):(\\d{2})))?");
-
public DateTimeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, String formatName) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DATETIME, validationContext);
this.formatName = formatName;
@@ -66,74 +59,26 @@ public Set validate(JsonNode node, JsonNode rootNode, String
}
private boolean isLegalDateTime(String string) {
- Matcher matcher = RFC3339_PATTERN.matcher(string);
- StringBuilder pattern = new StringBuilder();
- StringBuilder dateTime = new StringBuilder();
- // Validate the format
- if (!matcher.matches()) {
- logger.error("Failed to apply RFC3339 pattern on " + string);
- return false;
+ if(formatName.equals(DATE)) {
+ return tryParse(() -> LocalDate.parse(string));
+ } else if(formatName.equals(DATETIME)) {
+ return tryParse(() -> {
+ try {
+ ITU.parseDateTime(string);
+ } catch (LeapSecondException ignored) {}
+ });
+ } else {
+ throw new IllegalStateException("Unknown format: " + formatName);
}
- // Validate the date/time content
- String year = matcher.group(1);
- String month = matcher.group(2);
- String day = matcher.group(3);
- dateTime.append(year).append('-').append(month).append('-').append(day);
- pattern.append("yyyy-MM-dd");
-
- boolean isTimeGiven = matcher.group(4) != null;
- boolean isOffsetZuluTime = matcher.group(10) != null;
- String hour = null;
- String minute = null;
- String second = null;
- String milliseconds = null;
- String timeShiftSign = null;
- String timeShiftHour = null;
- String timeShiftMinute = null;
+ }
- if (!isTimeGiven && DATETIME.equals(formatName) || (isTimeGiven && DATE.equals(formatName))) {
- logger.error("The supplied date/time format type does not match the specification, expected: " + formatName);
+ private boolean tryParse(Runnable parser) {
+ try {
+ parser.run();
+ return true;
+ } catch (Exception ex) {
+ logger.error("Invalid " + formatName + ": " + ex.getMessage());
return false;
}
-
- if (isTimeGiven) {
- hour = matcher.group(5);
- minute = matcher.group(6);
- second = matcher.group(7);
- dateTime.append('T').append(hour).append(':').append(minute).append(':').append(second);
- pattern.append("'T'HH:mm:ss");
- if (matcher.group(8) != null) {
- // Normalize milliseconds to 3-length digit
- milliseconds = matcher.group(8);
- if (milliseconds.length() > 4) {
- milliseconds = milliseconds.substring(0, 4);
- } else {
- while (milliseconds.length() < 4) {
- milliseconds += "0";
- }
- }
- dateTime.append(milliseconds);
- pattern.append(".SSS");
- }
-
- if (isOffsetZuluTime) {
- dateTime.append('Z');
- pattern.append("'Z'");
- } else {
- timeShiftSign = matcher.group(11);
- timeShiftHour = matcher.group(12);
- timeShiftMinute = matcher.group(13);
- dateTime.append(timeShiftSign).append(timeShiftHour).append(':').append(timeShiftMinute);
- pattern.append("XXX");
- }
- }
- return validateDateTime(dateTime.toString(), pattern.toString());
- }
-
- private boolean validateDateTime(String dateTime, String pattern) {
- SimpleDateFormat sdf = new SimpleDateFormat(pattern);
- sdf.setLenient(false);
- sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
- return sdf.parse(dateTime, new ParsePosition(0)) != null;
}
}
diff --git a/src/test/java/com/networknt/schema/V4JsonSchemaTest.java b/src/test/java/com/networknt/schema/V4JsonSchemaTest.java
index f0d37d3b1..9e757ffe7 100644
--- a/src/test/java/com/networknt/schema/V4JsonSchemaTest.java
+++ b/src/test/java/com/networknt/schema/V4JsonSchemaTest.java
@@ -42,7 +42,6 @@ public void testLoadingWithId() throws Exception {
}
@Test
- @Disabled
public void testFormatDateTimeValidator() throws Exception {
runTestFile("draft4/optional/format/date-time.json");
}
diff --git a/src/test/resources/draft2019-09/optional/format/date-time.json b/src/test/resources/draft2019-09/optional/format/date-time.json
index 11af9acaf..b183a8d02 100644
--- a/src/test/resources/draft2019-09/optional/format/date-time.json
+++ b/src/test/resources/draft2019-09/optional/format/date-time.json
@@ -64,6 +64,16 @@
"description": "an invalid date-time string without colon in offset",
"data": "1963-06-19T08:30:06+0200",
"valid": false
+ },
+ {
+ "description": "a valid date-time string with leap second",
+ "data": "1998-12-31T23:59:60Z",
+ "valid": true
+ },
+ {
+ "description": "an invalid date-time string with leap second",
+ "data": "1998-10-31T23:59:60Z",
+ "valid": false
}
]
}
diff --git a/src/test/resources/draft7/optional/format/date-time.json b/src/test/resources/draft7/optional/format/date-time.json
index 11af9acaf..b183a8d02 100644
--- a/src/test/resources/draft7/optional/format/date-time.json
+++ b/src/test/resources/draft7/optional/format/date-time.json
@@ -64,6 +64,16 @@
"description": "an invalid date-time string without colon in offset",
"data": "1963-06-19T08:30:06+0200",
"valid": false
+ },
+ {
+ "description": "a valid date-time string with leap second",
+ "data": "1998-12-31T23:59:60Z",
+ "valid": true
+ },
+ {
+ "description": "an invalid date-time string with leap second",
+ "data": "1998-10-31T23:59:60Z",
+ "valid": false
}
]
}