[PATCH] libjava time zone fixes and improvements

Jakub Jelinek jakub@redhat.com
Tue Feb 20 22:48:00 GMT 2007


Hi!

The following patch adds a new class, gnu.java.util.ZoneInfo which
unlike java.util.SimpleTimeZone can handle historical (and future)
DST transitions and different offsets and changes to java.util.TimeZone
to read /usr/share/zoneinfo files to create the TimeZone objects
preferrably over the builtin simple timezones.  The advantage of using
system tzdata files over including our own files or current hardcoded
tables (which are only SimpleTimeZone and thus can't describe some rules
for anything but one year, in other cases e.g. only next year and onwards
or only last few years correctly) is that no separate step is needed to
keep libgcj's timezone information up to date (which changes often even
20 times a year), while libgcj's own files would mean updating system
zoneinfo files and libgcj files and the current hardcoded tables mean
libgcj has to be recompiled.
In addition to this a bunch of bugs have been fixed:

 1) Date.parse wasn't able to properly parse seconds (and sometimes
    minutes) in strings like:
    Sun Mar 11 03:30:59 2007
    while Sun JDK did the right thing - the number 59 was considered
    to be a year and then overwritten again with 2007, so it got
    parsed like 03:30:00.
 2) if /etc/localtime is a symlink, try to figure zone name from where
    the symlink points to (PR28550)
 3) added /etc/sysconfig/clock parsing (PR17002)
 4) getDefaultTimeZone couldn't properly handle e.g. EST5 or EST5EDT6
 5) parseTime forgotting minutes if time was with one colon like 12:45
    (would do the right thing for 12 or 12:45:03)
 6) several SimpleTimeZone bugs, see attached SimpleTimeZoneTest.java
    testcase; e.g. set{Start,End}Rule is documented to set the time
    in WALL_TIME mode, while it was using whatever was the last mode
    set, getOffset didn't properly handle the case when millis + dstSavings
    crossed a day boundary, etc.

TimeZoneTest.java checks in all available time zones the interesting
times zdump -v computes (second before and second on which each transition
is made) - can take several minutes, as 550 or so zdump -v calls are quite
slow (as zdump uses binary searching to find the interesting times).

Ok to commit?

	Jakub
-------------- next part --------------
2007-02-20  Jakub Jelinek  <jakub@redhat.com>

libjava/
	PR libgcj/17002
	PR classpath/28550
	* java/util/VMTimeZone.java (getDefaultTimeZoneId): To read
	/etc/localtime, use ZoneInfo.readTZFile instead of
	VMTimeZone.readtzFile.  Get better timezone name for /etc/localtime,
	either if it is a symlink or through /etc/sysconfig/clock.
	(readSysconfigClockFile): New static method.
	(readtzFile): Removed.
	* java/lang/System.java: Add gnu.java.util.zoneinfo.dir to comments.
	* posix.cc (_Jv_platform_initProperties): Set
	gnu.java.util.zoneinfo.dir.
	* sources.am (gnu_java_util_source_files): Add
	classpath/gnu/java/util/ZoneInfo.java.
	* Makefile.in: Regenerated.
	* java/util/VMTimeZone.h: Regenerated.
	* java/util/TimeZone.h: Regenerated.
	* gnu/java/util/ZoneInfo.h: Generated.
libjava/classpath/
	* java/util/Date.java (parse): Properly parse 09:01:02 as
	hours/minutes/seconds, not as hours/minutes/year.
	* java/util/SimpleTimeZone.java (SimpleTimeZone): Simplify
	{start,end}TimeMode constructor by calling shorter constructor,
	set {start,end}TimeMode fields after it returns.
	(setStartRule): Don't adjust startTime into WALL_TIME.  Set
	startTimeMode to WALL_TIME.
	(endStartRule): Similarly.
	(getOffset): Handle properly millis + dstOffset overflowing into the
	next day.  Adjust startTime resp. endTime based on startTimeMode
	resp. endTimeMode.
	* java/util/TimeZone.java (zoneinfo_dir, availableIDs, aliases0): New
	static fields.
	(timezones): Remove synchronized keyword.  Set zoneinfo_dir.
	If non-null, set up aliases0 and don't put anything into
	timezones0.
	(defaultZone): Call getTimeZone instead of timezones().get.
	(getDefaultTimeZone): Fix parsing of EST5 or EST5EDT6.  Use
	getTimeZoneInternal instead of timezones().get.
	(parseTime): Parse correctly hour:minute.
	(getTimeZoneInternal): New private method.
	(getTimeZone): Do the custom ID checking first, canonicalize
	ID for custom IDs as required by documentation.  Call
	getTimeZoneInternal to handle the rest.
	(getAvailableIDs(int)): Add locking.  Handle zoneinfo_dir != null.
	(getAvailableIDs(File,String,ArrayList)): New private method.
	(getAvailableIDs()): Add locking.  Handle zoneinfo_dir != null.
	* gnu/java/util/ZoneInfo.java: New file.

--- libjava/classpath/java/util/SimpleTimeZone.java.jj	2006-10-05 00:31:11.000000000 +0200
+++ libjava/classpath/java/util/SimpleTimeZone.java	2007-02-20 16:35:13.000000000 +0100
@@ -1,5 +1,6 @@
 /* java.util.SimpleTimeZone
-   Copyright (C) 1998, 1999, 2000, 2003, 2004, 2005  Free Software Foundation, Inc.
+   Copyright (C) 1998, 1999, 2000, 2003, 2004, 2005, 2007
+   Free Software Foundation, Inc.
 
 This file is part of GNU Classpath.
 
@@ -141,8 +142,8 @@ public class SimpleTimeZone extends Time
 
   /**
    * This variable specifies the time of change to daylight savings.
-   * This time is given in milliseconds after midnight local
-   * standard time.
+   * This time is given in milliseconds after midnight in startTimeMode
+   * chosen time mode.
    * @serial
    */
   private int startTime;
@@ -187,8 +188,8 @@ public class SimpleTimeZone extends Time
 
   /**
    * This variable specifies the time of change back to standard time.
-   * This time is given in milliseconds after midnight local
-   * standard time.
+   * This time is given in milliseconds after midnight in endTimeMode
+   * chosen time mode.
    * @serial
    */
   private int endTime;
