| /* GENERATED SOURCE. DO NOT MODIFY. */ |
| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ********************************************************************** |
| * Copyright (c) 2003-2016 International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ********************************************************************** |
| * Author: Alan Liu |
| * Created: September 4 2003 |
| * Since: ICU 2.8 |
| ********************************************************************** |
| */ |
| package android.icu.impl; |
| |
| import java.lang.ref.SoftReference; |
| import java.util.Collections; |
| import java.util.Locale; |
| import java.util.MissingResourceException; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import android.icu.util.Output; |
| import android.icu.util.SimpleTimeZone; |
| import android.icu.util.TimeZone; |
| import android.icu.util.TimeZone.SystemTimeZoneType; |
| import android.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 |
| * @hide Only a subset of ICU is exposed in Android |
| */ |
| 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[] 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<>(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[] 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<>(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[] 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<>(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<>(); |
| 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( |
| ICUData.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER); |
| ZONEIDS = top.getStringArray(kNAMES); |
| } 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<>(); |
| private static ICUCache<String, String> REGION_CACHE = new SimpleCache<>(); |
| private static ICUCache<String, Boolean> SINGLE_COUNTRY_CACHE = new SimpleCache<>(); |
| |
| 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(ICUData.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(ICUData.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; |
| } |
| |
| /** |
| * Returns primary IANA zone ID for the input zone ID. When input zone ID |
| * is not known, this method returns null. |
| * |
| * @param tzid An input zone ID. |
| * @return A primary IANA zone ID equivalent to the input zone ID. |
| */ |
| public static String getIanaID(String tzid) { |
| // First, get CLDR canonical ID |
| String canonicalID = getCanonicalCLDRID(tzid); |
| if (canonicalID == null) { |
| return null; |
| } |
| // Find IANA mapping if any. |
| UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, |
| "keyTypeData", ICUResourceBundle.ICU_DATA_CLASS_LOADER); |
| UResourceBundle ianaMap = keyTypeData.get("ianaMap"); |
| UResourceBundle ianaTzMap = ianaMap.get("timezone"); |
| try { |
| return ianaTzMap.getString(canonicalID.replace('/', ':')); |
| } catch (MissingResourceException e) { |
| // No IANA zone ID mapping. In this case, ianaId set by getCanonicalCLDRID() |
| // is also a primary IANA id. |
| return canonicalID; |
| } |
| } |
| |
| /** |
| * 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( |
| ICUData.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 = 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(ICUData.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( |
| ICUData.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 android.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(ICUData.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 OlsonTimeZone 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 android.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 SimpleTimeZone 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 = 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) { |
| if (id != null && id.length() > kGMT_ID.length() && |
| id.substring(0, 3).equalsIgnoreCase(kGMT_ID)) { |
| int sign = 1; |
| int hour = 0; |
| int min = 0; |
| int sec = 0; |
| |
| int[] pos = new int[1]; |
| pos[0] = kGMT_ID.length(); |
| if (id.charAt(pos[0]) == 0x002D /*'-'*/) { |
| sign = -1; |
| } else if (id.charAt(pos[0]) != 0x002B /*'+'*/) { |
| return false; |
| } |
| pos[0]++; |
| int start = pos[0]; |
| hour = Utility.parseNumber(id, pos, 10); |
| if (pos[0] == id.length()) { |
| // Handle the following cases |
| // HHmmss |
| // Hmmss |
| // HHmm |
| // Hmm |
| // HH |
| // H |
| |
| // Get all digits |
| // Should be 1 to 6 digits. |
| int length = pos[0] - start; |
| switch (length) { |
| case 1: // H |
| case 2: // HH |
| // already set to hour |
| break; |
| case 3: // Hmm |
| case 4: // HHmm |
| min = hour % 100; |
| hour /= 100; |
| break; |
| case 5: // Hmmss |
| case 6: // HHmmss |
| sec = hour % 100; |
| min = (hour/100) % 100; |
| hour /= 10000; |
| break; |
| default: |
| // invalid range |
| return false; |
| } |
| } else { |
| // Handle the following cases |
| // HH:mm:ss |
| // H:mm:ss |
| // HH:mm |
| // H:mm |
| if (pos[0] - start < 1 || pos[0] - start > 2 || id.charAt(pos[0]) != 0x003A /*':'*/) { |
| return false; |
| } |
| pos[0]++; // skip : after H |
| if (id.length() == pos[0]) { |
| return false; |
| } |
| start = pos[0]; |
| min = Utility.parseNumber(id, pos, 10); |
| if (pos[0] - start != 2) { |
| return false; |
| } |
| if (id.length() > pos[0]) { |
| if (id.charAt(pos[0]) != 0x003A /*':'*/) { |
| return false; |
| } |
| pos[0]++; // skip : after mm |
| start = pos[0]; |
| sec = Utility.parseNumber(id, pos, 10); |
| if (pos[0] - start != 2 || id.length() > pos[0]) { |
| return false; |
| } |
| } |
| } |
| |
| 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 SimpleTimeZone 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(); |
| } |
| else { |
| 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(ICUData.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; |
| } |
| |
| } |