2 **********************************************************************
\r
3 * Copyright (c) 2003-2009 International Business Machines
\r
4 * Corporation and others. All Rights Reserved.
\r
5 **********************************************************************
\r
7 * Created: September 4 2003
\r
9 **********************************************************************
\r
11 package com.ibm.icu.impl;
\r
13 import java.lang.ref.SoftReference;
\r
14 import java.text.ParsePosition;
\r
15 import java.util.Enumeration;
\r
16 import java.util.HashMap;
\r
17 import java.util.HashSet;
\r
18 import java.util.LinkedList;
\r
19 import java.util.List;
\r
20 import java.util.Map;
\r
21 import java.util.MissingResourceException;
\r
22 import java.util.Set;
\r
24 import com.ibm.icu.text.MessageFormat;
\r
25 import com.ibm.icu.text.NumberFormat;
\r
26 import com.ibm.icu.util.SimpleTimeZone;
\r
27 import com.ibm.icu.util.TimeZone;
\r
28 import com.ibm.icu.util.ULocale;
\r
29 import com.ibm.icu.util.UResourceBundle;
\r
30 import com.ibm.icu.util.UResourceBundleIterator;
\r
33 * This class, not to be instantiated, implements the meta-data
\r
34 * missing from the underlying core JDK implementation of time zones.
\r
35 * There are two missing features: Obtaining a list of available zones
\r
36 * for a given country (as defined by the Olson database), and
\r
37 * obtaining a list of equivalent zones for a given zone (as defined
\r
40 * This class uses a data class, ZoneMetaData, which is created by the
\r
46 public final class ZoneMeta {
\r
47 private static final boolean ASSERT = false;
\r
50 * Returns a String array containing all system TimeZone IDs
\r
51 * associated with the given country. These IDs may be passed to
\r
52 * <code>TimeZone.getTimeZone()</code> to construct the
\r
53 * corresponding TimeZone object.
\r
54 * @param country a two-letter ISO 3166 country code, or <code>null</code>
\r
55 * to return zones not associated with any country
\r
56 * @return an array of IDs for system TimeZones in the given
\r
57 * country. If there are none, return a zero-length array.
\r
59 public static synchronized String[] getAvailableIDs(String country) {
\r
60 if(!getOlsonMeta()){
\r
64 UResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
65 UResourceBundle regions = top.get(kREGIONS);
\r
66 UResourceBundle names = top.get(kNAMES); // dereference Zones section
\r
67 UResourceBundle temp = regions.get(country);
\r
68 int[] vector = temp.getIntVector();
\r
69 if (ASSERT) Assert.assrt("vector.length>0", vector.length>0);
\r
70 String[] ret = new String[vector.length];
\r
71 for (int i=0; i<vector.length; ++i) {
\r
72 if (ASSERT) Assert.assrt("vector[i] >= 0 && vector[i] < OLSON_ZONE_COUNT",
\r
73 vector[i] >= 0 && vector[i] < OLSON_ZONE_COUNT);
\r
74 ret[i] = names.getString(vector[i]);
\r
77 }catch(MissingResourceException ex){
\r
78 //throw away the exception
\r
82 public static synchronized String[] getAvailableIDs() {
\r
83 if(!getOlsonMeta()){
\r
87 UResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
88 UResourceBundle names = top.get(kNAMES); // dereference Zones section
\r
89 return names.getStringArray();
\r
90 }catch(MissingResourceException ex){
\r
91 //throw away the exception
\r
95 public static synchronized String[] getAvailableIDs(int offset){
\r
96 if(!getOlsonMeta()){
\r
99 LinkedList vector = new LinkedList();
\r
100 for (int i=0; i<OLSON_ZONE_COUNT; ++i) {
\r
102 if ((unistr=getID(i))!=null) {
\r
103 // This is VERY inefficient.
\r
104 TimeZone z = TimeZone.getTimeZone(unistr);
\r
105 // Make sure we get back the ID we wanted (if the ID is
\r
106 // invalid we get back GMT).
\r
107 if (z != null && z.getID().equals(unistr) &&
\r
108 z.getRawOffset() == offset) {
\r
109 vector.add(unistr);
\r
113 if(!vector.isEmpty()){
\r
114 String[] strings = new String[vector.size()];
\r
115 return (String[])vector.toArray(strings);
\r
119 private static String getID(int i) {
\r
121 UResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
122 UResourceBundle names = top.get(kNAMES); // dereference Zones section
\r
123 return names.getString(i);
\r
124 }catch(MissingResourceException ex){
\r
125 //throw away the exception
\r
130 * Returns the number of IDs in the equivalency group that
\r
131 * includes the given ID. An equivalency group contains zones
\r
132 * that behave identically to the given zone.
\r
134 * <p>If there are no equivalent zones, then this method returns
\r
135 * 0. This means either the given ID is not a valid zone, or it
\r
136 * is and there are no other equivalent zones.
\r
137 * @param id a system time zone ID
\r
138 * @return the number of zones in the equivalency group containing
\r
139 * 'id', or zero if there are no equivalent zones.
\r
140 * @see #getEquivalentID
\r
142 public static synchronized int countEquivalentIDs(String id) {
\r
144 UResourceBundle res = openOlsonResource(id);
\r
145 int size = res.getSize();
\r
146 if (size == 4 || size == 6) {
\r
147 UResourceBundle r=res.get(size-1);
\r
148 //result = ures_getSize(&r); // doesn't work
\r
149 int[] v = r.getIntVector();
\r
156 * Returns an ID in the equivalency group that includes the given
\r
157 * ID. An equivalency group contains zones that behave
\r
158 * identically to the given zone.
\r
160 * <p>The given index must be in the range 0..n-1, where n is the
\r
161 * value returned by <code>countEquivalentIDs(id)</code>. For
\r
162 * some value of 'index', the returned value will be equal to the
\r
163 * given id. If the given id is not a valid system time zone, or
\r
164 * if 'index' is out of range, then returns an empty string.
\r
165 * @param id a system time zone ID
\r
166 * @param index a value from 0 to n-1, where n is the value
\r
167 * returned by <code>countEquivalentIDs(id)</code>
\r
168 * @return the ID of the index-th zone in the equivalency group
\r
169 * containing 'id', or an empty string if 'id' is not a valid
\r
170 * system ID or 'index' is out of range
\r
171 * @see #countEquivalentIDs
\r
173 public static synchronized String getEquivalentID(String id, int index) {
\r
175 UResourceBundle res = openOlsonResource(id);
\r
178 int size = res.getSize();
\r
179 if (size == 4 || size == 6) {
\r
180 UResourceBundle r = res.get(size-1);
\r
181 int[] v = r.getIntVector();
\r
182 if (index >= 0 && index < v.length) {
\r
188 UResourceBundle top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo",
\r
189 ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
190 UResourceBundle ares = top.get(kNAMES); // dereference Zones section
\r
191 result = ares.getString(zone);
\r
192 } catch (MissingResourceException e) {
\r
200 private static String[] getCanonicalInfo(String id) {
\r
201 if (id == null || id.length() == 0) {
\r
204 if (canonicalMap == null) {
\r
205 Map m = new HashMap();
\r
206 Set s = new HashSet();
\r
208 UResourceBundle supplementalDataBundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "supplementalData", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
210 UResourceBundle zoneFormatting = supplementalDataBundle.get("zoneFormatting");
\r
211 UResourceBundleIterator it = zoneFormatting.getIterator();
\r
213 while ( it.hasNext()) {
\r
214 UResourceBundle temp = it.next();
\r
215 int resourceType = temp.getType();
\r
217 switch(resourceType) {
\r
218 case UResourceBundle.TABLE:
\r
219 String [] result = { "", "" };
\r
220 UResourceBundle zoneInfo = temp;
\r
221 String canonicalID = zoneInfo.getKey().replace(':','/');
\r
222 String territory = zoneInfo.get("territory").getString();
\r
223 result[0] = canonicalID;
\r
224 if ( territory.equals("001")) {
\r
228 result[1] = territory;
\r
230 m.put(canonicalID,result);
\r
232 UResourceBundle aliasBundle = zoneInfo.get("aliases");
\r
233 String [] aliases = aliasBundle.getStringArray();
\r
234 for (int i=0 ; i<aliases.length; i++) {
\r
235 m.put(aliases[i],result);
\r
237 } catch(MissingResourceException ex){
\r
238 // Disregard if there are no aliases
\r
241 case UResourceBundle.ARRAY:
\r
242 String[] territoryList = temp.getStringArray();
\r
243 for (int i=0 ; i < territoryList.length; i++) {
\r
244 s.add(territoryList[i]);
\r
249 } catch (MissingResourceException e) {
\r
250 // throws away the exception - maps are empty for this case
\r
253 // Some available Olson zones are not included in CLDR data (such as Asia/Riyadh87).
\r
254 // Also, when we update Olson tzdata, new zones may be added.
\r
255 // This code scans all available zones in zoneinfo.res, and if any of them are
\r
256 // missing, add them to the map.
\r
258 UResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,
\r
259 "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
260 UResourceBundle names = top.get(kNAMES);
\r
261 String[] ids = names.getStringArray();
\r
262 for (int i = 0; i < ids.length; i++) {
\r
263 if (m.containsKey(ids[i])) {
\r
264 // Already included in CLDR data
\r
267 // Not in CLDR data, but it could be new one whose alias is
\r
268 // available in CLDR
\r
269 String[] tmpinfo = null;
\r
270 int nTzdataEquivalent = TimeZone.countEquivalentIDs(ids[i]);
\r
271 for (int j = 0; j < nTzdataEquivalent; j++) {
\r
272 String alias = TimeZone.getEquivalentID(ids[i], j);
\r
273 if (alias.equals(ids[i])) {
\r
276 tmpinfo = (String[])m.get(alias);
\r
277 if (tmpinfo != null) {
\r
281 if (tmpinfo == null) {
\r
282 // Set dereferenced zone ID as the canonical ID
\r
283 UResourceBundle res = getZoneByName(top, ids[i]);
\r
284 String derefID = (res.getSize() == 1) ? ids[res.getInt()] : ids[i];
\r
285 m.put(ids[i], new String[] {derefID, null});
\r
287 // Use the canonical ID in the existing entry
\r
288 m.put(ids[i], tmpinfo);
\r
291 } catch (MissingResourceException ex) {
\r
292 //throw away the exception
\r
295 synchronized (ZoneMeta.class) {
\r
297 multiZoneTerritories = s;
\r
301 return (String[])canonicalMap.get(id);
\r
304 private static Map canonicalMap = null;
\r
305 private static Set multiZoneTerritories = null;
\r
308 * Return the canonical id for this system tzid, which might be the id itself.
\r
309 * If the given system tzid is not know, return null.
\r
311 public static String getCanonicalSystemID(String tzid) {
\r
312 String[] info = getCanonicalInfo(tzid);
\r
313 if (info != null) {
\r
320 * Return the canonical country code for this tzid. If we have none, or if the time zone
\r
321 * is not associated with a country, return null.
\r
323 public static String getCanonicalCountry(String tzid) {
\r
324 String[] info = getCanonicalInfo(tzid);
\r
325 if (info != null) {
\r
332 * Return the country code if this is a 'single' time zone that can fallback to just
\r
333 * the country, otherwise return null. (Note, one must also check the locale data
\r
334 * to see that there is a localization for the country in order to implement
\r
335 * tr#35 appendix J step 5.)
\r
337 public static String getSingleCountry(String tzid) {
\r
338 String[] info = getCanonicalInfo(tzid);
\r
339 if (info != null && info[1] != null && !multiZoneTerritories.contains(info[1])) {
\r
346 * Returns a time zone location(region) format string defined by UTR#35.
\r
347 * e.g. "Italy Time", "United States (Los Angeles) Time"
\r
349 public static String getLocationFormat(String tzid, String city, ULocale locale) {
\r
350 String[] info = getCanonicalInfo(tzid);
\r
351 if (info == null) {
\r
352 return null; // error
\r
355 String country_code = info[1];
\r
356 if (country_code == null) {
\r
357 return null; // error!
\r
360 String country = null;
\r
361 if (country_code != null) {
\r
363 ICUResourceBundle rb =
\r
364 (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
\r
366 // TODO: There is a design bug in UResourceBundle and getLoadingStatus() does not work well.
\r
368 // if (rb.getLoadingStatus() != ICUResourceBundle.FROM_ROOT && rb.getLoadingStatus() != ICUResourceBundle.FROM_DEFAULT) {
\r
369 // country = ULocale.getDisplayCountry("xx_" + country_code, locale);
\r
371 // START WORKAROUND
\r
372 ULocale rbloc = rb.getULocale();
\r
373 if (!rbloc.equals(ULocale.ROOT) && rbloc.getLanguage().equals(locale.getLanguage())) {
\r
374 country = ULocale.getDisplayCountry("xx_" + country_code, locale);
\r
377 } catch (MissingResourceException e) {
\r
380 if (country == null || country.length() == 0) {
\r
381 country = country_code;
\r
385 // This is not behavior specified in tr35, but behavior added by Mark.
\r
386 // TR35 says to display the country _only_ if there is a localization.
\r
387 if (getSingleCountry(tzid) != null) { // single country
\r
388 String regPat = getTZLocalizationInfo(locale, REGION_FORMAT);
\r
389 if (regPat == null) {
\r
390 regPat = DEF_REGION_FORMAT;
\r
392 MessageFormat mf = new MessageFormat(regPat);
\r
393 return mf.format(new Object[] { country });
\r
396 if (city == null) {
\r
397 city = tzid.substring(tzid.lastIndexOf('/')+1).replace('_',' ');
\r
400 String flbPat = getTZLocalizationInfo(locale, FALLBACK_FORMAT);
\r
401 if (flbPat == null) {
\r
402 flbPat = DEF_FALLBACK_FORMAT;
\r
404 MessageFormat mf = new MessageFormat(flbPat);
\r
406 return mf.format(new Object[] { city, country });
\r
409 private static final String DEF_REGION_FORMAT = "{0}";
\r
410 private static final String DEF_FALLBACK_FORMAT = "{1} ({0})";
\r
412 public static final String
\r
413 HOUR = "hourFormat",
\r
415 REGION_FORMAT = "regionFormat",
\r
416 FALLBACK_FORMAT = "fallbackFormat",
\r
417 ZONE_STRINGS = "zoneStrings",
\r
418 FORWARD_SLASH = "/";
\r
421 * Get the index'd tz datum for this locale. Index must be one of the
\r
422 * values PREFIX, HOUR, GMT, REGION_FORMAT, FALLBACK_FORMAT
\r
424 public static String getTZLocalizationInfo(ULocale locale, String format) {
\r
425 String result = null;
\r
427 ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(locale);
\r
428 result = bundle.getStringWithFallback(ZONE_STRINGS+FORWARD_SLASH+format);
\r
429 } catch (MissingResourceException e) {
\r
435 // private static Set getValidIDs() {
\r
436 // // Construct list of time zones that are valid, according
\r
437 // // to the current underlying core JDK. We have to do this
\r
438 // // at runtime since we don't know what we're running on.
\r
439 // Set valid = new TreeSet();
\r
440 // valid.addAll(Arrays.asList(java.util.TimeZone.getAvailableIDs()));
\r
445 * Empty string array.
\r
447 private static final String[] EMPTY = new String[0];
\r
452 * Given an ID, open the appropriate resource for the given time zone.
\r
453 * Dereference aliases if necessary.
\r
454 * @param id zone id
\r
455 * @return top-level resource bundle
\r
457 public static UResourceBundle openOlsonResource(String id)
\r
459 UResourceBundle res = null;
\r
461 ICUResourceBundle top = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
462 res = getZoneByName(top, id);
\r
463 // Dereference if this is an alias. Docs say result should be 1
\r
464 // but it is 0 in 2.8 (?).
\r
465 if (res.getSize() <= 1) {
\r
466 int deref = res.getInt() + 0;
\r
467 UResourceBundle ares = top.get(kZONES); // dereference Zones section
\r
468 res = (ICUResourceBundle) ares.get(deref);
\r
470 } catch (MissingResourceException e) {
\r
477 * Fetch a specific zone by name. Replaces the getByKey call.
\r
478 * @param top Top timezone resource
\r
479 * @param id Time zone ID
\r
480 * @return the zone's bundle if found, or undefined if error. Reuses oldbundle.
\r
482 private static UResourceBundle getZoneByName(UResourceBundle top, String id) throws MissingResourceException {
\r
483 // load the Rules object
\r
484 UResourceBundle tmp = top.get(kNAMES);
\r
486 // search for the string
\r
487 int idx = findInStringArray(tmp, id);
\r
491 throw new MissingResourceException(kNAMES, ((ICUResourceBundle)tmp).getResPath(), id);
\r
492 //ures_close(oldbundle);
\r
493 //oldbundle = NULL;
\r
495 tmp = top.get(kZONES); // get Zones object from top
\r
496 tmp = tmp.get(idx); // get nth Zone object
\r
500 private static int findInStringArray(UResourceBundle array, String id){
\r
502 int limit = array.getSize();
\r
505 int lastMid = Integer.MAX_VALUE;
\r
510 mid = (int)((start + limit) / 2);
\r
511 if (lastMid == mid) { /* Have we moved? */
\r
512 break; /* We haven't moved, and it wasn't found. */
\r
515 u = array.getString(mid);
\r
519 int r = id.compareTo(u);
\r
530 private static final String kREGIONS = "Regions";
\r
531 private static final String kZONES = "Zones";
\r
532 private static final String kNAMES = "Names";
\r
533 private static final String kGMT_ID = "GMT";
\r
534 private static final String kCUSTOM_TZ_PREFIX = "GMT";
\r
535 private static ICUCache zoneCache = new SimpleCache();
\r
537 * The Olson data is stored the "zoneinfo" resource bundle.
\r
538 * Sub-resources are organized into three ranges of data: Zones, final
\r
539 * rules, and country tables. There is also a meta-data resource
\r
540 * which has 3 integers: The number of zones, rules, and countries,
\r
541 * respectively. The country count includes the non-country 'Default'.
\r
543 static int OLSON_ZONE_START = -1; // starting index of zones
\r
544 static int OLSON_ZONE_COUNT = 0; // count of zones
\r
547 * Given a pointer to an open "zoneinfo" resource, load up the Olson
\r
548 * meta-data. Return true if successful.
\r
550 private static boolean getOlsonMeta(ICUResourceBundle top) {
\r
551 if (OLSON_ZONE_START < 0 && top != null) {
\r
553 UResourceBundle res = top.get(kZONES);
\r
554 OLSON_ZONE_COUNT = res.getSize();
\r
555 OLSON_ZONE_START = 0;
\r
556 } catch (MissingResourceException e) {
\r
557 // throws away the exception
\r
560 return (OLSON_ZONE_START >= 0);
\r
564 * Load up the Olson meta-data. Return true if successful.
\r
566 private static boolean getOlsonMeta() {
\r
567 if (OLSON_ZONE_START < 0) {
\r
569 ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
571 } catch (MissingResourceException e) {
\r
572 // throws away the exception
\r
575 return (OLSON_ZONE_START >= 0);
\r
579 * Lookup the given name in our system zone table. If found,
\r
580 * instantiate a new zone of that name and return it. If not
\r
583 public static TimeZone getSystemTimeZone(String id) {
\r
584 TimeZone z = (TimeZone)zoneCache.get(id);
\r
587 UResourceBundle top = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
588 UResourceBundle res = openOlsonResource(id);
\r
589 z = new OlsonTimeZone(top, res);
\r
591 zoneCache.put(id, z);
\r
592 }catch(Exception ex){
\r
596 return (TimeZone)z.clone();
\r
599 public static TimeZone getGMT(){
\r
600 TimeZone z = new SimpleTimeZone(0, kGMT_ID);
\r
605 // Maximum value of valid custom time zone hour/min
\r
606 private static final int kMAX_CUSTOM_HOUR = 23;
\r
607 private static final int kMAX_CUSTOM_MIN = 59;
\r
608 private static final int kMAX_CUSTOM_SEC = 59;
\r
611 * Parse a custom time zone identifier and return a corresponding zone.
\r
612 * @param id a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or
\r
614 * @return a newly created SimpleTimeZone with the given offset and
\r
615 * no Daylight Savings Time, or null if the id cannot be parsed.
\r
617 public static TimeZone getCustomTimeZone(String id){
\r
618 int[] fields = new int[4];
\r
619 if (parseCustomID(id, fields)) {
\r
620 String zid = formatCustomID(fields[1], fields[2], fields[3], fields[0] < 0);
\r
621 int offset = fields[0] * ((fields[1] * 60 + fields[2]) * 60 + fields[3]) * 1000;
\r
622 return new SimpleTimeZone(offset, zid);
\r
628 * Parse a custom time zone identifier and return the normalized
\r
629 * custom time zone identifier for the given custom id string.
\r
630 * @param id a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or
\r
632 * @return The normalized custom id string.
\r
634 public static String getCustomID(String id) {
\r
635 int[] fields = new int[4];
\r
636 if (parseCustomID(id, fields)) {
\r
637 return formatCustomID(fields[1], fields[2], fields[3], fields[0] < 0);
\r
643 * Parses the given custom time zone identifier
\r
644 * @param id id A string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or
\r
646 * @param fields An array of int (length = 4) to receive the parsed
\r
647 * offset time fields. The sign is set to fields[0] (-1 or 1),
\r
648 * hour is set to fields[1], minute is set to fields[2] and second is
\r
649 * set to fields[3].
\r
650 * @return Returns true when the given custom id is valid.
\r
652 static boolean parseCustomID(String id, int[] fields) {
\r
653 NumberFormat numberFormat = null;
\r
654 String idUppercase = id.toUpperCase();
\r
656 if (id != null && id.length() > kGMT_ID.length() &&
\r
657 idUppercase.startsWith(kGMT_ID)) {
\r
658 ParsePosition pos = new ParsePosition(kGMT_ID.length());
\r
664 if (id.charAt(pos.getIndex()) == 0x002D /*'-'*/) {
\r
666 } else if (id.charAt(pos.getIndex()) != 0x002B /*'+'*/) {
\r
669 pos.setIndex(pos.getIndex() + 1);
\r
671 numberFormat = NumberFormat.getInstance();
\r
672 numberFormat.setParseIntegerOnly(true);
\r
674 // Look for either hh:mm, hhmm, or hh
\r
675 int start = pos.getIndex();
\r
677 Number n = numberFormat.parse(id, pos);
\r
678 if (pos.getIndex() == start) {
\r
681 hour = n.intValue();
\r
683 if (pos.getIndex() < id.length()){
\r
684 if (pos.getIndex() - start > 2
\r
685 || id.charAt(pos.getIndex()) != 0x003A /*':'*/) {
\r
689 pos.setIndex(pos.getIndex() + 1);
\r
690 int oldPos = pos.getIndex();
\r
691 n = numberFormat.parse(id, pos);
\r
692 if ((pos.getIndex() - oldPos) != 2) {
\r
693 // must be 2 digits
\r
696 min = n.intValue();
\r
697 if (pos.getIndex() < id.length()) {
\r
698 if (id.charAt(pos.getIndex()) != 0x003A /*':'*/) {
\r
702 pos.setIndex(pos.getIndex() + 1);
\r
703 oldPos = pos.getIndex();
\r
704 n = numberFormat.parse(id, pos);
\r
705 if (pos.getIndex() != id.length()
\r
706 || (pos.getIndex() - oldPos) != 2) {
\r
709 sec = n.intValue();
\r
712 // Supported formats are below -
\r
721 int length = pos.getIndex() - start;
\r
722 if (length <= 0 || 6 < length) {
\r
729 // already set to hour
\r
739 min = (hour/100) % 100;
\r
745 if (hour <= kMAX_CUSTOM_HOUR && min <= kMAX_CUSTOM_MIN && sec <= kMAX_CUSTOM_SEC) {
\r
746 if (fields != null) {
\r
747 if (fields.length >= 1) {
\r
750 if (fields.length >= 2) {
\r
753 if (fields.length >= 3) {
\r
756 if (fields.length >= 4) {
\r
767 * Creates a custom zone for the offset
\r
768 * @param offset GMT offset in milliseconds
\r
769 * @return A custom TimeZone for the offset with normalized time zone id
\r
771 public static TimeZone getCustomTimeZone(int offset) {
\r
772 boolean negative = false;
\r
779 int hour, min, sec, millis;
\r
781 millis = tmp % 1000;
\r
783 Assert.assrt("millis!=0", millis != 0);
\r
791 // Note: No millisecond part included in TZID for now
\r
792 String zid = formatCustomID(hour, min, sec, negative);
\r
794 return new SimpleTimeZone(offset, zid);
\r
798 * Returns the normalized custom TimeZone ID
\r
800 static String formatCustomID(int hour, int min, int sec, boolean negative) {
\r
801 // Create normalized time zone ID - GMT[+|-]hh:mm[:ss]
\r
802 StringBuffer zid = new StringBuffer(kCUSTOM_TZ_PREFIX);
\r
803 if (hour != 0 || min != 0) {
\r
809 // Always use US-ASCII digits
\r
821 // Optional second field
\r
829 return zid.toString();
\r
832 private static SoftReference OLSON_TO_META_REF;
\r
833 private static SoftReference META_TO_OLSON_REF;
\r
835 static class OlsonToMetaMappingEntry {
\r
841 private static class MetaToOlsonMappingEntry {
\r
846 static Map getOlsonToMetaMap() {
\r
847 Map olsonToMeta = null;
\r
848 synchronized(ZoneMeta.class) {
\r
849 if (OLSON_TO_META_REF != null) {
\r
850 olsonToMeta = (HashMap)OLSON_TO_META_REF.get();
\r
852 if (olsonToMeta == null) {
\r
853 olsonToMeta = createOlsonToMetaMap();
\r
854 if (olsonToMeta == null) {
\r
855 // We need to return non-null Map to avoid disaster
\r
856 olsonToMeta = new HashMap();
\r
858 OLSON_TO_META_REF = new SoftReference(olsonToMeta);
\r
861 return olsonToMeta;
\r
865 * Create olson tzid to metazone mappings from metazoneInfo.res (3.8.1 or later)
\r
867 private static Map createOlsonToMetaMap() {
\r
868 // Create olson id to metazone mapping table
\r
869 HashMap olsonToMeta = null;
\r
870 UResourceBundle metazoneMappingsBundle = null;
\r
872 UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metazoneInfo");
\r
873 metazoneMappingsBundle = bundle.get("metazoneMappings");
\r
874 } catch (MissingResourceException mre) {
\r
877 if (metazoneMappingsBundle != null) {
\r
878 String[] tzids = getAvailableIDs();
\r
879 for (int i = 0; i < tzids.length; i++) {
\r
881 String canonicalID = TimeZone.getCanonicalID(tzids[i]);
\r
882 if (canonicalID == null || !tzids[i].equals(canonicalID)) {
\r
885 String tzkey = tzids[i].replace('/', ':');
\r
887 UResourceBundle zoneBundle = metazoneMappingsBundle.get(tzkey);
\r
888 LinkedList mzMappings = new LinkedList();
\r
889 for (int idx = 0; ; idx++) {
\r
891 UResourceBundle mz = zoneBundle.get("mz" + idx);
\r
892 String[] mzstr = mz.getStringArray();
\r
893 if (mzstr == null || mzstr.length != 3) {
\r
896 OlsonToMetaMappingEntry mzmap = new OlsonToMetaMappingEntry();
\r
897 mzmap.mzid = mzstr[0].intern();
\r
898 mzmap.from = parseDate(mzstr[1]);
\r
899 mzmap.to = parseDate(mzstr[2]);
\r
901 // Add this mapping to the list
\r
902 mzMappings.add(mzmap);
\r
903 } catch (MissingResourceException nomz) {
\r
906 } catch (IllegalArgumentException baddate) {
\r
910 if (mzMappings.size() != 0) {
\r
911 // Add to the olson-to-meta map
\r
912 if (olsonToMeta == null) {
\r
913 olsonToMeta = new HashMap();
\r
915 olsonToMeta.put(tzids[i], mzMappings);
\r
917 } catch (MissingResourceException noum) {
\r
918 // Does not use metazone, just skip this.
\r
922 return olsonToMeta;
\r
926 * Returns a CLDR metazone ID for the given Olson tzid and time.
\r
928 public static String getMetazoneID(String olsonID, long date) {
\r
929 String mzid = null;
\r
930 Map olsonToMeta = getOlsonToMetaMap();
\r
931 List mappings = (List)olsonToMeta.get(olsonID);
\r
932 if (mappings == null) {
\r
933 // The given ID might be an alias - try its canonical id
\r
934 String canonicalID = getCanonicalSystemID(olsonID);
\r
935 if (canonicalID != null && !canonicalID.equals(olsonID)) {
\r
936 mappings = (List)olsonToMeta.get(canonicalID);
\r
939 if (mappings != null) {
\r
940 for (int i = 0; i < mappings.size(); i++) {
\r
941 OlsonToMetaMappingEntry mzm = (OlsonToMetaMappingEntry)mappings.get(i);
\r
942 if (date >= mzm.from && date < mzm.to) {
\r
951 private static Map getMetaToOlsonMap() {
\r
952 HashMap metaToOlson = null;
\r
953 synchronized(ZoneMeta.class) {
\r
954 if (META_TO_OLSON_REF != null) {
\r
955 metaToOlson = (HashMap)META_TO_OLSON_REF.get();
\r
957 if (metaToOlson == null) {
\r
958 metaToOlson = new HashMap();
\r
959 UResourceBundle metazonesBundle = null;
\r
961 UResourceBundle supplementalBundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,
\r
962 "supplementalData");
\r
963 UResourceBundle mapTimezonesBundle = supplementalBundle.get("mapTimezones");
\r
964 metazonesBundle = mapTimezonesBundle.get("metazones");
\r
965 } catch (MissingResourceException mre) {
\r
968 if (metazonesBundle != null) {
\r
969 Enumeration mzenum = metazonesBundle.getKeys();
\r
970 while (mzenum.hasMoreElements()) {
\r
971 String mzkey = (String)mzenum.nextElement();
\r
972 if (!mzkey.startsWith("meta:")) {
\r
975 String tzid = null;
\r
977 tzid = metazonesBundle.getString(mzkey);
\r
978 } catch (MissingResourceException mre) {
\r
979 // It should not happen..
\r
981 if (tzid != null) {
\r
982 int territoryIdx = mzkey.lastIndexOf('_');
\r
983 if (territoryIdx > 0) {
\r
984 String mzid = mzkey.substring(5 /* "meta:".length() */, territoryIdx);
\r
985 String territory = mzkey.substring(territoryIdx + 1);
\r
986 List mappings = (List)metaToOlson.get(mzid);
\r
987 if (mappings == null) {
\r
988 mappings = new LinkedList();
\r
989 metaToOlson.put(mzid, mappings);
\r
991 MetaToOlsonMappingEntry olsonmap = new MetaToOlsonMappingEntry();
\r
992 olsonmap.id = tzid;
\r
993 olsonmap.territory = territory;
\r
994 mappings.add(olsonmap);
\r
999 META_TO_OLSON_REF = new SoftReference(metaToOlson);
\r
1002 return metaToOlson;
\r
1006 * Returns an Olson ID for the ginve metazone and region
\r
1008 public static String getZoneIdByMetazone(String metazoneID, String region) {
\r
1009 String tzid = null;
\r
1010 Map metaToOlson = getMetaToOlsonMap();
\r
1011 List mappings = (List)metaToOlson.get(metazoneID);
\r
1012 if (mappings != null) {
\r
1013 for (int i = 0; i < mappings.size(); i++) {
\r
1014 MetaToOlsonMappingEntry olsonmap = (MetaToOlsonMappingEntry)mappings.get(i);
\r
1015 if (olsonmap.territory.equals(region)) {
\r
1016 tzid = olsonmap.id;
\r
1018 } else if (olsonmap.territory.equals("001")) {
\r
1019 tzid = olsonmap.id;
\r
1027 // * Returns an Olson ID for the given metazone and locale
\r
1029 // public static String getZoneIdByMetazone(String metazoneID, ULocale loc) {
\r
1030 // String region = loc.getCountry();
\r
1031 // if (region.length() == 0) {
\r
1032 // // Get likely region
\r
1033 // ULocale tmp = ULocale.addLikelySubtag(loc);
\r
1034 // region = tmp.getCountry();
\r
1036 // return getZoneIdByMetazone(metazoneID, region);
\r
1040 * Convert a date string used by metazone mappings to long.
\r
1041 * The format used by CLDR metazone mapping is "yyyy-MM-dd HH:mm".
\r
1042 * We do not want to use SimpleDateFormat to parse the metazone
\r
1043 * mapping range strings in createOlsonToMeta, because it might be
\r
1044 * called from SimpleDateFormat initialization code.
\r
1046 static long parseDate (String text) throws IllegalArgumentException {
\r
1047 int year = 0, month = 0, day = 0, hour = 0, min = 0;
\r
1052 for (idx = 0; idx <= 3; idx++) {
\r
1053 n = text.charAt(idx) - '0';
\r
1054 if (n >= 0 && n < 10) {
\r
1055 year = 10*year + n;
\r
1057 throw new IllegalArgumentException("Bad year");
\r
1061 for (idx = 5; idx <= 6; idx++) {
\r
1062 n = text.charAt(idx) - '0';
\r
1063 if (n >= 0 && n < 10) {
\r
1064 month = 10*month + n;
\r
1066 throw new IllegalArgumentException("Bad month");
\r
1070 for (idx = 8; idx <= 9; idx++) {
\r
1071 n = text.charAt(idx) - '0';
\r
1072 if (n >= 0 && n < 10) {
\r
1075 throw new IllegalArgumentException("Bad day");
\r
1079 for (idx = 11; idx <= 12; idx++) {
\r
1080 n = text.charAt(idx) - '0';
\r
1081 if (n >= 0 && n < 10) {
\r
1082 hour = 10*hour + n;
\r
1084 throw new IllegalArgumentException("Bad hour");
\r
1088 for (idx = 14; idx <= 15; idx++) {
\r
1089 n = text.charAt(idx) - '0';
\r
1090 if (n >= 0 && n < 10) {
\r
1093 throw new IllegalArgumentException("Bad minute");
\r
1097 long date = Grego.fieldsToDay(year, month - 1, day) * Grego.MILLIS_PER_DAY
\r
1098 + hour * Grego.MILLIS_PER_HOUR + min * Grego.MILLIS_PER_MINUTE;
\r