Package com.ibm.icu.impl

Source Code of com.ibm.icu.impl.ZoneMeta$CustomTimeZoneCache

/*
**********************************************************************
* Copyright (c) 2003-2013 International Business Machines
* Corporation and others.  All Rights Reserved.
**********************************************************************
* Author: Alan Liu
* Created: September 4 2003
* Since: ICU 2.8
**********************************************************************
*/
package com.ibm.icu.impl;

import java.lang.ref.SoftReference;
import java.text.ParsePosition;
import java.util.Collections;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeSet;

import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.SimpleTimeZone;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.TimeZone.SystemTimeZoneType;
import com.ibm.icu.util.UResourceBundle;

/**
* This class, not to be instantiated, implements the meta-data missing from the underlying core JDK implementation of time zones. There are
* two missing features: Obtaining a list of available zones for a given country (as defined by the Olson database), and obtaining a list of
* equivalent zones for a given zone (as defined by Olson links).
*
* This class uses a data class, ZoneMetaData, which is created by the tool tz2icu.
*
* @author Alan Liu
* @since ICU 2.8
*/
public final class ZoneMeta {
  private static final boolean ASSERT = false;

  private static final String ZONEINFORESNAME = "zoneinfo64";
  private static final String kREGIONS = "Regions";
  private static final String kZONES = "Zones";
  private static final String kNAMES = "Names";

  private static final String kGMT_ID = "GMT";
  private static final String kCUSTOM_TZ_PREFIX = "GMT";

  private static final String kWorld = "001";

  private static SoftReference<Set<String>> REF_SYSTEM_ZONES;
  private static SoftReference<Set<String>> REF_CANONICAL_SYSTEM_ZONES;
  private static SoftReference<Set<String>> REF_CANONICAL_SYSTEM_LOCATION_ZONES;

  /**
   * Returns an immutable set of system time zone IDs. Etc/Unknown is excluded.
   *
   * @return An immutable set of system time zone IDs.
   */
  private static synchronized Set<String> getSystemZIDs() {
    Set<String> systemZones = null;
    if (REF_SYSTEM_ZONES != null) {
      systemZones = REF_SYSTEM_ZONES.get();
    }
    if (systemZones == null) {
      Set<String> systemIDs = new TreeSet<String>();
      String[] allIDs = getZoneIDs();
      for (String id : allIDs) {
        // exclude Etc/Unknown
        if (id.equals(TimeZone.UNKNOWN_ZONE_ID)) {
          continue;
        }
        systemIDs.add(id);
      }
      systemZones = Collections.unmodifiableSet(systemIDs);
      REF_SYSTEM_ZONES = new SoftReference<Set<String>>(systemZones);
    }
    return systemZones;
  }

  /**
   * Returns an immutable set of canonical system time zone IDs. The result set is a subset of {@link #getSystemZIDs()}, but not including
   * aliases, such as "US/Eastern".
   *
   * @return An immutable set of canonical system time zone IDs.
   */
  private static synchronized Set<String> getCanonicalSystemZIDs() {
    Set<String> canonicalSystemZones = null;
    if (REF_CANONICAL_SYSTEM_ZONES != null) {
      canonicalSystemZones = REF_CANONICAL_SYSTEM_ZONES.get();
    }
    if (canonicalSystemZones == null) {
      Set<String> canonicalSystemIDs = new TreeSet<String>();
      String[] allIDs = getZoneIDs();
      for (String id : allIDs) {
        // exclude Etc/Unknown
        if (id.equals(TimeZone.UNKNOWN_ZONE_ID)) {
          continue;
        }
        String canonicalID = getCanonicalCLDRID(id);
        if (id.equals(canonicalID)) {
          canonicalSystemIDs.add(id);
        }
      }
      canonicalSystemZones = Collections.unmodifiableSet(canonicalSystemIDs);
      REF_CANONICAL_SYSTEM_ZONES = new SoftReference<Set<String>>(canonicalSystemZones);
    }
    return canonicalSystemZones;
  }

