]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_4_2-src/main/classes/core/src/com/ibm/icu/impl/ZoneMeta.java
go
[Dictionary.git] / jars / icu4j-4_4_2-src / main / classes / core / src / com / ibm / icu / impl / ZoneMeta.java
1 /*\r
2 **********************************************************************\r
3 * Copyright (c) 2003-2010 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.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
21 \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
28 \r
29 /**\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
35  * by Olson links).\r
36  *\r
37  * This class uses a data class, ZoneMetaData, which is created by the\r
38  * tool tz2icu.\r
39  *\r
40  * @author Alan Liu\r
41  * @since ICU 2.8\r
42  */\r
43 public final class ZoneMeta {\r
44     private static final boolean ASSERT = false;\r
45 \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
50 \r
51     private static final String kGMT_ID   = "GMT";\r
52     private static final String kCUSTOM_TZ_PREFIX = "GMT";\r
53 \r
54     /**\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
63      */\r
64     public static synchronized String[] getAvailableIDs(String country) {\r
65         String[] ids = null;\r
66 \r
67         try{\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
71 \r
72             // Create a list of zones associated with the country\r
73             List<String> countryZones = new ArrayList<String>();\r
74 \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
79                 }\r
80             }\r
81             if (countryZones.size() > 0) {\r
82                 ids = countryZones.toArray(new String[countryZones.size()]);\r
83             }\r
84         } catch (MissingResourceException ex){\r
85             //throw away the exception\r
86         }\r
87 \r
88         if (ids == null) {\r
89             ids = new String[0];\r
90         }\r
91         return ids;\r
92     }\r
93 \r
94     public static synchronized String[] getAvailableIDs() {\r
95         String[] ids = getZoneIDs();\r
96         if (ids == null) {\r
97             return new String[0];\r
98         }\r
99         return ids.clone();\r
100     }\r
101 \r
102     public static synchronized String[] getAvailableIDs(int offset){\r
103         String[] ids = null;\r
104         String[] all = getZoneIDs();\r
105         if (all != null) {\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
113                     zones.add(zid);\r
114                 }\r
115             }\r
116             if (zones.size() > 0) {\r
117                 ids = zones.toArray(new String[zones.size()]);\r
118             }\r
119         }\r
120         if (ids == null) {\r
121             ids = new String[0];\r
122         }\r
123         return ids;\r
124     }\r
125 \r
126     /**\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
130      *\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
138      */\r
139     public static synchronized int countEquivalentIDs(String id) {\r
140         int count = 0;\r
141         try {\r
142             UResourceBundle res = openOlsonResource(null, id);\r
143             UResourceBundle links = res.get("links");\r
144             int[] v = links.getIntVector();\r
145             count = v.length;\r
146         } catch (MissingResourceException ex) {\r
147             // throw away\r
148         }\r
149         return count;\r
150     }\r
151 \r
152     /**\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
156      *\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
169      */\r
170     public static synchronized String getEquivalentID(String id, int index) {\r
171         String result = "";\r
172         int zoneIdx = -1;\r
173 \r
174         if (index >= 0) {\r
175             try {\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
181                 }\r
182             } catch (MissingResourceException ex) {\r
183                 // throw away\r
184                 zoneIdx = -1;\r
185             }\r
186         }\r
187         if (zoneIdx >= 0) {\r
188             String tmp = getZoneID(zoneIdx);\r
189             if (tmp != null) {\r
190                 result = tmp;\r
191             }\r
192         }\r
193         return result;\r
194     }\r
195 \r
196     private static String[] ZONEIDS = null;\r
197 \r
198     /*\r
199      * ICU frequently refers the zone ID array in zoneinfo resource\r
200      */\r
201     private static synchronized String[] getZoneIDs() {\r
202         if (ZONEIDS == null) {\r
203             try {\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
209                 // throw away..\r
210             }\r
211         }\r
212         if (ZONEIDS == null) {\r
213             ZONEIDS = new String[0];\r
214         }\r
215         return ZONEIDS;\r
216     }\r
217 \r
218     private static String getZoneID(int idx) {\r
219         if (idx >= 0) {\r
220             String[] ids = getZoneIDs();\r
221             if (idx < ids.length) {\r
222                 return ids[idx];\r
223             }\r
224         }\r
225         return null;\r
226     }\r
227 \r
228     private static int getZoneIndex(String zid) {\r
229         int zoneIdx = -1;\r
230 \r
231         String[] all = getZoneIDs();\r
232         if (all.length > 0) {\r
233             int start = 0;\r
234             int limit = all.length;\r
235 \r
236             int lastMid = Integer.MAX_VALUE;\r
237             for (;;) {\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
241                 }\r
242                 lastMid = mid;\r
243                 int r = zid.compareTo(all[mid]);\r
244                 if (r == 0) {\r
245                     zoneIdx = mid;\r
246                     break;\r
247                 } else if(r < 0) {\r
248                     limit = mid;\r
249                 } else {\r
250                     start = mid;\r
251                 }\r
252             }\r
253         }\r
254 \r
255         return zoneIdx;\r
256     }\r
257 \r
258 \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
262 \r
263     /**\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
266      */\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
272                 try {\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
278                         // resolve link\r
279                         String tmp = getZoneID(zone.getInt());\r
280                         if (tmp != null) {\r
281                             canonical = tmp;\r
282                         }\r
283                     } else {\r
284                         canonical = tzid;\r
285                     }\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
294                     }\r
295                 } catch (MissingResourceException e) {\r
296                     // fall through\r
297                 }\r
298             }\r
299             if (canonical != null) {\r
300                 CANONICAL_ID_CACHE.put(tzid, canonical);\r
301             }\r
302         }\r
303         return canonical;\r
304     }\r
305 \r
306     /**\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
309      */\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
315                 try {\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
321                     }\r
322                 } catch (MissingResourceException e) {\r
323                     // throw away\r
324                 }\r
325                 if (region != null) {\r
326                     REGION_CACHE.put(tzid, region);\r
327                 }\r
328             }\r
329         }\r
330         if (region.equals("001")) {\r
331             return null;\r
332         }\r
333         return region;\r
334     }\r
335 \r
336     /**\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
341      */\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
356                             break;\r
357                         }\r
358                     }\r
359                 }\r
360                 isSingle = Boolean.valueOf(isSingleCountryZone);\r
361                 SINGLE_COUNTRY_CACHE.put(tzid, isSingle);\r
362             }\r
363             if (!isSingle) {\r
364                 country = null;\r
365             }\r
366         }\r
367         return country;\r
368     }\r
369 \r
370     /**\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
373      */\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
378             return null;\r
379         }\r
380 \r
381         String country = null;\r
382         try {\r
383             ICUResourceBundle rb = \r
384                 (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_REGION_BASE_NAME, locale);\r
385 //\r
386 // TODO: There is a design bug in UResourceBundle and getLoadingStatus() does not work well.\r
387 //\r
388 //                if (rb.getLoadingStatus() != ICUResourceBundle.FROM_ROOT && rb.getLoadingStatus() != ICUResourceBundle.FROM_DEFAULT) {\r
389 //                    country = ULocale.getDisplayCountry("xx_" + country_code, locale);\r
390 //                }\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
395             }\r
396 // END WORKAROUND\r
397         } catch (MissingResourceException e) {\r
398             // fall through\r
399         }\r
400         if (country == null || country.length() == 0) {\r
401             country = country_code;\r
402         }\r
403 \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
410             }\r
411             MessageFormat mf = new MessageFormat(regPat);\r
412             return mf.format(new Object[] { country });\r
413         }\r
414 \r
415         if (city == null) {\r
416             city = tzid.substring(tzid.lastIndexOf('/')+1).replace('_',' ');\r
417         }\r
418 \r
419         String flbPat = getTZLocalizationInfo(locale, FALLBACK_FORMAT);\r
420         if (flbPat == null) {\r
421             flbPat = DEF_FALLBACK_FORMAT;\r
422         }\r
423         MessageFormat mf = new MessageFormat(flbPat);\r
424 \r
425         return mf.format(new Object[] { city, country });\r
426     }\r
427 \r
428     private static final String DEF_REGION_FORMAT = "{0}";\r
429     private static final String DEF_FALLBACK_FORMAT = "{1} ({0})";\r
430 \r
431     public static final String\r
432         HOUR = "hourFormat",\r
433         GMT = "gmtFormat",\r
434         REGION_FORMAT = "regionFormat",\r
435         FALLBACK_FORMAT = "fallbackFormat",\r
436         ZONE_STRINGS = "zoneStrings",\r
437         FORWARD_SLASH = "/";\r
438      \r
439     /**\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
442      */\r
443     public static String getTZLocalizationInfo(ULocale locale, String format) {\r
444         String result = null;\r
445         try {\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
450             result = null;\r
451         }\r
452         return result;\r
453     }\r
454 \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
461 //        return valid;\r
462 //    }\r
463 \r
464 \r
465     /**\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
472      */\r
473     public static UResourceBundle openOlsonResource(UResourceBundle top, String id)\r
474     {\r
475         UResourceBundle res = null;\r
476         int zoneIdx = getZoneIndex(id);\r
477         if (zoneIdx >= 0) {\r
478             try {\r
479                 if (top == null) {\r
480                     top = UResourceBundle.getBundleInstance(\r
481                             ICUResourceBundle.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER);\r
482                 }\r
483                 UResourceBundle zones = top.get(kZONES);\r
484                 UResourceBundle zone = zones.get(zoneIdx);\r
485                 if (zone.getType() == UResourceBundle.INT) {\r
486                     // resolve link\r
487                     zone = zones.get(zone.getInt());\r
488                 }\r
489                 res = zone;\r
490             } catch (MissingResourceException e) {\r
491                 res = null;\r
492             }\r
493         }\r
494         return res;\r
495     }\r
496 \r
497 \r
498     private static ICUCache<String, TimeZone> SYSTEM_ZONE_CACHE = new SimpleCache<String, TimeZone>();\r
499 \r
500     /**\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
503      * found, return 0.\r
504      */\r
505     public static TimeZone getSystemTimeZone(String id) {\r
506         TimeZone z = SYSTEM_ZONE_CACHE.get(id);\r
507         if (z == null) {\r
508             try{\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
513                 z.setID(id);\r
514                 SYSTEM_ZONE_CACHE.put(id, z);\r
515             }catch(Exception ex){\r
516                 return null;\r
517             }\r
518         }\r
519         return (TimeZone)z.clone();\r
520     }\r
521 \r
522     public static TimeZone getGMT(){\r
523         TimeZone z = new SimpleTimeZone(0, kGMT_ID);\r
524         z.setID(kGMT_ID);\r
525         return z;\r
526     }\r
527 \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
532 \r
533     /**\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
536      * GMT[+-]hh.\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
539     */\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
546         }\r
547         return null;\r
548     }\r
549 \r
550     /**\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
554      * GMT[+-]hh.\r
555      * @return The normalized custom id string.\r
556     */\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
561         }\r
562         return null;\r
563     }\r
564 \r
565     /*\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
568      * GMT[+-]hh.\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
574      */\r
575     static boolean parseCustomID(String id, int[] fields) {\r
576         NumberFormat numberFormat = null;\r
577         String idUppercase = id.toUpperCase();\r
578 \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
582             int sign = 1;\r
583             int hour = 0;\r
584             int min = 0;\r
585             int sec = 0;\r
586 \r
587             if (id.charAt(pos.getIndex()) == 0x002D /*'-'*/) {\r
588                 sign = -1;\r
589             } else if (id.charAt(pos.getIndex()) != 0x002B /*'+'*/) {\r
590                 return false;\r
591             }\r
592             pos.setIndex(pos.getIndex() + 1);\r
593 \r
594             numberFormat = NumberFormat.getInstance();\r
595             numberFormat.setParseIntegerOnly(true);\r
596 \r
597             // Look for either hh:mm, hhmm, or hh\r
598             int start = pos.getIndex();\r
599 \r
600             Number n = numberFormat.parse(id, pos);\r
601             if (pos.getIndex() == start) {\r
602                 return false;\r
603             }\r
604             hour = n.intValue();\r
605 \r
606             if (pos.getIndex() < id.length()){\r
607                 if (pos.getIndex() - start > 2\r
608                         || id.charAt(pos.getIndex()) != 0x003A /*':'*/) {\r
609                     return false;\r
610                 }\r
611                 // hh:mm\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
617                     return false;\r
618                 }\r
619                 min = n.intValue();\r
620                 if (pos.getIndex() < id.length()) {\r
621                     if (id.charAt(pos.getIndex()) != 0x003A /*':'*/) {\r
622                         return false;\r
623                     }\r
624                     // [:ss]\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
630                         return false;\r
631                     }\r
632                     sec = n.intValue();\r
633                 }\r
634             } else {\r
635                 // Supported formats are below -\r
636                 //\r
637                 // HHmmss\r
638                 // Hmmss\r
639                 // HHmm\r
640                 // Hmm\r
641                 // HH\r
642                 // H\r
643 \r
644                 int length = pos.getIndex() - start;\r
645                 if (length <= 0 || 6 < length) {\r
646                     // invalid length\r
647                     return false;\r
648                 }\r
649                 switch (length) {\r
650                     case 1:\r
651                     case 2:\r
652                         // already set to hour\r
653                         break;\r
654                     case 3:\r
655                     case 4:\r
656                         min = hour % 100;\r
657                         hour /= 100;\r
658                         break;\r
659                     case 5:\r
660                     case 6:\r
661                         sec = hour % 100;\r
662                         min = (hour/100) % 100;\r
663                         hour /= 10000;\r
664                         break;\r
665                 }\r
666             }\r
667 \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
671                         fields[0] = sign;\r
672                     }\r
673                     if (fields.length >= 2) {\r
674                         fields[1] = hour;\r
675                     }\r
676                     if (fields.length >= 3) {\r
677                         fields[2] = min;\r
678                     }\r
679                     if (fields.length >= 4) {\r
680                         fields[3] = sec;\r
681                     }\r
682                 }\r
683                 return true;\r
684             }\r
685         }\r
686         return false;\r
687     }\r
688 \r
689     /**\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
693      */\r
694     public static TimeZone getCustomTimeZone(int offset) {\r
695         boolean negative = false;\r
696         int tmp = offset;\r
697         if (offset < 0) {\r
698             negative = true;\r
699             tmp = -offset;\r
700         }\r
701 \r
702         int hour, min, sec, millis;\r
703 \r
704         millis = tmp % 1000;\r
705         if (ASSERT) {\r
706             Assert.assrt("millis!=0", millis != 0);\r
707         }\r
708         tmp /= 1000;\r
709         sec = tmp % 60;\r
710         tmp /= 60;\r
711         min = tmp % 60;\r
712         hour = tmp / 60;\r
713 \r
714         // Note: No millisecond part included in TZID for now\r
715         String zid = formatCustomID(hour, min, sec, negative);\r
716 \r
717         return new SimpleTimeZone(offset, zid);\r
718     }\r
719 \r
720     /*\r
721      * Returns the normalized custom TimeZone ID\r
722      */\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
727             if(negative) {\r
728                 zid.append('-');\r
729             } else {\r
730                 zid.append('+');\r
731             }\r
732             // Always use US-ASCII digits\r
733             if (hour < 10) {\r
734                 zid.append('0');\r
735             }\r
736             zid.append(hour);\r
737             zid.append(':');\r
738             if (min < 10) {\r
739                 zid.append('0');\r
740             }\r
741             zid.append(min);\r
742 \r
743             if (sec != 0) {\r
744                 // Optional second field\r
745                 zid.append(':');\r
746                 if (sec < 10) {\r
747                     zid.append('0');\r
748                 }\r
749                 zid.append(sec);\r
750             }\r
751         }\r
752         return zid.toString();\r
753     }\r
754 \r
755     /**\r
756      * Returns a CLDR metazone ID for the given Olson tzid and time.\r
757      */\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
765                     mzid = mzm.mzid;\r
766                     break;\r
767                 }\r
768             }\r
769         }\r
770         return mzid;\r
771     }\r
772 \r
773     private static ICUCache<String, List<OlsonToMetaMappingEntry>> OLSON_TO_META_CACHE =\r
774         new SimpleCache<String, List<OlsonToMetaMappingEntry>>();\r
775 \r
776     static class OlsonToMetaMappingEntry {\r
777         String mzid;\r
778         long from;\r
779         long to;\r
780     }\r
781 \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
785             try {\r
786                 UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");\r
787                 UResourceBundle metazoneInfoBundle = bundle.get("metazoneInfo");\r
788 \r
789                 String canonicalID = TimeZone.getCanonicalID(tzid);\r
790                 if (canonicalID == null) {\r
791                     return null;\r
792                 }\r
793                 String tzkey = canonicalID.replace('/', ':');\r
794                 UResourceBundle zoneBundle = metazoneInfoBundle.get(tzkey);\r
795 \r
796                 mzMappings = new LinkedList<OlsonToMetaMappingEntry>();\r
797 \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
806                     }\r
807                     OlsonToMetaMappingEntry mzmap = new OlsonToMetaMappingEntry();\r
808                     mzmap.mzid = mzid.intern();\r
809                     try {\r
810                         mzmap.from = parseDate(from);\r
811                         mzmap.to = parseDate(to);\r
812                     } catch (IllegalArgumentException baddate) {\r
813                         // skip this\r
814                         continue;\r
815                     }\r
816                     // Add this mapping to the list\r
817                     mzMappings.add(mzmap);\r
818                 }\r
819 \r
820             } catch (MissingResourceException mre) {\r
821                 // fall through\r
822             }\r
823             if (mzMappings != null) {\r
824                 OLSON_TO_META_CACHE.put(tzid, mzMappings);\r
825             }\r
826         }\r
827         return mzMappings;\r
828     }\r
829 \r
830     /*\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
836      */\r
837      static long parseDate (String text) throws IllegalArgumentException {\r
838         int year = 0, month = 0, day = 0, hour = 0, min = 0;\r
839         int idx;\r
840         int n;\r
841 \r
842         // "yyyy" (0 - 3)\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
847             } else {\r
848                 throw new IllegalArgumentException("Bad year");\r
849             }\r
850         }\r
851         // "MM" (5 - 6)\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
856             } else {\r
857                 throw new IllegalArgumentException("Bad month");\r
858             }\r
859         }\r
860         // "dd" (8 - 9)\r
861         for (idx = 8; idx <= 9; idx++) {\r
862             n = text.charAt(idx) - '0';\r
863             if (n >= 0 && n < 10) {\r
864                 day = 10*day + n;\r
865             } else {\r
866                 throw new IllegalArgumentException("Bad day");\r
867             }\r
868         }\r
869         // "HH" (11 - 12)\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
874             } else {\r
875                 throw new IllegalArgumentException("Bad hour");\r
876             }\r
877         }\r
878         // "mm" (14 - 15)\r
879         for (idx = 14; idx <= 15; idx++) {\r
880             n = text.charAt(idx) - '0';\r
881             if (n >= 0 && n < 10) {\r
882                 min = 10*min + n;\r
883             } else {\r
884                 throw new IllegalArgumentException("Bad minute");\r
885             }\r
886         }\r
887 \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
890         return date;\r
891      }\r
892 \r
893      private static ICUCache<String, Map<String, String>> META_TO_OLSON_CACHE =\r
894          new SimpleCache<String, Map<String, String>>();\r
895 \r
896      /**\r
897       * Returns an Olson ID for the ginve metazone and region\r
898       */\r
899      public static String getZoneIdByMetazone(String metazoneID, String region) {\r
900          String tzid = null;\r
901 \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
905              try {\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
915                  }\r
916                  // cache this\r
917                  META_TO_OLSON_CACHE.put(metazoneID, zoneMap);\r
918              } catch (MissingResourceException e) {\r
919                  // ignore\r
920              }\r
921          }\r
922 \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
927              }\r
928          }\r
929 \r
930          return tzid;\r
931      }\r
932 }\r