KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ibm > icu > impl > ZoneMeta


1 /*
2 **********************************************************************
3 * Copyright (c) 2003-2006, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 **********************************************************************
6 * Author: Alan Liu
7 * Created: September 4 2003
8 * Since: ICU 2.8
9 **********************************************************************
10 */

11 package com.ibm.icu.impl;
12
13 import java.text.ParsePosition JavaDoc;
14 import java.util.Arrays JavaDoc;
15 import java.util.ArrayList JavaDoc;
16 import java.util.Collections JavaDoc;
17 import java.util.HashMap JavaDoc;
18 import java.util.Map JavaDoc;
19 import java.util.MissingResourceException JavaDoc;
20 import java.util.Set JavaDoc;
21 import java.util.TreeMap JavaDoc;
22 import java.util.TreeSet JavaDoc;
23 import java.util.Vector JavaDoc;
24
25 import com.ibm.icu.text.MessageFormat;
26 import com.ibm.icu.text.NumberFormat;
27 import com.ibm.icu.text.SimpleDateFormat;
28 import com.ibm.icu.util.SimpleTimeZone;
29 import com.ibm.icu.util.TimeZone;
30 import com.ibm.icu.util.ULocale;
31 import com.ibm.icu.util.UResourceBundle;
32
33 /**
34  * This class, not to be instantiated, implements the meta-data
35  * missing from the underlying core JDK implementation of time zones.
36  * There are two missing features: Obtaining a list of available zones
37  * for a given country (as defined by the Olson database), and
38  * obtaining a list of equivalent zones for a given zone (as defined
39  * by Olson links).
40  *
41  * This class uses a data class, ZoneMetaData, which is created by the
42  * tool tz2icu.
43  *
44  * @author Alan Liu
45  * @since ICU 2.8
46  */

