]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/impl/ZoneMeta.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / impl / ZoneMeta.java
1 /*\r
2 **********************************************************************\r
3 * Copyright (c) 2003-2009 International Business Machines\r
4 * Corporation and others.  All Rights Reserved.\r
5 **********************************************************************\r
6 * Author: Alan Liu\r
7 * Created: September 4 2003\r
8 * Since: ICU 2.8\r
9 **********************************************************************\r
10 */\r
11 package com.ibm.icu.impl;\r
12 \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
23 \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
31 \r
32 /**\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
38  * by Olson links).\r
39  *\r
40  * This class uses a data class, ZoneMetaData, which is created by the\r
41  * tool tz2icu.\r
42  *\r
43  * @author Alan Liu\r
44  * @since ICU 2.8\r
45  */\r
46 public final class ZoneMeta {\r
47     private static final boolean ASSERT = false;\r
48 \r
49     /**\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
58      */\r
59     public static synchronized String[] getAvailableIDs(String country) {\r
60         if(!getOlsonMeta()){\r
61             return EMPTY;\r
62         }\r
63         try{\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
75             }\r
76             return ret;\r
77         }catch(MissingResourceException ex){\r
78             //throw away the exception\r
79         }\r
80         return EMPTY;\r
81     }\r
82     public static synchronized String[] getAvailableIDs() {\r
83         if(!getOlsonMeta()){\r
84             return EMPTY;\r
85         }\r
86         try{\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
92         }\r
93         return EMPTY;\r
94     }\r
95     public static synchronized String[] getAvailableIDs(int offset){\r
96         if(!getOlsonMeta()){\r
97             return EMPTY;\r
98         }\r
99         LinkedList vector = new LinkedList();\r
100         for (int i=0; i<OLSON_ZONE_COUNT; ++i) {\r
101             String unistr;\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
110                 }\r
111             }\r
112         }\r
113         if(!vector.isEmpty()){\r
114             String[] strings = new String[vector.size()];\r
115             return (String[])vector.toArray(strings);\r
116         }\r
117         return EMPTY;\r
118     }\r
119     private static String getID(int i) {\r
120         try{\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
126         }\r
127         return null;\r
128     }\r
129     /**\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
133      *\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
141      */\r
142     public static synchronized int countEquivalentIDs(String id) {\r
143 \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
150             return v.length;\r
151         }\r
152         return 0;\r
153     }\r
154 \r
155     /**\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
159      *\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
172      */\r
173     public static synchronized String getEquivalentID(String id, int index) {\r
174         String result="";\r
175         UResourceBundle res = openOlsonResource(id);\r
176         if (res != null) {\r
177             int zone = -1;\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
183                     zone = v[index];\r
184                 }\r
185             }\r
186             if (zone >= 0) {\r
187                 try {\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
193                     result = "";\r
194                 }\r
195             }\r
196         }\r
197         return result;\r
198     }\r
199 \r
200     private static String[] getCanonicalInfo(String id) {\r
201         if (id == null || id.length() == 0) {\r
202             return null;\r
203         }\r
204         if (canonicalMap == null) {\r
205             Map m = new HashMap();\r
206             Set s = new HashSet();\r
207             try {\r
208                 UResourceBundle supplementalDataBundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "supplementalData", ICUResourceBundle.ICU_DATA_CLASS_LOADER);\r
209     \r
210                 UResourceBundle zoneFormatting = supplementalDataBundle.get("zoneFormatting");\r
211                 UResourceBundleIterator it = zoneFormatting.getIterator();\r
212     \r
213                 while ( it.hasNext()) {\r
214                     UResourceBundle temp = it.next();\r
215                     int resourceType = temp.getType();\r
216     \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
225                                 result[1] = null;\r
226                             }\r
227                             else {\r
228                                 result[1] = territory;\r
229                             }\r
230                             m.put(canonicalID,result);\r
231                             try {\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
236                                 }\r
237                             } catch(MissingResourceException ex){\r
238                                 // Disregard if there are no aliases\r
239                             }\r
240                             break;\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
245                             }\r
246                             break;\r
247                     }\r
248                 }\r
249             } catch (MissingResourceException e) {\r
250                 // throws away the exception - maps are empty for this case\r
251             }\r
252 \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
257             try{\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
265                         continue;\r
266                     }\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
274                             continue;\r
275                         }\r
276                         tmpinfo = (String[])m.get(alias);\r
277                         if (tmpinfo != null) {\r
278                             break;\r
279                         }\r
280                     }\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
286                     } else {\r
287                         // Use the canonical ID in the existing entry\r
288                         m.put(ids[i], tmpinfo);\r
289                     }\r
290                 }\r
291             } catch (MissingResourceException ex) {\r
292                 //throw away the exception\r
293             }\r
294 \r
295             synchronized (ZoneMeta.class) {\r
296                 canonicalMap = m;\r
297                 multiZoneTerritories = s;\r
298             }\r
299         }\r
300 \r
301         return (String[])canonicalMap.get(id);\r
302     }\r
303 \r
304     private static Map canonicalMap = null;\r
305     private static Set multiZoneTerritories = null;\r
306 \r
307     /**\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
310      */\r
311     public static String getCanonicalSystemID(String tzid) {\r
312         String[] info = getCanonicalInfo(tzid);\r
313         if (info != null) {\r
314             return info[0];\r
315         }\r
316         return null;\r
317     }\r
318 \r
319     /**\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
322      */\r
323     public static String getCanonicalCountry(String tzid) {\r
324         String[] info = getCanonicalInfo(tzid);\r
325         if (info != null) {\r
326             return info[1];\r
327         }\r
328         return null;\r
329     }\r
330 \r
331     /**\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
336      */\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
340             return info[1];\r
341         }\r
342         return null;\r
343     }\r
344 \r
345     /**\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
348      */\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
353         }\r
354 \r
355         String country_code = info[1];\r
356         if (country_code == null) {\r
357             return null; // error!   \r
358         }\r
359 \r
360         String country = null;\r
361         if (country_code != null) {\r
362             try {\r
363                 ICUResourceBundle rb = \r
364                     (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);\r
365 //\r
366 // TODO: There is a design bug in UResourceBundle and getLoadingStatus() does not work well.\r
367 //\r
368 //                if (rb.getLoadingStatus() != ICUResourceBundle.FROM_ROOT && rb.getLoadingStatus() != ICUResourceBundle.FROM_DEFAULT) {\r
369 //                    country = ULocale.getDisplayCountry("xx_" + country_code, locale);\r
370 //                }\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
375                 }\r
376 // END WORKAROUND\r
377             } catch (MissingResourceException e) {\r
378                 // fall through\r
379             }\r
380             if (country == null || country.length() == 0) {\r
381                 country = country_code;\r
382             }\r
383         }\r
384         \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
391             }\r
392             MessageFormat mf = new MessageFormat(regPat);\r
393             return mf.format(new Object[] { country });\r
394         }\r
395 \r
396         if (city == null) {\r
397             city = tzid.substring(tzid.lastIndexOf('/')+1).replace('_',' ');\r
398         }\r
399 \r
400         String flbPat = getTZLocalizationInfo(locale, FALLBACK_FORMAT);\r
401         if (flbPat == null) {\r
402             flbPat = DEF_FALLBACK_FORMAT;\r
403         }\r
404         MessageFormat mf = new MessageFormat(flbPat);\r
405 \r
406         return mf.format(new Object[] { city, country });\r
407     }\r
408 \r
409     private static final String DEF_REGION_FORMAT = "{0}";\r
410     private static final String DEF_FALLBACK_FORMAT = "{1} ({0})";\r
411 \r
412     public static final String\r
413         HOUR = "hourFormat",\r
414         GMT = "gmtFormat",\r
415         REGION_FORMAT = "regionFormat",\r
416         FALLBACK_FORMAT = "fallbackFormat",\r
417         ZONE_STRINGS = "zoneStrings",\r
418         FORWARD_SLASH = "/";\r
419      \r
420     /**\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
423      */\r
424     public static String getTZLocalizationInfo(ULocale locale, String format) {\r
425         String result = null;\r
426         try {\r
427             ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(locale);\r
428             result = bundle.getStringWithFallback(ZONE_STRINGS+FORWARD_SLASH+format);\r
429         } catch (MissingResourceException e) {\r
430             result = null;\r
431         }\r
432         return result;\r
433     }\r
434 \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
441 //        return valid;\r
442 //    }\r
443 \r
444     /**\r
445      * Empty string array.\r
446      */\r
447     private static final String[] EMPTY = new String[0];\r
448 \r
449 \r
450 \r
451     /**\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
456      */\r
457     public static UResourceBundle openOlsonResource(String id)\r
458     {\r
459         UResourceBundle res = null;\r
460         try {\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
469             }\r
470         } catch (MissingResourceException e) {\r
471             res = null;\r
472         }\r
473         return res;\r
474     }\r
475 \r
476     /**\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
481      */\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
485         \r
486         // search for the string\r
487         int idx = findInStringArray(tmp, id);\r
488         \r
489         if((idx == -1)) {\r
490             // not found \r
491             throw new MissingResourceException(kNAMES, ((ICUResourceBundle)tmp).getResPath(), id);\r
492             //ures_close(oldbundle);\r
493             //oldbundle = NULL;\r
494         } else {\r
495             tmp = top.get(kZONES); // get Zones object from top\r
496             tmp = tmp.get(idx); // get nth Zone object\r
497         }\r
498         return tmp;\r
499     }\r
500     private static int findInStringArray(UResourceBundle array, String id){\r
501         int start = 0;\r
502         int limit = array.getSize();\r
503         int mid;\r
504         String u = null;\r
505         int lastMid = Integer.MAX_VALUE;\r
506         if((limit < 1)) { \r
507             return -1;\r
508         }\r
509         for (;;) {\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
513             }\r
514             lastMid = mid;\r
515             u = array.getString(mid);\r
516             if(u==null){\r
517                 break;\r
518             }\r
519             int r = id.compareTo(u);\r
520             if(r==0) {\r
521                 return mid;\r
522             } else if(r<0) {\r
523                 limit = mid;\r
524             } else {\r
525                 start = mid;\r
526             }\r
527         }\r
528         return -1;\r
529     }\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
536     /**\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
542      */\r
543     static int OLSON_ZONE_START = -1; // starting index of zones\r
544     static int OLSON_ZONE_COUNT = 0;  // count of zones\r
545 \r
546     /**\r
547      * Given a pointer to an open "zoneinfo" resource, load up the Olson\r
548      * meta-data. Return true if successful.\r
549      */\r
550     private static boolean getOlsonMeta(ICUResourceBundle top) {\r
551         if (OLSON_ZONE_START < 0 && top != null) {\r
552             try {\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
558             }\r
559         }\r
560         return (OLSON_ZONE_START >= 0);\r
561     }\r
562 \r
563     /**\r
564      * Load up the Olson meta-data. Return true if successful.\r
565      */\r
566     private static boolean getOlsonMeta() {\r
567         if (OLSON_ZONE_START < 0) {\r
568             try {\r
569                 ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);\r
570                 getOlsonMeta(top);\r
571             } catch (MissingResourceException e) {\r
572                 // throws away the exception\r
573             }\r
574         }\r
575         return (OLSON_ZONE_START >= 0);\r
576     }\r
577 \r
578     /**\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
581      * found, return 0.\r
582      */\r
583     public static TimeZone getSystemTimeZone(String id) {\r
584         TimeZone z = (TimeZone)zoneCache.get(id);\r
585         if (z == null) {\r
586             try{\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
590                 z.setID(id);\r
591                 zoneCache.put(id, z);\r
592             }catch(Exception ex){\r
593                 return null;\r
594             }\r
595         }\r
596         return (TimeZone)z.clone();\r
597     }\r
598     \r
599     public static TimeZone getGMT(){\r
600         TimeZone z = new SimpleTimeZone(0, kGMT_ID);\r
601         z.setID(kGMT_ID);\r
602         return z;\r
603     }\r
604 \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
609 \r
610     /**\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
613      * GMT[+-]hh.\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
616     */\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
623         }\r
624         return null;\r
625     }\r
626 \r
627     /**\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
631      * GMT[+-]hh.\r
632      * @return The normalized custom id string.\r
633     */\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
638         }\r
639         return null;\r
640     }\r
641 \r
642     /*\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
645      * GMT[+-]hh.\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
651      */\r
652     static boolean parseCustomID(String id, int[] fields) {\r
653         NumberFormat numberFormat = null;\r
654         String idUppercase = id.toUpperCase();\r
655 \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
659             int sign = 1;\r
660             int hour = 0;\r
661             int min = 0;\r
662             int sec = 0;\r
663 \r
664             if (id.charAt(pos.getIndex()) == 0x002D /*'-'*/) {\r
665                 sign = -1;\r
666             } else if (id.charAt(pos.getIndex()) != 0x002B /*'+'*/) {\r
667                 return false;\r
668             }\r
669             pos.setIndex(pos.getIndex() + 1);\r
670 \r
671             numberFormat = NumberFormat.getInstance();\r
672             numberFormat.setParseIntegerOnly(true);\r
673 \r
674             // Look for either hh:mm, hhmm, or hh\r
675             int start = pos.getIndex();\r
676 \r
677             Number n = numberFormat.parse(id, pos);\r
678             if (pos.getIndex() == start) {\r
679                 return false;\r
680             }\r
681             hour = n.intValue();\r
682 \r
683             if (pos.getIndex() < id.length()){\r
684                 if (pos.getIndex() - start > 2\r
685                         || id.charAt(pos.getIndex()) != 0x003A /*':'*/) {\r
686                     return false;\r
687                 }\r
688                 // hh:mm\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
694                     return false;\r
695                 }\r
696                 min = n.intValue();\r
697                 if (pos.getIndex() < id.length()) {\r
698                     if (id.charAt(pos.getIndex()) != 0x003A /*':'*/) {\r
699                         return false;\r
700                     }\r
701                     // [:ss]\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
707                         return false;\r
708                     }\r
709                     sec = n.intValue();\r
710                 }\r
711             } else {\r
712                 // Supported formats are below -\r
713                 //\r
714                 // HHmmss\r
715                 // Hmmss\r
716                 // HHmm\r
717                 // Hmm\r
718                 // HH\r
719                 // H\r
720 \r
721                 int length = pos.getIndex() - start;\r
722                 if (length <= 0 || 6 < length) {\r
723                     // invalid length\r
724                     return false;\r
725                 }\r
726                 switch (length) {\r
727                     case 1:\r
728                     case 2:\r
729                         // already set to hour\r
730                         break;\r
731                     case 3:\r
732                     case 4:\r
733                         min = hour % 100;\r
734                         hour /= 100;\r
735                         break;\r
736                     case 5:\r
737                     case 6:\r
738                         sec = hour % 100;\r
739                         min = (hour/100) % 100;\r
740                         hour /= 10000;\r
741                         break;\r
742                 }\r
743             }\r
744 \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
748                         fields[0] = sign;\r
749                     }\r
750                     if (fields.length >= 2) {\r
751                         fields[1] = hour;\r
752                     }\r
753                     if (fields.length >= 3) {\r
754                         fields[2] = min;\r
755                     }\r
756                     if (fields.length >= 4) {\r
757                         fields[3] = sec;\r
758                     }\r
759                 }\r
760                 return true;\r
761             }\r
762         }\r
763         return false;\r
764     }\r
765 \r
766     /**\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
770      */\r
771     public static TimeZone getCustomTimeZone(int offset) {\r
772         boolean negative = false;\r
773         int tmp = offset;\r
774         if (offset < 0) {\r
775             negative = true;\r
776             tmp = -offset;\r
777         }\r
778 \r
779         int hour, min, sec, millis;\r
780 \r
781         millis = tmp % 1000;\r
782         if (ASSERT) {\r
783             Assert.assrt("millis!=0", millis != 0);\r
784         }\r
785         tmp /= 1000;\r
786         sec = tmp % 60;\r
787         tmp /= 60;\r
788         min = tmp % 60;\r
789         hour = tmp / 60;\r
790 \r
791         // Note: No millisecond part included in TZID for now\r
792         String zid = formatCustomID(hour, min, sec, negative);\r
793 \r
794         return new SimpleTimeZone(offset, zid);\r
795     }\r
796 \r
797     /*\r
798      * Returns the normalized custom TimeZone ID\r
799      */\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
804             if(negative) {\r
805                 zid.append('-');\r
806             } else {\r
807                 zid.append('+');\r
808             }\r
809             // Always use US-ASCII digits\r
810             if (hour < 10) {\r
811                 zid.append('0');\r
812             }\r
813             zid.append(hour);\r
814             zid.append(':');\r
815             if (min < 10) {\r
816                 zid.append('0');\r
817             }\r
818             zid.append(min);\r
819 \r
820             if (sec != 0) {\r
821                 // Optional second field\r
822                 zid.append(':');\r
823                 if (sec < 10) {\r
824                     zid.append('0');\r
825                 }\r
826                 zid.append(sec);\r
827             }\r
828         }\r
829         return zid.toString();\r
830     }\r
831 \r
832     private static SoftReference OLSON_TO_META_REF;\r
833     private static SoftReference META_TO_OLSON_REF;\r
834 \r
835     static class OlsonToMetaMappingEntry {\r
836         String mzid;\r
837         long from;\r
838         long to;\r
839     }\r
840 \r
841     private static class MetaToOlsonMappingEntry {\r
842         String id;\r
843         String territory;\r
844     }\r
845 \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
851             }\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
857                 }\r
858                 OLSON_TO_META_REF = new SoftReference(olsonToMeta);\r
859             }\r
860         }\r
861         return olsonToMeta;\r
862     }\r
863 \r
864     /*\r
865      * Create olson tzid to metazone mappings from metazoneInfo.res (3.8.1 or later)\r
866      */\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
871         try {\r
872             UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metazoneInfo");\r
873             metazoneMappingsBundle = bundle.get("metazoneMappings");\r
874         } catch (MissingResourceException mre) {\r
875             // do nothing\r
876         }\r
877         if (metazoneMappingsBundle != null) {\r
878             String[] tzids = getAvailableIDs();\r
879             for (int i = 0; i < tzids.length; i++) {\r
880                 // Skip aliases\r
881                 String canonicalID = TimeZone.getCanonicalID(tzids[i]);\r
882                 if (canonicalID == null || !tzids[i].equals(canonicalID)) {\r
883                     continue;\r
884                 }\r
885                 String tzkey = tzids[i].replace('/', ':');\r
886                 try {\r
887                     UResourceBundle zoneBundle = metazoneMappingsBundle.get(tzkey);\r
888                     LinkedList mzMappings = new LinkedList();\r
889                     for (int idx = 0; ; idx++) {\r
890                         try {\r
891                             UResourceBundle mz = zoneBundle.get("mz" + idx);\r
892                             String[] mzstr = mz.getStringArray();\r
893                             if (mzstr == null || mzstr.length != 3) {\r
894                                 continue;\r
895                             }\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
900 \r
901                             // Add this mapping to the list\r
902                             mzMappings.add(mzmap);\r
903                         } catch (MissingResourceException nomz) {\r
904                             // we're done\r
905                             break;\r
906                         } catch (IllegalArgumentException baddate) {\r
907                             // skip this\r
908                         }\r
909                     }\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
914                         }\r
915                         olsonToMeta.put(tzids[i], mzMappings);\r
916                     }\r
917                 } catch (MissingResourceException noum) {\r
918                     // Does not use metazone, just skip this.\r
919                 }\r
920             }\r
921         }\r
922         return olsonToMeta;\r
923     }\r
924 \r
925     /**\r
926      * Returns a CLDR metazone ID for the given Olson tzid and time.\r
927      */\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
937             }\r
938         }\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
943                     mzid = mzm.mzid;\r
944                     break;\r
945                 }\r
946             }\r
947         }\r
948         return mzid;\r
949     }\r
950 \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
956             }\r
957             if (metaToOlson == null) {\r
958                 metaToOlson = new HashMap();\r
959                 UResourceBundle metazonesBundle = null;\r
960                 try {\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
966                     // do nothing\r
967                 }\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
973                             continue;\r
974                         }\r
975                         String tzid = null;\r
976                         try {\r
977                             tzid = metazonesBundle.getString(mzkey);\r
978                         } catch (MissingResourceException mre) {\r
979                             // It should not happen..\r
980                         }\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
990                                 }\r
991                                 MetaToOlsonMappingEntry olsonmap = new MetaToOlsonMappingEntry();\r
992                                 olsonmap.id = tzid;\r
993                                 olsonmap.territory = territory;\r
994                                 mappings.add(olsonmap);\r
995                             }\r
996                         }\r
997                     }\r
998                 }\r
999                 META_TO_OLSON_REF = new SoftReference(metaToOlson);\r
1000             }\r
1001         }\r
1002         return metaToOlson;\r
1003     }\r
1004 \r
1005     /**\r
1006      * Returns an Olson ID for the ginve metazone and region\r
1007      */\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
1017                     break;\r
1018                 } else if (olsonmap.territory.equals("001")) {\r
1019                     tzid = olsonmap.id;\r
1020                 }\r
1021             }\r
1022         }\r
1023         return tzid;\r
1024     }\r
1025 \r
1026 //    /**\r
1027 //     * Returns an Olson ID for the given metazone and locale\r
1028 //     */\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
1035 //        }\r
1036 //        return getZoneIdByMetazone(metazoneID, region);\r
1037 //    }\r
1038 \r
1039     /*\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
1045      */\r
1046      static long parseDate (String text) throws IllegalArgumentException {\r
1047         int year = 0, month = 0, day = 0, hour = 0, min = 0;\r
1048         int idx;\r
1049         int n;\r
1050 \r
1051         // "yyyy" (0 - 3)\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
1056             } else {\r
1057                 throw new IllegalArgumentException("Bad year");\r
1058             }\r
1059         }\r
1060         // "MM" (5 - 6)\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
1065             } else {\r
1066                 throw new IllegalArgumentException("Bad month");\r
1067             }\r
1068         }\r
1069         // "dd" (8 - 9)\r
1070         for (idx = 8; idx <= 9; idx++) {\r
1071             n = text.charAt(idx) - '0';\r
1072             if (n >= 0 && n < 10) {\r
1073                 day = 10*day + n;\r
1074             } else {\r
1075                 throw new IllegalArgumentException("Bad day");\r
1076             }\r
1077         }\r
1078         // "HH" (11 - 12)\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
1083             } else {\r
1084                 throw new IllegalArgumentException("Bad hour");\r
1085             }\r
1086         }\r
1087         // "mm" (14 - 15)\r
1088         for (idx = 14; idx <= 15; idx++) {\r
1089             n = text.charAt(idx) - '0';\r
1090             if (n >= 0 && n < 10) {\r
1091                 min = 10*min + n;\r
1092             } else {\r
1093                 throw new IllegalArgumentException("Bad minute");\r
1094             }\r
1095         }\r
1096 \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
1099         return date;\r
1100      }\r
1101 }\r