diff --git a/.gitignore b/.gitignore index abf22d1..7708236 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .idea *.iml *.versionsBackup +.cifuzz-corpus \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9b5227e..afffd3f 100644 --- a/pom.xml +++ b/pom.xml @@ -48,12 +48,26 @@ limitations under the License. <version>3.24.2</version> <scope>test</scope> </dependency> + <!-- <dependency> <groupId>com.code-intelligence</groupId> <artifactId>jazzer-junit</artifactId> <version>0.22.1</version> <scope>test</scope> </dependency> + --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</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> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> @@ -69,7 +83,7 @@ limitations under the License. <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> - <version>1.4.14</version> + <version>1.3.14</version> <scope>test</scope> </dependency> </dependencies> diff --git a/src/main/java/com/ethlo/time/DateTime.java b/src/main/java/com/ethlo/time/DateTime.java index b16bb9e..534298c 100644 --- a/src/main/java/com/ethlo/time/DateTime.java +++ b/src/main/java/com/ethlo/time/DateTime.java @@ -471,7 +471,6 @@ public boolean isSupported(final TemporalField field) return Field.of(field).ordinal() <= this.field.ordinal(); } - @SuppressWarnings("DuplicatedCode") @Override public long getLong(final TemporalField temporalField) { diff --git a/src/main/java/com/ethlo/time/internal/ErrorUtil.java b/src/main/java/com/ethlo/time/internal/ErrorUtil.java new file mode 100644 index 0000000..7285f41 --- /dev/null +++ b/src/main/java/com/ethlo/time/internal/ErrorUtil.java @@ -0,0 +1,45 @@ +package com.ethlo.time.internal; + +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.time.format.DateTimeParseException; + +public class ErrorUtil +{ + private ErrorUtil() + { + } + + public static DateTimeParseException raiseUnexpectedCharacter(String chars, int index) + { + throw new DateTimeParseException("Unexpected character " + chars.charAt(index) + " at position " + (index + 1) + ": " + chars, chars, index); + } + + public static DateTimeParseException raiseUnexpectedEndOfText(final String chars, final int offset) + { + throw new DateTimeParseException("Unexpected end of input: " + chars, chars, offset); + } + + public static DateTimeParseException raiseMissingTimeZone(String chars, int index) + { + throw new DateTimeParseException("No timezone information: " + chars, chars, index); + } +} diff --git a/src/main/java/com/ethlo/time/internal/EthloITU.java b/src/main/java/com/ethlo/time/internal/EthloITU.java index 2035092..12b4ef8 100644 --- a/src/main/java/com/ethlo/time/internal/EthloITU.java +++ b/src/main/java/com/ethlo/time/internal/EthloITU.java @@ -20,6 +20,9 @@ * #L% */ +import static com.ethlo.time.internal.ErrorUtil.raiseMissingTimeZone; +import static com.ethlo.time.internal.ErrorUtil.raiseUnexpectedCharacter; +import static com.ethlo.time.internal.ErrorUtil.raiseUnexpectedEndOfText; import static com.ethlo.time.internal.LeapSecondHandler.LEAP_SECOND_SECONDS; import static com.ethlo.time.internal.LimitedCharArrayIntegerUtil.DIGIT_9; import static com.ethlo.time.internal.LimitedCharArrayIntegerUtil.ZERO; @@ -96,7 +99,7 @@ private static int scale(int fractions, int len, String parsedData, final int in switch (len) { case 0: - throw new DateTimeParseException("Must have at least 1 fraction digit", parsedData, index); + throw new DateTimeParseException("Must have at least 1 fraction digit: " + parsedData, parsedData, index); case 1: return fractions * 100_000_000; case 2: @@ -137,44 +140,36 @@ private static Object handleTime(String chars, int year, int month, int day, int } return DateTime.of(year, month, day, hour, minute, zoneOffset); } - throw new DateTimeParseException("Unexpected character at position 16: " + chars.charAt(16), chars, 16); + throw raiseUnexpectedCharacter(chars, 16); } private static void assertPositionContains(String chars, int offset, char expected) { if (offset >= chars.length()) { - raiseDateTimeException(chars, "Unexpected end of input", offset); + raiseUnexpectedEndOfText(chars, offset); } if (chars.charAt(offset) != expected) { throw new DateTimeParseException("Expected character " + expected - + " at position " + (offset + 1) + " '" + chars + "'", chars, offset); + + " at position " + (offset + 1) + ": " + chars, chars, offset); } } - private static void assertPositionContains(String chars, char... expected) + private static void assertAllowedDateTimeSeparator(String chars) { - if (10 >= chars.length()) - { - raiseDateTimeException(chars, "Unexpected end of input", 10); - } - - boolean found = false; final char needle = chars.charAt(10); - for (char e : expected) - { - if (needle == e) - { - found = true; - break; - } - } - if (!found) + switch (needle) { - throw new DateTimeParseException("Expected character " + Arrays.toString(expected) - + " at position " + (10 + 1) + " '" + chars + "'", chars, 10); + case SEPARATOR_UPPER: + case SEPARATOR_LOWER: + case SEPARATOR_SPACE: + return; + + default: + throw new DateTimeParseException("Expected character " + Arrays.toString(new char[]{SEPARATOR_UPPER, SEPARATOR_LOWER, SEPARATOR_SPACE}) + + " at position " + (10 + 1) + ": " + chars, chars, 10); } } @@ -182,7 +177,7 @@ private static TimezoneOffset parseTimezone(String chars, int offset) { if (offset >= chars.length()) { - throw new DateTimeParseException("No timezone information: " + chars, chars, offset); + raiseMissingTimeZone(chars, offset); } final int len = chars.length(); final int left = len - offset; @@ -196,7 +191,7 @@ private static TimezoneOffset parseTimezone(String chars, int offset) final char sign = chars.charAt(offset); if (sign != PLUS && sign != MINUS) { - throw new DateTimeParseException("Invalid character starting at position " + offset + ": " + chars, chars, offset); + raiseUnexpectedCharacter(chars, offset); } if (left != 6) @@ -224,7 +219,7 @@ private static void assertNoMoreChars(String chars, int lastUsed) { if (chars.length() > lastUsed + 1) { - throw new DateTimeParseException("Trailing junk data after position " + (lastUsed + 1) + ": " + chars, chars, lastUsed + 1); + throw new DateTimeParseException("Trailing junk data after position " + (lastUsed + 2) + ": " + chars, chars, lastUsed + 1); } } @@ -275,7 +270,7 @@ private static Object parse(String chars, boolean raw) } // HOURS - assertPositionContains(chars, SEPARATOR_UPPER, SEPARATOR_LOWER, SEPARATOR_SPACE); + assertAllowedDateTimeSeparator(chars); final int hours = parsePositiveInt(chars, 11, 13); // MINUTES @@ -303,98 +298,89 @@ private static DateTimeException raiseMissingField(Field field, final String cha private static Object handleTime(int year, int month, int day, int hour, int minute, String chars, boolean raw) { // From here the specification is more lenient - final int len = chars.length(); - final int remaining = len - 17; - if (remaining == 2) - { - final int seconds = parsePositiveInt(chars, 17, 19); - if (raw) + final int length = chars.length(); + if (length > 19) + { + TimezoneOffset offset = null; + int fractions = 0; + int fractionDigits = 0; + char c = chars.charAt(19); + if (c == FRACTION_SEPARATOR) { - return new DateTime(Field.SECOND, year, month, day, hour, minute, seconds, 0, null, 0); - } - throw new DateTimeParseException("No timezone information: " + chars, chars, 19); - } - else if (remaining == 0) - { - if (raw) - { - return new DateTime(Field.SECOND, year, month, day, hour, minute, 0, 0, null, 0); - } - throw new DateTimeParseException("No timezone information: " + chars, chars, 16); - } - - TimezoneOffset offset = null; - int fractions = 0; - int fractionDigits = 0; - if (chars.length() < 20) - { - throw new DateTimeParseException("Unexpected end of input: " + chars, chars, 16); - } - char c = chars.charAt(19); - if (c == FRACTION_SEPARATOR) - { if (chars.length() < 21) { - throw new DateTimeParseException("Unexpected end of input: " + chars, chars, 20); + raiseUnexpectedEndOfText(chars, 20); } - // We have fractional seconds - int result = 0; - int idx = 20; - boolean nonDigitFound = false; - do - { - c = chars.charAt(idx); - if (c < ZERO || c > DIGIT_9) + // We have fractional seconds + int result = 0; + int idx = 20; + boolean nonDigitFound = false; + do + { + c = chars.charAt(idx); + if (c < ZERO || c > DIGIT_9) + { + nonDigitFound = true; + fractionDigits = idx - 20; + assertFractionDigits(chars, fractionDigits, idx); + fractions = scale(-result, fractionDigits, chars, idx); + offset = parseTimezone(chars, idx); + } + else + { + fractionDigits = idx - 19; + assertFractionDigits(chars, fractionDigits, idx); + result = (result << 1) + (result << 3); + result -= c - ZERO; + } + idx++; + } while (idx < length && !nonDigitFound); + + if (!nonDigitFound) { - nonDigitFound = true; fractionDigits = idx - 20; - assertFractionDigits(chars, fractionDigits, idx); fractions = scale(-result, fractionDigits, chars, idx); - offset = parseTimezone(chars, idx); - } - else - { - fractionDigits = idx - 19; - assertFractionDigits(chars, fractionDigits, idx); - result = (result << 1) + (result << 3); - result -= c - ZERO; + if (!raw) + { + offset = parseTimezone(chars, idx); + } } - idx++; - } while (idx < len && !nonDigitFound); + } + else if (c == ZULU_UPPER || c == ZULU_LOWER) + { + // Do nothing we are done + offset = TimezoneOffset.UTC; + } + else if (c == PLUS || c == MINUS) + { + // No fractional seconds + offset = parseTimezone(chars, 19); + } + else + { + throw raiseUnexpectedCharacter(chars, 19); + } - if (!nonDigitFound) + final int second = parsePositiveInt(chars, 17, 19); + + if (!raw) { - fractionDigits = idx - 20; - fractions = scale(-result, fractionDigits, chars, idx); - if (!raw) - { - offset = parseTimezone(chars, idx); - } + leapSecondCheck(year, month, day, hour, minute, second, fractions, offset); + return OffsetDateTime.of(year, month, day, hour, minute, second, fractions, offset.toZoneOffset()); } + return fractionDigits > 0 ? DateTime.of(year, month, day, hour, minute, second, fractions, offset, fractionDigits) : DateTime.of(year, month, day, hour, minute, second, offset); } - else if (c == ZULU_UPPER || c == ZULU_LOWER) - { - // Do nothing we are done - offset = TimezoneOffset.UTC; - } - else if (c == PLUS || c == MINUS) + else if (length == 19) { - // No fractional seconds - offset = parseTimezone(chars, 19); - } - else - { - raiseDateTimeException(chars, "Unexpected character at position 19", 19); + final int seconds = parsePositiveInt(chars, 17, 19); + if (raw) + { + return new DateTime(Field.SECOND, year, month, day, hour, minute, seconds, 0, null, 0); + } + raiseMissingTimeZone(chars, 19); } - final int second = parsePositiveInt(chars, 17, 19); - - if (!raw) - { - leapSecondCheck(year, month, day, hour, minute, second, fractions, offset); - return OffsetDateTime.of(year, month, day, hour, minute, second, fractions, offset.toZoneOffset()); - } - return fractionDigits > 0 ? DateTime.of(year, month, day, hour, minute, second, fractions, offset, fractionDigits) : DateTime.of(year, month, day, hour, minute, second, offset); + throw raiseUnexpectedEndOfText(chars, 16); } private static void assertFractionDigits(String chars, int fractionDigits, int idx) @@ -428,11 +414,6 @@ private static void leapSecondCheck(int year, int month, int day, int hour, int } } - private static void raiseDateTimeException(String chars, String message, int index) - { - throw new DateTimeParseException(message + ": " + chars, chars, index); - } - @Override public String formatUtc(OffsetDateTime date, int fractionDigits) { @@ -462,7 +443,7 @@ private String doFormat(OffsetDateTime date, ZoneOffset adjustTo, Field lastIncl } final TimezoneOffset tz = TimezoneOffset.of(adjustTo); - final char[] buffer = new char[31]; + final char[] buffer = new char[26 + fractionDigits]; if (handleDatePart(lastIncluded, buffer, adjusted.getYear(), 0, 4, Field.YEAR)) { diff --git a/src/main/java/com/ethlo/time/internal/LimitedCharArrayIntegerUtil.java b/src/main/java/com/ethlo/time/internal/LimitedCharArrayIntegerUtil.java index fee1e37..e884d99 100644 --- a/src/main/java/com/ethlo/time/internal/LimitedCharArrayIntegerUtil.java +++ b/src/main/java/com/ethlo/time/internal/LimitedCharArrayIntegerUtil.java @@ -20,8 +20,6 @@ * #L% */ -import java.time.DateTimeException; -import java.time.format.DateTimeParseException; import java.util.Arrays; public final class LimitedCharArrayIntegerUtil @@ -53,7 +51,7 @@ public static int parsePositiveInt(final String strNum, int startInclusive, int { if (endExclusive > strNum.length()) { - throw new DateTimeParseException("Unexpected end of expression at position " + strNum.length() + ": '" + strNum + "'", strNum, startInclusive); + ErrorUtil.raiseUnexpectedEndOfText(strNum, startInclusive); } int result = 0; @@ -62,7 +60,7 @@ public static int parsePositiveInt(final String strNum, int startInclusive, int final char c = strNum.charAt(i); if (c < ZERO || c > DIGIT_9) { - throw new DateTimeParseException("Character " + c + " is not a digit", strNum, i); + ErrorUtil.raiseUnexpectedCharacter(strNum, i); } result = (result << 1) + (result << 3); result -= c - ZERO; diff --git a/src/test/java/com/ethlo/time/ExternalParameterizedTest.java b/src/test/java/com/ethlo/time/ExternalParameterizedTest.java index 87077be..7fbf64c 100644 --- a/src/test/java/com/ethlo/time/ExternalParameterizedTest.java +++ b/src/test/java/com/ethlo/time/ExternalParameterizedTest.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.time.DateTimeException; import java.time.Instant; +import java.time.OffsetDateTime; import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; import java.util.List; @@ -59,14 +60,14 @@ void testAll(TestParam param) } // Compare to Java's parser result - final Instant expected = Instant.parse(param.getInput()); + final Instant expected = getExpected(param); if (result instanceof DateTime) { - assertThat(((DateTime) result).toInstant()).isEqualTo(expected); + assertEqualInstant(((DateTime) result).toInstant(), expected); } else { - assertThat(Instant.from(result)).isEqualTo(expected); + assertEqualInstant(((OffsetDateTime) result).toInstant(), expected); } } catch (DateTimeException exc) @@ -91,6 +92,35 @@ void testAll(TestParam param) } + private void assertEqualInstant(Instant result, Instant expected) + { + assertThat(result) + .overridingErrorMessage("Expected %s (%s), was %s (%s)", expected, asTs(expected), result, asTs(result)) + .isEqualTo(expected); + } + + private String asTs(Instant instant) + { + return instant.getEpochSecond() + "," + instant.getNano(); + } + + private Instant getExpected(TestParam testParam) + { + if (testParam.getExpected() != null) + { + return testParam.getExpected(); + } + + try + { + return Instant.parse(testParam.getInput()); + } + catch (DateTimeException exc) + { + throw new IllegalArgumentException("Cannot parse using Instant: " + testParam.getInput() + ": " + exc.getMessage(), exc); + } + } + public static List<TestParam> fromFile() throws IOException { final List<TestParam> result = new ObjectMapper() diff --git a/src/test/java/com/ethlo/time/FieldTest.java b/src/test/java/com/ethlo/time/FieldTest.java index 9e98464..5ff8cf6 100644 --- a/src/test/java/com/ethlo/time/FieldTest.java +++ b/src/test/java/com/ethlo/time/FieldTest.java @@ -26,7 +26,9 @@ import java.time.OffsetDateTime; import java.time.Year; import java.time.YearMonth; +import java.time.temporal.ChronoField; import java.time.temporal.Temporal; +import java.time.temporal.UnsupportedTemporalTypeException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Tag; @@ -36,7 +38,7 @@ public class FieldTest { @Test - public void testGetKnownFields() + void testGetKnownFields() { assertThat(Field.valueOf(Year.class)).isEqualTo(Field.YEAR); assertThat(Field.valueOf(YearMonth.class)).isEqualTo(Field.MONTH); @@ -45,8 +47,14 @@ public void testGetKnownFields() } @Test - public void testGetUnknown() + void testGetUnknown() { Assertions.assertThrows(IllegalArgumentException.class, () -> Field.valueOf(Temporal.class)); } + + @Test + void testUnknownField() + { + Assertions.assertThrows(UnsupportedTemporalTypeException.class, () -> Field.of(ChronoField.NANO_OF_DAY)); + } } diff --git a/src/test/java/com/ethlo/time/FormatterTest.java b/src/test/java/com/ethlo/time/FormatterTest.java index fefb96a..2ca4045 100644 --- a/src/test/java/com/ethlo/time/FormatterTest.java +++ b/src/test/java/com/ethlo/time/FormatterTest.java @@ -1,5 +1,25 @@ package com.ethlo.time; +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -11,11 +31,37 @@ public class FormatterTest { @Test - void testFormat3() + void testFormatSpecifiedResolution() { final String s = "2017-02-21T10:00:00.000+12:00"; final OffsetDateTime date = ITU.parseDateTime(s); - assertThat(ITU.formatUtcMilli(date)).isEqualTo("2017-02-20T22:00:00.000Z"); + assertThat(ITU.format(date, 9)).isEqualTo("2017-02-21T10:00:00.000000000+12:00"); + } + + @Test + void testFormatYear() + { + assertThat(DateTime.ofYear(1234).toString()).isEqualTo("1234"); + } + + @Test + void testFormatYearMonth() + { + assertThat(DateTime.ofYearMonth(1234, 12).toString()).isEqualTo("1234-12"); + } + + @Test + void testFormatHigherGranularityThanAvailable() + { + final DateTimeException exc = assertThrows(DateTimeException.class, () -> DateTime.ofYear(1234).toString(Field.DAY)); + assertThat(exc).hasMessage("Requested granularity was DAY, but contains only granularity YEAR"); + } + + @Test + void testFormatWithFractionDigits() + { + final DateTimeException exc = assertThrows(DateTimeException.class, () -> DateTime.ofYear(1234).toString(Field.DAY)); + assertThat(exc).hasMessage("Requested granularity was DAY, but contains only granularity YEAR"); } @Test diff --git a/src/test/java/com/ethlo/time/ITUTest.java b/src/test/java/com/ethlo/time/ITUTest.java index 4555487..8b543f0 100644 --- a/src/test/java/com/ethlo/time/ITUTest.java +++ b/src/test/java/com/ethlo/time/ITUTest.java @@ -299,7 +299,7 @@ public void handle(final Year year) assertThat(year).isEqualTo(Year.parse(input)); } })); - assertThat(exc).hasMessage("Expected character [T, t, ] at position 11 '2017-03-05G'"); + assertThat(exc).hasMessage("Expected character [T, t, ] at position 11: 2017-03-05G"); } @Test diff --git a/src/test/java/com/ethlo/time/TemporalAccessorTest.java b/src/test/java/com/ethlo/time/TemporalAccessorTest.java index a50bccd..1c430b2 100644 --- a/src/test/java/com/ethlo/time/TemporalAccessorTest.java +++ b/src/test/java/com/ethlo/time/TemporalAccessorTest.java @@ -1,5 +1,25 @@ package com.ethlo.time; +/*- + * #%L + * Internet Time Utility + * %% + * Copyright (C) 2017 - 2024 Morten Haraldsen (ethlo) + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + import static org.assertj.core.api.Assertions.assertThat; import java.time.temporal.ChronoField; diff --git a/src/test/java/com/ethlo/time/TestParam.java b/src/test/java/com/ethlo/time/TestParam.java index f73e928..4f5a88a 100644 --- a/src/test/java/com/ethlo/time/TestParam.java +++ b/src/test/java/com/ethlo/time/TestParam.java @@ -20,7 +20,10 @@ * #L% */ +import static org.assertj.core.api.Assertions.assertThat; + import java.beans.ConstructorProperties; +import java.time.Instant; public class TestParam { @@ -28,18 +31,32 @@ public class TestParam private final boolean lenient; private final String error; private final int errorIndex; + private final Instant expected; private final String note; - @ConstructorProperties(value = {"input", "lenient", "error", "error_index", "note"}) - public TestParam(String input, boolean lenient, String error, Integer errorIndex, String note) + @ConstructorProperties(value = {"input", "lenient", "error", "error_index", "expected", "note"}) + public TestParam(String input, boolean lenient, String error, Integer errorIndex, String expected, String note) { this.input = input; this.lenient = lenient; this.error = error; this.errorIndex = errorIndex != null ? errorIndex : -1; + this.expected = parseExpected(expected); this.note = note; } + private Instant parseExpected(String expected) + { + if (expected == null) + { + return null; + } + + final String[] parts = expected.split(","); + assertThat(parts.length).isEqualTo(2); + return Instant.ofEpochSecond(Long.parseLong(parts[0]), Long.parseLong(parts[1])); + } + public String getInput() { return input; @@ -66,6 +83,7 @@ public String toString() return "TestParam{" + "input='" + input + '\'' + ", lenient=" + lenient + + ", expected=" + expected + '\'' + ", error='" + error + '\'' + ", errorOffset=" + errorIndex + '\'' + ", note=" + note + @@ -76,4 +94,9 @@ public String getNote() { return note; } + + public Instant getExpected() + { + return expected; + } } diff --git a/src/test/java/com/ethlo/time/fuzzer/ParseDateTimeFuzzTest.java b/src/test/java/com/ethlo/time/fuzzer/ParseDateTimeFuzzTest.java index 9cdc19c..832feb7 100644 --- a/src/test/java/com/ethlo/time/fuzzer/ParseDateTimeFuzzTest.java +++ b/src/test/java/com/ethlo/time/fuzzer/ParseDateTimeFuzzTest.java @@ -20,6 +20,7 @@ * #L% */ +/* import java.time.DateTimeException; import com.code_intelligence.jazzer.api.FuzzedDataProvider; @@ -40,3 +41,4 @@ void parse(FuzzedDataProvider data) } } } +*/ \ No newline at end of file diff --git a/src/test/java/com/ethlo/time/fuzzer/ParseLenientFuzzTest.java b/src/test/java/com/ethlo/time/fuzzer/ParseLenientFuzzTest.java index 18fcb90..16d4629 100644 --- a/src/test/java/com/ethlo/time/fuzzer/ParseLenientFuzzTest.java +++ b/src/test/java/com/ethlo/time/fuzzer/ParseLenientFuzzTest.java @@ -22,6 +22,7 @@ import java.time.DateTimeException; +/* import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.ethlo.time.DateTime; import com.ethlo.time.ITU; @@ -49,3 +50,4 @@ void parse(FuzzedDataProvider data) } } } +*/ \ No newline at end of file diff --git a/src/test/resources/test-data.json b/src/test/resources/test-data.json index 8d7bd80..92f77d6 100644 --- a/src/test/resources/test-data.json +++ b/src/test/resources/test-data.json @@ -18,10 +18,6 @@ * #L% */ [ - { - "input": "2020-22-12T12:11.56+04:30", - "error": "Unexpected character at position 16: ." - }, { "input": "2017-02-21T15:27:39Z" }, @@ -35,51 +31,71 @@ "input": "2017-02-21T15:27:39.123456789Z" }, { - "input": "2017-02-21T15:27:39+00:00" + "input": "2017-02-21T15:27:39+00:00", + "expected": "1487690859,0" + }, + { + "input": "2017-02-21T15:27:39.123+00:00", + "expected": "1487690859,123000000" }, { - "input": "2017-02-21T15:27:39.123+00:00" + "input": "2017-02-21T15:27:39.123456+00:00", + "expected": "1487690859,123456000" }, { - "input": "2017-02-21T15:27:39.123456+00:00" + "input": "2017-02-21T15:27:39.123456789+00:00", + "expected": "1487690859,123456789" }, { - "input": "2017-02-21T15:27:39.123456789+00:00" + "input": "2017-02-21T15:27:39.1+00:00", + "expected": "1487690859,100000000" }, { - "input": "2017-02-21T15:27:39.1+00:00" + "input": "2017-02-21T15:27:39.12+00:00", + "expected": "1487690859,120000000" }, { - "input": "2017-02-21T15:27:39.12+00:00" + "input": "2017-02-21T15:27:39.123+00:00", + "expected": "1487690859,123000000" }, { - "input": "2017-02-21T15:27:39.123+00:00" + "input": "2017-02-21T15:27:39.1234+00:00", + "expected": "1487690859,123400000" }, { - "input": "2017-02-21T15:27:39.1234+00:00" + "input": "2017-02-21T15:27:39.12345+00:00", + "expected": "1487690859,123450000" }, { - "input": "2017-02-21T15:27:39.12345+00:00" + "input": "2017-02-21T15:27:39.123456+00:00", + "expected": "1487690859,123456000" }, { - "input": "2017-02-21T15:27:39.123456+00:00" + "input": "2017-02-21T15:27:39.1234567+00:00", + "expected": "1487690859,123456700" }, { - "input": "2017-02-21T15:27:39.1234567+00:00" + "input": "2017-02-21T15:27:39.12345678+00:00", + "expected": "1487690859,123456780" }, { - "input": "2017-02-21T15:27:39.12345678+00:00" + "input": "2017-02-21T15:27:39.123456789+00:00", + "expected": "1487690859,123456789" }, { - "input": "2017-02-21T15:27:39.123456789+00:00" + "input": "2020-22-12T12:11.56+04:30", + "error": "Unexpected character . at position 17: 2020-22-12T12:11.56+04:30", + "error_index": 16 }, { "input": "2017-02-21T15", - "error": "Unexpected end of input: 2017-02-21T15" + "error": "Unexpected end of input: 2017-02-21T15", + "error_index": 13 }, { "input": "2017-02-21T15Z", - "error": "Expected character : at position 14 '2017-02-21T15Z'" + "error": "Expected character : at position 14: 2017-02-21T15Z", + "error_index": 13 }, { "input": "2017-02-21T15:27", @@ -90,59 +106,69 @@ "error": "No SECOND field found" }, { - "input": "2017-02-21T15:27:19~10:00", - "error": "Unexpected character at position 19: 2017-02-21T15:27:19~10:00" + "input": "2017-02-21T15:27:22~10:00", + "error": "Unexpected character ~ at position 20: 2017-02-21T15:27:22~10:00", + "error_index": 19 }, { "input": "2017-02-21T15:27:39.+00:00", - "error": "Must have at least 1 fraction digit" + "error": "Must have at least 1 fraction digit: 2017-02-21T15:27:39.+00:00", + "error_index": 20 }, { "input": "2017-02-21T15:27:39", - "error": "No timezone information: 2017-02-21T15:27:39" + "error": "No timezone information: 2017-02-21T15:27:39", + "error_index": 19 }, { "input": "2017-02-21T15:27:39.123", - "error": "No timezone information: 2017-02-21T15:27:39.123" + "error": "No timezone information: 2017-02-21T15:27:39.123", + "error_index": 23 }, { "input": "2017-02-21T15:27:39.123456", - "error": "No timezone information: 2017-02-21T15:27:39.123456" + "error": "No timezone information: 2017-02-21T15:27:39.123456", + "error_index": 26 }, { "input": "2017-02-21T15:27:39.123456789", - "error": "No timezone information: 2017-02-21T15:27:39.123456789" + "error": "No timezone information: 2017-02-21T15:27:39.123456789", + "error_index": 29 }, { "input": "2017-02-21T15:27:39+0000", - "error": "Invalid timezone offset: 2017-02-21T15:27:39+0000" + "error": "Invalid timezone offset: 2017-02-21T15:27:39+0000", + "error_index": 19 }, { "input": "2017-02-21T15:27:39.123+0000", - "error": "Invalid timezone offset: 2017-02-21T15:27:39.123+0000" + "error": "Invalid timezone offset: 2017-02-21T15:27:39.123+0000", + "error_index": 23 }, { "input": "201702-21T15:27:39.123456+0000", - "error": "Expected character - at position 5 '201702-21T15:27:39.123456+0000'", + "error": "Expected character - at position 5: 201702-21T15:27:39.123456+0000", "error_index": 4 }, { "input": "20170221T15:27:39.123456789+0000", - "error": "Expected character - at position 5 '20170221T15:27:39.123456789+0000'", + "error": "Expected character - at position 5: 20170221T15:27:39.123456789+0000", "error_index": 4 }, { "input": "2017-12-21T12:20:45.987", - "error": "No timezone information: 2017-12-21T12:20:45.987" + "error": "No timezone information: 2017-12-21T12:20:45.987", + "error_index": 23 }, { "input": "2017-12-21T12:20:45.9b7Z", - "error": "Invalid character starting at position 21: 2017-12-21T12:20:45.9b7Z", + "error": "Unexpected character b at position 22: 2017-12-21T12:20:45.9b7Z", "error_index": 21 }, { "input": "2017-02-21T15:27:39.0000000", - "error": "No timezone information: 2017-02-21T15:27:39.0000000" + "error": "No timezone information: 2017-02-21T15:27:39.0000000", + "error_index": 27 }, { "input": "2017-02-21T15:27:39.000+30:00", @@ -150,7 +176,8 @@ }, { "input": "2017-02-21T15:00:00.1234567891Z", - "error": "Too many fraction digits: 2017-02-21T15:00:00.1234567891Z" + "error": "Too many fraction digits: 2017-02-21T15:00:00.1234567891Z", + "error_index": 29 }, { "input": "2017-13-21T15:00:00Z", @@ -179,32 +206,30 @@ }, { "input": "1994 11-05T08:15:30-05:00", - "error": "Expected character - at position 5 '1994 11-05T08:15:30-05:00'", + "error": "Expected character - at position 5: 1994 11-05T08:15:30-05:00", "error_index": 4, "note": "Invalid separator between year and month" }, { "input": "199g-11-05T08:15:30-05:00", - "error": "Character g is not a digit", + "error": "Unexpected character g at position 4: 199g-11-05T08:15:30-05:00", + "error_index": 3, "note": "Non-digit in year" }, { "input": "1994-11-05X08:15:30-05:00", - "error": "Expected character [T, t, ] at position 11 '1994-11-05X08:15:30-05:00'", + "error": "Expected character [T, t, ] at position 11: 1994-11-05X08:15:30-05:00", + "error_index": 10, "note": "invalid date/time separator" }, { "input": "1994-11-05t08:15:30z", "note": "Lowercase 'z' as UTC timezone" }, - { - "input": "1994-11-05 08:15:30Z", - "error": "Text '1994-11-05 08:15:30Z' could not be parsed at index 10", - "note": "Space as date/time separator" - }, { "input": "2017-02-21T15:27:39+0000", "error": "Invalid timezone offset: 2017-02-21T15:27:39+0000", + "error_index": 19, "note": "Military format offset" }, { @@ -213,16 +238,34 @@ }, { "input": "", - "error": "Unexpected end of expression at position 0: ''" + "error_index": 0, + "error": "Unexpected end of input: " }, { "input": "2017-02-21T15:00:00.123ZGGG", - "error": "Trailing junk data after position 24: 2017-02-21T15:00:00.123ZGGG" + "error": "Trailing junk data after position 25: 2017-02-21T15:00:00.123ZGGG", + "error_index": 24 }, - /** LENIENT **/ { "input": "2020-12-31T22:22:2", "error": "Unexpected end of input: 2020-12-31T22:22:2", + "error_index": 16, + "lenient": true + }, + { + "input": "2020-12-31T22:22:", + "error": "Unexpected end of input: 2020-12-31T22:22:", + "error_index": 16, + "lenient": true + }, + { + "input": "2020-12-31T22:56", + "expected": "1609455360,0", + "lenient": true + }, + { + "input": "2020-12-31", + "expected": "1609372800,0", "lenient": true } ]