Skip to content

Commit

Permalink
DateUtilities parse has optional param to ensure a "solo" date.
Browse files Browse the repository at this point in the history
  • Loading branch information
jdereg committed Jan 21, 2024
1 parent 2702558 commit 9cd01bc
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 37 deletions.
26 changes: 23 additions & 3 deletions src/main/java/com/cedarsoftware/util/DateUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,25 @@ public final class DateUtilities {
private DateUtilities() {
}

/**
* Main API. Retrieve date-time from passed in String.
* @param dateStr String containing a date. If there is excess content, it will be ignored.
* @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.
*/
public static Date parseDate(String dateStr) {
return parseDate(dateStr, false);
}

/**
* Main API. Retrieve date-time from passed in String. The boolean enSureSoloDate, if set true, ensures that
* no other non-date content existed in the String. That requires additional time to verify.
* @param dateStr String containing a date.
* @param ensureSoloDate If true, if there is excess non-Date content, it will throw an IllegalArgument exception.
* @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.
*/
public static Date parseDate(String dateStr, boolean ensureSoloDate) {
if (dateStr == null) {
return null;
}
Expand Down Expand Up @@ -241,7 +259,9 @@ public static Date parseDate(String dateStr) {
noTime = true; // indicates no "time" portion
}

verifyNoGarbageLeft(remnant);
if (ensureSoloDate) {
verifyNoGarbageLeft(remnant);
}

// Set Timezone into Calendar
Calendar c = initCalendar(tz);
Expand Down Expand Up @@ -330,11 +350,11 @@ private static void verifyNoGarbageLeft(String remnant) {
Matcher dayMatcher = dayPattern.matcher(remnant);
remnant = dayMatcher.replaceFirst("").trim();
if (remnant.startsWith("T")) {
remnant = remnant.substring(1).trim();
remnant = remnant.substring(1);
}
}

// Verify that nothing or "," is all that remains
// Verify that nothing or "," or timezone name is all that remains
if (StringUtilities.length(remnant) > 0) {
remnant = remnant.replaceAll(",|\\[.*?\\]", "").trim();
if (!remnant.isEmpty()) {
Expand Down
155 changes: 121 additions & 34 deletions src/test/java/com/cedarsoftware/util/TestDateUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

Expand Down Expand Up @@ -345,12 +346,12 @@ void testDayOfWeek()
DateUtilities.parseDate(" Dec 25, 2014, thursday ");
}
try {
Date date = DateUtilities.parseDate("text Dec 25, 2014");
Date date = DateUtilities.parseDate("text Dec 25, 2014", true);
fail();
} catch (Exception ignored) { }

try {
DateUtilities.parseDate("Dec 25, 2014 text");
DateUtilities.parseDate("Dec 25, 2014 text", true);
fail();
} catch (Exception ignored) { }
}
Expand Down Expand Up @@ -714,11 +715,11 @@ void testInconsistentDateSeparators()
@Test
void testBadTimeSeparators()
{
assertThatThrownBy(() -> DateUtilities.parseDate("12/24/1996 12.49.58"))
assertThatThrownBy(() -> DateUtilities.parseDate("12/24/1996 12.49.58", true))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Issue parsing date-time, other characters present: 12.49.58");

assertThatThrownBy(() -> DateUtilities.parseDate("12.49.58 12/24/1996"))
assertThatThrownBy(() -> DateUtilities.parseDate("12.49.58 12/24/1996", true))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Issue parsing date-time, other characters present: 12.49.58");

Expand All @@ -728,7 +729,7 @@ void testBadTimeSeparators()
calendar.set(1996, Calendar.DECEMBER, 24, 12, 49, 58);
assertEquals(calendar.getTime(), date);

assertThatThrownBy(() -> DateUtilities.parseDate("12/24/1996 12-49-58"))
assertThatThrownBy(() -> DateUtilities.parseDate("12/24/1996 12-49-58", true))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Issue parsing date-time, other characters present: 12-49-58");
}
Expand Down Expand Up @@ -760,42 +761,128 @@ void testEpochMillis()
assertEquals("31690708-07-05 01:46:39.999", gmtDateString);
}

private static Stream<String> provideTimeZones()
private static Stream provideTimeZones()
{
return Stream.of(
"2024-01-19T15:30:45[Europe/London]",
"2024-01-19T10:15:30[Asia/Tokyo]",
"2024-01-19T20:45:00[America/New_York]",
"2024-01-19T12:00:00-08:00[America/Los_Angeles]",
"2024-01-19T22:30:00+01:00[Europe/Paris]",
"2024-01-19T18:15:45+10:00[Australia/Sydney]",
"2024-01-19T05:00:00-03:00[America/Sao_Paulo]",
"2024-01-19T23:59:59Z[UTC]",
"2024-01-19T14:30:00+05:30[Asia/Kolkata]",
"2024-01-19T21:45:00-05:00[America/Toronto]",
"2024-01-19T16:00:00+02:00[Africa/Cairo]",
"2024-01-19T07:30:00-07:00[America/Denver]",
"2024-01-19T15:30:45 Europe/London",
"2024-01-19T10:15:30 Asia/Tokyo",
"2024-01-19T20:45:00 America/New_York",
"2024-01-19T12:00:00-08:00 America/Los_Angeles",
"2024-01-19T22:30:00+01:00 Europe/Paris",
"2024-01-19T18:15:45+10:00 Australia/Sydney",
"2024-01-19T05:00:00-03:00 America/Sao_Paulo",
"2024-01-19T23:59:59Z UTC",
"2024-01-19T14:30:00+05:30 Asia/Kolkata",
"2024-01-19T21:45:00-05:00 America/Toronto",
"2024-01-19T16:00:00+02:00 Africa/Cairo",
"2024-01-19T07:30:00-07:00 America/Denver",
"2024-01-19T07:30:00 CST",
"2024-01-19T07:30:00+10 EST"
Arguments.of("2024-01-19T15:30:45[Europe/London]", 1705696245000L),
Arguments.of("2024-01-19T10:15:30[Asia/Tokyo]", 1705677330000L),
Arguments.of("2024-01-19T20:45:00[America/New_York]", 1705715100000L),
Arguments.of("2024-01-19T12:00:00-08:00[America/Los_Angeles]", 1705694400000L),
Arguments.of("2024-01-19T22:30:00+01:00[Europe/Paris]", 1705699800000L),
Arguments.of("2024-01-19T18:15:45+10:00[Australia/Sydney]", 1705652145000L),
Arguments.of("2024-01-19T05:00:00-03:00[America/Sao_Paulo]", 1705651200000L),
Arguments.of("2024-01-19T23:59:59Z[UTC]", 1705708799000L),
Arguments.of("2024-01-19T14:30:00+05:30[Asia/Kolkata]", 1705654800000L),
Arguments.of("2024-01-19T21:45:00-05:00[America/Toronto]", 1705718700000L),

Arguments.of("2024-01-19T16:00:00+02:00[Africa/Cairo]", 1705672800000L),
Arguments.of("2024-01-19T07:30:00-07:00[America/Denver]", 1705674600000L),
Arguments.of("2024-01-19T15:30:45 Europe/London", 1705678245000L),
Arguments.of("2024-01-19T10:15:30 Asia/Tokyo", 1705626930000L),
Arguments.of("2024-01-19T20:45:00 America/New_York", 1705715100000L),
Arguments.of("2024-01-19T12:00:00-08:00 America/Los_Angeles", 1705694400000L),
Arguments.of("2024-01-19T22:30:00+01:00 Europe/Paris", 1705699800000L),
Arguments.of("2024-01-19T18:15:45+10:00 Australia/Sydney", 1705652145000L),
Arguments.of("2024-01-19T05:00:00-03:00 America/Sao_Paulo", 1705651200000L),
Arguments.of("2024-01-19T23:59:59Z UTC", 1705708799000L),

Arguments.of("2024-01-19T14:30:00+05:30 Asia/Kolkata", 1705654800000L),
Arguments.of("2024-01-19T21:45:00-05:00 America/Toronto", 1705718700000L),
Arguments.of("2024-01-19T16:00:00+02:00 Africa/Cairo", 1705672800000L),
Arguments.of("2024-01-19T07:30:00-07:00 America/Denver", 1705674600000L),
Arguments.of("2024-01-19T07:30GMT", 1705667400000L),
Arguments.of("2024-01-19T07:30[GMT]", 1705667400000L),
Arguments.of("2024-01-19T07:30 GMT", 1705649400000L),
Arguments.of("2024-01-19T07:30 [GMT]", 1705649400000L),
Arguments.of("2024-01-19T07:30 GMT", 1705649400000L),
Arguments.of("2024-01-19T07:30 [GMT] ", 1705649400000L),

Arguments.of("2024-01-19T07:30 GMT ", 1705649400000L),
Arguments.of("2024-01-19T07:30:01 GMT", 1705649401000L),
Arguments.of("2024-01-19T07:30:01 [GMT]", 1705649401000L),
Arguments.of("2024-01-19T07:30:01GMT", 1705667401000L),
Arguments.of("2024-01-19T07:30:01[GMT]", 1705667401000L),
Arguments.of("2024-01-19T07:30:01.1 GMT", 1705649401100L),
Arguments.of("2024-01-19T07:30:01.1 [GMT]", 1705649401100L),
Arguments.of("2024-01-19T07:30:01.1GMT", 1705667401100L),
Arguments.of("2024-01-19T07:30:01.1[GMT]", 1705667401100L),
Arguments.of("2024-01-19T07:30:01.12GMT", 1705667401120L),

Arguments.of("2024-01-19T07:30:01.12[GMT]", 1705667401120L),
Arguments.of("2024-01-19T07:30:01.12 GMT", 1705649401120L),
Arguments.of("2024-01-19T07:30:01.12 [GMT]", 1705649401120L),
Arguments.of("2024-01-19T07:30:01.123GMT", 1705667401123L),
Arguments.of("2024-01-19T07:30:01.123[GMT]", 1705667401123L),
Arguments.of("2024-01-19T07:30:01.123 GMT", 1705649401123L),
Arguments.of("2024-01-19T07:30:01.123 [GMT]", 1705649401123L),
Arguments.of("2024-01-19T07:30:01.1234GMT", 1705667401123L),
Arguments.of("2024-01-19T07:30:01.1234[GMT]", 1705667401123L),
Arguments.of("2024-01-19T07:30:01.1234 GMT", 1705649401123L),

Arguments.of("2024-01-19T07:30:01.1234 [GMT]", 1705649401123L),
Arguments.of("2024-01-19T07:30:01.123+0100GMT", 1705645801123L), // intentional redundancy on down because ISO 8601 allows it.
Arguments.of("2024-01-19T07:30:01.123+0100[GMT]", 1705645801123L),
Arguments.of("2024-01-19T07:30:01.123+0100 GMT", 1705645801123L),
Arguments.of("2024-01-19T07:30:01.123+0100 [GMT]", 1705645801123L),
Arguments.of("2024-01-19T07:30:01.123-1000GMT", 1705685401123L),
Arguments.of("2024-01-19T07:30:01.123-1000[GMT ]", 1705685401123L),
Arguments.of("2024-01-19T07:30:01.123-1000[ GMT ]", 1705685401123L),
Arguments.of("2024-01-19T07:30:01.123-1000 GMT", 1705685401123L),
Arguments.of("2024-01-19T07:30:01.123-1000 [GMT]", 1705685401123L),

Arguments.of("2024-01-19T07:30:01.123+2 GMT", 1705642201123L), // 18 is max, anything larger of smaller is a java.time exception
Arguments.of("2024-01-19T07:30:01.123+2 [GMT]", 1705642201123L),
Arguments.of("2024-01-19T07:30:01.123-2 GMT", 1705656601123L),
Arguments.of("2024-01-19T07:30:01.123-2 [GMT]", 1705656601123L),
Arguments.of("2024-01-19T07:30:01.123+2GMT", 1705642201123L),
Arguments.of("2024-01-19T07:30:01.123+2[GMT]", 1705642201123L),
Arguments.of("2024-01-19T07:30:01.123-2GMT", 1705656601123L),
Arguments.of("2024-01-19T07:30:01.123-2[GMT]", 1705656601123L),
Arguments.of("2024-01-19T07:30:01.123+18 GMT", 1705584601123L),
Arguments.of("2024-01-19T07:30:01.123+18 [GMT]", 1705584601123L),

Arguments.of("2024-01-19T07:30:01.123-18 GMT", 1705714201123L),
Arguments.of("2024-01-19T07:30:01.123-18 [GMT]", 1705714201123L),
Arguments.of("2024-01-19T07:30:01.123+18:00 GMT", 1705584601123L),
Arguments.of("2024-01-19T07:30:01.123+18:00 [GMT]", 1705584601123L),
Arguments.of("2024-01-19T07:30:01.123-18:00 GMT", 1705714201123L),
Arguments.of("2024-01-19T07:30:01.123-18:00 [GMT]", 1705714201123L),
Arguments.of("2024-01-19T07:30:00+10 EST", 1705613400000L),
Arguments.of("07:30EST 2024-01-19", 1705667400000L),
Arguments.of("07:30[EST] 2024-01-19", 1705667400000L),
Arguments.of("07:30 EST 2024-01-19", 1705667400000L),

Arguments.of("07:30 [EST] 2024-01-19", 1705667400000L),
Arguments.of("07:30:01EST 2024-01-19", 1705667401000L),
Arguments.of("07:30:01[EST] 2024-01-19", 1705667401000L),
Arguments.of("07:30:01 EST 2024-01-19", 1705667401000L),
Arguments.of("07:30:01 [EST] 2024-01-19", 1705667401000L),
Arguments.of("07:30:01.123 EST 2024-01-19", 1705667401123L),
Arguments.of("07:30:01.123 [EST] 2024-01-19", 1705667401123L),
Arguments.of("07:30:01.123+1100 EST 2024-01-19", 1705609801123L),
Arguments.of("07:30:01.123-1100 [EST] 2024-01-19", 1705689001123L),
Arguments.of("07:30:01.123+11:00 [EST] 2024-01-19", 1705609801123L),

Arguments.of("07:30:01.123-11:00 [EST] 2024-01-19", 1705689001123L),
Arguments.of("Wed 07:30:01.123-11:00 [EST] 2024-01-19", 1705689001123L),
Arguments.of("07:30:01.123-11:00 [EST] 2024-01-19 Wed", 1705689001123L),
Arguments.of("07:30:01.123-11:00 [EST] Sunday, January 21, 2024", 1705861801123L),
Arguments.of("07:30:01.123-11:00 [EST] Sunday January 21, 2024", 1705861801123L),
Arguments.of("07:30:01.123-11:00 [EST] January 21, 2024 Sunday", 1705861801123L),
Arguments.of("07:30:01.123-11:00 [EST] January 21, 2024, Sunday", 1705861801123L),
Arguments.of("07:30:01.123-11:00 [America/New_York] January 21, 2024, Sunday", 1705861801123L),
Arguments.of("07:30:01.123-11:00 [Africa/Cairo] 21 Jan 2024 Sun", 1705861801123L),
Arguments.of("07:30:01.123-11:00 [Africa/Cairo] 2024 Jan 21st Sat", 1705861801123L) // day of week should be ignored
);
}

@ParameterizedTest
@MethodSource("provideTimeZones")
void testTimeZoneParsing(String exampleZone)
void testTimeZoneParsing(String exampleZone, Long epochMilli)
{
DateUtilities.parseDate(exampleZone);
for (int i=0; i < 1; i++) {
Date date = DateUtilities.parseDate(exampleZone);
assertEquals(date.getTime(), epochMilli);
}
}
}

0 comments on commit 9cd01bc

Please sign in to comment.