| /* gnu.java.util.ZoneInfo | |
| Copyright (C) 2007 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package gnu.java.util; | |
| import java.io.BufferedInputStream; | |
| import java.io.DataInputStream; | |
| import java.io.EOFException; | |
| import java.io.FileInputStream; | |
| import java.io.InputStream; | |
| import java.io.IOException; | |
| import java.util.Calendar; | |
| import java.util.Date; | |
| import java.util.GregorianCalendar; | |
| import java.util.SimpleTimeZone; | |
| import java.util.TimeZone; | |
| /** | |
| * This class represents more advanced variant of java.util.SimpleTimeZone. | |
| * It can handle zic(8) compiled transition dates plus uses a SimpleTimeZone | |
| * for years beyond last precomputed transition. Before first precomputed | |
| * transition it assumes no daylight saving was in effect. | |
| * Timezones that never used daylight saving time should use just | |
| * SimpleTimeZone instead of this class. | |
| * | |
| * This object is tightly bound to the Gregorian calendar. It assumes | |
| * a regular seven days week, and the month lengths are that of the | |
| * Gregorian Calendar. | |
| * | |
| * @see Calendar | |
| * @see GregorianCalendar | |
| * @see SimpleTimeZone | |
| * @author Jakub Jelinek | |
| */ | |
| public class ZoneInfo extends TimeZone | |
| { | |
| private static final int SECS_SHIFT = 22; | |
| private static final long OFFSET_MASK = (1 << 21) - 1; | |
| private static final int OFFSET_SHIFT = 64 - 21; | |
| private static final long IS_DST = 1 << 21; | |
| /** | |
| * The raw time zone offset in milliseconds to GMT, ignoring | |
| * daylight savings. | |
| * @serial | |
| */ | |
| private int rawOffset; | |
| /** | |
| * Cached DST savings for the last transition rule. | |
| */ | |
| private int dstSavings; | |
| /** | |
| * Cached flag whether last transition rule uses DST saving. | |
| */ | |
| private boolean useDaylight; | |
| /** | |
| * Array of encoded transitions. | |
| * Transition time in UTC seconds since epoch is in the most | |
| * significant 64 - SECS_SHIFT bits, then one bit flag | |
| * whether DST is active and the least significant bits | |
| * containing offset relative to rawOffset. Both the DST | |
| * flag and relative offset apply to time before the transition | |
| * and after or equal to previous transition if any. | |
| * The array must be sorted. | |
| */ | |
| private long[] transitions; | |
| /** | |
| * SimpleTimeZone rule which applies on or after the latest | |
| * transition. If the DST changes are not expresible as a | |
| * SimpleTimeZone rule, then the rule should just contain | |
| * the standard time and no DST time. | |
| */ | |
| private SimpleTimeZone lastRule; | |
| /** | |
| * Cached GMT SimpleTimeZone object for internal use in | |
| * getOffset method. | |
| */ | |
| private static SimpleTimeZone gmtZone = null; | |
| static final long serialVersionUID = -3740626706860383657L; | |
| /** | |
| * Create a <code>ZoneInfo</code> with the given time offset | |
| * from GMT and with daylight savings. | |
| * | |
| * @param rawOffset The time offset from GMT in milliseconds. | |
| * @param id The identifier of this time zone. | |
| * @param transitions Array of transition times in UTC seconds since | |
| * Epoch in topmost 42 bits, below that 1 boolean bit whether the time | |
| * before that transition used daylight saving and in bottommost 21 | |
| * bits relative daylight saving offset against rawOffset in seconds | |
| * that applies before this transition. | |
| * @param endRule SimpleTimeZone class describing the daylight saving | |
| * rules after the last transition. | |
| */ | |
| public ZoneInfo(int rawOffset, String id, long[] transitions, | |
| SimpleTimeZone lastRule) | |
| { | |
| if (transitions == null || transitions.length < 1) | |
| throw new IllegalArgumentException("transitions must not be null"); | |
| if (lastRule == null) | |
| throw new IllegalArgumentException("lastRule must not be null"); | |
| this.rawOffset = rawOffset; | |
| this.transitions = transitions; | |
| this.lastRule = lastRule; | |
| setID(id); | |
| computeDSTSavings(); | |
| } | |
| /** | |
| * Gets the time zone offset, for current date, modified in case of | |
| * daylight savings. This is the offset to add to UTC to get the local | |
| * time. | |
| * | |
| * The day must be a positive number and dayOfWeek must be a positive value | |
| * from Calendar. dayOfWeek is redundant, but must match the other values | |
| * or an inaccurate result may be returned. | |
| * | |
| * @param era the era of the given date | |
| * @param year the year of the given date | |
| * @param month the month of the given date, 0 for January. | |
| * @param day the day of month | |
| * @param dayOfWeek the day of week; this must match the other fields. | |
| * @param millis the millis in the day (in local standard time) | |
| * @return the time zone offset in milliseconds. | |
| * @throws IllegalArgumentException if arguments are incorrect. | |
| */ | |
| public int getOffset(int era, int year, int month, int day, int dayOfWeek, | |
| int millis) | |
| { | |
| if (gmtZone == null) | |
| gmtZone = new SimpleTimeZone(0, "GMT"); | |
| if (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY) | |
| throw new IllegalArgumentException("dayOfWeek out of range"); | |
| if (month < Calendar.JANUARY || month > Calendar.DECEMBER) | |
| throw new IllegalArgumentException("month out of range:" + month); | |
| if (era != GregorianCalendar.AD) | |
| return (int) (((transitions[0] << OFFSET_SHIFT) >> OFFSET_SHIFT) * 1000); | |
| GregorianCalendar cal = new GregorianCalendar((TimeZone) gmtZone); | |
| cal.set(year, month, day, 0, 0, 0); | |
| if (cal.get(Calendar.DAY_OF_MONTH) != day) | |
| throw new IllegalArgumentException("day out of range"); | |
| return getOffset(cal.getTimeInMillis() - rawOffset + millis); | |
| } | |
| private long findTransition(long secs) | |
| { | |
| if (secs < (transitions[0] >> SECS_SHIFT)) | |
| return transitions[0]; | |
| if (secs >= (transitions[transitions.length-1] >> SECS_SHIFT)) | |
| return Long.MAX_VALUE; | |
| long val = (secs + 1) << SECS_SHIFT; | |
| int lo = 1; | |
| int hi = transitions.length; | |
| int mid = 1; | |
| while (lo < hi) | |
| { | |
| mid = (lo + hi) / 2; | |
| // secs < (transitions[mid-1] >> SECS_SHIFT) | |
| if (val <= transitions[mid-1]) | |
| hi = mid; | |
| // secs >= (transitions[mid] >> SECS_SHIFT) | |
| else if (val > transitions[mid]) | |
| lo = mid + 1; | |
| else | |
| break; | |
| } | |
| return transitions[mid]; | |
| } | |
| /** | |
| * Get the time zone offset for the specified date, modified in case of | |
| * daylight savings. This is the offset to add to UTC to get the local | |
| * time. | |
| * @param date the date represented in millisecends | |
| * since January 1, 1970 00:00:00 GMT. | |
| */ | |
| public int getOffset(long date) | |
| { | |
| long d = (date >= 0 ? date / 1000 : (date + 1) / 1000 - 1); | |
| long transition = findTransition(d); | |
| // For times on or after last transition use lastRule. | |
| if (transition == Long.MAX_VALUE) | |
| return lastRule.getOffset(date); | |
| return (int) (((transition << OFFSET_SHIFT) >> OFFSET_SHIFT) * 1000); | |
| } | |
| /** | |
| * Returns the time zone offset to GMT in milliseconds, ignoring | |
| * day light savings. | |
| * @return the time zone offset. | |
| */ | |
| public int getRawOffset() | |
| { | |
| return rawOffset; | |
| } | |
| /** | |
| * Sets the standard time zone offset to GMT. | |
| * @param rawOffset The time offset from GMT in milliseconds. | |
| */ | |
| public void setRawOffset(int rawOffset) | |
| { | |
| this.rawOffset = rawOffset; | |
| lastRule.setRawOffset(rawOffset); | |
| } | |
| private void computeDSTSavings() | |
| { | |
| if (lastRule.useDaylightTime()) | |
| { | |
| dstSavings = lastRule.getDSTSavings(); | |
| useDaylight = true; | |
| } | |
| else | |
| { | |
| dstSavings = 0; | |
| useDaylight = false; | |
| // lastRule might say no DST is in effect simply because | |
| // the DST rules are too complex for SimpleTimeZone, say | |
| // for Asia/Jerusalem. | |
| // Look at the last DST offset if it is newer than current time. | |
| long currentSecs = System.currentTimeMillis() / 1000; | |
| int i; | |
| for (i = transitions.length - 1; | |
| i >= 0 && currentSecs < (transitions[i] >> SECS_SHIFT); | |
| i--) | |
| if ((transitions[i] & IS_DST) != 0) | |
| { | |
| dstSavings = (int) (((transitions[i] << OFFSET_SHIFT) | |
| >> OFFSET_SHIFT) * 1000) | |
| - rawOffset; | |
| useDaylight = true; | |
| break; | |
| } | |
| } | |
| } | |
| /** | |
| * Gets the daylight savings offset. This is a positive offset in | |
| * milliseconds with respect to standard time. Typically this | |
| * is one hour, but for some time zones this may be half an our. | |
| * @return the daylight savings offset in milliseconds. | |
| */ | |
| public int getDSTSavings() | |
| { | |
| return dstSavings; | |
| } | |
| /** | |
| * Returns if this time zone uses daylight savings time. | |
| * @return true, if we use daylight savings time, false otherwise. | |
| */ | |
| public boolean useDaylightTime() | |
| { | |
| return useDaylight; | |
| } | |
| /** | |
| * Determines if the given date is in daylight savings time. | |
| * @return true, if it is in daylight savings time, false otherwise. | |
| */ | |
| public boolean inDaylightTime(Date date) | |
| { | |
| long d = date.getTime(); | |
| d = (d >= 0 ? d / 1000 : (d + 1) / 1000 - 1); | |
| long transition = findTransition(d); | |
| // For times on or after last transition use lastRule. | |
| if (transition == Long.MAX_VALUE) | |
| return lastRule.inDaylightTime(date); | |
| return (transition & IS_DST) != 0; | |
| } | |
| /** | |
| * Generates the hashCode for the SimpleDateFormat object. It is | |
| * the rawOffset, possibly, if useDaylightSavings is true, xored | |
| * with startYear, startMonth, startDayOfWeekInMonth, ..., endTime. | |
| */ | |
| public synchronized int hashCode() | |
| { | |
| int hash = lastRule.hashCode(); | |
| // FIXME - hash transitions? | |
| return hash; | |
| } | |
| public synchronized boolean equals(Object o) | |
| { | |
| if (! hasSameRules((TimeZone) o)) | |
| return false; | |
| ZoneInfo zone = (ZoneInfo) o; | |
| return getID().equals(zone.getID()); | |
| } | |
| /** | |
| * Test if the other time zone uses the same rule and only | |
| * possibly differs in ID. This implementation for this particular | |
| * class will return true if the other object is a ZoneInfo, | |
| * the raw offsets and useDaylight are identical and if useDaylight | |
| * is true, also the start and end datas are identical. | |
| * @return true if this zone uses the same rule. | |
| */ | |
| public boolean hasSameRules(TimeZone o) | |
| { | |
| if (this == o) | |
| return true; | |
| if (! (o instanceof ZoneInfo)) | |
| return false; | |
| ZoneInfo zone = (ZoneInfo) o; | |
| if (zone.hashCode() != hashCode() || rawOffset != zone.rawOffset) | |
| return false; | |
| if (! lastRule.equals(zone.lastRule)) | |
| return false; | |
| // FIXME - compare transitions? | |
| return true; | |
| } | |
| /** | |
| * Returns a string representation of this ZoneInfo object. | |
| * @return a string representation of this ZoneInfo object. | |
| */ | |
| public String toString() | |
| { | |
| return getClass().getName() + "[" + "id=" + getID() + ",offset=" | |
| + rawOffset + ",transitions=" + transitions.length | |
| + ",useDaylight=" + useDaylight | |
| + (useDaylight ? (",dstSavings=" + dstSavings) : "") | |
| + ",lastRule=" + lastRule.toString() + "]"; | |
| } | |
| /** | |
| * Reads zic(8) compiled timezone data file from file | |
| * and returns a TimeZone class describing it, either | |
| * SimpleTimeZone or ZoneInfo depending on whether | |
| * it can be described by SimpleTimeZone rule or not. | |
| */ | |
| public static TimeZone readTZFile(String id, String file) | |
| { | |
| DataInputStream dis = null; | |
| try | |
| { | |
| FileInputStream fis = new FileInputStream(file); | |
| BufferedInputStream bis = new BufferedInputStream(fis); | |
| dis = new DataInputStream(bis); | |
| // Make sure we are reading a tzfile. | |
| byte[] tzif = new byte[5]; | |
| dis.readFully(tzif); | |
| int tzif2 = 4; | |
| if (tzif[0] == 'T' && tzif[1] == 'Z' | |
| && tzif[2] == 'i' && tzif[3] == 'f') | |
| { | |
| if (tzif[4] >= '2') | |
| tzif2 = 8; | |
| // Reserved bytes | |
| skipFully(dis, 16 - 1); | |
| } | |
| else | |
| // Darwin has tzdata files that don't start with the TZif marker | |
| skipFully(dis, 16 - 5); | |
| int ttisgmtcnt = dis.readInt(); | |
| int ttisstdcnt = dis.readInt(); | |
| int leapcnt = dis.readInt(); | |
| int timecnt = dis.readInt(); | |
| int typecnt = dis.readInt(); | |
| int charcnt = dis.readInt(); | |
| if (tzif2 == 8) | |
| { | |
| skipFully(dis, timecnt * (4 + 1) + typecnt * (4 + 1 + 1) + charcnt | |
| + leapcnt * (4 + 4) + ttisgmtcnt + ttisstdcnt); | |
| dis.readFully(tzif); | |
| if (tzif[0] != 'T' || tzif[1] != 'Z' || tzif[2] != 'i' | |
| || tzif[3] != 'f' || tzif[4] < '2') | |
| return null; | |
| // Reserved bytes | |
| skipFully(dis, 16 - 1); | |
| ttisgmtcnt = dis.readInt(); | |
| ttisstdcnt = dis.readInt(); | |
| leapcnt = dis.readInt(); | |
| timecnt = dis.readInt(); | |
| typecnt = dis.readInt(); | |
| charcnt = dis.readInt(); | |
| } | |
| // Sanity checks | |
| if (typecnt <= 0 || timecnt < 0 || charcnt < 0 | |
| || leapcnt < 0 || ttisgmtcnt < 0 || ttisstdcnt < 0 | |
| || ttisgmtcnt > typecnt || ttisstdcnt > typecnt) | |
| return null; | |
| // Transition times | |
| long[] times = new long[timecnt]; | |
| for (int i = 0; i < timecnt; i++) | |
| if (tzif2 == 8) | |
| times[i] = dis.readLong(); | |
| else | |
| times[i] = (long) dis.readInt(); | |
| // Transition types | |
| int[] types = new int[timecnt]; | |
| for (int i = 0; i < timecnt; i++) | |
| { | |
| types[i] = dis.readByte(); | |
| if (types[i] < 0) | |
| types[i] += 256; | |
| if (types[i] >= typecnt) | |
| return null; | |
| } | |
| // Types | |
| int[] offsets = new int[typecnt]; | |
| int[] typeflags = new int[typecnt]; | |
| for (int i = 0; i < typecnt; i++) | |
| { | |
| offsets[i] = dis.readInt(); | |
| if (offsets[i] >= IS_DST / 2 || offsets[i] <= -IS_DST / 2) | |
| return null; | |
| int dst = dis.readByte(); | |
| int abbrind = dis.readByte(); | |
| if (abbrind < 0) | |
| abbrind += 256; | |
| if (abbrind >= charcnt) | |
| return null; | |
| typeflags[i] = (dst != 0 ? (1 << 8) : 0) + abbrind; | |
| } | |
| // Abbrev names | |
| byte[] names = new byte[charcnt]; | |
| dis.readFully(names); | |
| // Leap transitions, for now ignore | |
| skipFully(dis, leapcnt * (tzif2 + 4) + ttisstdcnt + ttisgmtcnt); | |
| // tzIf2 format has optional POSIX TZ env string | |
| String tzstr = null; | |
| if (tzif2 == 8 && dis.readByte() == '\n') | |
| { | |
| tzstr = dis.readLine(); | |
| if (tzstr.length() <= 0) | |
| tzstr = null; | |
| } | |
| // Get std/dst_offset and dst/non-dst time zone names. | |
| int std_ind = -1; | |
| int dst_ind = -1; | |
| if (timecnt == 0) | |
| std_ind = 0; | |
| else | |
| for (int i = timecnt - 1; i >= 0; i--) | |
| { | |
| if (std_ind == -1 && (typeflags[types[i]] & (1 << 8)) == 0) | |
| std_ind = types[i]; | |
| else if (dst_ind == -1 && (typeflags[types[i]] & (1 << 8)) != 0) | |
| dst_ind = types[i]; | |
| if (dst_ind != -1 && std_ind != -1) | |
| break; | |
| } | |
| if (std_ind == -1) | |
| return null; | |
| int j = typeflags[std_ind] & 255; | |
| while (j < charcnt && names[j] != 0) | |
| j++; | |
| String std_zonename = new String(names, typeflags[std_ind] & 255, | |
| j - (typeflags[std_ind] & 255), | |
| "ASCII"); | |
| String dst_zonename = ""; | |
| if (dst_ind != -1) | |
| { | |
| j = typeflags[dst_ind] & 255; | |
| while (j < charcnt && names[j] != 0) | |
| j++; | |
| dst_zonename = new String(names, typeflags[dst_ind] & 255, | |
| j - (typeflags[dst_ind] & 255), "ASCII"); | |
| } | |
| // Only use gmt offset when necessary. | |
| // Also special case GMT+/- timezones. | |
| String std_offset_string = ""; | |
| String dst_offset_string = ""; | |
| if (tzstr == null | |
| && (dst_ind != -1 | |
| || (offsets[std_ind] != 0 | |
| && !std_zonename.startsWith("GMT+") | |
| && !std_zonename.startsWith("GMT-")))) | |
| { | |
| std_offset_string = Integer.toString(-offsets[std_ind] / 3600); | |
| int seconds = -offsets[std_ind] % 3600; | |
| if (seconds != 0) | |
| { | |
| if (seconds < 0) | |
| seconds *= -1; | |
| if (seconds < 600) | |
| std_offset_string += ":0" + Integer.toString(seconds / 60); | |
| else | |
| std_offset_string += ":" + Integer.toString(seconds / 60); | |
| seconds = seconds % 60; | |
| if (seconds >= 10) | |
| std_offset_string += ":" + Integer.toString(seconds); | |
| else if (seconds > 0) | |
| std_offset_string += ":0" + Integer.toString(seconds); | |
| } | |
| if (dst_ind != -1 && offsets[dst_ind] != offsets[std_ind] + 3600) | |
| { | |
| dst_offset_string = Integer.toString(-offsets[dst_ind] / 3600); | |
| seconds = -offsets[dst_ind] % 3600; | |
| if (seconds != 0) | |
| { | |
| if (seconds < 0) | |
| seconds *= -1; | |
| if (seconds < 600) | |
| dst_offset_string | |
| += ":0" + Integer.toString(seconds / 60); | |
| else | |
| dst_offset_string | |
| += ":" + Integer.toString(seconds / 60); | |
| seconds = seconds % 60; | |
| if (seconds >= 10) | |
| dst_offset_string += ":" + Integer.toString(seconds); | |
| else if (seconds > 0) | |
| dst_offset_string += ":0" + Integer.toString(seconds); | |
| } | |
| } | |
| } | |
| /* | |
| * If no tzIf2 POSIX TZ string is available and the timezone | |
| * uses DST, try to guess the last rule by trying to make | |
| * sense from transitions at 5 years in the future and onwards. | |
| * tzdata actually uses only 3 forms of rules: | |
| * fixed date within a month, e.g. change on April, 5th | |
| * 1st weekday on or after Nth: change on Sun>=15 in April | |
| * last weekday in a month: change on lastSun in April | |
| */ | |
| String[] change_spec = { null, null }; | |
| if (tzstr == null && dst_ind != -1 && timecnt > 10) | |
| { | |
| long nowPlus5y = System.currentTimeMillis() / 1000 | |
| + 5 * 365 * 86400; | |
| int first; | |
| for (first = timecnt - 1; first >= 0; first--) | |
| if (times[first] < nowPlus5y | |
| || (types[first] != std_ind && types[first] != dst_ind) | |
| || types[first] != types[timecnt - 2 + ((first ^ timecnt) & 1)]) | |
| break; | |
| first++; | |
| if (timecnt - first >= 10 && types[timecnt - 1] != types[timecnt - 2]) | |
| { | |
| GregorianCalendar cal | |
| = new GregorianCalendar(new SimpleTimeZone(0, "GMT")); | |
| int[] values = new int[2 * 11]; | |
| int i; | |
| for (i = timecnt - 1; i >= first; i--) | |
| { | |
| int base = (i % 2) * 11; | |
| int offset = offsets[types[i > first ? i - 1 : i + 1]]; | |
| cal.setTimeInMillis((times[i] + offset) * 1000); | |
| if (i >= timecnt - 2) | |
| { | |
| values[base + 0] = cal.get(Calendar.YEAR); | |
| values[base + 1] = cal.get(Calendar.MONTH); | |
| values[base + 2] = cal.get(Calendar.DAY_OF_MONTH); | |
| values[base + 3] | |
| = cal.getActualMaximum(Calendar.DAY_OF_MONTH); | |
| values[base + 4] = cal.get(Calendar.DAY_OF_WEEK); | |
| values[base + 5] = cal.get(Calendar.HOUR_OF_DAY); | |
| values[base + 6] = cal.get(Calendar.MINUTE); | |
| values[base + 7] = cal.get(Calendar.SECOND); | |
| values[base + 8] = values[base + 2]; // Range start | |
| values[base + 9] = values[base + 2]; // Range end | |
| values[base + 10] = 0; // Determined type | |
| } | |
| else | |
| { | |
| int year = cal.get(Calendar.YEAR); | |
| int month = cal.get(Calendar.MONTH); | |
| int day_of_month = cal.get(Calendar.DAY_OF_MONTH); | |
| int month_days | |
| = cal.getActualMaximum(Calendar.DAY_OF_MONTH); | |
| int day_of_week = cal.get(Calendar.DAY_OF_WEEK); | |
| int hour = cal.get(Calendar.HOUR_OF_DAY); | |
| int minute = cal.get(Calendar.MINUTE); | |
| int second = cal.get(Calendar.SECOND); | |
| if (year != values[base + 0] - 1 | |
| || month != values[base + 1] | |
| || hour != values[base + 5] | |
| || minute != values[base + 6] | |
| || second != values[base + 7]) | |
| break; | |
| if (day_of_week == values[base + 4]) | |
| { | |
| // Either a Sun>=8 or lastSun rule. | |
| if (day_of_month < values[base + 8]) | |
| values[base + 8] = day_of_month; | |
| if (day_of_month > values[base + 9]) | |
| values[base + 9] = day_of_month; | |
| if (values[base + 10] < 0) | |
| break; | |
| if (values[base + 10] == 0) | |
| { | |
| values[base + 10] = 1; | |
| // If day of month > 28, this is | |
| // certainly lastSun rule. | |
| if (values[base + 2] > 28) | |
| values[base + 2] = 3; | |
| // If day of month isn't in the last | |
| // week, it can't be lastSun rule. | |
| else if (values[base + 2] | |
| <= values[base + 3] - 7) | |
| values[base + 3] = 2; | |
| } | |
| if (values[base + 10] == 1) | |
| { | |
| // If day of month is > 28, this is | |
| // certainly lastSun rule. | |
| if (day_of_month > 28) | |
| values[base + 10] = 3; | |
| // If day of month isn't in the last | |
| // week, it can't be lastSun rule. | |
| else if (day_of_month <= month_days - 7) | |
| values[base + 10] = 2; | |
| } | |
| else if ((values[base + 10] == 2 | |
| && day_of_month > 28) | |
| || (values[base + 10] == 3 | |
| && day_of_month <= month_days - 7)) | |
| break; | |
| } | |
| else | |
| { | |
| // Must be fixed day in month rule. | |
| if (day_of_month != values[base + 2] | |
| || values[base + 10] > 0) | |
| break; | |
| values[base + 4] = day_of_week; | |
| values[base + 10] = -1; | |
| } | |
| values[base + 0] -= 1; | |
| } | |
| } | |
| if (i < first) | |
| { | |
| for (i = 0; i < 2; i++) | |
| { | |
| int base = 11 * i; | |
| if (values[base + 10] == 0) | |
| continue; | |
| if (values[base + 10] == -1) | |
| { | |
| int[] dayCount | |
| = { 0, 31, 59, 90, 120, 151, | |
| 181, 212, 243, 273, 304, 334 }; | |
| int d = dayCount[values[base + 1] | |
| - Calendar.JANUARY]; | |
| d += values[base + 2]; | |
| change_spec[i] = ",J" + Integer.toString(d); | |
| } | |
| else if (values[base + 10] == 2) | |
| { | |
| // If we haven't seen all days of the week, | |
| // we can't be sure what the rule really is. | |
| if (values[base + 8] + 6 != values[base + 9]) | |
| continue; | |
| int d; | |
| d = values[base + 1] - Calendar.JANUARY + 1; | |
| // E.g. Sun >= 5 is not representable in POSIX | |
| // TZ env string, use ",Am.n.d" extension | |
| // where m is month 1 .. 12, n is the date on | |
| // or after which it happens and d is day | |
| // of the week, 0 .. 6. So Sun >= 5 in April | |
| // is ",A4.5.0". | |
| if ((values[base + 8] % 7) == 1) | |
| { | |
| change_spec[i] = ",M" + Integer.toString(d); | |
| d = (values[base + 8] + 6) / 7; | |
| } | |
| else | |
| { | |
| change_spec[i] = ",A" + Integer.toString(d); | |
| d = values[base + 8]; | |
| } | |
| change_spec[i] += "." + Integer.toString(d); | |
| d = values[base + 4] - Calendar.SUNDAY; | |
| change_spec[i] += "." + Integer.toString(d); | |
| } | |
| else | |
| { | |
| // If we don't know whether this is lastSun or | |
| // Sun >= 22 rule. That can be either because | |
| // there was insufficient number of | |
| // transitions, or February, where it is quite | |
| // probable we haven't seen any 29th dates. | |
| // For February, assume lastSun rule, otherwise | |
| // punt. | |
| if (values[base + 10] == 1 | |
| && values[base + 1] != Calendar.FEBRUARY) | |
| continue; | |
| int d; | |
| d = values[base + 1] - Calendar.JANUARY + 1; | |
| change_spec[i] = ",M" + Integer.toString(d); | |
| d = values[base + 4] - Calendar.SUNDAY; | |
| change_spec[i] += ".5." + Integer.toString(d); | |
| } | |
| // Don't add time specification if time is | |
| // 02:00:00. | |
| if (values[base + 5] != 2 | |
| || values[base + 6] != 0 | |
| || values[base + 7] != 0) | |
| { | |
| int d = values[base + 5]; | |
| change_spec[i] += "/" + Integer.toString(d); | |
| if (values[base + 6] != 0 || values[base + 7] != 0) | |
| { | |
| d = values[base + 6]; | |
| if (d < 10) | |
| change_spec[i] | |
| += ":0" + Integer.toString(d); | |
| else | |
| change_spec[i] += ":" + Integer.toString(d); | |
| d = values[base + 7]; | |
| if (d >= 10) | |
| change_spec[i] | |
| += ":" + Integer.toString(d); | |
| else if (d > 0) | |
| change_spec[i] | |
| += ":0" + Integer.toString(d); | |
| } | |
| } | |
| } | |
| if (types[(timecnt - 1) & -2] == std_ind) | |
| { | |
| String tmp = change_spec[0]; | |
| change_spec[0] = change_spec[1]; | |
| change_spec[1] = tmp; | |
| } | |
| } | |
| } | |
| } | |
| if (tzstr == null) | |
| { | |
| tzstr = std_zonename + std_offset_string; | |
| if (change_spec[0] != null && change_spec[1] != null) | |
| tzstr += dst_zonename + dst_offset_string | |
| + change_spec[0] + change_spec[1]; | |
| } | |
| if (timecnt == 0) | |
| return new SimpleTimeZone(offsets[std_ind] * 1000, | |
| id != null ? id : tzstr); | |
| SimpleTimeZone endRule = createLastRule(tzstr); | |
| if (endRule == null) | |
| return null; | |
| /* Finally adjust the times array into the form the constructor | |
| * expects. times[0] is special, the offset and DST flag there | |
| * are for all times before that transition. Use the first non-DST | |
| * type. For all other transitions, the data file has the type | |
| * (<offset, isdst, zonename>) for the time interval starting | |
| */ | |
| for (int i = 0; i < typecnt; i++) | |
| if ((typeflags[i] & (1 << 8)) == 0) | |
| { | |
| times[0] = (times[0] << SECS_SHIFT) | (offsets[i] & OFFSET_MASK); | |
| break; | |
| } | |
| for (int i = 1; i < timecnt; i++) | |
| times[i] = (times[i] << SECS_SHIFT) | |
| | (offsets[types[i - 1]] & OFFSET_MASK) | |
| | ((typeflags[types[i - 1]] & (1 << 8)) != 0 ? IS_DST : 0); | |
| return new ZoneInfo(offsets[std_ind] * 1000, id != null ? id : tzstr, | |
| times, endRule); | |
| } | |
| catch (IOException ioe) | |
| { | |
| // Parse error, not a proper tzfile. | |
| return null; | |
| } | |
| finally | |
| { | |
| try | |
| { | |
| if (dis != null) | |
| dis.close(); | |
| } | |
| catch(IOException ioe) | |
| { | |
| // Error while close, nothing we can do. | |
| } | |
| } | |
| } | |
| /** | |
| * Skips the requested number of bytes in the given InputStream. | |
| * Throws EOFException if not enough bytes could be skipped. | |
| * Negative numbers of bytes to skip are ignored. | |
| */ | |
| private static void skipFully(InputStream is, long l) throws IOException | |
| { | |
| while (l > 0) | |
| { | |
| long k = is.skip(l); | |
| if (k <= 0) | |
| throw new EOFException(); | |
| l -= k; | |
| } | |
| } | |
| /** | |
| * Create a SimpleTimeZone from a POSIX TZ environment string, | |
| * see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html | |
| * for details. | |
| * It supports also an extension, where Am.n.d rule (m 1 .. 12, n 1 .. 25, d | |
| * 0 .. 6) describes day of week d on or after nth day of month m. | |
| * Say A4.5.0 is Sun>=5 in April. | |
| */ | |
| private static SimpleTimeZone createLastRule(String tzstr) | |
| { | |
| String stdName = null; | |
| int stdOffs; | |
| int dstOffs; | |
| try | |
| { | |
| int idLength = tzstr.length(); | |
| int index = 0; | |
| int prevIndex; | |
| char c; | |
| // get std | |
| do | |
| c = tzstr.charAt(index); | |
| while (c != '+' && c != '-' && c != ',' && c != ':' | |
| && ! Character.isDigit(c) && c != '\0' && ++index < idLength); | |
| if (index >= idLength) | |
| return new SimpleTimeZone(0, tzstr); | |
| stdName = tzstr.substring(0, index); | |
| prevIndex = index; | |
| // get the std offset | |
| do | |
| c = tzstr.charAt(index++); | |
| while ((c == '-' || c == '+' || c == ':' || Character.isDigit(c)) | |
| && index < idLength); | |
| if (index < idLength) | |
| index--; | |
| { // convert the dst string to a millis number | |
| String offset = tzstr.substring(prevIndex, index); | |
| prevIndex = index; | |
| if (offset.charAt(0) == '+' || offset.charAt(0) == '-') | |
| stdOffs = parseTime(offset.substring(1)); | |
| else | |
| stdOffs = parseTime(offset); | |
| if (offset.charAt(0) == '-') | |
| stdOffs = -stdOffs; | |
| // TZ timezone offsets are positive when WEST of the meridian. | |
| stdOffs = -stdOffs; | |
| } | |
| // Done yet? (Format: std offset) | |
| if (index >= idLength) | |
| return new SimpleTimeZone(stdOffs, stdName); | |
| // get dst | |
| do | |
| c = tzstr.charAt(index); | |
| while (c != '+' && c != '-' && c != ',' && c != ':' | |
| && ! Character.isDigit(c) && c != '\0' && ++index < idLength); | |
| // Done yet? (Format: std offset dst) | |
| if (index >= idLength) | |
| return new SimpleTimeZone(stdOffs, stdName); | |
| // get the dst offset | |
| prevIndex = index; | |
| do | |
| c = tzstr.charAt(index++); | |
| while ((c == '-' || c == '+' || c == ':' || Character.isDigit(c)) | |
| && index < idLength); | |
| if (index < idLength) | |
| index--; | |
| if (index == prevIndex && (c == ',' || c == ';')) | |
| { | |
| // Missing dst offset defaults to one hour ahead of standard | |
| // time. | |
| dstOffs = stdOffs + 60 * 60 * 1000; | |
| } | |
| else | |
| { // convert the dst string to a millis number | |
| String offset = tzstr.substring(prevIndex, index); | |
| prevIndex = index; | |
| if (offset.charAt(0) == '+' || offset.charAt(0) == '-') | |
| dstOffs = parseTime(offset.substring(1)); | |
| else | |
| dstOffs = parseTime(offset); | |
| if (offset.charAt(0) == '-') | |
| dstOffs = -dstOffs; | |
| // TZ timezone offsets are positive when WEST of the meridian. | |
| dstOffs = -dstOffs; | |
| } | |
| // Done yet? (Format: std offset dst offset) | |
| if (index >= idLength) | |
| return new SimpleTimeZone(stdOffs, stdName); | |
| // get the DST rule | |
| if (tzstr.charAt(index) == ',' | |
| || tzstr.charAt(index) == ';') | |
| { | |
| index++; | |
| int offs = index; | |
| while (tzstr.charAt(index) != ',' | |
| && tzstr.charAt(index) != ';') | |
| index++; | |
| String startTime = tzstr.substring(offs, index); | |
| index++; | |
| String endTime = tzstr.substring(index); | |
| index = startTime.indexOf('/'); | |
| int startMillis; | |
| int endMillis; | |
| String startDate; | |
| String endDate; | |
| if (index != -1) | |
| { | |
| startDate = startTime.substring(0, index); | |
| startMillis = parseTime(startTime.substring(index + 1)); | |
| } | |
| else | |
| { | |
| startDate = startTime; | |
| // if time isn't given, default to 2:00:00 AM. | |
| startMillis = 2 * 60 * 60 * 1000; | |
| } | |
| index = endTime.indexOf('/'); | |
| if (index != -1) | |
| { | |
| endDate = endTime.substring(0, index); | |
| endMillis = parseTime(endTime.substring(index + 1)); | |
| } | |
| else | |
| { | |
| endDate = endTime; | |
| // if time isn't given, default to 2:00:00 AM. | |
| endMillis = 2 * 60 * 60 * 1000; | |
| } | |
| int[] start = getDateParams(startDate); | |
| int[] end = getDateParams(endDate); | |
| return new SimpleTimeZone(stdOffs, tzstr, start[0], start[1], | |
| start[2], startMillis, end[0], end[1], | |
| end[2], endMillis, (dstOffs - stdOffs)); | |
| } | |
| } | |
| catch (IndexOutOfBoundsException _) | |
| { | |
| } | |
| catch (NumberFormatException _) | |
| { | |
| } | |
| return null; | |
| } | |
| /** | |
| * Parses and returns the params for a POSIX TZ date field, | |
| * in the format int[]{ month, day, dayOfWeek }, following the | |
| * SimpleTimeZone constructor rules. | |
| */ | |
| private static int[] getDateParams(String date) | |
| { | |
| int[] dayCount = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; | |
| int month; | |
| int type = 0; | |
| if (date.charAt(0) == 'M' || date.charAt(0) == 'm') | |
| type = 1; | |
| else if (date.charAt(0) == 'A' || date.charAt(0) == 'a') | |
| type = 2; | |
| if (type > 0) | |
| { | |
| int day; | |
| // Month, week of month, day of week | |
| // "Mm.w.d". d is between 0 (Sunday) and 6. Week w is | |
| // between 1 and 5; Week 1 is the first week in which day d | |
| // occurs and Week 5 specifies the last d day in the month. | |
| // Month m is between 1 and 12. | |
| // Month, day of month, day of week | |
| // ZoneInfo extension, not in POSIX | |
| // "Am.n.d". d is between 0 (Sunday) and 6. Day of month n is | |
| // between 1 and 25. Month m is between 1 and 12. | |
| month = Integer.parseInt(date.substring(1, date.indexOf('.'))); | |
| int week = Integer.parseInt(date.substring(date.indexOf('.') + 1, | |
| date.lastIndexOf('.'))); | |
| int dayOfWeek = Integer.parseInt(date.substring(date.lastIndexOf('.') | |
| + 1)); | |
| dayOfWeek++; // Java day of week is one-based, Sunday is first day. | |
| if (type == 2) | |
| { | |
| day = week; | |
| dayOfWeek = -dayOfWeek; | |
| } | |
| else if (week == 5) | |
| day = -1; // last day of month is -1 in java, 5 in TZ | |
| else | |
| { | |
| // First day of week starting on or after. For example, | |
| // to specify the second Sunday of April, set month to | |
| // APRIL, day-of-month to 8, and day-of-week to -SUNDAY. | |
| day = (week - 1) * 7 + 1; | |
| dayOfWeek = -dayOfWeek; | |
| } | |
| month--; // Java month is zero-based. | |
| return new int[] { month, day, dayOfWeek }; | |
| } | |
| // julian day, either zero-based 0<=n<=365 (incl feb 29) | |
| // or one-based 1<=n<=365 (no feb 29) | |
| int julianDay; // Julian day | |
| if (date.charAt(0) != 'J' || date.charAt(0) != 'j') | |
| { | |
| julianDay = Integer.parseInt(date.substring(1)); | |
| julianDay++; // make 1-based | |
| // Adjust day count to include feb 29. | |
| dayCount = new int[] | |
| { | |
| 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 | |
| }; | |
| } | |
| else | |
| // 1-based julian day | |
| julianDay = Integer.parseInt(date); | |
| int i = 11; | |
| while (i > 0) | |
| if (dayCount[i] < julianDay) | |
| break; | |
| else | |
| i--; | |
| julianDay -= dayCount[i]; | |
| month = i; | |
| return new int[] { month, julianDay, 0 }; | |
| } | |
| /** | |
| * Parses a time field hh[:mm[:ss]], returning the result | |
| * in milliseconds. No leading sign. | |
| */ | |
| private static int parseTime(String time) | |
| { | |
| int millis = 0; | |
| int i = 0; | |
| while (i < time.length()) | |
| if (time.charAt(i) == ':') | |
| break; | |
| else | |
| i++; | |
| millis = 60 * 60 * 1000 * Integer.parseInt(time.substring(0, i)); | |
| if (i >= time.length()) | |
| return millis; | |
| int iprev = ++i; | |
| while (i < time.length()) | |
| if (time.charAt(i) == ':') | |
| break; | |
| else | |
| i++; | |
| millis += 60 * 1000 * Integer.parseInt(time.substring(iprev, i)); | |
| if (i >= time.length()) | |
| return millis; | |
| millis += 1000 * Integer.parseInt(time.substring(++i)); | |
| return millis; | |
| } | |
| } |