@@ -380,24 +381,17 @@ public class SimpleTimeZone extends Time
                         int endDayOfWeekInMonth, int endDayOfWeek,
                         int endTime, int endTimeMode, int dstSavings)
   {
-    this.rawOffset = rawOffset;
-    setID(id);
-    useDaylight = true;
+    this(rawOffset, id, startMonth, startDayOfWeekInMonth, startDayOfWeek,
+	 startTime, endMonth, endDayOfWeekInMonth, endDayOfWeek, endTime);
 
     if (startTimeMode < WALL_TIME || startTimeMode > UTC_TIME)
       throw new IllegalArgumentException("startTimeMode must be one of WALL_TIME, STANDARD_TIME, or UTC_TIME");
     if (endTimeMode < WALL_TIME || endTimeMode > UTC_TIME)
       throw new IllegalArgumentException("endTimeMode must be one of WALL_TIME, STANDARD_TIME, or UTC_TIME");
-    this.startTimeMode = startTimeMode;
-    this.endTimeMode = endTimeMode;
-
-    setStartRule(startMonth, startDayOfWeekInMonth, startDayOfWeek, startTime);
-    setEndRule(endMonth, endDayOfWeekInMonth, endDayOfWeek, endTime);
-    if (startMonth == endMonth)
-      throw new IllegalArgumentException("startMonth and endMonth must be different");
-    this.startYear = 0;
 
     this.dstSavings = dstSavings;
+    this.startTimeMode = startTimeMode;
+    this.endTimeMode = endTimeMode;
   }
 
   /**
@@ -477,12 +471,8 @@ public class SimpleTimeZone extends Time
     this.startMonth = month;
     this.startDay = day;
     this.startDayOfWeek = Math.abs(dayOfWeek);
-    if (this.startTimeMode == WALL_TIME || this.startTimeMode == STANDARD_TIME)
-      this.startTime = time;
-    else
-      // Convert from UTC to STANDARD
-      this.startTime = time + this.rawOffset;
-    useDaylight = true;
+    this.startTime = time;
+    this.startTimeMode = WALL_TIME;
   }
 
   /**
@@ -513,24 +503,10 @@ public class SimpleTimeZone extends Time
   public void setStartRule(int month, int day, int dayOfWeek, int time,
                            boolean after)
   {
-    // FIXME: XXX: Validate that checkRule and offset processing work with on
-    // or before mode.
-    this.startDay = after ? Math.abs(day) : -Math.abs(day);
-    this.startDayOfWeek = after ? Math.abs(dayOfWeek) : -Math.abs(dayOfWeek);
-    this.startMode = (dayOfWeek != 0)
-                     ? (after ? DOW_GE_DOM_MODE : DOW_LE_DOM_MODE)
-                     : checkRule(month, day, dayOfWeek);
-    this.startDay = Math.abs(this.startDay);
-    this.startDayOfWeek = Math.abs(this.startDayOfWeek);
-
-    this.startMonth = month;
-
-    if (this.startTimeMode == WALL_TIME || this.startTimeMode == STANDARD_TIME)
-      this.startTime = time;
+    if (after)
+      setStartRule(month, day, -dayOfWeek, time);
     else
-      // Convert from UTC to STANDARD
-      this.startTime = time + this.rawOffset;
-    useDaylight = true;
+      setStartRule(month, -day, -dayOfWeek, time);
   }
 
   /**
@@ -570,14 +546,8 @@ public class SimpleTimeZone extends Time
     this.endMonth = month;
     this.endDay = day;
     this.endDayOfWeek = Math.abs(dayOfWeek);
-    if (this.endTimeMode == WALL_TIME)
-      this.endTime = time;
-    else if (this.endTimeMode == STANDARD_TIME)
-      // Convert from STANDARD to DST
-      this.endTime = time + this.dstSavings;
-    else
-      // Convert from UTC to DST
-      this.endTime = time + this.rawOffset + this.dstSavings;
+    this.endTime = time;
+    this.endTimeMode = WALL_TIME;
     useDaylight = true;
   }
 
@@ -607,27 +577,10 @@ public class SimpleTimeZone extends Time
   public void setEndRule(int month, int day, int dayOfWeek, int time,
                          boolean after)
   {
-    // FIXME: XXX: Validate that checkRule and offset processing work with on
-    // or before mode.
-    this.endDay = after ? Math.abs(day) : -Math.abs(day);
-    this.endDayOfWeek = after ? Math.abs(dayOfWeek) : -Math.abs(dayOfWeek);
-    this.endMode = (dayOfWeek != 0)
-                   ? (after ? DOW_GE_DOM_MODE : DOW_LE_DOM_MODE)
-                   : checkRule(month, day, dayOfWeek);
-    this.endDay = Math.abs(this.endDay);
-    this.endDayOfWeek = Math.abs(endDayOfWeek);
-
-    this.endMonth = month;
-
-    if (this.endTimeMode == WALL_TIME)
-      this.endTime = time;
-    else if (this.endTimeMode == STANDARD_TIME)
-      // Convert from STANDARD to DST
-      this.endTime = time + this.dstSavings;
+    if (after)
+      setEndRule(month, day, -dayOfWeek, time);
     else
-      // Convert from UTC to DST
-      this.endTime = time + this.rawOffset + this.dstSavings;
-    useDaylight = true;
+      setEndRule(month, -day, -dayOfWeek, time);
   }
 
   /**
@@ -688,16 +641,37 @@ public class SimpleTimeZone extends Time
     int daylightSavings = 0;
     if (useDaylight && era == GregorianCalendar.AD && year >= startYear)
       {
+	int orig_year = year;
+	int time = startTime + (startTimeMode == UTC_TIME ? rawOffset : 0);
 	// This does only work for Gregorian calendars :-(
 	// This is mainly because setStartYear doesn't take an era.
 	boolean afterStart = ! isBefore(year, month, day, dayOfWeek, millis,
 	                                startMode, startMonth, startDay,
-	                                startDayOfWeek, startTime);
-	boolean beforeEnd = isBefore(year, month, day, dayOfWeek,
-				     millis + dstSavings,
-	                             endMode, endMonth, endDay, endDayOfWeek,
-	                             endTime);
+					startDayOfWeek, time);
+	millis += dstSavings;
+	if (millis >= 24 * 60 * 60 * 1000)
+	  {
+	    millis -= 24 * 60 * 60 * 1000;
+	    dayOfWeek = (dayOfWeek % 7) + 1;
+	    if (++day > daysInMonth)
+	      {
+		day = 1;
+		if (month++ == Calendar.DECEMBER)
+		  {
+		    month = Calendar.JANUARY;
+		    year++;
+		  }
+	      }
+	  }
+	time = endTime + (endTimeMode == UTC_TIME ? rawOffset : 0);
+	if (endTimeMode != WALL_TIME)
+	  time += dstSavings;
+	boolean beforeEnd = isBefore(year, month, day, dayOfWeek, millis,
+				     endMode, endMonth, endDay, endDayOfWeek,
+				     time);
 
+	if (year != orig_year)
+	  afterStart = false;
 	if (startMonth < endMonth)
 	  // use daylight savings, if the date is after the start of
 	  // savings, and before the end of savings.
--- libjava/classpath/java/util/Date.java.jj	2007-01-17 16:15:33.000000000 +0100
+++ libjava/classpath/java/util/Date.java	2007-02-20 16:35:13.000000000 +0100
@@ -754,6 +754,7 @@ public class Date
 	  }
 	else if (firstch >= '0' && firstch <= '9')
 	  {
+	    int lastPunct = -1;
 	    while (tok != null && tok.length() > 0)
 	      {
 		int punctOffset = tok.length();
@@ -791,6 +792,13 @@ public class Date
 		    else
 		      minute = num;
 		  }
+		else if (lastPunct == ':' && hour >= 0 && (minute < 0 || second < 0))
+		  {
+		    if (minute < 0)
+		      minute = num;
+		    else
+		      second = num;
+		  }
 	        else if ((num >= 70
 			  && (punct == ' ' || punct == ','
 			      || punct == '/' || punct < 0))
@@ -828,6 +836,7 @@ public class Date
 		  tok = null;
 		else
 		  tok = tok.substring(punctOffset + 1);
+		lastPunct = punct;
 	      }
 	  }
 	else if (firstch >= 'A' && firstch <= 'Z')
--- libjava/classpath/java/util/TimeZone.java.jj	2007-02-20 14:08:51.000000000 +0100
+++ libjava/classpath/java/util/TimeZone.java	2007-02-20 16:48:51.000000000 +0100
@@ -39,6 +39,9 @@ exception statement from your version. *
 
 package java.util;
 
+import gnu.classpath.SystemProperties;
+import gnu.java.util.ZoneInfo;
+import java.io.File;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.text.DateFormatSymbols;
@@ -115,7 +118,7 @@ public abstract class TimeZone implement
 		
 		// Fall back on GMT.
 		if (zone == null)
-		  zone = (TimeZone) timezones().get("GMT");
+		  zone = getTimeZone ("GMT");
 		
 		return zone;
 	      }
@@ -128,6 +131,22 @@ public abstract class TimeZone implement
   private static final long serialVersionUID = 3581463369166924961L;
 
   /**
+   * Flag whether zoneinfo data should be used,
+   * otherwise builtin timezone data will be provided.
+   */
+  private static String zoneinfo_dir;
+
+  /**
+   * Cached copy of getAvailableIDs().
+   */
+  private static String[] availableIDs = null;
+
+  /**
+   * JDK 1.1.x compatibility aliases.
+   */
+  private static HashMap aliases0;
+
+  /**
    * HashMap for timezones by ID.  
    */
   private static HashMap timezones0;
@@ -135,13 +154,55 @@ public abstract class TimeZone implement
    * it is not needed: 
    */
   // Package-private to avoid a trampoline.
-  static synchronized HashMap timezones()
+  static HashMap timezones()
   {
     if (timezones0 == null) 
       {
 	HashMap timezones = new HashMap();
 	timezones0 = timezones;
 
+	zoneinfo_dir = SystemProperties.getProperty("gnu.java.util.zoneinfo.dir");
+	if (zoneinfo_dir != null && !new File(zoneinfo_dir).isDirectory())
+	  zoneinfo_dir = null;
+
+	if (zoneinfo_dir != null)
+	  {
+	    aliases0 = new HashMap();
+
+	    // These deprecated aliases for JDK 1.1.x compatibility
+	    // should take precedence over data files read from
+	    // /usr/share/zoneinfo.
+	    aliases0.put("ACT", "Australia/Darwin");
+	    aliases0.put("AET", "Australia/Sydney");
+	    aliases0.put("AGT", "America/Argentina/Buenos_Aires");
+	    aliases0.put("ART", "Africa/Cairo");
+	    aliases0.put("AST", "America/Juneau");
+	    aliases0.put("BST", "Asia/Colombo");
+	    aliases0.put("CAT", "Africa/Gaborone");
+	    aliases0.put("CNT", "America/St_Johns");
+	    aliases0.put("CST", "CST6CDT");
+	    aliases0.put("CTT", "Asia/Brunei");
+	    aliases0.put("EAT", "Indian/Comoro");
+	    aliases0.put("ECT", "CET");
+	    aliases0.put("EST", "EST5EDT");
+	    aliases0.put("EST5", "EST5EDT");
+	    aliases0.put("IET", "EST5EDT");
+	    aliases0.put("IST", "Asia/Calcutta");
+	    aliases0.put("JST", "Asia/Seoul");
+	    aliases0.put("MIT", "Pacific/Niue");
+	    aliases0.put("MST", "MST7MDT");
+	    aliases0.put("MST7", "MST7MDT");
+	    aliases0.put("NET", "Indian/Mauritius");
+	    aliases0.put("NST", "Pacific/Auckland");
+	    aliases0.put("PLT", "Indian/Kerguelen");
+	    aliases0.put("PNT", "MST7MDT");
+	    aliases0.put("PRT", "America/Anguilla");
+	    aliases0.put("PST", "PST8PDT");
+	    aliases0.put("SST", "Pacific/Ponape");
+	    aliases0.put("VST", "Asia/Bangkok");
+	    return timezones;
+	  }
+
 	TimeZone tz;
 	// Automatically generated by scripts/timezones.pl
 	// XXX - Should we read this data from a file?
@@ -887,7 +948,6 @@ public abstract class TimeZone implement
   static TimeZone getDefaultTimeZone(String sysTimeZoneId)
   {
     String stdName = null;
-    String dstName;
     int stdOffs;
     int dstOffs;
     try
@@ -900,14 +960,14 @@ public abstract class TimeZone implement
 
 	// get std
 	do
-	  c = sysTimeZoneId.charAt(index++);
+	  c = sysTimeZoneId.charAt(index);
 	while (c != '+' && c != '-' && c != ',' && c != ':'
-	       && ! Character.isDigit(c) && c != '\0' && index < idLength);
+	       && ! Character.isDigit(c) && c != '\0' && ++index < idLength);
 
 	if (index >= idLength)
-	  return (TimeZone)timezones().get(sysTimeZoneId);
+	  return getTimeZoneInternal(sysTimeZoneId);
 
-	stdName = sysTimeZoneId.substring(0, --index);
+	stdName = sysTimeZoneId.substring(0, index);
 	prevIndex = index;
 
 	// get the std offset
@@ -938,7 +998,7 @@ public abstract class TimeZone implement
 	if (index >= idLength)
 	  {
 	    // Do we have an existing timezone with that name and offset?
-	    TimeZone tz = (TimeZone) timezones().get(stdName);
+	    TimeZone tz = getTimeZoneInternal(stdName);
 	    if (tz != null)
 	      if (tz.getRawOffset() == stdOffs)
 		return tz;
@@ -949,16 +1009,16 @@ public abstract class TimeZone implement
 
 	// get dst
 	do
-	  c = sysTimeZoneId.charAt(index++);
+	  c = sysTimeZoneId.charAt(index);
 	while (c != '+' && c != '-' && c != ',' && c != ':'
-	       && ! Character.isDigit(c) && c != '\0' && index < idLength);
+	       && ! Character.isDigit(c) && c != '\0' && ++index < idLength);
 
 	// Done yet? (Format: std offset dst)
 	if (index >= idLength)
 	  {
 	    // Do we have an existing timezone with that name and offset 
 	    // which has DST?
-	    TimeZone tz = (TimeZone) timezones().get(stdName);
+	    TimeZone tz = getTimeZoneInternal(stdName);
 	    if (tz != null)
 	      if (tz.getRawOffset() == stdOffs && tz.useDaylightTime())
 		return tz;
@@ -968,7 +1028,6 @@ public abstract class TimeZone implement
 	  }
 
 	// get the dst offset
-	dstName = sysTimeZoneId.substring(prevIndex, --index);
 	prevIndex = index;
 	do
 	  c = sysTimeZoneId.charAt(index++);
@@ -1005,7 +1064,7 @@ public abstract class TimeZone implement
 	if (index >= idLength)
 	  {
 	    // Time Zone existing with same name, dst and offsets?
-	    TimeZone tz = (TimeZone) timezones().get(stdName);
+	    TimeZone tz = getTimeZoneInternal(stdName);
 	    if (tz != null)
 	      if (tz.getRawOffset() == stdOffs && tz.useDaylightTime()
 	          && tz.getDSTSavings() == (dstOffs - stdOffs))
@@ -1171,10 +1230,10 @@ public abstract class TimeZone implement
 	break;
       else
 	i++;
+    millis += 60 * 1000 * Integer.parseInt(time.substring(iprev, i));
     if (i >= time.length())
       return millis;
 
-    millis += 60 * 1000 * Integer.parseInt(time.substring(iprev, i));
     millis += 1000 * Integer.parseInt(time.substring(++i));
     return millis;
   }
@@ -1406,30 +1465,67 @@ public abstract class TimeZone implement
    * @return The time zone for the identifier or GMT, if no such time
    * zone exists.
    */
-  // FIXME: XXX: JCL indicates this and other methods are synchronized.
-  public static TimeZone getTimeZone(String ID)
+  private static TimeZone getTimeZoneInternal(String ID)
   {
     // First check timezones hash
-    TimeZone tz = (TimeZone) timezones().get(ID);
-    if (tz != null)
+    TimeZone tz = null;
+    TimeZone tznew = null;
+    for (int pass = 0; pass < 2; pass++)
       {
-	if (tz.getID().equals(ID))
-	  return tz;
+	synchronized (TimeZone.class)
+	  {
+	    tz = (TimeZone) timezones().get(ID);
+	    if (tz != null)
+	      {
+		if (!tz.getID().equals(ID))
+		  {
+		    // We always return a timezone with the requested ID.
+		    // This is the same behaviour as with JDK1.2.
+		    tz = (TimeZone) tz.clone();
+		    tz.setID(ID);
+		    // We also save the alias, so that we return the same
+		    // object again if getTimeZone is called with the same
+		    // alias.
+		    timezones().put(ID, tz);
+		  }
+		return tz;
+	      }
+	    else if (tznew != null)
+	      {
+		timezones().put(ID, tznew);
+		return tznew;
+	      }
+	  }
 
-	// We always return a timezone with the requested ID.
-	// This is the same behaviour as with JDK1.2.
-	tz = (TimeZone) tz.clone();
-	tz.setID(ID);
-	// We also save the alias, so that we return the same
-	// object again if getTimeZone is called with the same
-	// alias.
-	timezones().put(ID, tz);
-	return tz;
+	if (pass == 1 || zoneinfo_dir == null)
+	  return null;
+
+	// aliases0 is never changing after first timezones(), so should
+	// be safe without synchronization.
+	String zonename = (String) aliases0.get(ID);
+	if (zonename == null)
+	  zonename = ID;
+
+	// Read the file outside of the critical section, it is expensive.
+	tznew = ZoneInfo.readTZFile (ID, zoneinfo_dir
+				     + File.separatorChar + zonename);
+	if (tznew == null)
+	  return null;
       }
 
-    // See if the ID is really a GMT offset form.
-    // Note that GMT is in the table so we know it is different.
-    if (ID.startsWith("GMT"))
+    return null;
+  }
+
+  /**
+   * Gets the TimeZone for the given ID.
+   * @param ID the time zone identifier.
+   * @return The time zone for the identifier or GMT, if no such time
+   * zone exists.
+   */
+  public static TimeZone getTimeZone(String ID)
+  {
+    // Check for custom IDs first
+    if (ID.startsWith("GMT") && ID.length() > 3)
       {
 	int pos = 3;
 	int offset_direction = 1;
@@ -1474,8 +1570,20 @@ public abstract class TimeZone implement
 		  }
 	      }
 
-	    return new SimpleTimeZone((hour * (60 * 60 * 1000) +
-				       minute * (60 * 1000))
+	    // Custom IDs have to be normalized
+	    StringBuffer sb = new StringBuffer(9);
+	    sb.append("GMT");
+
+	    sb.append(offset_direction >= 0 ? '+' : '-');
+	    sb.append((char) ('0' + hour / 10));
+	    sb.append((char) ('0' + hour % 10));
+	    sb.append(':');
+	    sb.append((char) ('0' + minute / 10));
+	    sb.append((char) ('0' + minute % 10));
+	    ID = sb.toString();
+
+	    return new SimpleTimeZone((hour * (60 * 60 * 1000)
+				       + minute * (60 * 1000))
 				      * offset_direction, ID);
 	  }
 	catch (NumberFormatException e)
@@ -1483,8 +1591,11 @@ public abstract class TimeZone implement
 	  }
       }
 
