Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8206372

SimpleDateFormat depends on platform timezone in cases when it should not

    Details

    • Subcomponent:
    • CPU:
      generic
    • OS:
      generic

      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


        Attachments

          Activity

            People

            • Assignee:
              naoto Naoto Sato
              Reporter:
              webbuggrp Webbug Group
            • Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: