TimeZoneNames.java revision 3020db601855fda8e33c870510204750b2386f3d
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
17package libcore.icu;
18
19import java.util.ArrayList;
20import java.util.Arrays;
21import java.util.Comparator;
22import java.util.HashMap;
23import java.util.Locale;
24import java.util.TimeZone;
25import libcore.util.BasicLruCache;
26import libcore.util.ZoneInfoDB;
27
28/**
29 * Provides access to ICU's time zone name data.
30 */
31public 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        public ZoneStringsCache() {
57            super(5); // Room for a handful of locales.
58        }
59
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).
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
108    private TimeZoneNames() {}
109
110    /**
111     * Returns the appropriate string from 'zoneStrings'. Used with getZoneStrings.
112     */
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     */
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     */
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
160    private static native void fillZoneStrings(String locale, String[][] result);
161}
162