Home | History | Annotate | Download | only in format
      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