re PR libgcj/17002 (java.util.TimeZone.getDefault() is broken)

2004-08-26  Mark Wielaard  <mark@klomp.org>

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

From-SVN: r86623
This commit is contained in:
Mark Wielaard 2004-08-26 16:10:54 +00:00 committed by Mark Wielaard
parent 20edd06b2f
commit 4234810174
3 changed files with 538 additions and 143 deletions

View file

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