  /**
   * Returns an immutable set of canonical system time zone IDs that are associated with actual locations. The result set is a subset of
   * {@link #getCanonicalSystemZIDs()}, but not including IDs, such as "Etc/GTM+5".
   *
   * @return An immutable set of canonical system time zone IDs that are associated with actual locations.
   */
  private static synchronized Set<String> getCanonicalSystemLocationZIDs() {
    Set<String> canonicalSystemLocationZones = null;
    if (REF_CANONICAL_SYSTEM_LOCATION_ZONES != null) {
      canonicalSystemLocationZones = REF_CANONICAL_SYSTEM_LOCATION_ZONES.get();
    }
    if (canonicalSystemLocationZones == null) {
      Set<String> canonicalSystemLocationIDs = new TreeSet<String>();
      String[] allIDs = getZoneIDs();
      for (String id : allIDs) {
        // exclude Etc/Unknown
        if (id.equals(TimeZone.UNKNOWN_ZONE_ID)) {
          continue;
        }
        String canonicalID = getCanonicalCLDRID(id);
        if (id.equals(canonicalID)) {
          String region = getRegion(id);
          if (region != null && !region.equals(kWorld)) {
            canonicalSystemLocationIDs.add(id);
          }
        }
      }
      canonicalSystemLocationZones = Collections.unmodifiableSet(canonicalSystemLocationIDs);
      REF_CANONICAL_SYSTEM_LOCATION_ZONES = new SoftReference<Set<String>>(canonicalSystemLocationZones);
    }
    return canonicalSystemLocationZones;
  }

  /**
   * Returns an immutable set of system IDs for the given conditions.
   *
   * @param type
   *            a system time zone type.
   * @param region
   *            a region, or null.
   * @param rawOffset
   *            a zone raw offset or null.
   * @return An immutable set of system IDs for the given conditions.
   */
  public static Set<String> getAvailableIDs(SystemTimeZoneType type, String region, Integer rawOffset) {
    Set<String> baseSet = null;
    switch (type) {
    case ANY:
      baseSet = getSystemZIDs();
      break;
    case CANONICAL:
      baseSet = getCanonicalSystemZIDs();
      break;
    case CANONICAL_LOCATION:
      baseSet = getCanonicalSystemLocationZIDs();
      break;
    default:
      // never occur
      throw new IllegalArgumentException("Unknown SystemTimeZoneType");
    }

    if (region == null && rawOffset == null) {
      return baseSet;
    }

    if (region != null) {
      region = region.toUpperCase(Locale.ENGLISH);
    }

    // Filter by region/rawOffset
    Set<String> result = new TreeSet<String>();
    for (String id : baseSet) {
      if (region != null) {
        String r = getRegion(id);
        if (!region.equals(r)) {
          continue;
        }
      }
      if (rawOffset != null) {
        // This is VERY inefficient.
        TimeZone z = getSystemTimeZone(id);
        if (z == null || !rawOffset.equals(z.getRawOffset())) {
          continue;
        }
      }
      result.add(id);
    }
    if (result.isEmpty()) {
      return Collections.emptySet();
    }

    return Collections.unmodifiableSet(result);
  }

  /**
   * Returns the number of IDs in the equivalency group that includes the given ID. An equivalency group contains zones that behave
   * identically to the given zone.
   *
   * <p>
   * If there are no equivalent zones, then this method returns 0. This means either the given ID is not a valid zone, or it is and there
   * are no other equivalent zones.
   *
   * @param id
   *            a system time zone ID
   * @return the number of zones in the equivalency group containing 'id', or zero if there are no equivalent zones.
   * @see #getEquivalentID
   */
  public static synchronized int countEquivalentIDs(String id) {
    int count = 0;
    UResourceBundle res = openOlsonResource(null, id);
    if (res != null) {
      try {
        UResourceBundle links = res.get("links");
        int[] v = links.getIntVector();
        count = v.length;
      } catch (MissingResourceException ex) {
        // throw away
      }
    }
    return count;
  }

