/* ******************************************************************************* * Copyright (C) 2005-2010, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package com.ibm.icu.impl; import java.io.IOException; import java.io.ObjectInputStream; import java.util.Arrays; import java.util.Date; import java.util.MissingResourceException; import com.ibm.icu.util.AnnualTimeZoneRule; import com.ibm.icu.util.BasicTimeZone; import com.ibm.icu.util.Calendar; import com.ibm.icu.util.DateTimeRule; import com.ibm.icu.util.GregorianCalendar; import com.ibm.icu.util.InitialTimeZoneRule; import com.ibm.icu.util.SimpleTimeZone; import com.ibm.icu.util.TimeArrayTimeZoneRule; import com.ibm.icu.util.TimeZone; import com.ibm.icu.util.TimeZoneRule; import com.ibm.icu.util.TimeZoneTransition; import com.ibm.icu.util.UResourceBundle; /** * A time zone based on the Olson tz database. Olson time zones change * behavior over time. The raw offset, rules, presence or absence of * daylight savings time, and even the daylight savings amount can all * vary. * * This class uses a resource bundle named "zoneinfo". Zoneinfo is a * table containing different kinds of resources. In several places, * zones are referred to using integers. A zone's integer is a number * from 0..n-1, where n is the number of zones, with the zones sorted * in lexicographic order. * * 1. Zones. These have keys corresponding to the Olson IDs, e.g., * "Asia/Shanghai". Each resource describes the behavior of the given * zone. Zones come in two different formats. * * a. Zone (table). A zone is a table resource contains several * type of resources below: * * - typeOffsets:intvector (Required) * * Sets of UTC raw/dst offset pairs in seconds. Entries at * 2n represents raw offset and 2n+1 represents dst offset * paired with the raw offset at 2n. The very first pair represents * the initial zone offset (before the first transition) always. * * - trans:intvector (Optional) * * List of transition times represented by 32bit seconds from the * epoch (1970-01-01T00:00Z) in ascending order. * * - transPre32/transPost32:intvector (Optional) * * List of transition times before/after 32bit minimum seconds. * Each time is represented by a pair of 32bit integer. * * - typeMap:bin (Optional) * * Array of bytes representing the mapping between each transition * time (transPre32/trans/transPost32) and its corresponding offset * data (typeOffsets). * * - finalRule:string (Optional) * * If a recurrent transition rule is applicable to a zone forever * after the final transition time, finalRule represents the rule * in Rules data. * * - finalRaw:int (Optional) * * When finalRule is available, finalRaw is required and specifies * the raw (base) offset of the rule. * * - finalYear:int (Optional) * * When finalRule is available, finalYear is required and specifies * the start year of the rule. * * - links:intvector (Optional) * * When this zone data is shared with other zones, links specifies * all zones including the zone itself. Each zone is referenced by * integer index. * * b. Link (int, length 1). A link zone is an int resource. The * integer is the zone number of the target zone. The key of this * resource is an alternate name for the target zone. This data * is corresponding to Link data in the tz database. * * * 2. Rules. These have keys corresponding to the Olson rule IDs, * with an underscore prepended, e.g., "_EU". Each resource describes * the behavior of the given rule using an intvector, containing the * onset list, the cessation list, and the DST savings. The onset and * cessation lists consist of the month, dowim, dow, time, and time * mode. The end result is that the 11 integers describing the rule * can be passed directly into the SimpleTimeZone 13-argument * constructor (the other two arguments will be the raw offset, taken * from the complex zone element 5, and the ID string, which is not * used), with the times and the DST savings multiplied by 1000 to * scale from seconds to milliseconds. * * 3. Regions. An array specifies mapping between zones and regions. * Each item is either a 2-letter ISO country code or "001" * (UN M.49 - World). This data is generated from "zone.tab" * in the tz database. */ public class OlsonTimeZone extends BasicTimeZone { // Generated by serialver from JDK 1.4.1_01 static final long serialVersionUID = -6281977362477515376L; /* (non-Javadoc) * @see com.ibm.icu.util.TimeZone#getOffset(int, int, int, int, int, int) */ public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) { if (month < Calendar.JANUARY || month > Calendar.DECEMBER) { throw new IllegalArgumentException("Month is not in the legal range: " +month); } else { return getOffset(era, year, month, day, dayOfWeek, milliseconds, Grego.monthLength(year, month)); } } /** * TimeZone API. */ public int getOffset(int era, int year, int month,int dom, int dow, int millis, int monthLength){ if ((era != GregorianCalendar.AD && era != GregorianCalendar.BC) || month < Calendar.JANUARY || month > Calendar.DECEMBER || dom < 1 || dom > monthLength || dow < Calendar.SUNDAY || dow > Calendar.SATURDAY || millis < 0 || millis >= Grego.MILLIS_PER_DAY || monthLength < 28 || monthLength > 31) { throw new IllegalArgumentException(); } if (era == GregorianCalendar.BC) { year = -year; } if (finalZone != null && year >= finalStartYear) { return finalZone.getOffset(era, year, month, dom, dow, millis); } // Compute local epoch millis from input fields long time = Grego.fieldsToDay(year, month, dom) * Grego.MILLIS_PER_DAY + millis; int[] offsets = new int[2]; getHistoricalOffset(time, true, LOCAL_DST, LOCAL_STD, offsets); return offsets[0] + offsets[1]; } /* (non-Javadoc) * @see com.ibm.icu.util.TimeZone#setRawOffset(int) */ public void setRawOffset(int offsetMillis) { if (getRawOffset() == offsetMillis) { return; } long current = System.currentTimeMillis(); if (current < finalStartMillis) { SimpleTimeZone stz = new SimpleTimeZone(offsetMillis, getID()); boolean bDst = useDaylightTime(); if (bDst) { TimeZoneRule[] currentRules = getSimpleTimeZoneRulesNear(current); if (currentRules.length != 3) { // DST was observed at the beginning of this year, so useDaylightTime // returned true. getSimpleTimeZoneRulesNear requires at least one // future transition for making a pair of rules. This implementation // rolls back the time before the latest offset transition. TimeZoneTransition tzt = getPreviousTransition(current, false); if (tzt != null) { currentRules = getSimpleTimeZoneRulesNear(tzt.getTime() - 1); } } if (currentRules.length == 3 && (currentRules[1] instanceof AnnualTimeZoneRule) && (currentRules[2] instanceof AnnualTimeZoneRule)) { // A pair of AnnualTimeZoneRule AnnualTimeZoneRule r1 = (AnnualTimeZoneRule)currentRules[1]; AnnualTimeZoneRule r2 = (AnnualTimeZoneRule)currentRules[2]; DateTimeRule start, end; int offset1 = r1.getRawOffset() + r1.getDSTSavings(); int offset2 = r2.getRawOffset() + r2.getDSTSavings(); int sav; if (offset1 > offset2) { start = r1.getRule(); end = r2.getRule(); sav = offset1 - offset2; } else { start = r2.getRule(); end = r1.getRule(); sav = offset2 - offset1; } // getSimpleTimeZoneRulesNear always return rules using DOW / WALL_TIME stz.setStartRule(start.getRuleMonth(), start.getRuleWeekInMonth(), start.getRuleDayOfWeek(), start.getRuleMillisInDay()); stz.setEndRule(end.getRuleMonth(), end.getRuleWeekInMonth(), end.getRuleDayOfWeek(), end.getRuleMillisInDay()); // set DST saving amount and start year stz.setDSTSavings(sav); } else { // This could only happen if last rule is DST // and the rule used forever. For example, Asia/Dhaka // in tzdata2009i stays in DST forever. // Hack - set DST starting at midnight on Jan 1st, // ending 23:59:59.999 on Dec 31st stz.setStartRule(0, 1, 0); stz.setEndRule(11, 31, Grego.MILLIS_PER_DAY - 1); } } int[] fields = Grego.timeToFields(current, null); finalStartYear = fields[0]; finalStartMillis = Grego.fieldsToDay(fields[0], 0, 1); if (bDst) { // we probably do not need to set start year of final rule // to finalzone itself, but we always do this for now. stz.setStartYear(finalStartYear); } finalZone = stz; } else { finalZone.setRawOffset(offsetMillis); } transitionRulesInitialized = false; } public Object clone() { OlsonTimeZone other = (OlsonTimeZone) super.clone(); if(finalZone != null){ finalZone.setID(getID()); other.finalZone = (SimpleTimeZone)finalZone.clone(); } // Following data are read-only and never changed. // Therefore, shallow copies should be sufficient. /* if (transitionTimes64 != null) { other.transitionTimes64 = transitionTimes64.clone(); } if (typeMapData != null) { other.typeMapData = typeMapData.clone(); } other.typeOffsets = typeOffsets.clone(); */ return other; } /** * TimeZone API. */ public void getOffset(long date, boolean local, int[] offsets) { if (finalZone != null && date >= finalStartMillis) { finalZone.getOffset(date, local, offsets); } else { getHistoricalOffset(date, local, LOCAL_FORMER, LOCAL_LATTER, offsets); } } /** * {@inheritDoc} * @internal * @deprecated This API is ICU internal only. */ public void getOffsetFromLocal(long date, int nonExistingTimeOpt, int duplicatedTimeOpt, int[] offsets) { if (finalZone != null && date >= finalStartMillis) { finalZone.getOffsetFromLocal(date, nonExistingTimeOpt, duplicatedTimeOpt, offsets); } else { getHistoricalOffset(date, true, nonExistingTimeOpt, duplicatedTimeOpt, offsets); } } /* (non-Javadoc) * @see com.ibm.icu.util.TimeZone#getRawOffset() */ public int getRawOffset() { int[] ret = new int[2]; getOffset(System.currentTimeMillis(), false, ret); return ret[0]; } /* (non-Javadoc) * @see com.ibm.icu.util.TimeZone#useDaylightTime() */ public boolean useDaylightTime() { // If DST was observed in 1942 (for example) but has never been // observed from 1943 to the present, most clients will expect // this method to return FALSE. This method determines whether // DST is in use in the current year (at any point in the year) // and returns TRUE if so. long current = System.currentTimeMillis(); if (finalZone != null && current >= finalStartMillis) { return (finalZone != null && finalZone.useDaylightTime()); } int[] fields = Grego.timeToFields(current, null); // Find start of this year, and start of next year long start = Grego.fieldsToDay(fields[0], 0, 1) * SECONDS_PER_DAY; long limit = Grego.fieldsToDay(fields[0] + 1, 0, 1) * SECONDS_PER_DAY; // Return TRUE if DST is observed at any time during the current // year. for (int i = 0; i < transitionCount; ++i) { if (transitionTimes64[i] >= limit) { break; } if ((transitionTimes64[i] >= start && dstOffsetAt(i) != 0) || (transitionTimes64[i] > start && i > 0 && dstOffsetAt(i - 1) != 0)) { return true; } } return false; } /** * TimeZone API * Returns the amount of time to be added to local standard time * to get local wall clock time. */ public int getDSTSavings() { if (finalZone != null){ return finalZone.getDSTSavings(); } return super.getDSTSavings(); } /* (non-Javadoc) * @see com.ibm.icu.util.TimeZone#inDaylightTime(java.util.Date) */ public boolean inDaylightTime(Date date) { int[] temp = new int[2]; getOffset(date.getTime(), false, temp); return temp[1] != 0; } /* (non-Javadoc) * @see com.ibm.icu.util.TimeZone#hasSameRules(com.ibm.icu.util.TimeZone) */ public boolean hasSameRules(TimeZone other) { // The super class implementation only check raw offset and // use of daylight saving time. if (!super.hasSameRules(other)) { return false; } if (!(other instanceof OlsonTimeZone)) { // We cannot reasonably compare rules in different types return false; } // Check final zone OlsonTimeZone o = (OlsonTimeZone)other; if (finalZone == null) { if (o.finalZone != null) { return false; } } else { if (o.finalZone == null || finalStartYear != o.finalStartYear || !(finalZone.hasSameRules(o.finalZone))) { return false; } } // Check transitions // Note: The code below actually fails to compare two equivalent rules in // different representation properly. if (transitionCount != o.transitionCount || !Arrays.equals(transitionTimes64, o.transitionTimes64) || typeCount != o.typeCount || !Arrays.equals(typeMapData, o.typeMapData) || !Arrays.equals(typeOffsets, o.typeOffsets)){ return false; } return true; } /** * Construct a GMT+0 zone with no transitions. This is done when a * constructor fails so the resultant object is well-behaved. */ private void constructEmpty(){ transitionCount = 0; transitionTimes64 = null; typeMapData = null; typeCount = 1; typeOffsets = new int[]{0,0}; finalZone = null; finalStartYear = Integer.MAX_VALUE; finalStartMillis = Double.MAX_VALUE; transitionRulesInitialized = false; } /** * Construct from a resource bundle * @param top the top-level zoneinfo resource bundle. This is used * to lookup the rule that `res' may refer to, if there is one. * @param res the resource bundle of the zone to be constructed */ public OlsonTimeZone(UResourceBundle top, UResourceBundle res){ construct(top, res); } private void construct(UResourceBundle top, UResourceBundle res){ if ((top == null || res == null)) { throw new IllegalArgumentException(); } if(DEBUG) System.out.println("OlsonTimeZone(" + res.getKey() +")"); UResourceBundle r; int[] transPre32, trans32, transPost32; transPre32 = trans32 = transPost32 = null; transitionCount = 0; // Pre-32bit second transitions try { r = res.get("transPre32"); transPre32 = r.getIntVector(); if (transPre32.length % 2 != 0) { // elements in the pre-32bit must be an even number throw new IllegalArgumentException("Invalid Format"); } transitionCount += transPre32.length / 2; } catch (MissingResourceException e) { // Pre-32bit transition data is optional } // 32bit second transitions try { r = res.get("trans"); trans32 = r.getIntVector(); transitionCount += trans32.length; } catch (MissingResourceException e) { // 32bit transition data is optional } // Post-32bit second transitions try { r = res.get("transPost32"); transPost32 = r.getIntVector(); if (transPost32.length % 2 != 0) { // elements in the post-32bit must be an even number throw new IllegalArgumentException("Invalid Format"); } transitionCount += transPost32.length / 2; } catch (MissingResourceException e) { // Post-32bit transition data is optional } transitionTimes64 = new long[transitionCount]; int idx = 0; if (transPre32 != null) { for (int i = 0; i < transPre32.length / 2; i++, idx++) { transitionTimes64[idx] = (((long)transPre32[i * 2]) & 0x00000000FFFFFFFFL) << 32 | (((long)transPre32[i * 2 + 1]) & 0x00000000FFFFFFFFL); } } if (trans32 != null) { for (int i = 0; i < trans32.length; i++, idx++) { transitionTimes64[idx] = (long)trans32[i]; } } if (transPost32 != null) { for (int i = 0; i < transPost32.length / 2; i++, idx++) { transitionTimes64[idx] = (((long)transPost32[i * 2]) & 0x00000000FFFFFFFFL) << 32 | (((long)transPost32[i * 2 + 1]) & 0x00000000FFFFFFFFL); } } // Type offsets list must be of even size, with size >= 2 r = res.get("typeOffsets"); typeOffsets = r.getIntVector(); if ((typeOffsets.length < 2 || typeOffsets.length > 0x7FFE || typeOffsets.length % 2 != 0)) { throw new IllegalArgumentException("Invalid Format"); } typeCount = typeOffsets.length / 2; // Type map data must be of the same size as the transition count typeMapData = null; if (transitionCount > 0) { r = res.get("typeMap"); typeMapData = r.getBinary(null); if (typeMapData.length != transitionCount) { throw new IllegalArgumentException("Invalid Format"); } } // Process final rule and data, if any finalZone = null; finalStartYear = Integer.MAX_VALUE; finalStartMillis = Double.MAX_VALUE; String ruleID = null; try { ruleID = res.getString("finalRule"); r = res.get("finalRaw"); int ruleRaw = r.getInt() * Grego.MILLIS_PER_SECOND; r = loadRule(top, ruleID); int[] ruleData = r.getIntVector(); if (ruleData == null || ruleData.length != 11) { throw new IllegalArgumentException("Invalid Format"); } finalZone = new SimpleTimeZone(ruleRaw, "", ruleData[0], ruleData[1], ruleData[2], ruleData[3] * Grego.MILLIS_PER_SECOND, ruleData[4], ruleData[5], ruleData[6], ruleData[7], ruleData[8] * Grego.MILLIS_PER_SECOND, ruleData[9], ruleData[10] * Grego.MILLIS_PER_SECOND); r = res.get("finalYear"); finalStartYear = r.getInt(); // Note: Setting finalStartYear to the finalZone is problematic. When a date is around // year boundary, SimpleTimeZone may return false result when DST is observed at the // beginning of year. We could apply safe margin (day or two), but when one of recurrent // rules falls around year boundary, it could return false result. Without setting the // start year, finalZone works fine around the year boundary of the start year. // finalZone.setStartYear(finalStartYear); // Compute the millis for Jan 1, 0:00 GMT of the finalYear // Note: finalStartMillis is used for detecting either if // historic transition data or finalZone to be used. In an // extreme edge case - for example, two transitions fall into // small windows of time around the year boundary, this may // result incorrect offset computation. But I think it will // never happen practically. Yoshito - Feb 20, 2010 finalStartMillis = Grego.fieldsToDay(finalStartYear, 0, 1) * Grego.MILLIS_PER_DAY; } catch (MissingResourceException e) { if (ruleID != null) { // ruleID is found, but missing other data required for // creating finalZone throw new IllegalArgumentException("Invalid Format"); } } } // This constructor is used for testing purpose only public OlsonTimeZone(String id){ UResourceBundle top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ZONEINFORES, ICUResourceBundle.ICU_DATA_CLASS_LOADER); UResourceBundle res = ZoneMeta.openOlsonResource(top, id); construct(top, res); if (finalZone != null){ finalZone.setID(id); } super.setID(id); } public void setID(String id){ if (finalZone != null){ finalZone.setID(id); } super.setID(id); transitionRulesInitialized = false; } private void getHistoricalOffset(long date, boolean local, int NonExistingTimeOpt, int DuplicatedTimeOpt, int[] offsets) { if (transitionCount != 0) { long sec = Grego.floorDivide(date, Grego.MILLIS_PER_SECOND); if (!local && sec < transitionTimes64[0]) { // Before the first transition time offsets[0] = initialRawOffset() * Grego.MILLIS_PER_SECOND; offsets[1] = initialDstOffset() * Grego.MILLIS_PER_SECOND; } else { // Linear search from the end is the fastest approach, since // most lookups will happen at/near the end. int transIdx; for (transIdx = transitionCount - 1; transIdx >= 0; transIdx--) { long transition = transitionTimes64[transIdx]; if (local) { int offsetBefore = zoneOffsetAt(transIdx - 1); boolean dstBefore = dstOffsetAt(transIdx - 1) != 0; int offsetAfter = zoneOffsetAt(transIdx); boolean dstAfter = dstOffsetAt(transIdx) != 0; boolean dstToStd = dstBefore && !dstAfter; boolean stdToDst = !dstBefore && dstAfter; if (offsetAfter - offsetBefore >= 0) { // Positive transition, which makes a non-existing local time range if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd) || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) { transition += offsetBefore; } else if (((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst) || ((NonExistingTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) { transition += offsetAfter; } else if ((NonExistingTimeOpt & FORMER_LATTER_MASK) == LOCAL_LATTER) { transition += offsetBefore; } else { // Interprets the time with rule before the transition, // default for non-existing time range transition += offsetAfter; } } else { // Negative transition, which makes a duplicated local time range if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && dstToStd) || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && stdToDst)) { transition += offsetAfter; } else if (((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_STD && stdToDst) || ((DuplicatedTimeOpt & STD_DST_MASK) == LOCAL_DST && dstToStd)) { transition += offsetBefore; } else if ((DuplicatedTimeOpt & FORMER_LATTER_MASK) == LOCAL_FORMER) { transition += offsetBefore; } else { // Interprets the time with rule after the transition, // default for duplicated local time range transition += offsetAfter; } } } if (sec >= transition) { break; } } // transIdx could be -1 when local=true offsets[0] = rawOffsetAt(transIdx) * Grego.MILLIS_PER_SECOND; offsets[1] = dstOffsetAt(transIdx) * Grego.MILLIS_PER_SECOND; } } else { // No transitions, single pair of offsets only offsets[0] = initialRawOffset() * Grego.MILLIS_PER_SECOND; offsets[1] = initialDstOffset() * Grego.MILLIS_PER_SECOND; } } private int getInt(byte val){ return val & 0xFF; } /* * Following 3 methods return an offset at the given transition time index. * When the index is negative, return the initial offset. */ private int zoneOffsetAt(int transIdx) { int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; return typeOffsets[typeIdx] + typeOffsets[typeIdx + 1]; } private int rawOffsetAt(int transIdx) { int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; return typeOffsets[typeIdx]; } private int dstOffsetAt(int transIdx) { int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0; return typeOffsets[typeIdx + 1]; } private int initialRawOffset() { return typeOffsets[0]; } private int initialDstOffset() { return typeOffsets[1]; } // temp public String toString() { StringBuilder buf = new StringBuilder(); buf.append(super.toString()); buf.append('['); buf.append("transitionCount=" + transitionCount); buf.append(",typeCount=" + typeCount); buf.append(",transitionTimes="); if (transitionTimes64 != null) { buf.append('['); for (int i = 0; i < transitionTimes64.length; ++i) { if (i > 0) { buf.append(','); } buf.append(Long.toString(transitionTimes64[i])); } buf.append(']'); } else { buf.append("null"); } buf.append(",typeOffsets="); if (typeOffsets != null) { buf.append('['); for (int i = 0; i < typeOffsets.length; ++i) { if (i > 0) { buf.append(','); } buf.append(Integer.toString(typeOffsets[i])); } buf.append(']'); } else { buf.append("null"); } buf.append(",finalStartYear=" + finalStartYear); buf.append(",finalStartMillis=" + finalStartMillis); buf.append(",finalZone=" + finalZone); buf.append(']'); return buf.toString(); } /** * Number of transitions, 0..~370 */ private int transitionCount; /** * Number of types, 1..255 */ private int typeCount; /** * Time of each transition in seconds from 1970 epoch. */ private long[] transitionTimes64; /** * Offset from GMT in seconds for each type. * Length is equal to typeCount */ private int[] typeOffsets; // alias into res; do not delete /** * Type description data, consisting of transitionCount uint8_t * type indices (from 0..typeCount-1). * Length is equal to transitionCount */ private byte[] typeMapData; // alias into res; do not delete /** * For year >= finalStartYear, the finalZone will be used. */ private int finalStartYear = Integer.MAX_VALUE; /** * For date >= finalStartMillis, the finalZone will be used. */ private double finalStartMillis = Double.MAX_VALUE; /** * A SimpleTimeZone that governs the behavior for years >= finalYear. * If and only if finalYear == INT32_MAX then finalZone == 0. */ private SimpleTimeZone finalZone = null; // owned, may be NULL private static final String ZONEINFORES = "zoneinfo64"; private static final boolean DEBUG = ICUDebug.enabled("olson"); private static final int SECONDS_PER_DAY = 24*60*60; private static UResourceBundle loadRule(UResourceBundle top, String ruleid) { UResourceBundle r = top.get("Rules"); r = r.get(ruleid); return r; } public boolean equals(Object obj){ if (!super.equals(obj)) return false; // super does class check OlsonTimeZone z = (OlsonTimeZone) obj; return (Utility.arrayEquals(typeMapData, z.typeMapData) || // If the pointers are not equal, the zones may still // be equal if their rules and transitions are equal (finalStartYear == z.finalStartYear && // Don't compare finalMillis; if finalYear is ==, so is finalMillis ((finalZone == null && z.finalZone == null) || (finalZone != null && z.finalZone != null && finalZone.equals(z.finalZone)) && transitionCount == z.transitionCount && typeCount == z.typeCount && Utility.arrayEquals(transitionTimes64, z.transitionTimes64) && Utility.arrayEquals(typeOffsets, z.typeOffsets) && Utility.arrayEquals(typeMapData, z.typeMapData) ))); } public int hashCode(){ int ret = (int) (finalStartYear ^ (finalStartYear>>>4) + transitionCount ^ (transitionCount>>>6) + typeCount ^ (typeCount>>>8) + Double.doubleToLongBits(finalStartMillis)+ (finalZone == null ? 0 : finalZone.hashCode()) + super.hashCode()); for(int i=0; i>>8); } for(int i=0; i>>8); } for(int i=0; i= firstFinalTZTransition.getTime()) { if (finalZone.useDaylightTime()) { //return finalZone.getNextTransition(base, inclusive); return finalZoneWithStartYear.getNextTransition(base, inclusive); } else { // No more transitions return null; } } } if (historicRules != null) { // Find a historical transition int ttidx = transitionCount - 1; for (; ttidx >= firstTZTransitionIdx; ttidx--) { long t = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; if (base > t || (!inclusive && base == t)) { break; } } if (ttidx == transitionCount - 1) { return firstFinalTZTransition; } else if (ttidx < firstTZTransitionIdx) { return firstTZTransition; } else { // Create a TimeZoneTransition TimeZoneRule to = historicRules[getInt(typeMapData[ttidx + 1])]; TimeZoneRule from = historicRules[getInt(typeMapData[ttidx])]; long startTime = transitionTimes64[ttidx+1] * Grego.MILLIS_PER_SECOND; // The transitions loaded from zoneinfo.res may contain non-transition data if (from.getName().equals(to.getName()) && from.getRawOffset() == to.getRawOffset() && from.getDSTSavings() == to.getDSTSavings()) { return getNextTransition(startTime, false); } return new TimeZoneTransition(startTime, from, to); } } return null; } /* (non-Javadoc) * @see com.ibm.icu.util.BasicTimeZone#getPreviousTransition(long, boolean) */ public TimeZoneTransition getPreviousTransition(long base, boolean inclusive) { initTransitionRules(); if (finalZone != null) { if (inclusive && base == firstFinalTZTransition.getTime()) { return firstFinalTZTransition; } else if (base > firstFinalTZTransition.getTime()) { if (finalZone.useDaylightTime()) { //return finalZone.getPreviousTransition(base, inclusive); return finalZoneWithStartYear.getPreviousTransition(base, inclusive); } else { return firstFinalTZTransition; } } } if (historicRules != null) { // Find a historical transition int ttidx = transitionCount - 1; for (; ttidx >= firstTZTransitionIdx; ttidx--) { long t = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; if (base > t || (inclusive && base == t)) { break; } } if (ttidx < firstTZTransitionIdx) { // No more transitions return null; } else if (ttidx == firstTZTransitionIdx) { return firstTZTransition; } else { // Create a TimeZoneTransition TimeZoneRule to = historicRules[getInt(typeMapData[ttidx])]; TimeZoneRule from = historicRules[getInt(typeMapData[ttidx-1])]; long startTime = transitionTimes64[ttidx] * Grego.MILLIS_PER_SECOND; // The transitions loaded from zoneinfo.res may contain non-transition data if (from.getName().equals(to.getName()) && from.getRawOffset() == to.getRawOffset() && from.getDSTSavings() == to.getDSTSavings()) { return getPreviousTransition(startTime, false); } return new TimeZoneTransition(startTime, from, to); } } return null; } /* (non-Javadoc) * @see com.ibm.icu.util.BasicTimeZone#getTimeZoneRules() */ public TimeZoneRule[] getTimeZoneRules() { initTransitionRules(); int size = 1; if (historicRules != null) { // historicRules may contain null entries when original zoneinfo data // includes non transition data. for (int i = 0; i < historicRules.length; i++) { if (historicRules[i] != null) { size++; } } } if (finalZone != null) { if (finalZone.useDaylightTime()) { size += 2; } else { size++; } } TimeZoneRule[] rules = new TimeZoneRule[size]; int idx = 0; rules[idx++] = initialRule; if (historicRules != null) { for (int i = 0; i < historicRules.length; i++) { if (historicRules[i] != null) { rules[idx++] = historicRules[i]; } } } if (finalZone != null) { if (finalZone.useDaylightTime()) { TimeZoneRule[] stzr = finalZoneWithStartYear.getTimeZoneRules(); // Adding only transition rules rules[idx++] = stzr[1]; rules[idx++] = stzr[2]; } else { // Create a TimeArrayTimeZoneRule at finalMillis rules[idx++] = new TimeArrayTimeZoneRule(getID() + "(STD)", finalZone.getRawOffset(), 0, new long[] {(long)finalStartMillis}, DateTimeRule.UTC_TIME); } } return rules; } private transient InitialTimeZoneRule initialRule; private transient TimeZoneTransition firstTZTransition; private transient int firstTZTransitionIdx; private transient TimeZoneTransition firstFinalTZTransition; private transient TimeArrayTimeZoneRule[] historicRules; private transient SimpleTimeZone finalZoneWithStartYear; // hack private transient boolean transitionRulesInitialized; private synchronized void initTransitionRules() { if (transitionRulesInitialized) { return; } initialRule = null; firstTZTransition = null; firstFinalTZTransition = null; historicRules = null; firstTZTransitionIdx = 0; finalZoneWithStartYear = null; String stdName = getID() + "(STD)"; String dstName = getID() + "(DST)"; int raw, dst; // Create initial rule raw = initialRawOffset() * Grego.MILLIS_PER_SECOND; dst = initialDstOffset() * Grego.MILLIS_PER_SECOND; initialRule = new InitialTimeZoneRule((dst == 0 ? stdName : dstName), raw, dst); if (transitionCount > 0) { int transitionIdx, typeIdx; // We probably no longer need to check the first "real" transition // here, because the new tzcode remove such transitions already. // For now, keeping this code for just in case. Feb 19, 2010 Yoshito for (transitionIdx = 0; transitionIdx < transitionCount; transitionIdx++) { if (getInt(typeMapData[transitionIdx]) != 0) { // type 0 is the initial type break; } firstTZTransitionIdx++; } if (transitionIdx == transitionCount) { // Actually no transitions... } else { // Build historic rule array long[] times = new long[transitionCount]; for (typeIdx = 0; typeIdx < typeCount; typeIdx++) { // Gather all start times for each pair of offsets int nTimes = 0; for (transitionIdx = firstTZTransitionIdx; transitionIdx < transitionCount; transitionIdx++) { if (typeIdx == getInt(typeMapData[transitionIdx])) { long tt = transitionTimes64[transitionIdx] * Grego.MILLIS_PER_SECOND; if (tt < finalStartMillis) { // Exclude transitions after finalMillis times[nTimes++] = tt; } } } if (nTimes > 0) { long[] startTimes = new long[nTimes]; System.arraycopy(times, 0, startTimes, 0, nTimes); // Create a TimeArrayTimeZoneRule raw = typeOffsets[typeIdx*2]*Grego.MILLIS_PER_SECOND; dst = typeOffsets[typeIdx*2 + 1]*Grego.MILLIS_PER_SECOND; if (historicRules == null) { historicRules = new TimeArrayTimeZoneRule[typeCount]; } historicRules[typeIdx] = new TimeArrayTimeZoneRule((dst == 0 ? stdName : dstName), raw, dst, startTimes, DateTimeRule.UTC_TIME); } } // Create initial transition typeIdx = getInt(typeMapData[firstTZTransitionIdx]); firstTZTransition = new TimeZoneTransition(transitionTimes64[firstTZTransitionIdx] * Grego.MILLIS_PER_SECOND, initialRule, historicRules[typeIdx]); } } if (finalZone != null) { // Get the first occurrence of final rule starts long startTime = (long)finalStartMillis; TimeZoneRule firstFinalRule; if (finalZone.useDaylightTime()) { /* * Note: When an OlsonTimeZone is constructed, we should set the final year * as the start year of finalZone. However, the boundary condition used for * getting offset from finalZone has some problems. So setting the start year * in the finalZone will cause a problem. For now, we do not set the valid * start year when the construction time and create a clone and set the * start year when extracting rules. */ finalZoneWithStartYear = (SimpleTimeZone)finalZone.clone(); finalZoneWithStartYear.setStartYear(finalStartYear); TimeZoneTransition tzt = finalZoneWithStartYear.getNextTransition(startTime, false); firstFinalRule = tzt.getTo(); startTime = tzt.getTime(); } else { finalZoneWithStartYear = finalZone; firstFinalRule = new TimeArrayTimeZoneRule(finalZone.getID(), finalZone.getRawOffset(), 0, new long[] {startTime}, DateTimeRule.UTC_TIME); } TimeZoneRule prevRule = null; if (transitionCount > 0) { prevRule = historicRules[getInt(typeMapData[transitionCount - 1])]; } if (prevRule == null) { // No historic transitions, but only finalZone available prevRule = initialRule; } firstFinalTZTransition = new TimeZoneTransition(startTime, prevRule, firstFinalRule); } transitionRulesInitialized = true; } // Note: This class does not support back level serialization compatibility // very well. ICU 4.4 introduced the 64bit transition data. It is probably // possible to implement this class to make old version of ICU to deserialize // object stream serialized by ICU 4.4+. However, such implementation will // introduce unnecessary complexity other than serialization support. // I decided to provide minimum level of backward compatibility, which // only support ICU 4.4+ to create an instance of OlsonTimeZone by reloading // the zone rules from bundles. ICU 4.2 or older version of ICU cannot // deserialize object stream created by ICU 4.4+. Yoshito -Feb 22, 2010 private static final int currentSerialVersion = 1; private int serialVersionOnStream = currentSerialVersion; private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); if (serialVersionOnStream < 1) { // No version - 4.2 or older // Just reloading the rule from bundle boolean initialized = false; String tzid = getID(); if (tzid != null) { try { UResourceBundle top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ZONEINFORES, ICUResourceBundle.ICU_DATA_CLASS_LOADER); UResourceBundle res = ZoneMeta.openOlsonResource(top, tzid); construct(top, res); if (finalZone != null){ finalZone.setID(tzid); } initialized = true; } catch (Exception e) { // throw away } } if (!initialized) { // final resort constructEmpty(); } } // need to rebuild transition rules when requested transitionRulesInitialized = false; } }