From fadb5dffc83416243722976f76e4675586b55d5c Mon Sep 17 00:00:00 2001 From: Ben Yu Date: Fri, 12 Apr 2024 15:15:42 -0700 Subject: [PATCH] parseToLocalDate() --- .../com/google/mu/time/DateTimeFormats.java | 25 +++++- .../google/mu/time/DateTimeFormatsTest.java | 84 +++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/mug/src/main/java/com/google/mu/time/DateTimeFormats.java b/mug/src/main/java/com/google/mu/time/DateTimeFormats.java index 0c134822e3..ee8d77d740 100644 --- a/mug/src/main/java/com/google/mu/time/DateTimeFormats.java +++ b/mug/src/main/java/com/google/mu/time/DateTimeFormats.java @@ -31,6 +31,7 @@ import java.time.DateTimeException; import java.time.Instant; +import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -128,7 +129,8 @@ public final class DateTimeFormats { BiStream.of( forExample("2011-12-03"), DateTimeFormatter.ISO_LOCAL_DATE, forExample("2011-12-03+08:00"), DateTimeFormatter.ISO_DATE, - forExample("2011-12-03-08:00"), DateTimeFormatter.ISO_DATE).toMap(); + forExample("2011-12-03-08:00"), DateTimeFormatter.ISO_DATE, + forExample("20111203"), DateTimeFormatter.BASIC_ISO_DATE).toMap(); /** These ISO formats all support optional nanoseconds in the format of ".nnnnnnnnn". */ private static final Map, DateTimeFormatter> ISO_DATE_TIME_FORMATTERS = @@ -278,11 +280,30 @@ public static DateTimeFormatter formatOf(String example) { private static T parseDateTime(String dateTimeString, TemporalQuery query) { List signature = forExample(dateTimeString); return lookup(RFC_1123_FORMATTERS, signature) + .orElseGet(() -> lookup(ISO_DATE_FORMATTERS, signature) .orElseGet(() -> lookup(ISO_DATE_TIME_FORMATTERS, forExample(removeNanosecondsPart(dateTimeString))) - .orElseGet(() -> DateTimeFormatter.ofPattern(inferDateTimePattern(dateTimeString, signature)))) + .orElseGet(() -> DateTimeFormatter.ofPattern(inferDateTimePattern(dateTimeString, signature))))) .parse(dateTimeString, query); } + /** + * Parses {@code dateString} to {@link LocalDate}. {@code dateString} could be in the format + * of {@link DateTimeFormatter#ISO_DATE}, {@link DateTimeFormatter#BASIC_ISO_DATE} + * or dates with natural month names like "Jan" or "January". + * + *

If {@code dateString} has valid time and timezone, they'll be ignored. + * + *

Prefer to pre-construct a {@link DateTimeFormatter} using {@link #formatOf} to get + * better performance and earlier error report in case the example date time string cannot + * be inferred. + * + * @throws DateTimeException if {@code dateTimeString} cannot be parsed to {@link LocalDate} + * @since 8.0 + */ + public static LocalDate parseToLocalDate(String dateString) { + return parseDateTime(dateString, LocalDate::from); + } + /** * Parses {@code dateTimeString} to {@link Instant}. {@code dateTimeString} could be in the format * of {@link DateTimeFormatter#ISO_INSTANT}, which is from {@link Instant#toString}; diff --git a/mug/src/test/java/com/google/mu/time/DateTimeFormatsTest.java b/mug/src/test/java/com/google/mu/time/DateTimeFormatsTest.java index 27da089655..d124c436de 100644 --- a/mug/src/test/java/com/google/mu/time/DateTimeFormatsTest.java +++ b/mug/src/test/java/com/google/mu/time/DateTimeFormatsTest.java @@ -676,6 +676,8 @@ public void parseOffsetDateTime_nonStandardFormat() public void parseOffsetDateTime_invalid() throws Exception { assertThrows(DateTimeException.class, () -> DateTimeFormats.parseOffsetDateTime("2020-01-01T00:00:00.123 bad +08:00")); + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseOffsetDateTime("2020-01-01")); + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseOffsetDateTime("2020/01/01")); } @Test @@ -693,6 +695,8 @@ public void parseZonedDateTime_nonStandardFormat() public void parseZonedDateTime_invalid() throws Exception { assertThrows(DateTimeException.class, () -> DateTimeFormats.parseZonedDateTime("2020-01-01T00:00:00.123 bad +08:00")); + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseZonedDateTime("2020-01-01")); + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseZonedDateTime("2020/01/02")); } @Test @@ -723,6 +727,86 @@ public void parseToInstant_nonStandardFormat() public void parseToInstant_invalid() throws Exception { assertThrows(DateTimeException.class, () -> DateTimeFormats.parseToInstant("2020-01-01T00:00:00.123 bad +08:00")); + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseToInstant("2020-01-01")); + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseToInstant("2020/01/01")); + } + + @Test + public void parseLocalDate_basicIsoDate() { + assertThat(DateTimeFormats.parseToLocalDate("20211020")).isEqualTo(LocalDate.of(2021, 10, 20)); + assertThat(DateTimeFormats.parseToLocalDate("20211001")).isEqualTo(LocalDate.of(2021, 10, 1)); + assertThat(DateTimeFormats.parseToLocalDate("20210101")).isEqualTo(LocalDate.of(2021, 1, 1)); + } + + @Test + public void parseLocalDate_isoDate() { + assertThat(DateTimeFormats.parseToLocalDate("2021-10-20")).isEqualTo(LocalDate.of(2021, 10, 20)); + assertThat(DateTimeFormats.parseToLocalDate("2021-10-01")).isEqualTo(LocalDate.of(2021, 10, 1)); + assertThat(DateTimeFormats.parseToLocalDate("2021-01-01")).isEqualTo(LocalDate.of(2021, 1, 1)); + assertThat(DateTimeFormats.parseToLocalDate("2021-01-2")).isEqualTo(LocalDate.of(2021, 1, 2)); + } + + @Test + public void parseLocalDate_euDate_mmddyyyy() { + assertThat(DateTimeFormats.parseToLocalDate("10-30-2021")).isEqualTo(LocalDate.of(2021, 10, 30)); + assertThat(DateTimeFormats.parseToLocalDate("1-30-2021")).isEqualTo(LocalDate.of(2021, 1, 30)); + assertThat(DateTimeFormats.parseToLocalDate("10/20/2021")).isEqualTo(LocalDate.of(2021, 10, 20)); + assertThat(DateTimeFormats.parseToLocalDate("1/20/2021")).isEqualTo(LocalDate.of(2021, 1, 20)); + } + + @Test + public void parseLocalDate_euDate_ddmmyyyy() { + assertThat(DateTimeFormats.parseToLocalDate("30-10-2021")).isEqualTo(LocalDate.of(2021, 10, 30)); + assertThat(DateTimeFormats.parseToLocalDate("20/10/2021")).isEqualTo(LocalDate.of(2021, 10, 20)); + } + + @Test + public void parseLocalDate_withMonthName_yyyymmdd() { + assertThat(DateTimeFormats.parseToLocalDate("2021 Oct 20")).isEqualTo(LocalDate.of(2021, 10, 20)); + assertThat(DateTimeFormats.parseToLocalDate("2021 October 1")).isEqualTo(LocalDate.of(2021, 10, 1)); + } + + @Test + public void parseLocalDate_withMonthName_mmddyyyy() { + assertThat(DateTimeFormats.parseToLocalDate("Oct 20 2021")).isEqualTo(LocalDate.of(2021, 10, 20)); + assertThat(DateTimeFormats.parseToLocalDate("October 1 2021")).isEqualTo(LocalDate.of(2021, 10, 1)); + } + + @Test + public void parseLocalDate_isoDateWithSlash() { + assertThat(DateTimeFormats.parseToLocalDate("2021/10/20")).isEqualTo(LocalDate.of(2021, 10, 20)); + assertThat(DateTimeFormats.parseToLocalDate("2021/10/01")).isEqualTo(LocalDate.of(2021, 10, 1)); + assertThat(DateTimeFormats.parseToLocalDate("2021/01/01")).isEqualTo(LocalDate.of(2021, 1, 1)); + assertThat(DateTimeFormats.parseToLocalDate("2021/01/2")).isEqualTo(LocalDate.of(2021, 1, 2)); + } + + @Test + public void parseLocalDate_instantHasNoDate() { + Instant time = OffsetDateTime.of(2024, 4, 1, 10, 05, 30, 0, ZoneOffset.UTC).toInstant(); + assertThrows( + DateTimeException.class, + () -> DateTimeFormats.parseToLocalDate(time.toString())); + } + + @Test + public void parseLocalDate_fromZonedDateTimeString() { + ZonedDateTime time = ZonedDateTime.of(2024, 4, 1, 10, 05, 30, 0, ZoneId.of("America/New_York")); + assertThat(DateTimeFormats.parseToLocalDate(time.toString())).isEqualTo(LocalDate.of(2024, 4, 1)); + } + + @Test + public void parseLocalDate_fromOffsetDateTimeString() { + OffsetDateTime time = OffsetDateTime.of(2024, 4, 1, 10, 05, 30, 0, ZoneOffset.of("-08:30")); + assertThat(DateTimeFormats.parseToLocalDate(time.toString())).isEqualTo(LocalDate.of(2024, 4, 1)); + } + + @Test + public void parseLocalDate_incorrectDate() { + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseToLocalDate("20213001")); + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseToLocalDate("2021/30/01")); + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseToLocalDate("2021-30-01")); + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseToLocalDate("01-01-2021")); + assertThrows(DateTimeException.class, () -> DateTimeFormats.parseToLocalDate("01/01/2021")); } @Test