Use own implementation of date formatters.

Original PR #1650 
Closes #1647
This commit is contained in:
Peter-Josef Meisch 2021-01-17 16:36:38 +01:00 committed by GitHub
parent e311df98c0
commit c6713fad5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 745 additions and 226 deletions

View File

@ -16,58 +16,73 @@
package org.springframework.data.elasticsearch.annotations;
/**
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/
*
* Values based on reference doc - https://www.elastic.co/guide/reference/mapping/date-format/. The patterns are taken
* from this documentation and slightly adapted so that a Java {@link java.time.format.DateTimeFormatter} produces the
* same values as the Elasticsearch formatter.
*
* @author Jakub Vavrik
* @author Tim te Beek
* @author Peter-Josef Meisch
*/
public enum DateFormat {
none, //
custom, //
basic_date, //
basic_date_time, //
basic_date_time_no_millis, //
basic_ordinal_date, //
basic_ordinal_date_time, //
basic_ordinal_date_time_no_millis, //
basic_time, //
basic_time_no_millis, //
basic_t_time, //
basic_t_time_no_millis, //
basic_week_date, //
basic_week_date_time, //
basic_week_date_time_no_millis, //
date, //
date_hour, //
date_hour_minute, //
date_hour_minute_second, //
date_hour_minute_second_fraction, //
date_hour_minute_second_millis, //
date_optional_time, //
date_time, //
date_time_no_millis, //
epoch_millis, //
epoch_second, //
hour, //
hour_minute, //
hour_minute_second, //
hour_minute_second_fraction, //
hour_minute_second_millis, //
ordinal_date, //
ordinal_date_time, //
ordinal_date_time_no_millis, //
time, //
time_no_millis, //
t_time, //
t_time_no_millis, //
week_date, //
week_date_time, //
week_date_time_no_millis, //
weekyear, //
weekyear_week, //
weekyear_week_day, //
year, //
year_month, //
year_month_day //
none(""), //
custom(""), //
basic_date("uuuuMMdd"), //
basic_date_time("uuuuMMdd'T'HHmmss.SSSXXX"), //
basic_date_time_no_millis("uuuuMMdd'T'HHmmssXXX"), //
basic_ordinal_date("uuuuDDD"), //
basic_ordinal_date_time("yyyyDDD'T'HHmmss.SSSXXX"), //
basic_ordinal_date_time_no_millis("yyyyDDD'T'HHmmssXXX"), //
basic_time("HHmmss.SSSXXX"), //
basic_time_no_millis("HHmmssXXX"), //
basic_t_time("'T'HHmmss.SSSXXX"), //
basic_t_time_no_millis("'T'HHmmssXXX"), //
basic_week_date("YYYY'W'wwe"), // week-based-year!
basic_week_date_time("YYYY'W'wwe'T'HHmmss.SSSX"), // here Elasticsearch uses a different zone format
basic_week_date_time_no_millis("YYYY'W'wwe'T'HHmmssX"), //
date("uuuu-MM-dd"), //
date_hour("uuuu-MM-dd'T'HH"), //
date_hour_minute("uuuu-MM-dd'T'HH:mm"), //
date_hour_minute_second("uuuu-MM-dd'T'HH:mm:ss"), //
date_hour_minute_second_fraction("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_hour_minute_second_millis("uuuu-MM-dd'T'HH:mm:ss.SSS"), //
date_optional_time("uuuu-MM-dd['T'HH:mm:ss.SSSXXX]"), //
date_time("uuuu-MM-dd'T'HH:mm:ss.SSSXXX"), //
date_time_no_millis("uuuu-MM-dd'T'HH:mm:ssVV"), // here Elasticsearch uses the zone-id in it's implementation
epoch_millis("epoch_millis"), //
epoch_second("epoch_second"), //
hour("HH"), //
hour_minute("HH:mm"), //
hour_minute_second("HH:mm:ss"), //
hour_minute_second_fraction("HH:mm:ss.SSS"), //
hour_minute_second_millis("HH:mm:ss.SSS"), //
ordinal_date("uuuu-DDD"), //
ordinal_date_time("uuuu-DDD'T'HH:mm:ss.SSSXXX"), //
ordinal_date_time_no_millis("uuuu-DDD'T'HH:mm:ssXXX"), //
time("HH:mm:ss.SSSXXX"), //
time_no_millis("HH:mm:ssXXX"), //
t_time("'T'HH:mm:ss.SSSXXX"), //
t_time_no_millis("'T'HH:mm:ssXXX"), //
week_date("YYYY-'W'ww-e"), //
week_date_time("YYYY-'W'ww-e'T'HH:mm:ss.SSSXXX"), //
week_date_time_no_millis("YYYY-'W'ww-e'T'HH:mm:ssXXX"), //
weekyear(""), // no TemporalAccessor available for these 3
weekyear_week(""), //
weekyear_week_day(""), //
year("uuuu"), //
year_month("uuuu-MM"), //
year_month_day("uuuu-MM-dd"); //
private final String pattern;
DateFormat(String pattern) {
this.pattern = pattern;
}
/**
* @since 4.2
*/
public String getPattern() {
return pattern;
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.elasticsearch.core.convert;
import java.time.temporal.TemporalAccessor;
/**
* Interface to convert from and to {@link TemporalAccessor}s.
*
* @author Peter-Josef Meisch
* @since 4.2
*/
public interface DateFormatter {
/**
* Formats a {@link TemporalAccessor} into a String.
*
* @param accessor must not be {@literal null}
* @return the formatted String
*/
String format(TemporalAccessor accessor);
/**
* Parses a String into a {@link TemporalAccessor}.
*
* @param input the String to parse, must not be {@literal null}
* @param type the class of T
* @param <T> the {@link TemporalAccessor} implementation
* @return the parsed instance
*/
<T extends TemporalAccessor> T parse(String input, Class<T> type);
}

View File

@ -18,13 +18,17 @@ package org.springframework.data.elasticsearch.core.convert;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQuery;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import org.elasticsearch.common.time.DateFormatter;
import org.elasticsearch.common.time.DateFormatters;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.util.Assert;
@ -61,9 +65,13 @@ final public class ElasticsearchDateConverter {
* @return converter
*/
public static ElasticsearchDateConverter of(String pattern) {
Assert.notNull(pattern, "pattern must not be null");
return converters.computeIfAbsent(pattern, p -> new ElasticsearchDateConverter(DateFormatter.forPattern(p)));
Assert.notNull(pattern, "pattern must not be null");
Assert.hasText(pattern, "pattern must not be empty");
String[] subPatterns = pattern.split("\\|\\|");
return converters.computeIfAbsent(subPatterns[0].trim(), p -> new ElasticsearchDateConverter(forPattern(p)));
}
private ElasticsearchDateConverter(DateFormatter dateFormatter) {
@ -71,14 +79,20 @@ final public class ElasticsearchDateConverter {
}
/**
* Formats the given {@link TemporalAccessor} int a String
*
* Formats the given {@link TemporalAccessor} into a String.
*
* @param accessor must not be {@literal null}
* @return the formatted object
*/
public String format(TemporalAccessor accessor) {
Assert.notNull(accessor, "accessor must not be null");
Assert.notNull("accessor", "accessor must not be null");
if (accessor instanceof Instant) {
Instant instant = (Instant) accessor;
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"));
return dateFormatter.format(zonedDateTime);
}
return dateFormatter.format(accessor);
}
@ -97,24 +111,15 @@ final public class ElasticsearchDateConverter {
}
/**
* Parses a String into an object
*
* Parses a String into a TemporalAccessor.
*
* @param input the String to parse, must not be {@literal null}.
* @param type the class to return
* @param <T> the class of type
* @return the new created object
*/
public <T extends TemporalAccessor> T parse(String input, Class<T> type) {
ZonedDateTime zonedDateTime = DateFormatters.from(dateFormatter.parse(input));
try {
Method method = type.getMethod("from", TemporalAccessor.class);
Object o = method.invoke(null, zonedDateTime);
return type.cast(o);
} catch (NoSuchMethodException e) {
throw new ConversionException("no 'from' factory method found in class " + type.getName());
} catch (IllegalAccessException | InvocationTargetException e) {
throw new ConversionException("could not create object of class " + type.getName(), e);
}
return dateFormatter.parse(input, type);
}
/**
@ -124,7 +129,171 @@ final public class ElasticsearchDateConverter {
* @return the new created object
*/
public Date parse(String input) {
ZonedDateTime zonedDateTime = DateFormatters.from(dateFormatter.parse(input));
return new Date(Instant.from(zonedDateTime).toEpochMilli());
return new Date(dateFormatter.parse(input, Instant.class).toEpochMilli());
}
/**
* Creates a {@link DateFormatter} for a given pattern. The pattern can be the name of a {@link DateFormat} enum value
* or a literal pattern.
*
* @param pattern the pattern to use
* @return DateFormatter
*/
private static DateFormatter forPattern(String pattern) {
String resolvedPattern = pattern;
if (DateFormat.epoch_millis.getPattern().equals(pattern)) {
return new EpochMillisDateFormatter();
}
if (DateFormat.epoch_second.getPattern().equals(pattern)) {
return new EpochSecondDateFormatter();
}
// check the enum values
for (DateFormat dateFormat : DateFormat.values()) {
switch (dateFormat) {
case weekyear:
case weekyear_week:
case weekyear_week_day:
case custom:
continue;
}
if (dateFormat.name().equals(pattern)) {
resolvedPattern = dateFormat.getPattern();
break;
}
}
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(resolvedPattern);
return new PatternDateFormatter(dateTimeFormatter);
}
private static <T extends TemporalAccessor> TemporalQuery<T> getTemporalQuery(Class<T> type) {
return temporal -> {
try {
Method method = type.getMethod("from", TemporalAccessor.class);
Object o = method.invoke(null, temporal);
return type.cast(o);
} catch (NoSuchMethodException e) {
throw new ConversionException("no 'from' factory method found in class " + type.getName());
} catch (IllegalAccessException | InvocationTargetException e) {
throw new ConversionException("could not create object of class " + type.getName(), e);
}
};
} // endregion
/**
* a DateFormatter to convert epoch milliseconds
*/
static class EpochMillisDateFormatter implements DateFormatter {
@Override
public String format(TemporalAccessor accessor) {
Assert.notNull(accessor, "accessor must not be null");
return Long.toString(Instant.from(accessor).toEpochMilli());
}
@Override
public <T extends TemporalAccessor> T parse(String input, Class<T> type) {
Assert.notNull(input, "input must not be null");
Assert.notNull(type, "type must not be null");
Instant instant = Instant.ofEpochMilli(Long.parseLong(input));
TemporalQuery<T> query = getTemporalQuery(type);
return query.queryFrom(instant);
}
}
/**
* a DateFormatter to convert epoch seconds. Elasticsearch's formatter uses double values, so do we
*/
static class EpochSecondDateFormatter implements DateFormatter {
@Override
public String format(TemporalAccessor accessor) {
Assert.notNull(accessor, "accessor must not be null");
long epochMilli = Instant.from(accessor).toEpochMilli();
long fraction = epochMilli % 1_000;
if (fraction == 0) {
return Long.toString(epochMilli / 1_000);
} else {
Double d = ((double) epochMilli) / 1_000;
return String.format(Locale.ROOT, "%.03f", d);
}
}
@Override
public <T extends TemporalAccessor> T parse(String input, Class<T> type) {
Assert.notNull(input, "input must not be null");
Assert.notNull(type, "type must not be null");
Double epochMilli = Double.parseDouble(input) * 1_000;
Instant instant = Instant.ofEpochMilli(epochMilli.longValue());
TemporalQuery<T> query = getTemporalQuery(type);
return query.queryFrom(instant);
}
}
static class PatternDateFormatter implements DateFormatter {
private final DateTimeFormatter dateTimeFormatter;
PatternDateFormatter(DateTimeFormatter dateTimeFormatter) {
this.dateTimeFormatter = dateTimeFormatter;
}
@Override
public String format(TemporalAccessor accessor) {
Assert.notNull(accessor, "accessor must not be null");
try {
return dateTimeFormatter.format(accessor);
} catch (Exception e) {
if (accessor instanceof Instant) {
// as alternatives try to format a ZonedDateTime or LocalDateTime
return dateTimeFormatter.format(ZonedDateTime.ofInstant((Instant) accessor, ZoneId.of("UTC")));
} else {
throw e;
}
}
}
@Override
public <T extends TemporalAccessor> T parse(String input, Class<T> type) {
Assert.notNull(input, "input must not be null");
Assert.notNull(type, "type must not be null");
try {
return dateTimeFormatter.parse(input, getTemporalQuery(type));
} catch (Exception e) {
if (type.equals(Instant.class)) {
// as alternatives try to parse a ZonedDateTime or LocalDateTime
try {
ZonedDateTime zonedDateTime = dateTimeFormatter.parse(input, getTemporalQuery(ZonedDateTime.class));
// noinspection unchecked
return (T) zonedDateTime.toInstant();
} catch (Exception exception) {
LocalDateTime localDateTime = dateTimeFormatter.parse(input, getTemporalQuery(LocalDateTime.class));
// noinspection unchecked
return (T) localDateTime.toInstant(ZoneOffset.UTC);
}
} else {
throw e;
}
}
}
}
}

View File

@ -179,7 +179,7 @@ public class SimpleElasticsearchPersistentProperty extends
return;
}
ElasticsearchDateConverter converter;
ElasticsearchDateConverter converter = null;
if (dateFormat == DateFormat.custom) {
String pattern = field.pattern();
@ -192,33 +192,47 @@ public class SimpleElasticsearchPersistentProperty extends
converter = ElasticsearchDateConverter.of(pattern);
} else {
converter = ElasticsearchDateConverter.of(dateFormat);
switch (dateFormat) {
case weekyear:
case weekyear_week:
case weekyear_week_day:
LOGGER.warn("no Converter available for " + actualType.getName() + " and date format " + dateFormat.name()
+ ". Use a custom converter instead");
break;
default:
converter = ElasticsearchDateConverter.of(dateFormat);
break;
}
}
propertyConverter = new ElasticsearchPersistentPropertyConverter() {
final ElasticsearchDateConverter dateConverter = converter;
if (converter != null) {
ElasticsearchDateConverter finalConverter = converter;
propertyConverter = new ElasticsearchPersistentPropertyConverter() {
final ElasticsearchDateConverter dateConverter = finalConverter;
@Override
public String write(Object property) {
if (isTemporalAccessor && TemporalAccessor.class.isAssignableFrom(property.getClass())) {
return dateConverter.format((TemporalAccessor) property);
} else if (isDate && Date.class.isAssignableFrom(property.getClass())) {
return dateConverter.format((Date) property);
} else {
return property.toString();
@Override
public String write(Object property) {
if (isTemporalAccessor && TemporalAccessor.class.isAssignableFrom(property.getClass())) {
return dateConverter.format((TemporalAccessor) property);
} else if (isDate && Date.class.isAssignableFrom(property.getClass())) {
return dateConverter.format((Date) property);
} else {
return property.toString();
}
}
}
@SuppressWarnings("unchecked")
@Override
public Object read(String s) {
if (isTemporalAccessor) {
return dateConverter.parse(s, (Class<? extends TemporalAccessor>) actualType);
} else { // must be date
return dateConverter.parse(s);
@SuppressWarnings("unchecked")
@Override
public Object read(String s) {
if (isTemporalAccessor) {
return dateConverter.parse(s, (Class<? extends TemporalAccessor>) actualType);
} else { // must be date
return dateConverter.parse(s);
}
}
}
};
};
}
}
}

View File

@ -1,134 +0,0 @@
package org.springframework.data.elasticsearch.core.convert;
import static org.assertj.core.api.Assertions.*;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.GregorianCalendar;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.data.elasticsearch.annotations.DateFormat;
/**
* @author Peter-Josef Meisch
* @author Tim te Beek
*/
class ElasticsearchDateConverterTests {
@ParameterizedTest // DATAES-716
@EnumSource(DateFormat.class)
void shouldCreateConvertersForAllKnownFormats(DateFormat dateFormat) {
if (dateFormat == DateFormat.none) {
return;
}
String pattern = (dateFormat != DateFormat.custom) ? dateFormat.name() : "dd.MM.uuuu";
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(pattern);
assertThat(converter).isNotNull();
}
@Test // DATAES-716
void shouldConvertTemporalAccessorToString() {
LocalDate localDate = LocalDate.of(2019, 12, 27);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.basic_date);
String formatted = converter.format(localDate);
assertThat(formatted).isEqualTo("20191227");
}
@Test // DATAES-716
void shouldParseTemporalAccessorFromString() {
LocalDate localDate = LocalDate.of(2019, 12, 27);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.basic_date);
LocalDate parsed = converter.parse("20191227", LocalDate.class);
assertThat(parsed).isEqualTo(localDate);
}
@Test // DATAES-792
void shouldConvertLegacyDateToString() {
GregorianCalendar calendar = GregorianCalendar
.from(ZonedDateTime.of(LocalDateTime.of(2020, 4, 19, 19, 44), ZoneId.of("UTC")));
Date legacyDate = calendar.getTime();
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.basic_date_time);
String formatted = converter.format(legacyDate);
assertThat(formatted).isEqualTo("20200419T194400.000Z");
}
@Test // DATAES-792
void shouldParseLegacyDateFromString() {
GregorianCalendar calendar = GregorianCalendar
.from(ZonedDateTime.of(LocalDateTime.of(2020, 4, 19, 19, 44), ZoneId.of("UTC")));
Date legacyDate = calendar.getTime();
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.basic_date_time);
Date parsed = converter.parse("20200419T194400.000Z");
assertThat(parsed).isEqualTo(legacyDate);
}
@Test // DATAES-847
void shouldParseEpochMillisString() {
Instant instant = Instant.ofEpochMilli(1234568901234L);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.epoch_millis);
Date parsed = converter.parse("1234568901234");
assertThat(parsed.toInstant()).isEqualTo(instant);
}
@Test // DATAES-847
void shouldConvertInstantToString() {
Instant instant = Instant.ofEpochMilli(1234568901234L);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.epoch_millis);
String formatted = converter.format(instant);
assertThat(formatted).isEqualTo("1234568901234");
}
@Test // DATAES-953
@DisplayName("should write and read Date with custom format")
void shouldWriteAndReadDateWithCustomFormat() {
// only seconds as the format string does not store millis
long currentTimeSeconds = System.currentTimeMillis() / 1_000;
Date date = new Date(currentTimeSeconds * 1_000);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of("uuuu-MM-dd HH:mm:ss");
String formatted = converter.format(date);
Date parsed = converter.parse(formatted);
assertThat(parsed).isEqualTo(date);
}
@Test // DATAES-953
@DisplayName("should write and read Instant with custom format")
void shouldWriteAndReadInstantWithCustomFormat() {
// only seconds as the format string does not store millis
long currentTimeSeconds = System.currentTimeMillis() / 1_000;
Instant instant = Instant.ofEpochSecond(currentTimeSeconds);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of("uuuu-MM-dd HH:mm:ss");
String formatted = converter.format(instant);
Instant parsed = converter.parse(formatted, Instant.class);
assertThat(parsed).isEqualTo(instant);
}
}

