blob: 763f99a0b66f0c8511d7348fe3b2d1c5e7ac956a [file] [log] [blame]
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
/*
**********************************************************************
* 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 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(
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<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(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;
}
/**
* 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 = 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(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 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(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 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 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 = 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 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();
}
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;
}
}