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);
Locale.getDefault()52         cachedZoneStrings.get(Locale.getDefault());
53     }
54 
55     public static class ZoneStringsCache extends BasicLruCache<Locale, String[][]> {
ZoneStringsCache()56         public ZoneStringsCache() {
57             super(5); // Room for a handful of locales.
58         }
59 
create(Locale locale)60         @Override protected String[][] create(Locale locale) {
61             long start = System.currentTimeMillis();
62 
63             // Set up the 2D array used to hold the names. The first column contains the Olson ids.
64             String[][] result = new String[availableTimeZoneIds.length][5];
65             for (int i = 0; i < availableTimeZoneIds.length; ++i) {
66                 result[i][0] = availableTimeZoneIds[i];
67             }
68 
69             long nativeStart = System.currentTimeMillis();
70             fillZoneStrings(locale.toString(), result);
71             long nativeEnd = System.currentTimeMillis();
72 
73             internStrings(result);
74             // Ending up in this method too often is an easy way to make your app slow, so we ensure
75             // it's easy to tell from the log (a) what we were doing, (b) how long it took, and
76             // (c) that it's all ICU's fault.
77             long end = System.currentTimeMillis();
78             long nativeDuration = nativeEnd - nativeStart;
79             long duration = end - start;
80             System.logI("Loaded time zone names for \"" + locale + "\" in " + duration + "ms" +
81                         " (" + nativeDuration + "ms in ICU)");
82             return result;
83         }
84 
85         // De-duplicate the strings (http://b/2672057).
internStrings(String[][] result)86         private synchronized void internStrings(String[][] result) {
87             HashMap<String, String> internTable = new HashMap<String, String>();
88             for (int i = 0; i < result.length; ++i) {
89                 for (int j = 1; j < NAME_COUNT; ++j) {
90                     String original = result[i][j];
91                     String nonDuplicate = internTable.get(original);
92                     if (nonDuplicate == null) {
93                         internTable.put(original, original);
94                     } else {
95                         result[i][j] = nonDuplicate;
96                     }
97                 }
98             }
99         }
100     }
101 
102     private static final Comparator<String[]> ZONE_STRINGS_COMPARATOR = new Comparator<String[]>() {
103         public int compare(String[] lhs, String[] rhs) {
104             return lhs[OLSON_NAME].compareTo(rhs[OLSON_NAME]);
105         }
106     };
107 
TimeZoneNames()108     private TimeZoneNames() {}
109 
110     /**
111      * Returns the appropriate string from 'zoneStrings'. Used with getZoneStrings.
112      */
getDisplayName(String[][] zoneStrings, String id, boolean daylight, int style)113     public static String getDisplayName(String[][] zoneStrings, String id, boolean daylight, int style) {
114         String[] needle = new String[] { id };
115         int index = Arrays.binarySearch(zoneStrings, needle, ZONE_STRINGS_COMPARATOR);
116         if (index >= 0) {
117             String[] row = zoneStrings[index];
118             if (daylight) {
119                 return (style == TimeZone.LONG) ? row[LONG_NAME_DST] : row[SHORT_NAME_DST];
120             } else {
121                 return (style == TimeZone.LONG) ? row[LONG_NAME] : row[SHORT_NAME];
122             }
123         }
124         return null;
125     }
126 
127     /**
128      * Returns an array of time zone strings, as used by DateFormatSymbols.getZoneStrings.
129      */
getZoneStrings(Locale locale)130     public static String[][] getZoneStrings(Locale locale) {
131         if (locale == null) {
132             locale = Locale.getDefault();
133         }
134         return cachedZoneStrings.get(locale);
135     }
136 
137     /**
138      * Returns an array containing the time zone ids in use in the country corresponding to
139      * the given locale. This is not necessary for Java API, but is used by telephony as a
140      * fallback. We retrieve these strings from zone.tab rather than icu4c because the latter
141      * supplies them in alphabetical order where zone.tab has them in a kind of "importance"
142      * order (as defined in the zone.tab header).
143      */
forLocale(Locale locale)144     public static String[] forLocale(Locale locale) {
145         String countryCode = locale.getCountry();
146         ArrayList<String> ids = new ArrayList<String>();
147         for (String line : ZoneInfoDB.getInstance().getZoneTab().split("\n")) {
148             if (line.startsWith(countryCode)) {
149                 int olsonIdStart = line.indexOf('\t', 4) + 1;
150                 int olsonIdEnd = line.indexOf('\t', olsonIdStart);
151                 if (olsonIdEnd == -1) {
152                     olsonIdEnd = line.length(); // Not all zone.tab lines have a comment.
153                 }
154                 ids.add(line.substring(olsonIdStart, olsonIdEnd));
155             }
156         }
157         return ids.toArray(new String[ids.size()]);
158     }
159 
getExemplarLocation(String locale, String tz)160     public static native String getExemplarLocation(String locale, String tz);
161 
fillZoneStrings(String locale, String[][] result)162     private static native void fillZoneStrings(String locale, String[][] result);
163 }
164