2007-02-09 Jakub Jelinek <jakub@redhat.com>
authoraph <aph@138bc75d-0d04-0410-961f-82ee72b054a4>
Mon, 12 Feb 2007 14:39:44 +0000 (14:39 +0000)
committeraph <aph@138bc75d-0d04-0410-961f-82ee72b054a4>
Mon, 12 Feb 2007 14:39:44 +0000 (14:39 +0000)
* java/util/VMTimeZone.java: Rewrite to handle both the old
'TZif\0' format and the new one.

git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@121845 138bc75d-0d04-0410-961f-82ee72b054a4

libjava/ChangeLog
libjava/classpath/lib/java/util/VMTimeZone.class
libjava/java/util/VMTimeZone.java

index 1c39b14..39a5902 100644 (file)
@@ -1,3 +1,8 @@
+2007-02-09  Jakub Jelinek  <jakub@redhat.com>
+
+       * java/util/VMTimeZone.java: Rewrite to handle both the old
+       'TZif\0' format and the new one.
+       
 2007-02-10  Andrew Haley  <aph@redhat.com>
 
        PR java/30742
index 351461d..a175a44 100644 (file)
Binary files a/libjava/classpath/lib/java/util/VMTimeZone.class and b/libjava/classpath/lib/java/util/VMTimeZone.class differ
index 86b6258..27bab93 100644 (file)
@@ -1,5 +1,5 @@
 /* java.util.VMTimeZone
-   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004
+   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2007
    Free Software Foundation, Inc.
 
 This file is part of GNU Classpath.
@@ -40,6 +40,9 @@ exception statement from your version. */
 package java.util;
 
 import gnu.classpath.Configuration;
+import java.util.TimeZone;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
 
 import java.io.*;
 
@@ -206,71 +209,152 @@ final class VMTimeZone
     File f = new File(file);
     if (!f.exists())
       return null;
