Skip to content

Commit

Permalink
Always use CLDR locale on ES v9 (elastic#113184)
Browse files Browse the repository at this point in the history
Regardless of JDK version, ES should always use CLDR locale database from 9.0.0.
This also removes IsoCalendarDataProvider used to override week-date calculations for the root locale only.
  • Loading branch information
thecoop authored Sep 23, 2024
1 parent 9da39f0 commit f9aa6f4
Show file tree
Hide file tree
Showing 16 changed files with 51 additions and 180 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ if (providers.systemProperty('idea.active').getOrNull() == 'true') {
vmParameters = [
'-ea',
'-Djava.security.manager=allow',
'-Djava.locale.providers=SPI,CLDR',
'-Djava.locale.providers=CLDR',
'-Des.nativelibs.path="' + testLibraryPath + '"',
// TODO: only open these for mockito when it is modularized
'--add-opens=java.base/java.security.cert=ALL-UNNAMED',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void execute(Task t) {
mkdirs(test.getWorkingDir().toPath().resolve("temp").toFile());

// TODO remove once jvm.options are added to test system properties
test.systemProperty("java.locale.providers", "SPI,CLDR");
test.systemProperty("java.locale.providers", "CLDR");
}
});
test.getJvmArgumentProviders().add(nonInputProperties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.core.UpdateForV9;

import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -61,7 +60,7 @@ static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, St
"-Dlog4j.shutdownHookEnabled=false",
"-Dlog4j2.disable.jmx=true",
"-Dlog4j2.formatMsgNoLookups=true",
"-Djava.locale.providers=" + getLocaleProviders(),
"-Djava.locale.providers=CLDR",
maybeEnableNativeAccess(),
maybeOverrideDockerCgroup(distroType),
maybeSetActiveProcessorCount(nodeSettings),
Expand All @@ -73,16 +72,6 @@ static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, St
).filter(e -> e.isEmpty() == false).collect(Collectors.toList());
}

@UpdateForV9 // only use CLDR in v9+
private static String getLocaleProviders() {
/*
* Specify SPI to load IsoCalendarDataProvider (see #48209), specifying the first day of week as Monday.
* When on pre-23, use COMPAT instead to maintain existing date formats as much as we can.
* When on JDK 23+, use the default CLDR locale database, as COMPAT was removed in JDK 23.
*/
return Runtime.version().feature() >= 23 ? "SPI,CLDR" : "SPI,COMPAT";
}