  /**
   * Returns an ID in the equivalency group that includes the given ID. An equivalency group contains zones that behave identically to the
   * given zone.
   *
   * <p>
   * The given index must be in the range 0..n-1, where n is the value returned by <code>countEquivalentIDs(id)</code>. For some value of
   * 'index', the returned value will be equal to the given id. If the given id is not a valid system time zone, or if 'index' is out of
   * range, then returns an empty string.
   *
   * @param id
   *            a system time zone ID
   * @param index
   *            a value from 0 to n-1, where n is the value returned by <code>countEquivalentIDs(id)</code>
   * @return the ID of the index-th zone in the equivalency group containing 'id', or an empty string if 'id' is not a valid system ID or
   *         'index' is out of range
   * @see #countEquivalentIDs
   */
  public static synchronized String getEquivalentID(String id, int index) {
    String result = "";
    if (index >= 0) {
      UResourceBundle res = openOlsonResource(null, id);
      if (res != null) {
        int zoneIdx = -1;
        try {
          UResourceBundle links = res.get("links");
          int[] zones = links.getIntVector();
          if (index < zones.length) {
            zoneIdx = zones[index];
          }
        } catch (MissingResourceException ex) {
          // throw away
        }
        if (zoneIdx >= 0) {
          String tmp = getZoneID(zoneIdx);
          if (tmp != null) {
            result = tmp;
          }
        }
      }
    }
    return result;
  }

  private static String[] ZONEIDS = null;

