2 *******************************************************************************
\r
3 * Copyright (C) 2007-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
7 package com.ibm.icu.impl;
\r
9 import java.util.ArrayList;
\r
10 import java.util.HashMap;
\r
11 import java.util.Iterator;
\r
12 import java.util.List;
\r
13 import java.util.Map;
\r
14 import java.util.MissingResourceException;
\r
15 import java.util.Set;
\r
17 import com.ibm.icu.impl.ZoneMeta.OlsonToMetaMappingEntry;
\r
18 import com.ibm.icu.text.MessageFormat;
\r
19 import com.ibm.icu.util.BasicTimeZone;
\r
20 import com.ibm.icu.util.Calendar;
\r
21 import com.ibm.icu.util.TimeZone;
\r
22 import com.ibm.icu.util.TimeZoneTransition;
\r
23 import com.ibm.icu.util.ULocale;
\r
24 import com.ibm.icu.util.UResourceBundle;
\r
30 public class ZoneStringFormat {
\r
32 * Constructs a ZoneStringFormat by zone strings array.
\r
33 * The internal structure of zoneStrings is compatible with
\r
34 * the one used by getZoneStrings/setZoneStrings in DateFormatSymbols.
\r
36 * @param zoneStrings zone strings
\r
38 public ZoneStringFormat(String[][] zoneStrings) {
\r
39 tzidToStrings = new HashMap<String, ZoneStrings>();
\r
40 zoneStringsTrie = new TextTrieMap<ZoneStringInfo>(true);
\r
41 for (int i = 0; i < zoneStrings.length; i++) {
\r
42 String tzid = zoneStrings[i][0];
\r
43 String[] names = new String[ZSIDX_MAX];
\r
44 for (int j = 1; j < zoneStrings[i].length; j++) {
\r
45 if (zoneStrings[i][j] != null) {
\r
46 int typeIdx = getNameTypeIndex(j);
\r
47 if (typeIdx != -1) {
\r
48 names[typeIdx] = zoneStrings[i][j];
\r
50 // Put the name into the trie
\r
51 int type = getNameType(typeIdx);
\r
52 ZoneStringInfo zsinfo = new ZoneStringInfo(tzid, zoneStrings[i][j], type);
\r
53 zoneStringsTrie.put(zoneStrings[i][j], zsinfo);
\r
58 ZoneStrings zstrings = new ZoneStrings(names, true, null);
\r
59 tzidToStrings.put(tzid, zstrings);
\r
61 isFullyLoaded = true;
\r
65 * Gets an instance of ZoneStringFormat for the specified locale
\r
66 * @param locale the locale
\r
67 * @return An instance of ZoneStringFormat for the locale
\r
69 public static ZoneStringFormat getInstance(ULocale locale) {
\r
70 ZoneStringFormat tzf = TZFORMAT_CACHE.get(locale);
\r
72 tzf = new ZoneStringFormat(locale);
\r
73 TZFORMAT_CACHE.put(locale, tzf);
\r
78 public String[][] getZoneStrings() {
\r
79 return getZoneStrings(System.currentTimeMillis());
\r
82 // APIs used by SimpleDateFormat to get a zone string
\r
83 public String getSpecificLongString(Calendar cal) {
\r
84 if (cal.get(Calendar.DST_OFFSET) == 0) {
\r
85 return getString(cal.getTimeZone().getID(), ZSIDX_LONG_STANDARD, cal.getTimeInMillis(), false /* not used */);
\r
87 return getString(cal.getTimeZone().getID(), ZSIDX_LONG_DAYLIGHT, cal.getTimeInMillis(), false /* not used */);
\r
90 public String getSpecificShortString(Calendar cal, boolean commonlyUsedOnly) {
\r
91 if (cal.get(Calendar.DST_OFFSET) == 0) {
\r
92 return getString(cal.getTimeZone().getID(), ZSIDX_SHORT_STANDARD, cal.getTimeInMillis(), commonlyUsedOnly);
\r
94 return getString(cal.getTimeZone().getID(), ZSIDX_SHORT_DAYLIGHT, cal.getTimeInMillis(), commonlyUsedOnly);
\r
97 public String getGenericLongString(Calendar cal) {
\r
98 return getGenericString(cal, false /* long */, false /* not used */);
\r
101 public String getGenericShortString(Calendar cal, boolean commonlyUsedOnly) {
\r
102 return getGenericString(cal, true /* long */, commonlyUsedOnly);
\r
105 public String getGenericLocationString(Calendar cal) {
\r
106 return getString(cal.getTimeZone().getID(), ZSIDX_LOCATION, cal.getTimeInMillis(), false /* not used */);
\r
109 // APIs used by SimpleDateFormat to lookup a zone string
\r
110 public static class ZoneStringInfo {
\r
112 private String str;
\r
115 private ZoneStringInfo(String id, String str, int type) {
\r
121 public String getID() {
\r
125 public String getString() {
\r
129 public boolean isStandard() {
\r
130 if ((type & STANDARD_LONG) != 0 || (type & STANDARD_SHORT) != 0) {
\r
136 public boolean isDaylight() {
\r
137 if ((type & DAYLIGHT_LONG) != 0 || (type & DAYLIGHT_SHORT) != 0) {
\r
143 public boolean isGeneric() {
\r
144 return !isStandard() && !isDaylight();
\r
147 private int getType() {
\r
152 public ZoneStringInfo findSpecificLong(String text, int start) {
\r
153 return find(text, start, STANDARD_LONG | DAYLIGHT_LONG);
\r
156 public ZoneStringInfo findSpecificShort(String text, int start) {
\r
157 return find(text, start, STANDARD_SHORT | DAYLIGHT_SHORT);
\r
160 public ZoneStringInfo findGenericLong(String text, int start) {
\r
161 return find(text, start, GENERIC_LONG | STANDARD_LONG | LOCATION);
\r
164 public ZoneStringInfo findGenericShort(String text, int start) {
\r
165 return find(text, start, GENERIC_SHORT | STANDARD_SHORT | LOCATION);
\r
168 public ZoneStringInfo findGenericLocation(String text, int start) {
\r
169 return find(text, start, LOCATION);
\r
172 // Following APIs are not used by SimpleDateFormat, but public for testing purpose
\r
173 public String getLongStandard(String tzid, long date) {
\r
174 return getString(tzid, ZSIDX_LONG_STANDARD, date, false /* not used */);
\r
177 public String getLongDaylight(String tzid, long date) {
\r
178 return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, false /* not used */);
\r
181 public String getLongGenericNonLocation(String tzid, long date) {
\r
182 return getString(tzid, ZSIDX_LONG_GENERIC, date, false /* not used */);
\r
185 public String getLongGenericPartialLocation(String tzid, long date) {
\r
186 return getGenericPartialLocationString(tzid, false, date, false /* not used */);
\r
189 public String getShortStandard(String tzid, long date, boolean commonlyUsedOnly) {
\r
190 return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly);
\r
193 public String getShortDaylight(String tzid, long date, boolean commonlyUsedOnly) {
\r
194 return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly);
\r
197 public String getShortGenericNonLocation(String tzid, long date, boolean commonlyUsedOnly) {
\r
198 return getString(tzid, ZSIDX_SHORT_GENERIC, date, commonlyUsedOnly);
\r
201 public String getShortGenericPartialLocation(String tzid, long date, boolean commonlyUsedOnly) {
\r
202 return getGenericPartialLocationString(tzid, true, date, commonlyUsedOnly);
\r
205 public String getGenericLocation(String tzid) {
\r
206 return getString(tzid, ZSIDX_LOCATION, 0L /* not used */, false /* not used */);
\r
210 * Constructs a ZoneStringFormat by locale. Because an instance of ZoneStringFormat
\r
211 * is read-only, only one instance for a locale is sufficient. Thus, this
\r
212 * constructor is protected and only called from getInstance(ULocale) to
\r
213 * create one for a locale.
\r
214 * @param locale The locale
\r
216 protected ZoneStringFormat(ULocale locale) {
\r
217 this.locale = locale;
\r
218 tzidToStrings = new HashMap<String, ZoneStrings>();
\r
219 mzidToStrings = new HashMap<String, ZoneStrings>();
\r
220 zoneStringsTrie = new TextTrieMap<ZoneStringInfo>(true);
\r
223 // Load only a single zone
\r
224 private synchronized void loadZone(String id) {
\r
225 if (isFullyLoaded) {
\r
228 String tzid = ZoneMeta.getCanonicalSystemID(id);
\r
229 if (tzid == null || tzidToStrings.containsKey(tzid)) {
\r
233 ICUResourceBundle zoneStringsBundle = null;
\r
235 ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_ZONE_BASE_NAME, locale);
\r
236 zoneStringsBundle = bundle.getWithFallback("zoneStrings");
\r
237 } catch (MissingResourceException e) {
\r
238 // If no locale bundles are available, zoneStringsBundle will be null.
\r
239 // We still want to go through the rest of zone strings initialization,
\r
240 // because generic location format is generated from tzid for the case.
\r
241 // The rest of code should work even zoneStrings is null.
\r
244 String[] zstrarray = new String[ZSIDX_MAX];
\r
245 String[] mzstrarray = new String[ZSIDX_MAX];
\r
246 String[][] mzPartialLoc = new String[10][4]; // maximum 10 metazones per zone
\r
248 addSingleZone(tzid, zoneStringsBundle,
\r
249 getFallbackFormat(locale), getRegionFormat(locale),
\r
250 zstrarray, mzstrarray, mzPartialLoc);
\r
253 // Loading all zone strings
\r
254 private synchronized void loadFull() {
\r
255 if (isFullyLoaded) {
\r
258 ICUResourceBundle zoneStringsBundle = null;
\r
260 ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_ZONE_BASE_NAME, locale);
\r
261 zoneStringsBundle = bundle.getWithFallback("zoneStrings");
\r
262 } catch (MissingResourceException e) {
\r
263 // If no locale bundles are available, zoneStringsBundle will be null.
\r
264 // We still want to go through the rest of zone strings initialization,
\r
265 // because generic location format is generated from tzid for the case.
\r
266 // The rest of code should work even zoneStrings is null.
\r
269 String[] zoneIDs = TimeZone.getAvailableIDs();
\r
271 String[] zstrarray = new String[ZSIDX_MAX];
\r
272 String[] mzstrarray = new String[ZSIDX_MAX];
\r
273 String[][] mzPartialLoc = new String[10][4]; // maximum 10 metazones per zone
\r
275 for (int i = 0; i < zoneIDs.length; i++) {
\r
277 String tzid = ZoneMeta.getCanonicalSystemID(zoneIDs[i]);
\r
278 if (tzid == null || !zoneIDs[i].equals(tzid)) {
\r
282 if (tzidToStrings.containsKey(tzid)) {
\r
286 addSingleZone(tzid, zoneStringsBundle,
\r
287 getFallbackFormat(locale), getRegionFormat(locale),
\r
288 zstrarray, mzstrarray, mzPartialLoc);
\r
290 isFullyLoaded = true;
\r
293 // This internal initialization code must be called in a synchronized block
\r
294 private void addSingleZone(String tzid, ICUResourceBundle zoneStringsBundle,
\r
295 MessageFormat fallbackFmt, MessageFormat regionFmt,
\r
296 String[] zstrarray, String[] mzstrarray, String[][] mzPartialLoc) {
\r
298 if (tzidToStrings.containsKey(tzid)) {
\r
302 String zoneKey = tzid.replace('/', ':');
\r
303 zstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_STANDARD);
\r
304 zstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_STANDARD);
\r
305 zstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_DAYLIGHT);
\r
306 zstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_DAYLIGHT);
\r
307 zstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_LONG_GENERIC);
\r
308 zstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_SHORT_GENERIC);
\r
310 // Compose location format string
\r
311 String countryCode = ZoneMeta.getCanonicalCountry(tzid);
\r
312 String country = null;
\r
313 String city = null;
\r
314 if (countryCode != null) {
\r
315 city = getZoneStringFromBundle(zoneStringsBundle, zoneKey, RESKEY_EXEMPLAR_CITY);
\r
316 if (city == null) {
\r
317 city = tzid.substring(tzid.lastIndexOf('/') + 1).replace('_', ' ');
\r
319 country = getLocalizedCountry(countryCode, locale);
\r
320 if (ZoneMeta.getSingleCountry(tzid) != null) {
\r
321 // If the zone is only one zone in the country, do not add city
\r
322 zstrarray[ZSIDX_LOCATION] = regionFmt.format(new Object[] {country});
\r
324 zstrarray[ZSIDX_LOCATION] = fallbackFmt.format(new Object[] {city, country});
\r
327 if (tzid.startsWith("Etc/")) {
\r
328 // "Etc/xxx" is not associated with a specific location, so localized
\r
329 // GMT format is always used as generic location format.
\r
330 zstrarray[ZSIDX_LOCATION] = null;
\r
332 // When a new time zone ID, which is actually associated with a specific
\r
333 // location, is added in tzdata, but the current CLDR data does not have
\r
334 // the information yet, ICU creates a generic location string based on
\r
335 // the ID. This implementation supports canonical time zone round trip
\r
336 // with format pattern "VVVV". See #6602 for the details.
\r
337 String location = tzid;
\r
338 int slashIdx = location.lastIndexOf('/');
\r
339 if (slashIdx == -1) {
\r
340 // A time zone ID without slash in the tz database is not
\r
341 // associated with a specific location. For instances,
\r
342 // MET, CET, EET and WET fall into this catetory.
\r
343 zstrarray[ZSIDX_LOCATION] = null;
\r
345 location = tzid.substring(slashIdx + 1);
\r
346 zstrarray[ZSIDX_LOCATION] = regionFmt.format(new Object[] {location});
\r
351 boolean commonlyUsed = isCommonlyUsed(zoneStringsBundle, zoneKey);
\r
353 // Resolve metazones used by this zone
\r
354 int mzPartialLocIdx = 0;
\r
355 List<OlsonToMetaMappingEntry> metazoneMappings = ZoneMeta.getOlsonToMatazones(tzid);
\r
356 if (metazoneMappings != null) {
\r
357 Iterator<OlsonToMetaMappingEntry> it = metazoneMappings.iterator();
\r
358 while (it.hasNext()) {
\r
359 ZoneMeta.OlsonToMetaMappingEntry mzmap = it.next();
\r
360 ZoneStrings mzStrings = mzidToStrings.get(mzmap.mzid);
\r
361 if (mzStrings == null) {
\r
362 // If the metazone strings are not yet processed, do it now.
\r
363 String mzkey = "meta:" + mzmap.mzid;
\r
364 boolean mzCommonlyUsed = isCommonlyUsed(zoneStringsBundle, mzkey);
\r
365 mzstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_LONG_STANDARD);
\r
366 mzstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_SHORT_STANDARD);
\r
367 mzstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_LONG_DAYLIGHT);
\r
368 mzstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_SHORT_DAYLIGHT);
\r
369 mzstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_LONG_GENERIC);
\r
370 mzstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(zoneStringsBundle, mzkey, RESKEY_SHORT_GENERIC);
\r
371 mzstrarray[ZSIDX_LOCATION] = null;
\r
372 mzStrings = new ZoneStrings(mzstrarray, mzCommonlyUsed, null);
\r
373 mzidToStrings.put(mzmap.mzid, mzStrings);
\r
375 // Add metazone strings to the zone string trie
\r
376 String preferredIdForLocale = ZoneMeta.getZoneIdByMetazone(mzmap.mzid, getRegion());
\r
377 for (int j = 0; j < mzstrarray.length; j++) {
\r
378 if (mzstrarray[j] != null) {
\r
379 int type = getNameType(j);
\r
380 ZoneStringInfo zsinfo = new ZoneStringInfo(preferredIdForLocale, mzstrarray[j], type);
\r
381 zoneStringsTrie.put(mzstrarray[j], zsinfo);
\r
385 // Compose generic partial location format
\r
386 String lg = mzStrings.getString(ZSIDX_LONG_GENERIC);
\r
387 String sg = mzStrings.getString(ZSIDX_SHORT_GENERIC);
\r
388 if (lg != null || sg != null) {
\r
389 boolean addMzPartialLocationNames = true;
\r
390 for (int j = 0; j < mzPartialLocIdx; j++) {
\r
391 if (mzPartialLoc[j][0].equals(mzmap.mzid)) {
\r
393 addMzPartialLocationNames = false;
\r
397 if (addMzPartialLocationNames) {
\r
398 String locationPart = null;
\r
399 // Check if the zone is the preferred zone for the territory associated with the zone
\r
400 String preferredID = ZoneMeta.getZoneIdByMetazone(mzmap.mzid, countryCode);
\r
401 if (tzid.equals(preferredID)) {
\r
402 // Use country for the location
\r
403 locationPart = country;
\r
405 // Use city for the location
\r
406 locationPart = city;
\r
408 mzPartialLoc[mzPartialLocIdx][0] = mzmap.mzid;
\r
409 mzPartialLoc[mzPartialLocIdx][1] = null;
\r
410 mzPartialLoc[mzPartialLocIdx][2] = null;
\r
411 mzPartialLoc[mzPartialLocIdx][3] = null;
\r
412 if (locationPart != null) {
\r
414 mzPartialLoc[mzPartialLocIdx][1] = fallbackFmt.format(new Object[] {locationPart, lg});
\r
417 mzPartialLoc[mzPartialLocIdx][2] = fallbackFmt.format(new Object[] {locationPart, sg});
\r
418 boolean shortMzCommonlyUsed = mzStrings.isShortFormatCommonlyUsed();
\r
419 if (shortMzCommonlyUsed) {
\r
420 mzPartialLoc[mzPartialLocIdx][3] = "1";
\r
429 String[][] genericPartialLocationNames = null;
\r
430 if (mzPartialLocIdx != 0) {
\r
431 // metazone generic partial location names are collected
\r
432 genericPartialLocationNames = new String[mzPartialLocIdx][];
\r
433 for (int mzi = 0; mzi < mzPartialLocIdx; mzi++) {
\r
434 genericPartialLocationNames[mzi] = mzPartialLoc[mzi].clone();
\r
437 // Finally, create ZoneStrings instance and put it into the tzidToStinrgs map
\r
438 ZoneStrings zstrings = new ZoneStrings(zstrarray, commonlyUsed, genericPartialLocationNames);
\r
439 tzidToStrings.put(tzid, zstrings);
\r
441 // Also add all available names to the zone string trie
\r
442 if (zstrarray != null) {
\r
443 for (int j = 0; j < zstrarray.length; j++) {
\r
444 if (zstrarray[j] != null) {
\r
445 int type = getNameType(j);
\r
446 ZoneStringInfo zsinfo = new ZoneStringInfo(tzid, zstrarray[j], type);
\r
447 zoneStringsTrie.put(zstrarray[j], zsinfo);
\r
451 if (genericPartialLocationNames != null) {
\r
452 for (int j = 0; j < genericPartialLocationNames.length; j++) {
\r
453 ZoneStringInfo zsinfo;
\r
454 if (genericPartialLocationNames[j][1] != null) {
\r
455 zsinfo = new ZoneStringInfo(tzid, genericPartialLocationNames[j][1], GENERIC_LONG);
\r
456 zoneStringsTrie.put(genericPartialLocationNames[j][1], zsinfo);
\r
458 if (genericPartialLocationNames[j][2] != null) {
\r
459 zsinfo = new ZoneStringInfo(tzid, genericPartialLocationNames[j][1], GENERIC_SHORT);
\r
460 zoneStringsTrie.put(genericPartialLocationNames[j][2], zsinfo);
\r
466 // Name types, these bit flag are used for zone string lookup
\r
467 private static final int LOCATION = 0x0001;
\r
468 private static final int GENERIC_LONG = 0x0002;
\r
469 private static final int GENERIC_SHORT = 0x0004;
\r
470 private static final int STANDARD_LONG = 0x0008;
\r
471 private static final int STANDARD_SHORT = 0x0010;
\r
472 private static final int DAYLIGHT_LONG = 0x0020;
\r
473 private static final int DAYLIGHT_SHORT = 0x0040;
\r
475 // Name type index, these constants are used for index in ZoneStrings.strings
\r
476 private static final int ZSIDX_LOCATION = 0;
\r
477 private static final int ZSIDX_LONG_STANDARD = 1;
\r
478 private static final int ZSIDX_SHORT_STANDARD = 2;
\r
479 private static final int ZSIDX_LONG_DAYLIGHT = 3;
\r
480 private static final int ZSIDX_SHORT_DAYLIGHT = 4;
\r
481 private static final int ZSIDX_LONG_GENERIC = 5;
\r
482 private static final int ZSIDX_SHORT_GENERIC = 6;
\r
484 private static final int ZSIDX_MAX = ZSIDX_SHORT_GENERIC + 1;
\r
486 // ZoneStringFormat cache
\r
487 private static ICUCache<ULocale, ZoneStringFormat> TZFORMAT_CACHE = new SimpleCache<ULocale, ZoneStringFormat>();
\r
490 * The translation type of the translated zone strings
\r
492 private static final String
\r
493 RESKEY_SHORT_GENERIC = "sg",
\r
494 RESKEY_SHORT_STANDARD = "ss",
\r
495 RESKEY_SHORT_DAYLIGHT = "sd",
\r
496 RESKEY_LONG_GENERIC = "lg",
\r
497 RESKEY_LONG_STANDARD = "ls",
\r
498 RESKEY_LONG_DAYLIGHT = "ld",
\r
499 RESKEY_EXEMPLAR_CITY = "ec",
\r
500 RESKEY_COMMONLY_USED = "cu";
\r
502 // Window size used for DST check for a zone in a metazone
\r
503 private static final long DST_CHECK_RANGE = 184L*(24*60*60*1000);
\r
505 // Map from zone id to ZoneStrings
\r
506 private Map<String, ZoneStrings> tzidToStrings;
\r
508 // Map from metazone id to ZoneStrings
\r
509 private Map<String, ZoneStrings> mzidToStrings;
\r
511 // Zone string dictionary, used for look up
\r
512 private TextTrieMap<ZoneStringInfo> zoneStringsTrie;
\r
514 // Locale used for initializing zone strings
\r
515 private ULocale locale;
\r
517 // Region used for resolving a zone in a metazone, initialized by locale
\r
518 private transient String region;
\r
521 private boolean isFullyLoaded = false;
\r
524 * Private method to get a zone string except generic partial location types.
\r
526 private String getString(String tzid, int typeIdx, long date, boolean commonlyUsedOnly) {
\r
527 if (!isFullyLoaded) {
\r
532 String result = null;
\r
533 ZoneStrings zstrings = tzidToStrings.get(tzid);
\r
534 if (zstrings == null) {
\r
535 // ICU's own array does not have entries for aliases
\r
536 String canonicalID = ZoneMeta.getCanonicalSystemID(tzid);
\r
537 if (canonicalID != null && !canonicalID.equals(tzid)) {
\r
538 // Canonicalize tzid here. The rest of operations
\r
539 // require tzid to be canonicalized.
\r
540 tzid = canonicalID;
\r
541 zstrings = tzidToStrings.get(tzid);
\r
544 if (zstrings != null) {
\r
546 case ZSIDX_LONG_STANDARD:
\r
547 case ZSIDX_LONG_DAYLIGHT:
\r
548 case ZSIDX_LONG_GENERIC:
\r
549 case ZSIDX_LOCATION:
\r
550 result = zstrings.getString(typeIdx);
\r
552 case ZSIDX_SHORT_STANDARD:
\r
553 case ZSIDX_SHORT_DAYLIGHT:
\r
554 case ZSIDX_SHORT_GENERIC:
\r
555 if (!commonlyUsedOnly || zstrings.isShortFormatCommonlyUsed()) {
\r
556 result = zstrings.getString(typeIdx);
\r
561 if (result == null && mzidToStrings != null && typeIdx != ZSIDX_LOCATION) {
\r
563 String mzid = ZoneMeta.getMetazoneID(tzid, date);
\r
564 if (mzid != null) {
\r
565 ZoneStrings mzstrings = mzidToStrings.get(mzid);
\r
566 if (mzstrings != null) {
\r
568 case ZSIDX_LONG_STANDARD:
\r
569 case ZSIDX_LONG_DAYLIGHT:
\r
570 case ZSIDX_LONG_GENERIC:
\r
571 result = mzstrings.getString(typeIdx);
\r
573 case ZSIDX_SHORT_STANDARD:
\r
574 case ZSIDX_SHORT_DAYLIGHT:
\r
575 case ZSIDX_SHORT_GENERIC:
\r
576 if (!commonlyUsedOnly || mzstrings.isShortFormatCommonlyUsed()) {
\r
577 result = mzstrings.getString(typeIdx);
\r
588 * Private method to get a generic string, with fallback logic involved,
\r
591 * 1. If a generic non-location string is avaiable for the zone, return it.
\r
592 * 2. If a generic non-location string is associated with a metazone and
\r
593 * the zone never use daylight time around the given date, use the standard
\r
594 * string (if available).
\r
596 * Note: In CLDR1.5.1, the same localization is used for generic and standard.
\r
597 * In this case, we do not use the standard string and do the rest.
\r
599 * 3. If a generic non-location string is associated with a metazone and
\r
600 * the offset at the given time is different from the preferred zone for the
\r
601 * current locale, then return the generic partial location string (if avaiable)
\r
602 * 4. If a generic non-location string is not available, use generic location
\r
605 private String getGenericString(Calendar cal, boolean isShort, boolean commonlyUsedOnly) {
\r
606 String result = null;
\r
607 TimeZone tz = cal.getTimeZone();
\r
608 String tzid = tz.getID();
\r
610 if (!isFullyLoaded) {
\r
615 ZoneStrings zstrings = tzidToStrings.get(tzid);
\r
616 if (zstrings == null) {
\r
617 // ICU's own array does not have entries for aliases
\r
618 String canonicalID = ZoneMeta.getCanonicalSystemID(tzid);
\r
619 if (canonicalID != null && !canonicalID.equals(tzid)) {
\r
620 // Canonicalize tzid here. The rest of operations
\r
621 // require tzid to be canonicalized.
\r
622 tzid = canonicalID;
\r
623 zstrings = tzidToStrings.get(tzid);
\r
626 if (zstrings != null) {
\r
628 if (!commonlyUsedOnly || zstrings.isShortFormatCommonlyUsed()) {
\r
629 result = zstrings.getString(ZSIDX_SHORT_GENERIC);
\r
632 result = zstrings.getString(ZSIDX_LONG_GENERIC);
\r
635 if (result == null && mzidToStrings != null) {
\r
637 long time = cal.getTimeInMillis();
\r
638 String mzid = ZoneMeta.getMetazoneID(tzid, time);
\r
639 if (mzid != null) {
\r
640 boolean useStandard = false;
\r
641 if (cal.get(Calendar.DST_OFFSET) == 0) {
\r
642 useStandard = true;
\r
643 // Check if the zone actually uses daylight saving time around the time
\r
644 if (tz instanceof BasicTimeZone) {
\r
645 BasicTimeZone btz = (BasicTimeZone)tz;
\r
646 TimeZoneTransition before = btz.getPreviousTransition(time, true);
\r
648 && (time - before.getTime() < DST_CHECK_RANGE)
\r
649 && before.getFrom().getDSTSavings() != 0) {
\r
650 useStandard = false;
\r
652 TimeZoneTransition after = btz.getNextTransition(time, false);
\r
654 && (after.getTime() - time < DST_CHECK_RANGE)
\r
655 && after.getTo().getDSTSavings() != 0) {
\r
656 useStandard = false;
\r
660 // If not BasicTimeZone... only if the instance is not an ICU's implementation.
\r
661 // We may get a wrong answer in edge case, but it should practically work OK.
\r
662 int[] offsets = new int[2];
\r
663 tz.getOffset(time - DST_CHECK_RANGE, false, offsets);
\r
664 if (offsets[1] != 0) {
\r
665 useStandard = false;
\r
667 tz.getOffset(time + DST_CHECK_RANGE, false, offsets);
\r
668 if (offsets[1] != 0){
\r
669 useStandard = false;
\r
675 result = getString(tzid, (isShort ? ZSIDX_SHORT_STANDARD : ZSIDX_LONG_STANDARD),
\r
676 time, commonlyUsedOnly);
\r
679 // In CLDR 1.5.1, a same localization is used for both generic and standard
\r
680 // for some metazones in some locales. This is actually data bugs and should
\r
681 // be resolved in later versions of CLDR. For now, we check if the standard
\r
682 // name is different from its generic name below.
\r
683 if (result != null) {
\r
684 String genericNonLocation = getString(tzid, (isShort ? ZSIDX_SHORT_GENERIC : ZSIDX_LONG_GENERIC),
\r
685 time, commonlyUsedOnly);
\r
686 if (genericNonLocation != null && result.equalsIgnoreCase(genericNonLocation)) {
\r
691 if (result == null){
\r
692 ZoneStrings mzstrings = mzidToStrings.get(mzid);
\r
693 if (mzstrings != null) {
\r
695 if (!commonlyUsedOnly || mzstrings.isShortFormatCommonlyUsed()) {
\r
696 result = mzstrings.getString(ZSIDX_SHORT_GENERIC);
\r
699 result = mzstrings.getString(ZSIDX_LONG_GENERIC);
\r
702 if (result != null) {
\r
703 // Check if the offsets at the given time matches the preferred zone's offsets
\r
704 String preferredId = ZoneMeta.getZoneIdByMetazone(mzid, getRegion());
\r
705 if (!tzid.equals(preferredId)) {
\r
706 // Check if the offsets at the given time are identical with the preferred zone
\r
707 int raw = cal.get(Calendar.ZONE_OFFSET);
\r
708 int sav = cal.get(Calendar.DST_OFFSET);
\r
709 TimeZone preferredZone = TimeZone.getTimeZone(preferredId);
\r
710 int[] preferredOffsets = new int[2];
\r
711 // Check offset in preferred time zone with wall time.
\r
712 // With getOffset(time, false, preferredOffsets),
\r
713 // you may get incorrect results because of time overlap at DST->STD
\r
715 preferredZone.getOffset(time + raw + sav, true, preferredOffsets);
\r
716 if (raw != preferredOffsets[0] || sav != preferredOffsets[1]) {
\r
717 // Use generic partial location string as fallback
\r
718 result = zstrings.getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly);
\r
725 if (result == null) {
\r
726 // Use location format as the final fallback
\r
727 result = getString(tzid, ZSIDX_LOCATION, cal.getTimeInMillis(), false /* not used */);
\r
733 * Private method to get a generic partial location string
\r
735 private String getGenericPartialLocationString(String tzid, boolean isShort, long date, boolean commonlyUsedOnly) {
\r
736 if (!isFullyLoaded) {
\r
741 String result = null;
\r
742 String mzid = ZoneMeta.getMetazoneID(tzid, date);
\r
743 if (mzid != null) {
\r
744 ZoneStrings zstrings = tzidToStrings.get(tzid);
\r
745 if (zstrings != null) {
\r
746 result = zstrings.getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly);
\r
753 * Gets zoneStrings compatible with DateFormatSymbols for the
\r
754 * specified date. In CLDR 1.5, zone names can be changed
\r
755 * time to time. This method generates flat 2-dimensional
\r
756 * String array including zone ids and its localized strings
\r
757 * at the moment. Thus, even you construct a new ZoneStringFormat
\r
758 * by the zone strings array returned by this method, you will
\r
759 * loose historic name changes. Also, commonly used flag for
\r
760 * short types is not reflected in the result.
\r
762 private String[][] getZoneStrings(long date) {
\r
765 Set<String> tzids = tzidToStrings.keySet();
\r
766 String[][] zoneStrings = new String[tzids.size()][8];
\r
768 for (String tzid : tzids) {
\r
769 zoneStrings[idx][0] = tzid;
\r
770 zoneStrings[idx][1] = getLongStandard(tzid, date);
\r
771 zoneStrings[idx][2] = getShortStandard(tzid, date, false);
\r
772 zoneStrings[idx][3] = getLongDaylight(tzid, date);
\r
773 zoneStrings[idx][4] = getShortDaylight(tzid, date, false);
\r
774 zoneStrings[idx][5] = getGenericLocation(tzid);
\r
775 zoneStrings[idx][6] = getLongGenericNonLocation(tzid, date);
\r
776 zoneStrings[idx][7] = getShortGenericNonLocation(tzid, date, false);
\r
779 return zoneStrings;
\r
783 * ZoneStrings is an internal implementation class for
\r
784 * holding localized name information for a zone/metazone
\r
786 private static class ZoneStrings {
\r
787 private String[] strings;
\r
788 private String[][] genericPartialLocationStrings;
\r
789 private boolean commonlyUsed;
\r
791 private ZoneStrings(String[] zstrarray, boolean commonlyUsed, String[][] genericPartialLocationStrings) {
\r
792 if (zstrarray != null) {
\r
794 for (int i = 0; i < zstrarray.length; i++) {
\r
795 if (zstrarray[i] != null) {
\r
799 if (lastIdx != -1) {
\r
800 strings = new String[lastIdx + 1];
\r
801 System.arraycopy(zstrarray, 0, strings, 0, lastIdx + 1);
\r
804 this.commonlyUsed = commonlyUsed;
\r
805 this.genericPartialLocationStrings = genericPartialLocationStrings;
\r
808 private String getString(int typeIdx) {
\r
809 if (strings != null && typeIdx >= 0 && typeIdx < strings.length) {
\r
810 return strings[typeIdx];
\r
815 private boolean isShortFormatCommonlyUsed() {
\r
816 return commonlyUsed;
\r
819 private String getGenericPartialLocationString(String mzid, boolean isShort, boolean commonlyUsedOnly) {
\r
820 String result = null;
\r
821 if (genericPartialLocationStrings != null) {
\r
822 for (int i = 0; i < genericPartialLocationStrings.length; i++) {
\r
823 if (genericPartialLocationStrings[i][0].equals(mzid)) {
\r
825 if (!commonlyUsedOnly || genericPartialLocationStrings[i][3] != null) {
\r
826 result = genericPartialLocationStrings[i][2];
\r
829 result = genericPartialLocationStrings[i][1];
\r
840 * Returns a localized zone string from bundle.
\r
842 private static String getZoneStringFromBundle(ICUResourceBundle bundle, String key, String type) {
\r
843 String zstring = null;
\r
844 if (bundle != null) {
\r
846 zstring = bundle.getStringWithFallback(key + "/" + type);
\r
847 } catch (MissingResourceException ex) {
\r
848 // throw away the exception
\r
855 * Returns if the short strings of the zone/metazone is commonly used.
\r
857 private static boolean isCommonlyUsed(ICUResourceBundle bundle, String key) {
\r
858 boolean commonlyUsed = false;
\r
859 if (bundle != null) {
\r
861 UResourceBundle cuRes = bundle.getWithFallback(key + "/" + RESKEY_COMMONLY_USED);
\r
862 int cuValue = cuRes.getInt();
\r
863 commonlyUsed = (cuValue != 0);
\r
864 } catch (MissingResourceException ex) {
\r
865 // throw away the exception
\r
868 return commonlyUsed;
\r
872 * Returns a localized country string for the country code. If no actual
\r
873 * localized string is found, countryCode itself is returned.
\r
875 private static String getLocalizedCountry(String countryCode, ULocale locale) {
\r
876 String countryStr = null;
\r
877 if (countryCode != null) {
\r
878 ICUResourceBundle rb =
\r
879 (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_REGION_BASE_NAME, locale);
\r
881 // TODO: There is a design bug in UResourceBundle and getLoadingStatus() does not work well.
\r
883 // if (rb.getLoadingStatus() != ICUResourceBundle.FROM_ROOT && rb.getLoadingStatus() != ICUResourceBundle.FROM_DEFAULT) {
\r
884 // country = ULocale.getDisplayCountry("xx_" + country_code, locale);
\r
886 // START WORKAROUND
\r
887 ULocale rbloc = rb.getULocale();
\r
888 if (!rbloc.equals(ULocale.ROOT) && rbloc.getLanguage().equals(locale.getLanguage())) {
\r
889 countryStr = ULocale.getDisplayCountry("xx_" + countryCode, locale);
\r
892 if (countryStr == null || countryStr.length() == 0) {
\r
893 countryStr = countryCode;
\r
900 * Gets an instance of MessageFormat used for formatting zone fallback string
\r
902 private static MessageFormat getFallbackFormat(ULocale locale) {
\r
903 String fallbackPattern = ZoneMeta.getTZLocalizationInfo(locale, ZoneMeta.FALLBACK_FORMAT);
\r
904 if (fallbackPattern == null) {
\r
905 fallbackPattern = "{1} ({0})";
\r
907 return new MessageFormat(fallbackPattern, locale);
\r
911 * Gets an instance of MessageFormat used for formatting zone region string
\r
913 private static MessageFormat getRegionFormat(ULocale locale) {
\r
914 String regionPattern = ZoneMeta.getTZLocalizationInfo(locale, ZoneMeta.REGION_FORMAT);
\r
915 if (regionPattern == null) {
\r
916 regionPattern = "{0}";
\r
918 return new MessageFormat(regionPattern, locale);
\r
922 * Index value mapping between DateFormatSymbols's zoneStrings and
\r
923 * the string types defined in this class.
\r
925 private static final int[] INDEXMAP = {
\r
927 ZSIDX_LONG_STANDARD, // 1 - long standard
\r
928 ZSIDX_SHORT_STANDARD, // 2 - short standard
\r
929 ZSIDX_LONG_DAYLIGHT, // 3 - long daylight
\r
930 ZSIDX_SHORT_DAYLIGHT, // 4 - short daylight
\r
931 ZSIDX_LOCATION, // 5 - generic location
\r
932 ZSIDX_LONG_GENERIC, // 6 - long generic non-location
\r
933 ZSIDX_SHORT_GENERIC // 7 - short generic non-location
\r
937 * Convert from zone string array index for zoneStrings used by DateFormatSymbols#get/setZoneStrings
\r
938 * to the type constants defined by this class, such as ZSIDX_LONG_STANDARD.
\r
940 private static int getNameTypeIndex(int i) {
\r
942 if (i >= 1 && i < INDEXMAP.length) {
\r
949 * Mapping from name type index to name type
\r
951 private static final int[] NAMETYPEMAP = {
\r
952 LOCATION, // ZSIDX_LOCATION
\r
953 STANDARD_LONG, // ZSIDX_LONG_STANDARD
\r
954 STANDARD_SHORT, // ZSIDX_SHORT_STANDARD
\r
955 DAYLIGHT_LONG, // ZSIDX_LONG_DAYLIGHT
\r
956 DAYLIGHT_SHORT, // ZSIDX_SHORT_DAYLIGHT
\r
957 GENERIC_LONG, // ZSIDX_LONG_GENERIC
\r
958 GENERIC_SHORT, // ZSIDX_SHORT_GENERIC
\r
961 private static int getNameType(int typeIdx) {
\r
963 if (typeIdx >= 0 && typeIdx < NAMETYPEMAP.length) {
\r
964 type = NAMETYPEMAP[typeIdx];
\r
970 * Returns region used for ZoneMeta#getZoneIdByMetazone.
\r
972 private String getRegion() {
\r
973 if (region == null) {
\r
974 if (locale != null) {
\r
975 region = locale.getCountry();
\r
976 if (region.length() == 0) {
\r
977 ULocale tmp = ULocale.addLikelySubtags(locale);
\r
978 region = tmp.getCountry();
\r
987 // This method does lazy zone string loading
\r
988 private ZoneStringInfo find(String text, int start, int types) {
\r
989 ZoneStringInfo result = subFind(text, start, types);
\r
990 if (isFullyLoaded) {
\r
993 // When zone string data is partially loaded,
\r
994 // this method return the result only when
\r
995 // the input text is fully consumed.
\r
996 if (result != null) {
\r
997 int matchLen = result.getString().length();
\r
998 if (text.length() - start == matchLen) {
\r
1002 // Now load all zone strings
\r
1004 return subFind(text, start, types);
\r
1008 * Find a prefix matching time zone for the given zone string types.
\r
1009 * @param text The text contains a time zone string
\r
1010 * @param start The start index within the text
\r
1011 * @param types The bit mask representing a set of requested types
\r
1012 * @return If any zone string matched for the requested types, returns a
\r
1013 * ZoneStringInfo for the longest match. If no matches are found for
\r
1014 * the requested types, returns a ZoneStringInfo for the longest match
\r
1015 * for any other types. If nothing matches at all, returns null.
\r
1017 private ZoneStringInfo subFind(String text, int start, int types) {
\r
1018 ZoneStringInfo result = null;
\r
1019 ZoneStringSearchResultHandler handler = new ZoneStringSearchResultHandler();
\r
1020 zoneStringsTrie.find(text, start, handler);
\r
1021 List<ZoneStringInfo> list = handler.getMatchedZoneStrings();
\r
1022 ZoneStringInfo fallback = null;
\r
1023 if (list != null && list.size() > 0) {
\r
1024 Iterator<ZoneStringInfo> it = list.iterator();
\r
1025 while (it.hasNext()) {
\r
1026 ZoneStringInfo tmp = it.next();
\r
1027 if ((types & tmp.getType()) != 0) {
\r
1028 if (result == null || result.getString().length() < tmp.getString().length()) {
\r
1030 } else if (result.getString().length() == tmp.getString().length()) {
\r
1031 // Tie breaker - there are some examples that a
\r
1032 // long standard name is identical with a location
\r
1033 // name - for example, "Uruguay Time". In this case,
\r
1034 // we interpret it as generic, not specific.
\r
1035 if (tmp.isGeneric() && !result.isGeneric()) {
\r
1039 } else if (result == null) {
\r
1040 if (fallback == null || fallback.getString().length() < tmp.getString().length()) {
\r
1042 } else if (fallback.getString().length() == tmp.getString().length()) {
\r
1043 if (tmp.isGeneric() && !fallback.isGeneric()) {
\r
1050 if (result == null && fallback != null) {
\r
1051 result = fallback;
\r
1058 private static class ZoneStringSearchResultHandler implements TextTrieMap.ResultHandler<ZoneStringInfo> {
\r
1060 private ArrayList<ZoneStringInfo> resultList;
\r
1062 public boolean handlePrefixMatch(int matchLength, Iterator<ZoneStringInfo> values) {
\r
1063 if (resultList == null) {
\r
1064 resultList = new ArrayList<ZoneStringInfo>();
\r
1066 while (values.hasNext()) {
\r
1067 ZoneStringInfo zsitem = values.next();
\r
1068 if (zsitem == null) {
\r
1072 for (; i < resultList.size(); i++) {
\r
1073 ZoneStringInfo tmp = resultList.get(i);
\r
1074 if (zsitem.getType() == tmp.getType()) {
\r
1075 if (matchLength > tmp.getString().length()) {
\r
1076 resultList.set(i, zsitem);
\r
1081 if (i == resultList.size()) {
\r
1082 // not found in the current list
\r
1083 resultList.add(zsitem);
\r
1089 List<ZoneStringInfo> getMatchedZoneStrings() {
\r
1090 if (resultList == null || resultList.size() == 0) {
\r
1093 return resultList;
\r