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 com.android.icu.text.TimeZoneNamesNative;
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 java.util.concurrent.TimeUnit;
26 import libcore.util.BasicLruCache;
27 
28 /**
29  * Provides access to ICU's time zone name data.
30  * @hide
31  */
32 public final class TimeZoneNames {
33     private static final String[] availableTimeZoneIds = TimeZone.getAvailableIDs();
34 
35     /*
36      * Offsets into the arrays returned by DateFormatSymbols.getZoneStrings.
37      */
38     private static final int OLSON_NAME = 0;
39     private static final int LONG_NAME = 1;
40     private static final int SHORT_NAME = 2;
41     private static final int LONG_NAME_DST = 3;
42     private static final int SHORT_NAME_DST = 4;
43     private static final int NAME_COUNT = 5;
44 
45     private static final ZoneStringsCache cachedZoneStrings = new ZoneStringsCache();
46 
47     private static class ZoneStringsCache extends BasicLruCache<Locale, String[][]> {
ZoneStringsCache()48         public ZoneStringsCache() {
49             super(5); // Room for a handful of locales.
50         }
51 
create(Locale locale)52         @Override protected String[][] create(Locale locale) {
53             long start = System.nanoTime();
54 
55             long nativeStart = System.nanoTime();
56             String[][] result = TimeZoneNamesNative.getFilledZoneStrings(locale, availableTimeZoneIds);
57             long nativeEnd = System.nanoTime();
58 
59             addOffsetStrings(result);
60             internStrings(result);
61             // Ending up in this method too often is an easy way to make your app slow, so we ensure
62             // it's easy to tell from the log (a) what we were doing, (b) how long it took, and
63             // (c) that it's all ICU's fault.
64             long end = System.nanoTime();
65             long nativeDuration = TimeUnit.NANOSECONDS.toMillis(nativeEnd - nativeStart);
66             long duration = TimeUnit.NANOSECONDS.toMillis(end - start);
67             System.logI("Loaded time zone names for \"" + locale + "\" in " + duration + "ms" +
68                         " (" + nativeDuration + "ms in ICU)");
69             return result;
70         }
71 
72         /**
73          * Generate offset strings for cases where we don't have a name. Note that this is a
74          * potentially slow operation, as we need to load the timezone data for all affected
75          * time zones.
76          */
addOffsetStrings(String[][] result)77         private void addOffsetStrings(String[][] result) {
78             for (int i = 0; i < result.length; ++i) {
79                 TimeZone tz = null;
80                 for (int j = 1; j < NAME_COUNT; ++j) {
81                     if (result[i][j] != null) {
82                         continue;
83                     }
84                     if (tz == null) {
85                         tz = TimeZone.getTimeZone(result[i][0]);
86                     }
87                     int offsetMillis = tz.getRawOffset();
88                     if (j == LONG_NAME_DST || j == SHORT_NAME_DST) {
89                         offsetMillis += tz.getDSTSavings();
90                     }
91                     result[i][j] = TimeZone.createGmtOffsetString(
92                             /* includeGmt */ true, /*includeMinuteSeparator */true, offsetMillis);
93                 }
94             }
95         }
96 
97         // De-duplicate the strings (http://b/2672057).
internStrings(String[][] result)98         private void internStrings(String[][] result) {
99             HashMap<String, String> internTable = new HashMap<String, String>();
100             for (int i = 0; i < result.length; ++i) {
101                 for (int j = 1; j < NAME_COUNT; ++j) {
102                     String original = result[i][j];
103                     String nonDuplicate = internTable.get(original);
104                     if (nonDuplicate == null) {
105                         internTable.put(original, original);
106                     } else {
107                         result[i][j] = nonDuplicate;
108                     }
109                 }
110             }
111         }
112     }
113 
114     private static final Comparator<String[]> ZONE_STRINGS_COMPARATOR = new Comparator<String[]>() {
115         public int compare(String[] lhs, String[] rhs) {
116             return lhs[OLSON_NAME].compareTo(rhs[OLSON_NAME]);
117         }
118     };
119 
TimeZoneNames()120     private TimeZoneNames() {}
121 
122     /**
123      * Returns the appropriate string from 'zoneStrings'. Used with getZoneStrings.
124      */
getDisplayName(String[][] zoneStrings, String id, boolean daylight, int style)125     public static String getDisplayName(String[][] zoneStrings, String id, boolean daylight, int style) {
126         String[] needle = new String[] { id };
127         int index = Arrays.binarySearch(zoneStrings, needle, ZONE_STRINGS_COMPARATOR);
128         if (index >= 0) {
129             String[] row = zoneStrings[index];
130             if (daylight) {
131                 return (style == TimeZone.LONG) ? row[LONG_NAME_DST] : row[SHORT_NAME_DST];
132             } else {
133                 return (style == TimeZone.LONG) ? row[LONG_NAME] : row[SHORT_NAME];
134             }
135         }
136         return null;
137     }
138 
139     /**
140      * Returns an array of time zone strings, as used by DateFormatSymbols.getZoneStrings.
141      */
getZoneStrings(Locale locale)142     public static String[][] getZoneStrings(Locale locale) {
143         if (locale == null) {
144             locale = Locale.getDefault();
145         }
146         return cachedZoneStrings.get(locale);
147     }
148 
149     /**
150      * A utility method to get display names in various {@param namesTypes} from
151      * ICU4J's {@param timeZoneNames}.
152      */
getDisplayNames(android.icu.text.TimeZoneNames timeZoneNames, String tzId, android.icu.text.TimeZoneNames.NameType[] nameTypes, long date, String[] names, int namesOffset)153     public static void getDisplayNames(android.icu.text.TimeZoneNames timeZoneNames, String tzId,
154             android.icu.text.TimeZoneNames.NameType[] nameTypes, long date, String[] names,
155             int namesOffset) {
156         for (int i = 0; i < nameTypes.length; i++) {
157             android.icu.text.TimeZoneNames.NameType nameType = nameTypes[i];
158             names[namesOffset + i] = timeZoneNames.getDisplayName(tzId, nameType, date);
159         }
160     }
161 }
162