  /*
   * ICU frequently refers the zone ID array in zoneinfo resource
   */
  private static synchronized String[] getZoneIDs() {
    if (ZONEIDS == null) {
      try {
        UResourceBundle top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME,
            ICUResourceBundle.ICU_DATA_CLASS_LOADER);
        UResourceBundle names = top.get(kNAMES);
        ZONEIDS = names.getStringArray();
      } catch (MissingResourceException ex) {
        // throw away..
      }
    }
    if (ZONEIDS == null) {
      ZONEIDS = new String[0];
    }
    return ZONEIDS;
  }

  private static String getZoneID(int idx) {
    if (idx >= 0) {
      String[] ids = getZoneIDs();
      if (idx < ids.length) {
        return ids[idx];
      }
    }
    return null;
  }

  private static int getZoneIndex(String zid) {
    int zoneIdx = -1;

    String[] all = getZoneIDs();
    if (all.length > 0) {
      int start = 0;
      int limit = all.length;

      int lastMid = Integer.MAX_VALUE;
      for (;;) {
        int mid = (start + limit) / 2;
        if (lastMid == mid) { /* Have we moved? */
          break; /* We haven't moved, and it wasn't found. */
        }
        lastMid = mid;
        int r = zid.compareTo(all[mid]);
        if (r == 0) {
          zoneIdx = mid;
          break;
        } else if (r < 0) {
          limit = mid;
        } else {
          start = mid;
        }
      }
    }

    return zoneIdx;
  }

  private static ICUCache<String, String> CANONICAL_ID_CACHE = new SimpleCache<String, String>();
  private static ICUCache<String, String> REGION_CACHE = new SimpleCache<String, String>();
  private static ICUCache<String, Boolean> SINGLE_COUNTRY_CACHE = new SimpleCache<String, Boolean>();

  public static String getCanonicalCLDRID(TimeZone tz) {
    if (tz instanceof OlsonTimeZone) {
      return ((OlsonTimeZone) tz).getCanonicalID();
    }
    return getCanonicalCLDRID(tz.getID());
  }

  /**
   * Return the canonical id for this tzid defined by CLDR, which might be the id itself. If the given tzid is not known, return null.
   *
   * Note: This internal API supports all known system IDs and "Etc/Unknown" (which is NOT a system ID).
   */
  public static String getCanonicalCLDRID(String tzid) {
    String canonical = CANONICAL_ID_CACHE.get(tzid);
    if (canonical == null) {
      canonical = findCLDRCanonicalID(tzid);
      if (canonical == null) {
        // Resolve Olson link and try it again if necessary
        try {
          int zoneIdx = getZoneIndex(tzid);
          if (zoneIdx >= 0) {
            UResourceBundle top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME,
                ICUResourceBundle.ICU_DATA_CLASS_LOADER);
            UResourceBundle zones = top.get(kZONES);
            UResourceBundle zone = zones.get(zoneIdx);
            if (zone.getType() == UResourceBundle.INT) {
              // It's a link - resolve link and lookup
              tzid = getZoneID(zone.getInt());
              canonical = findCLDRCanonicalID(tzid);
            }
            if (canonical == null) {
              canonical = tzid;
            }
          }
        } catch (MissingResourceException e) {
          // fall through
        }
      }
      if (canonical != null) {
        CANONICAL_ID_CACHE.put(tzid, canonical);
      }
    }
    return canonical;
  }

  private static String findCLDRCanonicalID(String tzid) {
    String canonical = null;
    String tzidKey = tzid.replace('/', ':');

    try {
      // First, try check if the given ID is canonical
      UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "keyTypeData",
          ICUResourceBundle.ICU_DATA_CLASS_LOADER);
      UResourceBundle typeMap = keyTypeData.get("typeMap");
      UResourceBundle typeKeys = typeMap.get("timezone");
      try {
        /* UResourceBundle canonicalEntry = */typeKeys.get(tzidKey);
        // The given tzid is available in the canonical list
        canonical = tzid;
      } catch (MissingResourceException e) {
        // fall through
      }
      if (canonical == null) {
        // Try alias map
        UResourceBundle typeAlias = keyTypeData.get("typeAlias");
        UResourceBundle aliasesForKey = typeAlias.get("timezone");
        canonical = aliasesForKey.getString(tzidKey);
      }
    } catch (MissingResourceException e) {
      // fall through
    }
    return canonical;
  }

  /**
   * Return the region code for this tzid. If tzid is not a system zone ID, this method returns null.
   */
  public static String getRegion(String tzid) {
    String region = REGION_CACHE.get(tzid);
    if (region == null) {
      int zoneIdx = getZoneIndex(tzid);
      if (zoneIdx >= 0) {
        try {
          UResourceBundle top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME,
              ICUResourceBundle.ICU_DATA_CLASS_LOADER);
          UResourceBundle regions = top.get(kREGIONS);
          if (zoneIdx < regions.getSize()) {
            region = regions.getString(zoneIdx);
          }
        } catch (MissingResourceException e) {
          // throw away
        }
        if (region != null) {
          REGION_CACHE.put(tzid, region);
        }
      }
    }
    return region;
  }

  /**
   * Return the canonical country code for this tzid. If we have none, or if the time zone is not associated with a country or unknown,
   * return null.
   */
  public static String getCanonicalCountry(String tzid) {
    String country = getRegion(tzid);
    if (country != null && country.equals(kWorld)) {
      country = null;
    }
    return country;
  }

  /**
   * Return the canonical country code for this tzid. If we have none, or if the time zone is not associated with a country or unknown,
   * return null. When the given zone is the primary zone of the country, true is set to isPrimary.
   */
  public static String getCanonicalCountry(String tzid, Output<Boolean> isPrimary) {
    isPrimary.value = Boolean.FALSE;

    String country = getRegion(tzid);
    if (country != null && country.equals(kWorld)) {
      return null;
    }

    // Check the cache
    Boolean singleZone = SINGLE_COUNTRY_CACHE.get(tzid);
    if (singleZone == null) {
      Set<String> ids = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL_LOCATION, country, null);
      assert (ids.size() >= 1);
      singleZone = Boolean.valueOf(ids.size() <= 1);
      SINGLE_COUNTRY_CACHE.put(tzid, singleZone);
    }

    if (singleZone) {
      isPrimary.value = Boolean.TRUE;
    } else {
      // Note: We may cache the primary zone map in future.

      // Even a country has multiple zones, one of them might be
      // dominant and treated as a primary zone.
      try {
        UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
        UResourceBundle primaryZones = bundle.get("primaryZones");
        String primaryZone = primaryZones.getString(country);
        if (tzid.equals(primaryZone)) {
          isPrimary.value = Boolean.TRUE;
        } else {
          // The given ID might not be a canonical ID
          String canonicalID = getCanonicalCLDRID(tzid);
          if (canonicalID != null && canonicalID.equals(primaryZone)) {
            isPrimary.value = Boolean.TRUE;
          }
        }
      } catch (MissingResourceException e) {
        // ignore
      }
    }

    return country;
  }

  /**
   * Given an ID and the top-level resource of the zoneinfo resource, open the appropriate resource for the given time zone. Dereference
   * links if necessary.
   *
   * @param top
   *            the top level resource of the zoneinfo resource or null.
   * @param id
   *            zone id
   * @return the corresponding zone resource or null if not found
   */
  public static UResourceBundle openOlsonResource(UResourceBundle top, String id) {
    UResourceBundle res = null;
    int zoneIdx = getZoneIndex(id);
    if (zoneIdx >= 0) {
      try {
        if (top == null) {
          top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME,
              ICUResourceBundle.ICU_DATA_CLASS_LOADER);
        }
        UResourceBundle zones = top.get(kZONES);
        UResourceBundle zone = zones.get(zoneIdx);
        if (zone.getType() == UResourceBundle.INT) {
          // resolve link
          zone = zones.get(zone.getInt());
        }
        res = zone;
      } catch (MissingResourceException e) {
        res = null;
      }
    }
    return res;
  }

  /**
   * System time zone object cache
   */
  private static class SystemTimeZoneCache extends SoftCache<String, OlsonTimeZone, String> {

    /* (non-Javadoc)
     * @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
     */
    @Override
    protected OlsonTimeZone createInstance(String key, String data) {
      OlsonTimeZone tz = null;
      try {
        UResourceBundle top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME,
            ICUResourceBundle.ICU_DATA_CLASS_LOADER);
        UResourceBundle res = openOlsonResource(top, data);
        if (res != null) {
          tz = new OlsonTimeZone(top, res, data);
          tz.freeze();
        }
      } catch (MissingResourceException e) {
        // do nothing
      }
      return tz;
    }
  }

  private static final SystemTimeZoneCache SYSTEM_ZONE_CACHE = new SystemTimeZoneCache();

  /**
   * Returns a frozen OlsonTimeZone instance for the given ID. This method returns null when the given ID is unknown.
   */
  public static TimeZone getSystemTimeZone(String id) {
    return SYSTEM_ZONE_CACHE.getInstance(id, id);
  }

  // Maximum value of valid custom time zone hour/min
  private static final int kMAX_CUSTOM_HOUR = 23;
  private static final int kMAX_CUSTOM_MIN = 59;
  private static final int kMAX_CUSTOM_SEC = 59;

  /**
   * Custom time zone object cache
   */
  private static class CustomTimeZoneCache extends SoftCache<Integer, SimpleTimeZone, int[]> {

    /* (non-Javadoc)
     * @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
     */
    @Override
    protected SimpleTimeZone createInstance(Integer key, int[] data) {
      assert (data.length == 4);
      assert (data[0] == 1 || data[0] == -1);
      assert (data[1] >= 0 && data[1] <= kMAX_CUSTOM_HOUR);
      assert (data[2] >= 0 && data[2] <= kMAX_CUSTOM_MIN);
      assert (data[3] >= 0 && data[3] <= kMAX_CUSTOM_SEC);
      String id = formatCustomID(data[1], data[2], data[3], data[0] < 0);
      int offset = data[0] * ((data[1] * 60 + data[2]) * 60 + data[3]) * 1000;
      SimpleTimeZone tz = new SimpleTimeZone(offset, id);
      tz.freeze();
      return tz;
    }
  }

  private static final CustomTimeZoneCache CUSTOM_ZONE_CACHE = new CustomTimeZoneCache();

  /**
   * Parse a custom time zone identifier and return a corresponding zone.
   *
   * @param id
   *            a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or GMT[+-]hh.
   * @return a frozen SimpleTimeZone with the given offset and no Daylight Savings Time, or null if the id cannot be parsed.
   */
  public static TimeZone getCustomTimeZone(String id) {
    int[] fields = new int[4];
    if (parseCustomID(id, fields)) {
      // fields[0] - sign
      // fields[1] - hour / 5-bit
      // fields[2] - min  / 6-bit
      // fields[3] - sec  / 6-bit
      Integer key = Integer.valueOf(fields[0] * (fields[1] | fields[2] << 5 | fields[3] << 11));
      return CUSTOM_ZONE_CACHE.getInstance(key, fields);
    }
    return null;
  }

  /**
   * Parse a custom time zone identifier and return the normalized custom time zone identifier for the given custom id string.
   *
   * @param id
   *            a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or GMT[+-]hh.
   * @return The normalized custom id string.
   */
  public static String getCustomID(String id) {
    int[] fields = new int[4];
    if (parseCustomID(id, fields)) {
      return formatCustomID(fields[1], fields[2], fields[3], fields[0] < 0);
    }
    return null;
  }

  /*
   * Parses the given custom time zone identifier
   * @param id id A string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or
   * GMT[+-]hh.
   * @param fields An array of int (length = 4) to receive the parsed
   * offset time fields.  The sign is set to fields[0] (-1 or 1),
   * hour is set to fields[1], minute is set to fields[2] and second is
   * set to fields[3].
   * @return Returns true when the given custom id is valid.
   */
  static boolean parseCustomID(String id, int[] fields) {
    NumberFormat numberFormat = null;

    if (id != null && id.length() > kGMT_ID.length() && id.toUpperCase(Locale.ENGLISH).startsWith(kGMT_ID)) {
      ParsePosition pos = new ParsePosition(kGMT_ID.length());
      int sign = 1;
      int hour = 0;
      int min = 0;
      int sec = 0;

      if (id.charAt(pos.getIndex()) == 0x002D /*'-'*/) {
        sign = -1;
      } else if (id.charAt(pos.getIndex()) != 0x002B /*'+'*/) {
        return false;
      }
      pos.setIndex(pos.getIndex() + 1);

      numberFormat = NumberFormat.getInstance();
      numberFormat.setParseIntegerOnly(true);

      // Look for either hh:mm, hhmm, or hh
      int start = pos.getIndex();

      Number n = numberFormat.parse(id, pos);
      if (pos.getIndex() == start) {
        return false;
      }
      hour = n.intValue();

      if (pos.getIndex() < id.length()) {
        if (pos.getIndex() - start > 2 || id.charAt(pos.getIndex()) != 0x003A /*':'*/) {
          return false;
        }
        // hh:mm
        pos.setIndex(pos.getIndex() + 1);
        int oldPos = pos.getIndex();
        n = numberFormat.parse(id, pos);
        if ((pos.getIndex() - oldPos) != 2) {
          // must be 2 digits
          return false;
        }
        min = n.intValue();
        if (pos.getIndex() < id.length()) {
          if (id.charAt(pos.getIndex()) != 0x003A /*':'*/) {
            return false;
          }
          // [:ss]
          pos.setIndex(pos.getIndex() + 1);
          oldPos = pos.getIndex();
          n = numberFormat.parse(id, pos);
          if (pos.getIndex() != id.length() || (pos.getIndex() - oldPos) != 2) {
            return false;
          }
          sec = n.intValue();
        }
      } else {
        // Supported formats are below -
        //
        // HHmmss
        // Hmmss
        // HHmm
        // Hmm
        // HH
        // H

        int length = pos.getIndex() - start;
        if (length <= 0 || 6 < length) {
          // invalid length
          return false;
        }
        switch (length) {
        case 1:
        case 2:
          // already set to hour
          break;
        case 3:
        case 4:
          min = hour % 100;
          hour /= 100;
          break;
        case 5:
        case 6:
          sec = hour % 100;
          min = (hour / 100) % 100;
          hour /= 10000;
          break;
        }
      }

      if (hour <= kMAX_CUSTOM_HOUR && min <= kMAX_CUSTOM_MIN && sec <= kMAX_CUSTOM_SEC) {
        if (fields != null) {
          if (fields.length >= 1) {
            fields[0] = sign;
          }
          if (fields.length >= 2) {
            fields[1] = hour;
          }
          if (fields.length >= 3) {
            fields[2] = min;
          }
          if (fields.length >= 4) {
            fields[3] = sec;
          }
        }
        return true;
      }
    }
    return false;
  }

  /**
   * Creates a custom zone for the offset
   *
   * @param offset
   *            GMT offset in milliseconds
   * @return A custom TimeZone for the offset with normalized time zone id
   */
  public static TimeZone getCustomTimeZone(int offset) {
    boolean negative = false;
    int tmp = offset;
    if (offset < 0) {
      negative = true;
      tmp = -offset;
    }

    int hour, min, sec;

    if (ASSERT) {
      Assert.assrt("millis!=0", tmp % 1000 != 0);
    }
    tmp /= 1000;
    sec = tmp % 60;
    tmp /= 60;
    min = tmp % 60;
    hour = tmp / 60;

    // Note: No millisecond part included in TZID for now
    String zid = formatCustomID(hour, min, sec, negative);

    return new SimpleTimeZone(offset, zid);
  }

  /*
   * Returns the normalized custom TimeZone ID
   */
  static String formatCustomID(int hour, int min, int sec, boolean negative) {
    // Create normalized time zone ID - GMT[+|-]hh:mm[:ss]
    StringBuilder zid = new StringBuilder(kCUSTOM_TZ_PREFIX);
    if (hour != 0 || min != 0) {
      if (negative) {
        zid.append('-');
      } else {
        zid.append('+');
      }
      // Always use US-ASCII digits
      if (hour < 10) {
        zid.append('0');
      }
      zid.append(hour);
      zid.append(':');
      if (min < 10) {
        zid.append('0');
      }
      zid.append(min);

      if (sec != 0) {
        // Optional second field
        zid.append(':');
        if (sec < 10) {
          zid.append('0');
        }
        zid.append(sec);
      }
    }
    return zid.toString();
  }

  /**
   * Returns the time zone's short ID for the zone. For example, "uslax" for zone "America/Los_Angeles".
   *
   * @param tz
   *            the time zone
   * @return the short ID of the time zone, or null if the short ID is not available.
   */
  public static String getShortID(TimeZone tz) {
    String canonicalID = null;

    if (tz instanceof OlsonTimeZone) {
      canonicalID = ((OlsonTimeZone) tz).getCanonicalID();
    }
    canonicalID = getCanonicalCLDRID(tz.getID());
    if (canonicalID == null) {
      return null;
    }
    return getShortIDFromCanonical(canonicalID);
  }

  /**
   * Returns the time zone's short ID for the zone ID. For example, "uslax" for zone ID "America/Los_Angeles".
   *
   * @param id
   *            the time zone ID
   * @return the short ID of the time zone ID, or null if the short ID is not available.
   */
  public static String getShortID(String id) {
    String canonicalID = getCanonicalCLDRID(id);
    if (canonicalID == null) {
      return null;
    }
    return getShortIDFromCanonical(canonicalID);
  }

  private static String getShortIDFromCanonical(String canonicalID) {
    String shortID = null;
    String tzidKey = canonicalID.replace('/', ':');

    try {
      // First, try check if the given ID is canonical
      UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "keyTypeData",
          ICUResourceBundle.ICU_DATA_CLASS_LOADER);
      UResourceBundle typeMap = keyTypeData.get("typeMap");
      UResourceBundle typeKeys = typeMap.get("timezone");
      shortID = typeKeys.getString(tzidKey);
    } catch (MissingResourceException e) {
      // fall through
    }

    return shortID;
  }

}
TOP

Related Classes of com.ibm.icu.impl.ZoneMeta$CustomTimeZoneCache

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.