2 **********************************************************************
\r
3 * Copyright (c) 2003-2010 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.text.ParsePosition;
\r
14 import java.util.ArrayList;
\r
15 import java.util.HashMap;
\r
16 import java.util.LinkedList;
\r
17 import java.util.List;
\r
18 import java.util.Map;
\r
19 import java.util.MissingResourceException;
\r
20 import java.util.Set;
\r
22 import com.ibm.icu.text.MessageFormat;
\r
23 import com.ibm.icu.text.NumberFormat;
\r
24 import com.ibm.icu.util.SimpleTimeZone;
\r
25 import com.ibm.icu.util.TimeZone;
\r
26 import com.ibm.icu.util.ULocale;
\r
27 import com.ibm.icu.util.UResourceBundle;
\r
30 * This class, not to be instantiated, implements the meta-data
\r
31 * missing from the underlying core JDK implementation of time zones.
\r
32 * There are two missing features: Obtaining a list of available zones
\r
33 * for a given country (as defined by the Olson database), and
\r
34 * obtaining a list of equivalent zones for a given zone (as defined
\r
37 * This class uses a data class, ZoneMetaData, which is created by the
\r
43 public final class ZoneMeta {
\r
44 private static final boolean ASSERT = false;
\r
46 private static final String ZONEINFORESNAME = "zoneinfo64";
\r
47 private static final String kREGIONS = "Regions";
\r
48 private static final String kZONES = "Zones";
\r
49 private static final String kNAMES = "Names";
\r
51 private static final String kGMT_ID = "GMT";
\r
52 private static final String kCUSTOM_TZ_PREFIX = "GMT";
\r
55 * Returns a String array containing all system TimeZone IDs
\r
56 * associated with the given country. These IDs may be passed to
\r
57 * <code>TimeZone.getTimeZone()</code> to construct the
\r
58 * corresponding TimeZone object.
\r
59 * @param country a two-letter ISO 3166 country code, or <code>null</code>
\r
60 * to return zones not associated with any country
\r
61 * @return an array of IDs for system TimeZones in the given
\r
62 * country. If there are none, return a zero-length array.
\r
64 public static synchronized String[] getAvailableIDs(String country) {
\r
65 String[] ids = null;
\r
68 UResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(
\r
69 ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
70 UResourceBundle regions = top.get(kREGIONS);
\r
72 // Create a list of zones associated with the country
\r
73 List<String> countryZones = new ArrayList<String>();
\r
75 for (int i = 0; i < regions.getSize(); i++) {
\r
76 if (country.equals(regions.getString(i))) {
\r
77 String zoneName = getZoneID(i);
\r
78 countryZones.add(zoneName);
\r
81 if (countryZones.size() > 0) {
\r
82 ids = countryZones.toArray(new String[countryZones.size()]);
\r
84 } catch (MissingResourceException ex){
\r
85 //throw away the exception
\r
89 ids = new String[0];
\r
94 public static synchronized String[] getAvailableIDs() {
\r
95 String[] ids = getZoneIDs();
\r
97 return new String[0];
\r
102 public static synchronized String[] getAvailableIDs(int offset){
\r
103 String[] ids = null;
\r
104 String[] all = getZoneIDs();
\r
106 ArrayList<String> zones = new ArrayList<String>();
\r
107 for (String zid : all) {
\r
108 // This is VERY inefficient.
\r
109 TimeZone z = TimeZone.getTimeZone(zid);
\r
110 // Make sure we get back the ID we wanted (if the ID is
\r
111 // invalid we get back GMT).
\r
112 if (z != null && z.getID().equals(zid) && z.getRawOffset() == offset) {
\r
116 if (zones.size() > 0) {
\r
117 ids = zones.toArray(new String[zones.size()]);
\r
121 ids = new String[0];
\r
127 * Returns the number of IDs in the equivalency group that
\r
128 * includes the given ID. An equivalency group contains zones
\r
129 * that behave identically to the given zone.
\r
131 * <p>If there are no equivalent zones, then this method returns
\r
132 * 0. This means either the given ID is not a valid zone, or it
\r
133 * is and there are no other equivalent zones.
\r
134 * @param id a system time zone ID
\r
135 * @return the number of zones in the equivalency group containing
\r
136 * 'id', or zero if there are no equivalent zones.
\r
137 * @see #getEquivalentID
\r
139 public static synchronized int countEquivalentIDs(String id) {
\r
142 UResourceBundle res = openOlsonResource(null, id);
\r
143 UResourceBundle links = res.get("links");
\r
144 int[] v = links.getIntVector();
\r
146 } catch (MissingResourceException ex) {
\r
153 * Returns an ID in the equivalency group that includes the given
\r
154 * ID. An equivalency group contains zones that behave
\r
155 * identically to the given zone.
\r
157 * <p>The given index must be in the range 0..n-1, where n is the
\r
158 * value returned by <code>countEquivalentIDs(id)</code>. For
\r
159 * some value of 'index', the returned value will be equal to the
\r
160 * given id. If the given id is not a valid system time zone, or
\r
161 * if 'index' is out of range, then returns an empty string.
\r
162 * @param id a system time zone ID
\r
163 * @param index a value from 0 to n-1, where n is the value
\r
164 * returned by <code>countEquivalentIDs(id)</code>
\r
165 * @return the ID of the index-th zone in the equivalency group
\r
166 * containing 'id', or an empty string if 'id' is not a valid
\r
167 * system ID or 'index' is out of range
\r
168 * @see #countEquivalentIDs
\r
170 public static synchronized String getEquivalentID(String id, int index) {
\r
171 String result = "";
\r
176 UResourceBundle res = openOlsonResource(null, id);
\r
177 UResourceBundle links = res.get("links");
\r
178 int[] zones = links.getIntVector();
\r
179 if (index < zones.length) {
\r
180 zoneIdx = zones[index];
\r
182 } catch (MissingResourceException ex) {
\r
187 if (zoneIdx >= 0) {
\r
188 String tmp = getZoneID(zoneIdx);
\r
196 private static String[] ZONEIDS = null;
\r
199 * ICU frequently refers the zone ID array in zoneinfo resource
\r
201 private static synchronized String[] getZoneIDs() {
\r
202 if (ZONEIDS == null) {
\r
204 UResourceBundle top = UResourceBundle.getBundleInstance(
\r
205 ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
206 UResourceBundle names = top.get(kNAMES);
\r
207 ZONEIDS = names.getStringArray();
\r
208 } catch (MissingResourceException ex) {
\r
212 if (ZONEIDS == null) {
\r
213 ZONEIDS = new String[0];
\r
218 private static String getZoneID(int idx) {
\r
220 String[] ids = getZoneIDs();
\r
221 if (idx < ids.length) {
\r
228 private static int getZoneIndex(String zid) {
\r
231 String[] all = getZoneIDs();
\r
232 if (all.length > 0) {
\r
234 int limit = all.length;
\r
236 int lastMid = Integer.MAX_VALUE;
\r
238 int mid = (start + limit) / 2;
\r
239 if (lastMid == mid) { /* Have we moved? */
\r
240 break; /* We haven't moved, and it wasn't found. */
\r
243 int r = zid.compareTo(all[mid]);
\r
259 private static ICUCache<String, String> CANONICAL_ID_CACHE = new SimpleCache<String, String>();
\r
260 private static ICUCache<String, String> REGION_CACHE = new SimpleCache<String, String>();
\r
261 private static ICUCache<String, Boolean> SINGLE_COUNTRY_CACHE = new SimpleCache<String, Boolean>();
\r
264 * Return the canonical id for this system tzid, which might be the id itself.
\r
265 * If the given system tzid is not know, return null.
\r
267 public static String getCanonicalSystemID(String tzid) {
\r
268 String canonical = CANONICAL_ID_CACHE.get(tzid);
\r
269 if (canonical == null) {
\r
270 int zoneIdx = getZoneIndex(tzid);
\r
271 if (zoneIdx >= 0) {
\r
273 UResourceBundle top = UResourceBundle.getBundleInstance(
\r
274 ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
275 UResourceBundle zones = top.get(kZONES);
\r
276 UResourceBundle zone = zones.get(zoneIdx);
\r
277 if (zone.getType() == UResourceBundle.INT) {
\r
279 String tmp = getZoneID(zone.getInt());
\r
286 // check canonical mapping in CLDR
\r
287 UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(
\r
288 ICUResourceBundle.ICU_BASE_NAME, "keyTypeData", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
289 UResourceBundle typeAlias = keyTypeData.get("typeAlias");
\r
290 UResourceBundle aliasesForKey = typeAlias.get("timezone");
\r
291 String cldrCanonical = aliasesForKey.getString(canonical.replace('/', ':'));
\r
292 if (cldrCanonical != null) {
\r
293 canonical = cldrCanonical;
\r
295 } catch (MissingResourceException e) {
\r
299 if (canonical != null) {
\r
300 CANONICAL_ID_CACHE.put(tzid, canonical);
\r
307 * Return the canonical country code for this tzid. If we have none, or if the time zone
\r
308 * is not associated with a country, return null.
\r
310 public static String getCanonicalCountry(String tzid) {
\r
311 String region = REGION_CACHE.get(tzid);
\r
312 if (region == null) {
\r
313 int zoneIdx = getZoneIndex(tzid);
\r
314 if (zoneIdx >= 0) {
\r
316 UResourceBundle top = UResourceBundle.getBundleInstance(
\r
317 ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
318 UResourceBundle regions = top.get(kREGIONS);
\r
319 if (zoneIdx < regions.getSize()) {
\r
320 region = regions.getString(zoneIdx);
\r
322 } catch (MissingResourceException e) {
\r
325 if (region != null) {
\r
326 REGION_CACHE.put(tzid, region);
\r
330 if (region.equals("001")) {
\r
337 * Return the country code if this is a 'single' time zone that can fallback to just
\r
338 * the country, otherwise return null. (Note, one must also check the locale data
\r
339 * to see that there is a localization for the country in order to implement
\r
340 * tr#35 appendix J step 5.)
\r
342 public static String getSingleCountry(String tzid) {
\r
343 String country = getCanonicalCountry(tzid);
\r
344 if (country != null) {
\r
345 Boolean isSingle = SINGLE_COUNTRY_CACHE.get(tzid);
\r
346 if (isSingle == null) {
\r
347 // This is not so efficient
\r
348 boolean isSingleCountryZone = true;
\r
349 String[] ids = TimeZone.getAvailableIDs(country);
\r
350 if (ids.length > 1) {
\r
351 // Check if there are multiple canonical zones included
\r
352 String canonical = getCanonicalSystemID(ids[0]);
\r
353 for (int i = 1; i < ids.length; i++) {
\r
354 if (!canonical.equals(getCanonicalSystemID(ids[i]))) {
\r
355 isSingleCountryZone = false;
\r
360 isSingle = Boolean.valueOf(isSingleCountryZone);
\r
361 SINGLE_COUNTRY_CACHE.put(tzid, isSingle);
\r
371 * Returns a time zone location(region) format string defined by UTR#35.
\r
372 * e.g. "Italy Time", "United States (Los Angeles) Time"
\r
374 public static String getLocationFormat(String tzid, String city, ULocale locale) {
\r
375 String country_code = getCanonicalCountry(tzid);
\r
376 if (country_code == null) {
\r
377 // no location is associated
\r
381 String country = null;
\r
383 ICUResourceBundle rb =
\r
384 (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_REGION_BASE_NAME, locale);
\r
386 // TODO: There is a design bug in UResourceBundle and getLoadingStatus() does not work well.
\r
388 // if (rb.getLoadingStatus() != ICUResourceBundle.FROM_ROOT && rb.getLoadingStatus() != ICUResourceBundle.FROM_DEFAULT) {
\r
389 // country = ULocale.getDisplayCountry("xx_" + country_code, locale);
\r
391 // START WORKAROUND
\r
392 ULocale rbloc = rb.getULocale();
\r
393 if (!rbloc.equals(ULocale.ROOT) && rbloc.getLanguage().equals(locale.getLanguage())) {
\r
394 country = ULocale.getDisplayCountry("xx_" + country_code, locale);
\r
397 } catch (MissingResourceException e) {
\r
400 if (country == null || country.length() == 0) {
\r
401 country = country_code;
\r
404 // This is not behavior specified in tr35, but behavior added by Mark.
\r
405 // TR35 says to display the country _only_ if there is a localization.
\r
406 if (getSingleCountry(tzid) != null) { // single country
\r
407 String regPat = getTZLocalizationInfo(locale, REGION_FORMAT);
\r
408 if (regPat == null) {
\r
409 regPat = DEF_REGION_FORMAT;
\r
411 MessageFormat mf = new MessageFormat(regPat);
\r
412 return mf.format(new Object[] { country });
\r
415 if (city == null) {
\r
416 city = tzid.substring(tzid.lastIndexOf('/')+1).replace('_',' ');
\r
419 String flbPat = getTZLocalizationInfo(locale, FALLBACK_FORMAT);
\r
420 if (flbPat == null) {
\r
421 flbPat = DEF_FALLBACK_FORMAT;
\r
423 MessageFormat mf = new MessageFormat(flbPat);
\r
425 return mf.format(new Object[] { city, country });
\r
428 private static final String DEF_REGION_FORMAT = "{0}";
\r
429 private static final String DEF_FALLBACK_FORMAT = "{1} ({0})";
\r
431 public static final String
\r
432 HOUR = "hourFormat",
\r
434 REGION_FORMAT = "regionFormat",
\r
435 FALLBACK_FORMAT = "fallbackFormat",
\r
436 ZONE_STRINGS = "zoneStrings",
\r
437 FORWARD_SLASH = "/";
\r
440 * Get the index'd tz datum for this locale. Index must be one of the
\r
441 * values PREFIX, HOUR, GMT, REGION_FORMAT, FALLBACK_FORMAT
\r
443 public static String getTZLocalizationInfo(ULocale locale, String format) {
\r
444 String result = null;
\r
446 ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(
\r
447 ICUResourceBundle.ICU_ZONE_BASE_NAME, locale);
\r
448 result = bundle.getStringWithFallback(ZONE_STRINGS+FORWARD_SLASH+format);
\r
449 } catch (MissingResourceException e) {
\r
455 // private static Set getValidIDs() {
\r
456 // // Construct list of time zones that are valid, according
\r
457 // // to the current underlying core JDK. We have to do this
\r
458 // // at runtime since we don't know what we're running on.
\r
459 // Set valid = new TreeSet();
\r
460 // valid.addAll(Arrays.asList(java.util.TimeZone.getAvailableIDs()));
\r
466 * Given an ID and the top-level resource of the zoneinfo resource,
\r
467 * open the appropriate resource for the given time zone.
\r
468 * Dereference links if necessary.
\r
469 * @param top the top level resource of the zoneinfo resource or null.
\r
470 * @param id zone id
\r
471 * @return the corresponding zone resource or null if not found
\r
473 public static UResourceBundle openOlsonResource(UResourceBundle top, String id)
\r
475 UResourceBundle res = null;
\r
476 int zoneIdx = getZoneIndex(id);
\r
477 if (zoneIdx >= 0) {
\r
480 top = UResourceBundle.getBundleInstance(
\r
481 ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
483 UResourceBundle zones = top.get(kZONES);
\r
484 UResourceBundle zone = zones.get(zoneIdx);
\r
485 if (zone.getType() == UResourceBundle.INT) {
\r
487 zone = zones.get(zone.getInt());
\r
490 } catch (MissingResourceException e) {
\r
498 private static ICUCache<String, TimeZone> SYSTEM_ZONE_CACHE = new SimpleCache<String, TimeZone>();
\r
501 * Lookup the given name in our system zone table. If found,
\r
502 * instantiate a new zone of that name and return it. If not
\r
505 public static TimeZone getSystemTimeZone(String id) {
\r
506 TimeZone z = SYSTEM_ZONE_CACHE.get(id);
\r
509 UResourceBundle top = UResourceBundle.getBundleInstance(
\r
510 ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
511 UResourceBundle res = openOlsonResource(top, id);
\r
512 z = new OlsonTimeZone(top, res);
\r
514 SYSTEM_ZONE_CACHE.put(id, z);
\r
515 }catch(Exception ex){
\r
519 return (TimeZone)z.clone();
\r
522 public static TimeZone getGMT(){
\r
523 TimeZone z = new SimpleTimeZone(0, kGMT_ID);
\r
528 // Maximum value of valid custom time zone hour/min
\r
529 private static final int kMAX_CUSTOM_HOUR = 23;
\r
530 private static final int kMAX_CUSTOM_MIN = 59;
\r
531 private static final int kMAX_CUSTOM_SEC = 59;
\r
534 * Parse a custom time zone identifier and return a corresponding zone.
\r
535 * @param id a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or
\r
537 * @return a newly created SimpleTimeZone with the given offset and
\r
538 * no Daylight Savings Time, or null if the id cannot be parsed.
\r
540 public static TimeZone getCustomTimeZone(String id){
\r
541 int[] fields = new int[4];
\r
542 if (parseCustomID(id, fields)) {
\r
543 String zid = formatCustomID(fields[1], fields[2], fields[3], fields[0] < 0);
\r
544 int offset = fields[0] * ((fields[1] * 60 + fields[2]) * 60 + fields[3]) * 1000;
\r
545 return new SimpleTimeZone(offset, zid);
\r
551 * Parse a custom time zone identifier and return the normalized
\r
552 * custom time zone identifier for the given custom id string.
\r
553 * @param id a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or
\r
555 * @return The normalized custom id string.
\r
557 public static String getCustomID(String id) {
\r
558 int[] fields = new int[4];
\r
559 if (parseCustomID(id, fields)) {
\r
560 return formatCustomID(fields[1], fields[2], fields[3], fields[0] < 0);
\r
566 * Parses the given custom time zone identifier
\r
567 * @param id id A string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or
\r
569 * @param fields An array of int (length = 4) to receive the parsed
\r
570 * offset time fields. The sign is set to fields[0] (-1 or 1),
\r
571 * hour is set to fields[1], minute is set to fields[2] and second is
\r
572 * set to fields[3].
\r
573 * @return Returns true when the given custom id is valid.
\r
575 static boolean parseCustomID(String id, int[] fields) {
\r
576 NumberFormat numberFormat = null;
\r
577 String idUppercase = id.toUpperCase();
\r
579 if (id != null && id.length() > kGMT_ID.length() &&
\r
580 idUppercase.startsWith(kGMT_ID)) {
\r
581 ParsePosition pos = new ParsePosition(kGMT_ID.length());
\r
587 if (id.charAt(pos.getIndex()) == 0x002D /*'-'*/) {
\r
589 } else if (id.charAt(pos.getIndex()) != 0x002B /*'+'*/) {
\r
592 pos.setIndex(pos.getIndex() + 1);
\r
594 numberFormat = NumberFormat.getInstance();
\r
595 numberFormat.setParseIntegerOnly(true);
\r
597 // Look for either hh:mm, hhmm, or hh
\r
598 int start = pos.getIndex();
\r
600 Number n = numberFormat.parse(id, pos);
\r
601 if (pos.getIndex() == start) {
\r
604 hour = n.intValue();
\r
606 if (pos.getIndex() < id.length()){
\r
607 if (pos.getIndex() - start > 2
\r
608 || id.charAt(pos.getIndex()) != 0x003A /*':'*/) {
\r
612 pos.setIndex(pos.getIndex() + 1);
\r
613 int oldPos = pos.getIndex();
\r
614 n = numberFormat.parse(id, pos);
\r
615 if ((pos.getIndex() - oldPos) != 2) {
\r
616 // must be 2 digits
\r
619 min = n.intValue();
\r
620 if (pos.getIndex() < id.length()) {
\r
621 if (id.charAt(pos.getIndex()) != 0x003A /*':'*/) {
\r
625 pos.setIndex(pos.getIndex() + 1);
\r
626 oldPos = pos.getIndex();
\r
627 n = numberFormat.parse(id, pos);
\r
628 if (pos.getIndex() != id.length()
\r
629 || (pos.getIndex() - oldPos) != 2) {
\r
632 sec = n.intValue();
\r
635 // Supported formats are below -
\r
644 int length = pos.getIndex() - start;
\r
645 if (length <= 0 || 6 < length) {
\r
652 // already set to hour
\r
662 min = (hour/100) % 100;
\r
668 if (hour <= kMAX_CUSTOM_HOUR && min <= kMAX_CUSTOM_MIN && sec <= kMAX_CUSTOM_SEC) {
\r
669 if (fields != null) {
\r
670 if (fields.length >= 1) {
\r
673 if (fields.length >= 2) {
\r
676 if (fields.length >= 3) {
\r
679 if (fields.length >= 4) {
\r
690 * Creates a custom zone for the offset
\r
691 * @param offset GMT offset in milliseconds
\r
692 * @return A custom TimeZone for the offset with normalized time zone id
\r
694 public static TimeZone getCustomTimeZone(int offset) {
\r
695 boolean negative = false;
\r
702 int hour, min, sec, millis;
\r
704 millis = tmp % 1000;
\r
706 Assert.assrt("millis!=0", millis != 0);
\r
714 // Note: No millisecond part included in TZID for now
\r
715 String zid = formatCustomID(hour, min, sec, negative);
\r
717 return new SimpleTimeZone(offset, zid);
\r
721 * Returns the normalized custom TimeZone ID
\r
723 static String formatCustomID(int hour, int min, int sec, boolean negative) {
\r
724 // Create normalized time zone ID - GMT[+|-]hh:mm[:ss]
\r
725 StringBuilder zid = new StringBuilder(kCUSTOM_TZ_PREFIX);
\r
726 if (hour != 0 || min != 0) {
\r
732 // Always use US-ASCII digits
\r
744 // Optional second field
\r
752 return zid.toString();
\r
756 * Returns a CLDR metazone ID for the given Olson tzid and time.
\r
758 public static String getMetazoneID(String olsonID, long date) {
\r
759 String mzid = null;
\r
760 List<OlsonToMetaMappingEntry> mappings = getOlsonToMatazones(olsonID);
\r
761 if (mappings != null) {
\r
762 for (int i = 0; i < mappings.size(); i++) {
\r
763 OlsonToMetaMappingEntry mzm = mappings.get(i);
\r
764 if (date >= mzm.from && date < mzm.to) {
\r
773 private static ICUCache<String, List<OlsonToMetaMappingEntry>> OLSON_TO_META_CACHE =
\r
774 new SimpleCache<String, List<OlsonToMetaMappingEntry>>();
\r
776 static class OlsonToMetaMappingEntry {
\r
782 static List<OlsonToMetaMappingEntry> getOlsonToMatazones(String tzid) {
\r
783 List<OlsonToMetaMappingEntry> mzMappings = OLSON_TO_META_CACHE.get(tzid);
\r
784 if (mzMappings == null) {
\r
786 UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
\r
787 UResourceBundle metazoneInfoBundle = bundle.get("metazoneInfo");
\r
789 String canonicalID = TimeZone.getCanonicalID(tzid);
\r
790 if (canonicalID == null) {
\r
793 String tzkey = canonicalID.replace('/', ':');
\r
794 UResourceBundle zoneBundle = metazoneInfoBundle.get(tzkey);
\r
796 mzMappings = new LinkedList<OlsonToMetaMappingEntry>();
\r
798 for (int idx = 0; idx < zoneBundle.getSize(); idx++) {
\r
799 UResourceBundle mz = zoneBundle.get(idx);
\r
800 String mzid = mz.getString(0);
\r
801 String from = "1970-01-01 00:00";
\r
802 String to = "9999-12-31 23:59";
\r
803 if (mz.getSize() == 3) {
\r
804 from = mz.getString(1);
\r
805 to = mz.getString(2);
\r
807 OlsonToMetaMappingEntry mzmap = new OlsonToMetaMappingEntry();
\r
808 mzmap.mzid = mzid.intern();
\r
810 mzmap.from = parseDate(from);
\r
811 mzmap.to = parseDate(to);
\r
812 } catch (IllegalArgumentException baddate) {
\r
816 // Add this mapping to the list
\r
817 mzMappings.add(mzmap);
\r
820 } catch (MissingResourceException mre) {
\r
823 if (mzMappings != null) {
\r
824 OLSON_TO_META_CACHE.put(tzid, mzMappings);
\r
831 * Convert a date string used by metazone mappings to long.
\r
832 * The format used by CLDR metazone mapping is "yyyy-MM-dd HH:mm".
\r
833 * We do not want to use SimpleDateFormat to parse the metazone
\r
834 * mapping range strings in createOlsonToMeta, because it might be
\r
835 * called from SimpleDateFormat initialization code.
\r
837 static long parseDate (String text) throws IllegalArgumentException {
\r
838 int year = 0, month = 0, day = 0, hour = 0, min = 0;
\r
843 for (idx = 0; idx <= 3; idx++) {
\r
844 n = text.charAt(idx) - '0';
\r
845 if (n >= 0 && n < 10) {
\r
846 year = 10*year + n;
\r
848 throw new IllegalArgumentException("Bad year");
\r
852 for (idx = 5; idx <= 6; idx++) {
\r
853 n = text.charAt(idx) - '0';
\r
854 if (n >= 0 && n < 10) {
\r
855 month = 10*month + n;
\r
857 throw new IllegalArgumentException("Bad month");
\r
861 for (idx = 8; idx <= 9; idx++) {
\r
862 n = text.charAt(idx) - '0';
\r
863 if (n >= 0 && n < 10) {
\r
866 throw new IllegalArgumentException("Bad day");
\r
870 for (idx = 11; idx <= 12; idx++) {
\r
871 n = text.charAt(idx) - '0';
\r
872 if (n >= 0 && n < 10) {
\r
873 hour = 10*hour + n;
\r
875 throw new IllegalArgumentException("Bad hour");
\r
879 for (idx = 14; idx <= 15; idx++) {
\r
880 n = text.charAt(idx) - '0';
\r
881 if (n >= 0 && n < 10) {
\r
884 throw new IllegalArgumentException("Bad minute");
\r
888 long date = Grego.fieldsToDay(year, month - 1, day) * Grego.MILLIS_PER_DAY
\r
889 + hour * Grego.MILLIS_PER_HOUR + min * Grego.MILLIS_PER_MINUTE;
\r
893 private static ICUCache<String, Map<String, String>> META_TO_OLSON_CACHE =
\r
894 new SimpleCache<String, Map<String, String>>();
\r
897 * Returns an Olson ID for the ginve metazone and region
\r
899 public static String getZoneIdByMetazone(String metazoneID, String region) {
\r
900 String tzid = null;
\r
902 // look up in the cache first
\r
903 Map<String, String> zoneMap = META_TO_OLSON_CACHE.get(metazoneID);
\r
904 if (zoneMap == null) {
\r
906 // Create zone mappings for the metazone
\r
907 UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
\r
908 UResourceBundle mapTimezones = bundle.get("mapTimezones");
\r
909 UResourceBundle territoryMap = mapTimezones.get(metazoneID);
\r
910 zoneMap = new HashMap<String, String>();
\r
911 Set<String> territories = territoryMap.keySet();
\r
912 for (String territory : territories) {
\r
913 String zone = territoryMap.getString(territory);
\r
914 zoneMap.put(territory, zone);
\r
917 META_TO_OLSON_CACHE.put(metazoneID, zoneMap);
\r
918 } catch (MissingResourceException e) {
\r
923 if (zoneMap != null) {
\r
924 tzid = zoneMap.get(region);
\r
925 if (tzid == null) {
\r
926 tzid = zoneMap.get("001"); // use the mapping for world as fallback
\r