From 89338c91a99e552458b80cc01342cb8e384c2038 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 4 Jul 2024 17:02:46 +0200 Subject: [PATCH] Stop using using legacy locale data for Date/Time formatting tests Commit 84714fbae9f335beb059753ee7d2a08653e6bb6c introduced usage of the -Djava.locale.providers=COMPAT command-line argument for javac in order to allow our JDK 20 builds to pass by using legacy locale data. That was done to ensure that Date/Time formats using AM/PM produced a standard space (" ") before the "AM" or "PM" instead of a narrow non-breaking space (NNBSP "\u202F"), which was introduced in Java 20 due to adoption of Unicode Common Locale Data Repository (CLDR-14032). This commit removes usage of the -Djava.locale.providers=COMPAT command-line argument and updates all affected tests to: - Use an NNBSP before "AM" or "PM" in input text when running on Java 20 or higher. - Leniently match against any Unicode space character in formatted values containing "AM" or "PM". See https://jdk.java.net/20/release-notes#JDK-8284840 See https://unicode-org.atlassian.net/browse/CLDR-14032 See gh-30185 Closes gh-33144 --- .../build/TestConventions.java | 5 +- .../DateTimeFormatterFactoryTests.java | 4 +- .../standard/DateTimeFormattingTests.java | 67 ++++++++++++++----- .../converter/json/GsonFactoryBeanTests.java | 3 +- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/build/TestConventions.java b/buildSrc/src/main/java/org/springframework/build/TestConventions.java index b3faae547be7..8c945825f73c 100644 --- a/buildSrc/src/main/java/org/springframework/build/TestConventions.java +++ b/buildSrc/src/main/java/org/springframework/build/TestConventions.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -63,8 +63,7 @@ private void configureTests(Project project, Test test) { test.systemProperty("testGroups", project.getProperties().get("testGroups")); } test.jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED", - "--add-opens=java.base/java.util=ALL-UNNAMED", - "-Djava.locale.providers=COMPAT"); + "--add-opens=java.base/java.util=ALL-UNNAMED"); } private void configureTestRetryPlugin(Project project, Test test) { diff --git a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormatterFactoryTests.java b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormatterFactoryTests.java index c2b44e3f3d34..8f1a9cff3cf6 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormatterFactoryTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormatterFactoryTests.java @@ -78,8 +78,8 @@ void createDateTimeFormatterWithFallback() { void createDateTimeFormatterInOrderOfPropertyPriority() { factory.setStylePattern("SS"); String value = applyLocale(factory.createDateTimeFormatter()).format(dateTime); - assertThat(value).startsWith("10/21/09"); - assertThat(value).endsWith("12:10 PM"); + // \p{Zs} matches any Unicode space character + assertThat(value).startsWith("10/21/09").matches(".+?12:10\\p{Zs}PM"); factory.setIso(ISO.DATE); assertThat(applyLocale(factory.createDateTimeFormatter()).format(dateTime)).isEqualTo("2009-10-21"); diff --git a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java index a2130906a5c2..43a8f1c13e47 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java @@ -42,6 +42,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -58,6 +59,8 @@ import org.springframework.validation.FieldError; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.condition.JRE.JAVA_19; +import static org.junit.jupiter.api.condition.JRE.JAVA_20; /** * @author Keith Donald @@ -68,6 +71,12 @@ */ class DateTimeFormattingTests { + // JDK <= 19 requires a standard space before "AM/PM". + // JDK >= 20 requires a NNBSP before "AM/PM". + // \u202F is a narrow non-breaking space (NNBSP). + private static final String TIME_SEPARATOR = (Runtime.version().feature() < 20 ? " " : "\u202F"); + + private final FormattingConversionService conversionService = new FormattingConversionService(); private DataBinder binder; @@ -210,10 +219,11 @@ void testBindLocalDateFromJavaUtilCalendar() { @Test void testBindLocalTime() { MutablePropertyValues propertyValues = new MutablePropertyValues(); - propertyValues.add("localTime", "12:00 PM"); + propertyValues.add("localTime", "12:00%sPM".formatted(TIME_SEPARATOR)); binder.bind(propertyValues); assertThat(binder.getBindingResult().getErrorCount()).isZero(); - assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("12:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(binder.getBindingResult().getFieldValue("localTime")).asString().matches("12:00\\p{Zs}PM"); } @Test @@ -222,7 +232,8 @@ void testBindLocalTimeWithISO() { propertyValues.add("localTime", "12:00:00"); binder.bind(propertyValues); assertThat(binder.getBindingResult().getErrorCount()).isZero(); - assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("12:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(binder.getBindingResult().getFieldValue("localTime")).asString().matches("12:00\\p{Zs}PM"); } @Test @@ -231,10 +242,11 @@ void testBindLocalTimeWithSpecificStyle() { registrar.setTimeStyle(FormatStyle.MEDIUM); setup(registrar); MutablePropertyValues propertyValues = new MutablePropertyValues(); - propertyValues.add("localTime", "12:00:00 PM"); + propertyValues.add("localTime", "12:00:00%sPM".formatted(TIME_SEPARATOR)); binder.bind(propertyValues); assertThat(binder.getBindingResult().getErrorCount()).isZero(); - assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("12:00:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(binder.getBindingResult().getFieldValue("localTime")).asString().matches("12:00:00\\p{Zs}PM"); } @Test @@ -252,10 +264,11 @@ void testBindLocalTimeWithSpecificFormatter() { @Test void testBindLocalTimeAnnotated() { MutablePropertyValues propertyValues = new MutablePropertyValues(); - propertyValues.add("styleLocalTime", "12:00:00 PM"); + propertyValues.add("styleLocalTime", "12:00:00%sPM".formatted(TIME_SEPARATOR)); binder.bind(propertyValues); assertThat(binder.getBindingResult().getErrorCount()).isZero(); - assertThat(binder.getBindingResult().getFieldValue("styleLocalTime")).isEqualTo("12:00:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(binder.getBindingResult().getFieldValue("styleLocalTime")).asString().matches("12:00:00\\p{Zs}PM"); } @Test @@ -264,7 +277,8 @@ void testBindLocalTimeFromJavaUtilCalendar() { propertyValues.add("localTime", new GregorianCalendar(1970, 0, 0, 12, 0)); binder.bind(propertyValues); assertThat(binder.getBindingResult().getErrorCount()).isZero(); - assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("12:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(binder.getBindingResult().getFieldValue("localTime")).asString().matches("12:00\\p{Zs}PM"); } @Test @@ -274,7 +288,8 @@ void testBindLocalDateTime() { binder.bind(propertyValues); assertThat(binder.getBindingResult().getErrorCount()).isZero(); String value = binder.getBindingResult().getFieldValue("localDateTime").toString(); - assertThat(value).startsWith("10/31/09").endsWith("12:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(value).startsWith("10/31/09").matches(".+?12:00\\p{Zs}PM"); } @Test @@ -284,7 +299,8 @@ void testBindLocalDateTimeWithISO() { binder.bind(propertyValues); assertThat(binder.getBindingResult().getErrorCount()).isZero(); String value = binder.getBindingResult().getFieldValue("localDateTime").toString(); - assertThat(value).startsWith("10/31/09").endsWith("12:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(value).startsWith("10/31/09").matches(".+?12:00\\p{Zs}PM"); } @Test @@ -294,7 +310,8 @@ void testBindLocalDateTimeAnnotated() { binder.bind(propertyValues); assertThat(binder.getBindingResult().getErrorCount()).isZero(); String value = binder.getBindingResult().getFieldValue("styleLocalDateTime").toString(); - assertThat(value).startsWith("Oct 31, 2009").endsWith("12:00:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(value).startsWith("Oct 31, 2009").matches(".+?12:00:00\\p{Zs}PM"); } @Test @@ -304,7 +321,8 @@ void testBindLocalDateTimeFromJavaUtilCalendar() { binder.bind(propertyValues); assertThat(binder.getBindingResult().getErrorCount()).isZero(); String value = binder.getBindingResult().getFieldValue("localDateTime").toString(); - assertThat(value).startsWith("10/31/09").endsWith("12:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(value).startsWith("10/31/09").matches(".+?12:00\\p{Zs}PM"); } @Test @@ -317,7 +335,8 @@ void testBindDateTimeWithSpecificStyle() { binder.bind(propertyValues); assertThat(binder.getBindingResult().getErrorCount()).isZero(); String value = binder.getBindingResult().getFieldValue("localDateTime").toString(); - assertThat(value).startsWith("Oct 31, 2009").endsWith("12:00:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(value).startsWith("Oct 31, 2009").matches(".+?12:00:00\\p{Zs}PM"); } @Test @@ -558,18 +577,32 @@ void patternLocalDate(String propertyValue) { assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02"); } + @EnabledForJreRange(max = JAVA_19) @ParameterizedTest(name = "input date: {0}") - // @ValueSource(strings = {"12:00:00\u202FPM", "12:00:00", "12:00"}) + // JDK <= 19 requires a standard space before the "PM". @ValueSource(strings = {"12:00:00 PM", "12:00:00", "12:00"}) - void styleLocalTime(String propertyValue) { + void styleLocalTime_PreJDK20(String propertyValue) { + styleLocalTime(propertyValue); + } + + @EnabledForJreRange(min = JAVA_20) + @ParameterizedTest(name = "input date: {0}") + // JDK >= 20 requires a NNBSP before the "PM". + // \u202F is a narrow non-breaking space (NNBSP). + @ValueSource(strings = {"12:00:00\u202FPM", "12:00:00", "12:00"}) + void styleLocalTime_PostJDK20(String propertyValue) { + styleLocalTime(propertyValue); + } + + private void styleLocalTime(String propertyValue) { String propertyName = "styleLocalTimeWithFallbackPatterns"; MutablePropertyValues propertyValues = new MutablePropertyValues(); propertyValues.add(propertyName, propertyValue); binder.bind(propertyValues); BindingResult bindingResult = binder.getBindingResult(); assertThat(bindingResult.getErrorCount()).isZero(); - // assertThat(bindingResult.getFieldValue(propertyName)).asString().matches("12:00:00\\SPM"); - assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("12:00:00 PM"); + // \p{Zs} matches any Unicode space character + assertThat(bindingResult.getFieldValue(propertyName)).asString().matches("12:00:00\\p{Zs}PM"); } @ParameterizedTest(name = "input date: {0}") diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/GsonFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/GsonFactoryBeanTests.java index 1f8e8baef82e..45aa4f6fb68b 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/GsonFactoryBeanTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/GsonFactoryBeanTests.java @@ -128,9 +128,10 @@ void customizeDateFormatNone() { cal.set(Calendar.DATE, 1); Date date = cal.getTime(); bean.setDate(date); + // \p{Zs} matches any Unicode space character assertThat(gson.toJson(bean)) .startsWith("{\"date\":\"Jan 1, 2014") - .endsWith("12:00:00 AM\"}"); + .matches(".+?12:00:00\\p{Zs}AM\"}"); } @Test