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

Historical timezone rules missing; no clear way to override timezone rules

XMLWordPrintable

    • generic
    • generic

      A DESCRIPTION OF THE PROBLEM :
      Due to an update of tzdb in Java minor versions 11.0.17 and 17.0.5, pre-1970 timezone transitions for Europe/Amsterdam (amongst others) were left out of the timezone rules database shipped with the JDK (JDK-8292223).

      Because our product depends on a consistent historical representation of the Europe/Amsterdam timezone transitions, I would like to provide the set of rules provided by the full TZ database release (i.e., the one built with PACKRATLIST). Doing this just for Europe/Amsterdam is fine in this case.

      I have explored the following options:

      java.time.zone.ZoneRulesProvider provides three ways to extend the timezone database:

      * Providing an alternative ZoneRulesProvider by setting a system property.
      * Loading additional ZoneRulesProvider instances via SPI.
      * Registering additional ZoneRulesProvider instances with a call to ZoneRulesProvider#registerProvider.

      Additionally:

      * Replacing the TZ database binary database file in the JDK distribution with one which includes these rules is an option.

      The first method, overriding the default ZoneRulesProvider, might be suitable to provide a wrapper around TzdbZoneRulesProvider which only overrides Europe/Amsterdam with the historical rules (defined in Java using a custom ZoneRules based on the full timezone data). This is tricky because all participating classes (like TzdbZoneRulesProvider) use private constructors and methods, necessitating either reflection on core JDK classes, or significant duplication of code from those classes. The latter actually works, but has a significant drawback. Our product runs as a war in Tomcat, and by the time our war is starting, ZoneRulesProvider has already loaded TzdbZoneRulesProvider and overriding it is no longer possible.

      The SPI route also fails for the same reason.

      Directly registering an additional ZoneRulesProvider instance does work, but ZoneRulesProvider is designed to disallow overriding of existing rules, so by the time I call ZoneRulesProvider#registerProvider, I cannot use Europe/Amsterdam, because the truncated version from TZ database is already registered.

      Finally, I can replace the database file in the JDK's Java home, but while this would work, it is undesirable:

      * It would mean providing our own TZ database build, and keeping that up to date because it affects all timezones, not just Europe/Amsterdam.
      * Replacing files in deployed JDK's is not something operations is very keen on.

      What does work is registering our own provider with a custom zone-id like Europe/Amstedam-hist, but this would export this issue beyond the bounds of the timezone handling code in classes like ZonedDateTime. I would very much like to avoid introducing a non-standard zone-id in our software, because the potential for bugs is bigger. Ideally, for most of our software Europe/Amsterdam should keep its meaning.

      So with the current update of the JDK to the trimmed down TZ database, I am missing a way to override timezone rules for existing zones programmatically.

      REGRESSION : Last worked in version 11

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Use Java 11.0.17 or 17.0.5 or newer, and execute the test case below. Basically any pre-1945 dates for Europe/Amsterdam will have the wrong offset.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      1937-05-22T03:00:00.000+01:19:32
      ACTUAL -
      1937-05-22T02:40:28.000+01:00:00

      ---------- BEGIN SOURCE ----------
              final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
                      .append(ISO_LOCAL_DATE)
                      .appendLiteral('T')
                      .appendValue(HOUR_OF_DAY, 2)
                      .appendLiteral(':')
                      .appendValue(MINUTE_OF_HOUR, 2)
                      .optionalStart()
                      .appendLiteral(':')
                      .appendValue(SECOND_OF_MINUTE, 2)
                      .optionalStart()
                      .appendFraction(NANO_OF_SECOND, 3, 3, true)
                      .appendOffset("+HH:MM:SS", "Z")
                      .toFormatter();

              Instant instant = Instant.ofEpochMilli(-1029190772000L);
              assertThat(
                      formatter.format(instant.atZone(ZoneId.of("Europe/Amsterdam"))),
                      is("1937-05-22T03:00:00.000+01:19:32")
              );
      ---------- END SOURCE ----------

      FREQUENCY : always


            naoto Naoto Sato
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: