Core: Rework epoch time parsing for java time (#36914)
This commit converts the epoch time parsing implementation which uses the java time api to create DateTimeFormatters instead of DateFormatter implementations. This will allow multi formats for java time to be implemented in a single DateTimeFormatter in a future change.
This commit is contained in:
parent
56e472bfbc
commit
55d3ca3aa8
|
@ -1366,9 +1366,9 @@ public class DateFormatters {
|
||||||
} else if ("yearMonthDay".equals(input) || "year_month_day".equals(input)) {
|
} else if ("yearMonthDay".equals(input) || "year_month_day".equals(input)) {
|
||||||
return YEAR_MONTH_DAY;
|
return YEAR_MONTH_DAY;
|
||||||
} else if ("epoch_second".equals(input)) {
|
} else if ("epoch_second".equals(input)) {
|
||||||
return EpochSecondsDateFormatter.INSTANCE;
|
return EpochTime.SECONDS_FORMATTER;
|
||||||
} else if ("epoch_millis".equals(input)) {
|
} else if ("epoch_millis".equals(input)) {
|
||||||
return EpochMillisDateFormatter.INSTANCE;
|
return EpochTime.MILLIS_FORMATTER;
|
||||||
// strict date formats here, must be at least 4 digits for year and two for months and two for day
|
// strict date formats here, must be at least 4 digits for year and two for months and two for day
|
||||||
} else if ("strictBasicWeekDate".equals(input) || "strict_basic_week_date".equals(input)) {
|
} else if ("strictBasicWeekDate".equals(input) || "strict_basic_week_date".equals(input)) {
|
||||||
return STRICT_BASIC_WEEK_DATE;
|
return STRICT_BASIC_WEEK_DATE;
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.common.time;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeParseException;
|
|
||||||
import java.time.temporal.TemporalAccessor;
|
|
||||||
import java.util.Locale;
|
|
||||||
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() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TemporalAccessor parse(String input) {
|
|
||||||
try {
|
|
||||||
if (input.contains(".")) {
|
|
||||||
String[] inputs = SPLIT_BY_DOT_PATTERN.split(input, 2);
|
|
||||||
Long seconds = Long.valueOf(inputs[0]);
|
|
||||||
if (inputs[1].length() == 0) {
|
|
||||||
// this is BWC compatible to joda time, nothing after the dot is allowed
|
|
||||||
return Instant.ofEpochSecond(seconds, 0).atZone(ZoneOffset.UTC);
|
|
||||||
}
|
|
||||||
// scientific notation it is!
|
|
||||||
if (inputs[1].contains("e")) {
|
|
||||||
return Instant.ofEpochSecond(Double.valueOf(input).longValue()).atZone(ZoneOffset.UTC);
|
|
||||||
}
|
|
||||||
if (inputs[1].length() > 9) {
|
|
||||||
throw new DateTimeParseException("too much granularity after dot [" + input + "]", input, 0);
|
|
||||||
}
|
|
||||||
Long nanos = new BigDecimal(inputs[1]).movePointRight(9 - inputs[1].length()).longValueExact();
|
|
||||||
if (seconds < 0) {
|
|
||||||
nanos = nanos * -1;
|
|
||||||
}
|
|
||||||
return Instant.ofEpochSecond(seconds, nanos).atZone(ZoneOffset.UTC);
|
|
||||||
} else {
|
|
||||||
return Instant.ofEpochSecond(Long.valueOf(input)).atZone(ZoneOffset.UTC);
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new DateTimeParseException("invalid number [" + input + "]", input, 0, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String format(TemporalAccessor accessor) {
|
|
||||||
Instant instant = Instant.from(accessor);
|
|
||||||
if (instant.getNano() != 0) {
|
|
||||||
return String.valueOf(instant.getEpochSecond()) + "." + String.valueOf(instant.getNano()).replaceAll("0*$", "");
|
|
||||||
}
|
|
||||||
return String.valueOf(instant.getEpochSecond());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String pattern() {
|
|
||||||
return "epoch_second";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Locale locale() {
|
|
||||||
return Locale.ROOT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ZoneId zone() {
|
|
||||||
return ZoneOffset.UTC;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DateMathParser toDateMathParser() {
|
|
||||||
return DATE_MATH_INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DateFormatter withZone(ZoneId zoneId) {
|
|
||||||
if (zoneId.equals(ZoneOffset.UTC) == false) {
|
|
||||||
throw new IllegalArgumentException(pattern() + " date formatter can only be in zone offset UTC");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DateFormatter withLocale(Locale locale) {
|
|
||||||
if (Locale.ROOT.equals(locale) == false) {
|
|
||||||
throw new IllegalArgumentException(pattern() + " date formatter can only be in locale ROOT");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
/*
|
||||||
|
* 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.common.time;
|
||||||
|
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
|
import java.time.format.ResolverStyle;
|
||||||
|
import java.time.format.SignStyle;
|
||||||
|
import java.time.temporal.ChronoField;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.time.temporal.Temporal;
|
||||||
|
import java.time.temporal.TemporalAccessor;
|
||||||
|
import java.time.temporal.TemporalField;
|
||||||
|
import java.time.temporal.TemporalUnit;
|
||||||
|
import java.time.temporal.ValueRange;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides {@link DateTimeFormatter}s capable of parsing epoch seconds and milliseconds.
|
||||||
|
* <p>
|
||||||
|
* The seconds formatter is provided by {@link #SECONDS_FORMATTER}.
|
||||||
|
* The milliseconds formatter is provided by {@link #MILLIS_FORMATTER}.
|
||||||
|
* <p>
|
||||||
|
* Both formatters support fractional time, up to nanosecond precision. Values must be positive numbers.
|
||||||
|
*/
|
||||||
|
class EpochTime {
|
||||||
|
|
||||||
|
private static final ValueRange LONG_POSITIVE_RANGE = ValueRange.of(0, Long.MAX_VALUE);
|
||||||
|
|
||||||
|
private static final EpochField SECONDS = new EpochField(ChronoUnit.SECONDS, ChronoUnit.FOREVER, LONG_POSITIVE_RANGE) {
|
||||||
|
@Override
|
||||||
|
public boolean isSupportedBy(TemporalAccessor temporal) {
|
||||||
|
return temporal.isSupported(ChronoField.INSTANT_SECONDS);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public long getFrom(TemporalAccessor temporal) {
|
||||||
|
return temporal.getLong(ChronoField.INSTANT_SECONDS);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public TemporalAccessor resolve(Map<TemporalField,Long> fieldValues,
|
||||||
|
TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
|
||||||
|
long seconds = fieldValues.remove(this);
|
||||||
|
fieldValues.put(ChronoField.INSTANT_SECONDS, seconds);
|
||||||
|
Long nanos = fieldValues.remove(NANOS_OF_SECOND);
|
||||||
|
if (nanos != null) {
|
||||||
|
fieldValues.put(ChronoField.NANO_OF_SECOND, nanos);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final EpochField NANOS_OF_SECOND = new EpochField(ChronoUnit.NANOS, ChronoUnit.SECONDS, ValueRange.of(0, 999_999_999)) {
|
||||||
|
@Override
|
||||||
|
public boolean isSupportedBy(TemporalAccessor temporal) {
|
||||||
|
return temporal.isSupported(ChronoField.NANO_OF_SECOND) && temporal.getLong(ChronoField.NANO_OF_SECOND) != 0;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public long getFrom(TemporalAccessor temporal) {
|
||||||
|
return temporal.getLong(ChronoField.NANO_OF_SECOND);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final EpochField MILLIS = new EpochField(ChronoUnit.MILLIS, ChronoUnit.FOREVER, LONG_POSITIVE_RANGE) {
|
||||||
|
@Override
|
||||||
|
public boolean isSupportedBy(TemporalAccessor temporal) {
|
||||||
|
return temporal.isSupported(ChronoField.INSTANT_SECONDS) && temporal.isSupported(ChronoField.MILLI_OF_SECOND);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public long getFrom(TemporalAccessor temporal) {
|
||||||
|
return temporal.getLong(ChronoField.INSTANT_SECONDS) * 1_000 + temporal.getLong(ChronoField.MILLI_OF_SECOND);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public TemporalAccessor resolve(Map<TemporalField,Long> fieldValues,
|
||||||
|
TemporalAccessor partialTemporal, ResolverStyle resolverStyle) {
|
||||||
|
long secondsAndMillis = fieldValues.remove(this);
|
||||||
|
long seconds = secondsAndMillis / 1_000;
|
||||||
|
long nanos = secondsAndMillis % 1000 * 1_000_000;
|
||||||
|
Long nanosOfMilli = fieldValues.remove(NANOS_OF_MILLI);
|
||||||
|
if (nanosOfMilli != null) {
|
||||||
|
nanos += nanosOfMilli;
|
||||||
|
}
|
||||||
|
fieldValues.put(ChronoField.INSTANT_SECONDS, seconds);
|
||||||
|
fieldValues.put(ChronoField.NANO_OF_SECOND, nanos);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final EpochField NANOS_OF_MILLI = new EpochField(ChronoUnit.NANOS, ChronoUnit.MILLIS, ValueRange.of(0, 999_999)) {
|
||||||
|
@Override
|
||||||
|
public boolean isSupportedBy(TemporalAccessor temporal) {
|
||||||
|
return temporal.isSupported(ChronoField.NANO_OF_SECOND) && temporal.getLong(ChronoField.NANO_OF_SECOND) % 1_000_000 != 0;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public long getFrom(TemporalAccessor temporal) {
|
||||||
|
return temporal.getLong(ChronoField.NANO_OF_SECOND);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// this supports seconds without any fraction
|
||||||
|
private static final DateTimeFormatter SECONDS_FORMATTER1 = new DateTimeFormatterBuilder()
|
||||||
|
.appendValue(SECONDS, 1, 19, SignStyle.NORMAL)
|
||||||
|
.toFormatter(Locale.ROOT);
|
||||||
|
|
||||||
|
// this supports seconds ending in dot
|
||||||
|
private static final DateTimeFormatter SECONDS_FORMATTER2 = new DateTimeFormatterBuilder()
|
||||||
|
.append(SECONDS_FORMATTER1)
|
||||||
|
.appendLiteral('.')
|
||||||
|
.toFormatter(Locale.ROOT);
|
||||||
|
|
||||||
|
// this supports seconds with a fraction and is also used for printing
|
||||||
|
private static final DateTimeFormatter SECONDS_FORMATTER3 = new DateTimeFormatterBuilder()
|
||||||
|
.append(SECONDS_FORMATTER1)
|
||||||
|
.optionalStart() // optional is used so isSupported will be called when printing
|
||||||
|
.appendFraction(NANOS_OF_SECOND, 1, 9, true)
|
||||||
|
.optionalEnd()
|
||||||
|
.toFormatter(Locale.ROOT);
|
||||||
|
|
||||||
|
// this supports milliseconds without any fraction
|
||||||
|
private static final DateTimeFormatter MILLISECONDS_FORMATTER1 = new DateTimeFormatterBuilder()
|
||||||
|
.appendValue(MILLIS, 1, 19, SignStyle.NORMAL)
|
||||||
|
.toFormatter(Locale.ROOT);
|
||||||
|
|
||||||
|
// this supports milliseconds ending in dot
|
||||||
|
private static final DateTimeFormatter MILLISECONDS_FORMATTER2 = new DateTimeFormatterBuilder()
|
||||||
|
.append(MILLISECONDS_FORMATTER1)
|
||||||
|
.appendLiteral('.')
|
||||||
|
.toFormatter(Locale.ROOT);
|
||||||
|
|
||||||
|
// this supports milliseconds with a fraction and is also used for printing
|
||||||
|
private static final DateTimeFormatter MILLISECONDS_FORMATTER3 = new DateTimeFormatterBuilder()
|
||||||
|
.append(MILLISECONDS_FORMATTER1)
|
||||||
|
.optionalStart() // optional is used so isSupported will be called when printing
|
||||||
|
.appendFraction(NANOS_OF_MILLI, 1, 6, true)
|
||||||
|
.optionalEnd()
|
||||||
|
.toFormatter(Locale.ROOT);
|
||||||
|
|
||||||
|
static final DateFormatter SECONDS_FORMATTER = new JavaDateFormatter("epoch_second", SECONDS_FORMATTER3,
|
||||||
|
SECONDS_FORMATTER1, SECONDS_FORMATTER2, SECONDS_FORMATTER3);
|
||||||
|
|
||||||
|
static final DateFormatter MILLIS_FORMATTER = new JavaDateFormatter("epoch_millis", MILLISECONDS_FORMATTER3,
|
||||||
|
MILLISECONDS_FORMATTER1, MILLISECONDS_FORMATTER2, MILLISECONDS_FORMATTER3);
|
||||||
|
|
||||||
|
private abstract static class EpochField implements TemporalField {
|
||||||
|
|
||||||
|
private final TemporalUnit baseUnit;
|
||||||
|
private final TemporalUnit rangeUnit;
|
||||||
|
private final ValueRange range;
|
||||||
|
|
||||||
|
private EpochField(TemporalUnit baseUnit, TemporalUnit rangeUnit, ValueRange range) {
|
||||||
|
this.baseUnit = baseUnit;
|
||||||
|
this.rangeUnit = rangeUnit;
|
||||||
|
this.range = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName(Locale locale) {
|
||||||
|
return toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Epoch" + baseUnit.toString() + (rangeUnit != ChronoUnit.FOREVER ? "Of" + rangeUnit.toString() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TemporalUnit getBaseUnit() {
|
||||||
|
return baseUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TemporalUnit getRangeUnit() {
|
||||||
|
return rangeUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueRange range() {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDateBased() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTimeBased() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
|
||||||
|
return range();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <R extends Temporal> R adjustInto(R temporal, long newValue) {
|
||||||
|
return (R) temporal.with(this, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.common.joda;
|
package org.elasticsearch.common.joda;
|
||||||
|
|
||||||
|
import org.elasticsearch.bootstrap.JavaVersion;
|
||||||
import org.elasticsearch.common.time.DateFormatter;
|
import org.elasticsearch.common.time.DateFormatter;
|
||||||
import org.elasticsearch.common.time.DateFormatters;
|
import org.elasticsearch.common.time.DateFormatters;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
@ -384,6 +385,7 @@ public class JavaJodaTimeDuellingTests extends ESTestCase {
|
||||||
|
|
||||||
ZonedDateTime javaDate = ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneOffset.UTC);
|
ZonedDateTime javaDate = ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneOffset.UTC);
|
||||||
DateTime jodaDate = new DateTime(year, month, day, hour, minute, second, DateTimeZone.UTC);
|
DateTime jodaDate = new DateTime(year, month, day, hour, minute, second, DateTimeZone.UTC);
|
||||||
|
assertSamePrinterOutput("epoch_second", javaDate, jodaDate);
|
||||||
|
|
||||||
assertSamePrinterOutput("basicDate", javaDate, jodaDate);
|
assertSamePrinterOutput("basicDate", javaDate, jodaDate);
|
||||||
assertSamePrinterOutput("basicDateTime", javaDate, jodaDate);
|
assertSamePrinterOutput("basicDateTime", javaDate, jodaDate);
|
||||||
|
@ -428,7 +430,7 @@ public class JavaJodaTimeDuellingTests extends ESTestCase {
|
||||||
assertSamePrinterOutput("year", javaDate, jodaDate);
|
assertSamePrinterOutput("year", javaDate, jodaDate);
|
||||||
assertSamePrinterOutput("yearMonth", javaDate, jodaDate);
|
assertSamePrinterOutput("yearMonth", javaDate, jodaDate);
|
||||||
assertSamePrinterOutput("yearMonthDay", javaDate, jodaDate);
|
assertSamePrinterOutput("yearMonthDay", javaDate, jodaDate);
|
||||||
assertSamePrinterOutput("epoch_second", javaDate, jodaDate);
|
|
||||||
assertSamePrinterOutput("epoch_millis", javaDate, jodaDate);
|
assertSamePrinterOutput("epoch_millis", javaDate, jodaDate);
|
||||||
assertSamePrinterOutput("strictBasicWeekDate", javaDate, jodaDate);
|
assertSamePrinterOutput("strictBasicWeekDate", javaDate, jodaDate);
|
||||||
assertSamePrinterOutput("strictBasicWeekDateTime", javaDate, jodaDate);
|
assertSamePrinterOutput("strictBasicWeekDateTime", javaDate, jodaDate);
|
||||||
|
@ -476,6 +478,12 @@ public class JavaJodaTimeDuellingTests extends ESTestCase {
|
||||||
assertThat(jodaDate.getMillis(), is(javaDate.toInstant().toEpochMilli()));
|
assertThat(jodaDate.getMillis(), is(javaDate.toInstant().toEpochMilli()));
|
||||||
String javaTimeOut = DateFormatters.forPattern(format).format(javaDate);
|
String javaTimeOut = DateFormatters.forPattern(format).format(javaDate);
|
||||||
String jodaTimeOut = DateFormatter.forPattern(format).formatJoda(jodaDate);
|
String jodaTimeOut = DateFormatter.forPattern(format).formatJoda(jodaDate);
|
||||||
|
if (JavaVersion.current().getVersion().get(0) == 8 && javaTimeOut.endsWith(".0")
|
||||||
|
&& (format.equals("epoch_second") || format.equals("epoch_millis"))) {
|
||||||
|
// java 8 has a bug in DateTimeFormatter usage when printing dates that rely on isSupportedBy for fields, which is
|
||||||
|
// what we use for epoch time. This change accounts for that bug. It should be removed when java 8 support is removed
|
||||||
|
jodaTimeOut += ".0";
|
||||||
|
}
|
||||||
String message = String.format(Locale.ROOT, "expected string representation to be equal for format [%s]: joda [%s], java [%s]",
|
String message = String.format(Locale.ROOT, "expected string representation to be equal for format [%s]: joda [%s], java [%s]",
|
||||||
format, jodaTimeOut, javaTimeOut);
|
format, jodaTimeOut, javaTimeOut);
|
||||||
assertThat(message, javaTimeOut, is(jodaTimeOut));
|
assertThat(message, javaTimeOut, is(jodaTimeOut));
|
||||||
|
@ -484,7 +492,6 @@ public class JavaJodaTimeDuellingTests extends ESTestCase {
|
||||||
private void assertSameDate(String input, String format) {
|
private void assertSameDate(String input, String format) {
|
||||||
DateFormatter jodaFormatter = Joda.forPattern(format);
|
DateFormatter jodaFormatter = Joda.forPattern(format);
|
||||||
DateFormatter javaFormatter = DateFormatters.forPattern(format);
|
DateFormatter javaFormatter = DateFormatters.forPattern(format);
|
||||||
|
|
||||||
assertSameDate(input, format, jodaFormatter, javaFormatter);
|
assertSameDate(input, format, jodaFormatter, javaFormatter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ import java.time.ZoneOffset;
|
||||||
|
|
||||||
public class JodaTests extends ESTestCase {
|
public class JodaTests extends ESTestCase {
|
||||||
|
|
||||||
|
|
||||||
public void testBasicTTimePattern() {
|
public void testBasicTTimePattern() {
|
||||||
DateFormatter formatter1 = DateFormatter.forPattern("basic_t_time");
|
DateFormatter formatter1 = DateFormatter.forPattern("basic_t_time");
|
||||||
assertEquals(formatter1.pattern(), "basic_t_time");
|
assertEquals(formatter1.pattern(), "basic_t_time");
|
||||||
|
|
|
@ -23,7 +23,6 @@ import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import java.time.temporal.TemporalAccessor;
|
import java.time.temporal.TemporalAccessor;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -58,21 +57,6 @@ public class DateFormattersTests extends ESTestCase {
|
||||||
assertThat(instant.getEpochSecond(), is(12L));
|
assertThat(instant.getEpochSecond(), is(12L));
|
||||||
assertThat(instant.getNano(), is(345_000_000));
|
assertThat(instant.getNano(), is(345_000_000));
|
||||||
}
|
}
|
||||||
{
|
|
||||||
Instant instant = Instant.from(formatter.parse("-12345.6789"));
|
|
||||||
assertThat(instant.getEpochSecond(), is(-13L));
|
|
||||||
assertThat(instant.getNano(), is(1_000_000_000 - 345_678_900));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
Instant instant = Instant.from(formatter.parse("-436134.241272"));
|
|
||||||
assertThat(instant.getEpochSecond(), is(-437L));
|
|
||||||
assertThat(instant.getNano(), is(1_000_000_000 - 134_241_272));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
Instant instant = Instant.from(formatter.parse("-12345"));
|
|
||||||
assertThat(instant.getEpochSecond(), is(-13L));
|
|
||||||
assertThat(instant.getNano(), is(1_000_000_000 - 345_000_000));
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
Instant instant = Instant.from(formatter.parse("0"));
|
Instant instant = Instant.from(formatter.parse("0"));
|
||||||
assertThat(instant.getEpochSecond(), is(0L));
|
assertThat(instant.getEpochSecond(), is(0L));
|
||||||
|
@ -83,10 +67,10 @@ public class DateFormattersTests extends ESTestCase {
|
||||||
public void testEpochMilliParser() {
|
public void testEpochMilliParser() {
|
||||||
DateFormatter formatter = DateFormatters.forPattern("epoch_millis");
|
DateFormatter formatter = DateFormatters.forPattern("epoch_millis");
|
||||||
DateTimeParseException e = expectThrows(DateTimeParseException.class, () -> formatter.parse("invalid"));
|
DateTimeParseException e = expectThrows(DateTimeParseException.class, () -> formatter.parse("invalid"));
|
||||||
assertThat(e.getMessage(), containsString("invalid number"));
|
assertThat(e.getMessage(), containsString("could not be parsed"));
|
||||||
|
|
||||||
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("123.1234567"));
|
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("123.1234567"));
|
||||||
assertThat(e.getMessage(), containsString("too much granularity after dot [123.1234567]"));
|
assertThat(e.getMessage(), containsString("unparsed text found at index 3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is not in the duelling tests, because the epoch second parser in joda time drops the milliseconds after the comma
|
// this is not in the duelling tests, because the epoch second parser in joda time drops the milliseconds after the comma
|
||||||
|
@ -108,17 +92,14 @@ public class DateFormattersTests extends ESTestCase {
|
||||||
assertThat(Instant.from(formatter.parse("1234.12345678")).getNano(), is(123_456_780));
|
assertThat(Instant.from(formatter.parse("1234.12345678")).getNano(), is(123_456_780));
|
||||||
assertThat(Instant.from(formatter.parse("1234.123456789")).getNano(), is(123_456_789));
|
assertThat(Instant.from(formatter.parse("1234.123456789")).getNano(), is(123_456_789));
|
||||||
|
|
||||||
assertThat(Instant.from(formatter.parse("-1234.567")).toEpochMilli(), is(-1234567L));
|
|
||||||
assertThat(Instant.from(formatter.parse("-1234")).getNano(), is(0));
|
|
||||||
|
|
||||||
DateTimeParseException e = expectThrows(DateTimeParseException.class, () -> formatter.parse("1234.1234567890"));
|
DateTimeParseException e = expectThrows(DateTimeParseException.class, () -> formatter.parse("1234.1234567890"));
|
||||||
assertThat(e.getMessage(), is("too much granularity after dot [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"));
|
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("1234.123456789013221"));
|
||||||
assertThat(e.getMessage(), is("too much granularity after dot [1234.123456789013221]"));
|
assertThat(e.getMessage(), is("Text '1234.123456789013221' could not be parsed, unparsed text found at index 4"));
|
||||||
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("abc"));
|
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("abc"));
|
||||||
assertThat(e.getMessage(), is("invalid number [abc]"));
|
assertThat(e.getMessage(), is("Text 'abc' could not be parsed at index 0"));
|
||||||
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("1234.abc"));
|
e = expectThrows(DateTimeParseException.class, () -> formatter.parse("1234.abc"));
|
||||||
assertThat(e.getMessage(), is("invalid number [1234.abc]"));
|
assertThat(e.getMessage(), is("Text '1234.abc' could not be parsed, unparsed text found at index 4"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEpochMilliParsersWithDifferentFormatters() {
|
public void testEpochMilliParsersWithDifferentFormatters() {
|
||||||
|
@ -132,18 +113,6 @@ public class DateFormattersTests extends ESTestCase {
|
||||||
assertThat(DateFormatters.forPattern("strict_date_optional_time").locale(), is(Locale.ROOT));
|
assertThat(DateFormatters.forPattern("strict_date_optional_time").locale(), is(Locale.ROOT));
|
||||||
Locale locale = randomLocale(random());
|
Locale locale = randomLocale(random());
|
||||||
assertThat(DateFormatters.forPattern("strict_date_optional_time").withLocale(locale).locale(), is(locale));
|
assertThat(DateFormatters.forPattern("strict_date_optional_time").withLocale(locale).locale(), is(locale));
|
||||||
if (locale.equals(Locale.ROOT)) {
|
|
||||||
DateFormatter millisFormatter = DateFormatters.forPattern("epoch_millis");
|
|
||||||
assertThat(millisFormatter.withLocale(locale), is(millisFormatter));
|
|
||||||
DateFormatter secondFormatter = DateFormatters.forPattern("epoch_second");
|
|
||||||
assertThat(secondFormatter.withLocale(locale), is(secondFormatter));
|
|
||||||
} else {
|
|
||||||
IllegalArgumentException e =
|
|
||||||
expectThrows(IllegalArgumentException.class, () -> DateFormatters.forPattern("epoch_millis").withLocale(locale));
|
|
||||||
assertThat(e.getMessage(), is("epoch_millis date formatter can only be in locale ROOT"));
|
|
||||||
e = expectThrows(IllegalArgumentException.class, () -> DateFormatters.forPattern("epoch_second").withLocale(locale));
|
|
||||||
assertThat(e.getMessage(), is("epoch_second date formatter can only be in locale ROOT"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testTimeZones() {
|
public void testTimeZones() {
|
||||||
|
@ -151,18 +120,6 @@ public class DateFormattersTests extends ESTestCase {
|
||||||
assertThat(DateFormatters.forPattern("strict_date_optional_time").zone(), is(nullValue()));
|
assertThat(DateFormatters.forPattern("strict_date_optional_time").zone(), is(nullValue()));
|
||||||
ZoneId zoneId = randomZone();
|
ZoneId zoneId = randomZone();
|
||||||
assertThat(DateFormatters.forPattern("strict_date_optional_time").withZone(zoneId).zone(), is(zoneId));
|
assertThat(DateFormatters.forPattern("strict_date_optional_time").withZone(zoneId).zone(), is(zoneId));
|
||||||
if (zoneId.equals(ZoneOffset.UTC)) {
|
|
||||||
DateFormatter millisFormatter = DateFormatters.forPattern("epoch_millis");
|
|
||||||
assertThat(millisFormatter.withZone(zoneId), is(millisFormatter));
|
|
||||||
DateFormatter secondFormatter = DateFormatters.forPattern("epoch_second");
|
|
||||||
assertThat(secondFormatter.withZone(zoneId), is(secondFormatter));
|
|
||||||
} else {
|
|
||||||
IllegalArgumentException e =
|
|
||||||
expectThrows(IllegalArgumentException.class, () -> DateFormatters.forPattern("epoch_millis").withZone(zoneId));
|
|
||||||
assertThat(e.getMessage(), is("epoch_millis date formatter can only be in zone offset UTC"));
|
|
||||||
e = expectThrows(IllegalArgumentException.class, () -> DateFormatters.forPattern("epoch_second").withZone(zoneId));
|
|
||||||
assertThat(e.getMessage(), is("epoch_second date formatter can only be in zone offset UTC"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEqualsAndHashcode() {
|
public void testEqualsAndHashcode() {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.elasticsearch.common.bytes.BytesArray;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.collect.MapBuilder;
|
import org.elasticsearch.common.collect.MapBuilder;
|
||||||
import org.elasticsearch.common.document.DocumentField;
|
import org.elasticsearch.common.document.DocumentField;
|
||||||
|
import org.elasticsearch.common.joda.Joda;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.time.DateFormatters;
|
import org.elasticsearch.common.time.DateFormatters;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
@ -897,8 +898,9 @@ public class SearchFieldsIT extends ESIntegTestCase {
|
||||||
assertThat(searchResponse.getHits().getAt(0).getFields().get("long_field").getValue(), equalTo("4.0"));
|
assertThat(searchResponse.getHits().getAt(0).getFields().get("long_field").getValue(), equalTo("4.0"));
|
||||||
assertThat(searchResponse.getHits().getAt(0).getFields().get("float_field").getValue(), equalTo("5.0"));
|
assertThat(searchResponse.getHits().getAt(0).getFields().get("float_field").getValue(), equalTo("5.0"));
|
||||||
assertThat(searchResponse.getHits().getAt(0).getFields().get("double_field").getValue(), equalTo("6.0"));
|
assertThat(searchResponse.getHits().getAt(0).getFields().get("double_field").getValue(), equalTo("6.0"));
|
||||||
|
// TODO: switch to java date formatter, but will require special casing java 8 as there is a bug with epoch formatting there
|
||||||
assertThat(searchResponse.getHits().getAt(0).getFields().get("date_field").getValue(),
|
assertThat(searchResponse.getHits().getAt(0).getFields().get("date_field").getValue(),
|
||||||
equalTo(DateFormatters.forPattern("epoch_millis").format(date)));
|
equalTo(Joda.forPattern("epoch_millis").format(date)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testScriptFields() throws Exception {
|
public void testScriptFields() throws Exception {
|
||||||
|
|
Loading…
Reference in New Issue