-    
+
     DataInputStream dis = null;
     try
       {
-        FileInputStream fis = new FileInputStream(f);
-        BufferedInputStream bis = new BufferedInputStream(fis);
-        dis = new DataInputStream(bis);
-       
-        // Make sure we are reading a tzfile.
-        byte[] tzif = new byte[4];
-        dis.readFully(tzif);
-        if (tzif[0] == 'T' && tzif[1] == 'Z'
-            && tzif[2] == 'i' && tzif[3] == 'f')
-         // Reserved bytes, ttisgmtcnt, ttisstdcnt and leapcnt
-         skipFully(dis, 16 + 3 * 4);
+       FileInputStream fis = new FileInputStream(f);
+       BufferedInputStream bis = new BufferedInputStream(fis);
+       dis = new DataInputStream(bis);
+
+       // Make sure we are reading a tzfile.
+       byte[] tzif = new byte[5];
+       dis.readFully(tzif);
+       int tzif2 = 4;
+       if (tzif[0] == 'T' && tzif[1] == 'Z'
+           && tzif[2] == 'i' && tzif[3] == 'f')
+         {
+           if (tzif[4] >= '2')
+             tzif2 = 8;
+           // Reserved bytes
+           skipFully(dis, 16 - 1);
+         }
        else
          // Darwin has tzdata files that don't start with the TZif marker
-         skipFully(dis, 16 + 3 * 4 - 4);
-       
+         skipFully(dis, 16 - 5);
+
+       String id = null;
+       int ttisgmtcnt = dis.readInt();
+       int ttisstdcnt = dis.readInt();
+       int leapcnt = dis.readInt();
        int timecnt = dis.readInt();
        int typecnt = dis.readInt();
+       int charcnt = dis.readInt();
+       if (tzif2 == 8)
+         {
+           skipFully(dis, timecnt * (4 + 1) + typecnt * (4 + 1 + 1) + charcnt
+                          + leapcnt * (4 + 4) + ttisgmtcnt + ttisstdcnt);
+
+           dis.readFully(tzif);
+           if (tzif[0] != 'T' || tzif[1] != 'Z' || tzif[2] != 'i'
+               || tzif[3] != 'f' || tzif[4] < '2')
+             return null;
+
+           // Reserved bytes
+           skipFully(dis, 16 - 1);
+           ttisgmtcnt = dis.readInt();
+           ttisstdcnt = dis.readInt();
+           leapcnt = dis.readInt();
+           timecnt = dis.readInt();
+           typecnt = dis.readInt();
+           charcnt = dis.readInt();
+         }
        if (typecnt > 0)
          {
-           int charcnt = dis.readInt();
-           // Transition times plus indexed transition times.
-           skipFully(dis, timecnt * (4 + 1));
-           
-           // Get last gmt_offset and dst/non-dst time zone names.
-           int abbrind = -1;
+           int seltimecnt = timecnt;
+           if (seltimecnt > 16)
+             seltimecnt = 16;
+
+           long[] times = new long[seltimecnt];
+           int[] types = new int[seltimecnt];
+
+           // Transition times
+           skipFully(dis, (timecnt - seltimecnt) * tzif2);
+
+           for (int i = 0; i < seltimecnt; i++)
+             if (tzif2 == 8)
+               times[i] = dis.readLong();
+             else
+               times[i] = (long) dis.readInt();
+
+           // Transition types
+           skipFully(dis, timecnt - seltimecnt);
+           for (int i = 0; i < seltimecnt; i++)
+             {
+               types[i] = dis.readByte();
+               if (types[i] < 0)
+                 types[i] += 256;
+             }
+
+           // Get std/dst_offset and dst/non-dst time zone names.
+           int std_abbrind = -1;
            int dst_abbrind = -1;
-           int gmt_offset = 0;
-           while (typecnt-- > 0)
+           int std_offset = 0;
+           int dst_offset = 0;
+           int std_ind = -1;
+           int dst_ind = -1;
+
+           int alternation = 0;
+           if (seltimecnt >= 4 && types[0] != types[1]
+               && types[0] < typecnt && types[1] < typecnt)
+             {
+               // Verify only two types are involved
+               // in the transitions and they alternate.
+               alternation = 1;
+               for (int i = 2; i < seltimecnt; i++)
+                 if (types[i] != types[i % 2])
+                   alternation = 0;
+             }
+
+           // If a timezone previously used DST, but no longer does
+           // (or no longer will in the near future, say 5 years),
+           // then always pick only the std zone type corresponding
+           // to latest applicable transition.
+           if (seltimecnt > 0
+               && times[seltimecnt - 1]
+                  < System.currentTimeMillis() / 1000 + 5 * 365 * 86400)
+             alternation = -1;
+
+           for (int i = 0; i < typecnt; i++)
              {
                // gmtoff
                int offset = dis.readInt();
                int dst = dis.readByte();
+               int abbrind = dis.readByte();
                if (dst == 0)
                  {
-                   abbrind = dis.readByte();
-                   gmt_offset = offset;
+                   if (alternation == 0
+                       || (alternation == 1
+                           && (i == types[0] || i == types[1]))
+                       || (alternation == -1 && i == types[seltimecnt - 1]))
+                     {
+                       std_abbrind = abbrind;
+                       std_offset = offset * -1;
+                       std_ind = i;
+                     }
+                 }
+               else if (alternation >= 0)
+                 {
+                   if (alternation == 0 || i == types[0] || i == types[1])
+                     {
+                       dst_abbrind = abbrind;
+                       dst_offset = offset * -1;
+                       dst_ind = i;
+                     }
                  }
-               else
-                 dst_abbrind = dis.readByte();
              }
-           
-           // gmt_offset is the offset you must add to UTC/GMT to
-           // get the local time, we need the offset to add to
-           // the local time to get UTC/GMT.
-           gmt_offset *= -1;
-           
-           // Turn into hours if possible.
-           if (gmt_offset % 3600 == 0)
-             gmt_offset /= 3600;
-           
-           if (abbrind >= 0)
+
+           if (std_abbrind >= 0)
              {
                byte[] names = new byte[charcnt];
                dis.readFully(names);
-               int j = abbrind;
+               int j = std_abbrind;
                while (j < charcnt && names[j] != 0)
                  j++;
-               
-               String zonename = new String(names, abbrind, j - abbrind,
-                                            "ASCII");
-               
+
+               String zonename = new String(names, std_abbrind,
+                                            j - std_abbrind, "ASCII");
+
                String dst_zonename;
                if (dst_abbrind >= 0)
                  {
@@ -282,26 +366,289 @@ final class VMTimeZone
                  }
                else
                  dst_zonename = "";
-               
+
+               String[] change_spec = { null, null };
+               if (dst_abbrind >= 0 && alternation > 0)
+                 {
+                   // Guess rules for the std->dst and dst->std transitions
+                   // from the transition times since Epoch.
+                   // tzdata actually uses only 3 forms of rules:
+                   // fixed date within a month, e.g. change on April, 5th
+                   // 1st weekday on or after Nth: change on Sun>=15 in April
+                   // last weekday in a month: change on lastSun in April
+                   GregorianCalendar cal
+                     = new GregorianCalendar (TimeZone.getTimeZone("GMT"));
+
+                   int[] values = new int[2 * 11];
+                   int i;
+                   for (i = seltimecnt - 1; i >= 0; i--)
+                     {
+                       int base = (i % 2) * 11;
+                       int offset = types[i] == dst_ind ? std_offset : dst_offset;
+                       cal.setTimeInMillis((times[i] - offset) * 1000);
+                       if (i >= seltimecnt - 2)
+                         {
+                           values[base + 0] = cal.get(Calendar.YEAR);
+                           values[base + 1] = cal.get(Calendar.MONTH);
+                           values[base + 2] = cal.get(Calendar.DAY_OF_MONTH);
+                           values[base + 3]
+                             = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
+                           values[base + 4] = cal.get(Calendar.DAY_OF_WEEK);
+                           values[base + 5] = cal.get(Calendar.HOUR_OF_DAY);
+                           values[base + 6] = cal.get(Calendar.MINUTE);
+                           values[base + 7] = cal.get(Calendar.SECOND);
+                           values[base + 8] = values[base + 2]; // Range start
+                           values[base + 9] = values[base + 2]; // Range end
+                           values[base + 10] = 0; // Determined type
+                         }
+                       else
+                         {
+                           int year = cal.get(Calendar.YEAR);
+                           int month = cal.get(Calendar.MONTH);
+                           int day_of_month = cal.get(Calendar.DAY_OF_MONTH);
+                           int month_days
+                             = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
+                           int day_of_week = cal.get(Calendar.DAY_OF_WEEK);
+                           int hour = cal.get(Calendar.HOUR_OF_DAY);
+                           int minute = cal.get(Calendar.MINUTE);
+                           int second = cal.get(Calendar.SECOND);
+                           if (year != values[base + 0] - 1
+                               || month != values[base + 1]
+                               || hour != values[base + 5]
+                               || minute != values[base + 6]
+                               || second != values[base + 7])
+                             break;
+                           if (day_of_week == values[base + 4])
+                             {
+                               // Either a Sun>=8 or lastSun rule.
+                               if (day_of_month < values[base + 8])
+                                 values[base + 8] = day_of_month;
+                               if (day_of_month > values[base + 9])
+                                 values[base + 9] = day_of_month;
+                               if (values[base + 10] < 0)
+                                 break;
+                               if (values[base + 10] == 0)
+                                 {
+                                   values[base + 10] = 1;
+                                   // If day of month > 28, this is
+                                   // certainly lastSun rule.
+                                   if (values[base + 2] > 28)
+                                     values[base + 2] = 3;
+                                   // If day of month isn't in the last
+                                   // week, it can't be lastSun rule.
+                                   else if (values[base + 2]
+                                            <= values[base + 3] - 7)
+                                     values[base + 3] = 2;
+                                 }
+                               if (values[base + 10] == 1)
+                                 {
+                                   // If day of month is > 28, this is
+                                   // certainly lastSun rule.
+                                   if (day_of_month > 28)
+                                     values[base + 10] = 3;
+                                   // If day of month isn't in the last
+                                   // week, it can't be lastSun rule.
+                                   else if (day_of_month <= month_days - 7)
+                                     values[base + 10] = 2;
+                                 }
+                               else if ((values[base + 10] == 2
+                                         && day_of_month > 28)
+                                        || (values[base + 10] == 3
+                                            && day_of_month
+                                               <= month_days - 7))
+                                 break;
+                             }
+                           else
+                             {
+                               // Must be fixed day in month rule.
+                               if (day_of_month != values[base + 2]
+                                   || values[base + 10] > 0)
+                                 break;
+                               values[base + 4] = day_of_week;
+                               values[base + 10] = -1;
+                             }
+                           values[base + 0] -= 1;
+                         }
+                     }
+                   if (i < 0)
+                     {
+                       for (i = 0; i < 2; i++)
+                         {
+                           int base = 11 * i;
+                           if (values[base + 10] == 0)
+                             continue;
+                           if (values[base + 10] == -1)
+                             {
+                               int[] dayCount
+                                 = { 0, 31, 59, 90, 120, 151,
+                                     181, 212, 243, 273, 304, 334 };
+                               int d = dayCount[values[base + 1]
+                                                - Calendar.JANUARY];
+                               d += values[base + 2];
+                               change_spec[i] = ",J" + Integer.toString(d);
+                             }
+                           else if (values[base + 10] == 2)
+                             {
+                               // If we haven't seen all days of the week,
+                               // we can't be sure what the rule really is.
+                               if (values[base + 8] + 6 != values[base + 9])
+                                 continue;
+
+                               // FIXME: Sun >= 5 is representable in
+                               // SimpleTimeZone, but not in POSIX TZ env
+                               // strings.  Should we change readtzFile
+                               // to actually return a SimpleTimeZone
+                               // rather than POSIX TZ string?
+                               if ((values[base + 8] % 7) != 1)
+                                 continue;
+
+                               int d;
+                               d = values[base + 1] - Calendar.JANUARY + 1;
+                               change_spec[i] = ",M" + Integer.toString(d);
+                               d = (values[base + 8] + 6) / 7;
+                               change_spec[i] += "." + Integer.toString(d);
+                               d = values[base + 4] - Calendar.SUNDAY;
+                               change_spec[i] += "." + Integer.toString(d);
+                             }
+                           else
+                             {
+                               // If we don't know whether this is lastSun or
+                               // Sun >= 22 rule.  That can be either because
+                               // there was insufficient number of
+                               // transitions, or February, where it is quite
+                               // probable we haven't seen any 29th dates.
+                               // For February, assume lastSun rule, otherwise
+                               // punt.
+                               if (values[base + 10] == 1
+                                   && values[base + 1] != Calendar.FEBRUARY)
+                                 continue;
+
+                               int d;
+                               d = values[base + 1] - Calendar.JANUARY + 1;
+                               change_spec[i] = ",M" + Integer.toString(d);
+                               d = values[base + 4] - Calendar.SUNDAY;
+                               change_spec[i] += ".5." + Integer.toString(d);
+                             }
+
+                           // Don't add time specification if time is
+                           // 02:00:00.
+                           if (values[base + 5] != 2
+                               || values[base + 6] != 0
+                               || values[base + 7] != 0)
+                             {
+                               int d = values[base + 5];
+                               change_spec[i] += "/" + Integer.toString(d);
+                               if (values[base + 6] != 0
+                                   || values[base + 7] != 0)
+                                 {
+                                   d = values[base + 6];
+                                   if (d < 10)
+                                     change_spec[i]
+                                       += ":0" + Integer.toString(d);
+                                   else
+                                     change_spec[i]
+                                       += ":" + Integer.toString(d);
+                                   d = values[base + 7];
+                                   if (d >= 10)
+                                     change_spec[i]
+                                       += ":" + Integer.toString(d);
+                                   else if (d > 0)
+                                     change_spec[i]
+                                       += ":0" + Integer.toString(d);
+                                 }
+                             }
+                         }
+                       if (types[0] == std_ind)
+                         {
+                           String tmp = change_spec[0];
+                           change_spec[0] = change_spec[1];
+                           change_spec[1] = tmp;
+                         }
+                     }
+                 }
+
                // Only use gmt offset when necessary.
                // Also special case GMT+/- timezones.
-               String offset_string;
-               if ("".equals(dst_zonename)
-                   && (gmt_offset == 0
+               String offset_string, dst_offset_string = "";
+               if (dst_abbrind < 0
+                   && (std_offset == 0
                        || zonename.startsWith("GMT+")
                        || zonename.startsWith("GMT-")))
                  offset_string = "";
                else
-                 offset_string = Integer.toString(gmt_offset);
-               
-               String id = zonename + offset_string + dst_zonename;
-               
-               return id;
+                 {
+                   offset_string = Integer.toString(std_offset / 3600);
+                   int seconds = std_offset % 3600;
+                   if (seconds != 0)
+                     {
+                       if (seconds < 0)
+                         seconds *= -1;
+                       if (seconds < 600)
+                         offset_string
+                           += ":0" + Integer.toString(seconds / 60);
+                       else
+                         offset_string
+                           += ":" + Integer.toString(seconds / 60);
+                       seconds = seconds % 60;
+                       if (seconds >= 10)
+                         offset_string
+                           += ":" + Integer.toString(seconds);
+                       else if (seconds > 0)
+                         offset_string
+                           += ":0" + Integer.toString(seconds);
+                     }
+                   if (dst_abbrind >= 0
+                       && dst_offset != std_offset - 3600)
+                     {
+                       dst_offset_string
+                         = Integer.toString(dst_offset / 3600);
+                       seconds = dst_offset % 3600;
+                       if (seconds != 0)
+                         {
+                           if (seconds < 0)
+                             seconds *= -1;
+                           if (seconds < 600)
+                             dst_offset_string
+                               += ":0" + Integer.toString(seconds / 60);
+                           else
+                             dst_offset_string
+                               += ":" + Integer.toString(seconds / 60);
+                           seconds = seconds % 60;
+                           if (seconds >= 10)
+                             dst_offset_string
+                               += ":" + Integer.toString(seconds);
+                           else if (seconds > 0)
+                             dst_offset_string
+                               += ":0" + Integer.toString(seconds);
+                         }
+                     }
+                 }
+
+               if (dst_abbrind < 0)
+                 id = zonename + offset_string;
+               else if (change_spec[0] != null && change_spec[1] != null)
+                 id = zonename + offset_string + dst_zonename
+                      + dst_offset_string + change_spec[0] + change_spec[1];
              }
+           else if (tzif2 == 8)
+             skipFully(dis, charcnt);
          }
-       
-       // Something didn't match while reading the file.
-       return null;
+       else if (tzif2 == 8)
+         skipFully(dis, timecnt * (8 + 1) + typecnt * (4 + 1 + 1) + charcnt);
+
+       if (tzif2 == 8)
+         {
+           // Skip over the rest of 64-bit data
+           skipFully(dis, leapcnt * (8 + 4) + ttisgmtcnt + ttisstdcnt);
+           if (dis.readByte() == '\n')
+             {
+               String posixtz = dis.readLine();
+               if (posixtz.length() > 0)
+                 id = posixtz;
+             }
+         }
+
+       return id;
       }
     catch (IOException ioe)
       {