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