|
@@ -37,21 +37,6 @@ import java.util.Set;
|
|
|
*/
|
|
|
class Iso8601Parser {
|
|
|
|
|
|
- /**
|
|
|
- * The result of the parse. If successful, {@code result} will be non-null.
|
|
|
- * If parse failed, {@code errorIndex} specifies the index into the parsed string
|
|
|
- * that the first invalid data was encountered.
|
|
|
- */
|
|
|
- record Result(@Nullable DateTime result, int errorIndex) {
|
|
|
- Result(DateTime result) {
|
|
|
- this(result, -1);
|
|
|
- }
|
|
|
-
|
|
|
- static Result error(int errorIndex) {
|
|
|
- return new Result(null, errorIndex);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
private static final Set<ChronoField> VALID_MANDATORY_FIELDS = EnumSet.of(
|
|
|
ChronoField.YEAR,
|
|
|
ChronoField.MONTH_OF_YEAR,
|
|
@@ -127,24 +112,24 @@ class Iso8601Parser {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Attempts to parse {@code str} as an ISO-8601 datetime, returning a {@link Result} indicating if the parse
|
|
|
+ * Attempts to parse {@code str} as an ISO-8601 datetime, returning a {@link ParseResult} indicating if the parse
|
|
|
* was successful or not, and what fields were present.
|
|
|
* @param str The string to parse
|
|
|
* @param defaultTimezone The default timezone to return, if no timezone is present in the string
|
|
|
- * @return The {@link Result} of the parse.
|
|
|
+ * @return The {@link ParseResult} of the parse.
|
|
|
*/
|
|
|
- Result tryParse(CharSequence str, @Nullable ZoneId defaultTimezone) {
|
|
|
+ ParseResult tryParse(CharSequence str, @Nullable ZoneId defaultTimezone) {
|
|
|
if (str.charAt(0) == '-') {
|
|
|
// the year is negative. This is most unusual.
|
|
|
// Instead of always adding offsets and dynamically calculating position in the main parser code below,
|
|
|
// just in case it starts with a -, just parse the substring, then adjust the output appropriately
|
|
|
- Result result = parse(new CharSubSequence(str, 1, str.length()), defaultTimezone);
|
|
|
+ ParseResult result = parse(new CharSubSequence(str, 1, str.length()), defaultTimezone);
|
|
|
|
|
|
if (result.errorIndex() >= 0) {
|
|
|
- return Result.error(result.errorIndex() + 1);
|
|
|
+ return ParseResult.error(result.errorIndex() + 1);
|
|
|
} else {
|
|
|
- DateTime dt = result.result();
|
|
|
- return new Result(
|
|
|
+ DateTime dt = (DateTime) result.result();
|
|
|
+ return new ParseResult(
|
|
|
new DateTime(
|
|
|
-dt.years(),
|
|
|
dt.months(),
|
|
@@ -178,15 +163,15 @@ class Iso8601Parser {
|
|
|
* at any point after a time field.
|
|
|
* It also does not use exceptions, instead returning {@code null} where a value cannot be parsed.
|
|
|
*/
|
|
|
- private Result parse(CharSequence str, @Nullable ZoneId defaultTimezone) {
|
|
|
+ private ParseResult parse(CharSequence str, @Nullable ZoneId defaultTimezone) {
|
|
|
int len = str.length();
|
|
|
|
|
|
// YEARS
|
|
|
Integer years = parseInt(str, 0, 4);
|
|
|
- if (years == null) return Result.error(0);
|
|
|
+ if (years == null) return ParseResult.error(0);
|
|
|
if (len == 4) {
|
|
|
return isOptional(ChronoField.MONTH_OF_YEAR)
|
|
|
- ? new Result(
|
|
|
+ ? new ParseResult(
|
|
|
withZoneOffset(
|
|
|
years,
|
|
|
defaults.get(ChronoField.MONTH_OF_YEAR),
|
|
@@ -198,17 +183,17 @@ class Iso8601Parser {
|
|
|
defaultTimezone
|
|
|
)
|
|
|
)
|
|
|
- : Result.error(4);
|
|
|
+ : ParseResult.error(4);
|
|
|
}
|
|
|
|
|
|
- if (str.charAt(4) != '-') return Result.error(4);
|
|
|
+ if (str.charAt(4) != '-') return ParseResult.error(4);
|
|
|
|
|
|
// MONTHS
|
|
|
Integer months = parseInt(str, 5, 7);
|
|
|
- if (months == null || months > 12) return Result.error(5);
|
|
|
+ if (months == null || months > 12) return ParseResult.error(5);
|
|
|
if (len == 7) {
|
|
|
return isOptional(ChronoField.DAY_OF_MONTH)
|
|
|
- ? new Result(
|
|
|
+ ? new ParseResult(
|
|
|
withZoneOffset(
|
|
|
years,
|
|
|
months,
|
|
@@ -220,17 +205,17 @@ class Iso8601Parser {
|
|
|
defaultTimezone
|
|
|
)
|
|
|
)
|
|
|
- : Result.error(7);
|
|
|
+ : ParseResult.error(7);
|
|
|
}
|
|
|
|
|
|
- if (str.charAt(7) != '-') return Result.error(7);
|
|
|
+ if (str.charAt(7) != '-') return ParseResult.error(7);
|
|
|
|
|
|
// DAYS
|
|
|
Integer days = parseInt(str, 8, 10);
|
|
|
- if (days == null || days > 31) return Result.error(8);
|
|
|
+ if (days == null || days > 31) return ParseResult.error(8);
|
|
|
if (len == 10) {
|
|
|
return optionalTime || isOptional(ChronoField.HOUR_OF_DAY)
|
|
|
- ? new Result(
|
|
|
+ ? new ParseResult(
|
|
|
withZoneOffset(
|
|
|
years,
|
|
|
months,
|
|
@@ -242,13 +227,13 @@ class Iso8601Parser {
|
|
|
defaultTimezone
|
|
|
)
|
|
|
)
|
|
|
- : Result.error(10);
|
|
|
+ : ParseResult.error(10);
|
|
|
}
|
|
|
|
|
|
- if (str.charAt(10) != 'T') return Result.error(10);
|
|
|
+ if (str.charAt(10) != 'T') return ParseResult.error(10);
|
|
|
if (len == 11) {
|
|
|
return isOptional(ChronoField.HOUR_OF_DAY)
|
|
|
- ? new Result(
|
|
|
+ ? new ParseResult(
|
|
|
withZoneOffset(
|
|
|
years,
|
|
|
months,
|
|
@@ -260,15 +245,15 @@ class Iso8601Parser {
|
|
|
defaultTimezone
|
|
|
)
|
|
|
)
|
|
|
- : Result.error(11);
|
|
|
+ : ParseResult.error(11);
|
|
|
}
|
|
|
|
|
|
// HOURS + timezone
|
|
|
Integer hours = parseInt(str, 11, 13);
|
|
|
- if (hours == null || hours > 23) return Result.error(11);
|
|
|
+ if (hours == null || hours > 23) return ParseResult.error(11);
|
|
|
if (len == 13) {
|
|
|
return isOptional(ChronoField.MINUTE_OF_HOUR)
|
|
|
- ? new Result(
|
|
|
+ ? new ParseResult(
|
|
|
withZoneOffset(
|
|
|
years,
|
|
|
months,
|
|
@@ -280,12 +265,12 @@ class Iso8601Parser {
|
|
|
defaultTimezone
|
|
|
)
|
|
|
)
|
|
|
- : Result.error(13);
|
|
|
+ : ParseResult.error(13);
|
|
|
}
|
|
|
if (isZoneId(str, 13)) {
|
|
|
ZoneId timezone = parseZoneId(str, 13);
|
|
|
return timezone != null && isOptional(ChronoField.MINUTE_OF_HOUR)
|
|
|
- ? new Result(
|
|
|
+ ? new ParseResult(
|
|
|
withZoneOffset(
|
|
|
years,
|
|
|
months,
|
|
@@ -297,17 +282,17 @@ class Iso8601Parser {
|
|
|
timezone
|
|
|
)
|
|
|
)
|
|
|
- : Result.error(13);
|
|
|
+ : ParseResult.error(13);
|
|
|
}
|
|
|
|
|
|
- if (str.charAt(13) != ':') return Result.error(13);
|
|
|
+ if (str.charAt(13) != ':') return ParseResult.error(13);
|
|
|
|
|
|
// MINUTES + timezone
|
|
|
Integer minutes = parseInt(str, 14, 16);
|
|
|
- if (minutes == null || minutes > 59) return Result.error(14);
|
|
|
+ if (minutes == null || minutes > 59) return ParseResult.error(14);
|
|
|
if (len == 16) {
|
|
|
return isOptional(ChronoField.SECOND_OF_MINUTE)
|
|
|
- ? new Result(
|
|
|
+ ? new ParseResult(
|
|
|
withZoneOffset(
|
|
|
years,
|
|
|
months,
|
|
@@ -319,12 +304,12 @@ class Iso8601Parser {
|
|
|
defaultTimezone
|
|
|
)
|
|
|
)
|
|
|
- : Result.error(16);
|
|
|
+ : ParseResult.error(16);
|
|
|
}
|
|
|
if (isZoneId(str, 16)) {
|
|
|
ZoneId timezone = parseZoneId(str, 16);
|
|
|
return timezone != null && isOptional(ChronoField.SECOND_OF_MINUTE)
|
|
|
- ? new Result(
|
|
|
+ ? new ParseResult(
|
|
|
withZoneOffset(
|
|
|
years,
|
|
|
months,
|
|
@@ -336,30 +321,30 @@ class Iso8601Parser {
|
|
|
timezone
|
|
|
)
|
|
|
)
|
|
|
- : Result.error(16);
|
|
|
+ : ParseResult.error(16);
|
|
|
}
|
|
|
|
|
|
- if (str.charAt(16) != ':') return Result.error(16);
|
|
|
+ if (str.charAt(16) != ':') return ParseResult.error(16);
|
|
|
|
|
|
// SECONDS + timezone
|
|
|
Integer seconds = parseInt(str, 17, 19);
|
|
|
- if (seconds == null || seconds > 59) return Result.error(17);
|
|
|
+ if (seconds == null || seconds > 59) return ParseResult.error(17);
|
|
|
if (len == 19) {
|
|
|
- return new Result(
|
|
|
+ return new ParseResult(
|
|
|
withZoneOffset(years, months, days, hours, minutes, seconds, defaultZero(ChronoField.NANO_OF_SECOND), defaultTimezone)
|
|
|
);
|
|
|
}
|
|
|
if (isZoneId(str, 19)) {
|
|
|
ZoneId timezone = parseZoneId(str, 19);
|
|
|
return timezone != null
|
|
|
- ? new Result(
|
|
|
+ ? new ParseResult(
|
|
|
withZoneOffset(years, months, days, hours, minutes, seconds, defaultZero(ChronoField.NANO_OF_SECOND), timezone)
|
|
|
)
|
|
|
- : Result.error(19);
|
|
|
+ : ParseResult.error(19);
|
|
|
}
|
|
|
|
|
|
char decSeparator = str.charAt(19);
|
|
|
- if (decSeparator != '.' && decSeparator != ',') return Result.error(19);
|
|
|
+ if (decSeparator != '.' && decSeparator != ',') return ParseResult.error(19);
|
|
|
|
|
|
// NANOS + timezone
|
|
|
// nanos are always optional
|
|
@@ -373,23 +358,23 @@ class Iso8601Parser {
|
|
|
nanos = nanos * 10 + (c - ZERO);
|
|
|
}
|
|
|
|
|
|
- if (pos == 20) return Result.error(20); // didn't find a number at all
|
|
|
+ if (pos == 20) return ParseResult.error(20); // didn't find a number at all
|
|
|
|
|
|
// multiply it by the correct multiplicand to get the nanos
|
|
|
nanos *= NANO_MULTIPLICANDS[29 - pos];
|
|
|
|
|
|
if (len == pos) {
|
|
|
- return new Result(withZoneOffset(years, months, days, hours, minutes, seconds, nanos, defaultTimezone));
|
|
|
+ return new ParseResult(withZoneOffset(years, months, days, hours, minutes, seconds, nanos, defaultTimezone));
|
|
|
}
|
|
|
if (isZoneId(str, pos)) {
|
|
|
ZoneId timezone = parseZoneId(str, pos);
|
|
|
return timezone != null
|
|
|
- ? new Result(withZoneOffset(years, months, days, hours, minutes, seconds, nanos, timezone))
|
|
|
- : Result.error(pos);
|
|
|
+ ? new ParseResult(withZoneOffset(years, months, days, hours, minutes, seconds, nanos, timezone))
|
|
|
+ : ParseResult.error(pos);
|
|
|
}
|
|
|
|
|
|
// still chars left at the end - string is not valid
|
|
|
- return Result.error(pos);
|
|
|
+ return ParseResult.error(pos);
|
|
|
}
|
|
|
|
|
|
private static boolean isZoneId(CharSequence str, int pos) {
|