• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5  *******************************************************************************
6  * Copyright (C) 2014-2016, International Business Machines Corporation and
7  * others. All Rights Reserved.
8  *******************************************************************************
9  */
10 package ohos.global.icu.impl;
11 
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.EnumSet;
15 import java.util.Iterator;
16 import java.util.LinkedList;
17 import java.util.MissingResourceException;
18 import java.util.Set;
19 import java.util.concurrent.ConcurrentHashMap;
20 
21 import ohos.global.icu.impl.TextTrieMap.ResultHandler;
22 import ohos.global.icu.text.TimeZoneNames;
23 import ohos.global.icu.util.ULocale;
24 import ohos.global.icu.util.UResourceBundle;
25 
26 /**
27  * Yet another TimeZoneNames implementation based on the tz database.
28  * This implementation contains only tz abbreviations (short standard
29  * and daylight names) for each metazone.
30  *
31  * The data file $ICU4C_ROOT/source/data/zone/tzdbNames.txt contains
32  * the metazone - abbreviations mapping data (manually edited).
33  *
34  * Note: The abbreviations in the tz database are not necessarily
35  * unique. For example, parsing abbreviation "IST" is ambiguous
36  * (can be parsed as India Standard Time or Israel Standard Time).
37  * The data file (tzdbNames.txt) contains regional mapping, and
38  * the locale in the constructor is used as a hint for resolving
39  * these ambiguous names.
40  * @hide exposed on OHOS
41  */
42 public class TZDBTimeZoneNames extends TimeZoneNames {
43     private static final long serialVersionUID = 1L;
44 
45     private static final ConcurrentHashMap<String, TZDBNames> TZDB_NAMES_MAP =
46             new ConcurrentHashMap<String, TZDBNames>();
47 
48     private static volatile TextTrieMap<TZDBNameInfo> TZDB_NAMES_TRIE = null;
49 
50     private static final ICUResourceBundle ZONESTRINGS;
51     static {
52         UResourceBundle bundle = ICUResourceBundle
53                 .getBundleInstance(ICUData.ICU_ZONE_BASE_NAME, "tzdbNames");
54         ZONESTRINGS = (ICUResourceBundle)bundle.get("zoneStrings");
55     }
56 
57     private ULocale _locale;
58     private transient volatile String _region;
59 
TZDBTimeZoneNames(ULocale loc)60     public TZDBTimeZoneNames(ULocale loc) {
61         _locale = loc;
62     }
63 
64     /* (non-Javadoc)
65      * @see ohos.global.icu.text.TimeZoneNames#getAvailableMetaZoneIDs()
66      */
67     @Override
getAvailableMetaZoneIDs()68     public Set<String> getAvailableMetaZoneIDs() {
69         return TimeZoneNamesImpl._getAvailableMetaZoneIDs();
70     }
71 
72     /* (non-Javadoc)
73      * @see ohos.global.icu.text.TimeZoneNames#getAvailableMetaZoneIDs(java.lang.String)
74      */
75     @Override
getAvailableMetaZoneIDs(String tzID)76     public Set<String> getAvailableMetaZoneIDs(String tzID) {
77         return TimeZoneNamesImpl._getAvailableMetaZoneIDs(tzID);
78     }
79 
80     /* (non-Javadoc)
81      * @see ohos.global.icu.text.TimeZoneNames#getMetaZoneID(java.lang.String, long)
82      */
83     @Override
getMetaZoneID(String tzID, long date)84     public String getMetaZoneID(String tzID, long date) {
85         return TimeZoneNamesImpl._getMetaZoneID(tzID, date);
86     }
87 
88     /* (non-Javadoc)
89      * @see ohos.global.icu.text.TimeZoneNames#getReferenceZoneID(java.lang.String, java.lang.String)
90      */
91     @Override
getReferenceZoneID(String mzID, String region)92     public String getReferenceZoneID(String mzID, String region) {
93         return TimeZoneNamesImpl._getReferenceZoneID(mzID, region);
94     }
95 
96     /* (non-Javadoc)
97      * @see ohos.global.icu.text.TimeZoneNames#getMetaZoneDisplayName(java.lang.String,
98      *      ohos.global.icu.text.TimeZoneNames.NameType)
99      */
100     @Override
getMetaZoneDisplayName(String mzID, NameType type)101     public String getMetaZoneDisplayName(String mzID, NameType type) {
102         if (mzID == null || mzID.length() == 0 ||
103                 (type != NameType.SHORT_STANDARD && type != NameType.SHORT_DAYLIGHT)) {
104             return null;
105         }
106         return getMetaZoneNames(mzID).getName(type);
107     }
108 
109     /* (non-Javadoc)
110      * @see ohos.global.icu.text.TimeZoneNames#getTimeZoneDisplayName(java.lang.String,
111      *      ohos.global.icu.text.TimeZoneNames.NameType)
112      */
113     @Override
getTimeZoneDisplayName(String tzID, NameType type)114     public String getTimeZoneDisplayName(String tzID, NameType type) {
115         // No abbreviations associated a zone directly for now.
116         return null;
117     }
118 
119 //    /* (non-Javadoc)
120 //     * @see ohos.global.icu.text.TimeZoneNames#getExemplarLocationName(java.lang.String)
121 //     */
122 //    public String getExemplarLocationName(String tzID) {
123 //        return super.getExemplarLocationName(tzID);
124 //    }
125 
126     /* (non-Javadoc)
127      * @see ohos.global.icu.text.TimeZoneNames#find(java.lang.CharSequence, int, java.util.EnumSet)
128      */
129     @Override
find(CharSequence text, int start, EnumSet<NameType> nameTypes)130     public Collection<MatchInfo> find(CharSequence text, int start, EnumSet<NameType> nameTypes) {
131         if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
132             throw new IllegalArgumentException("bad input text or range");
133         }
134 
135         prepareFind();
136         TZDBNameSearchHandler handler = new TZDBNameSearchHandler(nameTypes, getTargetRegion());
137         TZDB_NAMES_TRIE.find(text, start, handler);
138         return handler.getMatches();
139     }
140 
141     private static class TZDBNames {
142         public static final TZDBNames EMPTY_TZDBNAMES = new TZDBNames(null, null);
143 
144         private String[] _names;
145         private String[] _parseRegions;
146         private static final String[] KEYS = {"ss", "sd"};
147 
TZDBNames(String[] names, String[] parseRegions)148         private TZDBNames(String[] names, String[] parseRegions) {
149             _names = names;
150             _parseRegions = parseRegions;
151         }
152 
getInstance(ICUResourceBundle zoneStrings, String key)153         static TZDBNames getInstance(ICUResourceBundle zoneStrings, String key) {
154             if (zoneStrings == null || key == null || key.length() == 0) {
155                 return EMPTY_TZDBNAMES;
156             }
157 
158             ICUResourceBundle table = null;
159             try {
160                 table = (ICUResourceBundle)zoneStrings.get(key);
161             } catch (MissingResourceException e) {
162                 return EMPTY_TZDBNAMES;
163             }
164 
165             boolean isEmpty = true;
166             String[] names = new String[KEYS.length];
167             for (int i = 0; i < names.length; i++) {
168                 try {
169                     names[i] = table.getString(KEYS[i]);
170                     isEmpty = false;
171                 } catch (MissingResourceException e) {
172                     names[i] = null;
173                 }
174             }
175 
176             if (isEmpty) {
177                 return EMPTY_TZDBNAMES;
178             }
179 
180             String[] parseRegions = null;
181             try {
182                 ICUResourceBundle regionsRes = (ICUResourceBundle)table.get("parseRegions");
183                 if (regionsRes.getType() == UResourceBundle.STRING) {
184                     parseRegions = new String[1];
185                     parseRegions[0] = regionsRes.getString();
186                 } else if (regionsRes.getType() == UResourceBundle.ARRAY) {
187                     parseRegions = regionsRes.getStringArray();
188                 }
189             } catch (MissingResourceException e) {
190                 // fall through
191             }
192 
193             return new TZDBNames(names, parseRegions);
194         }
195 
getName(NameType type)196         String getName(NameType type) {
197             if (_names == null) {
198                 return null;
199             }
200             String name = null;
201             switch (type) {
202             case SHORT_STANDARD:
203                 name = _names[0];
204                 break;
205             case SHORT_DAYLIGHT:
206                 name = _names[1];
207                 break;
208             default:
209                 // No names for all other types handled by
210                 // this class.
211                 break;
212             }
213 
214             return name;
215         }
216 
getParseRegions()217         String[] getParseRegions() {
218             return _parseRegions;
219         }
220     }
221 
222     private static class TZDBNameInfo {
223         final String mzID;
224         final NameType type;
225         final boolean ambiguousType;
226         final String[] parseRegions;
227 
TZDBNameInfo(String mzID, NameType type, boolean ambiguousType, String[] parseRegions)228         TZDBNameInfo(String mzID, NameType type, boolean ambiguousType, String[] parseRegions) {
229             this.mzID = mzID;
230             this.type = type;
231             this.ambiguousType = ambiguousType;
232             this.parseRegions = parseRegions;
233         }
234     }
235 
236     private static class TZDBNameSearchHandler implements ResultHandler<TZDBNameInfo> {
237         private EnumSet<NameType> _nameTypes;
238         private Collection<MatchInfo> _matches;
239         private String _region;
240 
TZDBNameSearchHandler(EnumSet<NameType> nameTypes, String region)241         TZDBNameSearchHandler(EnumSet<NameType> nameTypes, String region) {
242             _nameTypes = nameTypes;
243             assert region != null;
244             _region = region;
245         }
246 
247         /* (non-Javadoc)
248          * @see ohos.global.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int,
249          *      java.util.Iterator)
250          */
251         @Override
handlePrefixMatch(int matchLength, Iterator<TZDBNameInfo> values)252         public boolean handlePrefixMatch(int matchLength, Iterator<TZDBNameInfo> values) {
253             TZDBNameInfo match = null;
254             TZDBNameInfo defaultRegionMatch = null;
255 
256             while (values.hasNext()) {
257                 TZDBNameInfo ninfo = values.next();
258 
259                 if (_nameTypes != null && !_nameTypes.contains(ninfo.type)) {
260                     continue;
261                 }
262 
263                 // Some tz database abbreviations are ambiguous. For example,
264                 // CST means either Central Standard Time or China Standard Time.
265                 // Unlike CLDR time zone display names, this implementation
266                 // does not use unique names. And TimeZoneFormat does not expect
267                 // multiple results returned for the same time zone type.
268                 // For this reason, this implementation resolve one among same
269                 // zone type with a same name at this level.
270                 if (ninfo.parseRegions == null) {
271                     // parseRegions == null means this is the default metazone
272                     // mapping for the abbreviation.
273                     if (defaultRegionMatch == null) {
274                         match = defaultRegionMatch = ninfo;
275                     }
276                 } else {
277                     boolean matchRegion = false;
278                     // non-default metazone mapping for an abbreviation
279                     // comes with applicable regions. For example, the default
280                     // metazone mapping for "CST" is America_Central,
281                     // but if region is one of CN/MO/TW, "CST" is parsed
282                     // as metazone China (China Standard Time).
283                     for (String region : ninfo.parseRegions) {
284                         if (_region.equals(region)) {
285                             match = ninfo;
286                             matchRegion = true;
287                             break;
288                         }
289                     }
290                     if (matchRegion) {
291                         break;
292                     }
293                     if (match == null) {
294                         match = ninfo;
295                     }
296                 }
297             }
298 
299             if (match != null) {
300                 NameType ntype = match.type;
301                 // Note: Workaround for duplicated standard/daylight names
302                 // The tz database contains a few zones sharing a
303                 // same name for both standard time and daylight saving
304                 // time. For example, Australia/Sydney observes DST,
305                 // but "EST" is used for both standard and daylight.
306                 // When both SHORT_STANDARD and SHORT_DAYLIGHT are included
307                 // in the find operation, we cannot tell which one was
308                 // actually matched.
309                 // TimeZoneFormat#parse returns a matched name type (standard
310                 // or daylight) and DateFormat implementation uses the info to
311                 // to adjust actual time. To avoid false type information,
312                 // this implementation replaces the name type with SHORT_GENERIC.
313                 if (match.ambiguousType
314                         && (ntype == NameType.SHORT_STANDARD || ntype == NameType.SHORT_DAYLIGHT)
315                         && _nameTypes.contains(NameType.SHORT_STANDARD)
316                         && _nameTypes.contains(NameType.SHORT_DAYLIGHT)) {
317                     ntype = NameType.SHORT_GENERIC;
318                 }
319                 MatchInfo minfo = new MatchInfo(ntype, null, match.mzID, matchLength);
320                 if (_matches == null) {
321                     _matches = new LinkedList<MatchInfo>();
322                 }
323                 _matches.add(minfo);
324             }
325 
326             return true;
327         }
328 
329         /**
330          * Returns the match results
331          * @return the match results
332          */
getMatches()333         public Collection<MatchInfo> getMatches() {
334             if (_matches == null) {
335                 return Collections.emptyList();
336             }
337             return _matches;
338         }
339     }
340 
getMetaZoneNames(String mzID)341     private static TZDBNames getMetaZoneNames(String mzID) {
342         TZDBNames names = TZDB_NAMES_MAP.get(mzID);
343         if (names == null) {
344             names = TZDBNames.getInstance(ZONESTRINGS, "meta:" + mzID);
345             mzID = mzID.intern();
346             TZDBNames tmpNames = TZDB_NAMES_MAP.putIfAbsent(mzID, names);
347             names = (tmpNames == null) ? names : tmpNames;
348         }
349         return names;
350     }
351 
prepareFind()352     private static void prepareFind() {
353         if (TZDB_NAMES_TRIE == null) {
354             synchronized(TZDBTimeZoneNames.class) {
355                 if (TZDB_NAMES_TRIE == null) {
356                     // loading all names into trie
357                     TextTrieMap<TZDBNameInfo> trie = new TextTrieMap<TZDBNameInfo>(true);
358                     Set<String> mzIDs = TimeZoneNamesImpl._getAvailableMetaZoneIDs();
359                     for (String mzID : mzIDs) {
360                         TZDBNames names = getMetaZoneNames(mzID);
361                         String std = names.getName(NameType.SHORT_STANDARD);
362                         String dst = names.getName(NameType.SHORT_DAYLIGHT);
363                         if (std == null && dst == null) {
364                             continue;
365                         }
366                         String[] parseRegions = names.getParseRegions();
367                         mzID = mzID.intern();
368 
369                         // The tz database contains a few zones sharing a
370                         // same name for both standard time and daylight saving
371                         // time. For example, Australia/Sydney observes DST,
372                         // but "EST" is used for both standard and daylight.
373                         // we need to store the information for later processing.
374                         boolean ambiguousType = (std != null && dst != null && std.equals(dst));
375 
376                         if (std != null) {
377                             TZDBNameInfo stdInf = new TZDBNameInfo(mzID,
378                                     NameType.SHORT_STANDARD,
379                                     ambiguousType,
380                                     parseRegions);
381                             trie.put(std, stdInf);
382                         }
383                         if (dst != null) {
384                             TZDBNameInfo dstInf = new TZDBNameInfo(mzID,
385                                     NameType.SHORT_DAYLIGHT,
386                                     ambiguousType,
387                                     parseRegions);
388                             trie.put(dst, dstInf);
389                         }
390                     }
391                     TZDB_NAMES_TRIE = trie;
392                 }
393             }
394         }
395     }
396 
getTargetRegion()397     private String getTargetRegion() {
398         if (_region == null) {
399             String region = _locale.getCountry();
400             if (region.length() == 0) {
401                 ULocale tmp = ULocale.addLikelySubtags(_locale);
402                 region = tmp.getCountry();
403                 if (region.length() == 0) {
404                     region = "001";
405                 }
406             }
407             _region = region;
408         }
409         return _region;
410     }
411 }
412