Skip to content

Commit

Permalink
Allow for redundant timezone name and timezone offset. Although, not …
Browse files Browse the repository at this point in the history
…ISO 8601 compliant, Java allows it:

ISO_ZONED_DATE_TIME
public static final DateTimeFormatter ISO_ZONED_DATE_TIME
The ISO-like date-time formatter that formats or parses a date-time with offset and zone, such as '2011-12-03T10:15:30+01:00[Europe/Paris]'.
This returns an immutable formatter capable of formatting and parsing a format that extends the ISO-8601 extended offset date-time format to add the time-zone. The section in square brackets is not part of the ISO-8601 standard. The format consists of:

The ISO_OFFSET_DATE_TIME
If the zone ID is not available or is a ZoneOffset then the format is complete.
An open square bracket '['.
The zone ID. This is not part of the ISO-8601 standard. Parsing is case sensitive.
A close square bracket ']'.
The returned formatter has a chronology of ISO set to ensure dates in other calendar systems are correctly converted. It has no override zone and uses the STRICT resolver style.
  • Loading branch information
jdereg committed Jan 29, 2024
1 parent aede85b commit e123c9a
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 20 deletions.
18 changes: 11 additions & 7 deletions src/main/java/com/cedarsoftware/util/DateUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public final class DateUtilities {
Pattern.CASE_INSENSITIVE);

private static final Pattern timePattern = Pattern.compile(
"(" + d2 + "):(" + d2 + ")(?::(" + d2 + ")(" + nano + ")?)?(" + tz_Hh_MM + "|" + tz_HHMM + "|" + tz_Hh + "|Z|" + tzNamed + ")?",
"(" + d2 + "):(" + d2 + ")(?::(" + d2 + ")(" + nano + ")?)?(" + tz_Hh_MM + "|" + tz_HHMM + "|" + tz_Hh + "|Z)?(" + tzNamed + ")?",
Pattern.CASE_INSENSITIVE);

private static final Pattern dayPattern = Pattern.compile("\\b(" + days + ")\\b", Pattern.CASE_INSENSITIVE);
Expand Down Expand Up @@ -150,9 +150,9 @@ private DateUtilities() {
}

/**
* Original API. If the date-time given does not include a timezone offset, then ZoneId.systemDefault() will be used.
* We recommend using the parseDate(3 args) version, so you can control the default timezone used when one is not
* specified.
* Original API. If the date-time given does not include a timezone offset or name, then ZoneId.systemDefault()
* will be used. We recommend using the parseDate(3 args) version, so you can control the default timezone used
* when one is not specified.
* @param dateStr String containing a date. If there is excess content, it will throw an IllegalArgumentException.
* @return Date instance that represents the passed in date. See comments at top of class for supported
* formats. This API is intended to be super flexible in terms of what it can parse. If a null or empty String is
Expand Down Expand Up @@ -185,7 +185,7 @@ public static ZonedDateTime parseDate(String dateStr, ZoneId defaultZoneId, bool
dateStr = dateStr.trim();

if (allDigits.matcher(dateStr).matches()) {
return Instant.ofEpochMilli(Long.parseLong(dateStr)).atZone(defaultZoneId);
return Instant.ofEpochMilli(Long.parseLong(dateStr)).atZone(ZoneId.of("UTC"));
}

String year, day, remains, tz = null;
Expand Down Expand Up @@ -246,7 +246,6 @@ public static ZonedDateTime parseDate(String dateStr, ZoneId defaultZoneId, bool
remains = remains.trim();
matcher = timePattern.matcher(remains);
remnant = matcher.replaceFirst("");
boolean noTime = false;

if (remnant.length() < remains.length()) {
hour = matcher.group(1);
Expand All @@ -258,7 +257,12 @@ public static ZonedDateTime parseDate(String dateStr, ZoneId defaultZoneId, bool
fracSec = "0" + matcher.group(4);
}
if (matcher.group(5) != null) {
tz = stripBrackets(matcher.group(5).trim());
tz = matcher.group(5).trim();
}
if (matcher.group(6) != null) {
if (StringUtilities.isEmpty(tz)) { // Only use timezone name when offset is not used
tz = stripBrackets(matcher.group(6).trim());
}
}
}

Expand Down
17 changes: 4 additions & 13 deletions src/test/java/com/cedarsoftware/util/TestDateUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ void testTimeBetterThanMilliResolution()
assertEquals(-60*60*5, zonedDateTime.getOffset().getTotalSeconds());
}

private static Stream provideBadFormats() {
private static Stream provideRedundantFormats() {
return Stream.of(
Arguments.of("2024-01-19T12:00:00-08:00[America/Los_Angeles]"),
Arguments.of("2024-01-19T22:30:00+01:00[Europe/Paris]"),
Expand All @@ -890,14 +890,11 @@ private static Stream provideBadFormats() {
Arguments.of("2024-01-19T22:30:00+01:00 Europe/Paris"),
Arguments.of("2024-01-19T23:59:59Z UTC"),
Arguments.of("2024-01-19T23:59:59Z[UTC]"),
Arguments.of("2024-01-19T07:30:01[UTC] America/New_York"),
Arguments.of("2024-01-19T07:30:01.123+0100GMT"),
Arguments.of("2024-01-19T07:30:01.123+0100[GMT]"),
Arguments.of("2024-01-19T07:30:01.123+0100 GMT"),
Arguments.of("2024-01-19T07:30:01.123+0100 [GMT]"),
Arguments.of("2024-01-19T07:30:01.123-1000GMT"),
Arguments.of("2024-01-19T07:30:01.123-1000[GMT ]"),
Arguments.of("2024-01-19T07:30:01.123-1000[ GMT ]"),
Arguments.of("2024-01-19T07:30:01.123-1000 GMT"),
Arguments.of("2024-01-19T07:30:01.123-1000 [GMT]"),
Arguments.of("2024-01-19T07:30:01.123+2 GMT"),
Expand Down Expand Up @@ -929,20 +926,14 @@ private static Stream provideBadFormats() {
Arguments.of("07:30:01.123-11:00 [EST] January 21, 2024, Sunday"),
Arguments.of("07:30:01.123-11:00 [America/New_York] January 21, 2024, Sunday"),
Arguments.of("07:30:01.123-11:00 [Africa/Cairo] 21 Jan 2024 Sun"),
Arguments.of("07:30:01.123-11:00 [Africa/Cairo] 2024 Jan 21st Sat"),
Arguments.of("12.17.1965 07:05:"),
Arguments.of("12.17.1965 07:05:.123"),
Arguments.of("12.17.1965 07:05.123"),
Arguments.of("12.17.1965 07:05.12-0500")
Arguments.of("07:30:01.123-11:00 [Africa/Cairo] 2024 Jan 21st Sat")
);
}

@ParameterizedTest
@MethodSource("provideBadFormats")
@MethodSource("provideRedundantFormats")
void testFormatsThatShouldNotWork(String badFormat)
{
assertThatThrownBy(() -> DateUtilities.parseDate(badFormat, ZoneId.systemDefault(), true))
.isInstanceOf(java.lang.IllegalArgumentException.class)
.hasMessageContaining("Issue parsing date-time, other characters present:");
DateUtilities.parseDate(badFormat, ZoneId.systemDefault(), true);
}
}

0 comments on commit e123c9a

Please sign in to comment.