Package com.ibm.icu.impl

Source Code of com.ibm.icu.impl.OlsonTimeZone

/*
*******************************************************************************
* Copyright (C) 2005-2012, 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.
*/
@SuppressWarnings("deprecation")
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)
   */
  @Override
  public int getOffset(final int era, final int year, final int month, final int day, final int dayOfWeek, final 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(final int era, int year, final int month, final int dom, final int dow, final int millis, final 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)
   */
  @Override
  public void setRawOffset(final int offsetMillis) {
    if (isFrozen()) {
      throw new UnsupportedOperationException("Attempt to modify a frozen OlsonTimeZone instance.");
    }

    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;
  }

  @Override
  public Object clone() {
    if (isFrozen()) {
      return this;
    }
    return cloneAsThawed();
  }

  /**
   * TimeZone API.
   */
  @Override
  public void getOffset(final long date, final boolean local, final 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.
   */
  @Deprecated
  @Override
  public void getOffsetFromLocal(final long date, final int nonExistingTimeOpt, final int duplicatedTimeOpt, final 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()
   */
  @Override
  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()
   */
  @Override
  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;
  }

  /* (non-Javadoc)
   * @see com.ibm.icu.util.TimeZone#observesDaylightTime()
   */
  @Override
  public boolean observesDaylightTime() {
    long current = System.currentTimeMillis();

    if (finalZone != null) {
      if (finalZone.useDaylightTime()) {
        return true;
      } else if (current >= finalStartMillis) {
        return false;
      }
    }

    // Return TRUE if DST is observed at any future time
    long currentSec = Grego.floorDivide(current, Grego.MILLIS_PER_SECOND);
    int trsIdx = transitionCount - 1;
    if (dstOffsetAt(trsIdx) != 0) {
      return true;
    }
    while (trsIdx >= 0) {
      if (transitionTimes64[trsIdx] <= currentSec) {
        break;
      }
      if (dstOffsetAt(trsIdx - 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.
   */
  @Override
  public int getDSTSavings() {
    if (finalZone != null) {
      return finalZone.getDSTSavings();
    }
    return super.getDSTSavings();
  }

  /* (non-Javadoc)
   * @see com.ibm.icu.util.TimeZone#inDaylightTime(java.util.Date)
   */
  @Override
  public boolean inDaylightTime(final 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)
   */
  @Override
  public boolean hasSameRules(final TimeZone other) {
    if (this == other) {
      return true;
    }
    // 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;
  }

  /**
   * Returns the canonical ID of this system time zone
   */
  public String getCanonicalID() {
    if (canonicalID == null) {
      synchronized (this) {
        if (canonicalID == null) {
          canonicalID = getCanonicalID(getID());

          assert (canonicalID != null);
          if (canonicalID == null) {
            // This should never happen...
            canonicalID = getID();
          }
        }
      }
    }
    return canonicalID;
  }

  /**
   * 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
   * @param id
   *            time zone ID
   */

  public OlsonTimeZone(final UResourceBundle top, final UResourceBundle res, final String id) {
    super(id);
    construct(top, res);
  }

  private void construct(final UResourceBundle top, final 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
    }

    if (transitionCount > 0) {
      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);
        }
      }
    } else {
      transitionTimes64 = null;
    }

    // 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
    if (transitionCount > 0) {
      r = res.get("typeMap");
      typeMapData = r.getBinary(null);
      if (typeMapData.length != transitionCount) {
        throw new IllegalArgumentException("Invalid Format");
      }
    } else {
      typeMapData = null;
    }

    // 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(final String id) {
    super(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);
    }
  }

  /* (non-Javadoc)
   * @see com.ibm.icu.util.TimeZone#setID(java.lang.String)
   */
  @Override
  public void setID(final String id) {
    if (isFrozen()) {
      throw new UnsupportedOperationException("Attempt to modify a frozen OlsonTimeZone instance.");
    }

    // Before updating the ID, preserve the original ID's canonical ID.
    if (canonicalID == null) {
      canonicalID = getCanonicalID(getID());
      assert (canonicalID != null);
      if (canonicalID == null) {
        // This should never happen...
        canonicalID = getID();
      }
    }

    if (finalZone != null) {
      finalZone.setID(id);
    }
    super.setID(id);
    transitionRulesInitialized = false;
  }

  private void getHistoricalOffset(final long date, final boolean local, final int NonExistingTimeOpt, final int DuplicatedTimeOpt,
      final 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(final 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(final int transIdx) {
    int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0;
    return typeOffsets[typeIdx] + typeOffsets[typeIdx + 1];
  }

  private int rawOffsetAt(final int transIdx) {
    int typeIdx = transIdx >= 0 ? getInt(typeMapData[transIdx]) * 2 : 0;
    return typeOffsets[typeIdx];
  }

  private int dstOffsetAt(final 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
  @Override
  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(",typeMapData=");
    if (typeMapData != null) {
      buf.append('[');
      for (int i = 0; i < typeMapData.length; ++i) {
        if (i > 0) {
          buf.append(',');
        }
        buf.append(Byte.toString(typeMapData[i]));
      }
    } 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;

  /**
   * Type description data, consisting of transitionCount uint8_t type indices (from 0..typeCount-1). Length is equal to transitionCount
   */
  private byte[] typeMapData;

  /**
   * 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

  /**
   * The canonical ID of this zone. Initialized when {@link #getCanonicalID()} is invoked first time, or {@link #setID(String)} is called.
   */
  private volatile String canonicalID = 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(final UResourceBundle top, final String ruleid) {
    UResourceBundle r = top.get("Rules");
    r = r.get(ruleid);
    return r;
  }

  @Override
  public boolean equals(final 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))));

  }

  @Override
  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());
    if (transitionTimes64 != null) {
      for (int i = 0; i < transitionTimes64.length; i++) {
        ret += transitionTimes64[i] ^ (transitionTimes64[i] >>> 8);
      }
    }
    for (int i = 0; i < typeOffsets.length; i++) {
      ret += typeOffsets[i] ^ (typeOffsets[i] >>> 8);
    }
    if (typeMapData != null) {
      for (int i = 0; i < typeMapData.length; i++) {
        ret += typeMapData[i] & 0xFF;
      }
    }
    return ret;
  }

  //
  // BasicTimeZone methods
  //

  /* (non-Javadoc)
   * @see com.ibm.icu.util.BasicTimeZone#getNextTransition(long, boolean)
   */
  @Override
  public TimeZoneTransition getNextTransition(final long base, final boolean inclusive) {
    initTransitionRules();

    if (finalZone != null) {
      if (inclusive && base == firstFinalTZTransition.getTime()) {
        return firstFinalTZTransition;
      } else if (base >= 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)
   */
  @Override
  public TimeZoneTransition getPreviousTransition(final long base, final 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()
   */
  @Override
  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(final 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;
  }

  // Freezable stuffs
  private transient boolean isFrozen = false;

  /* (non-Javadoc)
   * @see com.ibm.icu.util.TimeZone#isFrozen()
   */
  @Override
  public boolean isFrozen() {
    return isFrozen;
  }

  /* (non-Javadoc)
   * @see com.ibm.icu.util.TimeZone#freeze()
   */
  @Override
  public TimeZone freeze() {
    isFrozen = true;
    return this;
  }

  /* (non-Javadoc)
   * @see com.ibm.icu.util.TimeZone#cloneAsThawed()
   */
  @Override
  public TimeZone cloneAsThawed() {
    OlsonTimeZone tz = (OlsonTimeZone) super.cloneAsThawed();
    if (finalZone != null) {
      // TODO Do we really need this?
      finalZone.setID(getID());
      tz.finalZone = (SimpleTimeZone) finalZone.clone();
    }

    // Following data are read-only and never changed.
    // Therefore, shallow copies should be sufficient.
    //
    // transitionTimes64
    // typeMapData
    // typeOffsets

    tz.isFrozen = false;
    return tz;
  }
}
TOP

Related Classes of com.ibm.icu.impl.OlsonTimeZone

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.