This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new 92390270b Make FastDatePrinter time-zone tests deterministic (#1854)
92390270b is described below

commit 92390270b7bebc3da4937c6d3d0ac8e21d97607c
Author: Gary Gregory <[email protected]>
AuthorDate: Sun Nov 1 07:17:28 2026 -0500

    Make FastDatePrinter time-zone tests deterministic (#1854)
    
    Use fixed winter, summer, and DST transition instants with Locale.US.
    Preserve seasonal SimpleDateFormat comparisons and verify numeric
    offsets across transitions.
    
    Check zone names against TimeZone's naming API to account for newer
    JDK CLDR naming differences.
---
 .../lang3/time/FastDatePrinterTimeZonesTest.java   | 125 +++++++++++++++++++--
 1 file changed, 114 insertions(+), 11 deletions(-)

diff --git 
a/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTimeZonesTest.java 
b/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTimeZonesTest.java
index 58b2c39cc..0b3ed4aea 100644
--- 
a/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTimeZonesTest.java
+++ 
b/src/test/java/org/apache/commons/lang3/time/FastDatePrinterTimeZonesTest.java
@@ -14,39 +14,142 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.commons.lang3.time;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.text.SimpleDateFormat;
+import java.time.Instant;
 import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
 import java.util.TimeZone;
 import java.util.stream.Stream;
 
 import org.apache.commons.lang3.AbstractLangTest;
 import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
+/**
+ * Tests date formatting in every available time zone using fixed dates and 
{@link Locale#US}.
+ *
+ * <p>
+ * The old test used the current time. It began failing on November 1, 2026 
because newer JDKs and {@link FastDatePrinter} chose different names for some
+ * Canadian zones, even though the clock times and UTC offsets matched. {@link 
SimpleDateFormat} can use CLDR naming rules to choose a name such as {@code 
MDT},
+ * while the printer uses the calendar's DST flag and can choose {@code MST} 
for the same instant.
+ * </p>
+ *
+ * <p>
+ * We compare the complete output with {@link SimpleDateFormat} for fixed 
January and July dates. Around DST changes, we compare numeric offsets with
+ * {@link SimpleDateFormat} and names with {@link TimeZone}. The dates cover 
New York's skipped and repeated hours and the November 1 failure.
+ * </p>
+ *
+ * <p>
+ * Keep the dates and locale explicit, and set each calendar's time before 
formatting. Do not use the current time or remove transition cases to make the 
test
+ * pass. Comparing {@link SimpleDateFormat}'s zone names for every case would 
bring back the failure. These tests check the printer's existing naming 
behavior;
+ * they do not change it to match newer JDKs.
+ * </p>
+ */
 class FastDatePrinterTimeZonesTest extends AbstractLangTest {
 
-    private static final String PATTERN = "h:mma z";
+    private static final Locale LOCALE = Locale.US;
 
-    /*
-     * A @MethodSource.
+    /**
+     * Gets the time zones and fixed dates for offset and name checks, 
including both occurrences of New York's repeated hour.
+     *
+     * @return time-zone IDs paired with fixed instants.
      */
-    static Stream<TimeZone> getTimeZonesSorted() {
-        return 
Stream.of(TimeZones.SORTED_AVAILABLE_IDS).map(TimeZones::getTimeZone);
+    static Stream<Arguments> getTimeZonesAndInstants() {
+        // @formatter:off
+        return Stream.concat(getTimeZonesAndSeasonalInstants(), 
timeZonesAndInstants(
+                // Immediately before and after the spring gap in New York.
+                "2026-03-08T06:59:59Z",
+                "2026-03-08T07:00:00Z",
+                // Both occurrences of 1:30 AM during the fall overlap in New 
York.
+                "2026-11-01T05:30:00Z",
+                "2026-11-01T06:30:00Z",
+                // Exercises the Canadian zone-name differences after the 2026 
transition.
+                "2026-11-01T12:00:00Z"));
+        // @formatter:on
+    }
+
+    /**
+     * Gets the January and July dates used for complete {@link 
SimpleDateFormat} comparisons. Keep these separate from the transition cases 
where the
+     * formatters can choose different zone names.
+     *
+     * @return time-zone IDs paired with the fixed seasonal instants.
+     */
+    static Stream<Arguments> getTimeZonesAndSeasonalInstants() {
+        return timeZonesAndInstants("2026-01-15T12:00:00Z", 
"2026-07-15T12:00:00Z");
+    }
+
+    private static Stream<Arguments> timeZonesAndInstants(final String... 
instants) {
+        return Stream.of(TimeZones.SORTED_AVAILABLE_IDS).flatMap(id -> 
Stream.of(instants).map(instant -> Arguments.of(id, Instant.parse(instant))));
     }
 
+    /**
+     * Checks the clock time and the name chosen by {@link TimeZone}.
+     *
+     * <p>
+     * Keep the zone name out of the {@link SimpleDateFormat} pattern here. 
Its name can differ from the printer's even when the time is correct.
+     * </p>
+     *
+     * @param timeZoneId the time-zone ID to test.
+     * @param instant    the fixed instant to format.
+     */
     @ParameterizedTest
-    @MethodSource("getTimeZonesSorted")
-    void testCalendarTimezoneRespected(final TimeZone timeZone) {
-        final Calendar cal = Calendar.getInstance(timeZone);
-        final SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);
+    @MethodSource("getTimeZonesAndInstants")
+    void testCalendarTimezoneDisplayName(final String timeZoneId, final 
Instant instant) {
+        final TimeZone timeZone = TimeZones.getTimeZone(timeZoneId);
+        final Calendar cal = Calendar.getInstance(timeZone, LOCALE);
+        cal.setTimeInMillis(instant.toEpochMilli());
+        final Date date = cal.getTime();
+        final SimpleDateFormat sdf = new SimpleDateFormat("h:mma", LOCALE);
         sdf.setTimeZone(timeZone);
-        final String expectedValue = sdf.format(cal.getTime());
-        final String actualValue = FastDateFormat.getInstance(PATTERN, 
timeZone).format(cal);
+        final String expectedValue = sdf.format(date) + " " + 
timeZone.getDisplayName(timeZone.inDaylightTime(date), TimeZone.SHORT, LOCALE);
+        final String actualValue = FastDateFormat.getInstance("h:mma z", 
timeZone, LOCALE).format(cal);
         assertEquals(expectedValue, actualValue);
     }
 
+    /**
+     * Checks the clock time and numeric UTC offset against {@link 
SimpleDateFormat}.
+     *
+     * <p>
+     * Keep the uppercase {@code Z} pattern. Lowercase {@code z} compares 
names and would bring back the naming mismatch.
+     * </p>
+     *
+     * @param timeZoneId the time-zone ID to test.
+     * @param instant    the fixed instant to format.
+     */
+    @ParameterizedTest
+    @MethodSource("getTimeZonesAndInstants")
+    void testCalendarTimezoneOffset(final String timeZoneId, final Instant 
instant) {
+        final TimeZone timeZone = TimeZones.getTimeZone(timeZoneId);
+        final Calendar cal = Calendar.getInstance(timeZone, LOCALE);
+        cal.setTimeInMillis(instant.toEpochMilli());
+        final String pattern = "h:mma Z";
+        final SimpleDateFormat sdf = new SimpleDateFormat(pattern, LOCALE);
+        sdf.setTimeZone(timeZone);
+        assertEquals(sdf.format(cal.getTime()), 
FastDateFormat.getInstance(pattern, timeZone, LOCALE).format(cal));
+    }
+
+    /**
+     * Keeps the original comparison of complete formatted output, including 
zone names, for the fixed January and July dates.
+     *
+     * @param timeZoneId the time-zone ID to test.
+     * @param instant    the fixed seasonal instant to format.
+     */
+    @ParameterizedTest
+    @MethodSource("getTimeZonesAndSeasonalInstants")
+    void testCalendarTimezoneRespected(final String timeZoneId, final Instant 
instant) {
+        final TimeZone timeZone = TimeZones.getTimeZone(timeZoneId);
+        final Calendar cal = Calendar.getInstance(timeZone, LOCALE);
+        cal.setTimeInMillis(instant.toEpochMilli());
+        final String pattern = "h:mma z";
+        final SimpleDateFormat sdf = new SimpleDateFormat(pattern, LOCALE);
+        sdf.setTimeZone(timeZone);
+        assertEquals(sdf.format(cal.getTime()), 
FastDateFormat.getInstance(pattern, timeZone, LOCALE).format(cal));
+    }
 }

Reply via email to