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 4bb765ff503..a1da09c770b 100644 --- a/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java +++ b/server/src/main/java/org/elasticsearch/common/time/JavaDateFormatter.java @@ -105,18 +105,30 @@ class JavaDateFormatter implements DateFormatter { } else { this.parsers = Arrays.asList(parsers); } - //this is when the RoundUp Formatter is created. In further merges (with ||) it will only append this one to a list. List roundUp = createRoundUpParser(format, roundupParserConsumer); this.roundupParser = new RoundUpFormatter(format, roundUp) ; } + /** + * This is when the RoundUp Formatters are created. In further merges (with ||) it will only append them to a list. + * || is not expected to be provided as format when a RoundUp formatter is created. It will be splitted before in + * DateFormatter.forPattern + * JavaDateFormatter created with a custom format like DateFormatter.forPattern("YYYY") will only have one parser + * It is however possible to have a JavaDateFormatter with multiple parsers. For instance see a "date_time" formatter in + * DateFormatters. + * This means that we need to also have multiple RoundUp parsers. + */ private List createRoundUpParser(String format, Consumer roundupParserConsumer) { if (format.contains("||") == false) { - DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); - builder.append(this.parsers.get(0)); - roundupParserConsumer.accept(builder); - return Arrays.asList(builder.toFormatter(locale())); + List roundUpParsers = new ArrayList<>(); + for (DateTimeFormatter parser : this.parsers) { + DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); + builder.append(parser); + roundupParserConsumer.accept(builder); + roundUpParsers.add(builder.toFormatter(locale())); + } + return roundUpParsers; } return null; } 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 b1019a75c09..d74759d5396 100644 --- a/server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java +++ b/server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java @@ -27,10 +27,14 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.ResolverStyle; +import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.LongSupplier; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; public class JavaDateMathParserTests extends ESTestCase { @@ -38,6 +42,47 @@ public class JavaDateMathParserTests extends ESTestCase { private final DateFormatter formatter = DateFormatter.forPattern("date_optional_time||epoch_millis"); private final DateMathParser parser = formatter.toDateMathParser(); + + public void testRoundUpParserBasedOnList() { + DateFormatter formatter = new JavaDateFormatter("test", new DateTimeFormatterBuilder() + .appendPattern("uuuu-MM-dd") + .toFormatter(Locale.ROOT), + new DateTimeFormatterBuilder() + .appendPattern("uuuu-MM-dd'T'HH:mm:ss.S").appendZoneOrOffsetId().toFormatter(Locale.ROOT) + .withResolverStyle(ResolverStyle.STRICT), + new DateTimeFormatterBuilder() + .appendPattern("uuuu-MM-dd'T'HH:mm:ss.S").appendOffset("+HHmm", "Z").toFormatter(Locale.ROOT) + .withResolverStyle(ResolverStyle.STRICT)); + Instant parsed = formatter.toDateMathParser().parse("1970-01-01T00:00:00.0+0000", () -> 0L, true, (ZoneId) null); + assertThat(parsed.toEpochMilli(), equalTo(0L)); + } + + public void testMergingOfMultipleParsers() { + //date_time has 2 parsers, date_time_no_millis has 4. Parsing with rounding should be able to use all of them + DateFormatter formatter = DateFormatter.forPattern("date_time||date_time_no_millis"); + //date_time 2 parsers + Instant parsed = formatter.toDateMathParser().parse("1970-01-01T00:00:00.0+00:00", () -> 0L, true, (ZoneId) null); + assertThat(parsed.toEpochMilli(), equalTo(0L)); + + + parsed = formatter.toDateMathParser().parse("1970-01-01T00:00:00.0+0000", () -> 0L, true, (ZoneId) null); + assertThat(parsed.toEpochMilli(), equalTo(0L)); + + //date_time_no_millis 4 parsers + parsed = formatter.toDateMathParser().parse("1970-01-01T00:00:00+00:00", () -> 0L, true, (ZoneId) null); + assertThat(parsed.toEpochMilli(), equalTo(999L));//defaulting millis + + parsed = formatter.toDateMathParser().parse("1970-01-01T00:00:00+0000", () -> 0L, true, (ZoneId) null); + assertThat(parsed.toEpochMilli(), equalTo(999L));//defaulting millis + + parsed = formatter.toDateMathParser().parse("1970-01-01T00:00:00UTC+00:00", () -> 0L, true, (ZoneId) null); + assertThat(parsed.toEpochMilli(), equalTo(999L));//defaulting millis + + // this one is actually still using parser number 3. I don't see a combination to use parser number 4 + parsed = formatter.toDateMathParser().parse("1970-01-01T00:00:00", () -> 0L, true, (ZoneId) null); + assertThat(parsed.toEpochMilli(), equalTo(999L));//defaulting millis + } + public void testOverridingLocaleOrZoneAndCompositeRoundUpParser() { //the pattern has to be composite and the match should not be on the first one DateFormatter formatter = DateFormatter.forPattern("date||epoch_millis").withLocale(randomLocale(random()));