mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-05-10 10:28:55 +00:00
Use own implementation of date formatters.
Original PR #1650 Closes #1647
This commit is contained in:
parent
e311df98c0
commit
c6713fad5f
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user