View File

@ -0,0 +1,411 @@
package org.springframework.data.elasticsearch.core.convert;
import static org.assertj.core.api.Assertions.*;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.GregorianCalendar;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.data.elasticsearch.annotations.DateFormat;
/**
* @author Peter-Josef Meisch
* @author Tim te Beek
*/
class ElasticsearchDateConverterUnitTests {
private ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Europe/Berlin"));
@ParameterizedTest // DATAES-716
@EnumSource(DateFormat.class)
void shouldCreateConvertersForAllKnownFormats(DateFormat dateFormat) {
switch (dateFormat) {
case none:
case weekyear:
case weekyear_week:
case weekyear_week_day:
return;
}
String pattern = (dateFormat != DateFormat.custom) ? dateFormat.name() : "dd.MM.uuuu";
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(pattern);
assertThat(converter).isNotNull();
}
@Test // DATAES-716
void shouldConvertTemporalAccessorToString() {
LocalDate localDate = LocalDate.of(2019, 12, 27);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.basic_date);
String formatted = converter.format(localDate);
assertThat(formatted).isEqualTo("20191227");
}
@Test // DATAES-716
void shouldParseTemporalAccessorFromString() {
LocalDate localDate = LocalDate.of(2019, 12, 27);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.basic_date);
LocalDate parsed = converter.parse("20191227", LocalDate.class);
assertThat(parsed).isEqualTo(localDate);
}
@Test // DATAES-792
void shouldConvertLegacyDateToString() {
GregorianCalendar calendar = GregorianCalendar
.from(ZonedDateTime.of(LocalDateTime.of(2020, 4, 19, 19, 44), ZoneId.of("UTC")));
Date legacyDate = calendar.getTime();
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.basic_date_time);
String formatted = converter.format(legacyDate);
assertThat(formatted).isEqualTo("20200419T194400.000Z");
}
@Test // DATAES-792
void shouldParseLegacyDateFromString() {
GregorianCalendar calendar = GregorianCalendar
.from(ZonedDateTime.of(LocalDateTime.of(2020, 4, 19, 19, 44), ZoneId.of("UTC")));
Date legacyDate = calendar.getTime();
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.basic_date_time);
Date parsed = converter.parse("20200419T194400.000Z");
assertThat(parsed).isEqualTo(legacyDate);
}
@Test // DATAES-847
void shouldParseEpochMillisString() {
Instant instant = Instant.ofEpochMilli(1234568901234L);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.epoch_millis);
Date parsed = converter.parse("1234568901234");
assertThat(parsed.toInstant()).isEqualTo(instant);
}
@Test // DATAES-847
void shouldConvertInstantToString() {
Instant instant = Instant.ofEpochMilli(1234568901234L);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.epoch_millis);
String formatted = converter.format(instant);
assertThat(formatted).isEqualTo("1234568901234");
}
@Test // DATAES-953
@DisplayName("should write and read Date with custom format")
void shouldWriteAndReadDateWithCustomFormat() {
// only seconds as the format string does not store millis
long currentTimeSeconds = System.currentTimeMillis() / 1_000;
Date date = new Date(currentTimeSeconds * 1_000);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of("uuuu-MM-dd HH:mm:ss");
String formatted = converter.format(date);
Date parsed = converter.parse(formatted);
assertThat(parsed).isEqualTo(date);
}
@Test // DATAES-953
@DisplayName("should write and read Instant with custom format")
void shouldWriteAndReadInstantWithCustomFormat() {
// only seconds as the format string does not store millis
long currentTimeSeconds = System.currentTimeMillis() / 1_000;
Instant instant = Instant.ofEpochSecond(currentTimeSeconds);
ElasticsearchDateConverter converter = ElasticsearchDateConverter.of("uuuu-MM-dd HH:mm:ss");
String formatted = converter.format(instant);
Instant parsed = converter.parse(formatted, Instant.class);
assertThat(parsed).isEqualTo(instant);
}
@Test // #1647
@DisplayName("should convert basic_date")
void shouldConvertBasicDate() {
check(ElasticsearchDateConverter.of(DateFormat.basic_date), LocalDate.class);
}
@Test // #1647
@DisplayName("should convert basic_date_time")
void shouldConvertBasicDateTime() {
check(ElasticsearchDateConverter.of(DateFormat.basic_date_time), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert basic_date_time_no_millis")
void shouldConvertBasicDateTimeNoMillis() {
check(ElasticsearchDateConverter.of(DateFormat.basic_date_time_no_millis), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert basic_ordinal_date")
void shouldConvertBasicOrdinalDate() {
check(ElasticsearchDateConverter.of(DateFormat.basic_ordinal_date), LocalDate.class);
}
@Test // #1647
@DisplayName("should convert basic_ordinal_date_time")
void shouldConvertBasicOrdinalDateTime() {
check(ElasticsearchDateConverter.of(DateFormat.basic_ordinal_date_time), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert basic_ordinal_date_time_no_millis")
void shouldConvertBasicOrdinalDateTimeNoMillis() {
check(ElasticsearchDateConverter.of(DateFormat.basic_ordinal_date_time_no_millis), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert basic_time")
void shouldConvertBasicTime() {
check(ElasticsearchDateConverter.of(DateFormat.basic_time), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert basic_time_no_millis")
void shouldConvertBasicTimeNoMillis() {
check(ElasticsearchDateConverter.of(DateFormat.basic_time_no_millis), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert basic_t_time")
void shouldConvertBasicTTime() {
check(ElasticsearchDateConverter.of(DateFormat.basic_t_time), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert basic_t_time_no_millis")
void shouldConvertBasicTTimeNoMillis() {
check(ElasticsearchDateConverter.of(DateFormat.basic_t_time_no_millis), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert basic_week_date")
void shouldConvertBasicWeekDate() {
check(ElasticsearchDateConverter.of(DateFormat.basic_week_date), LocalDate.class);
}
@Test // #1647
@DisplayName("should convert basic_week_date_time")
void shouldConvertBasicWeekDateTime() {
check(ElasticsearchDateConverter.of(DateFormat.basic_week_date_time), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert basic_week_date_time_no_millis")
void shouldConvertBasicWeekDateTimeNoMillis() {
check(ElasticsearchDateConverter.of(DateFormat.basic_week_date_time_no_millis), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert date")
void shouldConvertDate() {
check(ElasticsearchDateConverter.of(DateFormat.date), LocalDate.class);
}
@Test // #1647
@DisplayName("should convert date_hour")
void shouldConvertDateHour() {
check(ElasticsearchDateConverter.of(DateFormat.date_hour), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert date_hour_minute")
void shouldConvertDateHourMinute() {
check(ElasticsearchDateConverter.of(DateFormat.date_hour_minute), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert date_hour_minute_second")
void shouldConvertDateHourMinuteSecond() {
check(ElasticsearchDateConverter.of(DateFormat.date_hour_minute_second), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert date_hour_minute_second_fraction")
void shouldConvertDateHourMinuteSecondFraction() {
check(ElasticsearchDateConverter.of(DateFormat.date_hour_minute_second_fraction), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert date_hour_minute_second_millis")
void shouldConvertDateHourMinuteSecondMillis() {
check(ElasticsearchDateConverter.of(DateFormat.date_hour_minute_second_millis), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert date_optional_time")
void shouldConvertDateOptionalTime() {
check(ElasticsearchDateConverter.of(DateFormat.date_optional_time), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert date_time")
void shouldConvertDateTime() {
check(ElasticsearchDateConverter.of(DateFormat.date_time), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert date_time_no_millis")
void shouldConvertDateTimeNoMillis() {
check(ElasticsearchDateConverter.of(DateFormat.date_time_no_millis), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert epoch_millis")
void shouldConvertEpochMillis() {
check(ElasticsearchDateConverter.of(DateFormat.epoch_millis), Instant.class);
}
@Test // #1647
@DisplayName("should convert epoch_second")
void shouldConvertEpochSecond() {
check(ElasticsearchDateConverter.of(DateFormat.epoch_second), Instant.class);
}
@Test // #1647
@DisplayName("should convert hour")
void shouldConvertHour() {
check(ElasticsearchDateConverter.of(DateFormat.hour), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert hour_minute")
void shouldConvertHourMinute() {
check(ElasticsearchDateConverter.of(DateFormat.hour_minute), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert hour_minute_second")
void shouldConvertHourMinuteSecond() {
check(ElasticsearchDateConverter.of(DateFormat.hour_minute_second), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert hour_minute_second_fraction")
void shouldConvertHourMinuteSecondFraction() {
check(ElasticsearchDateConverter.of(DateFormat.hour_minute_second_fraction), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert hour_minute_second_millis")
void shouldConvertHourMinuteSecondMillis() {
check(ElasticsearchDateConverter.of(DateFormat.hour_minute_second_millis), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert ordinal_date")
void shouldConvertOrdinalDate() {
check(ElasticsearchDateConverter.of(DateFormat.ordinal_date), LocalDate.class);
}
@Test // #1647
@DisplayName("should convert ordinal_date_time")
void shouldConvertOrdinalDateTime() {
check(ElasticsearchDateConverter.of(DateFormat.ordinal_date_time), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert ordinal_date_time_no_millis")
void shouldConvertOrdinalDateTimeNoMillis() {
check(ElasticsearchDateConverter.of(DateFormat.ordinal_date_time_no_millis), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert time")
void shouldConvertTime() {
check(ElasticsearchDateConverter.of(DateFormat.time), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert time_no_millis")
void shouldConvertTimeNoMillis() {
check(ElasticsearchDateConverter.of(DateFormat.time_no_millis), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert t_time")
void shouldConvertTTime() {
check(ElasticsearchDateConverter.of(DateFormat.t_time), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert t_time_no_millis")
void shouldConvertTTimeNoMillis() {
check(ElasticsearchDateConverter.of(DateFormat.t_time_no_millis), LocalTime.class);
}
@Test // #1647
@DisplayName("should convert week_date")
void shouldConvertWeekDate() {
check(ElasticsearchDateConverter.of(DateFormat.week_date), LocalDate.class);
}
@Test // #1647
@DisplayName("should convert week_date_time")
void shouldConvertWeekDateTime() {
check(ElasticsearchDateConverter.of(DateFormat.week_date_time), LocalDateTime.class);
}
@Test // #1647
@DisplayName("should convert week_date_time_no_millis")
void shouldConvertWeekDateTimeNoMillis() {
check(ElasticsearchDateConverter.of(DateFormat.week_date_time_no_millis), LocalDate.class);
}
@Test // #1647
@DisplayName("should convert year")
void shouldConvertYear() {
check(ElasticsearchDateConverter.of(DateFormat.year), Year.class);
}
@Test // #1647
@DisplayName("should convert year_month")
void shouldConvertYearMonth() {
check(ElasticsearchDateConverter.of(DateFormat.year_month), YearMonth.class);
}
@Test // #1647
@DisplayName("should convert year_month_day")
void shouldConvertYearMonthDay() {
check(ElasticsearchDateConverter.of(DateFormat.year_month_day), LocalDate.class);
}
@Test // #1647
@DisplayName("should convert with combined patterns")
void shouldConvertWithCombinedPatterns() {
check(ElasticsearchDateConverter.of("basic_date_time ||invalid-pattern"), LocalDateTime.class);
}
private <T extends TemporalAccessor> void check(ElasticsearchDateConverter converter, Class<T> type) {
String formatted = converter.format(zdt);
T parsed = converter.parse(formatted, type);
assertThat(parsed).isNotNull();
}
}