Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,51 @@
*/
package org.everit.json.schema.internal;

import org.everit.json.schema.FormatValidator;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.util.List;
import java.util.Optional;

import com.google.common.collect.ImmutableList;
import org.everit.json.schema.FormatValidator;

/**
* Implementation of the "date-time" format value.
*/
public class DateTimeFormatValidator implements FormatValidator {

private static final String DATETIME_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssXXX";
private static final List<String> FORMATS_ACCEPTED = ImmutableList.of(
"yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}Z"
);

private static final String PARTIAL_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";

private static final String ZONE_OFFSET_PATTERN = "XXX";

private static final DateTimeFormatter FORMATTER;

static {
final DateTimeFormatter secondsFractionFormatter = new DateTimeFormatterBuilder()
.appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, true)
.toFormatter();

private static final String DATETIME_FORMAT_STRING_SECFRAC = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
final DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder()
.appendPattern(PARTIAL_DATETIME_PATTERN)
.appendOptional(secondsFractionFormatter)
.appendPattern(ZONE_OFFSET_PATTERN);

private SimpleDateFormat dateFormat(final String pattern) {
SimpleDateFormat rval = new SimpleDateFormat(pattern);
rval.setLenient(false);
return rval;
FORMATTER = builder.toFormatter();
}

@Override
public Optional<String> validate(final String subject) {
try {
dateFormat(DATETIME_FORMAT_STRING).parse(subject);
FORMATTER.parse(subject);
return Optional.empty();
} catch (ParseException e) {
try {
dateFormat(DATETIME_FORMAT_STRING_SECFRAC).parse(subject);
return Optional.empty();
} catch (ParseException e1) {
return Optional.of(String.format("[%s] is not a valid date-time", subject));
}
} catch (DateTimeParseException e) {
return Optional.of(String.format("[%s] is not a valid date-time. Expected %s", subject, FORMATS_ACCEPTED));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@
*/
package org.everit.json.schema.internal;

import java.util.Optional;

import org.everit.json.schema.FormatValidator;
import org.junit.Assert;
import org.junit.Test;

import java.util.Optional;

public class DefaultFormatValidatorTest {

private static final String THERE_IS_NO_PLACE_LIKE = "127.0.0.1";

private static final String IPV6_ADDR = "2001:db8:85a3:0:0:8a2e:370:7334";

private void assertFailure(final String subject, final FormatValidator format,
final String expectedFailure) {
final String expectedFailure) {
Optional<String> opt = format.validate(subject);
Assert.assertNotNull("the optional is not null", opt);
Assert.assertTrue("failure exists", opt.isPresent());
Expand All @@ -44,18 +44,64 @@ private void assertSuccess(final String subject, final FormatValidator format) {
@Test
public void dateTimeExceedingLimits() {
assertFailure("1996-60-999T16:39:57-08:00", new DateTimeFormatValidator(),
"[1996-60-999T16:39:57-08:00] is not a valid date-time");
"[1996-60-999T16:39:57-08:00] is not a valid date-time. Expected [yyyy-MM-dd'T'HH:mm:ssZ, yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}Z]");
}

@Test
public void dateTimeFormatFailure() {
assertFailure("2015-03-13T11:00:000", new DateTimeFormatValidator(),
"[2015-03-13T11:00:000] is not a valid date-time");
"[2015-03-13T11:00:000] is not a valid date-time. Expected [yyyy-MM-dd'T'HH:mm:ssZ, yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}Z]");
}

@Test
public void dateTimeWithSingleDigitInSecFracSuccess() {
assertSuccess("2015-02-28T11:00:00.1Z", new DateTimeFormatValidator());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@deepakhalale Is there any reason to remove this testcase?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @erosb, don't think I have removed the test case. I have renamed it to dateTimeWithThreeDigitsInSecFracSuccess and updated the seconds fraction from "111" to "123" to aligh with rest of the test cases that I added.
If you think the test case was there for a reason, then I will be glad to put it back.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, found it.
One final note: I've just found in the RFC3339 document that their ABNF defines fractions as:

time-fraction     = ("," / ".") 1*DIGIT

I had a quick glance at the javadoc and I couldn't find any easy way to support comma too, not only dot as a separator character. Do you think there is any simple solution to fix it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Json Schema Validation spec seem to suggest that the date-time format need to specifically conform to Section 5.6 of RFC-3339.
I think Section 5.6 of RFC-3339 is a specific cut down version of the ISO 8601 grammar and the ABNF date time that your referring to in the above comment, is an ISO 8601 standard.
Hope it helps.

}

@Test
public void dateTimeWithTwoDigitsInSecFracSuccess() {
assertSuccess("2015-02-28T11:00:00.12Z", new DateTimeFormatValidator());
}

@Test
public void dateTimeWithThreeDigitsInSecFracSuccess() {
assertSuccess("2015-02-28T11:00:00.123Z", new DateTimeFormatValidator());
}

@Test
public void dateTimeWithFourDigitsInSecFracSuccess() {
assertSuccess("2015-02-28T11:00:00.1234Z", new DateTimeFormatValidator());
}

@Test
public void dateTimeWithFiveDigitsInSecFracSuccess() {
assertSuccess("2015-02-28T11:00:00.12345Z", new DateTimeFormatValidator());
}

@Test
public void dateTimeWithSixDigitsInSecFracSuccess() {
assertSuccess("2015-02-28T11:00:00.123456Z", new DateTimeFormatValidator());
}

@Test
public void dateTimeWithSevenDigitsInSecFracSuccess() {
assertSuccess("2015-02-28T11:00:00.1234567Z", new DateTimeFormatValidator());
}

@Test
public void dateTimeWithEightDigitsInSecFracSuccess() {
assertSuccess("2015-02-28T11:00:00.12345678Z", new DateTimeFormatValidator());
}

@Test
public void dateTimeWithNineDigitsInSecFracSuccess() {
assertSuccess("2015-02-28T11:00:00.123456789Z", new DateTimeFormatValidator());
}

@Test
public void dateTimeSecFracSuccess() {
assertSuccess("2015-02-28T11:00:00.111Z", new DateTimeFormatValidator());
public void dateTimeWithTenDigitsInSecFracFailure() {
assertFailure("2015-02-28T11:00:00.1234567890Z", new DateTimeFormatValidator(),
"[2015-02-28T11:00:00.1234567890Z] is not a valid date-time. Expected [yyyy-MM-dd'T'HH:mm:ssZ, yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,9}Z]");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"created": {
"type": "string",
"format": "date-time"
}
},
"required": [
"created"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"created": "2011-12-03T10:15:30.1Z"
}