From b3502aa8d46d9cd008916170a635b2bf3a5d5125 Mon Sep 17 00:00:00 2001 From: Jakub Jelinek Date: Thu, 22 Feb 2007 17:04:55 +0100 Subject: [PATCH] re PR libgcj/17002 (java.util.TimeZone.getDefault() is broken) 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. From-SVN: r122229 --- libjava/ChangeLog | 20 + libjava/Makefile.in | 3 +- libjava/classpath/ChangeLog | 31 + libjava/classpath/gnu/java/util/ZoneInfo.java | 1160 +++++++++++++++++ libjava/classpath/java/util/Date.java | 9 + .../classpath/java/util/SimpleTimeZone.java | 118 +- libjava/classpath/java/util/TimeZone.java | 314 ++++- .../lib/gnu/java/util/ZoneInfo.class | Bin 0 -> 12350 bytes libjava/classpath/lib/java/util/Date.class | Bin 10943 -> 8740 bytes .../lib/java/util/SimpleTimeZone.class | Bin 10254 -> 7861 bytes .../classpath/lib/java/util/TimeZone$1.class | Bin 1134 -> 859 bytes .../classpath/lib/java/util/TimeZone.class | Bin 27946 -> 29024 bytes .../classpath/lib/java/util/VMTimeZone.class | Bin 9024 -> 3256 bytes libjava/gnu/java/util/ZoneInfo.h | 70 + libjava/java/lang/System.java | 3 +- libjava/java/util/TimeZone.h | 9 + libjava/java/util/VMTimeZone.h | 3 +- libjava/java/util/VMTimeZone.java | 562 ++------ libjava/posix.cc | 4 + libjava/sources.am | 3 +- 20 files changed, 1702 insertions(+), 607 deletions(-) create mode 100644 libjava/classpath/gnu/java/util/ZoneInfo.java create mode 100644 libjava/classpath/lib/gnu/java/util/ZoneInfo.class create mode 100644 libjava/gnu/java/util/ZoneInfo.h diff --git a/libjava/ChangeLog b/libjava/ChangeLog index 6d337d522e6..eaea0ecaef3 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,23 @@ +2007-02-22 Jakub Jelinek + + 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. + 2007-02-22 Mohan Embar * include/win32-threads.h: Added #undef OUT. diff --git a/libjava/Makefile.in b/libjava/Makefile.in index db8813ed19b..27aa9ae9e69 100644 --- a/libjava/Makefile.in +++ b/libjava/Makefile.in @@ -2402,7 +2402,8 @@ gnu_java_text_header_files = $(patsubst classpath/%,%,$(patsubst %.java,%.h,$(gn 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 = \ diff --git a/libjava/classpath/ChangeLog b/libjava/classpath/ChangeLog index c95970572e6..fa92bda1bb0 100644 --- a/libjava/classpath/ChangeLog +++ b/libjava/classpath/ChangeLog @@ -1,3 +1,34 @@ +2007-02-20 Jakub Jelinek + + * 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. + 2007-02-20 Matthias Klose * doc/Makefile.am: Add rules to build and install man pages diff --git a/libjava/classpath/gnu/java/util/ZoneInfo.java b/libjava/classpath/gnu/java/util/ZoneInfo.java new file mode 100644 index 00000000000..2146a321f40 --- /dev/null +++ b/libjava/classpath/gnu/java/util/ZoneInfo.java @@ -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 ZoneInfo 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 + * () 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; + } +} diff --git a/libjava/classpath/java/util/Date.java b/libjava/classpath/java/util/Date.java index 5c43bf3c154..f481753db8d 100644 --- a/libjava/classpath/java/util/Date.java +++ b/libjava/classpath/java/util/Date.java @@ -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') diff --git a/libjava/classpath/java/util/SimpleTimeZone.java b/libjava/classpath/java/util/SimpleTimeZone.java index d94f89ad3f9..14821ba0274 100644 --- a/libjava/classpath/java/util/SimpleTimeZone.java +++ b/libjava/classpath/java/util/SimpleTimeZone.java @@ -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 TimeZone /** * 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 TimeZone /** * 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 TimeZone 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 TimeZone 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 TimeZone 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 TimeZone 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 TimeZone 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 TimeZone 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. diff --git a/libjava/classpath/java/util/TimeZone.java b/libjava/classpath/java/util/TimeZone.java index a253561b046..cede9fc789f 100644 --- a/libjava/classpath/java/util/TimeZone.java +++ b/libjava/classpath/java/util/TimeZone.java @@ -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 implements java.io.Serializable, Cloneable // Fall back on GMT. if (zone == null) - zone = (TimeZone) timezones().get("GMT"); + zone = getTimeZone ("GMT"); return zone; } @@ -127,6 +130,22 @@ public abstract class TimeZone implements java.io.Serializable, Cloneable 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. */ @@ -135,13 +154,55 @@ public abstract class TimeZone implements java.io.Serializable, Cloneable * 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 implements java.io.Serializable, Cloneable static TimeZone getDefaultTimeZone(String sysTimeZoneId) { String stdName = null; - String dstName; int stdOffs; int dstOffs; try @@ -900,14 +960,14 @@ public abstract class TimeZone implements java.io.Serializable, Cloneable // 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 implements java.io.Serializable, Cloneable 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 implements java.io.Serializable, Cloneable // 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 implements java.io.Serializable, Cloneable } // get the dst offset - dstName = sysTimeZoneId.substring(prevIndex, --index); prevIndex = index; do c = sysTimeZoneId.charAt(index++); @@ -1005,7 +1064,7 @@ public abstract class TimeZone implements java.io.Serializable, Cloneable 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 implements java.io.Serializable, Cloneable 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 implements java.io.Serializable, Cloneable * @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 implements java.io.Serializable, Cloneable } } - 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 implements java.io.Serializable, Cloneable } } - // 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 implements java.io.Serializable, Cloneable */ public static String[] getAvailableIDs(int rawOffset) { - int count = 0; - Iterator iter = timezones().entrySet().iterator(); - while (iter.hasNext()) + synchronized (TimeZone.class) { - // 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++; + 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; + for (int i = 0; i < s.length; i++) + { + 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; + } } /** diff --git a/libjava/classpath/lib/gnu/java/util/ZoneInfo.class b/libjava/classpath/lib/gnu/java/util/ZoneInfo.class new file mode 100644 index 0000000000000000000000000000000000000000..3ff3706ba849a9da3bd4dc1f9f2dc08a6a02d9bb GIT binary patch literal 12350 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 XH16|_L8osl4*T+{_c@ekslxvO#A@o` literal 10254 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 diff --git a/libjava/classpath/lib/java/util/TimeZone$1.class b/libjava/classpath/lib/java/util/TimeZone$1.class index fdc1c6149b55a0046deba30d5cbbdc6ef7c6cb3d..08d8bd2f52a4b4f5344988056b7de9c821432a73 100644 GIT binary patch delta 406 zcmY*VyG{a86r97NyIdEARa8`vmng8Z;43jU#>NIJsq83L#PCp9jA&tNOSbbXv`{w| zmXi1j{)2y@@wmpq++^nD+%q%xo)6`HRJ?sXzW^v=Yanh0!$>he@b|iACvVl8=4HEO z3TT*;F-ZuVRL`qMquQz&2mP*PHdTZ$ZHH((HA{fN92R8E{~xqE^;S(q1W~&~$yf{t z8F7NDl3AqS~QB4t0&-e?*tGM4R63QHMe?Xag1SD+K5{c=ShhP(;`DZ*II zwEon#kand_o-Rw7>o|xHFp_f*%>DU52OQvo5%K}}w z{I)=P)u~ApUFg%GAR~}1o3?x#G&@5$eNuWRQq&w@gq!X5;VhEQN40lwudF=VJq2UUy3iQ>bzu`Iu()Eu8 zrg90`P2V-`dMSBRyp_Q<0jRivn+ird!J|VO#&C<8$iu*}Sk6!qx20++zoTLTw-roM zivO$CFoipekl&hoZv>X#?vp?+3D-%pqoXAidCVx7?&$Y+rH!PafV+(Sf5&=_J!p%3 z7IOkc4fil7(AyR|sxek7N`GT`dlln=O<7>mURSnM+{Z%&4+IAO6&q)1c!bAHBabsZ zXls>dOT0ykg&ijQbF4z6r2P|u~Gu+rW3eT`HBvvW7=xWhP(u{b=#Kis%3bd zWLeplcGYq`I-w%{J*TE&HPo!H)@n-laHs{z^XujVOYx)Ox$5QL0%uwU-P2#8eu=^w zM>R^5NHf^awbn|)1!TFRp)kNP1o+6zF3yXhtm{n-Rti5cP|!zCas3lAg(k+!GryoT zkqhT+6SMmKDHeZVqCE4N=TgK5trTLJTv?>n;H$7PfLm@D diff --git a/libjava/classpath/lib/java/util/TimeZone.class b/libjava/classpath/lib/java/util/TimeZone.class index ca7db2b2ff24d7bf3eb9ce6b0dd4fd41de71124d..556c26d6ed97a2c3fda2f4ba0971f2cf80f5c5ef 100644 GIT binary patch literal 29024 zcmbt-2Ygf2`}bM7xoy)nO`5hTAgCbFvK7QKq=Qj9p#=oglD44@O_Q1d6c;M)Eh>Tw zT(|`XpeUjU;+CHi_uhMN-tW1|&AI*A|2safAK^ad+~R;@O2V-(}qcDpEPLD|g%NaE$HP;`B2Ew5^vNt-@Hu}EL ze_mE`=LYn$0?h-V#o?ybKmW^Ynw`J5BzG!=muM2~w z$G|n&u|&})wgdyAKx{H4e8|wbAg?^!>Q4im2Rlds@lujq9SHero#6&y0A3M-ffCZ&@KGF=L8h9oq>s~+9W zl^8P}q>5By_&~HG5P>;`BN!e^vOxA->mY@sk51}QRa)MtliASb(s(o$k?J?H!WUT< z2uXG{I>;OXz2aT*7$xJa!zHYAf@K298Y?}?DxDl7N1BR}7W5dYp}RHY@7Bq&^7VAQ zJ|286?Sx&m_(qmS+WetdAmkfa7Waq3(WX*R64l8fi98#~&0tOGVgt9_7l?#)0{!8o z4S2Pfk}R{InQ^#>xK7&fvP`~QBHKklXn8mo?rcW8AYPT1;#CMHlk9T3uQ?onq3NVc zzOBVuSh3UWwIS9tGu$4+e83rAF8f_di5cr#TF zb_$$SWvDga3t=Xm7)&QC*XZxM{R?w-2yc*$F2{Dd|jYx8(z+pFCQ}1pbSx`Bivyp4^vX0+(JX3vn%K~9U%Yv z#+@tE$#(LnG`^kEPgYu#-!$gAI(gi}%F?a?X8D9=8a08|Wj?6<9=M>?)O>B>rh5Fv z|9;~hqLZiO%uFk2^>uL4`8JVbKP$(64mQ%;*p~bC_wq4qtdN zwCp8|NM?jX;b1%n`*_7tYp8ZC3`x9ZndfX@B#g>7khI@IQlqaOLg#<~a@Z7|yk)_! z@kLIE`=h=n|0n0#l{$GB?^1MyTH3=AUmGOyzJ=V{a3^X$lxCP@zoW^_V_-?#7s3p`u(*H<-?FGA<|~9%v)^jzQ9^CdH)!iOmYL1< zg<8V#rT&OczC(gw;?Dz-eoQ%v`Tc;t=QNB=GIn^af0<5xvaqi{5Nm-)3h{qVT>-j( zwe()z9%zsI@V`!ex2SZDZ-p-g)6mJE2(IRsiFr4bNBsT{o&0T?%?y8}GaQ8xK%56G zG`lGa2QX{O5i!iFaxgFwXl?U1l|dV+&B`o)ND@!&mg!Ax?L^!L z3u&4KvoaK48tBkzh6S^sJeFeirI#B2@<2IqTP|zfGWt=5$_dtMYNWS_zbvoWc`D|Yx)D0;ZidJHwX0|WX z5ePL^hJt=DYobN5<^@8bK$jmhOa{kIPEGY8?TT=*?Nq}5`Fh?;ot9bJl*R&Z?64|Y zVUbc4Sxv#rZ;?1zMo*Ix58!>~Od#Pske+%54DkXvn?t)I}mOvi%74d(?$#K3}m1oA1C9!0a^1b4W4ftAfi zBr-))577+w#fKwhhD3F`&@#UYKho|1czPTqea!}@h9sX|;a~tt&_qf8e`COv`$JOo zd=}LTo;|Mamzj7vsznlx43BnNFs0(n4@(Y(Ez_zEbOqX^8#}?GjF3rNC=iLoLr^Zc zG&6~BY>$Up{gH%=j9a>Cl$l{H%nuBl23NM+GHhuy9%{iVMW-t)_@$u6a26+7ymDzI z-V9AZ7MMyZ%Y2b$cp%6CiC`)Ulp!v(3~#{tP4;`bMHkD$zNWbWI5eHEvJ6-5i}+f6 z=;bVHWn5N*pXXQxtdJINsO@Zm|#X;Q>n}xQvA%__)=g zmC$)iJAz6jRmShv;tf=JzT`lz*w2JFNH z{oVY)(T_~w2NoUykG9J)mFeNuSi7&8i*GyeW{+i<8mS$I;_tO^S1O2qX`s~)!98su zzriOH>9}+o&swTWk!Z%DG9cl3i@Q-P%yM50ZaE^8f)_0j(t;3h;X{ zk~loz%a-V?UcwsS;+LTDtG)J(K-(H#f!D8FqNr*v!vhim?2kyt@J6pgK$n|lBJb7d zTfGj^7;lLi@$H>nU(bmmA(e{#o<$SNkk+H>18WtsWkm9iERsXul(~aWKe6z88k8JC zU()oMMVV`$n*oUC3yUP{;j6&;0G8qOD+}&yKNh3t_8W<-lxuc4*6xq^_<_p~h3Ea= zG7+R5;ZQq9{82_kQ*|4{abzpg{ozPk0Iigmt!nXVZrbs;ib?ER-h(R z8;<)ru$+sZ}B4NnXl5ig-Y06BT=rGOkzwuE;Ek zO;)HCkOl$_o1!>g6$pW^QB;;Gy{I-87_36cu{zKkfmby;oTfUghGT*EXERh)4Mu>) zvYBd(TBv)cA&Y8-R~tYbcu=bbF#`4Q+VD~|uUE~H-nU}yioK4To>{502FSBU%5#pI zaJe5F0Xjn_Y#LN!JcNXmAqRGs8qo)tWvH#&k2DHiNoPoal@`r}kF1g2N@vJ{ok{7H zcFF}rtFKdM$a}NQS_lQ{ht81hn!?9wvMmBV(HRn5Q%MjS4JAREo0Dt{xx@m|Ad+{T zA-PR8kHn)NU%?`?;eeh)1{nVLEX_Ng@gEdLBOR~fsI2nIqJ;!MSw z1_XvBeyJsAtNt1~!d*+y-??hwh8T7m+QUHz@O*{X2x~@EVi&48&-Jy&SLp0wCFQvg zRExCQOI5?U0faLUv07El^F^Y*Wjeb;RW0zfMf}Y=TVtuh)@MsQw;gsY(b+oCVOp+C zqPioP%Qcv^S&4k16Dx8IbRD`#*3JOQ5^=rM=Y$W%Mx0MB&5hMNyFuYr$$bMjgqy&~ zByM%Q8J*%&5vp%dnAPzpB7n|rRaN+w6pw+kw_B@_aKlgEsr*n)Gg42<#Jg2pV*OXn+#Cjwc>%lE++8m1l*tCWKehD@=NgBSZ;|84Miq#A(H1NR*fxB-F{-9x z2TW%V!S1I{ZRF(5hrmW;mI?eytZ_okGAtsiOl;oQBB4iDI(rOANj)t^a)2#)hSbuu zkFt16JD4g>Y`0P_?4(4I4uak%RV88xDmy^jQ>s#p{EX7!s(^o~&Yn}FNd5J<`#M21 zwq8vylA7W^)pR!Grn8qU>L2J_>_@-}gSc0ewl;(pOX6Nry)_~S#2$#w_N&>=SJ+*L3#28odSq1JU+Fli6jy zw)R$u^RZ%fO*q;ThFN^7*f2L7Yzrqk`&}h_FAPj>Fi4>(u8i8MiKB)^|j0b z5>@#0)Z~xK9V4Ig$lroe^UV^Zi1-|*GbDXxl2wIulM(BFRESj&cPt!7>z|dq)r66W z1d!$G>{r#iR(>^VMwh=!OqFCdbcdH=UVoaZS=uUH@!yIW?Gd^DWCu+Bu8J@7VY7o% z(*R~+JrU74HxXtBx_zzfI=8{H6T2oO<#-{Pp9J9JxE-@fzCesZCWSB7JIi+J964X& z^HHN8qlR>jY%gIjW!Mg=i z+Jcof&o_H%Yz|-}2q<2lq-NFb*Lfc`&Dnkg-*AYNUq?^TIWoD#hovf?G*fQmZ_?7| z`#Z3I3h!=sFpm5!fmeD$4Ccp?w_q;r|48P2K77lTJ@1-~oT5SOmJ9IuNMPsnj zhplUVv^o1SsALDW_IatvmWp=it9iLO{aM{Bx`SAIqHe0$s<9nkrl87vI;NMLeL3<4 zEDw35)MUwbnP5c&&4CE)m{+Bk(;4mH)Sm^3Atg(&a5hrQFj7#CY|LSH7lw1?bmf^V zb>7g^I4Y2GVxT!@^O7CCXPJzg z1IHU`_DT1}>3g@lr*ouQmL8_YTY^4LziEbITwn?u%nUU9f>G&sE;1!l0Ux6COH4v$ zV%HoD0&SO>gw%J-WF7SB{Bn~=jp2@NxWg;a%-rI~ev)L{Rpz9P4}y?0OWVIw=U2-( zlOhnFI_Sf;kj}4_JAJA8vPc*P-!ubj8_ovJf_@;!@>)!|BHjrFt(X%@EPpsMElam$ zP(ULU;z+kF_0<9F56e`CBi~AWKgSn>PMss&%1x0V_bqD@I}JKV)|L8x0Tbww?eDa- zZ;ZqtlDkdLHTXK(Wti6aCR0jtgNSCZZ^OMH>#`i)kRO@?pdY!HrFI7Pt|IVvNWk2R zD>eS0TqA`1oK4v~MduIpcv}s((=;Qz43VhMvMKod%pb<*XZ{E!{r`8LpDTyXQJ7O_rUjJTllCorbTCNQoQXBOZk;KBD;xKtI6MtnftYpKJgL#N-z z*|IIr0_0|?&)nDuS{x2SKUR-`oS&Bhv$*P(nmBgybpE2$4^^MoC#nx)EzSR98c`{B z!MkCeFPj>Rd;{5JOnT^7EdrYsSOQgl9UM0)nHCTE7RzjfzX3O4)>bX^EkUL%MfsM6 zk{aI<>^SKB9Se7AagqW`|DFY{5upxg3ID({@;qM$Cj5~FYo5#!qntgw=sleyHA}Gt z5dvGRot&)6evgyq{#nG?(|sYOuPhUsDnEB1P{UJxW2tV0ox&q@#gWitfTzg7Tq&9Y z^27(?S^$+e>{O9BI%gi6pk#>k)R1{hB5`uUIDKVePDtlJ^Iz@!7qHPdgn@Na+sH%% z2mg)#PKk!Sp0*f#I1VyZO7uS+{4f4DUO+05QaD;Mq;hC^IzJ$&T@YxN>Imc`Q$Qx- z5KJI1v&)lFZ2_ZA`OpRFgbJHOXdIcO8)TTXs+8Y1p^q3%=nj!4kdLKB58rQGZOn$a9E%dDtsE9`#rFx`TnXc4PkXo!21>Wur_fnuf*(1n*E=R8Ft2L-Z3! z+w9VZEw4kol*}_m8sHEIi$ju)V5P<$ZB!E5%iI=L+QmRh4ppp1*hYu(Sl!~XFt$~r zQXSlB7ehpmT?~c(DNHGbHYvMe$uHgD7wRM(!yRIT zq$4AUPr-?yK}#CwO>hYCki^NDp-7IzkeNd<;lp7uM(>6SR)EHIF-DAqe-q;pNH|7E z#dwD}N|XRM5VA5BDw5Y;^4Wn>VhC50M@b_IRiwb*A%7t;l3n_(J|F}dn+ z3tWyv%oIoh(`AE%fm-Hc!J8ndu5pN|qE=e&g5(h=D@HH6tPVE*9})A=XD>b2#cX_+ zF(eZ6FOOk=Am&f#MMF3Cv4YWbF-zby;-Lb0r)eTR2AeA}<~hVMVt#@#^6+lL8V$`I zy0Di6x02e04lzkgmYQ^&LmV%VcWV98xtiIbcjw7 z!n}Q5*f?yZWLR%@`Y+Q>bTuJebcu*voM2jnL2M0Tg+oN8U2$JCxZft?a+IZz(vb3h zla}hMO)PhaZZR1~8Ve_;ESY?wgB(tdz>!jMvO}CAPDQuioYI>f9Jb6f$#mJpX%2C^ zI0IJFBF}qU)YsZpBA)3GrDTR(oQ*Ga|7Kqxh!7{GeXc{CC(eieTPn|oOaIs7bgn6G z%VbR7F3uv;(@2H5$RREkGQo}d;gEbdV=IFUPJx%Fi?hUKl3%NF*rIoS8S?=1&k|Qi z#49No{(mVo)pB{dSSzl!i*-;Gc^X-1mQ?;q96fi4YXmakbex%JZCoJF4^vXo%Q*k0 zGAU!j#>`-d^{qV5Y!)RK*E_@pfn+%y8+4IabY4J)=N@*GRGDsG5)=?iQQu;vPsvaRV(RmeVvAiofNVx}V7_&Ttx^R=2tLX#9yAjSV|47P4xB1y zmLax@?GEv<#MD8casNPeyTc(Km2ujRPcgOVK_Wip5RXel4Tx3#ZmGXdIK)P=TXL~C zPN-wp$phimBiKn^C!}f2suW)_y7Ad5HbI8=3bEHAo|2slB#E(f@r-!RE}rc{N2-NG zJTJW=_qDd_0_n1?3mXc{b%FHQ7LhrIE?!1QBVxX0DbrUS;x(xW5KpV22XeUm(n%o& zJ~XLw|MQ%O8Ugn$N&6n@C*E<0ccrwo4!@MOta{%eK9ISLh~iYA6!b?9@v-;>42^}2 zS<8@RRUXrZd#SS%ucUs7F210INboD^n6MT3U+J3Zfq?P8l+Je!au^vb72Xm`^ONKm zEH}+#g1nL8fUL0v<)4jH6^JPR10rGy7pK_;_9h1^mt|aRsSU>(;$2wo$Na6S0D13g3fzTuU(CLlnrb}TggC$t{IhOxLpF)Cs8LGU0{Cqk|9qL_jXb= ze=8ZbjSSyOFxC*%Zb}X3!a#)hiALH%Z5zoWD=|w1q(onXO8T(|P!tLZp z^mSBK(S2mBt7IFQcpoXdmz3=x`P)gwd_Y^tv@N7^9kF91;|(oOwz-eg+)HX)b*@=k zNW(k{&U4K-!G*5lOt8u2Gr<;@-vrxS0Tb+Sb(&z&@B?t{YA8X4gg&yiMWW z;kwJLy~nlL1n+Y_V1ip+4@$6T({{3bzUvX!>aAo)HKgELU7SD}>v}8&?Mgvcq@djf zWVC+Lwby17uBV;p0)KBM&lnx;wIV!kcK>{;gBMfKe^SuPDd^P{^tu5V1HIvT)9mL> zRr|K9rI+DbQXRaLg8V7yy%h9;0U6!D?fS^<{v%cUiR)8M(&bEJ7V18=BHYu<`1hnn z*qnksPeEU%ps!QVw<+j*12X3HiR(vmPCu&JpIyK7GN)fs9sFiRz1r;H>eTz&QqUhM z=&uy?j{zCO|Lj`R3vFvssB2BgO^E`T0)+xK1?mb+SD?cH<+yH|<)+#HCJ~hiJx76V z1$ugRlAk2rP4g9wSAl&L*iV7|6?m`$4^`kG1r9dAxz^k`e+ku+2{on*nA;yT+@m$S{DT<4mQ zo0clDT!B*+I9-923anCKjRNZwILiPNbDiR*4Uj#T7U!mOQuA!+i8I&4;VF!{s>5Tf z$gVSb5z84Svg=F}a?=G0JXV3nD{zqln-$oqz{Lt|SKtx_1{D}mV3z_T3XBvnu;A#b?;){9d0^BD5 zN{#uDLVvg?`VOZqQ!}bdIu1+r?q+pX9M@@JULpl{vsp>wb)9DR=sMMe+-$Z28x^PmGfMOObwoCo}<9?6nKFGFH+zo3cO5#mn-l}1C$bVJ?(1Jm{a3UoMd;it4v`v znaD1m6}Ka)jjj&!y_>C7;MFGR>NFuYyH9taO0b3!Nh%M3p3fgT8369^i3m%`ZE1LHyI zLf9KJg6ITw-PQ#y_5=hI3l+UDulYQ)bFt z3OQGSc?v8rL6@>Qs+kJIJQapr%FpmbKu%C_xU0>aLz~%y>--dSVG0TuP@>ThZay|ewk44B zBi(#_>cx1ot(%u9aH0YyD{zVe%M@6lz-b1U7#^d{=#{xM6#h&Lezlv|q-Is4a4f-` z&vI=w`H=8#(i!pDCbCO;Gu~)cy7^oM9;3ho3OrVU#~WaRf?009$i|#@BoDX=g-1%u z7xlzy?v>7F3sS3VnK|)gYB0YEy1Grs%~Jw)^Mo`F8aot1rvk$YJVAj`1;!O<8P_tJ za#Y;n1Wr}W5{XiRA+2tHMz5rvVc@5b&g`9QXIik%cJp&m(oUokiRS0I`T4y!Pw0CR z%XP8I&x_SuQ+D9ymfA~A1Xl`j^VMdhn_pp>7DivwD}`$;NNe4ET_P0+s>&%*t2({L zitnm3C!0`aNYpjUf^eOiU!P)z#fho3gsZfKTfC6PUEHR|zN2T?clAopT?+r6p7@)S zYu%o-ZC3ayH*xdpT=RNig=?O|P9!RlF=;kpsgK?@O=e- zXn=`XeeC9+NXp#&GaE?bpQqfMCAr~WxcQfVwt-}k8}O;{CVVQqnT#U0;IrDTq=ej#pCP-4)R9eO0oja?Z}*ZeavwR7+)vIT z50FdA7IF>QN;Z;hc=I56ggiv{;unryA`g>yQ2LzgAU}~u$pQS-QaX8zdhoMK1IR8~ zj2}}PM|RUv{D4w5d6Lc}ducO%FsYL~O_!5r=nArro<;scFCj0{b>wAw6M2Q+OF$R+4Yn1>{?HCHao6Bj2;@ z$PesB@*}$q<-5pFY%BShZ7095N6D}33Gy3z8s+E7@9cH*2m65h$-W|gv0upFxYhp; zPbUX>F236ipcI!K816H0TvibLNNU4<0)`6%8cy}waSUG`icjMmG@Zw320xWLaAG@? zucldi4b8^!XeW+3=iq#D7S0{Ja4gu(chFq^6!qX(YaV}>=JPLT0sonLabU7gWY9h$ zoAwnR+D{bHgTw%o2h#pxBprZ*a0iR2^bk=`4;9DLfg(T$i5NW$r+Eg8^XL$91sy8Z z(;{&PEf!noF!2~2j-v)6#M5-7c!7=*ub{l2jus!%G5FAaxcH48VdHeHEsKt`dFhe1 zfpolWBt6PjLQ8B@=>%InooHJ~C)t|lWSbw~3p(i(TMXspwA6M6Ewf!h%Wdmuh3zIf z)pie^W_yTEx9z4gY%kJE+ne|)#!qOK?R#2n`-|3Sc3P`t&^j%L)@vR*OY4jB06JSM z#vcDj+Nh1CbF`!ATx~MSWptiaLyyspq4Tw4=>pA17ix>?v04!2E_$4{f*!A}qD|Vx zbdk1>`m~#Av$l!0XxnM4wukz)eRQ$*7H!i$rR~}eG@$)Wm)P-xCHUqMv>!w}?T65i zeFzQPhtn?mXq3m&6YNuH#9l+A_IWgB_tCgLNSE4|(Pj42>2mvpwA+3aU17hGo@l?D zo@9TJo@{@Do??H2o@#%CuC#wlPqTkVPq+U`&(JlxO3$HZ>V4^1`e1stK8Bv7Pon4Q zGw6AG13h0qo?f5_=!JSGy-1JJi}mI768$8USJF%M^XX;!m2|bfj$W>>r&s8=&@1)3 zP~J?}=nvDY^qq9AzK5>UpP^Ul`%r#`UZcN9uhqY#*Xh5|>(eM*pXQ(&(wy{$v^;ua zT3>oo+QBFfqBp0FqPL_?pc~VUrnja|rMIP3(c9DNQEsGnq&3ky(^}|VY3=mxv=F@~ zEsk5J*>=)Uxu>3`BU(U;P< z)0flt&{xv;(O1*oqOYZYN?%X^f$mTLo4%2u(>F8n=vx^F)3-B*(RVUN(RVY((f2Y+ z==&L^C{LvyWYp6SGZxa1G6MADj2QhSV!l$m~d=iHpgR3b3DuJj@Ov( z_<*H3zGLZ*KUqem#vGYBEHkq&%gP+gvNOjpXXYf9lR1OAG8>pX^LUn<8DO5w7|Y9C z&hj%)VFj71m^brWlrLn3nOCtsnb)wsnHyNY%v;z&nRlVQnf1@y&IV-eW(Q}!$PUST zlO3A*2^*OCJsXty7dtG=&IV_>*pRG#Y-rXHR+M!(E6$qChGkW<;aQDrL{<|UnYDzC z%8IkmSu5F?tn=C7Sy!?nvNo`>S$DE=SzFnWS&y^vSLdUS--PM z*@8{Z&Spnv7qThYgIHHZ8k>P0wC{^6_j&b~~%gj^Nr$JKi~&H95=KBIk7Gb5^5V&zhYJS&OrYwK`jw z-`UOBFC+3W1C*@3JC+AFOr{v6L zr{)~TR_3&`({iHh^qf=J89Ar3RXJy~GjlFrXXUI$c?~-|=LU98&Ry)>oXzaKoUQEq zoJZINIZvRxmtC0i61ynpHFj~%+w78@57?zSpQHRWyDaB7w%WznzKR z+}qfr?j3BWdl!4m{WQwYv&Y^0*)I1d>Wn-+1n2-+CTl-+A`3?>#TE zA3U$IA3blgpFAJ3pFLlo{0;lX^E><1^Edk~kF($NboNJHHv2Wt&Hl_gi2apU%>K?B z%l^rm!VcukFX&ijTt^M2$xd4Hh%4|nCKad&!m+(XKujYs5-@*sxZ{maU zxAVjD_wd2_PxB%9FY=-JukfP$w@`kM7w3P;hvk3Ehv)yyN96y>M;1^%tiZ-c6*&3m zfA5x-{;c{zTz_qe&v+~fAX1L z!mGR*ul724rPs-8yoJ2hJBZhLNAh~_;e3|&C_dYJG;i=uN4bhOdgt;v-UWQFcM+fG z_48xAL6p1reD8^Tf%hD~(0ds_)_W~K&U-6A-g_T!^6ub^ynDIN`zmktzRz2{AM;l4 z7u@gtjxYB9g7P1{tx)jx!fYNWEaXcH2l9@>Av{<(f_D}k&O?PIC{O0$!b;v%*vL;P zZ03=|P97~>&SQmV@Oa@xd}-lYzO3*jzP#`r-d*?*Us1S&pIG<=KdJB;esbYHlwaYe z6u!q#E&PVBEc~6H)<^Ku`?&ZSefse;agpc;{$t_@o~;V2h#Z37yrtDeTli1o1RrNB z^4PZUKgQWTc4zVRWF#KLuO~zB7|C9sfhlDk}*NUr(G~U2G8<+eFr+jU>&hOF+HV`o<>V|HQ-w7Gq*1x>w&rvc38i z;p)4dpi|=%T;F1>%{Kji(>_cMk#7!x$!-yzwf{BL{}*3CkG5WlK?)@uj|uCEqc6UX zESkKLRQ*434Gj`pn{44)zu08_pWxb&iljqKlDtWha^$n1U*Fc=87XI%mEftI;X8(= zjMFzt+0tZv50nF66nP+O=uJsl5=CT`qkz{z&N-|wfkIV3;_iT}#Y?0-KlBHTWWQ+8YEVOWVGq;KUCE4CA`EbgGecxhl zHXq^57WiWu;mLrv7~yrI8 z$9(BhavV&03t@aajQtUk!FLfie}atUyU8^EB&p$h$!z{KS-_tm$MY9RfWJu2;`_*j z{6FM!{t{WkUnLv(Yvg9WAGc56Aba^+(^v{WW`kE-9Z(3}b`E za5i3yVC7;Yne!277TYgovv)-!`&`Une~9_)AF+@#aXe2GP24FK@q>hq z4-?INEZ~Wv4V+)XOGOCB*>Qa@;aKS(IQy(Y7K@#ZhtuZ^XaBnSHs^%8`I>V<-F&-q zdEIY019;eT$jf-3@<%+a@3uMX>i6 zUa|ukrp)`+%@_SJYmcoCzFhjLSCIpYclbspk#72@+>=7gSArin$SBV22aqWTc z`pLT1$sRGuGRxEk89AK{AVe%DmEuHl9BvE+#n~h-&LJn_d4@QboF~pB7vOohIG?N$ z7m{nmMdSu?Ik{6@K{ku4$o*n1*)7(S{RluGh#Ser;ui9y*oZBZo3Vv*Cmkp5rX{%N zGD&QrQ^jUFL)=HJP_7sE(|NeQvOsKs9^iUuLJ!E-IR7tC!Jo+|a>OThQD4c)61dBa zBx@C;eP!6)aNtY0?jqhdA+h#}iCSXobEiaskp9PZeQ+YJsGr$zk)>sy9R696Y;JhR z!R?DC_PG-iN!$dOOFUrHqlAf_M2N?UUF;$`;t7%~aNHi{{^Ch8T?3XBKO`((A}5Gfz}{B{6(hwcISQVmlPstJ3$VFH1{7@< zW9IkYDvsD9j$BK!ix7bEr(n@WVwbR*opEtM{6MnBkEDwQG=1j!|G0_+S z*9Vg$sQU}|i=&k;821-!4on6Y(~_v;;A3&;fVhgEBpKU9+5D}d;?bhw2gK3p-h(l` zEuRQm0dd$0Nsg@#m@_ps+#!&tye^Smgdac_)3%7}{~mL&fnS##a{%z=m^$!_d%+%O zVAm(HW$%(*=|z3|_TKw6P=uSJN&L5gZ3li}yO=e9_*T(SBD^roxg|C}4oX(;MXE4{ zj{%1lY!Sz#-RcBAuX=666(x^xC+IDO>uw4eZqCQ*2 zqD_z|!M{?`ELzEUa$Uk$jtv46(J4 zVKzS*Wm`SZ=+j3|qZhR(5{ROOnQzbV@b(z#>Ey*o< zjBG~++P7#Y*}g?AUNmXDXoF|jDgw2`aky=Z=$MDB=$_yturlnN7#t@OK$lD<))CzU zbDurD@6|+i7H=dr=kP5eHgC9dlt(zzgNrV+l)B7PiCJmufr65WvPCRY3Q;Dn?buEx zHrpx0X*+`y+Rh>aZD)h_bIBOnd1Qj^e9(O%skL29j<;Pxg0@S^3fpDmG}~%&vF&nl zt!)i-9v96G-N&_P&`;&%@+42>#T==O(wvZErooXwQd}8R!V2z#SHdm(L?b#2jFdv$ zjNj6(;qWgvx#;{7@%T2e0^OXnlhEqoJp^Cf#R8O%T}QkCR~pqQuaaoSM~h;w*tKcc z9+D$#hRLfV!(ZSRwA+Xv(n+lP?JCy>dfkjW=xgY7eNv+Z-T5q;ld`--HA z>r(9f0nF7gk&+@q2d`h7aP!(tG*_D_CKdIOF#t=K3^r*Mr5lMeQB#blfHDfmd2L>s zTim!s+&oUhZp8IufY-)7S|UJ9mL{7D=LnSJy;!pSB$sTq8?m&p$q>qA1nn^BQDz?B-i#Q z$+P`U2G|aeL*NjGXpD@}1R1MkkcpauOw%$+wU$ke(VQfp<&rMVLzZd<eXD;CElv9oXOR5g3h)Pwvrvw57e~ z)q1wi_1gN$E>OJ_-SVFsQoTLnwH5E$R16!{ig!J_P3)@XlcWlq`x+o6l?P!(__fd zC|7C=Nu72qnWY^^j@6DQP1+*Tq4^-!W^y6QYqVB!4f?uCYbUpA0dl*xgxsSA$yTkC z?AAi$S*?q_fV$VU2zgtJLe4Sru@)y^X-gq%Tx`~s!%gBQY*LQX6Lw5qg4=qscKD8Q z2;clCTtEcba;Ib%!o}0`j4*qf$F`P~4M#+Eix&)+S(GU3GYb6@g_pL7SJn|H9HA`3 z)9xWzfGAjYU?XXk_Z*F8TgB_rCUM(wEjiqSA0?1p63cMuBNHtzPq<2W$zj{Xo1%5m zIAL_Ok@Us$-{?SWFoOAT$YriasQA!~f3l2d2vFK75WuMX{aoVH&Lgeb`J_#|h{Uyv$;sNKKMb|GdA*h2>6?;WT& zLRHqVekk^Xnn4@oiwy_98wN~uNwj9WcxJxypsnJw+F@?-`EW1@97oq+l1HE)T#7ZG zGDU(MktIpVZ4qB$%o0178x)6m?H(O!-3KE`0ie(TuPdP2iNY$KdUUTHqraZu>lX11 zvLs`mq=}>7(bE2sCT>r5r3}*SPt^e1FnE|KNGjU(s1c|K(nDZBgGjCMtdo8b3_Jw? z-ifSF*padD(KbMgH$sdzk!khN5am5&fwl=^+zb)k z3lZK&R%s8Av$ZX-qiy6W?Lqj(hsZ73!(_Ae2-$)1Q+W4^_89pXuO)0*+ zy0yn?KkX^_#0?PhOnB+U6=b;(mj9)16W=Cs2bB{UDT($f@V`fvudXc17B*b}9inn^ zQ3;v2|KZ1)t6HWV-F!38|nfP7&fv3E{U4$o=5C03t+N>%7 literal 27946 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%0Ar0p$<1O^|kI2UnJQB*cB|mplQ386} zXhX-Bv}cSK^!*M><>!n>`X4}P{DKid|L?%L^pa7B)1ZSaK5f*{KLBO$YeqFrh7QUU z8eZO{<}5KE|HS4%%kZA|31|Va3Y32W8X?vp-v0uWEne4`(5HxQmUvH}O`l0VB~2ee zpCghZl(~2vI|C?NS**R0fR<~gamFy^C>zy#aLOo@tF)?z*#+P7ln(WPy>7m;UBp<0 zzLCnUB4k@ofG&79yFn{NFYICs&?t1o+gJxQ8a?qwHUN!5XWYdmpt0zWTX9l%&^UC- zC{DT#Jh<$}zbp&`ROI%FSDiK7F$&@g{D=zN+i5%x$84ed1G>_N3bZ{m&w=nL_`U-f zDzf8)ZzGLqq_7`Qt}&nAPC%I=gHu7vGb;J(oCbaQo^8l`I%p|)3B*5&;(tuBaQ>s^ z<3=D~$2S31F&4%tdqRFMXOtHhw_rP*;GC*soOd$6Abe3iC}-a4e>%^Tk|IuuxQ7gJ zFAW#>A&>VXGjS>q587{;BasDY<$NBAQ9=b=h*b^L$S6PxVuk +#include + +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__ diff --git a/libjava/java/lang/System.java b/libjava/java/lang/System.java index 587e637e974..76a39f0d3f2 100644 --- a/libjava/java/lang/System.java +++ b/libjava/java/lang/System.java @@ -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 *
gnu.java.io.encoding_scheme_alias.latin?
8859_?
*
gnu.java.io.encoding_scheme_alias.UTF-8
UTF8
*
gnu.java.io.encoding_scheme_alias.utf-8
UTF8
+ *
gnu.java.util.zoneinfo.dir
Root of zoneinfo tree
* * * @return the system properties, will never be null diff --git a/libjava/java/util/TimeZone.h b/libjava/java/util/TimeZone.h index 3eb30ad5ff5..9ae0ebc3f16 100644 --- a/libjava/java/util/TimeZone.h +++ b/libjava/java/util/TimeZone.h @@ -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$; diff --git a/libjava/java/util/VMTimeZone.h b/libjava/java/util/VMTimeZone.h index 6e571143dd0..26ca5e224cb 100644 --- a/libjava/java/util/VMTimeZone.h +++ b/libjava/java/util/VMTimeZone.h @@ -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$; diff --git a/libjava/java/util/VMTimeZone.java b/libjava/java/util/VMTimeZone.java index 27bab939166..992ecaf28a8 100644 --- a/libjava/java/util/VMTimeZone.java +++ b/libjava/java/util/VMTimeZone.java @@ -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 System.getenv("TZ"), * readTimeZoneFile("/etc/timezone"), - * readtzFile("/etc/localtime") and finally - * getSystemTimeZoneId() till a supported TimeZone is - * found through TimeZone.getDefaultTimeZone(String). + * ZoneInfo.readTZFile((String)null, "/etc/localtime") + * and finally getSystemTimeZoneId() till a supported + * TimeZone is found through + * TimeZone.getDefaultTimeZone(String). * If every method fails null is returned (which means * the TimeZone code will fall back on GMT as default time zone). *

@@ -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 getDefaultTimeZone(String). - * If the file doesn't exist, an IOException occurs or it isn't a tzfile - * that can be parsed null is returned. + * Tries to read the time zone name from a file. + * If the file cannot be read or an IOException occurs null is returned. *

- * The tzfile structure (as also used by glibc) is described in the Olson - * tz database archive as can be found at - * ftp://elsie.nci.nih.gov/pub/. - *

- * 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') + 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 + { + posstart = 5; + posend = line.length(); + } + if (posend < 0) 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(); + return line.substring(posstart, posend); } - 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') - { - String posixtz = dis.readLine(); - if (posixtz.length() > 0) - id = posixtz; - } - } - - 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. diff --git a/libjava/posix.cc b/libjava/posix.cc index df798b88a2b..5d64094c815 100644 --- a/libjava/posix.cc +++ b/libjava/posix.cc @@ -139,6 +139,10 @@ _Jv_platform_initProperties (java::util::Properties* newprops) 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 diff --git a/libjava/sources.am b/libjava/sources.am index 01618ce0c20..77e7796ff33 100644 --- a/libjava/sources.am +++ b/libjava/sources.am @@ -2110,7 +2110,8 @@ gnu/java/text.list: $(gnu_java_text_source_files) 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)))