classpath-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[cp-patches] FYI: java.util.TimeZone merge with libgcj


From: Mark Wielaard
Subject: [cp-patches] FYI: java.util.TimeZone merge with libgcj
Date: Sat, 28 Aug 2004 21:35:32 +0200

Hi,

This merges the java source code of TimeZone with libgcj.
It improves our detection of the system time zone a lot on some systems.
This is only the "generic" part. I am still working on importing the
native side. This will also mean splitting TimeZone in TimeZone and a
platform specific VMTimeZone class since it is very GNU/Posix specific
at the moment.

2004-08-28  Mark Wielaard  <address@hidden>

       * java/util/TimeZone.java (defaultZone): Try a couple of ways to get
       a TimeZoneId string and then try to convert that to a TimeZone with
       getDefaultSystemTimeZone(String).
       (timezones0): Changed type from Hashtable to HashMap.
       (timezones): Create HashMap, not Hashtable.
       (getDefaultTimeZone): New method, rewritten from CNI version.
       (readTimeZoneFile): New method.
       (readtzFile): Likewise.
       (skipFully): Likewise.

Since this relies on a working System.getenv() I will also check in that
patch.

Cheers,

Mark
Index: java/util/TimeZone.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/util/TimeZone.java,v
retrieving revision 1.23
diff -u -r1.23 TimeZone.java
--- java/util/TimeZone.java     22 Apr 2004 11:24:39 -0000      1.23
+++ java/util/TimeZone.java     28 Aug 2004 19:34:50 -0000
@@ -40,6 +40,9 @@
 package java.util;
 import gnu.classpath.Configuration;
 
+import java.io.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.text.DateFormatSymbols;
 
 /**
@@ -83,44 +86,99 @@
    * The default time zone, as returned by getDefault.
    */
   private static TimeZone defaultZone0;
-  /* initialize this static field lazily to overhead if
-   * it is not needed: 
+
+  /**
+   * Tries to get the default TimeZone for this system if not already
+   * set.  It will call <code>getDefaultTimeZone(String)</code> with
+   * the result of
+   * <code>System.getProperty("user.timezone")</code>,
+   * <code>System.getenv("TZ")</code>,
+   * <code>readTimeZoneFile("/etc/timezone")</code>,
+   * <code>readtzFile("/etc/localtime")</code> and
+   * <code>getDefaultTimeZoneId()</code>
+   * till a supported TimeZone is found.
+   * If every method fails GMT is returned.
    */