-    // Finally, return GMT per spec
-    return getTimeZone("GMT");
+    TimeZone tz = getTimeZoneInternal(ID);
+    if (tz != null)
+      return tz;
+
+    return new SimpleTimeZone(0, "GMT");
   }
 
   /**
@@ -1497,37 +1608,134 @@ public abstract class TimeZone implement
    */
   public static String[] getAvailableIDs(int rawOffset)
   {
+    synchronized (TimeZone.class)
+      {
+	HashMap h = timezones();
+	int count = 0;
+	if (zoneinfo_dir == null)
+	  {
+	    Iterator iter = h.entrySet().iterator();
+	    while (iter.hasNext())
+	      {
+		// Don't iterate the values, since we want to count
+		// doubled values (aliases)
+		Map.Entry entry = (Map.Entry) iter.next();
+		if (((TimeZone) entry.getValue()).getRawOffset() == rawOffset)
+		  count++;
+	      }
+
+	    String[] ids = new String[count];
+	    count = 0;
+	    iter = h.entrySet().iterator();
+	    while (iter.hasNext())
+	      {
+		Map.Entry entry = (Map.Entry) iter.next();
+		if (((TimeZone) entry.getValue()).getRawOffset() == rawOffset)
+		  ids[count++] = (String) entry.getKey();
+	      }
+	    return ids;
+	  }
+      }
+
+    String[] s = getAvailableIDs();
     int count = 0;
-    Iterator iter = timezones().entrySet().iterator();
-    while (iter.hasNext())
+    for (int i = 0; i < s.length; i++)
       {
-	// Don't iterate the values, since we want to count 
-	// doubled values (aliases)
-	Map.Entry entry = (Map.Entry) iter.next();
-	if (((TimeZone) entry.getValue()).getRawOffset() == rawOffset)
+	TimeZone t = getTimeZoneInternal(s[i]);
+	if (t == null || t.getRawOffset() != rawOffset)
+	  s[i] = null;
+	else
 	  count++;
       }
-
     String[] ids = new String[count];
     count = 0;
-    iter = timezones().entrySet().iterator();
-    while (iter.hasNext())
-      {
-	Map.Entry entry = (Map.Entry) iter.next();
-	if (((TimeZone) entry.getValue()).getRawOffset() == rawOffset)
-	  ids[count++] = (String) entry.getKey();
-      }
+    for (int i = 0; i < s.length; i++)
+    if (s[i] != null)
+      ids[count++] = s[i];
+
     return ids;
   }
 
