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

TimeZone.getTimeZone(ZoneOffset) does not work for all ZoneOffsets and returns GMT unexpected

XMLWordPrintable

    • Icon: Enhancement Enhancement
    • Resolution: Fixed
    • Icon: P4 P4
    • 19
    • 8u321, 11.0.14-oracle, 18, 19-pool
    • core-libs
    • 8
    • b23
    • generic
    • generic

      While implementing support for OffsetDateTime and the conversion to java.sql.Timestamp in the PostgresSQL JDBC driver, we stumbled on a problem with creating "legacy" TimeZone objects from the modern "ZoneOffset" (a subclass of "ZoneId"). A ZoneOffset may contain a fixed offset measured down to seconds.

      The PostgreSQL server returns timestamptz with absolute offsets from the server. For dates before 19:00 in some timezones the raw offset is not just hours/half hours, but it sometimes goes down to seconds (historic geographical zone based on latitude/longitude). In the JDBC driver we create ZoneOffsets in the modern parsing code. But when we tried to convert those dates to the legacy java.sql.Timestamp, we needed a legacy java.util.TimeZone for constructing a legacy GregorianCalendar.

      The most obvious way to convert this is to use TimeZone.getTimeZone(ZoneId) that can take a ZoneOffset. Unfortunately it returns the GMT timezone for ZoneOffsets which have seconds. This is totally unexpected! The reason for this comes from the fact that the code converts the ZoneOffset to a String "GMT".concat(ZoneOffset.toString()) and looks it up. The parser for GMT timezones cannot parse seconds, it stops at minutes. When the zone is not found it returns GMT instead throwing an Exception. This is mentioned in the Javadocs "the specified TimeZone, or the GMT zone if the given ID cannot be understood.", but this makes no sense! ZoneOffset instances should always return a correct time zone, because they are clearly defined, no conversion errors can/should occur.

      In addition we figured out that the conversion to string and then parsing it again is a bit slow and leads to problems like this one.

      In Postgresql we fixed the code on our side to create a SimpleTimeZone from the raw offset. This is in my opinion also the only correct thing to do in Timezone.getTimeZone(). The method should be implemented to do an instanceof check on ZoneOffset and then simply return a SimpleTimeZone!

      One additional thing: SimpleTimeZone#toZoneId() should be implemented and not inherited by returning a ZoneOffset by just using the plain offset (if it has no DST changes). So the inverse code is also not working. So there's not even a roundtrip!

      I attached a simple program showing the issues and also the "correct" implementation, output for some Zoneoffsets:
      ZoneOffset: +01:00
      ZoneOffset milliseconds: 3600000
      TimeZone.getTimeZone: sun.util.calendar.ZoneInfo[id="GMT+01:00",offset=3600000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
      TimeZone.getTimeZone milliseconds: 3600000
      Manual SimpleTimeZone: java.util.SimpleTimeZone[id=GMT+01:00,offset=3600000,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0]
      Manual SimpleTimeZone milliseconds: 3600000

      ZoneOffset: +01:01
      ZoneOffset milliseconds: 3660000
      TimeZone.getTimeZone: sun.util.calendar.ZoneInfo[id="GMT+01:01",offset=3660000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
      TimeZone.getTimeZone milliseconds: 3660000
      Manual SimpleTimeZone: java.util.SimpleTimeZone[id=GMT+01:01,offset=3660000,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0]
      Manual SimpleTimeZone milliseconds: 3660000

      ZoneOffset: +01:01:01
      ZoneOffset milliseconds: 3661000
      TimeZone.getTimeZone: sun.util.calendar.ZoneInfo[id="GMT",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
      TimeZone.getTimeZone milliseconds: 0
      Manual SimpleTimeZone: java.util.SimpleTimeZone[id=GMT+01:01:01,offset=3661000,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0]
      Manual SimpleTimeZone milliseconds: 3661000

      ZoneOffset: -01:00
      ZoneOffset milliseconds: -3600000
      TimeZone.getTimeZone: sun.util.calendar.ZoneInfo[id="GMT-01:00",offset=-3600000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
      TimeZone.getTimeZone milliseconds: -3600000
      Manual SimpleTimeZone: java.util.SimpleTimeZone[id=GMT-01:00,offset=-3600000,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0]
      Manual SimpleTimeZone milliseconds: -3600000

      ZoneOffset: -01:01
      ZoneOffset milliseconds: -3660000
      TimeZone.getTimeZone: sun.util.calendar.ZoneInfo[id="GMT-01:01",offset=-3660000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
      TimeZone.getTimeZone milliseconds: -3660000
      Manual SimpleTimeZone: java.util.SimpleTimeZone[id=GMT-01:01,offset=-3660000,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0]
      Manual SimpleTimeZone milliseconds: -3660000

      ZoneOffset: -01:01:01
      ZoneOffset milliseconds: -3661000
      TimeZone.getTimeZone: sun.util.calendar.ZoneInfo[id="GMT",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
      TimeZone.getTimeZone milliseconds: 0
      Manual SimpleTimeZone: java.util.SimpleTimeZone[id=GMT-01:01:01,offset=-3661000,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0]
      Manual SimpleTimeZone milliseconds: -3661000

            naoto Naoto Sato
            uschindler Uwe Schindler
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: