Skip to content

Commit d273e06

Browse files
committed
Used joda-time lib for RFC3339DateTimeAttribute. Added more unit tests
1 parent 01d2d64 commit d273e06

File tree

2 files changed

+108
-61
lines changed

2 files changed

+108
-61
lines changed

src/main/java/com/github/fge/jsonschema/format/common/RFC3339DateTimeAttribute.java

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.github.fge.jsonschema.format.common;
22

3-
import java.time.format.DateTimeFormatter;
4-
import java.time.format.DateTimeFormatterBuilder;
5-
import java.time.format.DateTimeParseException;
6-
import java.time.temporal.ChronoField;
73
import java.util.List;
84

5+
import org.joda.time.format.DateTimeFormatter;
6+
import org.joda.time.format.DateTimeFormatterBuilder;
7+
import org.joda.time.format.DateTimeParser;
8+
99
import com.github.fge.jackson.NodeType;
1010
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
1111
import com.github.fge.jsonschema.core.report.ProcessingReport;
@@ -22,21 +22,22 @@
2222
public class RFC3339DateTimeAttribute extends AbstractFormatAttribute {
2323

2424
private static final List<String> RFC3339_FORMATS = ImmutableList.of(
25-
"yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}((+|-)HH:mm|Z)"
25+
"yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,12}((+|-)HH:mm|Z)"
2626
);
2727

28-
private static final DateTimeFormatter RFC3339_FORMATTER;
28+
private static final DateTimeFormatter FORMATTER;
2929

3030
static {
31-
final DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder()
32-
.appendPattern("yyyy-MM-dd")
33-
.appendLiteral('T')
34-
.appendPattern("HH:mm:ss")
35-
.optionalStart()
36-
.appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true).parseDefaulting(ChronoField.NANO_OF_SECOND, 0)
37-
.optionalEnd()
38-
.appendOffset("+HH:mm", "Z");
39-
RFC3339_FORMATTER = builder.toFormatter();
31+
final DateTimeParser secFracsParser = new DateTimeFormatterBuilder()
32+
.appendLiteral('.').appendFractionOfSecond(1,12)
33+
.toParser();
34+
35+
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder()
36+
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
37+
.appendOptional(secFracsParser)
38+
.appendTimeZoneOffset("Z", true, 2, 2);
39+
40+
FORMATTER = builder.toFormatter();
4041
}
4142

4243
private static final FormatAttribute INSTANCE = new RFC3339DateTimeAttribute();
@@ -58,11 +59,50 @@ public void validate(final ProcessingReport report,
5859
{
5960
final String value = data.getInstance().getNode().textValue();
6061

61-
try {
62-
RFC3339_FORMATTER.parse(value);
63-
} catch (DateTimeParseException ignored) {
64-
report.error(newMsg(data, bundle, "err.format.invalidDate")
65-
.putArgument("value", value).putArgument("expected", RFC3339_FORMATS));
62+
try
63+
{
64+
FORMATTER.parseDateTime(value);
65+
66+
final String secFracsAndOffset = value.substring("yyyy-MM-ddTHH:mm:ss".length());
67+
final String offset;
68+
if (!secFracsAndOffset.startsWith(".")) {
69+
offset = secFracsAndOffset;
70+
} else{
71+
if (secFracsAndOffset.contains("Z")) {
72+
offset = secFracsAndOffset.substring(secFracsAndOffset.indexOf("Z"));
73+
} else if (secFracsAndOffset.contains("+")) {
74+
offset = secFracsAndOffset.substring(secFracsAndOffset.indexOf("+"));
75+
} else {
76+
offset = secFracsAndOffset.substring(secFracsAndOffset.indexOf("-"));
77+
}
78+
}
79+
if (!isOffSetStrictRFC3339(offset)) {
80+
throw new IllegalArgumentException();
81+
}
82+
83+
} catch (IllegalArgumentException ignored) {
84+
report.error(newMsg(data, bundle, "err.format.invalidDate")
85+
.putArgument("value", value).putArgument("expected", RFC3339_FORMATS));
6686
}
87+
6788
}
89+
90+
/**
91+
* Return true if date-time offset stricly follows RFC3339:
92+
* <code>time-hour = 2DIGIT ; 00-23</code>
93+
* <code>time-minute = 2DIGIT ; 00-59</code>
94+
* <code>time-numoffset = ("+" / "-") time-hour ":" time-minute</code>
95+
* <code>time-offset = "Z" / time-numoffset</code>,
96+
* and false otherwise
97+
* @param offset
98+
* @return
99+
*/
100+
private boolean isOffSetStrictRFC3339(final String offset)
101+
{
102+
if (offset.endsWith("Z")) return true;
103+
if (offset.length() == 6 && offset.contains(":")) {
104+
return true;
105+
}
106+
return false;
107+
}
68108
}

src/test/resources/format/common/date-time.json

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@
2626
{
2727
"data": "2012-08-07T20:42:32-05:30",
2828
"valid": true
29-
},
29+
},
3030
{
31-
"data": "2012-12-02T13:05:00+0100",
31+
"data": "201202030",
3232
"valid": false,
3333
"message": "err.format.invalidDate",
3434
"msgData": {
35-
"value": "2012-12-02T13:05:00+0100",
36-
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}((+|-)HH:mm|Z)" ]
35+
"value": "201202030",
36+
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,12}((+|-)HH:mm|Z)" ]
3737
},
3838
"msgParams": [ "value", "expected" ]
3939
},
@@ -43,7 +43,17 @@
4343
"message": "err.format.invalidDate",
4444
"msgData": {
4545
"value": "2012-12-02T13:05:00+0100",
46-
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}((+|-)HH:mm|Z)" ]
46+
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,12}((+|-)HH:mm|Z)" ]
47+
},
48+
"msgParams": [ "value", "expected" ]
49+
},
50+
{
51+
"data": "2012-12-02T13:05:00+01:30:30",
52+
"valid": false,
53+
"message": "err.format.invalidDate",
54+
"msgData": {
55+
"value": "2012-12-02T13:05:00+01:30:30",
56+
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,12}((+|-)HH:mm|Z)" ]
4757
},
4858
"msgParams": [ "value", "expected" ]
4959
},
@@ -53,36 +63,47 @@
5363
"message": "err.format.invalidDate",
5464
"msgData": {
5565
"value": "2012-12-02T13:05:00Z[Europe/Paris]",
56-
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}((+|-)HH:mm|Z)" ]
66+
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,12}((+|-)HH:mm|Z)" ]
5767
},
5868
"msgParams": [ "value", "expected" ]
59-
}, {
60-
"data": "2012-12-02T13:05:00+0100",
69+
},
70+
{
71+
"data": "2012-12-02T13:05:00+10:00Z",
6172
"valid": false,
6273
"message": "err.format.invalidDate",
6374
"msgData": {
64-
"value": "2012-12-02T13:05:00+0100",
65-
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}((+|-)HH:mm|Z)" ]
75+
"value": "2012-12-02T13:05:00+10:00Z",
76+
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,12}((+|-)HH:mm|Z)" ]
6677
},
6778
"msgParams": [ "value", "expected" ]
6879
},
6980
{
70-
"data": "2012-02-30T00:00:00+0000",
81+
"data": "2012-12-02T13:05:00America/New_York",
7182
"valid": false,
7283
"message": "err.format.invalidDate",
7384
"msgData": {
74-
"value": "2012-02-30T00:00:00+0000",
75-
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}((+|-)HH:mm|Z)" ]
85+
"value": "2012-12-02T13:05:00America/New_York",
86+
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,12}((+|-)HH:mm|Z)" ]
7687
},
7788
"msgParams": [ "value", "expected" ]
7889
},
7990
{
80-
"data": "201202030",
91+
"data": "2012-12-02T13:05:00[America/New_York]",
8192
"valid": false,
8293
"message": "err.format.invalidDate",
8394
"msgData": {
84-
"value": "201202030",
85-
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}((+|-)HH:mm|Z)" ]
95+
"value": "2012-12-02T13:05:00[America/New_York]",
96+
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,12}((+|-)HH:mm|Z)" ]
97+
},
98+
"msgParams": [ "value", "expected" ]
99+
},
100+
{
101+
"data": "2012-12-02T13:05:00.123456",
102+
"valid": false,
103+
"message": "err.format.invalidDate",
104+
"msgData": {
105+
"value": "2012-12-02T13:05:00.123456",
106+
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,12}((+|-)HH:mm|Z)" ]
86107
},
87108
"msgParams": [ "value", "expected" ]
88109
},
@@ -91,19 +112,19 @@
91112
"valid": true
92113
},
93114
{
94-
"data": "2012-08-07T20:42:32.12345Z",
115+
"data": "2012-08-07T20:42:32.1234+05:00",
95116
"valid": true
96117
},
97118
{
98-
"data": "2012-08-07T20:42:32.123456Z",
119+
"data": "2012-08-07T20:42:32.12345Z",
99120
"valid": true
100121
},
101122
{
102-
"data": "2012-08-07T20:42:32.1234567Z",
123+
"data": "2012-08-07T20:42:32.123456Z",
103124
"valid": true
104125
},
105126
{
106-
"data": "2012-08-07T20:42:32.12345678Z",
127+
"data": "2012-08-07T20:42:32.1234567Z",
107128
"valid": true
108129
},
109130
{
@@ -116,32 +137,18 @@
116137
},
117138
{
118139
"data": "2012-08-07T20:42:32.1234567890Z",
119-
"valid": false,
120-
"message": "err.format.invalidDate",
121-
"msgData": {
122-
"value": "2012-08-07T20:42:32.1234567890Z",
123-
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}((+|-)HH:mm|Z)" ]
124-
},
125-
"msgParams": [ "value", "expected" ]
140+
"valid": true
126141
},
127142
{
128143
"data": "2012-08-07T20:42:32.12345678901Z",
129-
"valid": false,
130-
"message": "err.format.invalidDate",
131-
"msgData": {
132-
"value": "2012-08-07T20:42:32.12345678901Z",
133-
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}((+|-)HH:mm|Z)" ]
134-
},
135-
"msgParams": [ "value", "expected" ]
144+
"valid": true
136145
},
137146
{
138147
"data": "2012-08-07T20:42:32.123456789012Z",
139-
"valid": false,
140-
"message": "err.format.invalidDate",
141-
"msgData": {
142-
"value": "2012-08-07T20:42:32.123456789012Z",
143-
"expected": [ "yyyy-MM-dd'T'HH:mm:ss((+|-)HH:mm|Z)", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}((+|-)HH:mm|Z)" ]
144-
},
145-
"msgParams": [ "value", "expected" ]
148+
"valid": true
149+
},
150+
{
151+
"data": "2012-08-07T20:42:32.123456789012+05:00",
152+
"valid": true
146153
}
147154
]

0 commit comments

Comments
 (0)