diff --git a/server/src/main/java/org/elasticsearch/common/time/DateFormatters.java b/server/src/main/java/org/elasticsearch/common/time/DateFormatters.java index ae8f8cb28da11..c1081f3d62850 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateFormatters.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateFormatters.java @@ -2323,6 +2323,8 @@ 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, diff --git a/server/src/main/java/org/elasticsearch/common/time/DateUtils.java b/server/src/main/java/org/elasticsearch/common/time/DateUtils.java index 8e98adc183369..e312ce78ea157 100644 --- a/server/src/main/java/org/elasticsearch/common/time/DateUtils.java +++ b/server/src/main/java/org/elasticsearch/common/time/DateUtils.java @@ -10,6 +10,9 @@ 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; @@ -19,6 +22,8 @@ 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; @@ -382,4 +387,16 @@ 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 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); + } + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/ClusterStateLicenseService.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/ClusterStateLicenseService.java index b352a9abce886..2558afb1bb8ed 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/ClusterStateLicenseService.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/ClusterStateLicenseService.java @@ -140,7 +140,7 @@ CharSequence buildExpirationMessage(long expirationMillis, boolean expired) { License [{}] on [{}]. # If you have a new license, please update it. Otherwise, please reach out to # your support contact. - #\s""", expiredMsg, LicenseUtils.DATE_FORMATTER.formatMillis(expirationMillis)); + #\s""", expiredMsg, LicenseUtils.formatMillis(expirationMillis)); if (expired) { general = general.toUpperCase(Locale.ROOT); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java index b27c1bb9d449c..7e67ee892043d 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java @@ -8,7 +8,6 @@ import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.common.hash.MessageDigests; -import org.elasticsearch.common.time.DateFormatter; import org.elasticsearch.license.License.LicenseType; import org.elasticsearch.license.internal.XPackLicenseStatus; import org.elasticsearch.protocol.xpack.license.LicenseStatus; @@ -16,6 +15,9 @@ import java.nio.charset.StandardCharsets; import java.time.Clock; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -25,7 +27,13 @@ public class LicenseUtils { public static final String EXPIRED_FEATURE_METADATA = "es.license.expired.feature"; - public static final DateFormatter DATE_FORMATTER = DateFormatter.forPattern("EEEE, MMMM dd, yyyy").withLocale(Locale.ENGLISH); + + public static String formatMillis(long millis) { + // DateFormatters logs a warning about the pattern on COMPAT + // this will be confusing to users, so call DateTimeFormatter directly instead + return DateTimeFormatter.ofPattern("EEEE, MMMM dd, yyyy", Locale.ENGLISH) + .format(Instant.ofEpochMilli(millis).atOffset(ZoneOffset.UTC)); + } /** * Exception to be thrown when a feature action requires a valid license, but license @@ -155,7 +163,7 @@ public static String getExpiryWarning(long licenseExpiryDate, long currentTime) ? "expires today" : (diff > 0 ? String.format(Locale.ROOT, "will expire in [%d] days", days) - : String.format(Locale.ROOT, "expired on [%s]", LicenseUtils.DATE_FORMATTER.formatMillis(licenseExpiryDate))); + : String.format(Locale.ROOT, "expired on [%s]", formatMillis(licenseExpiryDate))); return "Your license " + expiryMessage + ". "