package java.util;
import gnu.classpath.Configuration;
+import java.io.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
import java.text.DateFormatSymbols;
/**
* 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;
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
/**
* 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;
}
// natTimeZone.cc -- Native side of TimeZone class.
-/* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 Free Software Foundation
+/* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004
+ Free Software Foundation
This file is part of libgcj.
#include <string.h>
-/*
- * This method returns a time zone string that is used by init_properties
- * to set the default timezone property 'user.timezone'. That value is
- * used by default as a key into the timezone table used by the
- * java::util::TimeZone class.
+/**
+ * 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.
*/
-static jstring
-getSystemTimeZone (void)
+jstring
+java::util::TimeZone::getDefaultTimeZoneId ()
{
- struct tm *tim;
+ struct tm tim;
+#ifndef HAVE_LOCALTIME_R
+ struct tm *lt_tim;
+#endif
+#ifdef HAVE_TM_ZONE
+ int month;
+#endif
time_t current_time;
long tzoffset;
const char *tz1, *tz2;
char *tzid;
- current_time = time(0);
+ time(¤t_time);
+#ifdef HAVE_LOCALTIME_R
+ localtime_r(¤t_time, &tim);
+#else
+ /* Fall back on non-thread safe localtime. */
+ lt_tim = localtime(¤t_time);
+ memcpy(&tim, lt_tim, sizeof (struct tm));
+#endif
+ mktime(&tim);
+
+#ifdef HAVE_TM_ZONE
+ /* We will cycle through the months to make sure we hit dst. */
+ month = tim.tm_mon;
+ tz1 = tz2 = NULL;
+ while (tz1 == NULL || tz2 == NULL)
+ {
+ if (tim.tm_isdst > 0)
+ tz2 = tim.tm_zone;
+ else if (tz1 == NULL)
+ {
+ tz1 = tim.tm_zone;
+ month = tim.tm_mon;
+ }
+
+ if (tz1 == NULL || tz2 == NULL)
+ {
+ tim.tm_mon++;
+ tim.tm_mon %= 12;
+ }
+
+ if (tim.tm_mon == month && tz2 == NULL)
+ tz2 = "";
+ else
+ mktime(&tim);
+ }
+ /* We want to make sure the tm struct we use later on is not dst. */
+ tim.tm_mon = month;
+ mktime(&tim);
+#elif defined (HAVE_TZNAME)
+ /* If dst is never used, tzname[1] is the empty string. */
+ tzset();
+ tz1 = tzname[0];
+ tz2 = tzname[1];
+#else
+ /* Some targets have no concept of timezones. Assume GMT without dst. */
+ tz1 = "GMT";
+ tz2 = "";
+#endif
- mktime(tim = localtime(¤t_time));
#ifdef STRUCT_TM_HAS_GMTOFF
- // tm_gmtoff is secs EAST of UTC.
- tzoffset = -(tim->tm_gmtoff) + tim->tm_isdst * 3600L;
+ /* tm_gmtoff is the number of seconds that you must add to GMT to get
+ local time, we need the number of seconds to add to the local time
+ to get GMT. */
+ tzoffset = -1L * tim.tm_gmtoff;
#elif HAVE_UNDERSCORE_TIMEZONE
tzoffset = _timezone;
#elif HAVE_TIMEZONE
- // timezone is secs WEST of UTC.
+ /* timezone is secs WEST of UTC. */
tzoffset = timezone;
#else
- // FIXME: there must be another global if neither tm_gmtoff nor timezone
- // is available, esp. if tzname is valid.
- // Richard Earnshaw <rearnsha@arm.com> has suggested using difftime to
- // calculate between gmtime and localtime (and accounting for possible
- // daylight savings time) as an alternative.
+ /* FIXME: there must be another global if neither tm_gmtoff nor timezone
+ is available, esp. if tzname is valid.
+ Richard Earnshaw <rearnsha@arm.com> has suggested using difftime to
+ calculate between gmtime and localtime (and accounting for possible
+ daylight savings time) as an alternative. */
tzoffset = 0L;
#endif
-#ifdef HAVE_TM_ZONE
- tz1 = tim->tm_zone;
- tz2 = "";
-#elif defined (HAVE_TZNAME)
- tz1 = tzname[0];
- tz2 = strcmp (tzname[0], tzname[1]) ? tzname[1] : "";
-#else
- // Some targets have no concept of timezones.
- tz1 = "???";
- tz2 = tz1;
-#endif
-
if ((tzoffset % 3600) == 0)
tzoffset = tzoffset / 3600;
return retval;
}
-
-// Get the System Timezone as reported by the OS. It should be in
-// the form PST8PDT so we'll need to parse it and check that it's valid.
-// FIXME: Using the code from Classpath for generating the System
-// Timezone IMO is suboptimal because it ignores whether the rules for
-// DST match up.
-jstring
-java::util::TimeZone::getDefaultTimeZoneId ()
-{
- jstring sysTimeZoneId = getSystemTimeZone ();
-
- using namespace java::lang;
-
- // Check if this is a valid timezone. Make sure the IDs match
- // since getTimeZone returns GMT if no match is found.
- TimeZone *tz = TimeZone::getTimeZone (sysTimeZoneId);
- if (tz->getID ()->equals (sysTimeZoneId))
- return sysTimeZoneId;
-
- // Check if the base part of sysTimeZoneId is a valid timezone that
- // matches with daylight usage and rawOffset. Make sure the IDs match
- // since getTimeZone returns GMT if no match is found.
- // 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++)
- {
- if (Character::isDigit (sysTimeZoneId->charAt (i)))
- startGMToffset = i;
- }
-
- int startDaylightZoneName = 0;
- jboolean usesDaylight = false;
- for (int i = sysTimeZoneIdLength - 1;
- i >= 0 && !Character::isDigit (sysTimeZoneId->charAt (i)); --i)
- {
- startDaylightZoneName = i;
- }
- if (startDaylightZoneName > 0)
- usesDaylight = true;
-
- int GMToffset
- = Integer::parseInt (startDaylightZoneName == 0 ?
- sysTimeZoneId->substring (startGMToffset) :
- sysTimeZoneId->substring (startGMToffset,
- startDaylightZoneName));
-
- // Offset could be in hours or seconds. Convert to millis.
- if (GMToffset < 24)
- GMToffset *= 60 * 60;
- GMToffset *= -1000;
-
- jstring tzBasename = sysTimeZoneId->substring (0, startGMToffset);
- tz = TimeZone::getTimeZone (tzBasename);
- if (tz->getID ()->equals (tzBasename) && tz->getRawOffset () == GMToffset)
- {
- jboolean tzUsesDaylight = tz->useDaylightTime ();
- if (usesDaylight && tzUsesDaylight || !usesDaylight && !tzUsesDaylight)
- return tzBasename;
- }
-
- // If no match, see if a valid timezone has the same attributes as this
- // and then use it instead.
- jstringArray IDs = TimeZone::getAvailableIDs (GMToffset);
- jstring *elts = elements (IDs);
- for (int i = 0; i < IDs->length; ++i)
- {
- // FIXME: The daylight savings rules may not match the rules
- // for the desired zone.
- jboolean IDusesDaylight =
- TimeZone::getTimeZone (elts[i])->useDaylightTime ();
- if (usesDaylight && IDusesDaylight || !usesDaylight && !IDusesDaylight)
- return elts[i];
- }
-
- // If all else fails, return null.
- return NULL;
-}