Skip to content

Commit c9ff6aa

Browse files
authored
[LANG-1786] Map deprecated TimeZone short IDs and avoid JRE WARNINGs to the console (#1483)
* Add testLang1641() * Rename some test methods * [LANG-1786] FastDateFormat logs warnings on the console using FastDateFormat on Java 25 - Add TimeZones.getTimeZone(String) to map deprecated ZoneId#SHORT_IDS - Avoids messages to System.err from the JRE's TimeZone.getTimeZone(String) starting on Java 25 - Calls to TimeZone.getTimeZone(String) are now delegated to TimeZones.getTimeZone(String) - You can disable mapping from ZoneId#SHORT_IDS by setting the system property "TimeZones.mapShortIDs=false" * [LANG-1786] FastDateFormat logs warnings on the console using FastDateFormat on Java 25 - Only enable on Java 25 and up
1 parent ba85f5e commit c9ff6aa

15 files changed

+125
-68
lines changed

pom.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
<commons.distSvnStagingUrl>scm:svn:https://dist.apache.org/repos/dist/dev/commons/lang</commons.distSvnStagingUrl>
163163
<!-- JaCoCo: Don't make code coverage worse than: -->
164164
<commons.jacoco.haltOnFailure>true</commons.jacoco.haltOnFailure>
165-
<commons.jacoco.classRatio>0.99</commons.jacoco.classRatio>
165+
<commons.jacoco.classRatio>0.98</commons.jacoco.classRatio>
166166
<commons.jacoco.instructionRatio>0.96</commons.jacoco.instructionRatio>
167167
<commons.jacoco.methodRatio>0.96</commons.jacoco.methodRatio>
168168
<commons.jacoco.branchRatio>0.92</commons.jacoco.branchRatio>
@@ -482,6 +482,17 @@
482482
</plugins>
483483
</build>
484484
</profile>
485+
<profile>
486+
<!-- Java 25 and up -->
487+
<id>java-25-up</id>
488+
<activation>
489+
<jdk>[25,)</jdk>
490+
</activation>
491+
<properties>
492+
<!-- JaCoCo: Don't make code coverage worse than: -->
493+
<commons.jacoco.classRatio>0.99</commons.jacoco.classRatio>
494+
</properties>
495+
</profile>
485496
<profile>
486497
<id>benchmark</id>
487498
<properties>

src/main/java/org/apache/commons/lang3/time/FastDateParser.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ private StrategyAndWidth literal() {
482482
* A strategy that handles a time zone field in the parsing pattern
483483
*/
484484
static class TimeZoneStrategy extends PatternStrategy {
485+
485486
private static final class TzInfo {
486487
final TimeZone zone;
487488
final int dstOffset;
@@ -496,6 +497,7 @@ public String toString() {
496497
return "TzInfo [zone=" + zone + ", dstOffset=" + dstOffset + "]";
497498
}
498499
}
500+
499501
private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}";
500502

501503
private static final String GMT_OPTION = TimeZones.GMT_ID + "[+-]\\d{1,2}:\\d{2}";
@@ -505,6 +507,22 @@ public String toString() {
505507
*/
506508
private static final int ID = 0;
507509

510+
/**
511+
* Tests whether to skip the given time zone, true if TimeZone.getTimeZone().
512+
* <p>
513+
* On Java 25 and up, skips short IDs if {@code ignoreTimeZoneShortIDs} is true.
514+
* </p>
515+
* <p>
516+
* This method is package private only for testing.
517+
* </p>
518+
*
519+
* @param tzId the ID to test.
520+
* @return Whether to skip the given time zone ID.
521+
*/
522+
static boolean skipTimeZone(final String tzId) {
523+
return tzId.equalsIgnoreCase(TimeZones.GMT_ID);
524+
}
525+
508526
private final Locale locale;
509527

510528
/**
@@ -514,9 +532,9 @@ public String toString() {
514532
private final Map<String, TzInfo> tzNames = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
515533

516534
/**
517-
* Constructs a Strategy that parses a TimeZone
535+
* Constructs a Strategy that parses a TimeZone.
518536
*
519-
* @param locale The Locale
537+
* @param locale The Locale.
520538
*/
521539
TimeZoneStrategy(final Locale locale) {
522540
this.locale = LocaleUtils.toLocale(locale);
@@ -532,10 +550,10 @@ public String toString() {
532550
for (final String[] zoneNames : zones) {
533551
// offset 0 is the time zone ID and is not localized
534552
final String tzId = zoneNames[ID];
535-
if (tzId.equalsIgnoreCase(TimeZones.GMT_ID)) {
553+
if (skipTimeZone(tzId)) {
536554
continue;
537555
}
538-
final TimeZone tz = TimeZone.getTimeZone(tzId);
556+
final TimeZone tz = TimeZones.getTimeZone(tzId);
539557
// offset 1 is long standard name
540558
// offset 2 is short standard name
541559
final TzInfo standard = new TzInfo(tz, false);
@@ -561,10 +579,10 @@ public String toString() {
561579
}
562580
// Order is undefined.
563581
for (final String tzId : ArraySorter.sort(TimeZone.getAvailableIDs())) {
564-
if (tzId.equalsIgnoreCase(TimeZones.GMT_ID)) {
582+
if (skipTimeZone(tzId)) {
565583
continue;
566584
}
567-
final TimeZone tz = TimeZone.getTimeZone(tzId);
585+
final TimeZone tz = TimeZones.getTimeZone(tzId);
568586
final String zoneName = tz.getDisplayName(locale);
569587
if (sorted.add(zoneName)) {
570588
tzNames.put(zoneName, new TzInfo(tz, tz.observesDaylightTime()));

src/main/java/org/apache/commons/lang3/time/FastTimeZone.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public static TimeZone getTimeZone(final String id) {
7777
if (tz != null) {
7878
return tz;
7979
}
80-
return TimeZone.getTimeZone(id);
80+
return TimeZones.getTimeZone(id);
8181
}
8282

8383
private static int parseInt(final String group) {

src/main/java/org/apache/commons/lang3/time/TimeZones.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717

1818
package org.apache.commons.lang3.time;
1919

20+
import java.time.ZoneId;
2021
import java.util.TimeZone;
2122

23+
import org.apache.commons.lang3.JavaVersion;
2224
import org.apache.commons.lang3.ObjectUtils;
25+
import org.apache.commons.lang3.SystemProperties;
26+
import org.apache.commons.lang3.SystemUtils;
2327

2428
/**
2529
* Helps dealing with {@link java.util.TimeZone}s.
@@ -38,7 +42,34 @@ public class TimeZones {
3842
*
3943
* @since 3.13.0
4044
*/
41-
public static final TimeZone GMT = TimeZone.getTimeZone(GMT_ID);
45+
public static final TimeZone GMT = TimeZones.getTimeZone(GMT_ID);
46+
47+
private static final boolean JAVA_25 = SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_25);
48+
49+
/**
50+
* Delegates to {@link TimeZone#getTimeZone(String)} after mapping an ID if it's in {@link ZoneId#SHORT_IDS}.
51+
* <p>
52+
* On Java 25, calling {@link TimeZone#getTimeZone(String)} with an ID in {@link ZoneId#SHORT_IDS} writes a message to {@link System#err} in the form:
53+
* </p>
54+
*
55+
* <pre>
56+
* WARNING: Use of the three-letter time zone ID "the-short-id" is deprecated and it will be removed in a future release
57+
* </pre>
58+
* <p>
59+
* You can disable mapping from {@link ZoneId#SHORT_IDS} by setting the system property {@code "TimeZones.mapShortIDs=false"}.
60+
* </p>
61+
*
62+
* @param id Same as {@link TimeZone#getTimeZone(String)}.
63+
* @return Same as {@link TimeZone#getTimeZone(String)}.
64+
* @since 3.20.0
65+
*/
66+
public static TimeZone getTimeZone(final String id) {
67+
return TimeZone.getTimeZone(JAVA_25 && mapShortIDs() ? ZoneId.SHORT_IDS.getOrDefault(id, id) : id);
68+
}
69+
70+
private static boolean mapShortIDs() {
71+
return SystemProperties.getBoolean(TimeZones.class, "mapShortIDs", () -> true);
72+
}
4273

4374
/**
4475
* Returns the given TimeZone if non-{@code null}, otherwise {@link TimeZone#getDefault()}.
@@ -54,5 +85,4 @@ public static TimeZone toTimeZone(final TimeZone timeZone) {
5485
/** Do not instantiate. */
5586
private TimeZones() {
5687
}
57-
5888
}

src/test/java/org/apache/commons/lang3/time/CalendarUtilsTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ void testGetYear() {
9898
*/
9999
@Test
100100
void testToLocalDate() {
101-
final Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone(TimeZones.GMT_ID));
101+
final Calendar calendar = new GregorianCalendar(TimeZones.getTimeZone(TimeZones.GMT_ID));
102102
calendar.setTimeInMillis(-27078001200000L);
103103
assertEquals("1111-12-08T05:00:00Z", calendar.toInstant().toString());
104104
assertEquals(LocalDate.of(1111, Month.DECEMBER, 8), new CalendarUtils(calendar).toLocalDate());
@@ -109,7 +109,7 @@ void testToLocalDate() {
109109
@ParameterizedTest
110110
@MethodSource(TimeZonesTest.TIME_ZONE_GET_AVAILABLE_IDS)
111111
void testToLocalDateTime(final String timeZoneId) {
112-
final TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
112+
final TimeZone timeZone = TimeZones.getTimeZone(timeZoneId);
113113
final ZoneId zoneId = timeZone.toZoneId();
114114
final Calendar calendar = new GregorianCalendar(timeZone);
115115
calendar.setTimeInMillis(0);
@@ -122,7 +122,7 @@ void testToLocalDateTime(final String timeZoneId) {
122122
@ParameterizedTest
123123
@MethodSource(TimeZonesTest.TIME_ZONE_GET_AVAILABLE_IDS)
124124
void testToOffsetDateTime(final String timeZoneId) {
125-
final TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
125+
final TimeZone timeZone = TimeZones.getTimeZone(timeZoneId);
126126
final ZoneId zoneId = timeZone.toZoneId();
127127
final Calendar calendar = new GregorianCalendar(timeZone);
128128
calendar.setTimeInMillis(0);
@@ -135,7 +135,7 @@ void testToOffsetDateTime(final String timeZoneId) {
135135
@ParameterizedTest
136136
@MethodSource(TimeZonesTest.TIME_ZONE_GET_AVAILABLE_IDS)
137137
void testToZonedDateTime(final String timeZoneId) {
138-
final TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
138+
final TimeZone timeZone = TimeZones.getTimeZone(timeZoneId);
139139
final ZoneId zoneId = timeZone.toZoneId();
140140
final Calendar calendar = new GregorianCalendar(timeZone);
141141
calendar.setTimeInMillis(0);

src/test/java/org/apache/commons/lang3/time/DateFormatUtilsTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ void testFormatUTC() {
141141
}
142142

143143
private void testGmtMinus3(final String expectedValue, final String pattern) {
144-
final TimeZone timeZone = TimeZone.getTimeZone("GMT-3");
144+
final TimeZone timeZone = TimeZones.getTimeZone("GMT-3");
145145
assertFormats(expectedValue, pattern, timeZone, createFebruaryTestDate(timeZone));
146146
}
147147

@@ -153,10 +153,10 @@ void testLANG1000() throws Exception {
153153

154154
@Test
155155
void testLANG1462() {
156-
final TimeZone timeZone = TimeZone.getTimeZone("GMT-3");
156+
final TimeZone timeZone = TimeZones.getTimeZone("GMT-3");
157157
final Calendar calendar = createJuneTestDate(timeZone);
158158
assertEquals("20030608101112", DateFormatUtils.format(calendar, "yyyyMMddHHmmss"));
159-
calendar.setTimeZone(TimeZone.getTimeZone("JST"));
159+
calendar.setTimeZone(TimeZones.getTimeZone("JST"));
160160
assertEquals("20030608221112", DateFormatUtils.format(calendar, "yyyyMMddHHmmss"));
161161
}
162162

@@ -179,43 +179,43 @@ void testLang530() throws ParseException {
179179
@Test
180180
void testLang916() {
181181

182-
final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Europe/Paris"));
182+
final Calendar cal = Calendar.getInstance(TimeZones.getTimeZone("Europe/Paris"));
183183
cal.clear();
184184
cal.set(2009, 9, 16, 8, 42, 16);
185185

186186
// Long.
187187
{
188-
final String value = DateFormatUtils.format(cal.getTimeInMillis(), DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Europe/Paris"));
188+
final String value = DateFormatUtils.format(cal.getTimeInMillis(), DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZones.getTimeZone("Europe/Paris"));
189189
assertEquals("2009-10-16T08:42:16+02:00", value, "long");
190190
}
191191
{
192-
final String value = DateFormatUtils.format(cal.getTimeInMillis(), DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Asia/Kolkata"));
192+
final String value = DateFormatUtils.format(cal.getTimeInMillis(), DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZones.getTimeZone("Asia/Kolkata"));
193193
assertEquals("2009-10-16T12:12:16+05:30", value, "long");
194194
}
195195
{
196-
final String value = DateFormatUtils.format(cal.getTimeInMillis(), DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Europe/London"));
196+
final String value = DateFormatUtils.format(cal.getTimeInMillis(), DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZones.getTimeZone("Europe/London"));
197197
assertEquals("2009-10-16T07:42:16+01:00", value, "long");
198198
}
199199

200200
// Calendar.
201201
{
202-
final String value = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Europe/Paris"));
202+
final String value = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZones.getTimeZone("Europe/Paris"));
203203
assertEquals("2009-10-16T08:42:16+02:00", value, "calendar");
204204
}
205205
{
206-
final String value = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Asia/Kolkata"));
206+
final String value = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZones.getTimeZone("Asia/Kolkata"));
207207
assertEquals("2009-10-16T12:12:16+05:30", value, "calendar");
208208
}
209209
{
210-
final String value = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZone.getTimeZone("Europe/London"));
210+
final String value = DateFormatUtils.format(cal, DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), TimeZones.getTimeZone("Europe/London"));
211211
assertEquals("2009-10-16T07:42:16+01:00", value, "calendar");
212212
}
213213
}
214214

215215
@DefaultLocale(language = "en")
216216
@Test
217217
void testSMTP() {
218-
TimeZone timeZone = TimeZone.getTimeZone("GMT-3");
218+
TimeZone timeZone = TimeZones.getTimeZone("GMT-3");
219219
Calendar june = createJuneTestDate(timeZone);
220220

221221
assertFormats("Sun, 08 Jun 2003 10:11:12 -0300", DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern(),

src/test/java/org/apache/commons/lang3/time/DateUtilsTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@
6666
@WritesDefaultLocale
6767
class DateUtilsTest extends AbstractLangTest {
6868

69-
private static final TimeZone TIME_ZONE_NY = TimeZone.getTimeZone("America/New_York");
69+
private static final TimeZone TIME_ZONE_NY = TimeZones.getTimeZone("America/New_York");
7070
private static final TimeZone TIME_ZONE_DEFAULT = TimeZone.getDefault();
71-
private static final TimeZone TIME_ZONE_MET = TimeZone.getTimeZone("MET");
71+
private static final TimeZone TIME_ZONE_MET = TimeZones.getTimeZone("MET");
7272
private static Date BASE_DATE;
7373

7474
/**
@@ -209,7 +209,7 @@ private static Stream<Arguments> testToLocalDateTimeTimeZone() {
209209
Arguments.of(
210210
LocalDateTime.of(2023, 1, 1, 14, 0),
211211
Date.from(LocalDateTime.of(2023, 1, 1, 0, 0).atOffset(ZoneOffset.UTC).toInstant()),
212-
TimeZone.getTimeZone("Pacific/Kiritimati")
212+
TimeZones.getTimeZone("Pacific/Kiritimati")
213213
)
214214
);
215215
// @formatter:on
@@ -763,8 +763,8 @@ void testIsSameDay_DateNullNull() {
763763

764764
@Test
765765
void testIsSameInstant_Cal() {
766-
final GregorianCalendar cala = new GregorianCalendar(TimeZone.getTimeZone("GMT+1"));
767-
final GregorianCalendar calb = new GregorianCalendar(TimeZone.getTimeZone("GMT-1"));
766+
final GregorianCalendar cala = new GregorianCalendar(TimeZones.getTimeZone("GMT+1"));
767+
final GregorianCalendar calb = new GregorianCalendar(TimeZones.getTimeZone("GMT-1"));
768768
cala.set(2004, Calendar.JULY, 9, 13, 45, 0);
769769
cala.set(Calendar.MILLISECOND, 0);
770770
calb.set(2004, Calendar.JULY, 9, 13, 45, 0);
@@ -820,8 +820,8 @@ void testIsSameInstant_DateNullNull() {
820820

821821
@Test
822822
void testIsSameLocalTime_Cal() {
823-
final GregorianCalendar cala = new GregorianCalendar(TimeZone.getTimeZone("GMT+1"));
824-
final GregorianCalendar calb = new GregorianCalendar(TimeZone.getTimeZone("GMT-1"));
823+
final GregorianCalendar cala = new GregorianCalendar(TimeZones.getTimeZone("GMT+1"));
824+
final GregorianCalendar calb = new GregorianCalendar(TimeZones.getTimeZone("GMT-1"));
825825
cala.set(2004, Calendar.JULY, 9, 13, 45, 0);
826826
cala.set(Calendar.MILLISECOND, 0);
827827
calb.set(2004, Calendar.JULY, 9, 13, 45, 0);
@@ -1577,7 +1577,7 @@ void testTruncate_Bugzilla_31395() throws Exception {
15771577
@Test
15781578
void testTruncateLang59() {
15791579
// Set TimeZone to Mountain Time
1580-
final TimeZone denverZone = TimeZone.getTimeZone("America/Denver");
1580+
final TimeZone denverZone = TimeZones.getTimeZone("America/Denver");
15811581
TimeZone.setDefault(denverZone);
15821582
final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS XXX");
15831583
format.setTimeZone(denverZone);

src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ void testFormatPeriodeStartGreaterEnd() {
481481
@SuppressWarnings("deprecation")
482482
@Test
483483
void testFormatPeriodISO() {
484-
final TimeZone timeZone = TimeZone.getTimeZone("GMT-3");
484+
final TimeZone timeZone = TimeZones.getTimeZone("GMT-3");
485485
final Calendar base = Calendar.getInstance(timeZone);
486486
base.set(1970, Calendar.JANUARY, 1, 0, 0, 0);
487487
base.set(Calendar.MILLISECOND, 0);

0 commit comments

Comments
 (0)