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));
+ }
}