Skip to content

Commit 31d5967

Browse files
authored
Remove use of SPI locale for JDK 23+ (#113182)
On JDK 23 we're just going with what CLDR specifies for week-date calculations - the built-in locales are available for ISO weekdate uses.
1 parent 3028046 commit 31d5967

File tree

6 files changed

+44
-50
lines changed

6 files changed

+44
-50
lines changed

build-tools-internal/src/main/groovy/elasticsearch.ide.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ if (providers.systemProperty('idea.active').getOrNull() == 'true') {
168168
vmParameters = [
169169
'-ea',
170170
'-Djava.security.manager=allow',
171-
'-Djava.locale.providers=SPI,CLDR',
171+
'-Djava.locale.providers=CLDR',
172172
'-Des.nativelibs.path="' + testLibraryPath + '"',
173173
// TODO: only open these for mockito when it is modularized
174174
'--add-opens=java.base/java.security.cert=ALL-UNNAMED',

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public void execute(Task t) {
9393
mkdirs(test.getWorkingDir().toPath().resolve("temp").toFile());
9494

9595
// TODO remove once jvm.options are added to test system properties
96-
test.systemProperty("java.locale.providers", "SPI,CLDR");
96+
test.systemProperty("java.locale.providers", "CLDR");
9797
}
9898
});
9999
test.getJvmArgumentProviders().add(nonInputProperties);

distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@ static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, St
7676
@UpdateForV9 // only use CLDR in v9+
7777
private static String getLocaleProviders() {
7878
/*
79-
* Specify SPI to load IsoCalendarDataProvider (see #48209), specifying the first day of week as Monday.
80-
* When on pre-23, use COMPAT instead to maintain existing date formats as much as we can.
81-
* When on JDK 23+, use the default CLDR locale database, as COMPAT was removed in JDK 23.
79+
* When on pre-23, use COMPAT instead to maintain existing date formats as much as we can,
80+
* and specify SPI to load IsoCalendarDataProvider (see #48209), specifying the first day of week as Monday.
81+
* When on JDK 23+, use the default CLDR locale database for everything, as COMPAT was removed in JDK 23.
8282
*/
83-
return Runtime.version().feature() >= 23 ? "SPI,CLDR" : "SPI,COMPAT";
83+
return Runtime.version().feature() >= 23 ? "CLDR" : "SPI,COMPAT";
8484
}
8585

8686
/*

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,17 @@ public void testParseJavaDefaultYear() {
7575
public void testParseWeekBasedYearAndWeek() {
7676
String format = "YYYY-ww";
7777
ZoneId timezone = DateUtils.of("Europe/Amsterdam");
78-
Function<String, ZonedDateTime> javaFunction = DateFormat.Java.getFunction(format, timezone, Locale.ROOT);
78+
Function<String, ZonedDateTime> javaFunction = DateFormat.Java.getFunction(format, timezone, Locale.ENGLISH);
7979
ZonedDateTime dateTime = javaFunction.apply("2020-33");
80-
assertThat(dateTime, equalTo(ZonedDateTime.of(2020, 8, 10, 0, 0, 0, 0, timezone)));
80+
assertThat(dateTime, equalTo(ZonedDateTime.of(2020, 8, 9, 0, 0, 0, 0, timezone)));
8181
}
8282

8383
public void testParseWeekBasedYear() {
8484
String format = "YYYY";
8585
ZoneId timezone = DateUtils.of("Europe/Amsterdam");
86-
Function<String, ZonedDateTime> javaFunction = DateFormat.Java.getFunction(format, timezone, Locale.ROOT);
86+
Function<String, ZonedDateTime> javaFunction = DateFormat.Java.getFunction(format, timezone, Locale.ENGLISH);
8787
ZonedDateTime dateTime = javaFunction.apply("2019");
88-
assertThat(dateTime, equalTo(ZonedDateTime.of(2018, 12, 31, 0, 0, 0, 0, timezone)));
88+
assertThat(dateTime, equalTo(ZonedDateTime.of(2018, 12, 30, 0, 0, 0, 0, timezone)));
8989
}
9090

9191
public void testParseWeekBasedWithLocale() {

server/src/test/java/org/elasticsearch/common/time/DateFormattersTests.java

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.time.format.DateTimeFormatter;
2626
import java.time.format.DateTimeParseException;
2727
import java.time.temporal.ChronoField;
28+
import java.time.temporal.ChronoUnit;
2829
import java.time.temporal.TemporalAccessor;
2930
import java.util.List;
3031
import java.util.Locale;
@@ -85,38 +86,32 @@ private void assertDateMathEquals(String text, String expected, String pattern)
8586
}
8687

8788
private void assertDateMathEquals(String text, String expected, String pattern, Locale locale) {
88-
long gotMillisJava = dateMathToMillis(text, DateFormatter.forPattern(pattern), locale);
89-
long expectedMillis = DateFormatters.from(DateFormatter.forPattern("strict_date_optional_time").withLocale(locale).parse(expected))
90-
.toInstant()
91-
.toEpochMilli();
89+
Instant gotInstant = dateMathToInstant(text, DateFormatter.forPattern(pattern), locale).truncatedTo(ChronoUnit.MILLIS);
90+
Instant expectedInstant = DateFormatters.from(
91+
DateFormatter.forPattern("strict_date_optional_time").withLocale(locale).parse(expected)
92+
).toInstant().truncatedTo(ChronoUnit.MILLIS);
9293

93-
assertThat(gotMillisJava, equalTo(expectedMillis));
94+
assertThat(gotInstant, equalTo(expectedInstant));
9495
}
9596

9697
public void testWeekBasedDates() {
97-
// as per WeekFields.ISO first week starts on Monday and has minimum 4 days
98+
// the years and weeks this outputs depends on where the first day of the first week is for each year
9899
DateFormatter dateFormatter = DateFormatters.forPattern("YYYY-ww");
99100

100-
// first week of 2016 starts on Monday 2016-01-04 as previous week in 2016 has only 3 days
101101
assertThat(
102-
DateFormatters.from(dateFormatter.parse("2016-01")),
103-
equalTo(ZonedDateTime.of(2016, 01, 04, 0, 0, 0, 0, ZoneOffset.UTC))
102+
DateFormatters.from(dateFormatter.parse("2016-02")),
103+
equalTo(ZonedDateTime.of(2016, 01, 03, 0, 0, 0, 0, ZoneOffset.UTC))
104104
);
105105

106-
// first week of 2015 starts on Monday 2014-12-29 because 4days belong to 2019
107106
assertThat(
108-
DateFormatters.from(dateFormatter.parse("2015-01")),
109-
equalTo(ZonedDateTime.of(2014, 12, 29, 0, 0, 0, 0, ZoneOffset.UTC))
107+
DateFormatters.from(dateFormatter.parse("2015-02")),
108+
equalTo(ZonedDateTime.of(2015, 01, 04, 0, 0, 0, 0, ZoneOffset.UTC))
110109
);
111110

112-
// as per WeekFields.ISO first week starts on Monday and has minimum 4 days
113111
dateFormatter = DateFormatters.forPattern("YYYY");
114112

115-
// first week of 2016 starts on Monday 2016-01-04 as previous week in 2016 has only 3 days
116-
assertThat(DateFormatters.from(dateFormatter.parse("2016")), equalTo(ZonedDateTime.of(2016, 01, 04, 0, 0, 0, 0, ZoneOffset.UTC)));
117-
118-
// first week of 2015 starts on Monday 2014-12-29 because 4days belong to 2019
119-
assertThat(DateFormatters.from(dateFormatter.parse("2015")), equalTo(ZonedDateTime.of(2014, 12, 29, 0, 0, 0, 0, ZoneOffset.UTC)));
113+
assertThat(DateFormatters.from(dateFormatter.parse("2016")), equalTo(ZonedDateTime.of(2015, 12, 27, 0, 0, 0, 0, ZoneOffset.UTC)));
114+
assertThat(DateFormatters.from(dateFormatter.parse("2015")), equalTo(ZonedDateTime.of(2014, 12, 28, 0, 0, 0, 0, ZoneOffset.UTC)));
120115
}
121116

122117
public void testEpochMillisParser() {
@@ -600,8 +595,8 @@ public void testYearWithoutMonthRoundUp() {
600595
assertDateMathEquals("1500", "1500-01-01T23:59:59.999", "uuuu");
601596
assertDateMathEquals("2022", "2022-01-01T23:59:59.999", "uuuu");
602597
assertDateMathEquals("2022", "2022-01-01T23:59:59.999", "yyyy");
603-
// cannot reliably default week based years due to locale changing. See JavaDateFormatter javadocs
604-
assertDateMathEquals("2022", "2022-01-03T23:59:59.999", "YYYY", Locale.ROOT);
598+
// weird locales can change this to epoch-based
599+
assertDateMathEquals("2022", "2021-12-26T23:59:59.999", "YYYY", Locale.ROOT);
605600
}
606601

607602
private void assertRoundupFormatter(String format, String input, long expectedMilliSeconds) {
@@ -789,30 +784,28 @@ public void testExceptionWhenCompositeParsingFailsDateMath() {
789784
String text = "2014-06-06T12:01:02.123";
790785
ElasticsearchParseException e1 = expectThrows(
791786
ElasticsearchParseException.class,
792-
() -> dateMathToMillis(text, DateFormatter.forPattern(pattern), randomLocale(random()))
787+
() -> dateMathToInstant(text, DateFormatter.forPattern(pattern), randomLocale(random()))
793788
);
794789
assertThat(e1.getMessage(), containsString(pattern));
795790
assertThat(e1.getMessage(), containsString(text));
796791
}
797792

798-
private long dateMathToMillis(String text, DateFormatter dateFormatter, Locale locale) {
793+
private Instant dateMathToInstant(String text, DateFormatter dateFormatter, Locale locale) {
799794
DateFormatter javaFormatter = dateFormatter.withLocale(locale);
800795
DateMathParser javaDateMath = javaFormatter.toDateMathParser();
801-
return javaDateMath.parse(text, () -> 0, true, (ZoneId) null).toEpochMilli();
796+
return javaDateMath.parse(text, () -> 0, true, null);
802797
}
803798

804799
public void testDayOfWeek() {
805-
// 7 (ok joda) vs 1 (java by default) but 7 with customized org.elasticsearch.common.time.IsoLocale.ISO8601
806800
ZonedDateTime now = LocalDateTime.of(2009, 11, 15, 1, 32, 8, 328402).atZone(ZoneOffset.UTC); // Sunday
807801
DateFormatter javaFormatter = DateFormatter.forPattern("8e").withZone(ZoneOffset.UTC);
808-
assertThat(javaFormatter.format(now), equalTo("7"));
802+
assertThat(javaFormatter.format(now), equalTo("1"));
809803
}
810804

811805
public void testStartOfWeek() {
812-
// 2019-21 (ok joda) vs 2019-22 (java by default) but 2019-21 with customized org.elasticsearch.common.time.IsoLocale.ISO8601
813806
ZonedDateTime now = LocalDateTime.of(2019, 5, 26, 1, 32, 8, 328402).atZone(ZoneOffset.UTC);
814807
DateFormatter javaFormatter = DateFormatter.forPattern("8YYYY-ww").withZone(ZoneOffset.UTC);
815-
assertThat(javaFormatter.format(now), equalTo("2019-21"));
808+
assertThat(javaFormatter.format(now), equalTo("2019-22"));
816809
}
817810

818811
// these parsers should allow both ',' and '.' as a decimal point

server/src/test/java/org/elasticsearch/common/time/JavaDateMathParserTests.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.time.ZonedDateTime;
2020
import java.time.format.DateTimeFormatterBuilder;
2121
import java.time.format.ResolverStyle;
22+
import java.time.temporal.ChronoUnit;
2223
import java.util.Locale;
2324
import java.util.concurrent.atomic.AtomicBoolean;
2425
import java.util.function.LongSupplier;
@@ -82,12 +83,12 @@ public void testOverridingLocaleOrZoneAndCompositeRoundUpParser() {
8283
// the pattern has to be composite and the match should not be on the first one
8384
DateFormatter formatter = DateFormatter.forPattern("date||epoch_millis").withLocale(randomLocale(random()));
8485
DateMathParser parser = formatter.toDateMathParser();
85-
long gotMillis = parser.parse("297276785531", () -> 0, true, (ZoneId) null).toEpochMilli();
86+
Instant gotMillis = parser.parse("297276785531", () -> 0, true, null).truncatedTo(ChronoUnit.MILLIS);
8687
assertDateEquals(gotMillis, "297276785531", "297276785531");
8788

8889
formatter = DateFormatter.forPattern("date||epoch_millis").withZone(ZoneOffset.UTC);
8990
parser = formatter.toDateMathParser();
90-
gotMillis = parser.parse("297276785531", () -> 0, true, (ZoneId) null).toEpochMilli();
91+
gotMillis = parser.parse("297276785531", () -> 0, true, null).truncatedTo(ChronoUnit.MILLIS);
9192
assertDateEquals(gotMillis, "297276785531", "297276785531");
9293
}
9394

@@ -99,7 +100,7 @@ public void testWeekBasedDate() {
99100
// defaulting missing day of week
100101
formatter = DateFormatter.forPattern("YYYY'W'ww[e]");// YYYY'W'wwe
101102
// second week of 2022 is starting on Monday 10th Jan
102-
assertDateMathEquals(formatter.toDateMathParser(), "2022W02", "2022-01-10T23:59:59.999Z", 0, true, ZoneOffset.UTC);
103+
assertDateMathEquals(formatter.toDateMathParser(), "2022W02", "2022-01-02T23:59:59.999Z", 0, true, ZoneOffset.UTC);
103104
}
104105

105106
public void testDayOfYear() {
@@ -125,16 +126,16 @@ public void testAMPMWithTimeMissing() {
125126

126127
public void testWeekDates() {
127128
DateFormatter formatter = DateFormatter.forPattern("YYYY-ww");
128-
assertDateMathEquals(formatter.toDateMathParser(), "2016-01", "2016-01-04T23:59:59.999Z", 0, true, ZoneOffset.UTC);
129+
assertDateMathEquals(formatter.toDateMathParser(), "2016-02", "2016-01-03T23:59:59.999Z", 0, true, ZoneOffset.UTC);
129130

130131
formatter = DateFormatter.forPattern("YYYY");
131-
assertDateMathEquals(formatter.toDateMathParser(), "2016", "2016-01-04T23:59:59.999Z", 0, true, ZoneOffset.UTC);
132+
assertDateMathEquals(formatter.toDateMathParser(), "2016", "2015-12-27T23:59:59.999Z", 0, true, ZoneOffset.UTC);
132133

133134
formatter = DateFormatter.forPattern("YYYY-ww");
134-
assertDateMathEquals(formatter.toDateMathParser(), "2015-01", "2014-12-29T23:59:59.999Z", 0, true, ZoneOffset.UTC);
135+
assertDateMathEquals(formatter.toDateMathParser(), "2015-02", "2015-01-04T23:59:59.999Z", 0, true, ZoneOffset.UTC);
135136

136137
formatter = DateFormatter.forPattern("YYYY");
137-
assertDateMathEquals(formatter.toDateMathParser(), "2015", "2014-12-29T23:59:59.999Z", 0, true, ZoneOffset.UTC);
138+
assertDateMathEquals(formatter.toDateMathParser(), "2015", "2014-12-28T23:59:59.999Z", 0, true, ZoneOffset.UTC);
138139
}
139140

140141
public void testBasicDates() {
@@ -343,7 +344,7 @@ public void testTimestamps() {
343344

344345
// also check other time units
345346
DateMathParser parser = DateFormatter.forPattern("epoch_second||date_optional_time").toDateMathParser();
346-
long datetime = parser.parse("1418248078", () -> 0).toEpochMilli();
347+
Instant datetime = parser.parse("1418248078", () -> 0).truncatedTo(ChronoUnit.MILLIS);
347348
assertDateEquals(datetime, "1418248078", "2014-12-10T21:47:58.000");
348349

349350
// for date_optional_time a timestamp with more than 9digits is epoch
@@ -401,14 +402,14 @@ private void assertDateMathEquals(
401402
boolean roundUp,
402403
ZoneId timeZone
403404
) {
404-
long gotMillis = parser.parse(toTest, () -> now, roundUp, timeZone).toEpochMilli();
405+
Instant gotMillis = parser.parse(toTest, () -> now, roundUp, timeZone).truncatedTo(ChronoUnit.MILLIS);
405406
assertDateEquals(gotMillis, toTest, expected);
406407
}
407408

408-
private void assertDateEquals(long gotMillis, String original, String expected) {
409-
long expectedMillis = parser.parse(expected, () -> 0).toEpochMilli();
410-
if (gotMillis != expectedMillis) {
411-
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(gotMillis), ZoneOffset.UTC);
409+
private void assertDateEquals(Instant gotMillis, String original, String expected) {
410+
Instant expectedMillis = parser.parse(expected, () -> 0).truncatedTo(ChronoUnit.MILLIS);
411+
if (gotMillis.equals(expectedMillis) == false) {
412+
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(gotMillis, ZoneOffset.UTC);
412413
fail(Strings.format("""
413414
Date math not equal
414415
Original : %s

0 commit comments

Comments
 (0)