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) 2003-2016 International Business Machines 7* Corporation and others. All Rights Reserved. 8********************************************************************** 9* Author: Alan Liu 10* Created: September 4 2003 11* Since: ICU 2.8 12********************************************************************** 13*/ 14package android.icu.impl; 15 16import java.lang.ref.SoftReference; 17import java.text.ParsePosition; 18import java.util.Collections; 19import java.util.Locale; 20import java.util.MissingResourceException; 21import java.util.Set; 22import java.util.TreeSet; 23 24import android.icu.text.NumberFormat; 25import android.icu.util.Output; 26import android.icu.util.SimpleTimeZone; 27import android.icu.util.TimeZone; 28import android.icu.util.TimeZone.SystemTimeZoneType; 29import android.icu.util.UResourceBundle; 30 31/** 32 * This class, not to be instantiated, implements the meta-data 33 * missing from the underlying core JDK implementation of time zones. 34 * There are two missing features: Obtaining a list of available zones 35 * for a given country (as defined by the Olson database), and 36 * obtaining a list of equivalent zones for a given zone (as defined 37 * by Olson links). 38 * 39 * This class uses a data class, ZoneMetaData, which is created by the 40 * tool tz2icu. 41 * 42 * @author Alan Liu 43 * @hide Only a subset of ICU is exposed in Android 44 */ 45public final class ZoneMeta { 46 private static final boolean ASSERT = false; 47 48 private static final String ZONEINFORESNAME = "zoneinfo64"; 49 private static final String kREGIONS = "Regions"; 50 private static final String kZONES = "Zones"; 51 private static final String kNAMES = "Names"; 52 53 private static final String kGMT_ID = "GMT"; 54 private static final String kCUSTOM_TZ_PREFIX = "GMT"; 55 56 private static final String kWorld = "001"; 57 58 private static SoftReference<Set<String>> REF_SYSTEM_ZONES; 59 private static SoftReference<Set<String>> REF_CANONICAL_SYSTEM_ZONES; 60 private static SoftReference<Set<String>> REF_CANONICAL_SYSTEM_LOCATION_ZONES; 61 62 /** 63 * Returns an immutable set of system time zone IDs. 64 * Etc/Unknown is excluded. 65 * @return An immutable set of system time zone IDs. 66 */ 67 private static synchronized Set<String> getSystemZIDs() { 68 Set<String> systemZones = null; 69 if (REF_SYSTEM_ZONES != null) { 70 systemZones = REF_SYSTEM_ZONES.get(); 71 } 72 if (systemZones == null) { 73 Set<String> systemIDs = new TreeSet<String>(); 74 String[] allIDs = getZoneIDs(); 75 for (String id : allIDs) { 76 // exclude Etc/Unknown 77 if (id.equals(TimeZone.UNKNOWN_ZONE_ID)) { 78 continue; 79 } 80 systemIDs.add(id); 81 } 82 systemZones = Collections.unmodifiableSet(systemIDs); 83 REF_SYSTEM_ZONES = new SoftReference<Set<String>>(systemZones); 84 } 85 return systemZones; 86 } 87 88 /** 89 * Returns an immutable set of canonical system time zone IDs. 90 * The result set is a subset of {@link #getSystemZIDs()}, but not 91 * including aliases, such as "US/Eastern". 92 * @return An immutable set of canonical system time zone IDs. 93 */ 94 private static synchronized Set<String> getCanonicalSystemZIDs() { 95 Set<String> canonicalSystemZones = null; 96 if (REF_CANONICAL_SYSTEM_ZONES != null) { 97 canonicalSystemZones = REF_CANONICAL_SYSTEM_ZONES.get(); 98 } 99 if (canonicalSystemZones == null) { 100 Set<String> canonicalSystemIDs = new TreeSet<String>(); 101 String[] allIDs = getZoneIDs(); 102 for (String id : allIDs) { 103 // exclude Etc/Unknown 104 if (id.equals(TimeZone.UNKNOWN_ZONE_ID)) { 105 continue; 106 } 107 String canonicalID = getCanonicalCLDRID(id); 108 if (id.equals(canonicalID)) { 109 canonicalSystemIDs.add(id); 110 } 111 } 112 canonicalSystemZones = Collections.unmodifiableSet(canonicalSystemIDs); 113 REF_CANONICAL_SYSTEM_ZONES = new SoftReference<Set<String>>(canonicalSystemZones); 114 } 115 return canonicalSystemZones; 116 } 117 118 /** 119 * Returns an immutable set of canonical system time zone IDs that 120 * are associated with actual locations. 121 * The result set is a subset of {@link #getCanonicalSystemZIDs()}, but not 122 * including IDs, such as "Etc/GTM+5". 123 * @return An immutable set of canonical system time zone IDs that 124 * are associated with actual locations. 125 */ 126 private static synchronized Set<String> getCanonicalSystemLocationZIDs() { 127 Set<String> canonicalSystemLocationZones = null; 128 if (REF_CANONICAL_SYSTEM_LOCATION_ZONES != null) { 129 canonicalSystemLocationZones = REF_CANONICAL_SYSTEM_LOCATION_ZONES.get(); 130 } 131 if (canonicalSystemLocationZones == null) { 132 Set<String> canonicalSystemLocationIDs = new TreeSet<String>(); 133 String[] allIDs = getZoneIDs(); 134 for (String id : allIDs) { 135 // exclude Etc/Unknown 136 if (id.equals(TimeZone.UNKNOWN_ZONE_ID)) { 137 continue; 138 } 139 String canonicalID = getCanonicalCLDRID(id); 140 if (id.equals(canonicalID)) { 141 String region = getRegion(id); 142 if (region != null && !region.equals(kWorld)) { 143 canonicalSystemLocationIDs.add(id); 144 } 145 } 146 } 147 canonicalSystemLocationZones = Collections.unmodifiableSet(canonicalSystemLocationIDs); 148 REF_CANONICAL_SYSTEM_LOCATION_ZONES = new SoftReference<Set<String>>(canonicalSystemLocationZones); 149 } 150 return canonicalSystemLocationZones; 151 } 152 153 /** 154 * Returns an immutable set of system IDs for the given conditions. 155 * @param type a system time zone type. 156 * @param region a region, or null. 157 * @param rawOffset a zone raw offset or null. 158 * @return An immutable set of system IDs for the given conditions. 159 */ 160 public static Set<String> getAvailableIDs(SystemTimeZoneType type, String region, Integer rawOffset) { 161 Set<String> baseSet = null; 162 switch (type) { 163 case ANY: 164 baseSet = getSystemZIDs(); 165 break; 166 case CANONICAL: 167 baseSet = getCanonicalSystemZIDs(); 168 break; 169 case CANONICAL_LOCATION: 170 baseSet = getCanonicalSystemLocationZIDs(); 171 break; 172 default: 173 // never occur 174 throw new IllegalArgumentException("Unknown SystemTimeZoneType"); 175 } 176 177 if (region == null && rawOffset == null) { 178 return baseSet; 179 } 180 181 if (region != null) { 182 region = region.toUpperCase(Locale.ENGLISH); 183 } 184 185 // Filter by region/rawOffset 186 Set<String> result = new TreeSet<String>(); 187 for (String id : baseSet) { 188 if (region != null) { 189 String r = getRegion(id); 190 if (!region.equals(r)) { 191 continue; 192 } 193 } 194 if (rawOffset != null) { 195 // This is VERY inefficient. 196 TimeZone z = getSystemTimeZone(id); 197 if (z == null || !rawOffset.equals(z.getRawOffset())) { 198 continue; 199 } 200 } 201 result.add(id); 202 } 203 if (result.isEmpty()) { 204 return Collections.emptySet(); 205 } 206 207 return Collections.unmodifiableSet(result); 208 } 209 210 /** 211 * Returns the number of IDs in the equivalency group that 212 * includes the given ID. An equivalency group contains zones 213 * that behave identically to the given zone. 214 * 215 * <p>If there are no equivalent zones, then this method returns 216 * 0. This means either the given ID is not a valid zone, or it 217 * is and there are no other equivalent zones. 218 * @param id a system time zone ID 219 * @return the number of zones in the equivalency group containing 220 * 'id', or zero if there are no equivalent zones. 221 * @see #getEquivalentID 222 */ 223 public static synchronized int countEquivalentIDs(String id) { 224 int count = 0; 225 UResourceBundle res = openOlsonResource(null, id); 226 if (res != null) { 227 try { 228 UResourceBundle links = res.get("links"); 229 int[] v = links.getIntVector(); 230 count = v.length; 231 } catch (MissingResourceException ex) { 232 // throw away 233 } 234 } 235 return count; 236 } 237 238 /** 239 * Returns an ID in the equivalency group that includes the given 240 * ID. An equivalency group contains zones that behave 241 * identically to the given zone. 242 * 243 * <p>The given index must be in the range 0..n-1, where n is the 244 * value returned by <code>countEquivalentIDs(id)</code>. For 245 * some value of 'index', the returned value will be equal to the 246 * given id. If the given id is not a valid system time zone, or 247 * if 'index' is out of range, then returns an empty string. 248 * @param id a system time zone ID 249 * @param index a value from 0 to n-1, where n is the value 250 * returned by <code>countEquivalentIDs(id)</code> 251 * @return the ID of the index-th zone in the equivalency group 252 * containing 'id', or an empty string if 'id' is not a valid 253 * system ID or 'index' is out of range 254 * @see #countEquivalentIDs 255 */ 256 public static synchronized String getEquivalentID(String id, int index) { 257 String result = ""; 258 if (index >= 0) { 259 UResourceBundle res = openOlsonResource(null, id); 260 if (res != null) { 261 int zoneIdx = -1; 262 try { 263 UResourceBundle links = res.get("links"); 264 int[] zones = links.getIntVector(); 265 if (index < zones.length) { 266 zoneIdx = zones[index]; 267 } 268 } catch (MissingResourceException ex) { 269 // throw away 270 } 271 if (zoneIdx >= 0) { 272 String tmp = getZoneID(zoneIdx); 273 if (tmp != null) { 274 result = tmp; 275 } 276 } 277 } 278 } 279 return result; 280 } 281 282 private static String[] ZONEIDS = null; 283 284 /* 285 * ICU frequently refers the zone ID array in zoneinfo resource 286 */ 287 private static synchronized String[] getZoneIDs() { 288 if (ZONEIDS == null) { 289 try { 290 UResourceBundle top = UResourceBundle.getBundleInstance( 291 ICUData.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER); 292 ZONEIDS = top.getStringArray(kNAMES); 293 } catch (MissingResourceException ex) { 294 // throw away.. 295 } 296 } 297 if (ZONEIDS == null) { 298 ZONEIDS = new String[0]; 299 } 300 return ZONEIDS; 301 } 302 303 private static String getZoneID(int idx) { 304 if (idx >= 0) { 305 String[] ids = getZoneIDs(); 306 if (idx < ids.length) { 307 return ids[idx]; 308 } 309 } 310 return null; 311 } 312 313 private static int getZoneIndex(String zid) { 314 int zoneIdx = -1; 315 316 String[] all = getZoneIDs(); 317 if (all.length > 0) { 318 int start = 0; 319 int limit = all.length; 320 321 int lastMid = Integer.MAX_VALUE; 322 for (;;) { 323 int mid = (start + limit) / 2; 324 if (lastMid == mid) { /* Have we moved? */ 325 break; /* We haven't moved, and it wasn't found. */ 326 } 327 lastMid = mid; 328 int r = zid.compareTo(all[mid]); 329 if (r == 0) { 330 zoneIdx = mid; 331 break; 332 } else if(r < 0) { 333 limit = mid; 334 } else { 335 start = mid; 336 } 337 } 338 } 339 340 return zoneIdx; 341 } 342 343 private static ICUCache<String, String> CANONICAL_ID_CACHE = new SimpleCache<String, String>(); 344 private static ICUCache<String, String> REGION_CACHE = new SimpleCache<String, String>(); 345 private static ICUCache<String, Boolean> SINGLE_COUNTRY_CACHE = new SimpleCache<String, Boolean>(); 346 347 public static String getCanonicalCLDRID(TimeZone tz) { 348 if (tz instanceof OlsonTimeZone) { 349 return ((OlsonTimeZone)tz).getCanonicalID(); 350 } 351 return getCanonicalCLDRID(tz.getID()); 352 } 353 354 /** 355 * Return the canonical id for this tzid defined by CLDR, which might be 356 * the id itself. If the given tzid is not known, return null. 357 * 358 * Note: This internal API supports all known system IDs and "Etc/Unknown" (which is 359 * NOT a system ID). 360 */ 361 public static String getCanonicalCLDRID(String tzid) { 362 String canonical = CANONICAL_ID_CACHE.get(tzid); 363 if (canonical == null) { 364 canonical = findCLDRCanonicalID(tzid); 365 if (canonical == null) { 366 // Resolve Olson link and try it again if necessary 367 try { 368 int zoneIdx = getZoneIndex(tzid); 369 if (zoneIdx >= 0) { 370 UResourceBundle top = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, 371 ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER); 372 UResourceBundle zones = top.get(kZONES); 373 UResourceBundle zone = zones.get(zoneIdx); 374 if (zone.getType() == UResourceBundle.INT) { 375 // It's a link - resolve link and lookup 376 tzid = getZoneID(zone.getInt()); 377 canonical = findCLDRCanonicalID(tzid); 378 } 379 if (canonical == null) { 380 canonical = tzid; 381 } 382 } 383 } catch (MissingResourceException e) { 384 // fall through 385 } 386 } 387 if (canonical != null) { 388 CANONICAL_ID_CACHE.put(tzid, canonical); 389 } 390 } 391 return canonical; 392 } 393 394 private static String findCLDRCanonicalID(String tzid) { 395 String canonical = null; 396 String tzidKey = tzid.replace('/', ':'); 397 398 try { 399 // First, try check if the given ID is canonical 400 UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, 401 "keyTypeData", ICUResourceBundle.ICU_DATA_CLASS_LOADER); 402 UResourceBundle typeMap = keyTypeData.get("typeMap"); 403 UResourceBundle typeKeys = typeMap.get("timezone"); 404 try { 405 /* UResourceBundle canonicalEntry = */ typeKeys.get(tzidKey); 406 // The given tzid is available in the canonical list 407 canonical = tzid; 408 } catch (MissingResourceException e) { 409 // fall through 410 } 411 if (canonical == null) { 412 // Try alias map 413 UResourceBundle typeAlias = keyTypeData.get("typeAlias"); 414 UResourceBundle aliasesForKey = typeAlias.get("timezone"); 415 canonical = aliasesForKey.getString(tzidKey); 416 } 417 } catch (MissingResourceException e) { 418 // fall through 419 } 420 return canonical; 421 } 422 423 /** 424 * Return the region code for this tzid. 425 * If tzid is not a system zone ID, this method returns null. 426 */ 427 public static String getRegion(String tzid) { 428 String region = REGION_CACHE.get(tzid); 429 if (region == null) { 430 int zoneIdx = getZoneIndex(tzid); 431 if (zoneIdx >= 0) { 432 try { 433 UResourceBundle top = UResourceBundle.getBundleInstance( 434 ICUData.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER); 435 UResourceBundle regions = top.get(kREGIONS); 436 if (zoneIdx < regions.getSize()) { 437 region = regions.getString(zoneIdx); 438 } 439 } catch (MissingResourceException e) { 440 // throw away 441 } 442 if (region != null) { 443 REGION_CACHE.put(tzid, region); 444 } 445 } 446 } 447 return region; 448 } 449 450 /** 451 * Return the canonical country code for this tzid. If we have none, or if the time zone 452 * is not associated with a country or unknown, return null. 453 */ 454 public static String getCanonicalCountry(String tzid) { 455 String country = getRegion(tzid); 456 if (country != null && country.equals(kWorld)) { 457 country = null; 458 } 459 return country; 460 } 461 462 /** 463 * Return the canonical country code for this tzid. If we have none, or if the time zone 464 * is not associated with a country or unknown, return null. When the given zone is the 465 * primary zone of the country, true is set to isPrimary. 466 */ 467 public static String getCanonicalCountry(String tzid, Output<Boolean> isPrimary) { 468 isPrimary.value = Boolean.FALSE; 469 470 String country = getRegion(tzid); 471 if (country != null && country.equals(kWorld)) { 472 return null; 473 } 474 475 // Check the cache 476 Boolean singleZone = SINGLE_COUNTRY_CACHE.get(tzid); 477 if (singleZone == null) { 478 Set<String> ids = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL_LOCATION, country, null); 479 assert(ids.size() >= 1); 480 singleZone = Boolean.valueOf(ids.size() <= 1); 481 SINGLE_COUNTRY_CACHE.put(tzid, singleZone); 482 } 483 484 if (singleZone) { 485 isPrimary.value = Boolean.TRUE; 486 } else { 487 // Note: We may cache the primary zone map in future. 488 489 // Even a country has multiple zones, one of them might be 490 // dominant and treated as a primary zone. 491 try { 492 UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "metaZones"); 493 UResourceBundle primaryZones = bundle.get("primaryZones"); 494 String primaryZone = primaryZones.getString(country); 495 if (tzid.equals(primaryZone)) { 496 isPrimary.value = Boolean.TRUE; 497 } else { 498 // The given ID might not be a canonical ID 499 String canonicalID = getCanonicalCLDRID(tzid); 500 if (canonicalID != null && canonicalID.equals(primaryZone)) { 501 isPrimary.value = Boolean.TRUE; 502 } 503 } 504 } catch (MissingResourceException e) { 505 // ignore 506 } 507 } 508 509 return country; 510 } 511 512 /** 513 * Given an ID and the top-level resource of the zoneinfo resource, 514 * open the appropriate resource for the given time zone. 515 * Dereference links if necessary. 516 * @param top the top level resource of the zoneinfo resource or null. 517 * @param id zone id 518 * @return the corresponding zone resource or null if not found 519 */ 520 public static UResourceBundle openOlsonResource(UResourceBundle top, String id) 521 { 522 UResourceBundle res = null; 523 int zoneIdx = getZoneIndex(id); 524 if (zoneIdx >= 0) { 525 try { 526 if (top == null) { 527 top = UResourceBundle.getBundleInstance( 528 ICUData.ICU_BASE_NAME, ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER); 529 } 530 UResourceBundle zones = top.get(kZONES); 531 UResourceBundle zone = zones.get(zoneIdx); 532 if (zone.getType() == UResourceBundle.INT) { 533 // resolve link 534 zone = zones.get(zone.getInt()); 535 } 536 res = zone; 537 } catch (MissingResourceException e) { 538 res = null; 539 } 540 } 541 return res; 542 } 543 544 545 /** 546 * System time zone object cache 547 */ 548 private static class SystemTimeZoneCache extends SoftCache<String, OlsonTimeZone, String> { 549 550 /* (non-Javadoc) 551 * @see android.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object) 552 */ 553 @Override 554 protected OlsonTimeZone createInstance(String key, String data) { 555 OlsonTimeZone tz = null; 556 try { 557 UResourceBundle top = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, 558 ZONEINFORESNAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER); 559 UResourceBundle res = openOlsonResource(top, data); 560 if (res != null) { 561 tz = new OlsonTimeZone(top, res, data); 562 tz.freeze(); 563 } 564 } catch (MissingResourceException e) { 565 // do nothing 566 } 567 return tz; 568 } 569 } 570 571 private static final SystemTimeZoneCache SYSTEM_ZONE_CACHE = new SystemTimeZoneCache(); 572 573 /** 574 * Returns a frozen OlsonTimeZone instance for the given ID. 575 * This method returns null when the given ID is unknown. 576 */ 577 public static OlsonTimeZone getSystemTimeZone(String id) { 578 return SYSTEM_ZONE_CACHE.getInstance(id, id); 579 } 580 581 // Maximum value of valid custom time zone hour/min 582 private static final int kMAX_CUSTOM_HOUR = 23; 583 private static final int kMAX_CUSTOM_MIN = 59; 584 private static final int kMAX_CUSTOM_SEC = 59; 585 586 /** 587 * Custom time zone object cache 588 */ 589 private static class CustomTimeZoneCache extends SoftCache<Integer, SimpleTimeZone, int[]> { 590 591 /* (non-Javadoc) 592 * @see android.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object) 593 */ 594 @Override 595 protected SimpleTimeZone createInstance(Integer key, int[] data) { 596 assert (data.length == 4); 597 assert (data[0] == 1 || data[0] == -1); 598 assert (data[1] >= 0 && data[1] <= kMAX_CUSTOM_HOUR); 599 assert (data[2] >= 0 && data[2] <= kMAX_CUSTOM_MIN); 600 assert (data[3] >= 0 && data[3] <= kMAX_CUSTOM_SEC); 601 String id = formatCustomID(data[1], data[2], data[3], data[0] < 0); 602 int offset = data[0] * ((data[1] * 60 + data[2]) * 60 + data[3]) * 1000; 603 SimpleTimeZone tz = new SimpleTimeZone(offset, id); 604 tz.freeze(); 605 return tz; 606 } 607 } 608 609 private static final CustomTimeZoneCache CUSTOM_ZONE_CACHE = new CustomTimeZoneCache(); 610 611 /** 612 * Parse a custom time zone identifier and return a corresponding zone. 613 * @param id a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or 614 * GMT[+-]hh. 615 * @return a frozen SimpleTimeZone with the given offset and 616 * no Daylight Savings Time, or null if the id cannot be parsed. 617 */ 618 public static SimpleTimeZone getCustomTimeZone(String id){ 619 int[] fields = new int[4]; 620 if (parseCustomID(id, fields)) { 621 // fields[0] - sign 622 // fields[1] - hour / 5-bit 623 // fields[2] - min / 6-bit 624 // fields[3] - sec / 6-bit 625 Integer key = Integer.valueOf( 626 fields[0] * (fields[1] | fields[2] << 5 | fields[3] << 11)); 627 return CUSTOM_ZONE_CACHE.getInstance(key, fields); 628 } 629 return null; 630 } 631 632 /** 633 * Parse a custom time zone identifier and return the normalized 634 * custom time zone identifier for the given custom id string. 635 * @param id a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or 636 * GMT[+-]hh. 637 * @return The normalized custom id string. 638 */ 639 public static String getCustomID(String id) { 640 int[] fields = new int[4]; 641 if (parseCustomID(id, fields)) { 642 return formatCustomID(fields[1], fields[2], fields[3], fields[0] < 0); 643 } 644 return null; 645 } 646 647 /* 648 * Parses the given custom time zone identifier 649 * @param id id A string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or 650 * GMT[+-]hh. 651 * @param fields An array of int (length = 4) to receive the parsed 652 * offset time fields. The sign is set to fields[0] (-1 or 1), 653 * hour is set to fields[1], minute is set to fields[2] and second is 654 * set to fields[3]. 655 * @return Returns true when the given custom id is valid. 656 */ 657 static boolean parseCustomID(String id, int[] fields) { 658 NumberFormat numberFormat = null; 659 660 if (id != null && id.length() > kGMT_ID.length() && 661 id.toUpperCase(Locale.ENGLISH).startsWith(kGMT_ID)) { 662 ParsePosition pos = new ParsePosition(kGMT_ID.length()); 663 int sign = 1; 664 int hour = 0; 665 int min = 0; 666 int sec = 0; 667 668 if (id.charAt(pos.getIndex()) == 0x002D /*'-'*/) { 669 sign = -1; 670 } else if (id.charAt(pos.getIndex()) != 0x002B /*'+'*/) { 671 return false; 672 } 673 pos.setIndex(pos.getIndex() + 1); 674 675 numberFormat = NumberFormat.getInstance(); 676 numberFormat.setParseIntegerOnly(true); 677 678 // Look for either hh:mm, hhmm, or hh 679 int start = pos.getIndex(); 680 681 Number n = numberFormat.parse(id, pos); 682 if (pos.getIndex() == start) { 683 return false; 684 } 685 hour = n.intValue(); 686 687 if (pos.getIndex() < id.length()){ 688 if (pos.getIndex() - start > 2 689 || id.charAt(pos.getIndex()) != 0x003A /*':'*/) { 690 return false; 691 } 692 // hh:mm 693 pos.setIndex(pos.getIndex() + 1); 694 int oldPos = pos.getIndex(); 695 n = numberFormat.parse(id, pos); 696 if ((pos.getIndex() - oldPos) != 2) { 697 // must be 2 digits 698 return false; 699 } 700 min = n.intValue(); 701 if (pos.getIndex() < id.length()) { 702 if (id.charAt(pos.getIndex()) != 0x003A /*':'*/) { 703 return false; 704 } 705 // [:ss] 706 pos.setIndex(pos.getIndex() + 1); 707 oldPos = pos.getIndex(); 708 n = numberFormat.parse(id, pos); 709 if (pos.getIndex() != id.length() 710 || (pos.getIndex() - oldPos) != 2) { 711 return false; 712 } 713 sec = n.intValue(); 714 } 715 } else { 716 // Supported formats are below - 717 // 718 // HHmmss 719 // Hmmss 720 // HHmm 721 // Hmm 722 // HH 723 // H 724 725 int length = pos.getIndex() - start; 726 if (length <= 0 || 6 < length) { 727 // invalid length 728 return false; 729 } 730 switch (length) { 731 case 1: 732 case 2: 733 // already set to hour 734 break; 735 case 3: 736 case 4: 737 min = hour % 100; 738 hour /= 100; 739 break; 740 case 5: 741 case 6: 742 sec = hour % 100; 743 min = (hour/100) % 100; 744 hour /= 10000; 745 break; 746 } 747 } 748 749 if (hour <= kMAX_CUSTOM_HOUR && min <= kMAX_CUSTOM_MIN && sec <= kMAX_CUSTOM_SEC) { 750 if (fields != null) { 751 if (fields.length >= 1) { 752 fields[0] = sign; 753 } 754 if (fields.length >= 2) { 755 fields[1] = hour; 756 } 757 if (fields.length >= 3) { 758 fields[2] = min; 759 } 760 if (fields.length >= 4) { 761 fields[3] = sec; 762 } 763 } 764 return true; 765 } 766 } 767 return false; 768 } 769 770 /** 771 * Creates a custom zone for the offset 772 * @param offset GMT offset in milliseconds 773 * @return A custom TimeZone for the offset with normalized time zone id 774 */ 775 public static SimpleTimeZone getCustomTimeZone(int offset) { 776 boolean negative = false; 777 int tmp = offset; 778 if (offset < 0) { 779 negative = true; 780 tmp = -offset; 781 } 782 783 int hour, min, sec; 784 785 if (ASSERT) { 786 Assert.assrt("millis!=0", tmp % 1000 != 0); 787 } 788 tmp /= 1000; 789 sec = tmp % 60; 790 tmp /= 60; 791 min = tmp % 60; 792 hour = tmp / 60; 793 794 // Note: No millisecond part included in TZID for now 795 String zid = formatCustomID(hour, min, sec, negative); 796 797 return new SimpleTimeZone(offset, zid); 798 } 799 800 /* 801 * Returns the normalized custom TimeZone ID 802 */ 803 static String formatCustomID(int hour, int min, int sec, boolean negative) { 804 // Create normalized time zone ID - GMT[+|-]hh:mm[:ss] 805 StringBuilder zid = new StringBuilder(kCUSTOM_TZ_PREFIX); 806 if (hour != 0 || min != 0) { 807 if(negative) { 808 zid.append('-'); 809 } else { 810 zid.append('+'); 811 } 812 // Always use US-ASCII digits 813 if (hour < 10) { 814 zid.append('0'); 815 } 816 zid.append(hour); 817 zid.append(':'); 818 if (min < 10) { 819 zid.append('0'); 820 } 821 zid.append(min); 822 823 if (sec != 0) { 824 // Optional second field 825 zid.append(':'); 826 if (sec < 10) { 827 zid.append('0'); 828 } 829 zid.append(sec); 830 } 831 } 832 return zid.toString(); 833 } 834 835 /** 836 * Returns the time zone's short ID for the zone. 837 * For example, "uslax" for zone "America/Los_Angeles". 838 * @param tz the time zone 839 * @return the short ID of the time zone, or null if the short ID is not available. 840 */ 841 public static String getShortID(TimeZone tz) { 842 String canonicalID = null; 843 844 if (tz instanceof OlsonTimeZone) { 845 canonicalID = ((OlsonTimeZone)tz).getCanonicalID(); 846 } 847 else { 848 canonicalID = getCanonicalCLDRID(tz.getID()); 849 } 850 if (canonicalID == null) { 851 return null; 852 } 853 return getShortIDFromCanonical(canonicalID); 854 } 855 856 /** 857 * Returns the time zone's short ID for the zone ID. 858 * For example, "uslax" for zone ID "America/Los_Angeles". 859 * @param id the time zone ID 860 * @return the short ID of the time zone ID, or null if the short ID is not available. 861 */ 862 public static String getShortID(String id) { 863 String canonicalID = getCanonicalCLDRID(id); 864 if (canonicalID == null) { 865 return null; 866 } 867 return getShortIDFromCanonical(canonicalID); 868 } 869 870 private static String getShortIDFromCanonical(String canonicalID) { 871 String shortID = null; 872 String tzidKey = canonicalID.replace('/', ':'); 873 874 try { 875 // First, try check if the given ID is canonical 876 UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, 877 "keyTypeData", ICUResourceBundle.ICU_DATA_CLASS_LOADER); 878 UResourceBundle typeMap = keyTypeData.get("typeMap"); 879 UResourceBundle typeKeys = typeMap.get("timezone"); 880 shortID = typeKeys.getString(tzidKey); 881 } catch (MissingResourceException e) { 882 // fall through 883 } 884 885 return shortID; 886 } 887 888} 889