/*
* The virtual file /proc/self/cgroup should list the current cgroup
* membership. For each hierarchy, you can follow the cgroup path from
Expand Down
4 changes: 4 additions & 0 deletions modules/aggregations/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ dependencies {
compileOnly(project(':modules:lang-painless:spi'))
clusterModules(project(':modules:lang-painless'))
}

tasks.named("yamlRestCompatTestTransform").configure({ task ->
task.skipTest("aggregations/date_agg_per_day_of_week/Date aggregartion per day of week", "week-date behaviour has changed")
})

This file was deleted.

4 changes: 4 additions & 0 deletions modules/ingest-common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ tasks.named("thirdPartyAudit").configure {
'org.apache.commons.logging.LogFactory',
)
}

tasks.named("yamlRestCompatTestTransform").configure({ task ->
task.skipTest("ingest/30_date_processor/Test week based date parsing", "week-date behaviour has changed")
})
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,17 @@ public void testParseJavaDefaultYear() {
public void testParseWeekBasedYearAndWeek() {
String format = "YYYY-ww";
ZoneId timezone = DateUtils.of("Europe/Amsterdam");
Function<String, ZonedDateTime> javaFunction = DateFormat.Java.getFunction(format, timezone, Locale.ROOT);
Function<String, ZonedDateTime> javaFunction = DateFormat.Java.getFunction(format, timezone, Locale.ENGLISH);
ZonedDateTime dateTime = javaFunction.apply("2020-33");
assertThat(dateTime, equalTo(ZonedDateTime.of(2020, 8, 10, 0, 0, 0, 0, timezone)));
assertThat(dateTime, equalTo(ZonedDateTime.of(2020, 8, 9, 0, 0, 0, 0, timezone)));
}

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

public void testParseWeekBasedWithLocale() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ teardown:
}
- length: { docs: 1 }
- match: { docs.0.doc._source.date_source_field: "2020-33" }
- match: { docs.0.doc._source.date_target_field: "2020-08-10T00:00:00.000Z" }
- match: { docs.0.doc._source.date_target_field: "2020-08-09T00:00:00.000Z" }
- length: { docs.0.doc._ingest: 1 }
- is_true: docs.0.doc._ingest.timestamp

Expand All @@ -262,7 +262,7 @@ teardown:
index: test
id: "1"
- match: { _source.date_source_field: "2020-33" }
- match: { _source.date_target_field: "2020-08-10T00:00:00.000Z" }
- match: { _source.date_target_field: "2020-08-09T00:00:00.000Z" }

---
"Test week based date parsing with locale":
Expand Down
1 change: 0 additions & 1 deletion server/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,6 @@
exports org.elasticsearch.telemetry;
exports org.elasticsearch.telemetry.metric;

provides java.util.spi.CalendarDataProvider with org.elasticsearch.common.time.IsoCalendarDataProvider;
provides org.elasticsearch.xcontent.ErrorOnUnknown with org.elasticsearch.common.xcontent.SuggestingErrorOnUnknown;
provides org.elasticsearch.xcontent.XContentBuilderExtension with org.elasticsearch.common.xcontent.XContentElasticsearchExtension;
provides org.elasticsearch.cli.CliToolProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2324,8 +2324,6 @@ static DateFormatter forPattern(String input) {
} else if (FormatNames.STRICT_YEAR_MONTH_DAY.matches(input)) {
return STRICT_YEAR_MONTH_DAY;
} else {
DateUtils.checkTextualDateFormats(input);

try {
return newDateFormatter(
input,
Expand Down
17 changes: 0 additions & 17 deletions server/src/main/java/org/elasticsearch/common/time/DateUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@

import org.elasticsearch.common.logging.DeprecationCategory;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.core.Predicates;
import org.elasticsearch.core.UpdateForV9;
import org.elasticsearch.logging.LogManager;

import java.time.Clock;
import java.time.Duration;
Expand All @@ -23,8 +20,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import static java.util.Map.entry;
import static org.elasticsearch.common.time.DateUtilsRounding.getMonthOfYear;
Expand Down Expand Up @@ -388,16 +383,4 @@ public static ZonedDateTime nowWithMillisResolution(Clock clock) {
Clock millisResolutionClock = Clock.tick(clock, Duration.ofMillis(1));
return ZonedDateTime.now(millisResolutionClock);
}

// check for all textual fields, and localized zone offset
private static final Predicate<String> CONTAINS_CHANGING_TEXT_SPECIFIERS = System.getProperty("java.locale.providers", "")
.contains("COMPAT") ? Pattern.compile("[EcGaO]|MMM|LLL|eee|ccc|QQQ|ZZZZ").asPredicate() : Predicates.never();

@UpdateForV9 // this can be removed, we will only use CLDR on v9
static void checkTextualDateFormats(String format) {
if (CONTAINS_CHANGING_TEXT_SPECIFIERS.test(format)) {
LogManager.getLogger(DateFormatter.class)
.warn("Date format [{}] contains textual field specifiers that could change in JDK 23", format);
}
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -85,38 +86,32 @@ private void assertDateMathEquals(String text, String expected, String pattern)
}

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

assertThat(gotMillisJava, equalTo(expectedMillis));
assertThat(gotInstant, equalTo(expectedInstant));
}

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

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

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

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

// first week of 2016 starts on Monday 2016-01-04 as previous week in 2016 has only 3 days
assertThat(DateFormatters.from(dateFormatter.parse("2016")), equalTo(ZonedDateTime.of(2016, 01, 04, 0, 0, 0, 0, ZoneOffset.UTC)));

// first week of 2015 starts on Monday 2014-12-29 because 4days belong to 2019
assertThat(DateFormatters.from(dateFormatter.parse("2015")), equalTo(ZonedDateTime.of(2014, 12, 29, 0, 0, 0, 0, ZoneOffset.UTC)));
assertThat(DateFormatters.from(dateFormatter.parse("2016")), equalTo(ZonedDateTime.of(2015, 12, 27, 0, 0, 0, 0, ZoneOffset.UTC)));
assertThat(DateFormatters.from(dateFormatter.parse("2015")), equalTo(ZonedDateTime.of(2014, 12, 28, 0, 0, 0, 0, ZoneOffset.UTC)));
}

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

private void assertRoundupFormatter(String format, String input, long expectedMilliSeconds) {
Expand Down Expand Up @@ -789,30 +784,28 @@ public void testExceptionWhenCompositeParsingFailsDateMath() {
String text = "2014-06-06T12:01:02.123";
ElasticsearchParseException e1 = expectThrows(
ElasticsearchParseException.class,
() -> dateMathToMillis(text, DateFormatter.forPattern(pattern), randomLocale(random()))
() -> dateMathToInstant(text, DateFormatter.forPattern(pattern), randomLocale(random()))
);
assertThat(e1.getMessage(), containsString(pattern));
assertThat(e1.getMessage(), containsString(text));
}

private long dateMathToMillis(String text, DateFormatter dateFormatter, Locale locale) {
private Instant dateMathToInstant(String text, DateFormatter dateFormatter, Locale locale) {
DateFormatter javaFormatter = dateFormatter.withLocale(locale);
DateMathParser javaDateMath = javaFormatter.toDateMathParser();
return javaDateMath.parse(text, () -> 0, true, (ZoneId) null).toEpochMilli();
return javaDateMath.parse(text, () -> 0, true, null);
}

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

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

// these parsers should allow both ',' and '.' as a decimal point
Expand Down
Loading

0 comments on commit f9aa6f4

Please sign in to comment.