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) 2007-2016, Google, International Business Machines Corporation 7 * and others. All Rights Reserved. 8 ******************************************************************************** 9 */ 10 11 package android.icu.dev.test.format; 12 13 import java.text.FieldPosition; 14 import java.text.ParseException; 15 import java.text.ParsePosition; 16 import java.util.ArrayList; 17 import java.util.Arrays; 18 import java.util.Collections; 19 import java.util.Date; 20 import java.util.EnumSet; 21 import java.util.List; 22 import java.util.Locale; 23 import java.util.Random; 24 import java.util.Set; 25 import java.util.TreeSet; 26 import java.util.concurrent.atomic.AtomicInteger; 27 import java.util.regex.Pattern; 28 29 import org.junit.Test; 30 31 import android.icu.dev.test.TestFmwk; 32 import android.icu.impl.TZDBTimeZoneNames; 33 import android.icu.impl.ZoneMeta; 34 import android.icu.lang.UCharacter; 35 import android.icu.text.DateFormat; 36 import android.icu.text.SimpleDateFormat; 37 import android.icu.text.TimeZoneFormat; 38 import android.icu.text.TimeZoneFormat.GMTOffsetPatternType; 39 import android.icu.text.TimeZoneFormat.ParseOption; 40 import android.icu.text.TimeZoneFormat.Style; 41 import android.icu.text.TimeZoneFormat.TimeType; 42 import android.icu.text.TimeZoneNames; 43 import android.icu.text.TimeZoneNames.Factory; 44 import android.icu.text.TimeZoneNames.NameType; 45 import android.icu.util.BasicTimeZone; 46 import android.icu.util.Calendar; 47 import android.icu.util.Output; 48 import android.icu.util.SimpleTimeZone; 49 import android.icu.util.TimeZone; 50 import android.icu.util.TimeZone.SystemTimeZoneType; 51 import android.icu.util.TimeZoneTransition; 52 import android.icu.util.ULocale; 53 54 public class TimeZoneFormatTest extends android.icu.dev.test.TestFmwk { 55 56 private static boolean JDKTZ = (TimeZone.getDefaultTimeZoneType() == TimeZone.TIMEZONE_JDK); 57 private static final Pattern EXCL_TZ_PATTERN = Pattern.compile(".*/Riyadh8[7-9]"); 58 59 private static final String[] PATTERNS = { 60 "z", 61 "zzzz", 62 "Z", // equivalent to "xxxx" 63 "ZZZZ", // equivalent to "OOOO" 64 "v", 65 "vvvv", 66 "O", 67 "OOOO", 68 "X", 69 "XX", 70 "XXX", 71 "XXXX", 72 "XXXXX", 73 "x", 74 "xx", 75 "xxx", 76 "xxxx", 77 "xxxxx", 78 "V", 79 "VV", 80 "VVV", 81 "VVVV" 82 }; 83 boolean REALLY_VERBOSE_LOG = false; 84 85 /* 86 * Test case for checking if a TimeZone is properly set in the result calendar 87 * and if the result TimeZone has the expected behavior. 88 */ 89 @Test 90 public void TestTimeZoneRoundTrip() { 91 boolean TEST_ALL = getBooleanProperty("TimeZoneRoundTripAll", false); 92 93 TimeZone unknownZone = new SimpleTimeZone(-31415, "Etc/Unknown"); 94 int badDstOffset = -1234; 95 int badZoneOffset = -2345; 96 97 int[][] testDateData = { 98 {2007, 1, 15}, 99 {2007, 6, 15}, 100 {1990, 1, 15}, 101 {1990, 6, 15}, 102 {1960, 1, 15}, 103 {1960, 6, 15}, 104 }; 105 106 Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 107 cal.clear(); 108 109 // Set up rule equivalency test range 110 long low, high; 111 cal.set(1900, 0, 1); 112 low = cal.getTimeInMillis(); 113 cal.set(2040, 0, 1); 114 high = cal.getTimeInMillis(); 115 116 // Set up test dates 117 Date[] DATES = new Date[testDateData.length]; 118 cal.clear(); 119 for (int i = 0; i < DATES.length; i++) { 120 cal.set(testDateData[i][0], testDateData[i][1], testDateData[i][2]); 121 DATES[i] = cal.getTime(); 122 } 123 124 // Set up test locales 125 ULocale[] LOCALES = null; 126 if (TEST_ALL || TestFmwk.getExhaustiveness() > 5) { 127 LOCALES = ULocale.getAvailableLocales(); 128 } else { 129 LOCALES = new ULocale[] {new ULocale("en"), new ULocale("en_CA"), new ULocale("fr"), new ULocale("zh_Hant")}; 130 } 131 132 String[] tzids; 133 if (JDKTZ) { 134 tzids = java.util.TimeZone.getAvailableIDs(); 135 } else { 136 tzids = TimeZone.getAvailableIDs(); 137 } 138 int[] inOffsets = new int[2]; 139 int[] outOffsets = new int[2]; 140 141 // Run the roundtrip test 142 for (int locidx = 0; locidx < LOCALES.length; locidx++) { 143 logln("Locale: " + LOCALES[locidx].toString()); 144 145 String localGMTString = TimeZoneFormat.getInstance(LOCALES[locidx]).formatOffsetLocalizedGMT(0); 146 147 for (int patidx = 0; patidx < PATTERNS.length; patidx++) { 148 logln(" pattern: " + PATTERNS[patidx]); 149 SimpleDateFormat sdf = new SimpleDateFormat(PATTERNS[patidx], LOCALES[locidx]); 150 151 for (int tzidx = 0; tzidx < tzids.length; tzidx++) { 152 if (EXCL_TZ_PATTERN.matcher(tzids[tzidx]).matches()) { 153 continue; 154 } 155 TimeZone tz = TimeZone.getTimeZone(tzids[tzidx]); 156 157 for (int datidx = 0; datidx < DATES.length; datidx++) { 158 // Format 159 sdf.setTimeZone(tz); 160 String tzstr = sdf.format(DATES[datidx]); 161 162 // Before parse, set unknown zone to SimpleDateFormat instance 163 // just for making sure that it does not depends on the time zone 164 // originally set. 165 sdf.setTimeZone(unknownZone); 166 167 // Parse 168 ParsePosition pos = new ParsePosition(0); 169 Calendar outcal = Calendar.getInstance(unknownZone); 170 outcal.set(Calendar.DST_OFFSET, badDstOffset); 171 outcal.set(Calendar.ZONE_OFFSET, badZoneOffset); 172 173 sdf.parse(tzstr, outcal, pos); 174 175 // Check the result 176 TimeZone outtz = outcal.getTimeZone(); 177 178 tz.getOffset(DATES[datidx].getTime(), false, inOffsets); 179 outtz.getOffset(DATES[datidx].getTime(), false, outOffsets); 180 181 if (PATTERNS[patidx].equals("V")) { 182 // Short zone ID - should support roundtrip for canonical CLDR IDs 183 String canonicalID = TimeZone.getCanonicalID(tzids[tzidx]); 184 if (!outtz.getID().equals(canonicalID)) { 185 if (outtz.getID().equals("Etc/Unknown")) { 186 // Note that some zones like Asia/Riyadh87 does not have 187 // short zone ID and "unk" is used as the fallback 188 if (REALLY_VERBOSE_LOG) { 189 logln("Canonical round trip failed (probably as expected); tz=" + tzids[tzidx] 190 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx] 191 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr 192 + ", outtz=" + outtz.getID()); 193 } 194 } else { 195 errln("Canonical round trip failed; tz=" + tzids[tzidx] 196 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx] 197 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr 198 + ", outtz=" + outtz.getID()); 199 } 200 } 201 } else if (PATTERNS[patidx].equals("VV")) { 202 // Zone ID - full roundtrip support 203 if (!outtz.getID().equals(tzids[tzidx])) { 204 errln("Zone ID round trip failed; tz=" + tzids[tzidx] 205 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx] 206 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr 207 + ", outtz=" + outtz.getID()); 208 } 209 } else if (PATTERNS[patidx].equals("VVV") || PATTERNS[patidx].equals("VVVV")) { 210 // Location: time zone rule must be preserved except 211 // zones not actually associated with a specific location. 212 String canonicalID = TimeZone.getCanonicalID(tzids[tzidx]); 213 if (canonicalID != null && !outtz.getID().equals(canonicalID)) { 214 // Canonical ID did not match - check the rules 215 boolean bFailure = false; 216 if ((tz instanceof BasicTimeZone) && (outtz instanceof BasicTimeZone)) { 217 boolean hasNoLocation = TimeZone.getRegion(tzids[tzidx]).equals("001"); 218 bFailure = !hasNoLocation 219 && !((BasicTimeZone)outtz).hasEquivalentTransitions(tz, low, high); 220 } 221 if (bFailure) { 222 errln("Canonical round trip failed; tz=" + tzids[tzidx] 223 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx] 224 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr 225 + ", outtz=" + outtz.getID()); 226 } else if (REALLY_VERBOSE_LOG) { 227 logln("Canonical round trip failed (as expected); tz=" + tzids[tzidx] 228 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx] 229 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr 230 + ", outtz=" + outtz.getID()); 231 } 232 } 233 } else { 234 boolean isOffsetFormat = (PATTERNS[patidx].charAt(0) == 'Z' 235 || PATTERNS[patidx].charAt(0) == 'O' 236 || PATTERNS[patidx].charAt(0) == 'X' 237 || PATTERNS[patidx].charAt(0) == 'x'); 238 boolean minutesOffset = false; 239 if (PATTERNS[patidx].charAt(0) == 'X' || PATTERNS[patidx].charAt(0) == 'x') { 240 minutesOffset = PATTERNS[patidx].length() <= 3; 241 } 242 243 if (!isOffsetFormat) { 244 // Check if localized GMT format is used as a fallback of name styles 245 int numDigits = 0; 246 for (int n = 0; n < tzstr.length(); n++) { 247 if (UCharacter.isDigit(tzstr.charAt(n))) { 248 numDigits++; 249 } 250 } 251 isOffsetFormat = (numDigits > 0); 252 } 253 254 if (isOffsetFormat || tzstr.equals(localGMTString)) { 255 // Localized GMT or ISO: total offset (raw + dst) must be preserved. 256 int inOffset = inOffsets[0] + inOffsets[1]; 257 int outOffset = outOffsets[0] + outOffsets[1]; 258 int diff = outOffset - inOffset; 259 if (minutesOffset) { 260 diff = (diff / 60000) * 60000; 261 } 262 if (diff != 0) { 263 errln("Offset round trip failed; tz=" + tzids[tzidx] 264 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx] 265 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr 266 + ", inOffset=" + inOffset + ", outOffset=" + outOffset); 267 } 268 } else { 269 // Specific or generic: raw offset must be preserved. 270 if (inOffsets[0] != outOffsets[0]) { 271 if (JDKTZ && tzids[tzidx].startsWith("SystemV/")) { 272 // JDK uses rule SystemV for these zones while 273 // ICU handles these zones as aliases of existing time zones 274 if (REALLY_VERBOSE_LOG) { 275 logln("Raw offset round trip failed; tz=" + tzids[tzidx] 276 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx] 277 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr 278 + ", inRawOffset=" + inOffsets[0] + ", outRawOffset=" + outOffsets[0]); 279 } 280 281 } else { 282 errln("Raw offset round trip failed; tz=" + tzids[tzidx] 283 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx] 284 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr 285 + ", inRawOffset=" + inOffsets[0] + ", outRawOffset=" + outOffsets[0]); 286 } 287 } 288 } 289 } 290 } 291 } 292 } 293 } 294 295 } 296 297 /* 298 * Test case of round trip time and text. This test case detects every canonical TimeZone's 299 * rule transition since 1900 until 2020, then check if time around each transition can 300 * round trip as expected. 301 */ 302 @Test 303 public void TestTimeRoundTrip() { 304 305 boolean TEST_ALL = getBooleanProperty("TimeZoneRoundTripAll", false); 306 307 int startYear, endYear; 308 309 if (TEST_ALL || TestFmwk.getExhaustiveness() > 5) { 310 startYear = 1900; 311 } else { 312 startYear = 1990; 313 } 314 315 Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 316 endYear = cal.get(Calendar.YEAR) + 3; 317 318 cal.set(startYear, Calendar.JANUARY, 1); 319 final long START_TIME = cal.getTimeInMillis(); 320 321 cal.set(endYear, Calendar.JANUARY, 1); 322 final long END_TIME = cal.getTimeInMillis(); 323 324 // These patterns are ambiguous at DST->STD local time overlap 325 List<String> AMBIGUOUS_DST_DECESSION = Arrays.asList("v", "vvvv", "V", "VV", "VVV", "VVVV"); 326 327 // These patterns are ambiguous at STD->STD/DST->DST local time overlap 328 List<String> AMBIGUOUS_NEGATIVE_SHIFT = Arrays.asList("z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV"); 329 330 // These patterns only support integer minutes offset 331 List<String> MINUTES_OFFSET = Arrays.asList("X", "XX", "XXX", "x", "xx", "xxx"); 332 333 // Regex pattern used for filtering zone IDs without exemplar location 334 final Pattern LOC_EXCLUSION_PATTERN = Pattern.compile("Etc/.*|SystemV/.*|.*/Riyadh8[7-9]"); 335 336 final String BASEPATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS"; 337 338 ULocale[] LOCALES = null; 339 340 // timer for performance analysis 341 long[] times = new long[PATTERNS.length]; 342 long timer; 343 344 if (TEST_ALL) { 345 // It may take about an hour for testing all locales 346 LOCALES = ULocale.getAvailableLocales(); 347 } else if (TestFmwk.getExhaustiveness() > 5) { 348 LOCALES = new ULocale[] { 349 new ULocale("ar_EG"), new ULocale("bg_BG"), new ULocale("ca_ES"), new ULocale("da_DK"), new ULocale("de"), 350 new ULocale("de_DE"), new ULocale("el_GR"), new ULocale("en"), new ULocale("en_AU"), new ULocale("en_CA"), 351 new ULocale("en_US"), new ULocale("es"), new ULocale("es_ES"), new ULocale("es_MX"), new ULocale("fi_FI"), 352 new ULocale("fr"), new ULocale("fr_CA"), new ULocale("fr_FR"), new ULocale("he_IL"), new ULocale("hu_HU"), 353 new ULocale("it"), new ULocale("it_IT"), new ULocale("ja"), new ULocale("ja_JP"), new ULocale("ko"), 354 new ULocale("ko_KR"), new ULocale("nb_NO"), new ULocale("nl_NL"), new ULocale("nn_NO"), new ULocale("pl_PL"), 355 new ULocale("pt"), new ULocale("pt_BR"), new ULocale("pt_PT"), new ULocale("ru_RU"), new ULocale("sv_SE"), 356 new ULocale("th_TH"), new ULocale("tr_TR"), new ULocale("zh"), new ULocale("zh_Hans"), new ULocale("zh_Hans_CN"), 357 new ULocale("zh_Hant"), new ULocale("zh_Hant_HK"), new ULocale("zh_Hant_TW") 358 }; 359 } else { 360 LOCALES = new ULocale[] { 361 new ULocale("en"), 362 }; 363 } 364 365 SimpleDateFormat sdfGMT = new SimpleDateFormat(BASEPATTERN); 366 sdfGMT.setTimeZone(TimeZone.getTimeZone("Etc/GMT")); 367 368 long testCounts = 0; 369 long[] testTimes = new long[4]; 370 boolean[] expectedRoundTrip = new boolean[4]; 371 int testLen = 0; 372 for (int locidx = 0; locidx < LOCALES.length; locidx++) { 373 logln("Locale: " + LOCALES[locidx].toString()); 374 for (int patidx = 0; patidx < PATTERNS.length; patidx++) { 375 logln(" pattern: " + PATTERNS[patidx]); 376 String pattern = BASEPATTERN + " " + PATTERNS[patidx]; 377 SimpleDateFormat sdf = new SimpleDateFormat(pattern, LOCALES[locidx]); 378 boolean minutesOffset = MINUTES_OFFSET.contains(PATTERNS[patidx]); 379 380 Set<String> ids = null; 381 if (JDKTZ) { 382 ids = new TreeSet<String>(); 383 String[] jdkIDs = java.util.TimeZone.getAvailableIDs(); 384 for (String jdkID : jdkIDs) { 385 if (EXCL_TZ_PATTERN.matcher(jdkID).matches()) { 386 continue; 387 } 388 String tmpID = TimeZone.getCanonicalID(jdkID); 389 if (tmpID != null) { 390 ids.add(tmpID); 391 } 392 } 393 } else { 394 ids = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null); 395 } 396 397 for (String id : ids) { 398 if (PATTERNS[patidx].equals("V")) { 399 // Some zones do not have short ID assigned, such as Asia/Riyadh87. 400 // The time roundtrip will fail for such zones with pattern "V" (short zone ID). 401 // This is expected behavior. 402 String shortZoneID = ZoneMeta.getShortID(id); 403 if (shortZoneID == null) { 404 continue; 405 } 406 } else if (PATTERNS[patidx].equals("VVV")) { 407 // Some zones are not associated with any region, such as Etc/GMT+8. 408 // The time roundtrip will fail for such zones with pattern "VVV" (exemplar location). 409 // This is expected behavior. 410 if (id.indexOf('/') < 0 || LOC_EXCLUSION_PATTERN.matcher(id).matches()) { 411 continue; 412 } 413 } 414 415 if (id.equals("Pacific/Apia") && PATTERNS[patidx].equals("vvvv") 416 && logKnownIssue("11052", "Ambiguous zone name - Samoa Time")) { 417 continue; 418 } 419 420 BasicTimeZone btz = (BasicTimeZone)TimeZone.getTimeZone(id, TimeZone.TIMEZONE_ICU); 421 TimeZone tz = TimeZone.getTimeZone(id); 422 sdf.setTimeZone(tz); 423 424 long t = START_TIME; 425 TimeZoneTransition tzt = null; 426 boolean middle = true; 427 boolean last = false; 428 while (t < END_TIME) { 429 if (tzt == null) { 430 testTimes[0] = t; 431 expectedRoundTrip[0] = true; 432 testLen = 1; 433 } else { 434 int fromOffset = tzt.getFrom().getRawOffset() + tzt.getFrom().getDSTSavings(); 435 int toOffset = tzt.getTo().getRawOffset() + tzt.getTo().getDSTSavings(); 436 int delta = toOffset - fromOffset; 437 if (delta < 0) { 438 boolean isDstDecession = tzt.getFrom().getDSTSavings() > 0 && tzt.getTo().getDSTSavings() == 0; 439 testTimes[0] = t + delta - 1; 440 expectedRoundTrip[0] = true; 441 testTimes[1] = t + delta; 442 expectedRoundTrip[1] = isDstDecession ? 443 !AMBIGUOUS_DST_DECESSION.contains(PATTERNS[patidx]) : 444 !AMBIGUOUS_NEGATIVE_SHIFT.contains(PATTERNS[patidx]); 445 testTimes[2] = t - 1; 446 expectedRoundTrip[2] = isDstDecession ? 447 !AMBIGUOUS_DST_DECESSION.contains(PATTERNS[patidx]) : 448 !AMBIGUOUS_NEGATIVE_SHIFT.contains(PATTERNS[patidx]); 449 testTimes[3] = t; 450 expectedRoundTrip[3] = true; 451 testLen = 4; 452 } else { 453 testTimes[0] = t - 1; 454 expectedRoundTrip[0] = true; 455 testTimes[1] = t; 456 expectedRoundTrip[1] = true; 457 testLen = 2; 458 } 459 } 460 for (int testidx = 0; testidx < testLen; testidx++) { 461 testCounts++; 462 timer = System.currentTimeMillis(); 463 String text = sdf.format(new Date(testTimes[testidx])); 464 try { 465 Date parsedDate = sdf.parse(text); 466 long restime = parsedDate.getTime(); 467 long timeDiff = restime - testTimes[testidx]; 468 boolean bTimeMatch = minutesOffset ? 469 (timeDiff/60000)*60000 == 0 : timeDiff == 0; 470 if (!bTimeMatch) { 471 StringBuffer msg = new StringBuffer(); 472 msg.append("Time round trip failed for ") 473 .append("tzid=").append(id) 474 .append(", locale=").append(LOCALES[locidx]) 475 .append(", pattern=").append(PATTERNS[patidx]) 476 .append(", text=").append(text) 477 .append(", gmt=").append(sdfGMT.format(new Date(testTimes[testidx]))) 478 .append(", time=").append(testTimes[testidx]) 479 .append(", restime=").append(restime) 480 .append(", diff=").append(timeDiff); 481 if (expectedRoundTrip[testidx] 482 && !isSpecialTimeRoundTripCase(LOCALES[locidx], id, PATTERNS[patidx], testTimes[testidx])) { 483 errln("FAIL: " + msg.toString()); 484 } else if (REALLY_VERBOSE_LOG) { 485 logln(msg.toString()); 486 } 487 } 488 } catch (ParseException pe) { 489 errln("FAIL: " + pe.getMessage() + " tzid=" + id + ", locale=" + LOCALES[locidx] + 490 ", pattern=" + PATTERNS[patidx] + ", text=" + text); 491 } 492 times[patidx] += System.currentTimeMillis() - timer; 493 } 494 495 if (last) { 496 break; 497 } 498 499 tzt = btz.getNextTransition(t, false); 500 if (tzt == null) { 501 last = true; 502 t = END_TIME - 1; 503 } else if (middle) { 504 // Test the date in the middle of two transitions. 505 t += (tzt.getTime() - t)/2; 506 middle = false; 507 tzt = null; 508 } else { 509 t = tzt.getTime(); 510 } 511 } 512 } 513 } 514 } 515 516 long total = 0; 517 logln("### Elapsed time by patterns ###"); 518 for (int i = 0; i < PATTERNS.length; i++) { 519 logln(times[i] + "ms (" + PATTERNS[i] + ")"); 520 total += times[i]; 521 } 522 logln("Total: " + total + "ms"); 523 logln("Iteration: " + testCounts); 524 } 525 526 // Special exclusions in TestTimeZoneRoundTrip. 527 // These special cases do not round trip time as designed. 528 private boolean isSpecialTimeRoundTripCase(ULocale loc, String id, String pattern, long time) { 529 final Object[][] EXCLUSIONS = { 530 {null, "Asia/Chita", "zzzz", Long.valueOf(1414252800000L)}, 531 {null, "Asia/Chita", "vvvv", Long.valueOf(1414252800000L)}, 532 {null, "Asia/Srednekolymsk", "zzzz", Long.valueOf(1414241999999L)}, 533 {null, "Asia/Srednekolymsk", "vvvv", Long.valueOf(1414241999999L)}, 534 }; 535 boolean isExcluded = false; 536 for (Object[] excl : EXCLUSIONS) { 537 if (excl[0] == null || loc.equals(excl[0])) { 538 if (id.equals(excl[1])) { 539 if (excl[2] == null || pattern.equals(excl[2])) { 540 if (excl[3] == null || ((Long)excl[3]).compareTo(time) == 0) { 541 isExcluded = true; 542 break; 543 } 544 } 545 } 546 } 547 } 548 return isExcluded; 549 } 550 551 @Test 552 public void TestParse() { 553 final Object[][] DATA = { 554 // text inpos locale style 555 // parseOptions expected outpos time type 556 {"Z", 0, "en_US", Style.ISO_EXTENDED_FULL, 557 null, "Etc/GMT", 1, TimeType.UNKNOWN}, 558 559 {"Z", 0, "en_US", Style.SPECIFIC_LONG, 560 null, "Etc/GMT", 1, TimeType.UNKNOWN}, 561 562 {"Zambia time", 0, "en_US", Style.ISO_EXTENDED_FULL, 563 EnumSet.of(ParseOption.ALL_STYLES), "Etc/GMT", 1, TimeType.UNKNOWN}, 564 565 {"Zambia time", 0, "en_US", Style.GENERIC_LOCATION, 566 null, "Africa/Lusaka", 11, TimeType.UNKNOWN}, 567 568 {"Zambia time", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, 569 EnumSet.of(ParseOption.ALL_STYLES), "Africa/Lusaka", 11, TimeType.UNKNOWN}, 570 571 {"+00:00", 0, "en_US", Style.ISO_EXTENDED_FULL, 572 null, "Etc/GMT", 6, TimeType.UNKNOWN}, 573 574 {"-01:30:45", 0, "en_US", Style.ISO_EXTENDED_FULL, 575 null, "GMT-01:30:45", 9, TimeType.UNKNOWN}, 576 577 {"-7", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, 578 null, "GMT-07:00", 2, TimeType.UNKNOWN}, 579 580 {"-2222", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, 581 null, "GMT-22:22", 5, TimeType.UNKNOWN}, 582 583 {"-3333", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, 584 null, "GMT-03:33", 4, TimeType.UNKNOWN}, 585 586 {"XXX+01:30YYY", 3, "en_US", Style.LOCALIZED_GMT, 587 null, "GMT+01:30", 9, TimeType.UNKNOWN}, 588 589 {"GMT0", 0, "en_US", Style.SPECIFIC_SHORT, 590 null, "Etc/GMT", 3, TimeType.UNKNOWN}, 591 592 {"EST", 0, "en_US", Style.SPECIFIC_SHORT, 593 null, "America/New_York", 3, TimeType.STANDARD}, 594 595 {"ESTx", 0, "en_US", Style.SPECIFIC_SHORT, 596 null, "America/New_York", 3, TimeType.STANDARD}, 597 598 {"EDTx", 0, "en_US", Style.SPECIFIC_SHORT, 599 null, "America/New_York", 3, TimeType.DAYLIGHT}, 600 601 {"EST", 0, "en_US", Style.SPECIFIC_LONG, 602 null, null, 0, TimeType.UNKNOWN}, 603 604 {"EST", 0, "en_US", Style.SPECIFIC_LONG, 605 EnumSet.of(ParseOption.ALL_STYLES), "America/New_York", 3, TimeType.STANDARD}, 606 607 {"EST", 0, "en_CA", Style.SPECIFIC_SHORT, 608 null, "America/Toronto", 3, TimeType.STANDARD}, 609 610 {"CST", 0, "en_US", Style.SPECIFIC_SHORT, 611 null, "America/Chicago", 3, TimeType.STANDARD}, 612 613 {"CST", 0, "en_GB", Style.SPECIFIC_SHORT, 614 null, null, 0, TimeType.UNKNOWN}, 615 616 {"CST", 0, "en_GB", Style.SPECIFIC_SHORT, 617 EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS), "America/Chicago", 3, TimeType.STANDARD}, 618 619 {"--CST--", 2, "en_GB", Style.SPECIFIC_SHORT, 620 EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS), "America/Chicago", 5, TimeType.STANDARD}, 621 622 {"CST", 0, "zh_CN", Style.SPECIFIC_SHORT, 623 EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS), "Asia/Shanghai", 3, TimeType.STANDARD}, 624 625 {"AEST", 0, "en_AU", Style.SPECIFIC_SHORT, 626 EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS), "Australia/Sydney", 4, TimeType.STANDARD}, 627 628 {"AST", 0, "ar_SA", Style.SPECIFIC_SHORT, 629 EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS), "Asia/Riyadh", 3, TimeType.STANDARD}, 630 631 {"AQTST", 0, "en", Style.SPECIFIC_LONG, 632 null, null, 0, TimeType.UNKNOWN}, 633 634 {"AQTST", 0, "en", Style.SPECIFIC_LONG, 635 EnumSet.of(ParseOption.ALL_STYLES), null, 0, TimeType.UNKNOWN}, 636 637 {"AQTST", 0, "en", Style.SPECIFIC_LONG, 638 EnumSet.of(ParseOption.ALL_STYLES, ParseOption.TZ_DATABASE_ABBREVIATIONS), "Asia/Aqtobe", 5, TimeType.DAYLIGHT}, 639 640 {"hora de verano britnica", 0, "es", Style.SPECIFIC_LONG, 641 null, "Europe/London", 24, TimeType.DAYLIGHT}, 642 }; 643 644 for (Object[] test : DATA) { 645 String text = (String)test[0]; 646 int inPos = (Integer)test[1]; 647 ULocale loc = new ULocale((String)test[2]); 648 Style style = (Style)test[3]; 649 EnumSet<ParseOption> options = (EnumSet<ParseOption>)test[4]; 650 String expID = (String)test[5]; 651 int expPos = (Integer)test[6]; 652 TimeType expType = (TimeType)test[7]; 653 654 TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(loc); 655 Output<TimeType> timeType = new Output<TimeType>(TimeType.UNKNOWN); 656 ParsePosition pos = new ParsePosition(inPos); 657 TimeZone tz = tzfmt.parse(style, text, pos, options, timeType); 658 659 String errMsg = null; 660 if (tz == null) { 661 if (expID != null) { 662 errMsg = "Parse failure - expected: " + expID; 663 } 664 } else if (!tz.getID().equals(expID)) { 665 errMsg = "Time zone ID: " + tz.getID() + " - expected: " + expID; 666 } else if (pos.getIndex() != expPos) { 667 errMsg = "Parsed pos: " + pos.getIndex() + " - expected: " + expPos; 668 } else if (timeType.value != expType) { 669 errMsg = "Time type: " + timeType + " - expected: " + expType; 670 } 671 672 if (errMsg != null) { 673 errln("Fail: " + errMsg + 674 " [text=" + text + ", pos=" + inPos + 675 ", locale=" + loc + ", style=" + style + "]"); 676 } 677 } 678 } 679 680 // Coverage tests for other versions of the parse() method. All of them end up 681 // calling the full parse() method tested on the TestParse() test. 682 public void TestParseCoverage() { 683 TimeZone expectedTZ = TimeZone.getTimeZone("America/Los_Angeles"); 684 TimeZoneFormat fmt = TimeZoneFormat.getInstance(ULocale.ENGLISH); 685 686 // Test parse(String) 687 try { 688 TimeZone tz1 = fmt.parse("America/Los_Angeles"); 689 if (tz1 == null) { 690 errln("Parse failure using parse(String) - expected: " + expectedTZ.getID()); 691 } else if (!expectedTZ.equals(tz1)) { 692 errln("Parsed TimeZone: '" + tz1.getID() + "' using parse(String) - expected: " 693 + expectedTZ.getID()); 694 } 695 } catch (ParseException e) { 696 errln("Parse failure using parse(String) - expected: " + expectedTZ.getID() 697 + " exception: " + e.getMessage()); 698 } 699 700 // Test parse(String, ParsePosition) 701 TimeZone tz2 = fmt.parse("++America/Los_Angeles", new ParsePosition(2)); 702 if (tz2 == null) { 703 errln("Parse failure using parse(String, ParsePosition) - expected: " 704 + expectedTZ.getID()); 705 } else if (!expectedTZ.equals(tz2)) { 706 errln("Parsed TimeZone: '" + tz2.getID() + "' using parse(String, ParsePosition) - expected: " 707 + expectedTZ.getID()); 708 } 709 710 // Test parseObject(String, ParsePosition) 711 Object tz3 = fmt.parseObject("++America/Los_Angeles", new ParsePosition(2)); 712 if (tz3 == null) { 713 errln("Parse failure using parseObject(String, ParsePosition) - expected: " 714 + expectedTZ.getID()); 715 } else if (!expectedTZ.equals(tz3)) { 716 errln("Parsed TimeZone: '" + ((TimeZone)tz3).getID() 717 + "' using parseObject(String, ParsePosition) - expected: " 718 + expectedTZ.getID()); 719 } 720 } 721 722 @Test 723 public void TestISOFormat() { 724 final int[] OFFSET = { 725 0, // 0 726 999, // 0.999s 727 -59999, // -59.999s 728 60000, // 1m 729 -77777, // -1m 17.777s 730 1800000, // 30m 731 -3600000, // -1h 732 36000000, // 10h 733 -37800000, // -10h 30m 734 -37845000, // -10h 30m 45s 735 108000000, // 30h 736 }; 737 738 final String[][] ISO_STR = { 739 // 0 740 { 741 "Z", "Z", "Z", "Z", "Z", 742 "+00", "+0000", "+00:00", "+0000", "+00:00", 743 "+0000" 744 }, 745 // 999 746 { 747 "Z", "Z", "Z", "Z", "Z", 748 "+00", "+0000", "+00:00", "+0000", "+00:00", 749 "+0000" 750 }, 751 // -59999 752 { 753 "Z", "Z", "Z", "-000059", "-00:00:59", 754 "+00", "+0000", "+00:00", "-000059", "-00:00:59", 755 "-000059" 756 }, 757 // 60000 758 { 759 "+0001", "+0001", "+00:01", "+0001", "+00:01", 760 "+0001", "+0001", "+00:01", "+0001", "+00:01", 761 "+0001" 762 }, 763 // -77777 764 { 765 "-0001", "-0001", "-00:01", "-000117", "-00:01:17", 766 "-0001", "-0001", "-00:01", "-000117", "-00:01:17", 767 "-000117" 768 }, 769 // 1800000 770 { 771 "+0030", "+0030", "+00:30", "+0030", "+00:30", 772 "+0030", "+0030", "+00:30", "+0030", "+00:30", 773 "+0030" 774 }, 775 // -3600000 776 { 777 "-01", "-0100", "-01:00", "-0100", "-01:00", 778 "-01", "-0100", "-01:00", "-0100", "-01:00", 779 "-0100" 780 }, 781 // 36000000 782 { 783 "+10", "+1000", "+10:00", "+1000", "+10:00", 784 "+10", "+1000", "+10:00", "+1000", "+10:00", 785 "+1000" 786 }, 787 // -37800000 788 { 789 "-1030", "-1030", "-10:30", "-1030", "-10:30", 790 "-1030", "-1030", "-10:30", "-1030", "-10:30", 791 "-1030" 792 }, 793 // -37845000 794 { 795 "-1030", "-1030", "-10:30", "-103045", "-10:30:45", 796 "-1030", "-1030", "-10:30", "-103045", "-10:30:45", 797 "-103045" 798 }, 799 // 108000000 800 { 801 null, null, null, null, null, 802 null, null, null, null, null, 803 null 804 } 805 }; 806 807 final String[] PATTERN = { 808 "X", "XX", "XXX", "XXXX", "XXXXX", "x", "xx", "xxx", "xxxx", "xxxxx", 809 "Z", // equivalent to "xxxx" 810 }; 811 812 final int[] MIN_OFFSET_UNIT = { 813 60000, 60000, 60000, 1000, 1000, 60000, 60000, 60000, 1000, 1000, 814 1000, 815 }; 816 817 // Formatting 818 SimpleDateFormat sdf = new SimpleDateFormat(); 819 Date d = new Date(); 820 821 for (int i = 0; i < OFFSET.length; i++) { 822 SimpleTimeZone tz = new SimpleTimeZone(OFFSET[i], "Zone Offset:" + String.valueOf(OFFSET[i]) + "ms"); 823 sdf.setTimeZone(tz); 824 for (int j = 0; j < PATTERN.length; j++) { 825 sdf.applyPattern(PATTERN[j]); 826 try { 827 String result = sdf.format(d); 828 if (!result.equals(ISO_STR[i][j])) { 829 errln("FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> " 830 + result + " (expected: " + ISO_STR[i][j] + ")"); 831 } 832 } catch (IllegalArgumentException e) { 833 if (ISO_STR[i][j] != null) { 834 errln("FAIL: IAE thrown for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] 835 + " (expected: " + ISO_STR[i][j] + ")"); 836 } 837 } 838 } 839 } 840 841 // Parsing 842 SimpleTimeZone bogusTZ = new SimpleTimeZone(-1, "Zone Offset: -1ms"); 843 for (int i = 0; i < ISO_STR.length; i++) { 844 for (int j = 0; j < ISO_STR[i].length; j++) { 845 if (ISO_STR[i][j] == null) { 846 continue; 847 } 848 ParsePosition pos = new ParsePosition(0); 849 Calendar outcal = Calendar.getInstance(bogusTZ); 850 sdf.applyPattern(PATTERN[j]); 851 852 sdf.parse(ISO_STR[i][j], outcal, pos); 853 854 if (pos.getIndex() != ISO_STR[i][j].length()) { 855 errln("FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]); 856 continue; 857 } 858 859 TimeZone outtz = outcal.getTimeZone(); 860 int outOffset = outtz.getRawOffset(); 861 int adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j]; 862 863 if (outOffset != adjustedOffset) { 864 errln("FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j] 865 + " (expected:" + adjustedOffset + "ms)"); 866 } 867 } 868 } 869 } 870 871 @Test 872 public void TestFormat() { 873 final Date dateJan = new Date(1358208000000L); // 2013-01-15T00:00:00Z 874 final Date dateJul = new Date(1373846400000L); // 2013-07-15T00:00:00Z 875 876 final Object[][] TESTDATA = { 877 { 878 "en", 879 "America/Los_Angeles", 880 dateJan, 881 Style.GENERIC_LOCATION, 882 "Los Angeles Time", 883 TimeType.UNKNOWN 884 }, 885 { 886 "en", 887 "America/Los_Angeles", 888 dateJan, 889 Style.GENERIC_LONG, 890 "Pacific Time", 891 TimeType.UNKNOWN 892 }, 893 { 894 "en", 895 "America/Los_Angeles", 896 dateJan, 897 Style.SPECIFIC_LONG, 898 "Pacific Standard Time", 899 TimeType.STANDARD 900 }, 901 { 902 "en", 903 "America/Los_Angeles", 904 dateJul, 905 Style.SPECIFIC_LONG, 906 "Pacific Daylight Time", 907 TimeType.DAYLIGHT 908 }, 909 { 910 "ja", 911 "America/Los_Angeles", 912 dateJan, 913 Style.ZONE_ID, 914 "America/Los_Angeles", 915 TimeType.UNKNOWN 916 }, 917 { 918 "fr", 919 "America/Los_Angeles", 920 dateJul, 921 Style.ZONE_ID_SHORT, 922 "uslax", 923 TimeType.UNKNOWN 924 }, 925 { 926 "en", 927 "America/Los_Angeles", 928 dateJan, 929 Style.EXEMPLAR_LOCATION, 930 "Los Angeles", 931 TimeType.UNKNOWN 932 }, 933 { 934 "ja", 935 "Asia/Tokyo", 936 dateJan, 937 Style.GENERIC_LONG, 938 "\u65E5\u672C\u6A19\u6E96\u6642", // "" 939 TimeType.UNKNOWN 940 }, 941 }; 942 943 for (Object[] testCase : TESTDATA) { 944 TimeZone tz = TimeZone.getTimeZone((String)testCase[1]); 945 Output<TimeType> timeType = new Output<TimeType>(); 946 947 ULocale uloc = new ULocale((String)testCase[0]); 948 TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(uloc); 949 String out = tzfmt.format((Style)testCase[3], tz, ((Date)testCase[2]).getTime(), timeType); 950 951 if (!out.equals(testCase[4]) || timeType.value != testCase[5]) { 952 errln("Format result for [locale=" + testCase[0] + ",tzid=" + testCase[1] + ",date=" + testCase[2] 953 + ",style=" + testCase[3] + "]: expected [output=" + testCase[4] + ",type=" + testCase[5] 954 + "]; actual [output=" + out + ",type=" + timeType.value + "]"); 955 } 956 957 // with equivalent Java Locale 958 Locale loc = uloc.toLocale(); 959 tzfmt = TimeZoneFormat.getInstance(loc); 960 out = tzfmt.format((Style)testCase[3], tz, ((Date)testCase[2]).getTime(), timeType); 961 962 if (!out.equals(testCase[4]) || timeType.value != testCase[5]) { 963 errln("Format result for [locale(Java)=" + testCase[0] + ",tzid=" + testCase[1] + ",date=" + testCase[2] 964 + ",style=" + testCase[3] + "]: expected [output=" + testCase[4] + ",type=" + testCase[5] 965 + "]; actual [output=" + out + ",type=" + timeType.value + "]"); 966 } 967 } 968 } 969 970 @Test 971 public void TestFormatTZDBNames() { 972 final Date dateJan = new Date(1358208000000L); // 2013-01-15T00:00:00Z 973 final Date dateJul = new Date(1373846400000L); // 2013-07-15T00:00:00Z 974 975 final Object[][] TESTDATA = { 976 { 977 "en", 978 "America/Chicago", 979 dateJan, 980 Style.SPECIFIC_SHORT, 981 "CST", 982 TimeType.STANDARD 983 }, 984 { 985 "en", 986 "Asia/Shanghai", 987 dateJan, 988 Style.SPECIFIC_SHORT, 989 "CST", 990 TimeType.STANDARD 991 }, 992 { 993 "zh_Hans", 994 "Asia/Shanghai", 995 dateJan, 996 Style.SPECIFIC_SHORT, 997 "CST", 998 TimeType.STANDARD 999 }, 1000 { 1001 "en", 1002 "America/Los_Angeles", 1003 dateJul, 1004 Style.SPECIFIC_LONG, 1005 "GMT-07:00", // No long display names 1006 TimeType.DAYLIGHT 1007 }, 1008 { 1009 "ja", 1010 "America/Los_Angeles", 1011 dateJul, 1012 Style.SPECIFIC_SHORT, 1013 "PDT", 1014 TimeType.DAYLIGHT 1015 }, 1016 { 1017 "en", 1018 "Australia/Sydney", 1019 dateJan, 1020 Style.SPECIFIC_SHORT, 1021 "AEDT", 1022 TimeType.DAYLIGHT 1023 }, 1024 { 1025 "en", 1026 "Australia/Sydney", 1027 dateJul, 1028 Style.SPECIFIC_SHORT, 1029 "AEST", 1030 TimeType.STANDARD 1031 }, 1032 }; 1033 1034 for (Object[] testCase : TESTDATA) { 1035 ULocale loc = new ULocale((String)testCase[0]); 1036 TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(loc).cloneAsThawed(); 1037 TimeZoneNames tzdbNames = TimeZoneNames.getTZDBInstance(loc); 1038 tzfmt.setTimeZoneNames(tzdbNames); 1039 1040 TimeZone tz = TimeZone.getTimeZone((String)testCase[1]); 1041 Output<TimeType> timeType = new Output<TimeType>(); 1042 String out = tzfmt.format((Style)testCase[3], tz, ((Date)testCase[2]).getTime(), timeType); 1043 1044 if (!out.equals(testCase[4]) || timeType.value != testCase[5]) { 1045 errln("Format result for [locale=" + testCase[0] + ",tzid=" + testCase[1] + ",date=" + testCase[2] 1046 + ",style=" + testCase[3] + "]: expected [output=" + testCase[4] + ",type=" + testCase[5] 1047 + "]; actual [output=" + out + ",type=" + timeType.value + "]"); 1048 } 1049 } 1050 } 1051 1052 // Tests format(Object, StringBuffer, FieldPosition):StringBuffer method 1053 // inherited from Format class 1054 public void TestInheritedFormat() { 1055 TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); 1056 Calendar cal = Calendar.getInstance(tz); 1057 cal.setTimeInMillis(1459187377690L); // Mar 28, 2016 1058 1059 StringBuffer sb = new StringBuffer(); 1060 FieldPosition fp = new FieldPosition(DateFormat.Field.TIME_ZONE); 1061 1062 TimeZoneFormat fmt = TimeZoneFormat.getInstance(ULocale.ENGLISH); 1063 1064 // Test formatting a non-timezone related object 1065 try { 1066 fmt.format(new Object(), sb, fp); 1067 errln("ERROR: format non-timezone related object failed"); 1068 } catch (IllegalArgumentException e) { /* Expected */ } 1069 1070 // Test formatting a TimeZone object 1071 sb = new StringBuffer(); 1072 fmt.format(tz, sb, fp); 1073 // When formatting a TimeZone object the formatter uses the current date. 1074 String fmtOutput = tz.inDaylightTime(new Date()) ? "GMT-07:00" : "GMT-08:00"; 1075 if (!sb.toString().equals(fmtOutput)) { 1076 errln("ERROR: format TimerZone object failed. Expected: " + fmtOutput + ", actual: " + sb); 1077 } 1078 1079 // Test formatting a Calendar object 1080 sb = new StringBuffer(); 1081 fmt.format(cal, sb, fp); 1082 if (!sb.toString().equals("GMT-07:00")) { 1083 errln("ERROR: format Calendar object failed. Expected: GMT-07:00, actual: " + sb); 1084 } 1085 } 1086 1087 // This is a test case of Ticket#11487. 1088 // Because the problem is reproduced for the very first time, 1089 // the reported problem cannot be reproduced with regular test 1090 // execution. Run this test alone reproduced the problem before 1091 // the fix was merged. 1092 @Test 1093 public void TestTZDBNamesThreading() { 1094 final TZDBTimeZoneNames names = new TZDBTimeZoneNames(ULocale.ENGLISH); 1095 final AtomicInteger found = new AtomicInteger(); 1096 List<Thread> threads = new ArrayList<Thread>(); 1097 final int numIteration = 1000; 1098 1099 try { 1100 for (int i = 0; i < numIteration; i++) { 1101 Thread thread = new Thread() { 1102 @Override 1103 public void run() { 1104 int resultSize = names.find("GMT", 0, EnumSet.allOf(NameType.class)).size(); 1105 if (resultSize > 0) { 1106 found.incrementAndGet(); 1107 } 1108 } 1109 }; 1110 thread.start(); 1111 threads.add(thread); 1112 } 1113 1114 for(Thread thread: threads) { 1115 thread.join(); 1116 } 1117 } catch (Throwable t) { 1118 errln(t.toString()); 1119 } 1120 1121 if (found.intValue() != numIteration) { 1122 errln("Incorrect count: " + found.toString() + ", expected: " + numIteration); 1123 } 1124 } 1125 1126 @Test 1127 public void TestGetDisplayNames() { 1128 long date = System.currentTimeMillis(); 1129 NameType[] types = new NameType[]{ 1130 NameType.LONG_STANDARD, NameType.LONG_DAYLIGHT, 1131 NameType.SHORT_STANDARD, NameType.SHORT_DAYLIGHT 1132 }; 1133 Set<String> zones = ZoneMeta.getAvailableIDs(SystemTimeZoneType.ANY, null, null); 1134 1135 int casesTested = 0; 1136 Random rnd = new Random(2016); 1137 for (ULocale uloc : ULocale.getAvailableLocales()) { 1138 if (rnd.nextDouble() > 0.01) { continue; } 1139 for (String zone : zones) { 1140 if (rnd.nextDouble() > 0.01) { continue; } 1141 casesTested++; 1142 1143 // Test default TimeZoneNames (uses an overridden getDisplayNames) 1144 { 1145 TimeZoneNames tznames = TimeZoneNames.getInstance(uloc); 1146 tznames.loadAllDisplayNames(); 1147 String[] result = new String[types.length]; 1148 tznames.getDisplayNames(zone, types, date, result, 0); 1149 for (int i=0; i<types.length; i++) { 1150 NameType type = types[i]; 1151 String expected = result[i]; 1152 String actual = tznames.getDisplayName(zone, type, date); 1153 assertEquals("TimeZoneNames: getDisplayNames() returns different result than getDisplayName()" 1154 + " for " + zone + " in locale " + uloc, expected, actual); 1155 } 1156 // Coverage for empty call to getDisplayNames 1157 tznames.getDisplayNames(null, null, 0, null, 0); 1158 } 1159 1160 // Test TZDBTimeZoneNames (uses getDisplayNames from abstract class) 1161 { 1162 TimeZoneNames tznames = new TZDBTimeZoneNames(uloc); 1163 tznames.loadAllDisplayNames(); 1164 String[] result = new String[types.length]; 1165 tznames.getDisplayNames(zone, types, date, result, 0); 1166 for (int i=0; i<types.length; i++) { 1167 NameType type = types[i]; 1168 String expected = result[i]; 1169 String actual = tznames.getDisplayName(zone, type, date); 1170 assertEquals("TZDBTimeZoneNames: getDisplayNames() returns different result than getDisplayName()" 1171 + " for " + zone + " in locale " + uloc, expected, actual); 1172 } 1173 // Coverage for empty call to getDisplayNames 1174 tznames.getDisplayNames(null, null, 0, null, 0); 1175 } 1176 } 1177 } 1178 1179 assertTrue("No cases were tested", casesTested > 0); 1180 } 1181 1182 class TimeZoneNamesInheriter extends TimeZoneNames { 1183 private static final long serialVersionUID = 1L; 1184 1185 @Override 1186 public Set<String> getAvailableMetaZoneIDs() { 1187 return null; 1188 } 1189 1190 @Override 1191 public Set<String> getAvailableMetaZoneIDs(String tzID) { 1192 return null; 1193 } 1194 1195 @Override 1196 public String getMetaZoneID(String tzID, long date) { 1197 return null; 1198 } 1199 1200 @Override 1201 public String getReferenceZoneID(String mzID, String region) { 1202 return null; 1203 } 1204 1205 @Override 1206 public String getMetaZoneDisplayName(String mzID, NameType type) { 1207 return null; 1208 } 1209 1210 @Override 1211 public String getTimeZoneDisplayName(String tzID, NameType type) { 1212 return null; 1213 } 1214 } 1215 1216 // Coverage for default implementation and abstract methods in base class. 1217 @Test 1218 public void TestDefaultTimeZoneNames() { 1219 long date = System.currentTimeMillis(); 1220 TimeZoneNames.Factory factory; 1221 try { 1222 Class cls = Class.forName("android.icu.text.TimeZoneNames$DefaultTimeZoneNames$FactoryImpl"); 1223 factory = (Factory) cls.newInstance(); 1224 } catch (Exception e) { 1225 errln("Could not create class DefaultTimeZoneNames.FactoryImpl: " + e.getClass() + ": " + e.getMessage()); 1226 return; 1227 } 1228 TimeZoneNames tzn = factory.getTimeZoneNames(ULocale.ENGLISH); 1229 assertEquals("Abstract: getAvailableMetaZoneIDs()", 1230 tzn.getAvailableMetaZoneIDs(), Collections.emptySet()); 1231 assertEquals("Abstract: getAvailableMetaZoneIDs(String tzID)", 1232 tzn.getAvailableMetaZoneIDs("America/Chicago"), Collections.emptySet()); 1233 assertEquals("Abstract: getMetaZoneID(String tzID, long date)", 1234 tzn.getMetaZoneID("America/Chicago", date), null); 1235 assertEquals("Abstract: getReferenceZoneID(String mzID, String region)", 1236 tzn.getReferenceZoneID("America_Central", "IT"), null); 1237 assertEquals("Abstract: getMetaZoneDisplayName(String mzID, NameType type)", 1238 tzn.getMetaZoneDisplayName("America_Central", NameType.LONG_DAYLIGHT), null); 1239 assertEquals("Abstract: getTimeZoneDisplayName(String mzID, NameType type)", 1240 tzn.getTimeZoneDisplayName("America/Chicago", NameType.LONG_DAYLIGHT), null); 1241 assertEquals("Abstract: find(CharSequence text, int start, EnumSet<NameType> nameTypes)", 1242 tzn.find("foo", 0, EnumSet.noneOf(NameType.class)), Collections.emptyList()); 1243 1244 // Other abstract-class methods that aren't covered 1245 tzn = new TimeZoneNamesInheriter(); 1246 try { 1247 tzn.find(null, 0, null); 1248 } catch (UnsupportedOperationException e) { 1249 assertEquals("find() exception", "The method is not implemented in TimeZoneNames base class.", e.getMessage()); 1250 } 1251 } 1252 1253 // Basic get/set test for methods not being called otherwise. 1254 @Test 1255 public void TestAPI() { 1256 TimeZoneFormat tzfmtEn = TimeZoneFormat.getInstance(ULocale.ENGLISH); 1257 TimeZoneFormat tzfmtAr = TimeZoneFormat.getInstance(new ULocale("ar")).cloneAsThawed(); 1258 TimeZoneNames tzn = TimeZoneNames.getInstance(Locale.ENGLISH); 1259 1260 String digits = tzfmtEn.getGMTOffsetDigits(); 1261 tzfmtAr.setGMTOffsetDigits(digits); 1262 if (!digits.equals(tzfmtAr.getGMTOffsetDigits())) { 1263 errln("ERROR: get/set GMTOffsetDigits failed"); 1264 } 1265 1266 String pattern = tzfmtEn.getGMTOffsetPattern(GMTOffsetPatternType.POSITIVE_H); 1267 tzfmtAr.setGMTOffsetPattern(GMTOffsetPatternType.POSITIVE_H, pattern); 1268 if (!pattern.equals(tzfmtAr.getGMTOffsetPattern(GMTOffsetPatternType.POSITIVE_H))) { 1269 errln("ERROR: get/set GMTOffsetPattern failed"); 1270 } 1271 1272 String zeroFmt = tzfmtEn.getGMTZeroFormat(); 1273 tzfmtAr.setGMTZeroFormat(zeroFmt); 1274 if (!zeroFmt.equals(tzfmtAr.getGMTZeroFormat())) { 1275 errln("ERROR: get/set GMTZeroFormat failed"); 1276 } 1277 1278 Set<String> allAvailableMZIDs = tzn.getAvailableMetaZoneIDs(); 1279 if (allAvailableMZIDs.size() < 150 || !allAvailableMZIDs.contains("America_Central")) { 1280 errln("ERROR: getAvailableMetaZoneIDs() did not return expected value"); 1281 } 1282 1283 Set<String> kinshasaAvailableMZIDs = tzn.getAvailableMetaZoneIDs("Africa/Kinshasa"); 1284 if (!kinshasaAvailableMZIDs.contains("Africa_Western") || kinshasaAvailableMZIDs.contains("America_Central")) { 1285 errln("ERROR: getAvailableMetaZoneIDs('Africa/Kinshasa') did not return expected value"); 1286 } 1287 1288 try { 1289 new TimeZoneNames.MatchInfo(null, null, null, -1); 1290 assertTrue("MatchInfo doesn't throw IllegalArgumentException", false); 1291 } catch (IllegalArgumentException e) { 1292 assertEquals("MatchInfo constructor exception", "nameType is null", e.getMessage()); 1293 } 1294 1295 try { 1296 new TimeZoneNames.MatchInfo(NameType.LONG_GENERIC, null, null, -1); 1297 assertTrue("MatchInfo doesn't throw IllegalArgumentException", false); 1298 } catch (IllegalArgumentException e) { 1299 assertEquals("MatchInfo constructor exception", "Either tzID or mzID must be available", e.getMessage()); 1300 } 1301 1302 try { 1303 new TimeZoneNames.MatchInfo(NameType.LONG_GENERIC, "America/Chicago", null, -1); 1304 assertTrue("MatchInfo doesn't throw IllegalArgumentException", false); 1305 } catch (IllegalArgumentException e) { 1306 assertEquals("MatchInfo constructor exception", "matchLength must be positive value", e.getMessage()); 1307 } 1308 } 1309 } 1310