From 69ce4f3a316fcf29e6ffc165805d8206a222800d Mon Sep 17 00:00:00 2001 From: Dan Hermann Date: Thu, 10 Feb 2022 13:12:52 -0600 Subject: [PATCH 1/4] Add method that parses dates without throwing exceptions --- .../common/time/DateFormatter.java | 9 +++++++++ .../common/time/JavaDateFormatter.java | 14 +++++++++++++ .../common/time/DateFormattersTests.java | 20 +++++++++++++++++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java b/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java index b6b11abcfd90d..5972cc27a3114 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java @@ -9,6 +9,7 @@ package org.elasticsearch.common.time; import org.elasticsearch.common.Strings; +import org.elasticsearch.core.Tuple; import java.time.Instant; import java.time.ZoneId; @@ -30,6 +31,14 @@ public interface DateFormatter { */ TemporalAccessor parse(String input); + /** + * Try to parse input to a java time TemporalAccessor + * @param input An arbitrary string resembling the string representation of a date or time + * @return Tuple containing a boolean value indicating parsing success or failure and a java time object containing + * the parsed input in the case of successful parsing + */ + Tuple parseWithoutException(String input); + /** * Parse the given input into millis-since-epoch. */ diff --git a/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java b/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java index 2b74e0de0acf9..2dc31e54080d7 100644 --- a/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.Maps; +import org.elasticsearch.core.Tuple; import java.text.ParsePosition; import java.time.ZoneId; @@ -176,6 +177,19 @@ public TemporalAccessor parse(String input) { } } + @Override + public Tuple parseWithoutException(String input) { + for (DateTimeFormatter formatter : parsers) { + ParsePosition pos = new ParsePosition(0); + Object object = formatter.toFormat().parseObject(input, pos); + if (parsingSucceeded(object, input, pos)) { + return Tuple.tuple(true, (TemporalAccessor) object); + } + } + + return Tuple.tuple(false, null); + } + /** * Attempt parsing the input without throwing exception. If multiple parsers are provided, * it will continue iterating if the previous parser failed. The pattern must fully match, meaning whole input was used. diff --git a/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java b/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java index df323e6bc625c..202a8b573dc91 100644 --- a/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java +++ b/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java @@ -26,20 +26,31 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.equalToObject; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; +import static org.mockito.ArgumentMatchers.eq; public class DateFormattersTests extends ESTestCase { private void assertParseException(String input, String format) { + assertParseException(input, format, true); + } + + private void assertParseException(String input, String format, boolean testParseWithoutException) { + DateFormatter javaTimeFormatter = DateFormatter.forPattern(format); IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> javaTimeFormatter.parse(input)); assertThat(e.getMessage(), containsString(input)); assertThat(e.getMessage(), containsString(format)); + if (testParseWithoutException) { + var result = javaTimeFormatter.parseWithoutException(input); + assertFalse(result.v1()); + } } private void assertParses(String input, String format) { @@ -53,6 +64,11 @@ private void assertParses(String input, DateFormatter formatter) { ZonedDateTime zonedDateTime = DateFormatters.from(javaTimeAccessor); assertThat(zonedDateTime, notNullValue()); + + var result = formatter.parseWithoutException(input); + assertTrue(result.v1()); + ZonedDateTime zdt2 = DateFormatters.from(result.v2()); + assertThat(zonedDateTime, equalTo(zdt2)); } private void assertDateMathEquals(String text, String pattern) { @@ -426,8 +442,8 @@ public void testFractionalSeconds() { } public void testIncorrectFormat() { - assertParseException("2021-01-01T23-35-00Z", "strict_date_optional_time||epoch_millis"); - assertParseException("2021-01-01T23-35-00Z", "strict_date_optional_time"); + assertParseException("2021-01-01T23-35-00Z", "strict_date_optional_time||epoch_millis", false); + assertParseException("2021-01-01T23-35-00Z", "strict_date_optional_time", false); } public void testMinMillis() { From dfbb1e6e27b29d95c8786f1f52b2d89f8d2420a1 Mon Sep 17 00:00:00 2001 From: Dan Hermann Date: Thu, 10 Feb 2022 13:36:51 -0600 Subject: [PATCH 2/4] checkstyle --- .../java/org/elasticsearch/common/time/DateFormattersTests.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java b/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java index 202a8b573dc91..0ed73a1d243f8 100644 --- a/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java +++ b/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java @@ -26,14 +26,12 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.equalToObject; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; -import static org.mockito.ArgumentMatchers.eq; public class DateFormattersTests extends ESTestCase { From 15622ec612a3c30324754758328f9b4d23cd51ee Mon Sep 17 00:00:00 2001 From: Dan Hermann Date: Thu, 10 Feb 2022 16:10:54 -0600 Subject: [PATCH 3/4] Change return type to Optional --- .../java/org/elasticsearch/common/time/DateFormatter.java | 3 ++- .../org/elasticsearch/common/time/JavaDateFormatter.java | 8 ++++---- .../elasticsearch/common/time/DateFormattersTests.java | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java b/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java index 5972cc27a3114..2883e979dcf2f 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Optional; public interface DateFormatter { @@ -37,7 +38,7 @@ public interface DateFormatter { * @return Tuple containing a boolean value indicating parsing success or failure and a java time object containing * the parsed input in the case of successful parsing */ - Tuple parseWithoutException(String input); + Optional parseWithoutException(String input); /** * Parse the given input into millis-since-epoch. diff --git a/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java b/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java index 2dc31e54080d7..8d8f6f021a877 100644 --- a/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java @@ -10,7 +10,6 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.Maps; -import org.elasticsearch.core.Tuple; import java.text.ParsePosition; import java.time.ZoneId; @@ -28,6 +27,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -178,16 +178,16 @@ public TemporalAccessor parse(String input) { } @Override - public Tuple parseWithoutException(String input) { + public Optional parseWithoutException(String input) { for (DateTimeFormatter formatter : parsers) { ParsePosition pos = new ParsePosition(0); Object object = formatter.toFormat().parseObject(input, pos); if (parsingSucceeded(object, input, pos)) { - return Tuple.tuple(true, (TemporalAccessor) object); + return Optional.of((TemporalAccessor) object); } } - return Tuple.tuple(false, null); + return Optional.empty(); } /** diff --git a/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java b/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java index 0ed73a1d243f8..2873aa3cde269 100644 --- a/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java +++ b/server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java @@ -47,7 +47,7 @@ private void assertParseException(String input, String format, boolean testParse assertThat(e.getMessage(), containsString(format)); if (testParseWithoutException) { var result = javaTimeFormatter.parseWithoutException(input); - assertFalse(result.v1()); + assertTrue(result.isEmpty()); } } @@ -64,8 +64,8 @@ private void assertParses(String input, DateFormatter formatter) { assertThat(zonedDateTime, notNullValue()); var result = formatter.parseWithoutException(input); - assertTrue(result.v1()); - ZonedDateTime zdt2 = DateFormatters.from(result.v2()); + assertFalse(result.isEmpty()); + ZonedDateTime zdt2 = DateFormatters.from(result.get()); assertThat(zonedDateTime, equalTo(zdt2)); } From fc08d6a45a5ff316090e1a539bfd8b3f757c8403 Mon Sep 17 00:00:00 2001 From: Dan Hermann Date: Thu, 10 Feb 2022 16:19:42 -0600 Subject: [PATCH 4/4] checkstyle --- .../main/java/org/elasticsearch/common/time/DateFormatter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java b/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java index 2883e979dcf2f..d51a037579e6a 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java @@ -9,7 +9,6 @@ package org.elasticsearch.common.time; import org.elasticsearch.common.Strings; -import org.elasticsearch.core.Tuple; import java.time.Instant; import java.time.ZoneId;