Skip to content

Commit

Permalink
Fix NullPointerException in
Browse files Browse the repository at this point in the history
FastDateParser.TimeZoneStrategy.setCalendar(FastDateParser, Calendar,
String) on Java 23

- Tested locally on Java 8, 17, and 23
- Javadoc
  • Loading branch information
garydgregory committed Nov 2, 2024
1 parent 0d0dd5a commit d8faf08
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 18 deletions.
1 change: 1 addition & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ The <action> type attribute can be add,update,fix,remove.
<action issue="LANG-1753" type="fix" dev="ggregory" due-to="Capt. Cutlass">StringUtils.replaceEachRepeatedly regression in 3.11+ #1297.</action>
<action type="fix" dev="ggregory" due-to="Capt. Cutlass">Use simplified JUnit assertion methods #1298.</action>
<action issue="LANG-1682" type="fix" dev="ggregory" due-to="Capt. Cutlass">Javadoc and test: Use Strings.CI.startsWithAny method instead #1299.</action>
<action type="fix" dev="ggregory" due-to="Gary Gregory">Fix NullPointerException in FastDateParser.TimeZoneStrategy.setCalendar(FastDateParser, Calendar, String) on Java 23.</action>
<!-- ADD -->
<action type="add" dev="ggregory" due-to="Gary Gregory">Add Strings and refactor StringUtils.</action>
<action issue="LANG-1747" type="add" dev="ggregory" due-to="Oliver B. Fischer, Gary Gregory">Add StopWatch.run([Failable]Runnable) and get([Failable]Supplier).</action>
Expand Down
43 changes: 25 additions & 18 deletions src/main/java/org/apache/commons/lang3/time/FastDateParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
Expand All @@ -35,12 +36,14 @@
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.ArraySorter;
import org.apache.commons.lang3.LocaleUtils;

/**
Expand Down Expand Up @@ -494,14 +497,19 @@ public String toString() {
private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}";

private static final String GMT_OPTION = TimeZones.GMT_ID + "[+-]\\d{1,2}:\\d{2}";

/**
* Index of zone id
* Index of zone id from {@link DateFormatSymbols#getZoneStrings()}.
*/
private static final int ID = 0;

private final Locale locale;

private final Map<String, TzInfo> tzNames = new HashMap<>();
/**
* Using lower case only or upper case only will cause problems with some Locales like Turkey, Armenia, Colognian and also depending on the Java
* version. For details, see https://garygregory.wordpress.com/2015/11/03/java-lowercase-conversion-turkey/
*/
private final Map<String, TzInfo> tzNames = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

/**
* Constructs a Strategy that parses a TimeZone
Expand Down Expand Up @@ -543,30 +551,23 @@ public String toString() {
break;
}
final String zoneName = zoneNames[i];
if (zoneName != null) {
final String key = zoneName.toLowerCase(locale);
// ignore the data associated with duplicates supplied in
// the additional names
if (sorted.add(key)) {
tzNames.put(key, tzInfo);
}
// ignore the data associated with duplicates supplied in the additional names
if (zoneName != null && sorted.add(zoneName)) {
tzNames.put(zoneName, tzInfo);
}
}
}

// Order is undefined.
for (final String tzId : TimeZone.getAvailableIDs()) {
for (final String tzId : ArraySorter.sort(TimeZone.getAvailableIDs())) {
if (tzId.equalsIgnoreCase(TimeZones.GMT_ID)) {
continue;
}
final TimeZone tz = TimeZone.getTimeZone(tzId);
final String zoneName = tz.getDisplayName(locale);
final String key = zoneName.toLowerCase(locale);
if (sorted.add(key)) {
tzNames.put(key, new TzInfo(tz, tz.observesDaylightTime()));
if (sorted.add(zoneName)) {
tzNames.put(zoneName, new TzInfo(tz, tz.observesDaylightTime()));
}
}

// order the regex alternatives with longer strings first, greedy
// match will ensure the longest string will be consumed
sorted.forEach(zoneName -> simpleQuote(sb.append('|'), zoneName));
Expand All @@ -583,11 +584,16 @@ void setCalendar(final FastDateParser parser, final Calendar calendar, final Str
if (tz != null) {
calendar.setTimeZone(tz);
} else {
final String lowerCase = timeZone.toLowerCase(locale);
TzInfo tzInfo = tzNames.get(lowerCase);
TzInfo tzInfo = tzNames.get(timeZone);
if (tzInfo == null) {
// match missing the optional trailing period
tzInfo = tzNames.get(lowerCase + '.');
tzInfo = tzNames.get(timeZone + '.');
if (tzInfo == null) {
// show chars in case this is multiple byte character issue
final char[] charArray = timeZone.toCharArray();
throw new IllegalStateException(String.format("Can't find time zone '%s' (%d %s) in %s", timeZone, charArray.length,
Arrays.toString(charArray), new TreeSet<>(tzNames.keySet())));
}
}
calendar.set(Calendar.DST_OFFSET, tzInfo.dstOffset);
calendar.set(Calendar.ZONE_OFFSET, tzInfo.zone.getRawOffset());
Expand Down Expand Up @@ -1079,6 +1085,7 @@ private void readObject(final ObjectInputStream in) throws IOException, ClassNot
public String toString() {
return "FastDateParser[" + pattern + ", " + locale + ", " + timeZone.getID() + "]";
}

/**
* Converts all state of this instance to a String handy for debugging.
*
Expand Down

0 comments on commit d8faf08

Please sign in to comment.