• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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 android.icu.impl.ICUData;
20 import android.icu.impl.ICUResourceBundle;
21 import android.icu.text.NumberingSystem;
22 import android.icu.util.UResourceBundle;
23 import dalvik.annotation.compat.UnsupportedAppUsage;
24 import java.text.DateFormat;
25 import java.util.HashMap;
26 import java.util.Locale;
27 import java.util.MissingResourceException;
28 import libcore.util.Objects;
29 
30 /**
31  * Passes locale-specific from ICU native code to Java.
32  * <p>
33  * Note that you share these; you must not alter any of the fields, nor their array elements
34  * in the case of arrays. If you ever expose any of these things to user code, you must give
35  * them a clone rather than the original.
36  * @hide
37  */
38 @libcore.api.CorePlatformApi
39 public final class LocaleData {
40     // A cache for the locale-specific data.
41     private static final HashMap<String, LocaleData> localeDataCache = new HashMap<String, LocaleData>();
42     static {
43         // Ensure that we pull in the locale data for the root locale, en_US, and the
44         // user's default locale. (All devices must support the root locale and en_US,
45         // and they're used for various system things like HTTP headers.) Pre-populating
46         // the cache is especially useful on Android because we'll share this via the Zygote.
47         get(Locale.ROOT);
48         get(Locale.US);
Locale.getDefault()49         get(Locale.getDefault());
50     }
51 
52     // Used by Calendar.
53     @UnsupportedAppUsage
54     @libcore.api.CorePlatformApi
55     public Integer firstDayOfWeek;
56     @UnsupportedAppUsage
57     public Integer minimalDaysInFirstWeek;
58 
59     // Used by DateFormatSymbols.
60     @libcore.api.CorePlatformApi
61     public String[] amPm; // "AM", "PM".
62     public String[] eras; // "BC", "AD".
63 
64     @libcore.api.CorePlatformApi
65     public String[] longMonthNames; // "January", ...
66     @UnsupportedAppUsage
67     @libcore.api.CorePlatformApi
68     public String[] shortMonthNames; // "Jan", ...
69     @libcore.api.CorePlatformApi
70     public String[] tinyMonthNames; // "J", ...
71     @libcore.api.CorePlatformApi
72     public String[] longStandAloneMonthNames; // "January", ...
73     @UnsupportedAppUsage
74     @libcore.api.CorePlatformApi
75     public String[] shortStandAloneMonthNames; // "Jan", ...
76     @libcore.api.CorePlatformApi
77     public String[] tinyStandAloneMonthNames; // "J", ...
78 
79     @libcore.api.CorePlatformApi
80     public String[] longWeekdayNames; // "Sunday", ...
81     @libcore.api.CorePlatformApi
82     public String[] shortWeekdayNames; // "Sun", ...
83     @libcore.api.CorePlatformApi
84     public String[] tinyWeekdayNames; // "S", ...
85     @UnsupportedAppUsage
86     @libcore.api.CorePlatformApi
87     public String[] longStandAloneWeekdayNames; // "Sunday", ...
88     @UnsupportedAppUsage
89     @libcore.api.CorePlatformApi
90     public String[] shortStandAloneWeekdayNames; // "Sun", ...
91     @libcore.api.CorePlatformApi
92     public String[] tinyStandAloneWeekdayNames; // "S", ...
93 
94     // Used by frameworks/base DateSorter and DateUtils.
95     @libcore.api.CorePlatformApi
96     public String yesterday; // "Yesterday".
97     @UnsupportedAppUsage
98     @libcore.api.CorePlatformApi
99     public String today; // "Today".
100     @UnsupportedAppUsage
101     public String tomorrow; // "Tomorrow".
102 
103     public String fullTimeFormat;
104     public String longTimeFormat;
105     public String mediumTimeFormat;
106     public String shortTimeFormat;
107 
108     public String fullDateFormat;
109     public String longDateFormat;
110     public String mediumDateFormat;
111     public String shortDateFormat;
112 
113     // Used by TimePicker. Not currently used by UTS#35.
114     @libcore.api.CorePlatformApi
115     public String narrowAm; // "a".
116     @libcore.api.CorePlatformApi
117     public String narrowPm; // "p".
118 
119     // Used by DateFormat to implement 12- and 24-hour SHORT and MEDIUM.
120     // They are also used directly by frameworks code.
121     @UnsupportedAppUsage
122     @libcore.api.CorePlatformApi
123     public String timeFormat_hm;
124     @UnsupportedAppUsage
125     @libcore.api.CorePlatformApi
126     public String timeFormat_Hm;
127     @libcore.api.CorePlatformApi
128     public String timeFormat_hms;
129     @libcore.api.CorePlatformApi
130     public String timeFormat_Hms;
131 
132     // Used by DecimalFormatSymbols.
133     @UnsupportedAppUsage
134     @libcore.api.CorePlatformApi
135     public char zeroDigit;
136     public char decimalSeparator;
137     public char groupingSeparator;
138     public char patternSeparator;
139     public String percent;
140     public String perMill;
141     public char monetarySeparator;
142     public String minusSign;
143     public String exponentSeparator;
144     public String infinity;
145     public String NaN;
146     // Also used by Currency.
147     public String currencySymbol;
148     public String internationalCurrencySymbol;
149 
150     // Used by DecimalFormat and NumberFormat.
151     public String numberPattern;
152     public String integerPattern;
153     public String currencyPattern;
154     public String percentPattern;
155 
LocaleData()156     private LocaleData() {
157     }
158 
159     @UnsupportedAppUsage
mapInvalidAndNullLocales(Locale locale)160     public static Locale mapInvalidAndNullLocales(Locale locale) {
161         if (locale == null) {
162             return Locale.getDefault();
163         }
164 
165         if ("und".equals(locale.toLanguageTag())) {
166             return Locale.ROOT;
167         }
168 
169         return locale;
170     }
171 
172     /**
173      * Returns a shared LocaleData for the given locale.
174      */
175     @UnsupportedAppUsage
176     @libcore.api.CorePlatformApi
get(Locale locale)177     public static LocaleData get(Locale locale) {
178         if (locale == null) {
179             throw new NullPointerException("locale == null");
180         }
181 
182         final String languageTag = locale.toLanguageTag();
183         synchronized (localeDataCache) {
184             LocaleData localeData = localeDataCache.get(languageTag);
185             if (localeData != null) {
186                 return localeData;
187             }
188         }
189         LocaleData newLocaleData = initLocaleData(locale);
190         synchronized (localeDataCache) {
191             LocaleData localeData = localeDataCache.get(languageTag);
192             if (localeData != null) {
193                 return localeData;
194             }
195             localeDataCache.put(languageTag, newLocaleData);
196             return newLocaleData;
197         }
198     }
199 
toString()200     @Override public String toString() {
201         return Objects.toString(this);
202     }
203 
204     @libcore.api.CorePlatformApi
getDateFormat(int style)205     public String getDateFormat(int style) {
206         switch (style) {
207         case DateFormat.SHORT:
208             return shortDateFormat;
209         case DateFormat.MEDIUM:
210             return mediumDateFormat;
211         case DateFormat.LONG:
212             return longDateFormat;
213         case DateFormat.FULL:
214             return fullDateFormat;
215         }
216         throw new AssertionError();
217     }
218 
getTimeFormat(int style)219     public String getTimeFormat(int style) {
220         switch (style) {
221         case DateFormat.SHORT:
222             if (DateFormat.is24Hour == null) {
223                 return shortTimeFormat;
224             } else {
225                 return DateFormat.is24Hour ? timeFormat_Hm : timeFormat_hm;
226             }
227         case DateFormat.MEDIUM:
228             if (DateFormat.is24Hour == null) {
229                 return mediumTimeFormat;
230             } else {
231                 return DateFormat.is24Hour ? timeFormat_Hms : timeFormat_hms;
232             }
233         case DateFormat.LONG:
234             // CLDR doesn't really have anything we can use to obey the 12-/24-hour preference.
235             return longTimeFormat;
236         case DateFormat.FULL:
237             // CLDR doesn't really have anything we can use to obey the 12-/24-hour preference.
238             return fullTimeFormat;
239         }
240         throw new AssertionError();
241     }
242 
initLocaleData(Locale locale)243     private static LocaleData initLocaleData(Locale locale) {
244         LocaleData localeData = new LocaleData();
245         if (!ICU.initLocaleDataNative(locale.toLanguageTag(), localeData)) {
246             throw new AssertionError("couldn't initialize LocaleData for locale " + locale);
247         }
248 
249         // Libcore localizes pattern separator while ICU doesn't. http://b/112080617
250         initializePatternSeparator(localeData, locale);
251 
252         // Get the SHORT and MEDIUM 12- and 24-hour time format strings.
253         localeData.timeFormat_hm = ICU.getBestDateTimePattern("hm", locale);
254         localeData.timeFormat_Hm = ICU.getBestDateTimePattern("Hm", locale);
255         localeData.timeFormat_hms = ICU.getBestDateTimePattern("hms", locale);
256         localeData.timeFormat_Hms = ICU.getBestDateTimePattern("Hms", locale);
257 
258         // Fix up a couple of patterns.
259         if (localeData.fullTimeFormat != null) {
260             // There are some full time format patterns in ICU that use the pattern character 'v'.
261             // Java doesn't accept this, so we replace it with 'z' which has about the same result
262             // as 'v', the timezone name.
263             // 'v' -> "PT", 'z' -> "PST", v is the generic timezone and z the standard tz
264             // "vvvv" -> "Pacific Time", "zzzz" -> "Pacific Standard Time"
265             localeData.fullTimeFormat = localeData.fullTimeFormat.replace('v', 'z');
266         }
267         if (localeData.numberPattern != null) {
268             // The number pattern might contain positive and negative subpatterns. Arabic, for
269             // example, might look like "#,##0.###;#,##0.###-" because the minus sign should be
270             // written last. Macedonian supposedly looks something like "#,##0.###;(#,##0.###)".
271             // (The negative subpattern is optional, though, and not present in most locales.)
272             // By only swallowing '#'es and ','s after the '.', we ensure that we don't
273             // accidentally eat too much.
274             localeData.integerPattern = localeData.numberPattern.replaceAll("\\.[#,]*", "");
275         }
276         return localeData;
277     }
278 
279     // Libcore localizes pattern separator while ICU doesn't. http://b/112080617
initializePatternSeparator(LocaleData localeData, Locale locale)280     private static void initializePatternSeparator(LocaleData localeData, Locale locale) {
281         NumberingSystem ns = NumberingSystem.getInstance(locale);
282         // A numbering system could be numeric or algorithmic. DecimalFormat can only use
283         // a numeric and decimal-based (radix == 10) system. Fallback to a Latin, a known numeric
284         // and decimal-based if the default numbering system isn't. All locales should have data
285         // for Latin numbering system after locale data fallback. See Numbering system section
286         // in Unicode Technical Standard #35 for more details.
287         String nsName = ns != null && ns.getRadix() == 10 && !ns.isAlgorithmic()
288             ? ns.getName() : "latn";
289         ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(
290             ICUData.ICU_BASE_NAME, locale);
291         String patternSeparator = null;
292         // The fallback of number format data isn't well-specified in the spec.
293         // But the separator can't be null / empty, and ICU uses Latin numbering system
294         // as fallback.
295         if (!"latn".equals(nsName)) {
296             try {
297                 patternSeparator = rb.getStringWithFallback(
298                     "NumberElements/" + nsName +"/symbols/list");
299             } catch (MissingResourceException e) {
300                 // Try Latin numbering system later
301             }
302         }
303 
304         if (patternSeparator == null) {
305             try {
306                 patternSeparator = rb.getStringWithFallback("NumberElements/latn/symbols/list");
307             } catch (MissingResourceException e) {
308                 // Fallback to the default separator ';'.
309             }
310         }
311 
312         if (patternSeparator == null || patternSeparator.isEmpty()) {
313             patternSeparator = ";";
314         }
315 
316         // Pattern separator in libcore supports single java character only.
317         localeData.patternSeparator = patternSeparator.charAt(0);
318     }
319 }
320