From b72a332311f40d275bf74859ec2e46a7896d685b Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Fri, 7 Dec 2018 11:00:47 -0800 Subject: [PATCH] Core: Remove parseDefaulting from DateFormatter This commit removes the parseDefaulting method from DateFormatter, bringing it more inline with the joda equivalent FormatDateTimeFormatter. This method was only needed for the java time implementation of DateMathParser. Instead, a DateFormatter now returns an implementation of DateMathParser for the given format, allowing the java time implementation to construct the appropriate date math parser internally. --- .../metadata/IndexNameExpressionResolver.java | 3 +- .../common/time/DateFormatter.java | 36 ++++++++++++------- .../common/time/EpochMillisDateFormatter.java | 15 ++++---- .../time/EpochSecondsDateFormatter.java | 13 ++++--- .../common/time/JavaDateFormatter.java | 21 +++++++++-- .../common/time/JavaDateMathParser.java | 18 ++-------- .../common/time/JavaDateMathParserTests.java | 8 ++--- 7 files changed, 64 insertions(+), 50 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java index 9baedf6b65e33..f60866383107a 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java @@ -30,7 +30,6 @@ import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.time.DateMathParser; -import org.elasticsearch.common.time.JavaDateMathParser; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexNotFoundException; @@ -921,7 +920,7 @@ String resolveExpression(String expression, final Context context) { dateFormatter = DateFormatters.forPattern(dateFormatterPattern); } DateFormatter formatter = dateFormatter.withZone(timeZone); - DateMathParser dateMathParser = new JavaDateMathParser(formatter); + DateMathParser dateMathParser = formatter.toDateMathParser(); long millis = dateMathParser.parse(mathExpression, context::getStartTime, false, timeZone); String time = formatter.format(Instant.ofEpochMilli(millis)); 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 b952f1d69bcd6..4c9487fe3bd6b 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateFormatter.java @@ -19,13 +19,13 @@ package org.elasticsearch.common.time; +import org.elasticsearch.ElasticsearchParseException; + import java.time.ZoneId; import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalField; import java.util.Arrays; import java.util.Locale; -import java.util.Map; import java.util.stream.Collectors; public interface DateFormatter { @@ -86,13 +86,9 @@ public interface DateFormatter { ZoneId getZone(); /** - * Configure a formatter using default fields for a TemporalAccessor that should be used in case - * the supplied date is not having all of those fields - * - * @param fields A Map<TemporalField, Long> of fields to be used as fallbacks - * @return A new date formatter instance, that will use those fields during parsing + * Return a {@link DateMathParser} built from this formatter. */ - DateFormatter parseDefaulting(Map fields); + DateMathParser toDateMathParser(); /** * Merge several date formatters into a single one. Useful if you need to have several formatters with @@ -102,7 +98,7 @@ public interface DateFormatter { * @param formatters The list of date formatters to be merged together * @return The new date formtter containing the specified date formatters */ - static DateFormatter merge(DateFormatter ... formatters) { + static DateFormatter merge(DateFormatter... formatters) { return new MergedDateFormatter(formatters); } @@ -110,10 +106,12 @@ class MergedDateFormatter implements DateFormatter { private final String format; private final DateFormatter[] formatters; + private final DateMathParser[] dateMathParsers; - MergedDateFormatter(DateFormatter ... formatters) { + MergedDateFormatter(DateFormatter... formatters) { this.formatters = formatters; this.format = Arrays.stream(formatters).map(DateFormatter::pattern).collect(Collectors.joining("||")); + this.dateMathParsers = Arrays.stream(formatters).map(DateFormatter::toDateMathParser).toArray(DateMathParser[]::new); } @Override @@ -164,8 +162,22 @@ public ZoneId getZone() { } @Override - public DateFormatter parseDefaulting(Map fields) { - return new MergedDateFormatter(Arrays.stream(formatters).map(f -> f.parseDefaulting(fields)).toArray(DateFormatter[]::new)); + public DateMathParser toDateMathParser() { + return (text, now, roundUp, tz) -> { + ElasticsearchParseException failure = null; + for (DateMathParser parser : dateMathParsers) { + try { + return parser.parse(text, now, roundUp, tz); + } catch (ElasticsearchParseException e) { + if (failure == null) { + failure = e; + } else { + failure.addSuppressed(e); + } + } + } + throw failure; + }; } } } diff --git a/server/src/main/java/org/elasticsearch/common/time/EpochMillisDateFormatter.java b/server/src/main/java/org/elasticsearch/common/time/EpochMillisDateFormatter.java index 4fce63510029d..fc7c44c71424c 100644 --- a/server/src/main/java/org/elasticsearch/common/time/EpochMillisDateFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/EpochMillisDateFormatter.java @@ -25,9 +25,7 @@ import java.time.ZoneOffset; import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalField; import java.util.Locale; -import java.util.Map; import java.util.regex.Pattern; /** @@ -42,7 +40,8 @@ class EpochMillisDateFormatter implements DateFormatter { private static final Pattern SPLIT_BY_DOT_PATTERN = Pattern.compile("\\."); - static DateFormatter INSTANCE = new EpochMillisDateFormatter(); + static final DateFormatter INSTANCE = new EpochMillisDateFormatter(); + static final DateMathParser DATE_MATH_INSTANCE = new JavaDateMathParser(INSTANCE, INSTANCE); private EpochMillisDateFormatter() { } @@ -103,11 +102,6 @@ public String pattern() { return "epoch_millis"; } - @Override - public DateFormatter parseDefaulting(Map fields) { - return this; - } - @Override public Locale getLocale() { return Locale.ROOT; @@ -117,4 +111,9 @@ public Locale getLocale() { public ZoneId getZone() { return ZoneOffset.UTC; } + + @Override + public DateMathParser toDateMathParser() { + return DATE_MATH_INSTANCE; + } } diff --git a/server/src/main/java/org/elasticsearch/common/time/EpochSecondsDateFormatter.java b/server/src/main/java/org/elasticsearch/common/time/EpochSecondsDateFormatter.java index 218542f817be6..8215355a67821 100644 --- a/server/src/main/java/org/elasticsearch/common/time/EpochSecondsDateFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/EpochSecondsDateFormatter.java @@ -25,14 +25,13 @@ import java.time.ZoneOffset; import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalField; import java.util.Locale; -import java.util.Map; import java.util.regex.Pattern; public class EpochSecondsDateFormatter implements DateFormatter { public static DateFormatter INSTANCE = new EpochSecondsDateFormatter(); + static final DateMathParser DATE_MATH_INSTANCE = new JavaDateMathParser(INSTANCE, INSTANCE); private static final Pattern SPLIT_BY_DOT_PATTERN = Pattern.compile("\\."); private EpochSecondsDateFormatter() {} @@ -91,6 +90,11 @@ public ZoneId getZone() { return ZoneOffset.UTC; } + @Override + public DateMathParser toDateMathParser() { + return DATE_MATH_INSTANCE; + } + @Override public DateFormatter withZone(ZoneId zoneId) { if (zoneId.equals(ZoneOffset.UTC) == false) { @@ -106,9 +110,4 @@ public DateFormatter withLocale(Locale locale) { } return this; } - - @Override - public DateFormatter parseDefaulting(Map fields) { - return this; - } } 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 75cd82b51e85a..440bdb3f80b7c 100644 --- a/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java @@ -23,15 +23,28 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import java.util.Arrays; +import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Objects; class JavaDateFormatter implements DateFormatter { + // base fields which should be used for default parsing, when we round up for date math + private static final Map ROUND_UP_BASE_FIELDS = new HashMap<>(6); + { + ROUND_UP_BASE_FIELDS.put(ChronoField.MONTH_OF_YEAR, 1L); + ROUND_UP_BASE_FIELDS.put(ChronoField.DAY_OF_MONTH, 1L); + ROUND_UP_BASE_FIELDS.put(ChronoField.HOUR_OF_DAY, 23L); + ROUND_UP_BASE_FIELDS.put(ChronoField.MINUTE_OF_HOUR, 59L); + ROUND_UP_BASE_FIELDS.put(ChronoField.SECOND_OF_MINUTE, 59L); + ROUND_UP_BASE_FIELDS.put(ChronoField.MILLI_OF_SECOND, 999L); + } + private final String format; private final DateTimeFormatter printer; private final DateTimeFormatter[] parsers; @@ -116,8 +129,7 @@ public String pattern() { return format; } - @Override - public DateFormatter parseDefaulting(Map fields) { + JavaDateFormatter parseDefaulting(Map fields) { final DateTimeFormatterBuilder parseDefaultingBuilder = new DateTimeFormatterBuilder().append(printer); fields.forEach(parseDefaultingBuilder::parseDefaulting); if (parsers.length == 1 && parsers[0].equals(printer)) { @@ -143,6 +155,11 @@ public ZoneId getZone() { return this.printer.getZone(); } + @Override + public DateMathParser toDateMathParser() { + return new JavaDateMathParser(this, this.parseDefaulting(ROUND_UP_BASE_FIELDS)); + } + @Override public int hashCode() { return Objects.hash(getLocale(), printer.getZone(), format); diff --git a/server/src/main/java/org/elasticsearch/common/time/JavaDateMathParser.java b/server/src/main/java/org/elasticsearch/common/time/JavaDateMathParser.java index c3a59f521904b..2d3fb7e176e89 100644 --- a/server/src/main/java/org/elasticsearch/common/time/JavaDateMathParser.java +++ b/server/src/main/java/org/elasticsearch/common/time/JavaDateMathParser.java @@ -31,10 +31,7 @@ import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAdjusters; -import java.time.temporal.TemporalField; import java.time.temporal.TemporalQueries; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; import java.util.function.LongSupplier; @@ -47,24 +44,15 @@ */ public class JavaDateMathParser implements DateMathParser { - // base fields which should be used for default parsing, when we round up - private static final Map ROUND_UP_BASE_FIELDS = new HashMap<>(6); - { - ROUND_UP_BASE_FIELDS.put(ChronoField.MONTH_OF_YEAR, 1L); - ROUND_UP_BASE_FIELDS.put(ChronoField.DAY_OF_MONTH, 1L); - ROUND_UP_BASE_FIELDS.put(ChronoField.HOUR_OF_DAY, 23L); - ROUND_UP_BASE_FIELDS.put(ChronoField.MINUTE_OF_HOUR, 59L); - ROUND_UP_BASE_FIELDS.put(ChronoField.SECOND_OF_MINUTE, 59L); - ROUND_UP_BASE_FIELDS.put(ChronoField.MILLI_OF_SECOND, 999L); - } + private final DateFormatter formatter; private final DateFormatter roundUpFormatter; - public JavaDateMathParser(DateFormatter formatter) { + public JavaDateMathParser(DateFormatter formatter, DateFormatter roundUpFormatter) { Objects.requireNonNull(formatter); this.formatter = formatter; - this.roundUpFormatter = formatter.parseDefaulting(ROUND_UP_BASE_FIELDS); + this.roundUpFormatter = roundUpFormatter; } @Override diff --git a/server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java b/server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java index a543af0445db1..ce625c33fea8d 100644 --- a/server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java +++ b/server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java @@ -36,7 +36,7 @@ public class JavaDateMathParserTests extends ESTestCase { private final DateFormatter formatter = DateFormatters.forPattern("dateOptionalTime||epoch_millis"); - private final JavaDateMathParser parser = new JavaDateMathParser(formatter); + private final DateMathParser parser = formatter.toDateMathParser(); public void testBasicDates() { assertDateMathEquals("2014", "2014-01-01T00:00:00.000"); @@ -139,7 +139,7 @@ public void testNow() { public void testRoundingPreservesEpochAsBaseDate() { // If a user only specifies times, then the date needs to always be 1970-01-01 regardless of rounding DateFormatter formatter = DateFormatters.forPattern("HH:mm:ss"); - JavaDateMathParser parser = new JavaDateMathParser(formatter); + DateMathParser parser = formatter.toDateMathParser(); ZonedDateTime zonedDateTime = DateFormatters.toZonedDateTime(formatter.parse("04:52:20")); assertThat(zonedDateTime.getYear(), is(1970)); long millisStart = zonedDateTime.toInstant().toEpochMilli(); @@ -165,7 +165,7 @@ public void testImplicitRounding() { // implicit rounding with explicit timezone in the date format DateFormatter formatter = DateFormatters.forPattern("yyyy-MM-ddXXX"); - JavaDateMathParser parser = new JavaDateMathParser(formatter); + DateMathParser parser = formatter.toDateMathParser(); long time = parser.parse("2011-10-09+01:00", () -> 0, false, (ZoneId) null); assertEquals(this.parser.parse("2011-10-09T00:00:00.000+01:00", () -> 0), time); time = parser.parse("2011-10-09+01:00", () -> 0, true, (ZoneId) null); @@ -239,7 +239,7 @@ public void testTimestamps() { assertDateMathEquals("1418248078000||/m", "2014-12-10T21:47:00.000"); // also check other time units - JavaDateMathParser parser = new JavaDateMathParser(DateFormatters.forPattern("epoch_second||dateOptionalTime")); + DateMathParser parser = DateFormatters.forPattern("epoch_second||dateOptionalTime").toDateMathParser(); long datetime = parser.parse("1418248078", () -> 0); assertDateEquals(datetime, "1418248078", "2014-12-10T21:47:58.000");