JDK-8206372 : SimpleDateFormat depends on platform timezone in cases when it should not
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.text
  • Affected Version: 8,9,10,11
  • Priority: P4
  • Status: Resolved
  • Resolution: Won't Fix
  • OS: generic
  • CPU: generic
  • Submitted: 2018-06-28
  • Updated: 2018-07-05
  • Resolved: 2018-07-05
Description
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



Comments
Although I am not the original author of SimpleDateFormat, I guess the intention of the code in question is to provide the most possible time zone that the user is using. Yes, it depends on user's settings, but the name (e.g. AKST) is inherently ambiguous, JDK needs to guess the best one for the user. It has been this way since, at least JDK6, so changing the logic that parses ambiguous time zone name will cause regressions.
05-07-2018

To reproduce the issue, run the attached test case . Following are the results with : JDK 8u171 - Fail JDK 10.0.1 - Fail JDK 11-ea + 18 - Fail Output : input: Thu Nov 13 04:35:51 AKST 2008 parse then format with America/Anchorage: Thu Nov 13 04:35:51 AKST 2008 parse then format with America/Metlakatla: Thu Nov 13 05:35:51 AKST 2008 parse then format as ISO8601: 2008-11-13T13:35:51Z result as ISO8601: 2008-11-13T12:35:51Z Exception in thread "main" java.lang.AssertionError: Unexpected result from SimpleDateFormat: 1226579751000 at SimpleDateFormatTimeZoneBug.main(SimpleDateFormatTimeZoneBug.java:62)
04-07-2018