47 public final class ZoneMeta {
48     private static final boolean ASSERT = false;
49
50     /**
51      * Returns a String array containing all system TimeZone IDs
52      * associated with the given country. These IDs may be passed to
53      * <code>TimeZone.getTimeZone()</code> to construct the
54      * corresponding TimeZone object.
55      * @param country a two-letter ISO 3166 country code, or <code>null</code>
56      * to return zones not associated with any country
57      * @return an array of IDs for system TimeZones in the given
58      * country. If there are none, return a zero-length array.
59      */

60     public static synchronized String JavaDoc[] getAvailableIDs(String JavaDoc country) {
61         if(!getOlsonMeta()){
62             return EMPTY;
63         }
64         try{
65             ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
66             ICUResourceBundle regions = top.get(kREGIONS);
67             ICUResourceBundle names = top.get(kNAMES); // dereference Zones section
68
ICUResourceBundle temp = regions.get(country);
69             int[] vector = temp.getIntVector();
70             if (ASSERT) Assert.assrt("vector.length>0", vector.length>0);
71             String JavaDoc[] ret = new String JavaDoc[vector.length];
72             for (int i=0; i<vector.length; ++i) {
73                 if (ASSERT) Assert.assrt("vector[i] >= 0 && vector[i] < OLSON_ZONE_COUNT",
74                         vector[i] >= 0 && vector[i] < OLSON_ZONE_COUNT);
75                 ret[i] = names.getString(vector[i]);
76             }
77             return ret;
78         }catch(MissingResourceException JavaDoc ex){
79             //throw away the exception
80
}
81         return EMPTY;
82     }
83     public static synchronized String JavaDoc[] getAvailableIDs() {
84         if(!getOlsonMeta()){
85             return EMPTY;
86         }
87         try{
88             ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
89             ICUResourceBundle names = top.get(kNAMES); // dereference Zones section
90
return names.getStringArray();
91         }catch(MissingResourceException JavaDoc ex){
92             //throw away the exception
93
}
94         return EMPTY;
95     }
96     public static synchronized String JavaDoc[] getAvailableIDs(int offset){
97         Vector JavaDoc vector = new Vector JavaDoc();
98         for (int i=0; i<OLSON_ZONE_COUNT; ++i) {
99             String JavaDoc unistr;
100             if ((unistr=getID(i))!=null) {
101                 // This is VERY inefficient.
102
TimeZone z = TimeZone.getTimeZone(unistr);
103                 // Make sure we get back the ID we wanted (if the ID is
104
// invalid we get back GMT).
105
if (z != null && z.getID().equals(unistr) &&
106                     z.getRawOffset() == offset) {
107                     vector.add(unistr);
108                 }
109             }
110         }
111         if(!vector.isEmpty()){
112             String JavaDoc[] strings = new String JavaDoc[vector.size()];
113             return (String JavaDoc[])vector.toArray(strings);
114         }
115         return EMPTY;
116     }
117     private static String JavaDoc getID(int i) {
118         try{
119             ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
120             ICUResourceBundle names = top.get(kNAMES); // dereference Zones section
121
return names.getString(i);
122         }catch(MissingResourceException JavaDoc ex){
123             //throw away the exception
124
}
125         return null;
126     }
127     /**
128      * Returns the number of IDs in the equivalency group that
129      * includes the given ID. An equivalency group contains zones
130      * that behave identically to the given zone.
131      *
132      * <p>If there are no equivalent zones, then this method returns
133      * 0. This means either the given ID is not a valid zone, or it
134      * is and there are no other equivalent zones.
135      * @param id a system time zone ID
136      * @return the number of zones in the equivalency group containing
137      * 'id', or zero if there are no equivalent zones.
138      * @see #getEquivalentID
139      */

140     public static synchronized int countEquivalentIDs(String JavaDoc id) {
141
142         ICUResourceBundle res = openOlsonResource(id);
143         int size = res.getSize();
144         if (size == 4 || size == 6) {
145             ICUResourceBundle r=res.get(size-1);
146             //result = ures_getSize(&r); // doesn't work
147
int[] v = r.getIntVector();
148             return v.length;
149         }
150         return 0;
151     }
152
153     /**
154      * Returns an ID in the equivalency group that includes the given
155      * ID. An equivalency group contains zones that behave
156      * identically to the given zone.
157      *
158      * <p>The given index must be in the range 0..n-1, where n is the
159      * value returned by <code>countEquivalentIDs(id)</code>. For
160      * some value of 'index', the returned value will be equal to the
161      * given id. If the given id is not a valid system time zone, or
162      * if 'index' is out of range, then returns an empty string.
163      * @param id a system time zone ID
164      * @param index a value from 0 to n-1, where n is the value
165      * returned by <code>countEquivalentIDs(id)</code>
166      * @return the ID of the index-th zone in the equivalency group
167      * containing 'id', or an empty string if 'id' is not a valid
168      * system ID or 'index' is out of range
169      * @see #countEquivalentIDs
170      */

171     public static synchronized String JavaDoc getEquivalentID(String JavaDoc id, int index) {
172         String JavaDoc result="";
173         ICUResourceBundle res = openOlsonResource(id);
174         int zone = -1;
175         int size = res.getSize();
176         if (size == 4 || size == 6) {
177             ICUResourceBundle r = res.get(size-1);
178             int[] v = r.getIntVector();
179             if (index >= 0 && index < size && getOlsonMeta()) {
180                 zone = v[index];
181             }
182         }
183         if (zone >= 0) {
184             ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
185             ICUResourceBundle ares = top.get(kNAMES); // dereference Zones section
186
result = ares.getString(zone);
187
188         }
189         return result;
190     }
191
192     /**
193      * Create the equivalency map.
194      *
195     private static void createEquivMap() {
196         EQUIV_MAP = new TreeMap();
197
198         // try leaving all ids as valid
199 // Set valid = getValidIDs();
200
201         ArrayList list = new ArrayList(); // reuse this below
202
203         for (int i=0; i<ZoneMetaData.EQUIV.length; ++i) {
204             String[] z = ZoneMetaData.EQUIV[i];
205             list.clear();
206             for (int j=0; j<z.length; ++j) {
207 // if (valid.contains(z[j])) {
208                     list.add(z[j]);
209 // }
210             }
211             if (list.size() > 1) {
212                 String[] a = (String[]) list.toArray(EMPTY);
213                 for (int j=0; j<a.length; ++j) {
214                     EQUIV_MAP.put(a[j], a);
215                 }
216             }
217         }
218     }
219  */

220     private static String JavaDoc[] getCanonicalInfo(String JavaDoc id) {
221         if (canonicalMap == null) {
222             Map JavaDoc m = new HashMap JavaDoc();
223             for (int i = 0; i < ZoneInfoExt.CLDR_INFO.length; ++i) {
224                 String JavaDoc[] clist = ZoneInfoExt.CLDR_INFO[i];
225                 String JavaDoc c = clist[0];
226                 m.put(c, clist);
227                 for (int j = 3; j < clist.length; ++j) {
228                     m.put(clist[j], clist);
229                 }
230             }
231             synchronized (ZoneMeta.class) {
232                 canonicalMap = m;
233             }
234         }
235
236         return (String JavaDoc[])canonicalMap.get(id);
237     }
238     private static Map JavaDoc canonicalMap = null;
239
240     /**
241      * Return the canonical id for this tzid, which might be the id itself.
242      * If there is no canonical id for it, return the passed-in id.
243      */

244     public static String JavaDoc getCanonicalID(String JavaDoc tzid) {
245         String JavaDoc[] info = getCanonicalInfo(tzid);
246         if (info != null) {
247             return info[0];
248         }
249         return tzid;
250     }
251
252     /**
253      * Return the canonical country code for this tzid. If we have none, or if the time zone
254      * is not associated with a country, return null.
255      */

256     public static String JavaDoc getCanonicalCountry(String JavaDoc tzid) {
257         String JavaDoc[] info = getCanonicalInfo(tzid);
258         if (info != null) {
259             return info[1];
260         }
261         return null;
262     }
263
264     /**
265      * Return the country code if this is a 'single' time zone that can fallback to just
266      * the country, otherwise return null. (Note, one must also check the locale data
267      * to see that there is a localization for the country in order to implement
268      * tr#35 appendix J step 5.)
269      */

270     public static String JavaDoc getSingleCountry(String JavaDoc tzid) {
271         String JavaDoc[] info = getCanonicalInfo(tzid);
272         if (info != null && info[2] != null) {
273             return info[1];
274         }
275         return null;
276     }
277
278     /**
279      * Handle fallbacks for generic time (rules E.. G)
280      */

281     public static String JavaDoc displayFallback(String JavaDoc tzid, String JavaDoc city, ULocale locale) {
282         String JavaDoc[] info = getCanonicalInfo(tzid);
283         if (info == null) {
284             return null; // error
285
}
286
287         String JavaDoc country_code = info[1];
288         if (country_code == null) {
289             return null; // error!
290
}
291
292         String JavaDoc country = null;
293         if (country_code != null) {
294             ICUResourceBundle rb =
295                 (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
296             if (rb.getLoadingStatus() != rb.FROM_ROOT && rb.getLoadingStatus() != rb.FROM_DEFAULT) {
297                 country = ULocale.getDisplayCountry("xx_" + country_code, locale);
298             }
299             if (country == null || country.length() == 0) country = country_code;
300         }
301         
302         // This is not behavior specified in tr35, but behavior added by Mark.
303
// TR35 says to display the country _only_ if there is a localization.
304
if (info[2] != null) { // single country
305
return displayRegion(country, locale);
306         }
307
308         if (city == null) {
309             city = tzid.substring(tzid.lastIndexOf('/')+1).replace('_',' ');
310         }
311
312         String JavaDoc flbPat = getTZLocalizationInfo(locale, FALLBACK_FORMAT);
313         MessageFormat mf = new MessageFormat(flbPat);
314
315         return mf.format(new Object JavaDoc[] { city, country });
316     }
317
318     public static String JavaDoc displayRegion(String JavaDoc cityOrCountry, ULocale locale) {
319         String JavaDoc regPat = getTZLocalizationInfo(locale, REGION_FORMAT);
320         MessageFormat mf = new MessageFormat(regPat);
321         return mf.format(new Object JavaDoc[] { cityOrCountry });
322     }
323
324     public static String JavaDoc displayGMT(long value, ULocale locale) {
325         String JavaDoc msgpat = getTZLocalizationInfo(locale, GMT);
326         String JavaDoc dtepat = getTZLocalizationInfo(locale, HOUR);
327         
328         int n = dtepat.indexOf(';');
329         if (n != -1) {
330             if (value < 0) {
331                 value = - value;
332                 dtepat = dtepat.substring(n+1);
333             } else {
334                 dtepat = dtepat.substring(0, n);
335             }
336         }
337
338         final long mph = 3600000;
339         final long mpm = 60000;
340
341         SimpleDateFormat sdf = new SimpleDateFormat(dtepat, locale);
342         sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
343         String JavaDoc res = sdf.format(new Long JavaDoc(value));
344         MessageFormat mf = new MessageFormat(msgpat);
345         res = mf.format(new Object JavaDoc[] { res });
346         return res;
347     }
348
349     public static final String JavaDoc
350         HOUR = "hourFormat",
351         GMT = "gmtFormat",
352         REGION_FORMAT = "regionFormat",
353         FALLBACK_FORMAT = "fallbackFormat",
354         ZONE_STRINGS = "zoneStrings",
355         FORWARD_SLASH = "/";
356      
357     /**
358      * Get the index'd tz datum for this locale. Index must be one of the
359      * values PREFIX, HOUR, GMT, REGION_FORMAT, FALLBACK_FORMAT
360      */

361     public static String JavaDoc getTZLocalizationInfo(ULocale locale, String JavaDoc format) {
362         ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(locale);
363         return bundle.getStringWithFallback(ZONE_STRINGS+FORWARD_SLASH+format);
364     }
365
366     private static Set JavaDoc getValidIDs() {
367         // Construct list of time zones that are valid, according
368
// to the current underlying core JDK. We have to do this
369
// at runtime since we don't know what we're running on.
370
Set JavaDoc valid = new TreeSet JavaDoc();
371         valid.addAll(Arrays.asList(java.util.TimeZone.getAvailableIDs()));
372         return valid;
373     }
374
375     /**
376      * Empty string array.
377      */

378     private static final String JavaDoc[] EMPTY = new String JavaDoc[0];
379
380
381
382     /**
383      * Given an ID, open the appropriate resource for the given time zone.
384      * Dereference aliases if necessary.
385      * @param id zone id
386      * @param res resource, which must be ready for use (initialized but not open)
387      * @return top-level resource bundle
388      */

389     public static ICUResourceBundle openOlsonResource(String JavaDoc id)
390     {
391         if(!getOlsonMeta()){
392             return null;
393         }
394         ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
395         ICUResourceBundle res = getZoneByName(top, id);
396         // Dereference if this is an alias. Docs say result should be 1
397
// but it is 0 in 2.8 (?).
398
if (res.getSize() <= 1 && getOlsonMeta(top)) {
399             int deref = res.getInt() + 0;
400             ICUResourceBundle ares = top.get(kZONES); // dereference Zones section
401
res = ares.get(deref);
402         }
403         return res;
404     }
405     /**
406      * Fetch a specific zone by name. Replaces the getByKey call.
407      * @param top Top timezone resource
408      * @param id Time zone ID
409      * @return the zone's bundle if found, or undefined if error. Reuses oldbundle.
410      */

411     private static ICUResourceBundle getZoneByName(ICUResourceBundle top, String JavaDoc id) {
412         // load the Rules object
413
ICUResourceBundle tmp = top.get(kNAMES);
414         
415         // search for the string
416
int idx = findInStringArray(tmp, id);
417         
418         if((idx == -1)) {
419             // not found
420
throw new MissingResourceException JavaDoc(kNAMES, tmp.resPath, id);
421             //ures_close(oldbundle);
422
//oldbundle = NULL;
423
} else {
424             tmp = top.get(kZONES); // get Zones object from top
425
tmp = tmp.get(idx); // get nth Zone object
426
}
427         return tmp;
428     }
429     private static int findInStringArray(ICUResourceBundle array, String JavaDoc id){
430         int start = 0;
431         int limit = array.getSize();
432         int mid;
433         String JavaDoc u = null;
434         int lastMid = Integer.MAX_VALUE;
435         if((limit < 1)) {
436             return -1;
437         }
438         for (;;) {
439             mid = (int)((start + limit) / 2);
440             if (lastMid == mid) { /* Have we moved? */
441                 break; /* We haven't moved, and it wasn't found. */
442             }
443             lastMid = mid;
444             u = array.getString(mid);
445             if(u==null){
446                 break;
447             }
448             int r = id.compareTo(u);
449             if(r==0) {
450                 return mid;
451             } else if(r<0) {
452                 limit = mid;
453             } else {
454                 start = mid;
455             }
456         }
457         return -1;
458     }
459     private static final String JavaDoc kZONEINFO = "zoneinfo";
460     private static final String JavaDoc kREGIONS = "Regions";
461     private static final String JavaDoc kZONES = "Zones";
462     private static final String JavaDoc kRULES = "Rules";
463     private static final String JavaDoc kNAMES = "Names";
464     private static final String JavaDoc kDEFAULT = "Default";
465     private static final String JavaDoc kGMT_ID = "GMT";
466     private static final String JavaDoc kCUSTOM_ID= "Custom";
467     //private static ICUResourceBundle zoneBundle = null;
468
private static java.util.Enumeration JavaDoc idEnum = null;
469     private static SoftCache zoneCache = new SoftCache();
470     /**
471      * The Olson data is stored the "zoneinfo" resource bundle.
472      * Sub-resources are organized into three ranges of data: Zones, final
473      * rules, and country tables. There is also a meta-data resource
474      * which has 3 integers: The number of zones, rules, and countries,
475      * respectively. The country count includes the non-country 'Default'.
476      */

477     static int OLSON_ZONE_START = -1; // starting index of zones
478
static int OLSON_ZONE_COUNT = 0; // count of zones
479

480     /**
481      * Given a pointer to an open "zoneinfo" resource, load up the Olson
482      * meta-data. Return true if successful.
483      */

484     private static boolean getOlsonMeta(ICUResourceBundle top) {
485         if (OLSON_ZONE_START < 0) {
486             ICUResourceBundle res = top.get(kZONES);
487             OLSON_ZONE_COUNT = res.getSize();
488             OLSON_ZONE_START = 0;
489         }
490         return (OLSON_ZONE_START >= 0);
491     }
492
493     /**
494      * Load up the Olson meta-data. Return true if successful.
495      */

496     private static boolean getOlsonMeta() {
497         ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
498         if(OLSON_ZONE_START < 0) {
499             getOlsonMeta(top);
500         }
501         return (OLSON_ZONE_START >= 0);
502     }
503     /**
504      * Lookup the given name in our system zone table. If found,
505      * instantiate a new zone of that name and return it. If not
506      * found, return 0.
507      */

508     public static TimeZone getSystemTimeZone(String JavaDoc id) {
509         TimeZone z = (TimeZone)zoneCache.get(id);
510         if (z == null) {
511             try{
512                 ICUResourceBundle top = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "zoneinfo", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
513                 ICUResourceBundle res = openOlsonResource(id);
514                 z = new OlsonTimeZone(top, res);
515                 z.setID(id);
516                 zoneCache.put(id, z);
517             }catch(Exception JavaDoc ex){
518                 return null;
519             }
520         }
521         return (TimeZone)z.clone();
522     }
523     
524     public static TimeZone getGMT(){
525         TimeZone z = new SimpleTimeZone(0, kGMT_ID);
526         z.setID(kGMT_ID);
527         return z;
528     }
529
530     /**
531      * Parse a custom time zone identifier and return a corresponding zone.
532      * @param id a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or
533      * GMT[+-]hh.
534      * @return a newly created SimpleTimeZone with the given offset and
535      * no Daylight Savings Time, or null if the id cannot be parsed.
536     */

537     public static TimeZone getCustomTimeZone(String JavaDoc id){
538
539         NumberFormat numberFormat = null;
540         
541         String JavaDoc idUppercase = id.toUpperCase();
542
543         if (id.length() > kGMT_ID.length() &&
544             idUppercase.startsWith(kGMT_ID))
545         {
546             ParsePosition JavaDoc pos = new ParsePosition JavaDoc(kGMT_ID.length());
547             boolean negative = false;
548             long offset;
549
550             if (id.charAt(pos.getIndex()) == 0x002D /*'-'*/)
551                 negative = true;
552             else if (id.charAt(pos.getIndex()) != 0x002B /*'+'*/)
553                 return null;
554             pos.setIndex(pos.getIndex() + 1);
555
556             numberFormat = NumberFormat.getInstance();
557
558             numberFormat.setParseIntegerOnly(true);
559
560         
561             // Look for either hh:mm, hhmm, or hh
562
int start = pos.getIndex();
563             
564             Number JavaDoc n = numberFormat.parse(id, pos);
565             if (pos.getIndex() == start) {
566                 return null;
567             }
568             offset = n.longValue();
569
570             if (pos.getIndex() < id.length() &&
571                 id.charAt(pos.getIndex()) == 0x003A /*':'*/)
572             {
573                 // hh:mm
574
offset *= 60;
575                 pos.setIndex(pos.getIndex() + 1);
576                 int oldPos = pos.getIndex();
577                 n = numberFormat.parse(id, pos);
578                 if (pos.getIndex() == oldPos) {
579                     return null;
580                 }
581                 offset += n.longValue();
582             }
583             else
584             {
585                 // hhmm or hh
586

587                 // Be strict about interpreting something as hh; it must be
588
// an offset < 30, and it must be one or two digits. Thus
589
// 0010 is interpreted as 00:10, but 10 is interpreted as
590
// 10:00.
591
if (offset < 30 && (pos.getIndex() - start) <= 2)
592                     offset *= 60; // hh, from 00 to 29; 30 is 00:30
593
else
594                     offset = offset % 100 + offset / 100 * 60; // hhmm
595
}
596
597             if(negative)
598                 offset = -offset;
599
600             TimeZone z = new SimpleTimeZone((int)(offset * 60000), kCUSTOM_ID);
601             z.setID(kCUSTOM_ID);
602             return z;
603         }
604         return null;
605     }
606 }
607
Popular Tags