-  private static synchronized TimeZone defaultZone() {
+  private static synchronized TimeZone defaultZone()
+  {
     /* Look up default timezone */
     if (defaultZone0 == null) 
       {
-       if (Configuration.INIT_LOAD_LIBRARY)
-         {
-           System.loadLibrary("javautil");
-         }
-       String tzid = System.getProperty("user.timezone");
-       
-       if (tzid == null)
-         tzid = getDefaultTimeZoneId();
-       
-       if (tzid == null)
-         tzid = "GMT";
-       
-       defaultZone0 = getTimeZone(tzid);
+       defaultZone0 = (TimeZone) AccessController.doPrivileged
+         (new PrivilegedAction()
+           {
+             public Object run()
+             {
+               if (Configuration.INIT_LOAD_LIBRARY)
+                 {
+                   System.loadLibrary("javautil");
+                 }
+               
+               TimeZone zone = null;
+               
+               // Prefer System property user.timezone.
+               String tzid = System.getProperty("user.timezone");
+               if (tzid != null && !tzid.equals(""))
+                 zone = getDefaultTimeZone(tzid);
+               
+               // See if TZ environment variable is set and accessible.
+               if (zone == null)
+                 {
+                   tzid = System.getenv("TZ");
+                   if (tzid != null && !tzid.equals(""))
+                     zone = getDefaultTimeZone(tzid);
+                 }
+               
+               // Try to parse /etc/timezone.
+               if (zone == null)
+                 {
+                   tzid = readTimeZoneFile("/etc/timezone");
+                   if (tzid != null && !tzid.equals(""))
+                     zone = getDefaultTimeZone(tzid);
+                 }
+               
+               // Try to parse /etc/localtime
+               if (zone == null)
+                 {
+                   tzid = readtzFile("/etc/localtime");
+                   if (tzid != null && !tzid.equals(""))
+                     zone = getDefaultTimeZone(tzid);
+                 }
+               
+               // Try some system specific way
+               if (zone == null)
+                 {
+                   tzid = getDefaultTimeZoneId();
+                   if (tzid != null && !tzid.equals(""))
+                     zone = getDefaultTimeZone(tzid);
+                 }
+               
+               // Fall back on GMT.
+               if (zone == null)
+                 zone = (TimeZone) timezones().get("GMT");
+               
+               return zone;
+             }
+           });
       }
+    
     return defaultZone0; 
   }
-
-
+  
   private static final long serialVersionUID = 3581463369166924961L;
 
   /**
-   * Hashtable for timezones by ID.  
+   * HashMap for timezones by ID.  
    */
-  private static Hashtable timezones0;
+  private static HashMap timezones0;
   /* initialize this static field lazily to overhead if
    * it is not needed: 
    */
-  private static synchronized Hashtable timezones() {
-    if (timezones0==null) 
+  private static synchronized HashMap timezones()
+  {
+    if (timezones0 == null) 
       {
-       Hashtable timezones = new Hashtable();
+       HashMap timezones = new HashMap();
        timezones0 = timezones;
 
        TimeZone tz;
@@ -784,19 +842,363 @@
     return timezones0;
   }
 
-
-  /* This method returns us a time zone id string which is in the
-     form <standard zone name><GMT offset><daylight time zone name>.
-     The GMT offset is in seconds, except where it is evenly divisible
-     by 3600, then it is in hours.  If the zone does not observe
-     daylight time, then the daylight zone name is omitted.  Examples:
-     in Chicago, the timezone would be CST6CDT.  In Indianapolis 
-     (which does not have Daylight Savings Time) the string would
-     be EST5
+  /**
+   * This method returns a time zone id string which is in the form
+   * (standard zone name) or (standard zone name)(GMT offset) or
+   * (standard zone name)(GMT offset)(daylight time zone name).  The
+   * GMT offset can be in seconds, or where it is evenly divisible by
+   * 3600, then it can be in hours.  The offset must be the time to
+   * add to the local time to get GMT.  If a offset is given and the
+   * time zone observes daylight saving then the (daylight time zone
+   * name) must also be given (otherwise it is assumed the time zone
+   * does not observe any daylight savings).
+   * <p>
+   * The result of this method is given to getDefaultTimeZone(String)
+   * which tries to map the time zone id to a known TimeZone.  See
+   * that method on how the returned String is mapped to a real
+   * TimeZone object.
    */
   private static native String getDefaultTimeZoneId();
 
   /**
+   * Tries to read the time zone name from a file. Only the first
+   * consecutive letters, digits, slashes, dashes and underscores are
+   * read from the file. If the file cannot be read or an IOException
+   * occurs null is returned.
+   * <p>
+   * The /etc/timezone file is not standard, but a lot of systems have
+   * it. If it exist the first line always contains a string
+   * describing the timezone of the host of domain. Some systems
+   * contain a /etc/TIMEZONE file which is used to set the TZ
+   * environment variable (which is checked before /etc/timezone is
+   * read).
+   */
+  private static String readTimeZoneFile(String file)
+  {
+    File f = new File(file);
+    if (!f.exists())
+      return null;
+
+    InputStreamReader isr = null;
+    try
+      {
+       FileInputStream fis = new FileInputStream(f);
+       BufferedInputStream bis = new BufferedInputStream(fis);
+       isr = new InputStreamReader(bis);
+       
+       StringBuffer sb = new StringBuffer();
+       int i = isr.read();
+       while (i != -1)
+         {
+           char c = (char) i;
+           if (Character.isLetter(c) || Character.isDigit(c)
+               || c == '/' || c == '-' || c == '_')
+             {
+               sb.append(c);
+               i = isr.read();
+             }
+           else
+             break;
+         }
+       return sb.toString();
+      }
+    catch (IOException ioe)
+      {
+       // Parse error, not a proper tzfile.
+       return null;
+      }
+    finally
+      {
+       try
+         {
+           if (isr != null)
+             isr.close();
+         }
+       catch (IOException ioe)
+         {
+           // Error while close, nothing we can do.
+         }
+      }
+  }
+
+  /**
+   * Tries to read a file as a "standard" tzfile and return a time
+   * zone id string as expected by <code>getDefaultTimeZone(String)</code>.
+   * If the file doesn't exist, an IOException occurs or it isn't a tzfile
+   * that can be parsed null is returned.
+   * <p>
+   * The tzfile structure (as also used by glibc) is described in the Olson
+   * tz database archive as can be found at
+   * <code>ftp://elsie.nci.nih.gov/pub/</code>.
+   * <p>
+   * At least the following platforms support the tzdata file format
+   * and /etc/localtime (GNU/Linux, Darwin, Solaris and FreeBSD at
+   * least). Some systems (like Darwin) don't start the file with the
+   * required magic bytes 'TZif', this implementation can handle
+   * that).
+   */
+  private static String readtzFile(String file)
+  {
+    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);
+       else
+         // Darwin has tzdata files that don't start with the TZif marker
+         skipFully(dis, 16 + 3 * 4 - 4);
+       
+       int timecnt = dis.readInt();
+       int typecnt = 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 dst_abbrind = -1;
+           int gmt_offset = 0;
+           while (typecnt-- > 0)
+             {
+               // gmtoff
+               int offset = dis.readInt();
+               int dst = dis.readByte();
+               if (dst == 0)
+                 {
+                   abbrind = dis.readByte();
+                   gmt_offset = offset;
+                 }
+               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)
+             {
+               byte[] names = new byte[charcnt];
+               dis.readFully(names);
+               int j = abbrind;
+               while (j < charcnt && names[j] != 0)
+                 j++;
+               
+               String zonename = new String(names, abbrind, j - abbrind,
+                                            "ASCII");
+               
+               String dst_zonename;
+               if (dst_abbrind >= 0)
+                 {
+                   j = dst_abbrind;
+                   while (j < charcnt && names[j] != 0)
+                     j++;
+                   dst_zonename = new String(names, dst_abbrind,
+                                             j - dst_abbrind, "ASCII");
+                 }
+               else
+                 dst_zonename = "";
+               
+               // Only use gmt offset when necessary.
+               // Also special case GMT+/- timezones.
+               String offset_string;
+               if ("".equals(dst_zonename)
+                   && (gmt_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;
+             }
+         }
+       
+       // Something didn't match while reading the file.
+       return null;
+      }
+    catch (IOException ioe)
+      {
+       // Parse error, not a proper tzfile.
+       return null;
+      }
+    finally
+      {
+       try
+         {
+           if (dis != null)
+             dis.close();
+         }
+       catch(IOException ioe)
+         {
+           // Error while close, nothing we can do.
+         }
+      }
+  }
+  
+  /**
+   * Skips the requested number of bytes in the given InputStream.
+   * Throws EOFException if not enough bytes could be skipped.
+   * Negative numbers of bytes to skip are ignored.
+   */
+  private static void skipFully(InputStream is, long l) throws IOException
+  {
+    while (l > 0)
+      {
+        long k = is.skip(l);
+        if (k <= 0)
+          throw new EOFException();
+        l -= k;
+      }
+  }
+  
+  /**
+   * Maps a time zone name (with optional GMT offset and daylight time
+   * zone name) to one of the known time zones.  This method called
+   * with the result of <code>System.getProperty("user.timezone")</code>
+   * or <code>getDefaultTimeZoneId()</code>.  Note that giving one of
+   * the standard tz data names from ftp://elsie.nci.nih.gov/pub/ is
+   * preferred.  The time zone name can be given as follows:
+   * <code>(standard zone name)[(GMT offset)[(daylight time zone name)]]</code>
+   * <p>
+   * If only a (standard zone name) is given (no numbers in the
+   * String) then it gets mapped directly to the TimeZone with that
+   * name, if that fails null is returned.
+   * <p>
+   * A GMT offset is the offset to add to the local time to get GMT.
+   * If a (GMT offset) is included (either in seconds or hours) then
+   * an attempt is made to find a TimeZone name matching both the name
+   * and the offset (that doesn't observe daylight time, if the
+   * timezone observes daylight time then you must include a daylight
+   * time zone name after the offset), if that fails then a TimeZone
+   * with the given GMT offset is returned (whether or not the
+   * TimeZone observes daylight time is ignored), if that also fails
+   * the GMT TimeZone is returned.
+   * <p>
+   * If the String ends with (GMT offset)(daylight time zone name)
+   * then an attempt is made to find a TimeZone with the given name and
+   * GMT offset that also observes (the daylight time zone name is not
+   * currently used in any other way), if that fails a TimeZone with
+   * the given GMT offset that observes daylight time is returned, if
+   * that also fails the GMT TimeZone is returned.
+   * <p>
+   * Examples: In Chicago, the time zone id could be "CST6CDT", but
+   * the preferred name would be "America/Chicago".  In Indianapolis
+   * (which does not have Daylight Savings Time) the string could be
+   * "EST5", but the preferred name would be "America/Indianapolis".
+   * The standard time zone name for The Netherlands is "Europe/Amsterdam",
+   * but can also be given as "CET-1CEST".
+   */
+  private static TimeZone getDefaultTimeZone(String sysTimeZoneId)
+  {
+    // First find start of GMT offset info and any Daylight zone name.
+    int startGMToffset = 0;
+    int sysTimeZoneIdLength = sysTimeZoneId.length();
+    for (int i = 0; i < sysTimeZoneIdLength && startGMToffset == 0; i++)
+      {
+       char c = sysTimeZoneId.charAt(i);
+       if (c == '+' || c == '-' || Character.isDigit(c))
+         startGMToffset = i;
+      }
+    
+    String tzBasename;
+    if (startGMToffset == 0)
+      tzBasename = sysTimeZoneId;
+    else
+      tzBasename = sysTimeZoneId.substring (0, startGMToffset);
+    
+    int startDaylightZoneName = 0;
+    for (int i = sysTimeZoneIdLength - 1;
+        i >= 0 && !Character.isDigit(sysTimeZoneId.charAt(i)); --i)
+      startDaylightZoneName = i;
+    
+    boolean useDaylightTime = startDaylightZoneName > 0;
+    
+    // Integer.parseInt() doesn't handle leading +.
+    if (sysTimeZoneId.charAt(startGMToffset) == '+')
+      startGMToffset++;
+    
+    int gmtOffset = 0;
+    if (startGMToffset > 0)
+      {
+       gmtOffset = Integer.parseInt
+         (startDaylightZoneName == 0
+          ? sysTimeZoneId.substring(startGMToffset)
+          : sysTimeZoneId.substring(startGMToffset,
+                                    startDaylightZoneName));
+       
+       // Offset could be in hours or seconds.  Convert to millis.
+       // The offset is given as the time to add to local time to get GMT
+       // we need the time to add to GMT to get localtime.
+       if (gmtOffset < 24)
+         gmtOffset *= 60 * 60;
+       gmtOffset *= -1000;
+      }
+    
+    // Try to be optimistic and get the timezone that matches the base name.
+    // If we only have the base name then just accept this timezone.
+    // Otherwise check the gmtOffset and day light attributes.
+    TimeZone tz = (TimeZone) timezones().get(tzBasename);
+    if (tz != null
+       && (tzBasename == sysTimeZoneId
+           || (tz.getRawOffset() == gmtOffset
+               && tz.useDaylightTime() == useDaylightTime)))
+      return tz;
+    
+    // Maybe there is one with the daylight zone name?
+    if (useDaylightTime)
+      {
+       String daylightZoneName;
+       daylightZoneName = sysTimeZoneId.substring(startDaylightZoneName);
+       if (!daylightZoneName.equals(tzBasename))
+         {
+           tz = (TimeZone) timezones().get(tzBasename);
+           if (tz != null
+               && tz.getRawOffset() == gmtOffset
+               && tz.useDaylightTime())
+             return tz;
+         }
+      }
+    
+    // If no match, see if a valid timezone has similar attributes as this
+    // and then use it instead. We take the first one that looks OKish.
+    if (startGMToffset > 0)
+      {
+       String[] ids = getAvailableIDs(gmtOffset);
+       for (int i = 0; i < ids.length; i++)
+         {
+           tz = (TimeZone) timezones().get(ids[i]);
+           if (tz.useDaylightTime() == useDaylightTime)
+             return tz;
+         }
+      }
+    
+    return null;
+  }
+
+  /**
    * Gets the time zone offset, for current date, modified in case of 
    * daylight savings.  This is the offset to add to UTC to get the local
    * time.
@@ -1140,16 +1542,18 @@
   /**
    * Returns the time zone under which the host is running.  This
    * can be changed with setDefault.
-   * @return the time zone for this host.
+   *
+   * @return A clone of the current default time zone for this host.
    * @see #setDefault
    */
   public static TimeZone getDefault()
   {
-    return defaultZone();
+    return (TimeZone) defaultZone().clone();
   }
 
   public static void setDefault(TimeZone zone)
   {
+    // Hmmmm. No Security checks?
     defaultZone0 = zone;
   }
 

Attachment: signature.asc
Description: This is a digitally signed message part


reply via email to

[Prev in Thread] Current Thread [Next in Thread]