+  private static int getAvailableIDs(File d, String prefix, ArrayList list)
+    {
+      String[] files = d.list();
+      int count = files.length;
+      boolean top = prefix.length() == 0;
+      list.add (files);
+      for (int i = 0; i < files.length; i++)
+	{
+	  if (top
+	      && (files[i].equals("posix")
+		  || files[i].equals("right")
+		  || files[i].endsWith(".tab")
+		  || aliases0.get(files[i]) != null))
+	    {
+	      files[i] = null;
+	      count--;
+	      continue;
+	    }
+
+	  File f = new File(d, files[i]);
+	  if (f.isDirectory())
+	    {
+	      count += getAvailableIDs(f, prefix + files[i]
+				       + File.separatorChar, list) - 1;
+	      files[i] = null;
+	    }
+	  else
+	    files[i] = prefix + files[i];
+	}
+      return count;
+    }
+
   /**
    * Gets all available IDs.
    * @return An array of all supported IDs.
    */
   public static String[] getAvailableIDs()
   {
-    return (String[])
-      timezones().keySet().toArray(new String[timezones().size()]);
+    synchronized (TimeZone.class)
+      {
+	HashMap h = timezones();
+	if (zoneinfo_dir == null)
+	  return (String[]) h.keySet().toArray(new String[h.size()]);
+
+	if (availableIDs != null)
+	  {
+	    String[] ids = new String[availableIDs.length];
+	    for (int i = 0; i < availableIDs.length; i++)
+	      ids[i] = availableIDs[i];
+	    return ids;
+	  }
+
+	File d = new File(zoneinfo_dir);
+	ArrayList list = new ArrayList(30);
+	int count = getAvailableIDs(d, "", list) + aliases0.size();
+	availableIDs = new String[count];
+	String[] ids = new String[count];
+
+	count = 0;
+	for (int i = 0; i < list.size(); i++)
+	  {
+	    String[] s = (String[]) list.get(i);
+	    for (int j = 0; j < s.length; j++)
+	      if (s[j] != null)
+		{
+		  availableIDs[count] = s[j];
+		  ids[count++] = s[j];
+		}
+	  }
+
+	Iterator iter = aliases0.entrySet().iterator();
+	while (iter.hasNext())
+	  {
+	    Map.Entry entry = (Map.Entry) iter.next();
+	    availableIDs[count] = (String) entry.getKey();
+	    ids[count++] = (String) entry.getKey();
+	  }
+
+	return ids;
+      }
   }
 
   /**
--- libjava/classpath/gnu/java/util/ZoneInfo.java.jj	2007-02-20 16:35:13.000000000 +0100
+++ libjava/classpath/gnu/java/util/ZoneInfo.java	2007-02-20 16:35:13.000000000 +0100
@@ -0,0 +1,1160 @@
+/* gnu.java.util.ZoneInfo
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.util;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+
+/**
+ * This class represents more advanced variant of java.util.SimpleTimeZone.
+ * It can handle zic(8) compiled transition dates plus uses a SimpleTimeZone
+ * for years beyond last precomputed transition.  Before first precomputed
+ * transition it assumes no daylight saving was in effect.
+ * Timezones that never used daylight saving time should use just
+ * SimpleTimeZone instead of this class.
+ *
+ * This object is tightly bound to the Gregorian calendar.  It assumes
+ * a regular seven days week, and the month lengths are that of the
+ * Gregorian Calendar.
+ *
+ * @see Calendar
+ * @see GregorianCalendar
+ * @see SimpleTimeZone
+ * @author Jakub Jelinek
+ */
+public class ZoneInfo extends TimeZone
+{
+  private static final int SECS_SHIFT = 22;
+  private static final long OFFSET_MASK = (1 << 21) - 1;
+  private static final int OFFSET_SHIFT = 64 - 21;
+  private static final long IS_DST = 1 << 21;
+
+  /**
+   * The raw time zone offset in milliseconds to GMT, ignoring
+   * daylight savings.
+   * @serial
+   */
+  private int rawOffset;
+
+  /**
+   * Cached DST savings for the last transition rule.
+   */
+  private int dstSavings;
+
+  /**
+   * Cached flag whether last transition rule uses DST saving.
+   */
+  private boolean useDaylight;
+
+  /**
+   * Array of encoded transitions.
+   * Transition time in UTC seconds since epoch is in the most
+   * significant 64 - SECS_SHIFT bits, then one bit flag
+   * whether DST is active and the least significant bits
+   * containing offset relative to rawOffset.  Both the DST
+   * flag and relative offset apply to time before the transition
+   * and after or equal to previous transition if any.
+   * The array must be sorted.
+   */
+  private long[] transitions;
+
+  /**
+   * SimpleTimeZone rule which applies on or after the latest
+   * transition.  If the DST changes are not expresible as a
+   * SimpleTimeZone rule, then the rule should just contain
+   * the standard time and no DST time.
+   */
+  private SimpleTimeZone lastRule;
+
+  /**
+   * Cached GMT SimpleTimeZone object for internal use in
+   * getOffset method.
+   */
+  private static SimpleTimeZone gmtZone = null;
+
+  static final long serialVersionUID = -3740626706860383657L;
+
+  /**
+   * Create a <code>ZoneInfo</code> with the given time offset
+   * from GMT and with daylight savings.
+   *
+   * @param rawOffset The time offset from GMT in milliseconds.
+   * @param id  The identifier of this time zone.
+   * @param transitions  Array of transition times in UTC seconds since
+   * Epoch in topmost 42 bits, below that 1 boolean bit whether the time
+   * before that transition used daylight saving and in bottommost 21
+   * bits relative daylight saving offset against rawOffset in seconds
+   * that applies before this transition.
+   * @param endRule SimpleTimeZone class describing the daylight saving
+   * rules after the last transition.
+   */
+  public ZoneInfo(int rawOffset, String id, long[] transitions,
+		  SimpleTimeZone lastRule)
+  {
+    if (transitions == null || transitions.length < 1)
+      throw new IllegalArgumentException("transitions must not be null");
+    if (lastRule == null)
+      throw new IllegalArgumentException("lastRule must not be null");
+    this.rawOffset = rawOffset;
+    this.transitions = transitions;
+    this.lastRule = lastRule;
+    setID(id);
+    computeDSTSavings();
+  }
+
+  /**
+   * 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.
+   *
+   * The day must be a positive number and dayOfWeek must be a positive value
+   * from Calendar.  dayOfWeek is redundant, but must match the other values
+   * or an inaccurate result may be returned.
+   *
+   * @param era the era of the given date
+   * @param year the year of the given date
+   * @param month the month of the given date, 0 for January.
+   * @param day the day of month
+   * @param dayOfWeek the day of week; this must match the other fields.
+   * @param millis the millis in the day (in local standard time)
+   * @return the time zone offset in milliseconds.
+   * @throws IllegalArgumentException if arguments are incorrect.
+   */
+  public int getOffset(int era, int year, int month, int day, int dayOfWeek,
+		       int millis)
+  {
+    if (gmtZone == null)
+      gmtZone = new SimpleTimeZone(0, "GMT");
+
+    if (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY)
+      throw new IllegalArgumentException("dayOfWeek out of range");
+    if (month < Calendar.JANUARY || month > Calendar.DECEMBER)
+      throw new IllegalArgumentException("month out of range:" + month);
+
+    if (era != GregorianCalendar.AD)
+      return (int) (((transitions[0] << OFFSET_SHIFT) >> OFFSET_SHIFT) * 1000);
+
+    GregorianCalendar cal = new GregorianCalendar((TimeZone) gmtZone);
+    cal.set(year, month, day, 0, 0, 0);
+    if (cal.get(Calendar.DAY_OF_MONTH) != day)
+      throw new IllegalArgumentException("day out of range");
+
+    return getOffset(cal.getTimeInMillis() - rawOffset + millis);
+  }
+
+  private long findTransition(long secs)
+  {
+    if (secs < (transitions[0] >> SECS_SHIFT))
+      return transitions[0];
+
+    if (secs >= (transitions[transitions.length-1] >> SECS_SHIFT))
+      return Long.MAX_VALUE;
+
+    long val = (secs + 1) << SECS_SHIFT;
+    int lo = 1;
+    int hi = transitions.length;
+    int mid = 1;
+    while (lo < hi)
+      {
+	mid = (lo + hi) / 2;
+	// secs < (transitions[mid-1] >> SECS_SHIFT)
+	if (val <= transitions[mid-1])
+	  hi = mid;
+	// secs >= (transitions[mid] >> SECS_SHIFT)
+	else if (val > transitions[mid])
+	  lo = mid + 1;
+	else
+	  break;
+      }
+    return transitions[mid];
+  }
+
+  /**
+   * Get the time zone offset for the specified date, modified in case of
+   * daylight savings.  This is the offset to add to UTC to get the local
+   * time.
+   * @param date the date represented in millisecends
+   * since January 1, 1970 00:00:00 GMT.
+   */
+  public int getOffset(long date)
+  {
+    long d = (date >= 0 ? date / 1000 : (date + 1) / 1000 - 1);
+    long transition = findTransition(d);
+
+    // For times on or after last transition use lastRule.
+    if (transition == Long.MAX_VALUE)
+      return lastRule.getOffset(date);
+
+    return (int) (((transition << OFFSET_SHIFT) >> OFFSET_SHIFT) * 1000);
+  }
+
+  /**
+   * Returns the time zone offset to GMT in milliseconds, ignoring
+   * day light savings.
+   * @return the time zone offset.
+   */
+  public int getRawOffset()
+  {
+    return rawOffset;
+  }
+
+  /**
+   * Sets the standard time zone offset to GMT.
+   * @param rawOffset The time offset from GMT in milliseconds.
+   */
+  public void setRawOffset(int rawOffset)
+  {
+    this.rawOffset = rawOffset;
+    lastRule.setRawOffset(rawOffset);
+  }
+
+  private void computeDSTSavings()
+  {
+    if (lastRule.useDaylightTime())
+      {
+	dstSavings = lastRule.getDSTSavings();
+	useDaylight = true;
+      }
+    else
+      {
+	dstSavings = 0;
+	useDaylight = false;
+	// lastRule might say no DST is in effect simply because
+	// the DST rules are too complex for SimpleTimeZone, say
+	// for Asia/Jerusalem.
+	// Look at the last DST offset if it is newer than current time.
+	long currentSecs = System.currentTimeMillis() / 1000;
+	int i;
+	for (i = transitions.length - 1;
+	     i >= 0 && currentSecs < (transitions[i] >> SECS_SHIFT);
+	     i--)
+	  if ((transitions[i] & IS_DST) != 0)
+	    {
+	      dstSavings = (int) (((transitions[i] << OFFSET_SHIFT)
+				   >> OFFSET_SHIFT) * 1000)
+			   - rawOffset;
+	      useDaylight = true;
+	      break;
+	    }
+      }
+  }
+
+  /**
+   * Gets the daylight savings offset.  This is a positive offset in
+   * milliseconds with respect to standard time.  Typically this
+   * is one hour, but for some time zones this may be half an our.
+   * @return the daylight savings offset in milliseconds.
+   */
+  public int getDSTSavings()
+  {
+    return dstSavings;
+  }
+
+  /**
+   * Returns if this time zone uses daylight savings time.
+   * @return true, if we use daylight savings time, false otherwise.
+   */
+  public boolean useDaylightTime()
+  {
+    return useDaylight;
+  }
+
+  /**
+   * Determines if the given date is in daylight savings time.
+   * @return true, if it is in daylight savings time, false otherwise.
+   */
+  public boolean inDaylightTime(Date date)
+  {
+    long d = date.getTime();
+    d = (d >= 0 ? d / 1000 : (d + 1) / 1000 - 1);
+    long transition = findTransition(d);
+
+    // For times on or after last transition use lastRule.
+    if (transition == Long.MAX_VALUE)
+      return lastRule.inDaylightTime(date);
+
+    return (transition & IS_DST) != 0;
+  }
+
+  /**
+   * Generates the hashCode for the SimpleDateFormat object.  It is
+   * the rawOffset, possibly, if useDaylightSavings is true, xored
+   * with startYear, startMonth, startDayOfWeekInMonth, ..., endTime.
+   */
+  public synchronized int hashCode()
+  {
+    int hash = lastRule.hashCode();
+    // FIXME - hash transitions?
+    return hash;
+  }
+
+  public synchronized boolean equals(Object o)
+  {
+    if (! hasSameRules((TimeZone) o))
+      return false;
+
+    ZoneInfo zone = (ZoneInfo) o;
+    return getID().equals(zone.getID());
+  }
+
+  /**
+   * Test if the other time zone uses the same rule and only
+   * possibly differs in ID.  This implementation for this particular
+   * class will return true if the other object is a ZoneInfo,
+   * the raw offsets and useDaylight are identical and if useDaylight
+   * is true, also the start and end datas are identical.
+   * @return true if this zone uses the same rule.
+   */
+  public boolean hasSameRules(TimeZone o)
+  {
+    if (this == o)
+      return true;
+    if (! (o instanceof ZoneInfo))
+      return false;
+    ZoneInfo zone = (ZoneInfo) o;
+    if (zone.hashCode() != hashCode() || rawOffset != zone.rawOffset)
+      return false;
+    if (! lastRule.equals(zone.lastRule))
+      return false;
+    // FIXME - compare transitions?
+    return true;
+  }
+
+  /**
+   * Returns a string representation of this ZoneInfo object.
+   * @return a string representation of this ZoneInfo object.
+   */
+  public String toString()
+  {
+    return getClass().getName() + "[" + "id=" + getID() + ",offset="
+	   + rawOffset + ",transitions=" + transitions.length
+	   + ",useDaylight=" + useDaylight
+	   + (useDaylight ? (",dstSavings=" + dstSavings) : "")
+	   + ",lastRule=" + lastRule.toString() + "]";
+  }
+
+  /**
+   * Reads zic(8) compiled timezone data file from file
+   * and returns a TimeZone class describing it, either
+   * SimpleTimeZone or ZoneInfo depending on whether
+   * it can be described by SimpleTimeZone rule or not.
+   */
+  public static TimeZone readTZFile(String id, String file)
+  {
+    DataInputStream dis = null;
+    try
+      {
+	FileInputStream fis = new FileInputStream(file);
+	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 - 5);
+
+	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();
+	  }
+
+	// Sanity checks
+	if (typecnt <= 0 || timecnt < 0 || charcnt < 0
+	    || leapcnt < 0 || ttisgmtcnt < 0 || ttisstdcnt < 0
+	    || ttisgmtcnt > typecnt || ttisstdcnt > typecnt)
+	  return null;
+
+	// Transition times
+	long[] times = new long[timecnt];
+	for (int i = 0; i < timecnt; i++)
+	  if (tzif2 == 8)
+	    times[i] = dis.readLong();
+	  else
+	    times[i] = (long) dis.readInt();
+
+	// Transition types
+	int[] types = new int[timecnt];
+	for (int i = 0; i < timecnt; i++)
+	  {
+	    types[i] = dis.readByte();
+	    if (types[i] < 0)
+	      types[i] += 256;
+	    if (types[i] >= typecnt)
+	      return null;
+	  }
+
+	// Types
+	int[] offsets = new int[typecnt];
+	int[] typeflags = new int[typecnt];
+	for (int i = 0; i < typecnt; i++)
+	  {
+	    offsets[i] = dis.readInt();
+	    if (offsets[i] >= IS_DST / 2 || offsets[i] <= -IS_DST / 2)
+	      return null;
+	    int dst = dis.readByte();
+	    int abbrind = dis.readByte();
+	    if (abbrind < 0)
+	      abbrind += 256;
+	    if (abbrind >= charcnt)
+	      return null;
+	    typeflags[i] = (dst != 0 ? (1 << 8) : 0) + abbrind;
+	  }
+
+	// Abbrev names
+	byte[] names = new byte[charcnt];
+	dis.readFully(names);
+
+	// Leap transitions, for now ignore
+	skipFully(dis, leapcnt * (tzif2 + 4) + ttisstdcnt + ttisgmtcnt);
+
+	// tzIf2 format has optional POSIX TZ env string
+	String tzstr = null;
+	if (tzif2 == 8 && dis.readByte() == '\n')
+	  {
+	    tzstr = dis.readLine();
+	    if (tzstr.length() <= 0)
+	      tzstr = null;
+	  }
+
+	// Get std/dst_offset and dst/non-dst time zone names.
+	int std_ind = -1;
+	int dst_ind = -1;
+	if (timecnt == 0)
+	  std_ind = 0;
+	else
+	  for (int i = timecnt - 1; i >= 0; i--)
+	    {
+	      if (std_ind == -1 && (typeflags[types[i]] & (1 << 8)) == 0)
+		std_ind = types[i];
+	      else if (dst_ind == -1 && (typeflags[types[i]] & (1 << 8)) != 0)
+		dst_ind = types[i];
+	      if (dst_ind != -1 && std_ind != -1)
+		break;
+	    }
+
+	if (std_ind == -1)
+	  return null;
+
+	int j = typeflags[std_ind] & 255;
+	while (j < charcnt && names[j] != 0)
+	  j++;
+	String std_zonename = new String(names, typeflags[std_ind] & 255,
+					 j - (typeflags[std_ind] & 255),
+					 "ASCII");
+
+	String dst_zonename = "";
+	if (dst_ind != -1)
+	  {
+	    j = typeflags[dst_ind] & 255;
+	    while (j < charcnt && names[j] != 0)
+	      j++;
+	    dst_zonename = new String(names, typeflags[dst_ind] & 255,
+				      j - (typeflags[dst_ind] & 255), "ASCII");
+	  }
+
+	// Only use gmt offset when necessary.
+	// Also special case GMT+/- timezones.
+	String std_offset_string = "";
+	String dst_offset_string = "";
+	if (tzstr == null
+	    && (dst_ind != -1
+		|| (offsets[std_ind] != 0
+		    && !std_zonename.startsWith("GMT+")
+		    && !std_zonename.startsWith("GMT-"))))
+	  {
+	    std_offset_string = Integer.toString(-offsets[std_ind] / 3600);
+	    int seconds = -offsets[std_ind] % 3600;
+	    if (seconds != 0)
+	      {
+		if (seconds < 0)
+		  seconds *= -1;
+		if (seconds < 600)
+		  std_offset_string += ":0" + Integer.toString(seconds / 60);
+		else
+		  std_offset_string += ":" + Integer.toString(seconds / 60);
+		seconds = seconds % 60;
+		if (seconds >= 10)
+		  std_offset_string += ":" + Integer.toString(seconds);
+		else if (seconds > 0)
+		  std_offset_string += ":0" + Integer.toString(seconds);
+	      }
+
+	    if (dst_ind != -1 && offsets[dst_ind] != offsets[std_ind] + 3600)
+	      {
+		dst_offset_string = Integer.toString(-offsets[dst_ind] / 3600);
+		seconds = -offsets[dst_ind] % 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 no tzIf2 POSIX TZ string is available and the timezone
+	 * uses DST, try to guess the last rule by trying to make
+	 * sense from transitions at 5 years in the future and onwards.
+	 * 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
+	 */
+	String[] change_spec = { null, null };
+	if (tzstr == null && dst_ind != -1 && timecnt > 10)
+	  {
+	    long nowPlus5y = System.currentTimeMillis() / 1000
+			     + 5 * 365 * 86400;
+	    int first;
+
+	    for (first = timecnt - 1; first >= 0; first--)
+	      if (times[first] < nowPlus5y
+		  || (types[first] != std_ind && types[first] != dst_ind)
+		  || types[first] != types[timecnt - 2 + ((first ^ timecnt) & 1)])
+		break;
+	    first++;
+
+	    if (timecnt - first >= 10 && types[timecnt - 1] != types[timecnt - 2])
+	      {
+		GregorianCalendar cal
+		  = new GregorianCalendar(new SimpleTimeZone(0, "GMT"));
+
+		int[] values = new int[2 * 11];
+		int i;
+		for (i = timecnt - 1; i >= first; i--)
+		  {
+		    int base = (i % 2) * 11;
+		    int offset = offsets[types[i > first ? i - 1 : i + 1]];
+		    cal.setTimeInMillis((times[i] + offset) * 1000);
+		    if (i >= timecnt - 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 < first)
+		  {
+		    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;
+
+			    int d;
+			    d = values[base + 1] - Calendar.JANUARY + 1;
+			    // E.g. Sun >= 5 is not representable in POSIX
+			    // TZ env string, use ",Am.n.d" extension
+			    // where m is month 1 .. 12, n is the date on
+			    // or after which it happens and d is day
+			    // of the week, 0 .. 6.  So Sun >= 5 in April
+			    // is ",A4.5.0".
+			    if ((values[base + 8] % 7) == 1)
+			      {
+				change_spec[i] = ",M" + Integer.toString(d);
+				d = (values[base + 8] + 6) / 7;
+			      }
+			    else
+			      {
+				change_spec[i] = ",A" + Integer.toString(d);
+				d = values[base + 8];
+			      }
+			    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[(timecnt - 1) & -2] == std_ind)
+		      {
+			String tmp = change_spec[0];
+			change_spec[0] = change_spec[1];
+			change_spec[1] = tmp;
+		      }
+		  }
+	      }
+	  }
+
+	if (tzstr == null)
+	  {
+	    tzstr = std_zonename + std_offset_string;
+	    if (change_spec[0] != null && change_spec[1] != null)
+	      tzstr += dst_zonename + dst_offset_string
+		       + change_spec[0] + change_spec[1];
+	  }
+
+	if (timecnt == 0)
+	  return new SimpleTimeZone(offsets[std_ind] * 1000,
+				    id != null ? id : tzstr);
+
+	SimpleTimeZone endRule = createLastRule(tzstr);
+	if (endRule == null)
+	  return null;
+
+	/* Finally adjust the times array into the form the constructor
+	 * expects.  times[0] is special, the offset and DST flag there
+	 * are for all times before that transition.  Use the first non-DST
+	 * type.  For all other transitions, the data file has the type
+	 * (<offset, isdst, zonename>) for the time interval starting
+	 */
+	for (int i = 0; i < typecnt; i++)
+	  if ((typeflags[i] & (1 << 8)) == 0)
+	    {
+	      times[0] = (times[0] << SECS_SHIFT) | (offsets[i] & OFFSET_MASK);
+	      break;
+	    }
+
+	for (int i = 1; i < timecnt; i++)
+	  times[i] = (times[i] << SECS_SHIFT)
+		     | (offsets[types[i - 1]] & OFFSET_MASK)
+		     | ((typeflags[types[i - 1]] & (1 << 8)) != 0 ? IS_DST : 0);
+
+	return new ZoneInfo(offsets[std_ind] * 1000, id != null ? id : tzstr,
+			    times, endRule);
+      }
+    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;
+      }
+  }
+
+  /**
+   * Create a SimpleTimeZone from a POSIX TZ environment string,
+   * see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
+   * for details.
+   * It supports also an extension, where Am.n.d rule (m 1 .. 12, n 1 .. 25, d
+   * 0 .. 6) describes day of week d on or after nth day of month m.
+   * Say A4.5.0 is Sun>=5 in April.
+   */
+  private static SimpleTimeZone createLastRule(String tzstr)
+  {
+    String stdName = null;
+    int stdOffs;
+    int dstOffs;
+    try
+      {
+	int idLength = tzstr.length();
+
+	int index = 0;
+	int prevIndex;
+	char c;
+
+	// get std
+	do
+	  c = tzstr.charAt(index);
+	while (c != '+' && c != '-' && c != ',' && c != ':'
+	       && ! Character.isDigit(c) && c != '\0' && ++index < idLength);
+
+	if (index >= idLength)
+	  return new SimpleTimeZone(0, tzstr);
+
+	stdName = tzstr.substring(0, index);
+	prevIndex = index;
+
+	// get the std offset
+	do
+	  c = tzstr.charAt(index++);
+	while ((c == '-' || c == '+' || c == ':' || Character.isDigit(c))
+	       && index < idLength);
+	if (index < idLength)
+	  index--;
+
+	{ // convert the dst string to a millis number
+	    String offset = tzstr.substring(prevIndex, index);
+	    prevIndex = index;
+
+	    if (offset.charAt(0) == '+' || offset.charAt(0) == '-')
+	      stdOffs = parseTime(offset.substring(1));
+	    else
+	      stdOffs = parseTime(offset);
+
+	    if (offset.charAt(0) == '-')
+	      stdOffs = -stdOffs;
+
+	    // TZ timezone offsets are positive when WEST of the meridian.
+	    stdOffs = -stdOffs;
+	}
+
+	// Done yet? (Format: std offset)
+	if (index >= idLength)
+	  return new SimpleTimeZone(stdOffs, stdName);
+
+	// get dst
+	do
+	  c = tzstr.charAt(index);
+	while (c != '+' && c != '-' && c != ',' && c != ':'
+	       && ! Character.isDigit(c) && c != '\0' && ++index < idLength);
+
+	// Done yet? (Format: std offset dst)
+	if (index >= idLength)
+	  return new SimpleTimeZone(stdOffs, stdName);
+
+	// get the dst offset
+	prevIndex = index;
+	do
+	  c = tzstr.charAt(index++);
+	while ((c == '-' || c == '+' || c == ':' || Character.isDigit(c))
+	       && index < idLength);
+	if (index < idLength)
+	  index--;
+
+	if (index == prevIndex && (c == ',' || c == ';'))
+	  {
+	    // Missing dst offset defaults to one hour ahead of standard
+	    // time.
+	    dstOffs = stdOffs + 60 * 60 * 1000;
+	  }
+	else
+	  { // convert the dst string to a millis number
+	    String offset = tzstr.substring(prevIndex, index);
+	    prevIndex = index;
+
+	    if (offset.charAt(0) == '+' || offset.charAt(0) == '-')
+	      dstOffs = parseTime(offset.substring(1));
+	    else
+	      dstOffs = parseTime(offset);
+
+	    if (offset.charAt(0) == '-')
+	      dstOffs = -dstOffs;
+
+	    // TZ timezone offsets are positive when WEST of the meridian.
+	    dstOffs = -dstOffs;
+	  }
+
+	// Done yet? (Format: std offset dst offset)
+	if (index >= idLength)
+	  return new SimpleTimeZone(stdOffs, stdName);
+
+	// get the DST rule
+	if (tzstr.charAt(index) == ','
+	    || tzstr.charAt(index) == ';')
+	  {
+	    index++;
+	    int offs = index;
+	    while (tzstr.charAt(index) != ','
+		   && tzstr.charAt(index) != ';')
+	      index++;
+	    String startTime = tzstr.substring(offs, index);
+	    index++;
+	    String endTime = tzstr.substring(index);
+
+	    index = startTime.indexOf('/');
+	    int startMillis;
+	    int endMillis;
+	    String startDate;
+	    String endDate;
+	    if (index != -1)
+	      {
+		startDate = startTime.substring(0, index);
+		startMillis = parseTime(startTime.substring(index + 1));
+	      }
+	    else
+	      {
+		startDate = startTime;
+		// if time isn't given, default to 2:00:00 AM.
+		startMillis = 2 * 60 * 60 * 1000;
+	      }
+	    index = endTime.indexOf('/');
+	    if (index != -1)
+	      {
+		endDate = endTime.substring(0, index);
+		endMillis = parseTime(endTime.substring(index + 1));
+	      }
+	    else
+	      {
+		endDate = endTime;
+		// if time isn't given, default to 2:00:00 AM.
+		endMillis = 2 * 60 * 60 * 1000;
+	      }
+
+	    int[] start = getDateParams(startDate);
+	    int[] end = getDateParams(endDate);
+	    return new SimpleTimeZone(stdOffs, tzstr, start[0], start[1],
+				      start[2], startMillis, end[0], end[1],
+				      end[2], endMillis, (dstOffs - stdOffs));
+	  }
+      }
+
+    catch (IndexOutOfBoundsException _)
+      {
+      }
+    catch (NumberFormatException _)
+      {
+      }
+
+    return null;
+  }
+
+  /**
+   * Parses and returns the params for a POSIX TZ date field,
+   * in the format int[]{ month, day, dayOfWeek }, following the
+   * SimpleTimeZone constructor rules.
+   */
+  private static int[] getDateParams(String date)
+  {
+    int[] dayCount = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
+    int month;
+    int type = 0;
+
+    if (date.charAt(0) == 'M' || date.charAt(0) == 'm')
+      type = 1;
+    else if (date.charAt(0) == 'A' || date.charAt(0) == 'a')
+      type = 2;
+
+    if (type > 0)
+      {
+	int day;
+
+	// Month, week of month, day of week
+	// "Mm.w.d".  d is between 0 (Sunday) and 6.  Week w is
+	// between 1 and 5; Week 1 is the first week in which day d
+	// occurs and Week 5 specifies the last d day in the month.
+	// Month m is between 1 and 12.
+
+	// Month, day of month, day of week
+	// ZoneInfo extension, not in POSIX
+	// "Am.n.d".  d is between 0 (Sunday) and 6.  Day of month n is
+	// between 1 and 25.  Month m is between 1 and 12.
+
+	month = Integer.parseInt(date.substring(1, date.indexOf('.')));
+	int week = Integer.parseInt(date.substring(date.indexOf('.') + 1,
+						   date.lastIndexOf('.')));
+	int dayOfWeek = Integer.parseInt(date.substring(date.lastIndexOf('.')
+							+ 1));
+	dayOfWeek++; // Java day of week is one-based, Sunday is first day.
+
+	if (type == 2)
+	  {
+	    day = week;
+	    dayOfWeek = -dayOfWeek;
+	  }
+ 	else if (week == 5)
+ 	  day = -1; // last day of month is -1 in java, 5 in TZ
+ 	else
+	  {
+	    // First day of week starting on or after.  For example,
+	    // to specify the second Sunday of April, set month to
+	    // APRIL, day-of-month to 8, and day-of-week to -SUNDAY.
+	    day = (week - 1) * 7 + 1;
+	    dayOfWeek = -dayOfWeek;
+	  }
+
+	month--; // Java month is zero-based.
+	return new int[] { month, day, dayOfWeek };
+      }
+
+    // julian day, either zero-based 0<=n<=365 (incl feb 29)
+    // or one-based 1<=n<=365 (no feb 29)
+    int julianDay; // Julian day
+
+    if (date.charAt(0) != 'J' || date.charAt(0) != 'j')
+      {
+	julianDay = Integer.parseInt(date.substring(1));
+	julianDay++; // make 1-based
+	// Adjust day count to include feb 29.
+	dayCount = new int[]
+		   {
+		     0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
+		   };
+      }
+    else
+      // 1-based julian day
+      julianDay = Integer.parseInt(date);
+
+    int i = 11;
+    while (i > 0)
+      if (dayCount[i] < julianDay)
+	break;
+      else
+	i--;
+    julianDay -= dayCount[i];
+    month = i;
+    return new int[] { month, julianDay, 0 };
+  }
+
+  /**
+   * Parses a time field hh[:mm[:ss]], returning the result
+   * in milliseconds. No leading sign.
+   */
+  private static int parseTime(String time)
+  {
+    int millis = 0;
+    int i = 0;
+
+    while (i < time.length())
+      if (time.charAt(i) == ':')
+	break;
+      else
+	i++;
+    millis = 60 * 60 * 1000 * Integer.parseInt(time.substring(0, i));
+    if (i >= time.length())
+      return millis;
+
+    int iprev = ++i;
+    while (i < time.length())
+      if (time.charAt(i) == ':')
+	break;
+      else
+	i++;
+    millis += 60 * 1000 * Integer.parseInt(time.substring(iprev, i));
+    if (i >= time.length())
+      return millis;
+
+    millis += 1000 * Integer.parseInt(time.substring(++i));
+    return millis;
+  }
+}
--- libjava/Makefile.in.jj	2007-02-20 14:09:45.000000000 +0100
+++ libjava/Makefile.in	2007-02-20 17:07:07.000000000 +0100
@@ -2402,7 +2402,8 @@ gnu_java_text_header_files = $(patsubst 
 gnu_java_util_source_files = \
 classpath/gnu/java/util/DoubleEnumeration.java \
 classpath/gnu/java/util/EmptyEnumeration.java \
-classpath/gnu/java/util/WeakIdentityHashMap.java
+classpath/gnu/java/util/WeakIdentityHashMap.java \
+classpath/gnu/java/util/ZoneInfo.java
 
 gnu_java_util_header_files = $(patsubst classpath/%,%,$(patsubst %.java,%.h,$(gnu_java_util_source_files)))
 gnu_java_util_jar_source_files = \
--- libjava/java/util/VMTimeZone.h.jj	2007-01-26 19:02:12.000000000 +0100
+++ libjava/java/util/VMTimeZone.h	2007-02-20 17:31:13.000000000 +0100
@@ -16,8 +16,7 @@ public: // actually package-private
   static ::java::util::TimeZone * getDefaultTimeZoneId();
 private:
   static ::java::lang::String * readTimeZoneFile(::java::lang::String *);
-  static ::java::lang::String * readtzFile(::java::lang::String *);
-  static void skipFully(::java::io::InputStream *, jlong);
+  static ::java::lang::String * readSysconfigClockFile(::java::lang::String *);
   static ::java::lang::String * getSystemTimeZoneId();
 public:
   static ::java::lang::Class class$;
--- libjava/java/util/VMTimeZone.java.jj	2007-02-16 11:14:16.000000000 +0100
+++ libjava/java/util/VMTimeZone.java	2007-02-20 16:48:23.000000000 +0100
@@ -40,9 +40,9 @@ exception statement from your version. *
 package java.util;
 
 import gnu.classpath.Configuration;
+import gnu.classpath.SystemProperties;
+import gnu.java.util.ZoneInfo;
 import java.util.TimeZone;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
 
 import java.io.*;
 
@@ -78,9 +78,10 @@ final class VMTimeZone
    * The reference implementation which is made for GNU/Posix like
    * systems calls <code>System.getenv("TZ")</code>,
    * <code>readTimeZoneFile("/etc/timezone")</code>,
-   * <code>readtzFile("/etc/localtime")</code> and finally
-   * <code>getSystemTimeZoneId()</code> till a supported TimeZone is
-   * found through <code>TimeZone.getDefaultTimeZone(String)</code>.
+   * <code>ZoneInfo.readTZFile((String)null, "/etc/localtime")</code>
+   * and finally <code>getSystemTimeZoneId()</code> till a supported
+   * TimeZone is found through
+   * <code>TimeZone.getDefaultTimeZone(String)</code>.
    * If every method fails <code>null</code> is returned (which means
    * the TimeZone code will fall back on GMT as default time zone).
    * <p>
@@ -111,9 +112,51 @@ final class VMTimeZone
     // Try to parse /etc/localtime
     if (zone == null)
       {
-	tzid = readtzFile("/etc/localtime");
-	if (tzid != null && !tzid.equals(""))
-	  zone = TimeZone.getDefaultTimeZone(tzid);
+	zone = ZoneInfo.readTZFile((String) null, "/etc/localtime");
+	if (zone != null)
+	  {
+	    // Try to find a more suitable ID for the /etc/localtime
+	    // timezone.
+	    // Sometimes /etc/localtime is a symlink to some
+	    // /usr/share/zoneinfo/ file.
+	    String id = null;
+	    try
+	      {
+		id = new File("/etc/localtime").getCanonicalPath();
+		if (id != null)
+		  {
+		    String zoneinfo_dir
+		      = SystemProperties.getProperty("gnu.java.util.zoneinfo.dir");
+		    if (zoneinfo_dir != null)
+		      zoneinfo_dir
+			= new File(zoneinfo_dir
+				   + File.separatorChar).getCanonicalPath();
+		    if (zoneinfo_dir != null && id.startsWith(zoneinfo_dir))
+		      {
+			int pos = zoneinfo_dir.length();
+			while (pos < id.length()
+			       && id.charAt(pos) == File.separatorChar)
+			  pos++;
+			if (pos < id.length())
+			  id = id.substring(pos);
+			else
+			  id = null;
+		      }
+		    else
+		      id = null;
+		  }
+	      }
+	    catch (IOException ioe)
+	      {
+		id = null;
+	      }
+
+	    if (id == null)
+	      id = readSysconfigClockFile("/etc/sysconfig/clock");
+
+	    if (id != null)
+	      zone.setID(id);
+	  }
       }
 
     // Try some system specific way
@@ -189,466 +232,47 @@ final class VMTimeZone
   }
 
   /**
-   * 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>.
+   * Tries to read the time zone name from a file.
+   * If the file cannot be read or an IOException occurs null is returned.
    * <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).
+   * The /etc/sysconfig/clock file is not standard, but a lot of systems
+   * have it. The file is included by shell scripts and the timezone
+   * name is defined in ZONE variable.
+   * This routine should grok it with or without quotes:
+   * ZONE=America/New_York
+   * or
+   * ZONE="Europe/London"
    */
