Browse Source

Core: Remove parseDefaulting from DateFormatter (#36386)

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.
Ryan Ernst 6 years ago
parent
commit
a998f4dec6

+ 1 - 2
server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java

@@ -30,7 +30,6 @@ import org.elasticsearch.common.regex.Regex;
 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 @@ public class IndexNameExpressionResolver {
                                     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));

+ 24 - 12
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 <code>Map&lt;TemporalField, Long&gt;</code> 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<TemporalField, Long> 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 @@ public interface 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 interface DateFormatter {
         }
 
         @Override
-        public DateFormatter parseDefaulting(Map<TemporalField, Long> 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;
+            };
         }
     }
 }

+ 7 - 8
server/src/main/java/org/elasticsearch/common/time/EpochMillisDateFormatter.java

@@ -25,9 +25,7 @@ import java.time.ZoneId;
 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 @@ import java.util.regex.Pattern;
 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 @@ class EpochMillisDateFormatter implements DateFormatter {
         return "epoch_millis";
     }
 
-    @Override
-    public DateFormatter parseDefaulting(Map<TemporalField, Long> fields) {
-        return this;
-    }
-
     @Override
     public Locale getLocale() {
         return Locale.ROOT;
@@ -117,4 +111,9 @@ class EpochMillisDateFormatter implements DateFormatter {
     public ZoneId getZone() {
         return ZoneOffset.UTC;
     }
+
+    @Override
+    public DateMathParser toDateMathParser() {
+        return DATE_MATH_INSTANCE;
+    }
 }

+ 6 - 7
server/src/main/java/org/elasticsearch/common/time/EpochSecondsDateFormatter.java

@@ -25,14 +25,13 @@ import java.time.ZoneId;
 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 class EpochSecondsDateFormatter implements DateFormatter {
         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 class EpochSecondsDateFormatter implements DateFormatter {
         }
         return this;
     }
-
-    @Override
-    public DateFormatter parseDefaulting(Map<TemporalField, Long> fields) {
-        return this;
-    }
 }

+ 19 - 2
server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java

@@ -23,15 +23,28 @@ import java.time.ZoneId;
 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<TemporalField, Long> 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 @@ class JavaDateFormatter implements DateFormatter {
         return format;
     }
 
-    @Override
-    public DateFormatter parseDefaulting(Map<TemporalField, Long> fields) {
+    JavaDateFormatter parseDefaulting(Map<TemporalField, Long> fields) {
         final DateTimeFormatterBuilder parseDefaultingBuilder = new DateTimeFormatterBuilder().append(printer);
         fields.forEach(parseDefaultingBuilder::parseDefaulting);
         if (parsers.length == 1 && parsers[0].equals(printer)) {
@@ -143,6 +155,11 @@ class JavaDateFormatter implements DateFormatter {
         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);

+ 3 - 15
server/src/main/java/org/elasticsearch/common/time/JavaDateMathParser.java

@@ -31,10 +31,7 @@ import java.time.ZonedDateTime;
 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 @@ import java.util.function.LongSupplier;
  */
 public class JavaDateMathParser implements DateMathParser {
 
-    // base fields which should be used for default parsing, when we round up
-    private static final Map<TemporalField, Long> 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

+ 4 - 4
server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java

@@ -36,7 +36,7 @@ import static org.hamcrest.Matchers.is;
 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 class JavaDateMathParserTests extends ESTestCase {
     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 class JavaDateMathParserTests extends ESTestCase {
 
         // 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 class JavaDateMathParserTests extends ESTestCase {
         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");