• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.compat.annotation.UnsupportedAppUsage;
20 import android.icu.text.DateTimePatternGenerator;
21 import android.icu.text.TimeZoneFormat;
22 import android.icu.util.Currency;
23 import android.icu.util.IllformedLocaleException;
24 import android.icu.util.ULocale;
25 
26 import com.android.icu.util.ExtendedCalendar;
27 import com.android.icu.util.LocaleNative;
28 
29 import java.util.Collections;
30 import java.util.Date;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.LinkedHashSet;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.Map.Entry;
37 import java.util.Set;
38 import libcore.util.BasicLruCache;
39 
40 /**
41  * Makes ICU data accessible to Java.
42  * @hide
43  */
44 public final class ICU {
45 
46   @UnsupportedAppUsage
47   private static final BasicLruCache<String, String> CACHED_PATTERNS =
48       new BasicLruCache<String, String>(8);
49 
50   private static Locale[] availableLocalesCache;
51 
52   private static String[] isoCountries;
53   private static Set<String> isoCountriesSet;
54 
55   private static String[] isoLanguages;
56 
57   /**
58    * Avoid initialization with many dependencies here, because when this is called,
59    * lower-level classes, e.g. java.lang.System, are not initialized and java.lang.System
60    * relies on getIcuVersion().
61    */
62   static {
63 
64   }
65 
ICU()66   private ICU() {
67   }
68 
initializeCacheInZygote()69   public static void initializeCacheInZygote() {
70     // Fill CACHED_PATTERNS with the patterns from default locale and en-US initially.
71     // This should be called in Zygote pre-fork process and the initial values in the cache
72     // can be shared among app. The cache was filled by LocaleData in the older Android platform,
73     // but moved here, due to an performance issue http://b/161846393.
74     // It initializes 2 x 4 = 8 values in the CACHED_PATTERNS whose max size should be >= 8.
75     for (Locale locale : new Locale[] {Locale.US, Locale.getDefault()}) {
76       getTimePattern(locale, false, false);
77       getTimePattern(locale, false, true);
78       getTimePattern(locale, true, false);
79       getTimePattern(locale, true, true);
80     }
81   }
82 
83   /**
84    * Returns an array of two-letter ISO 639-1 language codes, either from ICU or our cache.
85    */
getISOLanguages()86   public static String[] getISOLanguages() {
87     if (isoLanguages == null) {
88       isoLanguages = getISOLanguagesNative();
89     }
90     return isoLanguages.clone();
91   }
92 
93   /**
94    * Returns an array of two-letter ISO 3166 country codes, either from ICU or our cache.
95    */
getISOCountries()96   public static String[] getISOCountries() {
97     return getISOCountriesInternal().clone();
98   }
99 
100   /**
101    * Returns true if the string is a 2-letter ISO 3166 country code.
102    */
isIsoCountry(String country)103   public static boolean isIsoCountry(String country) {
104     if (isoCountriesSet == null) {
105       String[] isoCountries = getISOCountriesInternal();
106       Set<String> newSet = new HashSet<>(isoCountries.length);
107       for (String isoCountry : isoCountries) {
108         newSet.add(isoCountry);
109       }
110       isoCountriesSet = newSet;
111     }
112     return country != null && isoCountriesSet.contains(country);
113   }
114 
getISOCountriesInternal()115   private static String[] getISOCountriesInternal() {
116     if (isoCountries == null) {
117       isoCountries = getISOCountriesNative();
118     }
119     return isoCountries;
120   }
121 
122 
123 
124   private static final int IDX_LANGUAGE = 0;
125   private static final int IDX_SCRIPT = 1;
126   private static final int IDX_REGION = 2;
127   private static final int IDX_VARIANT = 3;
128 
129   /*
130    * Parse the {Language, Script, Region, Variant*} section of the ICU locale
131    * ID. This is the bit that appears before the keyword separate "@". The general
132    * structure is a series of ASCII alphanumeric strings (subtags)
133    * separated by underscores.
134    *
135    * Each subtag is interpreted according to its position in the list of subtags
136    * AND its length (groan...). The various cases are explained in comments
137    * below.
138    */
parseLangScriptRegionAndVariants(String string, String[] outputArray)139   private static void parseLangScriptRegionAndVariants(String string,
140           String[] outputArray) {
141     final int first = string.indexOf('_');
142     final int second = string.indexOf('_', first + 1);
143     final int third = string.indexOf('_', second + 1);
144 
145     if (first == -1) {
146       outputArray[IDX_LANGUAGE] = string;
147     } else if (second == -1) {
148       // Language and country ("ja_JP") OR
149       // Language and script ("en_Latn") OR
150       // Language and variant ("en_POSIX").
151 
152       outputArray[IDX_LANGUAGE] = string.substring(0, first);
153       final String secondString = string.substring(first + 1);
154 
155       if (secondString.length() == 4) {
156           // 4 Letter ISO script code.
157           outputArray[IDX_SCRIPT] = secondString;
158       } else if (secondString.length() == 2 || secondString.length() == 3) {
159           // 2 or 3 Letter region code.
160           outputArray[IDX_REGION] = secondString;
161       } else {
162           // If we're here, the length of the second half is either 1 or greater
163           // than 5. Assume that ICU won't hand us malformed tags, and therefore
164           // assume the rest of the string is a series of variant tags.
165           outputArray[IDX_VARIANT] = secondString;
166       }
167     } else if (third == -1) {
168       // Language and country and variant ("ja_JP_TRADITIONAL") OR
169       // Language and script and variant ("en_Latn_POSIX") OR
170       // Language and script and region ("en_Latn_US"). OR
171       // Language and variant with multiple subtags ("en_POSIX_XISOP")
172 
173       outputArray[IDX_LANGUAGE] = string.substring(0, first);
174       final String secondString = string.substring(first + 1, second);
175       final String thirdString = string.substring(second + 1);
176 
177       if (secondString.length() == 4) {
178           // The second subtag is a script.
179           outputArray[IDX_SCRIPT] = secondString;
180 
181           // The third subtag can be either a region or a variant, depending
182           // on its length.
183           if (thirdString.length() == 2 || thirdString.length() == 3 ||
184                   thirdString.isEmpty()) {
185               outputArray[IDX_REGION] = thirdString;
186           } else {
187               outputArray[IDX_VARIANT] = thirdString;
188           }
189       } else if (secondString.isEmpty() ||
190               secondString.length() == 2 || secondString.length() == 3) {
191           // The second string is a region, and the third a variant.
192           outputArray[IDX_REGION] = secondString;
193           outputArray[IDX_VARIANT] = thirdString;
194       } else {
195           // Variant with multiple subtags.
196           outputArray[IDX_VARIANT] = string.substring(first + 1);
197       }
198     } else {
199       // Language, script, region and variant with 1 or more subtags
200       // ("en_Latn_US_POSIX") OR
201       // Language, region and variant with 2 or more subtags
202       // (en_US_POSIX_VARIANT).
203       outputArray[IDX_LANGUAGE] = string.substring(0, first);
204       final String secondString = string.substring(first + 1, second);
205       if (secondString.length() == 4) {
206           outputArray[IDX_SCRIPT] = secondString;
207           outputArray[IDX_REGION] = string.substring(second + 1, third);
208           outputArray[IDX_VARIANT] = string.substring(third + 1);
209       } else {
210           outputArray[IDX_REGION] = secondString;
211           outputArray[IDX_VARIANT] = string.substring(second + 1);
212       }
213     }
214   }
215 
216   /**
217    * Returns the appropriate {@code Locale} given a {@code String} of the form returned
218    * by {@code toString}. This is very lenient, and doesn't care what's between the underscores:
219    * this method can parse strings that {@code Locale.toString} won't produce.
220    * Used to remove duplication.
221    */
localeFromIcuLocaleId(String localeId)222   public static Locale localeFromIcuLocaleId(String localeId) {
223     // @ == ULOC_KEYWORD_SEPARATOR_UNICODE (uloc.h).
224     final int extensionsIndex = localeId.indexOf('@');
225 
226     Map<Character, String> extensionsMap = Collections.EMPTY_MAP;
227     Map<String, String> unicodeKeywordsMap = Collections.EMPTY_MAP;
228     Set<String> unicodeAttributeSet = Collections.EMPTY_SET;
229 
230     if (extensionsIndex != -1) {
231       extensionsMap = new HashMap<Character, String>();
232       unicodeKeywordsMap = new HashMap<String, String>();
233       unicodeAttributeSet = new HashSet<String>();
234 
235       // ICU sends us a semi-colon (ULOC_KEYWORD_ITEM_SEPARATOR) delimited string
236       // containing all "keywords" it could parse. An ICU keyword is a key-value pair
237       // separated by an "=" (ULOC_KEYWORD_ASSIGN).
238       //
239       // Each keyword item can be one of three things :
240       // - A unicode extension attribute list: In this case the item key is "attribute"
241       //   and the value is a hyphen separated list of unicode attributes.
242       // - A unicode extension keyword: In this case, the item key will be larger than
243       //   1 char in length, and the value will be the unicode extension value.
244       // - A BCP-47 extension subtag: In this case, the item key will be exactly one
245       //   char in length, and the value will be a sequence of unparsed subtags that
246       //   represent the extension.
247       //
248       // Note that this implies that unicode extension keywords are "promoted" to
249       // to the same namespace as the top level extension subtags and their values.
250       // There can't be any collisions in practice because the BCP-47 spec imposes
251       // restrictions on their lengths.
252       final String extensionsString = localeId.substring(extensionsIndex + 1);
253       final String[] extensions = extensionsString.split(";");
254       for (String extension : extensions) {
255         // This is the special key for the unicode attributes
256         if (extension.startsWith("attribute=")) {
257           String unicodeAttributeValues = extension.substring("attribute=".length());
258           for (String unicodeAttribute : unicodeAttributeValues.split("-")) {
259             unicodeAttributeSet.add(unicodeAttribute);
260           }
261         } else {
262           final int separatorIndex = extension.indexOf('=');
263 
264           if (separatorIndex == 1) {
265             // This is a BCP-47 extension subtag.
266             final String value = extension.substring(2);
267             final char extensionId = extension.charAt(0);
268 
269             extensionsMap.put(extensionId, value);
270           } else {
271             // This is a unicode extension keyword.
272             unicodeKeywordsMap.put(extension.substring(0, separatorIndex),
273             extension.substring(separatorIndex + 1));
274           }
275         }
276       }
277     }
278 
279     final String[] outputArray = new String[] { "", "", "", "" };
280     if (extensionsIndex == -1) {
281       parseLangScriptRegionAndVariants(localeId, outputArray);
282     } else {
283       parseLangScriptRegionAndVariants(localeId.substring(0, extensionsIndex),
284           outputArray);
285     }
286     Locale.Builder builder = new Locale.Builder();
287     builder.setLanguage(outputArray[IDX_LANGUAGE]);
288     builder.setRegion(outputArray[IDX_REGION]);
289     builder.setVariant(outputArray[IDX_VARIANT]);
290     builder.setScript(outputArray[IDX_SCRIPT]);
291     for (String attribute : unicodeAttributeSet) {
292       builder.addUnicodeLocaleAttribute(attribute);
293     }
294     for (Entry<String, String> keyword : unicodeKeywordsMap.entrySet()) {
295       builder.setUnicodeLocaleKeyword(keyword.getKey(), keyword.getValue());
296     }
297 
298     for (Entry<Character, String> extension : extensionsMap.entrySet()) {
299       builder.setExtension(extension.getKey(), extension.getValue());
300     }
301 
302     return builder.build();
303   }
304 
localesFromStrings(String[] localeNames)305   public static Locale[] localesFromStrings(String[] localeNames) {
306     // We need to remove duplicates caused by the conversion of "he" to "iw", et cetera.
307     // Java needs the obsolete code, ICU needs the modern code, but we let ICU know about
308     // both so that we never need to convert back when talking to it.
309     LinkedHashSet<Locale> set = new LinkedHashSet<Locale>();
310     for (String localeName : localeNames) {
311       set.add(localeFromIcuLocaleId(localeName));
312     }
313     return set.toArray(new Locale[set.size()]);
314   }
315 
getAvailableLocales()316   public static Locale[] getAvailableLocales() {
317     if (availableLocalesCache == null) {
318       availableLocalesCache = localesFromStrings(getAvailableLocalesNative());
319     }
320     return availableLocalesCache.clone();
321   }
322 
323   /**
324    * DO NOT USE this method directly.
325    * Please use {@link SimpleDateFormatData.DateTimeFormatStringGenerator#getTimePattern}
326    */
getTimePattern(Locale locale, boolean is24Hour, boolean withSecond)327   /* package */ static String getTimePattern(Locale locale, boolean is24Hour, boolean withSecond) {
328     final String skeleton;
329     if (withSecond) {
330       skeleton = is24Hour ? "Hms" : "hms";
331     } else {
332       skeleton = is24Hour ? "Hm" : "hm";
333     }
334     return getBestDateTimePattern(skeleton, locale);
335   }
336   /**
337    * DO NOT USE this method directly.
338    * Please use {@link SimpleDateFormatData.DateTimeFormatStringGenerator#getTimePattern}
339    */
340   @UnsupportedAppUsage
getBestDateTimePattern(String skeleton, Locale locale)341   public static String getBestDateTimePattern(String skeleton, Locale locale) {
342     String languageTag = locale.toLanguageTag();
343     String key = skeleton + "\t" + languageTag;
344     synchronized (CACHED_PATTERNS) {
345       String pattern = CACHED_PATTERNS.get(key);
346       if (pattern == null) {
347         pattern = getBestDateTimePattern0(skeleton, locale);
348         CACHED_PATTERNS.put(key, pattern);
349       }
350       return pattern;
351     }
352   }
353 
getBestDateTimePattern0(String skeleton, Locale locale)354   private static String getBestDateTimePattern0(String skeleton, Locale locale) {
355       DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(locale);
356       return dtpg.getBestPattern(skeleton);
357   }
358 
359   @UnsupportedAppUsage
getBestDateTimePatternNative(String skeleton, String languageTag)360   private static String getBestDateTimePatternNative(String skeleton, String languageTag) {
361     return getBestDateTimePattern0(skeleton, Locale.forLanguageTag(languageTag));
362   }
363 
364   @UnsupportedAppUsage
getDateFormatOrder(String pattern)365   public static char[] getDateFormatOrder(String pattern) {
366     char[] result = new char[3];
367     int resultIndex = 0;
368     boolean sawDay = false;
369     boolean sawMonth = false;
370     boolean sawYear = false;
371 
372     for (int i = 0; i < pattern.length(); ++i) {
373       char ch = pattern.charAt(i);
374       if (ch == 'd' || ch == 'L' || ch == 'M' || ch == 'y') {
375         if (ch == 'd' && !sawDay) {
376           result[resultIndex++] = 'd';
377           sawDay = true;
378         } else if ((ch == 'L' || ch == 'M') && !sawMonth) {
379           result[resultIndex++] = 'M';
380           sawMonth = true;
381         } else if ((ch == 'y') && !sawYear) {
382           result[resultIndex++] = 'y';
383           sawYear = true;
384         }
385       } else if (ch == 'G') {
386         // Ignore the era specifier, if present.
387       } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
388         throw new IllegalArgumentException("Bad pattern character '" + ch + "' in " + pattern);
389       } else if (ch == '\'') {
390         if (i < pattern.length() - 1 && pattern.charAt(i + 1) == '\'') {
391           ++i;
392         } else {
393           i = pattern.indexOf('\'', i + 1);
394           if (i == -1) {
395             throw new IllegalArgumentException("Bad quoting in " + pattern);
396           }
397           ++i;
398         }
399       } else {
400         // Ignore spaces and punctuation.
401       }
402     }
403     return result;
404   }
405 
406   /**
407    * {@link java.time.format.DateTimeFormatter} does not handle some date symbols, e.g. 'B' / 'b',
408    * and thus we use a heuristic algorithm to remove the symbol. See http://b/174804526.
409    * See {@link #transformIcuDateTimePattern(String)} for documentation about the implementation.
410    */
transformIcuDateTimePattern_forJavaTime(String pattern)411   public static String transformIcuDateTimePattern_forJavaTime(String pattern) {
412     return transformIcuDateTimePattern(pattern);
413   }
414 
415   /**
416    * {@link java.text.SimpleDateFormat} does not handle some date symbols, e.g. 'B' / 'b',
417    * and simply ignore the symbol in formatting. Instead, we should avoid exposing the symbol
418    * entirely in all public APIs, e.g. {@link java.text.SimpleDateFormat#toPattern()},
419    * and thus we use a heuristic algorithm to remove the symbol. See http://b/174804526.
420    * See {@link #transformIcuDateTimePattern(String)} for documentation about the implementation.
421    */
transformIcuDateTimePattern_forJavaText(String pattern)422   public static String transformIcuDateTimePattern_forJavaText(String pattern) {
423     return transformIcuDateTimePattern(pattern);
424   }
425 
426   /**
427    * Rewrite the date/time pattern coming ICU to be consumed by libcore classes.
428    * It's an ideal place to rewrite the pattern entirely when multiple symbols not digested
429    * by libcore need to be removed/processed. Rewriting in single place could be more efficient
430    * in a small or constant number of scans instead of scanning for every symbol.
431    *
432    * {@link LocaleData#initLocaleData(Locale)} also rewrites time format, but only a subset of
433    * patterns. In the future, that should migrate to this function in order to handle the symbols
434    * in one place, but now separate because java.text and java.time handles different sets of
435    * symbols.
436    */
transformIcuDateTimePattern(String pattern)437   private static String transformIcuDateTimePattern(String pattern) {
438     if (pattern == null) {
439       return null;
440     }
441 
442     // For details about the different symbols, see
443     // http://cldr.unicode.org/translation/date-time-1/date-time-patterns#TOC-Day-period-patterns
444     // The symbols B means "Day periods with locale-specific ranges".
445     // English example: 2:00 at night, 10:00 in the morning, 12:00 in the afternoon.
446     boolean contains_B = pattern.indexOf('B') != -1;
447     // AM, PM, noon and midnight. English example: 10:00 AM, 12:00 noon, 7:00 PM
448     boolean contains_b = pattern.indexOf('b') != -1;
449 
450     // Simply remove the symbol 'B' and 'b' if 24-hour 'H' exists because the 24-hour format
451     // provides enough information and the day periods are optional. See http://b/174804526.
452     // Don't handle symbol 'B'/'b' with 12-hour 'h' because it's much more complicated because
453     // we likely need to replace 'B'/'b' with 'a' inserted into a new right position or use other
454     // ways.
455     boolean remove_B_and_b = (contains_B || contains_b) && (pattern.indexOf('H') != -1);
456 
457     if (remove_B_and_b) {
458       return removeBFromDateTimePattern(pattern);
459     }
460 
461     // Non-ideal workaround until http://b/68139386 is implemented.
462     // This workaround may create a pattern that isn't usual / common for the language users.
463     if (pattern.indexOf('h') != -1) {
464       if (contains_b) {
465         pattern = pattern.replace('b', 'a');
466       }
467       if (contains_B) {
468         pattern = pattern.replace('B', 'a');
469       }
470     }
471 
472     return pattern;
473   }
474 
475   /**
476    * Remove 'b' and 'B' from simple patterns, e.g. "B H:mm" and "dd-MM-yy B HH:mm:ss" only.
477    */
removeBFromDateTimePattern(String pattern)478   private static String removeBFromDateTimePattern(String pattern) {
479     // The below implementation can likely be replaced by a regular expression via
480     // String.replaceAll(). However, it's known that libcore's regex implementation is more
481     // memory-intensive, and the below implementation is likely cheaper, but it's not yet measured.
482     StringBuilder sb = new StringBuilder(pattern.length());
483     char prev = ' '; // the initial value is not used.
484     for (int i = 0; i < pattern.length(); i++) {
485       char curr = pattern.charAt(i);
486       switch(curr) {
487         case 'B':
488         case 'b':
489           // Ignore 'B' and 'b'
490           break;
491         case ' ': // Ascii whitespace
492           // caveat: Ideally it's a case for all Unicode whitespaces by UCharacter.isUWhiteSpace(c)
493           // but checking ascii whitespace only is enough for the CLDR data when this is written.
494           if (i != 0 && (prev == 'B' || prev == 'b')) {
495             // Ignore the whitespace behind the symbol 'B'/'b' because it's likely a whitespace to
496             // separate the day period with the next text.
497           } else {
498             sb.append(curr);
499           }
500           break;
501         default:
502           sb.append(curr);
503           break;
504       }
505       prev = curr;
506     }
507 
508     // Remove the trailing whitespace which is likely following the symbol 'B'/'b' in the original
509     // pattern, e.g. "hh:mm B" (12:00 in the afternoon).
510     int lastIndex = sb.length() - 1;
511     if (lastIndex >= 0 && sb.charAt(lastIndex) == ' ') {
512       sb.deleteCharAt(lastIndex);
513     }
514     return sb.toString();
515   }
516 
517   /**
518    * Returns the version of the CLDR data in use, such as "22.1.1".
519    *
520    */
getCldrVersion()521   public static native String getCldrVersion();
522 
523   /**
524    * Returns the icu4c version in use, such as "50.1.1".
525    */
getIcuVersion()526   public static native String getIcuVersion();
527 
528   /**
529    * Returns the Unicode version our ICU supports, such as "6.2".
530    */
getUnicodeVersion()531   public static native String getUnicodeVersion();
532 
533   // --- Errors.
534 
535   // --- Native methods accessing ICU's database.
536 
getAvailableLocalesNative()537   private static native String[] getAvailableLocalesNative();
538 
539     /**
540      * Query ICU for the currency being used in the country right now.
541      * @param countryCode ISO 3166 two-letter country code
542      * @return ISO 4217 3-letter currency code if found, otherwise null.
543      */
getCurrencyCode(String countryCode)544   public static String getCurrencyCode(String countryCode) {
545       // Fail fast when country code is not valid.
546       if (countryCode == null || countryCode.length() == 0) {
547           return null;
548       }
549       final ULocale countryLocale;
550       try {
551           countryLocale = new ULocale.Builder().setRegion(countryCode).build();
552       } catch (IllformedLocaleException e) {
553           return null; // Return null on invalid country code.
554       }
555       String[] isoCodes = Currency.getAvailableCurrencyCodes(countryLocale, new Date());
556       if (isoCodes == null || isoCodes.length == 0) {
557         return null;
558       }
559       return isoCodes[0];
560   }
561 
562 
getISO3Country(String languageTag)563   public static native String getISO3Country(String languageTag);
564 
getISO3Language(String languageTag)565   public static native String getISO3Language(String languageTag);
566 
567   /**
568    * @deprecated Use {@link android.icu.util.ULocale#addLikelySubtags(ULocale)} instead.
569    * The method is only kept for @UnsupportedAppUsage.
570    */
571   @UnsupportedAppUsage
572   @Deprecated
addLikelySubtags(Locale locale)573   public static Locale addLikelySubtags(Locale locale) {
574       return ULocale.addLikelySubtags(ULocale.forLocale(locale)).toLocale();
575   }
576 
577   /**
578    * @return ICU localeID
579    * @deprecated Use {@link android.icu.util.ULocale#addLikelySubtags(ULocale)} instead.
580    * The method is only kept for @UnsupportedAppUsage.
581    */
582   @UnsupportedAppUsage
583   @Deprecated
addLikelySubtags(String locale)584   public static String addLikelySubtags(String locale) {
585       return ULocale.addLikelySubtags(new ULocale(locale)).getName();
586   }
587 
588   /**
589    * @deprecated use {@link java.util.Locale#getScript()} instead. This has been kept
590    *     around only for the support library.
591    */
592   @UnsupportedAppUsage
593   @Deprecated
getScript(String locale)594   public static native String getScript(String locale);
595 
getISOLanguagesNative()596   private static native String[] getISOLanguagesNative();
getISOCountriesNative()597   private static native String[] getISOCountriesNative();
598 
599   /**
600    * Takes a BCP-47 language tag (Locale.toLanguageTag()). e.g. en-US, not en_US
601    */
setDefaultLocale(String languageTag)602   public static void setDefaultLocale(String languageTag) {
603     LocaleNative.setDefault(languageTag);
604   }
605 
606   /**
607    * Returns a locale name, not a BCP-47 language tag. e.g. en_US not en-US.
608    */
getDefaultLocale()609   public static native String getDefaultLocale();
610 
611 
612   /**
613    * @param calendarType LDML-defined legacy calendar type. See keyTypeData.txt in ICU.
614    */
getExtendedCalendar(Locale locale, String calendarType)615   public static ExtendedCalendar getExtendedCalendar(Locale locale, String calendarType) {
616       ULocale uLocale = ULocale.forLocale(locale)
617               .setKeywordValue("calendar", calendarType);
618       return ExtendedCalendar.getInstance(uLocale);
619   }
620 
621   /**
622    * Converts CLDR LDML short time zone id to an ID that can be recognized by
623    * {@link java.util.TimeZone#getTimeZone(String)}.
624    * @param cldrShortTzId
625    * @return null if no tz id can be matched to the short id.
626    */
convertToTzId(String cldrShortTzId)627   public static String convertToTzId(String cldrShortTzId) {
628     if (cldrShortTzId == null) {
629       return null;
630     }
631     String tzid = ULocale.toLegacyType("tz", cldrShortTzId);
632     // ULocale.toLegacyType() returns the lower case of the input ID if it matches the spec, but
633     // it's not a valid tz id.
634     if (tzid == null || tzid.equals(cldrShortTzId.toLowerCase(Locale.ROOT))) {
635       return null;
636     }
637     return tzid;
638   }
639 
getGMTZeroFormatString(Locale locale)640   public static String getGMTZeroFormatString(Locale locale) {
641     return TimeZoneFormat.getInstance(locale).getGMTZeroFormat();
642   }
643 
644 }
645