-  private static String readtzFile(String file)
+  private static String readSysconfigClockFile(String file)
   {
-    File f = new File(file);
-    if (!f.exists())
-      return null;
-
-    DataInputStream dis = null;
+    BufferedReader br = null;
     try
       {
-	FileInputStream fis = new FileInputStream(f);
+	FileInputStream fis = new FileInputStream(file);
 	BufferedInputStream bis = new BufferedInputStream(fis);
-	dis = new DataInputStream(bis);
+	br = new BufferedReader(new InputStreamReader(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')
+	for (String line = br.readLine(); line != null; line = br.readLine())
 	  {
-	    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 - 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 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 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)
-		  {
-		    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;
-		      }
-		  }
-	      }
-
-	    if (std_abbrind >= 0)
-	      {
-		byte[] names = new byte[charcnt];
-		dis.readFully(names);
-		int j = std_abbrind;
-		while (j < charcnt && names[j] != 0)
-		  j++;
-
-		String zonename = new String(names, std_abbrind,
-					     j - std_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 = "";
-
-		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, dst_offset_string = "";
-		if (dst_abbrind < 0
-		    && (std_offset == 0
-			|| zonename.startsWith("GMT+")
-			|| zonename.startsWith("GMT-")))
-		  offset_string = "";
-		else
-		  {
-		    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);
-	  }
-	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')
+	    line = line.trim();
+	    if (line.length() < 8 || !line.startsWith("ZONE="))
+	      continue;
+	    int posstart = 6;
+	    int posend;
+	    if (line.charAt(5) == '"')
+	      posend = line.indexOf('"', 6);
+	    else if (line.charAt(5) == '\'')
+	      posend = line.indexOf('\'', 6);
+	    else
 	      {
-		String posixtz = dis.readLine();
-		if (posixtz.length() > 0)
-		  id = posixtz;
+		posstart = 5;
+		posend = line.length();
 	      }
+	    if (posend < 0)
+	      return null;
+	    return line.substring(posstart, posend);
 	  }
-
-	return id;
+	return null;
       }
     catch (IOException ioe)
       {
@@ -659,31 +283,15 @@ final class VMTimeZone
       {
 	try
 	  {
-	    if (dis != null)
-	      dis.close();
+	    if (br != null)
+	      br.close();
 	  }
-	catch(IOException ioe)
+	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;
-      }
-  }
 
   /**
    * Tries to get the system time zone id through native code.
--- libjava/java/util/TimeZone.h.jj	2007-01-26 19:02:06.000000000 +0100
+++ libjava/java/util/TimeZone.h	2007-02-20 17:30:02.000000000 +0100
@@ -40,8 +40,14 @@ public:
   virtual jboolean useDaylightTime() = 0;
   virtual jboolean inDaylightTime(::java::util::Date *) = 0;
   virtual jint getDSTSavings();
+private:
+  static ::java::util::TimeZone * getTimeZoneInternal(::java::lang::String *);
+public:
   static ::java::util::TimeZone * getTimeZone(::java::lang::String *);
   static JArray< ::java::lang::String * > * getAvailableIDs(jint);
+private:
+  static jint getAvailableIDs(::java::io::File *, ::java::lang::String *, ::java::util::ArrayList *);
+public:
   static JArray< ::java::lang::String * > * getAvailableIDs();
   static ::java::util::TimeZone * getDefault();
   static void setDefault(::java::util::TimeZone *);
@@ -53,6 +59,9 @@ private:
   ::java::lang::String * __attribute__((aligned(__alignof__( ::java::lang::Object)))) ID;
   static ::java::util::TimeZone * defaultZone0;
   static const jlong serialVersionUID = 3581463369166924961LL;
+  static ::java::lang::String * zoneinfo_dir;
+  static JArray< ::java::lang::String * > * availableIDs;
+  static ::java::util::HashMap * aliases0;
   static ::java::util::HashMap * timezones0;
 public:
   static ::java::lang::Class class$;
--- libjava/java/lang/System.java.jj	2007-01-17 16:17:14.000000000 +0100
+++ libjava/java/lang/System.java	2007-02-20 16:35:13.000000000 +0100
@@ -1,5 +1,5 @@
 /* System.java -- useful methods to interface with the system
-   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
    Free Software Foundation, Inc.
 
 This file is part of GNU Classpath.
@@ -318,6 +318,7 @@ public final class System
    * <dt>gnu.java.io.encoding_scheme_alias.latin?</dt>       <dd>8859_?</dd>
    * <dt>gnu.java.io.encoding_scheme_alias.UTF-8</dt>        <dd>UTF8</dd>
    * <dt>gnu.java.io.encoding_scheme_alias.utf-8</dt>        <dd>UTF8</dd>
+   * <dt>gnu.java.util.zoneinfo.dir</dt>	<dd>Root of zoneinfo tree</dd>
    * </dl>
    *
    * @return the system properties, will never be null
--- libjava/sources.am.jj	2007-02-20 14:09:45.000000000 +0100
+++ libjava/sources.am	2007-02-20 17:06:43.000000000 +0100
@@ -2110,7 +2110,8 @@ gnu/java/text.list: $(gnu_java_text_sour
 gnu_java_util_source_files = \
 classpath/gnu/java/util/DoubleEnumeration.java \
 classpath/gnu/java/util/EmptyEnumeration.java \
-classpath/gnu/java/util/WeakIdentityHashMap.java
+classpath/gnu/java/util/WeakIdentityHashMap.java \
+classpath/gnu/java/util/ZoneInfo.java
 
 gnu_java_util_header_files = $(patsubst classpath/%,%,$(patsubst %.java,%.h,$(gnu_java_util_source_files)))
 
--- libjava/posix.cc.jj	2006-10-05 00:31:20.000000000 +0200
+++ libjava/posix.cc	2007-02-20 16:35:13.000000000 +0100
@@ -139,6 +139,10 @@ _Jv_platform_initProperties (java::util:
   if (! tmpdir)
     tmpdir = "/tmp";
   SET ("java.io.tmpdir", tmpdir);
+  const char *zoneinfodir = ::getenv("TZDATA");
+  if (! zoneinfodir)
+    zoneinfodir = "/usr/share/zoneinfo";
+  SET ("gnu.java.util.zoneinfo.dir", zoneinfodir);
 }
 
 static inline void
--- libjava/gnu/java/util/ZoneInfo.h.jj	2007-02-20 17:28:09.000000000 +0100
+++ libjava/gnu/java/util/ZoneInfo.h	2007-02-20 17:14:47.000000000 +0100
@@ -0,0 +1,70 @@
+
+// DO NOT EDIT THIS FILE - it is machine generated -*- c++ -*-
+
+#ifndef __gnu_java_util_ZoneInfo__
+#define __gnu_java_util_ZoneInfo__
+
+#pragma interface
+
+#include <java/util/TimeZone.h>
+#include <gcj/array.h>
+
+extern "Java"
+{
+  namespace gnu
+  {
+    namespace java
+    {
+      namespace util
+      {
+          class ZoneInfo;
+      }
+    }
+  }
+}
+
+class gnu::java::util::ZoneInfo : public ::java::util::TimeZone
+{
+
+public:
+  ZoneInfo(jint, ::java::lang::String *, JArray< jlong > *, ::java::util::SimpleTimeZone *);
+  virtual jint getOffset(jint, jint, jint, jint, jint, jint);
+private:
+  jlong findTransition(jlong);
+public:
+  virtual jint getOffset(jlong);
+  virtual jint getRawOffset();
+  virtual void setRawOffset(jint);
+private:
+  void computeDSTSavings();
+public:
+  virtual jint getDSTSavings();
+  virtual jboolean useDaylightTime();
+  virtual jboolean inDaylightTime(::java::util::Date *);
+  virtual jint hashCode();
+  virtual jboolean equals(::java::lang::Object *);
+  virtual jboolean hasSameRules(::java::util::TimeZone *);
+  virtual ::java::lang::String * toString();
+  static ::java::util::TimeZone * readTZFile(::java::lang::String *, ::java::lang::String *);
+private:
+  static void skipFully(::java::io::InputStream *, jlong);
+  static ::java::util::SimpleTimeZone * createLastRule(::java::lang::String *);
+  static JArray< jint > * getDateParams(::java::lang::String *);
+  static jint parseTime(::java::lang::String *);
+  static const jint SECS_SHIFT = 22;
+  static const jlong OFFSET_MASK = 2097151LL;
+  static const jint OFFSET_SHIFT = 43;
+  static const jlong IS_DST = 2097152LL;
+  jint __attribute__((aligned(__alignof__( ::java::util::TimeZone)))) rawOffset;
+  jint dstSavings;
+  jboolean useDaylight;
+  JArray< jlong > * transitions;
+  ::java::util::SimpleTimeZone * lastRule;
+  static ::java::util::SimpleTimeZone * gmtZone;
+public: // actually package-private
+  static const jlong serialVersionUID = -3740626706860383657LL;
+public:
+  static ::java::lang::Class class$;
+};
+
+#endif // __gnu_java_util_ZoneInfo__
-------------- next part --------------
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.Date;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

final class TimeZoneTest
{
  public static void main (String[] args)
    {
      long errorcount = 0;

      if (!new File("/usr/share/zoneinfo").isDirectory()
	  || !new File("/usr/sbin/zdump").exists())
        System.exit (0);

      TimeZone utc = (TimeZone) new SimpleTimeZone(0, "GMT");
      TimeZone.setDefault(utc);

      String[] zones = TimeZone.getAvailableIDs();
      for (int i = 0; i < zones.length; i++)
	{
	  if (!new File("/usr/share/zoneinfo/" + zones[i]).exists())
	    continue;
	  // These two timezones have different definitions between
	  // tzdata and JDK.  In JDK EST is EST5EDT, while in tzdata
	  // just EST5, similarly for MST.
	  if (zones[i].equals("EST") || zones[i].equals("MST"))
	    continue;

          TimeZone tz = TimeZone.getTimeZone(zones[i]);
          if (tz == null)
            {
              System.out.println ("getTimeZone(" + zones[i] + ") failed");
              errorcount++;
              continue;
            }

          Calendar cal = Calendar.getInstance(tz);

          BufferedReader br = null;
          Process process = null;
          try
            {
              process = Runtime.getRuntime().exec("/usr/sbin/zdump -v " + zones[i]);
              br = new BufferedReader(new InputStreamReader(process.getInputStream()));
              for (String line = br.readLine(); line != null; line = br.readLine())
		{
		  int end1 = line.indexOf(" UTC = ");
		  if (end1 < 0)
		    continue;
		  int start1 = line.indexOf("  ");
		  if (start1 < 0 || start1 >= end1)
		    continue;
		  int start2 = line.indexOf(" isdst=");
		  int start3 = line.indexOf(" gmtoff=");
		  if (start2 <= end1 || start3 <= start2)
		    continue;

                  Date d = new Date(line.substring(start1 + 2, end1 + 4));
                  cal.setTime(d);

		  int isdst = Integer.parseInt(line.substring(start2 + 7, start3));
		  int gmtoff = Integer.parseInt(line.substring(start3 + 8, line.length()));

		  if (tz.inDaylightTime(d) != (isdst != 0))
		    {
		      System.out.println ("Zone " + zones[i] + " " + d
                                          + " isdst=" + isdst + " inDaylightTime=" + tz.inDaylightTime(d));
		      errorcount++;
		    }
                  if (tz.getOffset(d.getTime()) != gmtoff * 1000)
                    {
		      System.out.println ("Zone " + zones[i] + " " + d
                                          + " gmtoff=" + gmtoff + " getOffset=" + tz.getOffset(d.getTime()));
		      errorcount++;
                    }
                  if (cal.get(Calendar.DST_OFFSET) + cal.get(Calendar.ZONE_OFFSET) != gmtoff * 1000)
                    {
		      System.out.println ("Zone " + zones[i] + " " + d
                                          + " gmtoff=" + gmtoff + " DST_OFFSET+ZONE_OFFSET=" + (cal.get(Calendar.DST_OFFSET) + cal.get(Calendar.ZONE_OFFSET)));
		      errorcount++;
                    
                    }
		}
            }
          catch (IOException ioe)
            {
            }
          finally
            {
              try
                {
                  if (br != null)
                    br.close();
                  if (process != null)
                    {
                      process.waitFor();
                      process.exitValue();
                    }
                }
              catch (IOException ioe)
                {
                }
              catch (InterruptedException ine)
                {
                }
            }
	}
      if (errorcount > 0)
        {
          System.out.println(errorcount + " errors encountered");
          System.exit(1);
        }
      System.out.println("All OK");
      System.exit(0);
    }
}
-------------- next part --------------
import gnu.classpath.SystemProperties;
import java.util.Calendar;
import java.util.Date;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

final class SimpleTimeZoneTest
{
  public static void main (String[] args)
    {
      long errorcount = 0;

      TimeZone utc = (TimeZone) new SimpleTimeZone(0, "GMT");
      TimeZone.setDefault(utc);
      Calendar cal = Calendar.getInstance(utc);

      TimeZone tz1 = new SimpleTimeZone(-12600000, "Canada/Newfoundland",
					Calendar.MARCH, 8, -Calendar.SUNDAY, 60000,
					Calendar.NOVEMBER, 1, -Calendar.SUNDAY, 60000);
      System.out.println(tz1);

      cal.set(2037, Calendar.NOVEMBER, 1, 2, 30, 0);
      if (!tz1.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 1 before failed");
          errorcount++;
        }
      cal.set(2037, Calendar.NOVEMBER, 1, 2, 31, 0);
      if (tz1.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 1 after failed");
          errorcount++;
        }
      cal.set(2038, Calendar.JANUARY, 1, 2, 29, 0);
      if (tz1.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 1 New Year failed");
          errorcount++;
        }

      TimeZone tz2 = new SimpleTimeZone(-12600000, "Test1",
					Calendar.MARCH, 8, -Calendar.SUNDAY, 60000, SimpleTimeZone.WALL_TIME,
					Calendar.NOVEMBER, 1, -Calendar.SUNDAY, 60000, SimpleTimeZone.STANDARD_TIME,
					3600000);
      System.out.println(tz2);

      cal.set(2037, Calendar.NOVEMBER, 1, 3, 30, 0);
      if (!tz2.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 2 before failed");
          errorcount++;
        }
      cal.set(2037, Calendar.NOVEMBER, 1, 3, 31, 0);
      if (tz2.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 2 after failed");
          errorcount++;
        }
      if (tz2.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 2 New Year failed");
          errorcount++;
        }

      TimeZone tz3 = new SimpleTimeZone(-12600000, "Test2",
					Calendar.MARCH, 8, -Calendar.SUNDAY, 60000,
					Calendar.NOVEMBER, 1, -Calendar.SUNDAY, 3660000,
					3600000);
      System.out.println(tz3);

      cal.set(2037, Calendar.NOVEMBER, 1, 3, 30, 0);
      if (!tz3.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 3 before failed");
          errorcount++;
        }
      cal.set(2037, Calendar.NOVEMBER, 1, 3, 31, 0);
      if (tz3.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 3 after failed");
          errorcount++;
        }
      if (tz3.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 3 New Year failed");
          errorcount++;
        }

      if (tz2.equals(tz3) || tz2.hasSameRules(tz3))
        {
          System.out.println("tz2.equals(tz3) || tz2.hasSameRules(tz3)");
          errorcount++;
        }

      ((SimpleTimeZone) tz2).setEndRule(Calendar.NOVEMBER, 1, -Calendar.SUNDAY, 3660000);

      cal.set(2037, Calendar.NOVEMBER, 1, 3, 30, 0);
      if (!tz2.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 4 before failed");
          errorcount++;
        }
      cal.set(2037, Calendar.NOVEMBER, 1, 3, 31, 0);
      if (tz2.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 4 after failed");
          errorcount++;
        }
      if (tz2.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 4 New Year failed");
          errorcount++;
        }

      if (tz2.equals(tz3) || !tz2.hasSameRules(tz3) || tz2.hashCode() != tz3.hashCode())
        {
          System.out.println("tz2.equals(tz3) || !tz2.hasSameRules(tz3) || tz2.hashCode() != tz3.hashCode()");
          errorcount++;
        }
      tz3.setID("Test1");
      if (!tz2.equals(tz3))
        {
          System.out.println("!tz2.equals(tz3)");
          errorcount++;
        }

      TimeZone tz4 = new SimpleTimeZone(-12600000, "Test1",
					Calendar.MARCH, 8, -Calendar.SUNDAY, 60000, SimpleTimeZone.STANDARD_TIME,
					Calendar.NOVEMBER, 1, -Calendar.SUNDAY, 60000, SimpleTimeZone.STANDARD_TIME,
					3600000);
      System.out.println(tz4);

      cal.set(2037, Calendar.NOVEMBER, 1, 3, 30, 0);
      if (!tz4.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 5 before failed");
          errorcount++;
        }
      cal.set(2037, Calendar.NOVEMBER, 1, 3, 31, 0);
      if (tz4.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 5 after failed");
          errorcount++;
        }
      if (tz4.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 5 New Year failed");
          errorcount++;
        }

      // A very much made up zone
      TimeZone tz5 = new SimpleTimeZone(-12600000, "Test3",
					Calendar.MARCH, 8, -Calendar.SUNDAY, 60000,
					Calendar.JANUARY, 1, 0, 60000,
					3600000);
      System.out.println(tz5);

      cal.set(2007, Calendar.DECEMBER, 31, 23, 59, 0);
      if (!tz5.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 6 before 1 failed");
          errorcount++;
        }
      cal.set(2007, Calendar.JANUARY, 1, 2, 29, 0);
      if (!tz5.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 6 before 2 failed");
          errorcount++;
        }
      cal.set(2007, Calendar.JANUARY, 1, 2, 30, 0);
      if (!tz5.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 6 before 3 failed");
          errorcount++;
        }
      cal.set(2007, Calendar.JANUARY, 1, 2, 31, 0);
      if (tz5.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 6 after 1 failed");
          errorcount++;
        }
      cal.set(2007, Calendar.JANUARY, 3, 2, 31, 0);
      if (tz5.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 6 after 2 failed");
          errorcount++;
        }
      cal.set(2007, Calendar.JANUARY, 3, 2, 31, 0);
      if (tz5.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 6 after 2 failed");
          errorcount++;
        }
      cal.set(2007, Calendar.MARCH, 11, 3, 30, 0);
      if (tz5.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 6 after 3 failed");
          errorcount++;
        }
      cal.set(2007, Calendar.MARCH, 11, 3, 31, 0);
      if (!tz5.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 6 after 4 failed");
          errorcount++;
        }

      TimeZone tz6 = new SimpleTimeZone(12600000, "Test4",
					Calendar.MARCH, 6, 0, 18000000 - 12600000, SimpleTimeZone.UTC_TIME,
					Calendar.NOVEMBER, -16, -Calendar.THURSDAY, 18000000 - 3600000 - 12600000, SimpleTimeZone.UTC_TIME,
					3600000);
      System.out.println(tz6);

      cal.set(2007, Calendar.MARCH, 6, 1, 29, 0);
      if (tz6.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 7 before failed");
          errorcount++;
        }
      cal.set(2007, Calendar.MARCH, 6, 1, 30, 0);
      if (!tz6.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 7 after 1 failed");
          errorcount++;
        }
      cal.set(2007, Calendar.NOVEMBER, 15, 0, 29, 0);
      if (!tz6.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 7 after 2 failed");
          errorcount++;
        }
      cal.set(2007, Calendar.NOVEMBER, 15, 0, 30, 0);
      if (tz6.inDaylightTime(new Date(cal.getTimeInMillis())))
        {
          System.out.println("inDaylightTime test 7 after 3 failed");
          errorcount++;
        }

      if (errorcount > 0)
        {
          System.out.println(errorcount + " errors");
          System.exit(1);
        }
      System.out.println("All OK");
      System.exit(0);
    }
}


More information about the Java-patches mailing list