Core: Add java time xcontent serializers (#33120)
This ensures that the java time class exposed by painless have proper serialization/string representations. Closes #31853
This commit is contained in:
parent
f29f0af7bc
commit
48b388ce82
|
@ -95,7 +95,7 @@ setup:
|
|||
field:
|
||||
script:
|
||||
source: "doc.date.get(0)"
|
||||
- match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12Z' }
|
||||
- match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12.000Z' }
|
||||
|
||||
- do:
|
||||
search:
|
||||
|
@ -104,7 +104,7 @@ setup:
|
|||
field:
|
||||
script:
|
||||
source: "doc.date.value"
|
||||
- match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12Z' }
|
||||
- match: { hits.hits.0.fields.field.0: '2017-01-01T12:11:12.000Z' }
|
||||
|
||||
---
|
||||
"geo_point":
|
||||
|
|
|
@ -48,6 +48,7 @@ import static java.time.temporal.ChronoField.HOUR_OF_DAY;
|
|||
import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
|
||||
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
|
||||
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
|
||||
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
|
||||
import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
|
||||
|
||||
public class DateFormatters {
|
||||
|
@ -81,7 +82,7 @@ public class DateFormatters {
|
|||
.appendFraction(MILLI_OF_SECOND, 3, 3, true)
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.appendOffset("+HHmm", "Z")
|
||||
.appendZoneOrOffsetId()
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
@ -95,7 +96,7 @@ public class DateFormatters {
|
|||
.appendFraction(MILLI_OF_SECOND, 3, 3, true)
|
||||
.optionalEnd()
|
||||
.optionalStart()
|
||||
.appendZoneOrOffsetId()
|
||||
.appendOffset("+HHmm", "Z")
|
||||
.optionalEnd()
|
||||
.optionalEnd()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
@ -106,6 +107,40 @@ public class DateFormatters {
|
|||
private static final CompoundDateTimeFormatter STRICT_DATE_OPTIONAL_TIME =
|
||||
new CompoundDateTimeFormatter(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()
|
||||
.optionalEnd()
|
||||
.toFormatter(Locale.ROOT);
|
||||
|
||||
private static final DateTimeFormatter STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_2 = 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()
|
||||
.appendOffset("+HHmm", "Z")
|
||||
.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 CompoundDateTimeFormatter STRICT_DATE_OPTIONAL_TIME_NANOS =
|
||||
new CompoundDateTimeFormatter(STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_1, STRICT_DATE_OPTIONAL_TIME_FORMATTER_WITH_NANOS_2);
|
||||
|
||||
/////////////////////////////////////////
|
||||
//
|
||||
// BEGIN basic time formatters
|
||||
|
@ -1326,6 +1361,8 @@ public class DateFormatters {
|
|||
return STRICT_DATE_HOUR_MINUTE_SECOND_MILLIS;
|
||||
} else if ("strictDateOptionalTime".equals(input) || "strict_date_optional_time".equals(input)) {
|
||||
return STRICT_DATE_OPTIONAL_TIME;
|
||||
} else if ("strictDateOptionalTimeNanos".equals(input) || "strict_date_optional_time_nanos".equals(input)) {
|
||||
return STRICT_DATE_OPTIONAL_TIME_NANOS;
|
||||
} else if ("strictDateTime".equals(input) || "strict_date_time".equals(input)) {
|
||||
return STRICT_DATE_TIME;
|
||||
} else if ("strictDateTimeNoMillis".equals(input) || "strict_date_time_no_millis".equals(input)) {
|
||||
|
|
|
@ -21,6 +21,8 @@ package org.elasticsearch.common.xcontent;
|
|||
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.time.CompoundDateTimeFormatter;
|
||||
import org.elasticsearch.common.time.DateFormatters;
|
||||
import org.elasticsearch.common.unit.ByteSizeValue;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.joda.time.DateTime;
|
||||
|
@ -33,6 +35,19 @@ import org.joda.time.format.ISODateTimeFormat;
|
|||
import org.joda.time.tz.CachedDateTimeZone;
|
||||
import org.joda.time.tz.FixedDateTimeZone;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Month;
|
||||
import java.time.MonthDay;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.Year;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
@ -49,6 +64,9 @@ import java.util.function.Function;
|
|||
public class XContentElasticsearchExtension implements XContentBuilderExtension {
|
||||
|
||||
public static final DateTimeFormatter DEFAULT_DATE_PRINTER = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC);
|
||||
public static final CompoundDateTimeFormatter DEFAULT_FORMATTER = DateFormatters.forPattern("strict_date_optional_time_nanos");
|
||||
public static final CompoundDateTimeFormatter LOCAL_TIME_FORMATTER = DateFormatters.forPattern("HH:mm:ss.SSS");
|
||||
public static final CompoundDateTimeFormatter OFFSET_TIME_FORMATTER = DateFormatters.forPattern("HH:mm:ss.SSSZZZZZ");
|
||||
|
||||
@Override
|
||||
public Map<Class<?>, XContentBuilder.Writer> getXContentWriters() {
|
||||
|
@ -62,6 +80,19 @@ public class XContentElasticsearchExtension implements XContentBuilderExtension
|
|||
writers.put(MutableDateTime.class, XContentBuilder::timeValue);
|
||||
writers.put(DateTime.class, XContentBuilder::timeValue);
|
||||
writers.put(TimeValue.class, (b, v) -> b.value(v.toString()));
|
||||
writers.put(ZonedDateTime.class, XContentBuilder::timeValue);
|
||||
writers.put(OffsetDateTime.class, XContentBuilder::timeValue);
|
||||
writers.put(OffsetTime.class, XContentBuilder::timeValue);
|
||||
writers.put(java.time.Instant.class, XContentBuilder::timeValue);
|
||||
writers.put(LocalDateTime.class, XContentBuilder::timeValue);
|
||||
writers.put(LocalDate.class, XContentBuilder::timeValue);
|
||||
writers.put(LocalTime.class, XContentBuilder::timeValue);
|
||||
writers.put(DayOfWeek.class, (b, v) -> b.value(v.toString()));
|
||||
writers.put(Month.class, (b, v) -> b.value(v.toString()));
|
||||
writers.put(MonthDay.class, (b, v) -> b.value(v.toString()));
|
||||
writers.put(Year.class, (b, v) -> b.value(v.toString()));
|
||||
writers.put(Duration.class, (b, v) -> b.value(v.toString()));
|
||||
writers.put(Period.class, (b, v) -> b.value(v.toString()));
|
||||
|
||||
writers.put(BytesReference.class, (b, v) -> {
|
||||
if (v == null) {
|
||||
|
@ -102,6 +133,14 @@ public class XContentElasticsearchExtension implements XContentBuilderExtension
|
|||
transformers.put(Calendar.class, d -> DEFAULT_DATE_PRINTER.print(((Calendar) d).getTimeInMillis()));
|
||||
transformers.put(GregorianCalendar.class, d -> DEFAULT_DATE_PRINTER.print(((Calendar) d).getTimeInMillis()));
|
||||
transformers.put(Instant.class, d -> DEFAULT_DATE_PRINTER.print((Instant) d));
|
||||
transformers.put(ZonedDateTime.class, d -> DEFAULT_FORMATTER.format((ZonedDateTime) d));
|
||||
transformers.put(OffsetDateTime.class, d -> DEFAULT_FORMATTER.format((OffsetDateTime) d));
|
||||
transformers.put(OffsetTime.class, d -> OFFSET_TIME_FORMATTER.format((OffsetTime) d));
|
||||
transformers.put(LocalDateTime.class, d -> DEFAULT_FORMATTER.format((LocalDateTime) d));
|
||||
transformers.put(java.time.Instant.class,
|
||||
d -> DEFAULT_FORMATTER.format(ZonedDateTime.ofInstant((java.time.Instant) d, ZoneOffset.UTC)));
|
||||
transformers.put(LocalDate.class, d -> ((LocalDate) d).toString());
|
||||
transformers.put(LocalTime.class, d -> LOCAL_TIME_FORMATTER.format((LocalTime) d));
|
||||
return transformers;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ package org.elasticsearch.common.xcontent;
|
|||
import com.fasterxml.jackson.core.JsonGenerationException;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.apache.lucene.util.Constants;
|
||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||
|
@ -51,6 +50,19 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.file.Path;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Month;
|
||||
import java.time.MonthDay;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.Year;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
|
@ -459,6 +471,116 @@ public abstract class BaseXContentTestCase extends ESTestCase {
|
|||
.endObject());
|
||||
}
|
||||
|
||||
public void testJavaTime() throws Exception {
|
||||
final ZonedDateTime d1 = ZonedDateTime.of(2016, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
|
||||
// ZonedDateTime
|
||||
assertResult("{'date':null}", () -> builder().startObject().timeField("date", (ZonedDateTime) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date").timeValue((ZonedDateTime) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date", (ZonedDateTime) null).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000Z'}", () -> builder().startObject().timeField("d1", d1).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000Z'}", () -> builder().startObject().field("d1").timeValue(d1).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000Z'}", () -> builder().startObject().field("d1", d1).endObject());
|
||||
|
||||
// Instant
|
||||
assertResult("{'date':null}", () -> builder().startObject().timeField("date", (java.time.Instant) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date").timeValue((java.time.Instant) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date", (java.time.Instant) null).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000Z'}", () -> builder().startObject().timeField("d1", d1.toInstant()).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000Z'}", () -> builder().startObject().field("d1").timeValue(d1.toInstant()).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000Z'}", () -> builder().startObject().field("d1", d1.toInstant()).endObject());
|
||||
|
||||
// LocalDateTime (no time zone)
|
||||
assertResult("{'date':null}", () -> builder().startObject().timeField("date", (LocalDateTime) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date").timeValue((LocalDateTime) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date", (LocalDateTime) null).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000'}",
|
||||
() -> builder().startObject().timeField("d1", d1.toLocalDateTime()).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000'}",
|
||||
() -> builder().startObject().field("d1").timeValue(d1.toLocalDateTime()).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000'}", () -> builder().startObject().field("d1", d1.toLocalDateTime()).endObject());
|
||||
|
||||
// LocalDate (no time, no time zone)
|
||||
assertResult("{'date':null}", () -> builder().startObject().timeField("date", (LocalDate) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date").timeValue((LocalDate) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date", (LocalDate) null).endObject());
|
||||
assertResult("{'d1':'2016-01-01'}", () -> builder().startObject().timeField("d1", d1.toLocalDate()).endObject());
|
||||
assertResult("{'d1':'2016-01-01'}", () -> builder().startObject().field("d1").timeValue(d1.toLocalDate()).endObject());
|
||||
assertResult("{'d1':'2016-01-01'}", () -> builder().startObject().field("d1", d1.toLocalDate()).endObject());
|
||||
|
||||
// LocalTime (no date, no time zone)
|
||||
assertResult("{'date':null}", () -> builder().startObject().timeField("date", (LocalTime) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date").timeValue((LocalTime) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date", (LocalTime) null).endObject());
|
||||
assertResult("{'d1':'00:00:00.000'}", () -> builder().startObject().timeField("d1", d1.toLocalTime()).endObject());
|
||||
assertResult("{'d1':'00:00:00.000'}", () -> builder().startObject().field("d1").timeValue(d1.toLocalTime()).endObject());
|
||||
assertResult("{'d1':'00:00:00.000'}", () -> builder().startObject().field("d1", d1.toLocalTime()).endObject());
|
||||
final ZonedDateTime d2 = ZonedDateTime.of(2016, 1, 1, 7, 59, 23, 123_000_000, ZoneOffset.UTC);
|
||||
assertResult("{'d1':'07:59:23.123'}", () -> builder().startObject().timeField("d1", d2.toLocalTime()).endObject());
|
||||
assertResult("{'d1':'07:59:23.123'}", () -> builder().startObject().field("d1").timeValue(d2.toLocalTime()).endObject());
|
||||
assertResult("{'d1':'07:59:23.123'}", () -> builder().startObject().field("d1", d2.toLocalTime()).endObject());
|
||||
|
||||
// OffsetDateTime
|
||||
assertResult("{'date':null}", () -> builder().startObject().timeField("date", (OffsetDateTime) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date").timeValue((OffsetDateTime) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date", (OffsetDateTime) null).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000Z'}", () -> builder().startObject().field("d1", d1.toOffsetDateTime()).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000Z'}",
|
||||
() -> builder().startObject().timeField("d1", d1.toOffsetDateTime()).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000Z'}",
|
||||
() -> builder().startObject().field("d1").timeValue(d1.toOffsetDateTime()).endObject());
|
||||
// also test with a date that has a real offset
|
||||
OffsetDateTime offsetDateTime = d1.withZoneSameLocal(ZoneOffset.ofHours(5)).toOffsetDateTime();
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000+05:00'}", () -> builder().startObject().field("d1", offsetDateTime).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000+05:00'}", () -> builder().startObject().timeField("d1", offsetDateTime).endObject());
|
||||
assertResult("{'d1':'2016-01-01T00:00:00.000+05:00'}",
|
||||
() -> builder().startObject().field("d1").timeValue(offsetDateTime).endObject());
|
||||
|
||||
// OffsetTime
|
||||
assertResult("{'date':null}", () -> builder().startObject().timeField("date", (OffsetTime) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date").timeValue((OffsetTime) null).endObject());
|
||||
assertResult("{'date':null}", () -> builder().startObject().field("date", (OffsetTime) null).endObject());
|
||||
final OffsetTime offsetTime = d2.toOffsetDateTime().toOffsetTime();
|
||||
assertResult("{'o':'07:59:23.123Z'}", () -> builder().startObject().timeField("o", offsetTime).endObject());
|
||||
assertResult("{'o':'07:59:23.123Z'}", () -> builder().startObject().field("o").timeValue(offsetTime).endObject());
|
||||
assertResult("{'o':'07:59:23.123Z'}", () -> builder().startObject().field("o", offsetTime).endObject());
|
||||
// also test with a date that has a real offset
|
||||
final OffsetTime zonedOffsetTime = offsetTime.withOffsetSameLocal(ZoneOffset.ofHours(5));
|
||||
assertResult("{'o':'07:59:23.123+05:00'}", () -> builder().startObject().timeField("o", zonedOffsetTime).endObject());
|
||||
assertResult("{'o':'07:59:23.123+05:00'}", () -> builder().startObject().field("o").timeValue(zonedOffsetTime).endObject());
|
||||
assertResult("{'o':'07:59:23.123+05:00'}", () -> builder().startObject().field("o", zonedOffsetTime).endObject());
|
||||
|
||||
// DayOfWeek enum, not a real time value, but might be used in scripts
|
||||
assertResult("{'dayOfWeek':null}", () -> builder().startObject().field("dayOfWeek", (DayOfWeek) null).endObject());
|
||||
DayOfWeek dayOfWeek = randomFrom(DayOfWeek.values());
|
||||
assertResult("{'dayOfWeek':'" + dayOfWeek + "'}", () -> builder().startObject().field("dayOfWeek", dayOfWeek).endObject());
|
||||
|
||||
// Month
|
||||
Month month = randomFrom(Month.values());
|
||||
assertResult("{'m':null}", () -> builder().startObject().field("m", (Month) null).endObject());
|
||||
assertResult("{'m':'" + month + "'}", () -> builder().startObject().field("m", month).endObject());
|
||||
|
||||
// MonthDay
|
||||
MonthDay monthDay = MonthDay.of(month, randomIntBetween(1, 28));
|
||||
assertResult("{'m':null}", () -> builder().startObject().field("m", (MonthDay) null).endObject());
|
||||
assertResult("{'m':'" + monthDay + "'}", () -> builder().startObject().field("m", monthDay).endObject());
|
||||
|
||||
// Year
|
||||
Year year = Year.of(randomIntBetween(0, 2300));
|
||||
assertResult("{'y':null}", () -> builder().startObject().field("y", (Year) null).endObject());
|
||||
assertResult("{'y':'" + year + "'}", () -> builder().startObject().field("y", year).endObject());
|
||||
|
||||
// Duration
|
||||
Duration duration = Duration.ofSeconds(randomInt(100000));
|
||||
assertResult("{'d':null}", () -> builder().startObject().field("d", (Duration) null).endObject());
|
||||
assertResult("{'d':'" + duration + "'}", () -> builder().startObject().field("d", duration).endObject());
|
||||
|
||||
// Period
|
||||
Period period = Period.ofDays(randomInt(1000));
|
||||
assertResult("{'p':null}", () -> builder().startObject().field("p", (Period) null).endObject());
|
||||
assertResult("{'p':'" + period + "'}", () -> builder().startObject().field("p", period).endObject());
|
||||
}
|
||||
|
||||
public void testGeoPoint() throws Exception {
|
||||
assertResult("{'geo':null}", () -> builder().startObject().field("geo", (GeoPoint) null).endObject());
|
||||
assertResult("{'geo':{'lat':52.4267578125,'lon':13.271484375}}", () -> builder()
|
||||
|
|
Loading…
Reference in New Issue