Skip to content

Commit a87ae91

Browse files
spinscalerjernst
authored andcommitted
Core: Backport java time date formatters (#31997)
* Date: Add DateFormatters class that uses java.time (#31856) A newly added class called DateFormatters now contains java.time based builders for dates, which also intends to be fully backwards compatible, when the name based date formatters are picked. Also a new class named CompoundDateTimeFormatter for being able to parse multiple different formats has been added. A duelling test class has been added that ensures the same dates when parsing java or joda time formatted dates for the name based dates. Note, that java.time and joda time are not fully backwards compatible, which also means that old formats will currently not work with this setup. * Tests: Remove use of joda time in some tests (#31922) This also extends the dateformatters test to ensure that the printers are acting the same in java time and joda time. * Tests: Fix SearchFieldsIT.testDocValueFields This test produced different implementations of joda time classes, depending on if the data was serialized or not (DateTime vs MutableDateTime). This now uses a common base class to extract the milliseconds from the data. Closes #31992
1 parent 0382984 commit a87ae91

File tree

20 files changed

+1767
-99
lines changed

20 files changed

+1767
-99
lines changed

modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/DateProcessorTests.java

+19-16
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@
2424
import org.elasticsearch.ingest.TestTemplateService;
2525
import org.elasticsearch.script.TemplateScript;
2626
import org.elasticsearch.test.ESTestCase;
27-
import org.joda.time.DateTime;
28-
import org.joda.time.DateTimeZone;
2927

28+
import java.time.ZoneId;
29+
import java.time.ZoneOffset;
30+
import java.time.ZonedDateTime;
3031
import java.util.ArrayList;
3132
import java.util.Collections;
3233
import java.util.HashMap;
@@ -36,19 +37,21 @@
3637

3738
import static org.hamcrest.CoreMatchers.containsString;
3839
import static org.hamcrest.CoreMatchers.equalTo;
39-
import static org.joda.time.DateTimeZone.UTC;
4040

4141
public class DateProcessorTests extends ESTestCase {
42+
4243
private TemplateScript.Factory templatize(Locale locale) {
4344
return new TestTemplateService.MockTemplateScript.Factory(locale.getLanguage());
4445
}
4546

46-
private TemplateScript.Factory templatize(DateTimeZone timezone) {
47-
return new TestTemplateService.MockTemplateScript.Factory(timezone.getID());
47+
private TemplateScript.Factory templatize(ZoneId timezone) {
48+
// prevent writing "UTC" as string, as joda time does not parse it
49+
String id = timezone.equals(ZoneOffset.UTC) ? "UTC" : timezone.getId();
50+
return new TestTemplateService.MockTemplateScript.Factory(id);
4851
}
4952
public void testJodaPattern() {
5053
DateProcessor dateProcessor = new DateProcessor(randomAlphaOfLength(10),
51-
templatize(DateTimeZone.forID("Europe/Amsterdam")), templatize(Locale.ENGLISH),
54+
templatize(ZoneId.of("Europe/Amsterdam")), templatize(Locale.ENGLISH),
5255
"date_as_string", Collections.singletonList("yyyy dd MM hh:mm:ss"), "date_as_date");
5356
Map<String, Object> document = new HashMap<>();
5457
document.put("date_as_string", "2010 12 06 11:05:15");
@@ -63,7 +66,7 @@ public void testJodaPatternMultipleFormats() {
6366
matchFormats.add("dd/MM/yyyy");
6467
matchFormats.add("dd-MM-yyyy");
6568
DateProcessor dateProcessor = new DateProcessor(randomAlphaOfLength(10),
66-
templatize(DateTimeZone.forID("Europe/Amsterdam")), templatize(Locale.ENGLISH),
69+
templatize(ZoneId.of("Europe/Amsterdam")), templatize(Locale.ENGLISH),
6770
"date_as_string", matchFormats, "date_as_date");
6871

6972
Map<String, Object> document = new HashMap<>();
@@ -98,7 +101,7 @@ public void testJodaPatternMultipleFormats() {
98101
public void testInvalidJodaPattern() {
99102
try {
100103
DateProcessor processor = new DateProcessor(randomAlphaOfLength(10),
101-
templatize(UTC), templatize(randomLocale(random())),
104+
templatize(ZoneOffset.UTC), templatize(randomLocale(random())),
102105
"date_as_string", Collections.singletonList("invalid pattern"), "date_as_date");
103106
Map<String, Object> document = new HashMap<>();
104107
document.put("date_as_string", "2010");
@@ -112,7 +115,7 @@ public void testInvalidJodaPattern() {
112115

113116
public void testJodaPatternLocale() {
114117
DateProcessor dateProcessor = new DateProcessor(randomAlphaOfLength(10),
115-
templatize(DateTimeZone.forID("Europe/Amsterdam")), templatize(Locale.ITALIAN),
118+
templatize(ZoneId.of("Europe/Amsterdam")), templatize(Locale.ITALIAN),
116119
"date_as_string", Collections.singletonList("yyyy dd MMM"), "date_as_date");
117120
Map<String, Object> document = new HashMap<>();
118121
document.put("date_as_string", "2010 12 giugno");
@@ -123,18 +126,18 @@ public void testJodaPatternLocale() {
123126

124127
public void testJodaPatternDefaultYear() {
125128
DateProcessor dateProcessor = new DateProcessor(randomAlphaOfLength(10),
126-
templatize(DateTimeZone.forID("Europe/Amsterdam")), templatize(Locale.ENGLISH),
129+
templatize(ZoneId.of("Europe/Amsterdam")), templatize(Locale.ENGLISH),
127130
"date_as_string", Collections.singletonList("dd/MM"), "date_as_date");
128131
Map<String, Object> document = new HashMap<>();
129132
document.put("date_as_string", "12/06");
130133
IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document);
131134
dateProcessor.execute(ingestDocument);
132135
assertThat(ingestDocument.getFieldValue("date_as_date", String.class),
133-
equalTo(DateTime.now().getYear() + "-06-12T00:00:00.000+02:00"));
136+
equalTo(ZonedDateTime.now().getYear() + "-06-12T00:00:00.000+02:00"));
134137
}
135138

136139
public void testTAI64N() {
137-
DateProcessor dateProcessor = new DateProcessor(randomAlphaOfLength(10), templatize(DateTimeZone.forOffsetHours(2)),
140+
DateProcessor dateProcessor = new DateProcessor(randomAlphaOfLength(10), templatize(ZoneOffset.ofHours(2)),
138141
templatize(randomLocale(random())),
139142
"date_as_string", Collections.singletonList("TAI64N"), "date_as_date");
140143
Map<String, Object> document = new HashMap<>();
@@ -146,8 +149,8 @@ public void testTAI64N() {
146149
}
147150

148151
public void testUnixMs() {
149-
DateProcessor dateProcessor = new DateProcessor(randomAlphaOfLength(10), templatize(UTC), templatize(randomLocale(random())),
150-
"date_as_string", Collections.singletonList("UNIX_MS"), "date_as_date");
152+
DateProcessor dateProcessor = new DateProcessor(randomAlphaOfLength(10), templatize(ZoneOffset.UTC),
153+
templatize(randomLocale(random())), "date_as_string", Collections.singletonList("UNIX_MS"), "date_as_date");
151154
Map<String, Object> document = new HashMap<>();
152155
document.put("date_as_string", "1000500");
153156
IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document);
@@ -162,7 +165,7 @@ public void testUnixMs() {
162165
}
163166

164167
public void testUnix() {
165-
DateProcessor dateProcessor = new DateProcessor(randomAlphaOfLength(10), templatize(UTC),
168+
DateProcessor dateProcessor = new DateProcessor(randomAlphaOfLength(10), templatize(ZoneOffset.UTC),
166169
templatize(randomLocale(random())),
167170
"date_as_string", Collections.singletonList("UNIX"), "date_as_date");
168171
Map<String, Object> document = new HashMap<>();
@@ -186,7 +189,7 @@ public void testInvalidTimezone() {
186189

187190
public void testInvalidLocale() {
188191
DateProcessor processor = new DateProcessor(randomAlphaOfLength(10),
189-
templatize(UTC), new TestTemplateService.MockTemplateScript.Factory("invalid_locale"),
192+
templatize(ZoneOffset.UTC), new TestTemplateService.MockTemplateScript.Factory("invalid_locale"),
190193
"date_as_string", Collections.singletonList("yyyy"), "date_as_date");
191194
Map<String, Object> document = new HashMap<>();
192195
document.put("date_as_string", "2010");

server/src/main/java/org/elasticsearch/cluster/metadata/IndexMetaData.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import com.carrotsearch.hppc.cursors.IntObjectCursor;
2424
import com.carrotsearch.hppc.cursors.ObjectCursor;
2525
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
26-
2726
import org.elasticsearch.Version;
2827
import org.elasticsearch.action.admin.indices.rollover.RolloverInfo;
2928
import org.elasticsearch.action.support.ActiveShardCount;
@@ -56,10 +55,11 @@
5655
import org.elasticsearch.index.mapper.MapperService;
5756
import org.elasticsearch.index.shard.ShardId;
5857
import org.elasticsearch.rest.RestStatus;
59-
import org.joda.time.DateTime;
60-
import org.joda.time.DateTimeZone;
6158

6259
import java.io.IOException;
60+
import java.time.Instant;
61+
import java.time.ZoneOffset;
62+
import java.time.ZonedDateTime;
6363
import java.util.Arrays;
6464
import java.util.Collections;
6565
import java.util.EnumSet;
@@ -1346,7 +1346,7 @@ public static Settings addHumanReadableSettings(Settings settings) {
13461346
}
13471347
Long creationDate = settings.getAsLong(SETTING_CREATION_DATE, null);
13481348
if (creationDate != null) {
1349-
DateTime creationDateTime = new DateTime(creationDate, DateTimeZone.UTC);
1349+
ZonedDateTime creationDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(creationDate), ZoneOffset.UTC);
13501350
builder.put(SETTING_CREATION_DATE_STRING, creationDateTime.toString());
13511351
}
13521352
return builder.build();

server/src/main/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexService.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,10 @@
7575
import org.elasticsearch.indices.InvalidIndexNameException;
7676
import org.elasticsearch.indices.cluster.IndicesClusterStateService.AllocatedIndices.IndexRemovalReason;
7777
import org.elasticsearch.threadpool.ThreadPool;
78-
import org.joda.time.DateTime;
79-
import org.joda.time.DateTimeZone;
8078

8179
import java.io.UnsupportedEncodingException;
8280
import java.nio.file.Path;
81+
import java.time.Instant;
8382
import java.util.ArrayList;
8483
import java.util.Collections;
8584
import java.util.HashMap;
@@ -390,7 +389,7 @@ public ClusterState execute(ClusterState currentState) throws Exception {
390389
}
391390

392391
if (indexSettingsBuilder.get(SETTING_CREATION_DATE) == null) {
393-
indexSettingsBuilder.put(SETTING_CREATION_DATE, new DateTime(DateTimeZone.UTC).getMillis());
392+
indexSettingsBuilder.put(SETTING_CREATION_DATE, Instant.now().toEpochMilli());
394393
}
395394
indexSettingsBuilder.put(IndexMetaData.SETTING_INDEX_PROVIDED_NAME, request.getProvidedName());
396395
indexSettingsBuilder.put(SETTING_INDEX_UUID, UUIDs.randomBase64UUID());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.common.time;
20+
21+
import java.time.ZoneId;
22+
import java.time.format.DateTimeFormatter;
23+
import java.time.format.DateTimeParseException;
24+
import java.time.temporal.TemporalAccessor;
25+
26+
/**
27+
* wrapper class around java.time.DateTimeFormatter that supports multiple formats for easier parsing,
28+
* and one specific format for printing
29+
*/
30+
public class CompoundDateTimeFormatter {
31+
32+
final DateTimeFormatter printer;
33+
final DateTimeFormatter[] parsers;
34+
35+
CompoundDateTimeFormatter(DateTimeFormatter ... parsers) {
36+
if (parsers.length == 0) {
37+
throw new IllegalArgumentException("at least one date time formatter is required");
38+
}
39+
this.printer = parsers[0];
40+
this.parsers = parsers;
41+
}
42+
43+
public TemporalAccessor parse(String input) {
44+
DateTimeParseException failure = null;
45+
for (int i = 0; i < parsers.length; i++) {
46+
try {
47+
return parsers[i].parse(input);
48+
} catch (DateTimeParseException e) {
49+
if (failure == null) {
50+
failure = e;
51+
} else {
52+
failure.addSuppressed(e);
53+
}
54+
}
55+
}
56+
57+
// ensure that all parsers exceptions are returned instead of only the last one
58+
throw failure;
59+
}
60+
61+
public CompoundDateTimeFormatter withZone(ZoneId zoneId) {
62+
final DateTimeFormatter[] parsersWithZone = new DateTimeFormatter[parsers.length];
63+
for (int i = 0; i < parsers.length; i++) {
64+
parsersWithZone[i] = parsers[i].withZone(zoneId);
65+
}
66+
67+
return new CompoundDateTimeFormatter(parsersWithZone);
68+
}
69+
70+
public String format(TemporalAccessor accessor) {
71+
return printer.format(accessor);
72+
}
73+
}

0 commit comments

Comments
 (0)