1616
1717package com .networknt .schema ;
1818
19+ import com .ethlo .time .ITU ;
20+ import com .ethlo .time .LeapSecondException ;
1921import com .fasterxml .jackson .databind .JsonNode ;
2022import org .slf4j .Logger ;
2123import org .slf4j .LoggerFactory ;
2224
23- import java .text .ParsePosition ;
24- import java .text .SimpleDateFormat ;
25+ import java .time .LocalDate ;
2526import java .util .Collections ;
2627import java .util .LinkedHashSet ;
2728import java .util .Set ;
28- import java .util .TimeZone ;
29- import java .util .regex .Matcher ;
30- import java .util .regex .Pattern ;
3129
3230public class DateTimeValidator extends BaseJsonValidator implements JsonValidator {
3331 private static final Logger logger = LoggerFactory .getLogger (DateTimeValidator .class );
@@ -38,11 +36,6 @@ public class DateTimeValidator extends BaseJsonValidator implements JsonValidato
3836 private final String DATE = "date" ;
3937 private final String DATETIME = "date-time" ;
4038
41- private static final Pattern RFC3339_PATTERN = Pattern .compile (
42- "^(\\ d{4})-(\\ d{2})-(\\ d{2})" // yyyy-MM-dd
43- + "([Tt](\\ d{2}):(\\ d{2}):(\\ d{2})(\\ .\\ d+)?" // 'T'HH:mm:ss.milliseconds
44- + "(([Zz])|([+-])(\\ d{2}):(\\ d{2})))?" );
45-
4639 public DateTimeValidator (String schemaPath , JsonNode schemaNode , JsonSchema parentSchema , ValidationContext validationContext , String formatName ) {
4740 super (schemaPath , schemaNode , parentSchema , ValidatorTypeCode .DATETIME , validationContext );
4841 this .formatName = formatName ;
@@ -66,74 +59,26 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
6659 }
6760
6861 private boolean isLegalDateTime (String string ) {
69- Matcher matcher = RFC3339_PATTERN .matcher (string );
70- StringBuilder pattern = new StringBuilder ();
71- StringBuilder dateTime = new StringBuilder ();
72- // Validate the format
73- if (!matcher .matches ()) {
74- logger .error ("Failed to apply RFC3339 pattern on " + string );
75- return false ;
62+ if (formatName .equals (DATE )) {
63+ return tryParse (() -> LocalDate .parse (string ));
64+ } else if (formatName .equals (DATETIME )) {
65+ return tryParse (() -> {
66+ try {
67+ ITU .parseDateTime (string );
68+ } catch (LeapSecondException ignored ) {}
69+ });
70+ } else {
71+ throw new IllegalStateException ("Unknown format: " + formatName );
7672 }
77- // Validate the date/time content
78- String year = matcher .group (1 );
79- String month = matcher .group (2 );
80- String day = matcher .group (3 );
81- dateTime .append (year ).append ('-' ).append (month ).append ('-' ).append (day );
82- pattern .append ("yyyy-MM-dd" );
83-
84- boolean isTimeGiven = matcher .group (4 ) != null ;
85- boolean isOffsetZuluTime = matcher .group (10 ) != null ;
86- String hour = null ;
87- String minute = null ;
88- String second = null ;
89- String milliseconds = null ;
90- String timeShiftSign = null ;
91- String timeShiftHour = null ;
92- String timeShiftMinute = null ;
73+ }
9374
94- if (!isTimeGiven && DATETIME .equals (formatName ) || (isTimeGiven && DATE .equals (formatName ))) {
95- logger .error ("The supplied date/time format type does not match the specification, expected: " + formatName );
75+ private boolean tryParse (Runnable parser ) {
76+ try {
77+ parser .run ();
78+ return true ;
79+ } catch (Exception ex ) {
80+ logger .error ("Invalid " + formatName + ": " + ex .getMessage ());
9681 return false ;
9782 }
98-
99- if (isTimeGiven ) {
100- hour = matcher .group (5 );
101- minute = matcher .group (6 );
102- second = matcher .group (7 );
103- dateTime .append ('T' ).append (hour ).append (':' ).append (minute ).append (':' ).append (second );
104- pattern .append ("'T'HH:mm:ss" );
105- if (matcher .group (8 ) != null ) {
106- // Normalize milliseconds to 3-length digit
107- milliseconds = matcher .group (8 );
108- if (milliseconds .length () > 4 ) {
109- milliseconds = milliseconds .substring (0 , 4 );
110- } else {
111- while (milliseconds .length () < 4 ) {
112- milliseconds += "0" ;
113- }
114- }
115- dateTime .append (milliseconds );
116- pattern .append (".SSS" );
117- }
118-
119- if (isOffsetZuluTime ) {
120- dateTime .append ('Z' );
121- pattern .append ("'Z'" );
122- } else {
123- timeShiftSign = matcher .group (11 );
124- timeShiftHour = matcher .group (12 );
125- timeShiftMinute = matcher .group (13 );
126- dateTime .append (timeShiftSign ).append (timeShiftHour ).append (':' ).append (timeShiftMinute );
127- pattern .append ("XXX" );
128- }
129- }
130- return validateDateTime (dateTime .toString (), pattern .toString ());
131- }
132-
133- private boolean validateDateTime (String dateTime , String pattern ) {
134- SimpleDateFormat sdf = new SimpleDateFormat (pattern );
135- sdf .setLenient (false );
136- sdf .setTimeZone (TimeZone .getTimeZone ("UTC" ));
137- return sdf .parse (dateTime , new ParsePosition (0 )) != null ;
13883 }
13984}
0 commit comments