diff options
Diffstat (limited to 'libjava/java/util/TimeZone.java')
-rw-r--r-- | libjava/java/util/TimeZone.java | 472 |
1 files changed, 438 insertions, 34 deletions
diff --git a/libjava/java/util/TimeZone.java b/libjava/java/util/TimeZone.java index 504d34e..cc3c7a3 100644 --- a/libjava/java/util/TimeZone.java +++ b/libjava/java/util/TimeZone.java @@ -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,19 +842,363 @@ 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 * time. @@ -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; } |