ADDITIONAL SYSTEM INFORMATION :
Also reproduces with jdk-9.0.1
A DESCRIPTION OF THE PROBLEM :
If SimpleDateFormat is configured with a pattern that allows for an ambiguous timezone (like AKST in English Locale), and if that timezone is an alias for the current platform/default timezone (such as America/Metlakatla), then the input is parsed using the platform/default timezone. The objective of many server Java applications is to be able to parse dates/times insensitive to whatever the platform time zone may be but in this case it seems impossible.
My analysis using a debugger is that SimpleDateFormat line 1683 (of subParseZoneString) contains what appears to be an optimization to avoid a brute force time zone table lookup. This optimization is triggered when the default time zone has a matching zone alias.
This bug was found in a randomized test for Apache Solr's "extraction" contrib module: https://issues.apache.org/jira/browse/SOLR-10243
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Set the default time zone to "America/Metlakatla"
2. Create a SimpleDateFormat with a pattern containing a single 'z', and with Locale.ENGLISH.
3. parse input with an abbreviated timezone like "AKST"
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Results should be *consistent* for any default time zone; the results should not vary depending on the default time zone.
ACTUAL -
Result varies depending on the default time zone.
---------- BEGIN SOURCE ----------
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Locale;
import java.util.TimeZone;
public class SimpleDateFormatTimeZoneBug {
public static void main(String[] args) throws ParseException {
if (SimpleDateFormatTimeZoneBug.class.desiredAssertionStatus() == false) {
throw new IllegalStateException("Try again with assertions enabled.");
}
// Bug depends on this default time zone. Use some other time zone without troubles.
// Source code inspection suggests the TZ itself is not the problem, it's assumptions in SimpleDateFormat
TimeZone.setDefault(TimeZone.getTimeZone("America/Metlakatla")); // in Alaska, Metlakatla
// there are numerous time zones with a "AKST" alias.
System.out.println("=== Time zones with AKST alias:");
final DateFormatSymbols formatSymbols = DateFormatSymbols.getInstance(Locale.ENGLISH);
zoneStrings: for (String[] zoneAliases : formatSymbols.getZoneStrings()) {
for (String zoneAlias : zoneAliases) {
if (zoneAlias.equals("AKST")) {
System.out.println(Arrays.toString(zoneAliases));
continue zoneStrings;
}
}
}
System.out.println();
// Notice AKST zone, which is ambiguous as it might mean any number of Alaskan zones
final String input = "Thu Nov 13 04:35:51 AKST 2008";
System.out.println("input: " + input);
// a pattern that contains the timezone "z"
final String pattern = "EEE MMM d HH:mm:ss z yyyy";
// What we expect (use java.time API)
final long expectTs = 1226583351000L;
final DateTimeFormatter javaTimeDTF = DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH);
assert expectTs == javaTimeDTF.parse(input, Instant::from).toEpochMilli()
: "Unexpected result from java.time";
System.out.println("parse then format with America/Anchorage: " + javaTimeDTF.format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(expectTs), ZoneId.of("America/Anchorage"))));
System.out.println("parse then format with America/Metlakatla: " + javaTimeDTF.format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(expectTs), ZoneId.of("America/Metlakatla"))));
System.out.println("parse then format as ISO8601: " + Instant.ofEpochMilli(expectTs));
System.out.println();
// Here's what breaks. This code is very standard looking, and ought not to depend on the platform default TimeZone,
// even if "ASKT" is perhaps ambiguous.
SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.ENGLISH);
//good practice for server software but doesn't actually matter for the above pattern as it contains the zone
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
final long resultTs = sdf.parse(input).getTime();
System.out.println("result as ISO8601: " + Instant.ofEpochMilli(resultTs));
System.out.flush();
assert expectTs == resultTs : "Unexpected result from SimpleDateFormat: " + resultTs; // this varies depending on the default timezone!!!
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Use java.time API :-) Or if SimpleDateFormat must be used, then insist that the default time zone be configured to UTC.
FREQUENCY : always