| /* |
| ********************************************************************** |
| * Copyright (c) 2003-2011 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.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.US); |
| } |
| |
| // 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; |
| try { |
| UResourceBundle res = openOlsonResource(null, id); |
| 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 = ""; |
| int zoneIdx = -1; |
| |
| if (index >= 0) { |
| try { |
| UResourceBundle res = openOlsonResource(null, id); |
| UResourceBundle links = res.get("links"); |
| int[] zones = links.getIntVector(); |
| if (index < zones.length) { |
| zoneIdx = zones[index]; |
| } |
| } catch (MissingResourceException ex) { |
| // throw away |
| zoneIdx = -1; |
| } |
| } |
| 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 sysmte ID). |
| */ |
| public static String getCanonicalCLDRID(String tzid) { |
| String canonical = CANONICAL_ID_CACHE.get(tzid); |
| if (canonical == null) { |
| int zoneIdx = getZoneIndex(tzid); |
| if (zoneIdx >= 0) { |
| try { |
| 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) { |
| // resolve link |
| String tmp = getZoneID(zone.getInt()); |
| if (tmp != null) { |
| canonical = tmp; |
| } |
| } else { |
| canonical = tzid; |
| } |
| // check canonical mapping in CLDR |
| UResourceBundle keyTypeData = UResourceBundle.getBundleInstance( |
| ICUResourceBundle.ICU_BASE_NAME, "keyTypeData", ICUResourceBundle.ICU_DATA_CLASS_LOADER); |
| UResourceBundle typeAlias = keyTypeData.get("typeAlias"); |
| UResourceBundle aliasesForKey = typeAlias.get("timezone"); |
| String cldrCanonical = aliasesForKey.getString(canonical.replace('/', ':')); |
| if (cldrCanonical != null) { |
| canonical = cldrCanonical; |
| } |
| } catch (MissingResourceException e) { |
| // fall through |
| } |
| } |
| if (canonical != null) { |
| CANONICAL_ID_CACHE.put(tzid, canonical); |
| } |
| } |
| 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 country code if this is a 'single' time zone that can fallback to just |
| * the country, otherwise return null. (Note, one must also check the locale data |
| * to see that there is a localization for the country in order to implement |
| * tr#35 appendix J step 5.) |
| */ |
| public static String getSingleCountry(String tzid) { |
| String country = getCanonicalCountry(tzid); |
| if (country != null) { |
| Boolean isSingle = SINGLE_COUNTRY_CACHE.get(tzid); |
| if (isSingle == null) { |
| Set<String> ids = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL_LOCATION, country, null); |
| assert(ids.size() >= 1); |
| isSingle = Boolean.valueOf(ids.size() <= 1); |
| SINGLE_COUNTRY_CACHE.put(tzid, isSingle); |
| } |
| if (!isSingle) { |
| country = null; |
| } |
| } |
| 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; |
| } |
| |
| |
| private static ICUCache<String, TimeZone> SYSTEM_ZONE_CACHE = new SimpleCache<String, TimeZone>(); |
| |
| /** |
| * Lookup the given name in our system zone table. If found, |
| * instantiate a new zone of that name and return it. If not |
| * found, return 0. |
| */ |
| public static TimeZone getSystemTimeZone(String id) { |
| TimeZone z = SYSTEM_ZONE_CACHE.get(id); |
| if (z == null) { |
| try{ |
| UResourceBundle top = UResourceBundle.getBundleInstance( |
| ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER); |
| UResourceBundle res = openOlsonResource(top, id); |
| z = new OlsonTimeZone(top, res); |
| z.setID(id); |
| SYSTEM_ZONE_CACHE.put(id, z); |
| }catch(Exception ex){ |
| return null; |
| } |
| } |
| return (TimeZone)z.clone(); |
| } |
| |
| // 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; |
| |
| /** |
| * 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 newly created 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)) { |
| String zid = formatCustomID(fields[1], fields[2], fields[3], fields[0] < 0); |
| int offset = fields[0] * ((fields[1] * 60 + fields[2]) * 60 + fields[3]) * 1000; |
| return new SimpleTimeZone(offset, zid); |
| } |
| 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; |
| String idUppercase = id.toUpperCase(); |
| |
| if (id != null && id.length() > kGMT_ID.length() && |
| idUppercase.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, millis; |
| |
| millis = tmp % 1000; |
| if (ASSERT) { |
| Assert.assrt("millis!=0", millis != 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(); |
| } |
| } |