Date/Time parsing: Use java time API instead of exception handling (#37222)
* Add benchmark * Use java time API instead of exception handling when several formatters are used, the existing way of parsing those is to throw an exception catch it, and try the next one. This is is considerably slower than the approach taken in joda time, so that indexing is reduced when a date format like `x||y` is used and y is the date format being used. This commit now uses the java API to parse the date by appending the date time formatters to each other and does not rely on exception handling. * fix benchmark * fix tests by changing formatter, also expose printer * restore optional printing logic to fix tests * fix tests * incorporate review comments
This commit is contained in:
parent
bbd093059f
commit
9f3da013d8
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.elasticsearch.benchmark.time;
|
||||
|
||||
import org.elasticsearch.common.joda.Joda;
|
||||
import org.elasticsearch.common.time.DateFormatter;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Fork(3)
|
||||
@Warmup(iterations = 10)
|
||||
@Measurement(iterations = 10)
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Benchmark)
|
||||
@SuppressWarnings("unused") //invoked by benchmarking framework
|
||||
public class DateFormatterBenchmark {
|
||||
|
||||
private final DateFormatter javaFormatter = DateFormatter.forPattern("8year_month_day||ordinal_date||epoch_millis");
|
||||
private final DateFormatter jodaFormatter = Joda.forPattern("year_month_day||ordinal_date||epoch_millis");
|
||||
|
||||
@Benchmark
|
||||
public TemporalAccessor parseJavaDate() {
|
||||
return javaFormatter.parse("1234567890");
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public TemporalAccessor parseJodaDate() {
|
||||
return jodaFormatter.parse("1234567890");
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +145,7 @@ public interface DateFormatter {
|
|||
if (formatters.size() == 1) {
|
||||
return formatters.get(0);
|
||||
}
|
||||
return new DateFormatters.MergedDateFormatter(input, formatters);
|
||||
|
||||
return DateFormatters.merge(input, formatters);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
package org.elasticsearch.common.time;
|
||||
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
|
@ -31,7 +30,6 @@ import java.time.ZoneOffset;
|
|||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.format.ResolverStyle;
|
||||
import java.time.format.SignStyle;
|
||||
import java.time.temporal.ChronoField;
|
||||
|
@ -40,10 +38,9 @@ import java.time.temporal.TemporalAccessor;
|
|||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.time.temporal.TemporalQueries;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.time.temporal.ChronoField.DAY_OF_MONTH;
|
||||
import static java.time.temporal.ChronoField.DAY_OF_WEEK;
|
||||
|
@ -77,58 +74,6 @@ public class DateFormatters {
|
|||
.appendValue(SECOND_OF_MINUTE, 2, 2, SignStyle.NOT_NEGATIVE)
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
private static final DateTimeFormatter STRICT_DATE_OPTIONAL_TIME_FORMATTER_1 = new DateTimeFormatterBuilder()
|
||||
.append(STRICT_YEAR_MONTH_DAY_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendLiteral('T')
|
||||
.append(STRICT_HOUR_MINUTE_SECOND_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendFraction(MILLI_OF_SECOND, 3, 3, true)
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.appendZoneOrOffsetId()
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
private static final DateTimeFormatter STRICT_DATE_OPTIONAL_TIME_FORMATTER_2 = new DateTimeFormatterBuilder()
|
||||
.append(STRICT_YEAR_MONTH_DAY_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendLiteral('T')
|
||||
.append(STRICT_HOUR_MINUTE_SECOND_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendFraction(MILLI_OF_SECOND, 3, 3, true)
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.appendOffset("+HHmm", "Z")
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
/**
|
||||
* Returns a generic ISO datetime parser where the date is mandatory and the time is optional.
|
||||
*/
|
||||
private static final DateFormatter STRICT_DATE_OPTIONAL_TIME =
|
||||
new JavaDateFormatter("strict_date_optional_time", STRICT_DATE_OPTIONAL_TIME_FORMATTER_1,
|
||||
STRICT_DATE_OPTIONAL_TIME_FORMATTER_1, STRICT_DATE_OPTIONAL_TIME_FORMATTER_2);
|
||||
|
||||
private static final DateTimeFormatter STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_1 = new DateTimeFormatterBuilder()
|
||||
.append(STRICT_YEAR_MONTH_DAY_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendLiteral('T')
|
||||
.append(STRICT_HOUR_MINUTE_SECOND_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendFraction(NANO_OF_SECOND, 3, 9, true)
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.appendZoneOrOffsetId()
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.appendOffset("+HHmm", "Z")
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
private static final DateTimeFormatter STRICT_DATE_OPTIONAL_TIME_PRINTER = new DateTimeFormatterBuilder()
|
||||
.append(STRICT_YEAR_MONTH_DAY_FORMATTER)
|
||||
.appendLiteral('T')
|
||||
|
@ -139,11 +84,52 @@ public class DateFormatters {
|
|||
.optionalEnd()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
private static final DateTimeFormatter STRICT_DATE_OPTIONAL_TIME_FORMATTER = new DateTimeFormatterBuilder()
|
||||
.append(STRICT_YEAR_MONTH_DAY_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendLiteral('T')
|
||||
.append(STRICT_HOUR_MINUTE_SECOND_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendFraction(MILLI_OF_SECOND, 3, 3, true)
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.appendZoneOrOffsetId()
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.append(TIME_ZONE_FORMATTER_NO_COLON)
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
/**
|
||||
* Returns a generic ISO datetime parser where the date is mandatory and the time is optional.
|
||||
*/
|
||||
private static final DateFormatter STRICT_DATE_OPTIONAL_TIME =
|
||||
new JavaDateFormatter("strict_date_optional_time", STRICT_DATE_OPTIONAL_TIME_PRINTER, STRICT_DATE_OPTIONAL_TIME_FORMATTER);
|
||||
|
||||
private static final DateTimeFormatter STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS = new DateTimeFormatterBuilder()
|
||||
.append(STRICT_YEAR_MONTH_DAY_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendLiteral('T')
|
||||
.append(STRICT_HOUR_MINUTE_SECOND_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendFraction(NANO_OF_SECOND, 3, 9, true)
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.appendZoneOrOffsetId()
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.append(TIME_ZONE_FORMATTER_NO_COLON)
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
/**
|
||||
* Returns a generic ISO datetime parser where the date is mandatory and the time is optional with nanosecond resolution.
|
||||
*/
|
||||
private static final DateFormatter STRICT_DATE_OPTIONAL_TIME_NANOS = new JavaDateFormatter("strict_date_optional_time_nanos",
|
||||
STRICT_DATE_OPTIONAL_TIME_PRINTER, STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_1);
|
||||
STRICT_DATE_OPTIONAL_TIME_PRINTER, STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS);
|
||||
|
||||
/////////////////////////////////////////
|
||||
//
|
||||
// BEGIN basic time formatters
|
||||
|
@ -818,7 +804,7 @@ public class DateFormatters {
|
|||
* yyyy-MM-dd'T'HH:mm:ss.SSSZ
|
||||
*/
|
||||
private static final DateFormatter DATE_OPTIONAL_TIME = new JavaDateFormatter("date_optional_time",
|
||||
STRICT_DATE_OPTIONAL_TIME_FORMATTER_1,
|
||||
STRICT_DATE_OPTIONAL_TIME_PRINTER,
|
||||
new DateTimeFormatterBuilder()
|
||||
.append(DATE_FORMATTER)
|
||||
.optionalStart()
|
||||
|
@ -836,26 +822,6 @@ public class DateFormatters {
|
|||
.appendFraction(MILLI_OF_SECOND, 1, 3, true)
|
||||
.optionalEnd()
|
||||
.optionalStart().appendZoneOrOffsetId().optionalEnd()
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
.toFormatter(Locale.ROOT),
|
||||
new DateTimeFormatterBuilder()
|
||||
.append(DATE_FORMATTER)
|
||||
.optionalStart()
|
||||
.appendLiteral('T')
|
||||
.optionalStart()
|
||||
.appendValue(HOUR_OF_DAY, 1, 2, SignStyle.NOT_NEGATIVE)
|
||||
.optionalStart()
|
||||
.appendLiteral(':')
|
||||
.appendValue(MINUTE_OF_HOUR, 1, 2, SignStyle.NOT_NEGATIVE)
|
||||
.optionalStart()
|
||||
.appendLiteral(':')
|
||||
.appendValue(SECOND_OF_MINUTE, 1, 2, SignStyle.NOT_NEGATIVE)
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.appendFraction(MILLI_OF_SECOND, 1, 3, true)
|
||||
.optionalEnd()
|
||||
.optionalStart().appendOffset("+HHmm", "Z").optionalEnd()
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
|
@ -1006,7 +972,7 @@ public class DateFormatters {
|
|||
* (yyyy-MM-dd'T'HH:mm:ss.SSSZZ).
|
||||
*/
|
||||
private static final DateFormatter DATE_TIME = new JavaDateFormatter("date_time",
|
||||
STRICT_DATE_OPTIONAL_TIME_FORMATTER_1,
|
||||
STRICT_DATE_OPTIONAL_TIME_PRINTER,
|
||||
new DateTimeFormatterBuilder().append(DATE_TIME_FORMATTER).appendZoneOrOffsetId().toFormatter(Locale.ROOT),
|
||||
new DateTimeFormatterBuilder().append(DATE_TIME_FORMATTER).append(TIME_ZONE_FORMATTER_NO_COLON).toFormatter(Locale.ROOT)
|
||||
);
|
||||
|
@ -1483,90 +1449,22 @@ public class DateFormatters {
|
|||
}
|
||||
}
|
||||
|
||||
static class MergedDateFormatter implements DateFormatter {
|
||||
static JavaDateFormatter merge(String pattern, List<DateFormatter> formatters) {
|
||||
assert formatters.size() > 0;
|
||||
|
||||
private final String pattern;
|
||||
// package private for tests
|
||||
final List<DateFormatter> formatters;
|
||||
private final List<DateMathParser> dateMathParsers;
|
||||
|
||||
MergedDateFormatter(String pattern, List<DateFormatter> formatters) {
|
||||
assert formatters.size() > 0;
|
||||
this.pattern = pattern;
|
||||
this.formatters = Collections.unmodifiableList(formatters);
|
||||
this.dateMathParsers = formatters.stream().map(DateFormatter::toDateMathParser).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemporalAccessor parse(String input) {
|
||||
IllegalArgumentException failure = null;
|
||||
for (DateFormatter formatter : formatters) {
|
||||
try {
|
||||
return formatter.parse(input);
|
||||
// TODO: remove DateTimeParseException when JavaDateFormatter throws IAE
|
||||
} catch (IllegalArgumentException | DateTimeParseException e) {
|
||||
if (failure == null) {
|
||||
// wrap so the entire multi format is in the message
|
||||
failure = new IllegalArgumentException("failed to parse date field [" + input + "] with format [" + pattern + "]",
|
||||
e);
|
||||
} else {
|
||||
failure.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
List<DateTimeFormatter> dateTimeFormatters = new ArrayList<>(formatters.size());
|
||||
DateTimeFormatter printer = null;
|
||||
for (DateFormatter formatter : formatters) {
|
||||
assert formatter instanceof JavaDateFormatter;
|
||||
JavaDateFormatter javaDateFormatter = (JavaDateFormatter) formatter;
|
||||
DateTimeFormatter dateTimeFormatter = javaDateFormatter.getParser();
|
||||
if (printer == null) {
|
||||
printer = javaDateFormatter.getPrinter();
|
||||
}
|
||||
throw failure;
|
||||
dateTimeFormatters.add(dateTimeFormatter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateFormatter withZone(ZoneId zoneId) {
|
||||
return new MergedDateFormatter(pattern, formatters.stream().map(f -> f.withZone(zoneId)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateFormatter withLocale(Locale locale) {
|
||||
return new MergedDateFormatter(pattern, formatters.stream().map(f -> f.withLocale(locale)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(TemporalAccessor accessor) {
|
||||
return formatters.get(0).format(accessor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale locale() {
|
||||
return formatters.get(0).locale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZoneId zone() {
|
||||
return formatters.get(0).zone();
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
// wrap so the entire multi format is in the message
|
||||
failure = new ElasticsearchParseException("failed to parse date field [" + text + "] with format ["
|
||||
+ pattern + "]", e);
|
||||
} else {
|
||||
failure.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw failure;
|
||||
};
|
||||
}
|
||||
return new JavaDateFormatter(pattern, printer, dateTimeFormatters.toArray(new DateTimeFormatter[0]));
|
||||
}
|
||||
|
||||
private static final ZonedDateTime EPOCH_ZONED_DATE_TIME = Instant.EPOCH.atZone(ZoneOffset.UTC);
|
||||
|
|
|
@ -19,10 +19,11 @@
|
|||
|
||||
package org.elasticsearch.common.time;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
|
||||
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;
|
||||
|
@ -47,7 +48,7 @@ class JavaDateFormatter implements DateFormatter {
|
|||
|
||||
private final String format;
|
||||
private final DateTimeFormatter printer;
|
||||
private final DateTimeFormatter[] parsers;
|
||||
private final DateTimeFormatter parser;
|
||||
|
||||
JavaDateFormatter(String format, DateTimeFormatter printer, DateTimeFormatter... parsers) {
|
||||
if (printer == null) {
|
||||
|
@ -62,61 +63,54 @@ class JavaDateFormatter implements DateFormatter {
|
|||
throw new IllegalArgumentException("formatters must have the same locale");
|
||||
}
|
||||
if (parsers.length == 0) {
|
||||
this.parsers = new DateTimeFormatter[]{printer};
|
||||
this.parser = printer;
|
||||
} else if (parsers.length == 1) {
|
||||
this.parser = parsers[0];
|
||||
} else {
|
||||
this.parsers = parsers;
|
||||
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
|
||||
for (DateTimeFormatter parser : parsers) {
|
||||
builder.appendOptional(parser);
|
||||
}
|
||||
this.parser = builder.toFormatter(Locale.ROOT);
|
||||
}
|
||||
this.format = format;
|
||||
this.printer = printer;
|
||||
}
|
||||
|
||||
DateTimeFormatter getParser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
DateTimeFormatter getPrinter() {
|
||||
return printer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemporalAccessor parse(String input) {
|
||||
DateTimeParseException failure = null;
|
||||
for (int i = 0; i < parsers.length; i++) {
|
||||
try {
|
||||
return parsers[i].parse(input);
|
||||
} catch (DateTimeParseException e) {
|
||||
if (failure == null) {
|
||||
failure = e;
|
||||
} else {
|
||||
failure.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
if (Strings.isNullOrEmpty(input)) {
|
||||
throw new IllegalArgumentException("cannot parse empty date");
|
||||
}
|
||||
|
||||
// ensure that all parsers exceptions are returned instead of only the last one
|
||||
throw failure;
|
||||
return parser.parse(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateFormatter withZone(ZoneId zoneId) {
|
||||
// shortcurt to not create new objects unnecessarily
|
||||
if (zoneId.equals(parsers[0].getZone())) {
|
||||
if (zoneId.equals(parser.getZone())) {
|
||||
return this;
|
||||
}
|
||||
|
||||
final DateTimeFormatter[] parsersWithZone = new DateTimeFormatter[parsers.length];
|
||||
for (int i = 0; i < parsers.length; i++) {
|
||||
parsersWithZone[i] = parsers[i].withZone(zoneId);
|
||||
}
|
||||
|
||||
return new JavaDateFormatter(format, printer.withZone(zoneId), parsersWithZone);
|
||||
return new JavaDateFormatter(format, printer.withZone(zoneId), parser.withZone(zoneId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateFormatter withLocale(Locale locale) {
|
||||
// shortcurt to not create new objects unnecessarily
|
||||
if (locale.equals(parsers[0].getLocale())) {
|
||||
if (locale.equals(parser.getLocale())) {
|
||||
return this;
|
||||
}
|
||||
|
||||
final DateTimeFormatter[] parsersWithZone = new DateTimeFormatter[parsers.length];
|
||||
for (int i = 0; i < parsers.length; i++) {
|
||||
parsersWithZone[i] = parsers[i].withLocale(locale);
|
||||
}
|
||||
|
||||
return new JavaDateFormatter(format, printer.withLocale(locale), parsersWithZone);
|
||||
return new JavaDateFormatter(format, printer.withLocale(locale), parser.withLocale(locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -132,17 +126,7 @@ class JavaDateFormatter implements DateFormatter {
|
|||
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)) {
|
||||
return new JavaDateFormatter(format, parseDefaultingBuilder.toFormatter(Locale.ROOT));
|
||||
} else {
|
||||
final DateTimeFormatter[] parsersWithDefaulting = new DateTimeFormatter[parsers.length];
|
||||
for (int i = 0; i < parsers.length; i++) {
|
||||
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder().append(parsers[i]);
|
||||
fields.forEach(builder::parseDefaulting);
|
||||
parsersWithDefaulting[i] = builder.toFormatter(Locale.ROOT);
|
||||
}
|
||||
return new JavaDateFormatter(format, parseDefaultingBuilder.toFormatter(Locale.ROOT), parsersWithDefaulting);
|
||||
}
|
||||
return new JavaDateFormatter(format, parseDefaultingBuilder.toFormatter(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -72,8 +72,6 @@ public class JavaJodaTimeDuellingTests extends ESTestCase {
|
|||
|
||||
public void testDuellingFormatsValidParsing() {
|
||||
assertSameDate("1522332219", "epoch_second");
|
||||
assertSameDate("1522332219.", "epoch_second");
|
||||
assertSameDate("1522332219.0", "epoch_second");
|
||||
assertSameDate("0", "epoch_second");
|
||||
assertSameDate("1", "epoch_second");
|
||||
assertSameDate("1522332219321", "epoch_millis");
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.elasticsearch.test.ESTestCase;
|
|||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Locale;
|
||||
|
@ -42,21 +43,11 @@ public class DateFormattersTests extends ESTestCase {
|
|||
// as this feature is supported it also makes sense to make it exact
|
||||
public void testEpochMillisParser() {
|
||||
DateFormatter formatter = DateFormatters.forPattern("epoch_millis");
|
||||
{
|
||||
Instant instant = Instant.from(formatter.parse("12345.6789"));
|
||||
assertThat(instant.getEpochSecond(), is(12L));
|
||||
assertThat(instant.getNano(), is(345_678_900));
|
||||
}
|
||||
{
|
||||
Instant instant = Instant.from(formatter.parse("12345"));
|
||||
assertThat(instant.getEpochSecond(), is(12L));
|
||||
assertThat(instant.getNano(), is(345_000_000));
|
||||
}
|
||||
{
|
||||
Instant instant = Instant.from(formatter.parse("12345."));
|
||||
assertThat(instant.getEpochSecond(), is(12L));
|
||||
assertThat(instant.getNano(), is(345_000_000));
|
||||
}
|
||||
{
|
||||
Instant instant = Instant.from(formatter.parse("0"));
|
||||
assertThat(instant.getEpochSecond(), is(0L));
|
||||
|
@ -79,25 +70,12 @@ public class DateFormattersTests extends ESTestCase {
|
|||
public void testEpochSecondParser() {
|
||||
DateFormatter formatter = DateFormatters.forPattern("epoch_second");
|
||||
|
||||
assertThat(Instant.from(formatter.parse("1234.567")).toEpochMilli(), is(1234567L));
|
||||
assertThat(Instant.from(formatter.parse("1234.")).getNano(), is(0));
|
||||
assertThat(Instant.from(formatter.parse("1234.")).getEpochSecond(), is(1234L));
|
||||
assertThat(Instant.from(formatter.parse("1234.1")).getNano(), is(100_000_000));
|
||||
assertThat(Instant.from(formatter.parse("1234.12")).getNano(), is(120_000_000));
|
||||
assertThat(Instant.from(formatter.parse("1234.123")).getNano(), is(123_000_000));
|
||||
assertThat(Instant.from(formatter.parse("1234.1234")).getNano(), is(123_400_000));
|
||||
assertThat(Instant.from(formatter.parse("1234.12345")).getNano(), is(123_450_000));
|
||||
assertThat(Instant.from(formatter.parse("1234.123456")).getNano(), is(123_456_000));
|
||||
assertThat(Instant.from(formatter.parse("1234.1234567")).getNano(), is(123_456_700));
|
||||
assertThat(Instant.from(formatter.parse("1234.12345678")).getNano(), is(123_456_780));
|
||||
assertThat(Instant.from(formatter.parse("1234.123456789")).getNano(), is(123_456_789));
|
||||
|
||||
DateTimeParseException e = expectThrows(DateTimeParseException.class, () -> formatter.parse("1234.1234567890"));
|
||||
assertThat(e.getMessage(), is("Text '1234.1234567890' could not be parsed, unparsed text found at index 4"));
|
||||
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("1234.123456789013221"));
|
||||
assertThat(e.getMessage(), is("Text '1234.123456789013221' could not be parsed, unparsed text found at index 4"));
|
||||
DateTimeParseException e = expectThrows(DateTimeParseException.class, () -> formatter.parse("1234.1"));
|
||||
assertThat(e.getMessage(), is("Text '1234.1' could not be parsed, unparsed text found at index 4"));
|
||||
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("1234."));
|
||||
assertThat(e.getMessage(), is("Text '1234.' could not be parsed, unparsed text found at index 4"));
|
||||
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("abc"));
|
||||
assertThat(e.getMessage(), is("Text 'abc' could not be parsed at index 0"));
|
||||
assertThat(e.getMessage(), is("Text 'abc' could not be parsed, unparsed text found at index 0"));
|
||||
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("1234.abc"));
|
||||
assertThat(e.getMessage(), is("Text '1234.abc' could not be parsed, unparsed text found at index 4"));
|
||||
}
|
||||
|
@ -109,6 +87,14 @@ public class DateFormattersTests extends ESTestCase {
|
|||
assertThat(formatter.pattern(), is("strict_date_optional_time||epoch_millis"));
|
||||
}
|
||||
|
||||
public void testParsersWithMultipleInternalFormats() throws Exception {
|
||||
ZonedDateTime first = DateFormatters.toZonedDateTime(
|
||||
DateFormatters.forPattern("strict_date_optional_time_nanos").parse("2018-05-15T17:14:56+0100"));
|
||||
ZonedDateTime second = DateFormatters.toZonedDateTime(
|
||||
DateFormatters.forPattern("strict_date_optional_time_nanos").parse("2018-05-15T17:14:56+01:00"));
|
||||
assertThat(first, is(second));
|
||||
}
|
||||
|
||||
public void testLocales() {
|
||||
assertThat(DateFormatters.forPattern("strict_date_optional_time").locale(), is(Locale.ROOT));
|
||||
Locale locale = randomLocale(random());
|
||||
|
@ -157,10 +143,7 @@ public class DateFormattersTests extends ESTestCase {
|
|||
assertThat(DateFormatter.forPattern("8date_optional_time"), instanceOf(JavaDateFormatter.class));
|
||||
// named formats too
|
||||
DateFormatter formatter = DateFormatter.forPattern("8date_optional_time||ww-MM-dd");
|
||||
assertThat(formatter, instanceOf(DateFormatters.MergedDateFormatter.class));
|
||||
DateFormatters.MergedDateFormatter mergedFormatter = (DateFormatters.MergedDateFormatter) formatter;
|
||||
assertThat(mergedFormatter.formatters.get(0), instanceOf(JavaDateFormatter.class));
|
||||
assertThat(mergedFormatter.formatters.get(1), instanceOf(JavaDateFormatter.class));
|
||||
assertThat(formatter, instanceOf(JavaDateFormatter.class));
|
||||
}
|
||||
|
||||
public void testParsingStrictNanoDates() {
|
||||
|
|
Loading…
Reference in New Issue