Home | History | Annotate | Download | only in icu
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package libcore.icu;
     18 
     19 import java.util.ArrayList;
     20 import java.util.Arrays;
     21 import java.util.Comparator;
     22 import java.util.HashMap;
     23 import java.util.Locale;
     24 import java.util.TimeZone;
     25 import libcore.util.BasicLruCache;
     26 import libcore.util.ZoneInfoDB;
     27 
     28 /**
     29  * Provides access to ICU's time zone name data.
     30  */
     31 public final class TimeZoneNames {
     32     private static final String[] availableTimeZoneIds = TimeZone.getAvailableIDs();
     33 
     34     /*
     35      * Offsets into the arrays returned by DateFormatSymbols.getZoneStrings.
     36      */
     37     public static final int OLSON_NAME = 0;
     38     public static final int LONG_NAME = 1;
     39     public static final int SHORT_NAME = 2;
     40     public static final int LONG_NAME_DST = 3;
     41     public static final int SHORT_NAME_DST = 4;
     42     public static final int NAME_COUNT = 5;
     43 
     44     private static final ZoneStringsCache cachedZoneStrings = new ZoneStringsCache();
     45     static {
     46         // Ensure that we pull in the zone strings for the root locale, en_US, and the
     47         // user's default locale. (All devices must support the root locale and en_US,
     48         // and they're used for various system things like HTTP headers.) Pre-populating
     49         // the cache is especially useful on Android because we'll share this via the Zygote.
     50         cachedZoneStrings.get(Locale.ROOT);
     51         cachedZoneStrings.get(Locale.US);
     52         cachedZoneStrings.get(Locale.getDefault());
     53     }
     54 
     55     public static class ZoneStringsCache extends BasicLruCache<Locale, String[][]> {
     56         // De-duplicate the strings (http://b/2672057).
     57         private final HashMap<String, String> internTable = new HashMap<String, String>();
     58 
     59         public ZoneStringsCache() {
     60             // We make room for all the time zones known to the system, since each set of strings
     61             // isn't particularly large (and we remove duplicates), but is currently (Honeycomb)
     62             // really expensive to compute.
     63             // If you change this, you might want to change the scope of the intern table too.
     64             super(availableTimeZoneIds.length);
     65         }
     66 
     67         @Override protected String[][] create(Locale locale) {
     68             long start = System.currentTimeMillis();
     69 
     70             // Set up the 2D array used to hold the names. The first column contains the Olson ids.
     71             String[][] result = new String[availableTimeZoneIds.length][5];
     72             for (int i = 0; i < availableTimeZoneIds.length; ++i) {
     73                 result[i][0] = availableTimeZoneIds[i];
     74             }
     75 
     76             long nativeStart = System.currentTimeMillis();
     77             fillZoneStrings(locale.toString(), result);
     78             long nativeEnd = System.currentTimeMillis();
     79 
     80             internStrings(result);
     81             // Ending up in this method too often is an easy way to make your app slow, so we ensure
     82             // it's easy to tell from the log (a) what we were doing, (b) how long it took, and
     83             // (c) that it's all ICU's fault.
     84             long end = System.currentTimeMillis();
     85             long nativeDuration = nativeEnd - nativeStart;
     86             long duration = end - start;
     87             System.logI("Loaded time zone names for \"" + locale + "\" in " + duration + "ms" +
     88                     " (" + nativeDuration + "ms in ICU)");
     89             return result;
     90         }
     91 
     92         private synchronized void internStrings(String[][] result) {
     93             for (int i = 0; i < result.length; ++i) {
     94                 for (int j = 1; j < NAME_COUNT; ++j) {
     95                     String original = result[i][j];
     96                     String nonDuplicate = internTable.get(original);
     97                     if (nonDuplicate == null) {
     98                         internTable.put(original, original);
     99                     } else {
    100                         result[i][j] = nonDuplicate;
    101                     }
    102                 }
    103             }
    104         }
    105     }
    106 
    107     private static final Comparator<String[]> ZONE_STRINGS_COMPARATOR = new Comparator<String[]>() {
    108         public int compare(String[] lhs, String[] rhs) {
    109             return lhs[OLSON_NAME].compareTo(rhs[OLSON_NAME]);
    110         }
    111     };
    112 
    113     private TimeZoneNames() {}
    114 
    115     /**
    116      * Returns the appropriate string from 'zoneStrings'. Used with getZoneStrings.
    117      */
    118     public static String getDisplayName(String[][] zoneStrings, String id, boolean daylight, int style) {
    119         String[] needle = new String[] { id };
    120         int index = Arrays.binarySearch(zoneStrings, needle, ZONE_STRINGS_COMPARATOR);
    121         if (index >= 0) {
    122             String[] row = zoneStrings[index];
    123             if (daylight) {
    124                 return (style == TimeZone.LONG) ? row[LONG_NAME_DST] : row[SHORT_NAME_DST];
    125             } else {
    126                 return (style == TimeZone.LONG) ? row[LONG_NAME] : row[SHORT_NAME];
    127             }
    128         }
    129         return null;
    130     }
    131 
    132     /**
    133      * Returns an array of time zone strings, as used by DateFormatSymbols.getZoneStrings.
    134      */
    135     public static String[][] getZoneStrings(Locale locale) {
    136         if (locale == null) {
    137             locale = Locale.getDefault();
    138         }
    139         return cachedZoneStrings.get(locale);
    140     }
    141 
    142     /**
    143      * Returns an array containing the time zone ids in use in the country corresponding to
    144      * the given locale. This is not necessary for Java API, but is used by telephony as a
    145      * fallback. We retrieve these strings from zone.tab rather than icu4c because the latter
    146      * supplies them in alphabetical order where zone.tab has them in a kind of "importance"
    147      * order (as defined in the zone.tab header).
    148      */
    149     public static String[] forLocale(Locale locale) {
    150         String countryCode = locale.getCountry();
    151         ArrayList<String> ids = new ArrayList<String>();
    152         for (String line : ZoneInfoDB.getInstance().getZoneTab().split("\n")) {
    153             if (line.startsWith(countryCode)) {
    154                 int olsonIdStart = line.indexOf('\t', 4) + 1;
    155                 int olsonIdEnd = line.indexOf('\t', olsonIdStart);
    156                 if (olsonIdEnd == -1) {
    157                     olsonIdEnd = line.length(); // Not all zone.tab lines have a comment.
    158                 }
    159                 ids.add(line.substring(olsonIdStart, olsonIdEnd));
    160             }
    161         }
    162         return ids.toArray(new String[ids.size()]);
    163     }
    164 
    165     private static native void fillZoneStrings(String locale, String[][] result);
    166 }
    167