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