Home | History | Annotate | Download | only in intltest
      1 /*
      2 *******************************************************************************
      3 * Copyright (C) 2007-2013, International Business Machines Corporation and    *
      4 * others. All Rights Reserved.                                                *
      5 *******************************************************************************
      6 */
      7 #include "unicode/utypes.h"
      8 
      9 #if !UCONFIG_NO_FORMATTING
     10 
     11 #include "tzfmttst.h"
     12 
     13 #include "simplethread.h"
     14 #include "unicode/timezone.h"
     15 #include "unicode/simpletz.h"
     16 #include "unicode/calendar.h"
     17 #include "unicode/strenum.h"
     18 #include "unicode/smpdtfmt.h"
     19 #include "unicode/uchar.h"
     20 #include "unicode/basictz.h"
     21 #include "unicode/tzfmt.h"
     22 #include "unicode/localpointer.h"
     23 #include "cstring.h"
     24 #include "zonemeta.h"
     25 
     26 static const char* PATTERNS[] = {
     27     "z",
     28     "zzzz",
     29     "Z",    // equivalent to "xxxx"
     30     "ZZZZ", // equivalent to "OOOO"
     31     "v",
     32     "vvvv",
     33     "O",
     34     "OOOO",
     35     "X",
     36     "XX",
     37     "XXX",
     38     "XXXX",
     39     "XXXXX",
     40     "x",
     41     "xx",
     42     "xxx",
     43     "xxxx",
     44     "xxxxx",
     45     "V",
     46     "VV",
     47     "VVV",
     48     "VVVV"
     49 };
     50 static const int NUM_PATTERNS = sizeof(PATTERNS)/sizeof(const char*);
     51 
     52 static const UChar ETC_UNKNOWN[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x6E, 0x6B, 0x6E, 0x6F, 0x77, 0x6E, 0};
     53 
     54 static const UChar ETC_SLASH[] = { 0x45, 0x74, 0x63, 0x2F, 0 }; // "Etc/"
     55 static const UChar SYSTEMV_SLASH[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F, 0 }; // "SystemV/
     56 static const UChar RIYADH8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38, 0 }; // "Riyadh8"
     57 
     58 static UBool contains(const char** list, const char* str) {
     59     for (int32_t i = 0; list[i]; i++) {
     60         if (uprv_strcmp(list[i], str) == 0) {
     61             return TRUE;
     62         }
     63     }
     64     return FALSE;
     65 }
     66 
     67 void
     68 TimeZoneFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
     69 {
     70     if (exec) {
     71         logln("TestSuite TimeZoneFormatTest");
     72     }
     73     switch (index) {
     74         TESTCASE(0, TestTimeZoneRoundTrip);
     75         TESTCASE(1, TestTimeRoundTrip);
     76         TESTCASE(2, TestParse);
     77         TESTCASE(3, TestISOFormat);
     78         default: name = ""; break;
     79     }
     80 }
     81 
     82 void
     83 TimeZoneFormatTest::TestTimeZoneRoundTrip(void) {
     84     UErrorCode status = U_ZERO_ERROR;
     85 
     86     SimpleTimeZone unknownZone(-31415, ETC_UNKNOWN);
     87     int32_t badDstOffset = -1234;
     88     int32_t badZoneOffset = -2345;
     89 
     90     int32_t testDateData[][3] = {
     91         {2007, 1, 15},
     92         {2007, 6, 15},
     93         {1990, 1, 15},
     94         {1990, 6, 15},
     95         {1960, 1, 15},
     96         {1960, 6, 15},
     97     };
     98 
     99     Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString)"UTC"), status);
    100     if (U_FAILURE(status)) {
    101         dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
    102         return;
    103     }
    104 
    105     // Set up rule equivalency test range
    106     UDate low, high;
    107     cal->set(1900, UCAL_JANUARY, 1);
    108     low = cal->getTime(status);
    109     cal->set(2040, UCAL_JANUARY, 1);
    110     high = cal->getTime(status);
    111     if (U_FAILURE(status)) {
    112         errln("getTime failed");
    113         return;
    114     }
    115 
    116     // Set up test dates
    117     UDate DATES[(sizeof(testDateData)/sizeof(int32_t))/3];
    118     const int32_t nDates = (sizeof(testDateData)/sizeof(int32_t))/3;
    119     cal->clear();
    120     for (int32_t i = 0; i < nDates; i++) {
    121         cal->set(testDateData[i][0], testDateData[i][1], testDateData[i][2]);
    122         DATES[i] = cal->getTime(status);
    123         if (U_FAILURE(status)) {
    124             errln("getTime failed");
    125             return;
    126         }
    127     }
    128 
    129     // Set up test locales
    130     const Locale testLocales[] = {
    131         Locale("en"),
    132         Locale("en_CA"),
    133         Locale("fr"),
    134         Locale("zh_Hant")
    135     };
    136 
    137     const Locale *LOCALES;
    138     int32_t nLocales;
    139 
    140     if (quick) {
    141         LOCALES = testLocales;
    142         nLocales = sizeof(testLocales)/sizeof(Locale);
    143     } else {
    144         LOCALES = Locale::getAvailableLocales(nLocales);
    145     }
    146 
    147     StringEnumeration *tzids = TimeZone::createEnumeration();
    148     int32_t inRaw, inDst;
    149     int32_t outRaw, outDst;
    150 
    151     // Run the roundtrip test
    152     for (int32_t locidx = 0; locidx < nLocales; locidx++) {
    153         UnicodeString localGMTString;
    154         SimpleDateFormat gmtFmt(UnicodeString("ZZZZ"), LOCALES[locidx], status);
    155         if (U_FAILURE(status)) {
    156             dataerrln("Error creating SimpleDateFormat - %s", u_errorName(status));
    157             continue;
    158         }
    159         gmtFmt.setTimeZone(*TimeZone::getGMT());
    160         gmtFmt.format(0.0, localGMTString);
    161 
    162         for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) {
    163 
    164             SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status);
    165             if (U_FAILURE(status)) {
    166                 dataerrln((UnicodeString)"new SimpleDateFormat failed for pattern " +
    167                     PATTERNS[patidx] + " for locale " + LOCALES[locidx].getName() + " - " + u_errorName(status));
    168                 status = U_ZERO_ERROR;
    169                 continue;
    170             }
    171 
    172             tzids->reset(status);
    173             const UnicodeString *tzid;
    174             while ((tzid = tzids->snext(status))) {
    175                 TimeZone *tz = TimeZone::createTimeZone(*tzid);
    176 
    177                 for (int32_t datidx = 0; datidx < nDates; datidx++) {
    178                     UnicodeString tzstr;
    179                     FieldPosition fpos(0);
    180                     // Format
    181                     sdf->setTimeZone(*tz);
    182                     sdf->format(DATES[datidx], tzstr, fpos);
    183 
    184                     // Before parse, set unknown zone to SimpleDateFormat instance
    185                     // just for making sure that it does not depends on the time zone
    186                     // originally set.
    187                     sdf->setTimeZone(unknownZone);
    188 
    189                     // Parse
    190                     ParsePosition pos(0);
    191                     Calendar *outcal = Calendar::createInstance(unknownZone, status);
    192                     if (U_FAILURE(status)) {
    193                         errln("Failed to create an instance of calendar for receiving parse result.");
    194                         status = U_ZERO_ERROR;
    195                         continue;
    196                     }
    197                     outcal->set(UCAL_DST_OFFSET, badDstOffset);
    198                     outcal->set(UCAL_ZONE_OFFSET, badZoneOffset);
    199 
    200                     sdf->parse(tzstr, *outcal, pos);
    201 
    202                     // Check the result
    203                     const TimeZone &outtz = outcal->getTimeZone();
    204                     UnicodeString outtzid;
    205                     outtz.getID(outtzid);
    206 
    207                     tz->getOffset(DATES[datidx], false, inRaw, inDst, status);
    208                     if (U_FAILURE(status)) {
    209                         errln((UnicodeString)"Failed to get offsets from time zone" + *tzid);
    210                         status = U_ZERO_ERROR;
    211                     }
    212                     outtz.getOffset(DATES[datidx], false, outRaw, outDst, status);
    213                     if (U_FAILURE(status)) {
    214                         errln((UnicodeString)"Failed to get offsets from time zone" + outtzid);
    215                         status = U_ZERO_ERROR;
    216                     }
    217 
    218                     if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
    219                         // Short zone ID - should support roundtrip for canonical CLDR IDs
    220                         UnicodeString canonicalID;
    221                         TimeZone::getCanonicalID(*tzid, canonicalID, status);
    222                         if (U_FAILURE(status)) {
    223                             // Uknown ID - we should not get here
    224                             errln((UnicodeString)"Unknown ID " + *tzid);
    225                             status = U_ZERO_ERROR;
    226                         } else if (outtzid != canonicalID) {
    227                             if (outtzid.compare(ETC_UNKNOWN, -1) == 0) {
    228                                 // Note that some zones like Asia/Riyadh87 does not have
    229                                 // short zone ID and "unk" is used as fallback
    230                                 logln((UnicodeString)"Canonical round trip failed (probably as expected); tz=" + *tzid
    231                                         + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
    232                                         + ", time=" + DATES[datidx] + ", str=" + tzstr
    233                                         + ", outtz=" + outtzid);
    234                             } else {
    235                                 errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid
    236                                     + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
    237                                     + ", time=" + DATES[datidx] + ", str=" + tzstr
    238                                     + ", outtz=" + outtzid);
    239                             }
    240                         }
    241                     } else if (uprv_strcmp(PATTERNS[patidx], "VV") == 0) {
    242                         // Zone ID - full roundtrip support
    243                         if (outtzid != *tzid) {
    244                             errln((UnicodeString)"Zone ID round trip failued; tz="  + *tzid
    245                                 + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
    246                                 + ", time=" + DATES[datidx] + ", str=" + tzstr
    247                                 + ", outtz=" + outtzid);
    248                         }
    249                     } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0 || uprv_strcmp(PATTERNS[patidx], "VVVV") == 0) {
    250                         // Location: time zone rule must be preserved except
    251                         // zones not actually associated with a specific location.
    252                         // Time zones in this category do not have "/" in its ID.
    253                         UnicodeString canonical;
    254                         TimeZone::getCanonicalID(*tzid, canonical, status);
    255                         if (U_FAILURE(status)) {
    256                             // Uknown ID - we should not get here
    257                             errln((UnicodeString)"Unknown ID " + *tzid);
    258                             status = U_ZERO_ERROR;
    259                         } else if (outtzid != canonical) {
    260                             // Canonical ID did not match - check the rules
    261                             if (!((BasicTimeZone*)&outtz)->hasEquivalentTransitions((BasicTimeZone&)*tz, low, high, TRUE, status)) {
    262                                 if (canonical.indexOf((UChar)0x27 /*'/'*/) == -1) {
    263                                     // Exceptional cases, such as CET, EET, MET and WET
    264                                     logln((UnicodeString)"Canonical round trip failed (as expected); tz=" + *tzid
    265                                             + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
    266                                             + ", time=" + DATES[datidx] + ", str=" + tzstr
    267                                             + ", outtz=" + outtzid);
    268                                 } else {
    269                                     errln((UnicodeString)"Canonical round trip failed; tz=" + *tzid
    270                                         + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
    271                                         + ", time=" + DATES[datidx] + ", str=" + tzstr
    272                                         + ", outtz=" + outtzid);
    273                                 }
    274                                 if (U_FAILURE(status)) {
    275                                     errln("hasEquivalentTransitions failed");
    276                                     status = U_ZERO_ERROR;
    277                                 }
    278                             }
    279                         }
    280 
    281                     } else {
    282                         UBool isOffsetFormat = (*PATTERNS[patidx] == 'Z'
    283                                                 || *PATTERNS[patidx] == 'O'
    284                                                 || *PATTERNS[patidx] == 'X'
    285                                                 || *PATTERNS[patidx] == 'x');
    286                         UBool minutesOffset = FALSE;
    287                         if (*PATTERNS[patidx] == 'X' || *PATTERNS[patidx] == 'x') {
    288                             minutesOffset = (uprv_strlen(PATTERNS[patidx]) <= 3);
    289                         }
    290 
    291                         if (!isOffsetFormat) {
    292                             // Check if localized GMT format is used as a fallback of name styles
    293                             int32_t numDigits = 0;
    294                             for (int n = 0; n < tzstr.length(); n++) {
    295                                 if (u_isdigit(tzstr.charAt(n))) {
    296                                     numDigits++;
    297                                 }
    298                             }
    299                             isOffsetFormat = (numDigits > 0);
    300                         }
    301                         if (isOffsetFormat || tzstr == localGMTString) {
    302                             // Localized GMT or ISO: total offset (raw + dst) must be preserved.
    303                             int32_t inOffset = inRaw + inDst;
    304                             int32_t outOffset = outRaw + outDst;
    305                             int32_t diff = outOffset - inOffset;
    306                             if (minutesOffset) {
    307                                 diff = (diff / 60000) * 60000;
    308                             }
    309                             if (diff != 0) {
    310                                 errln((UnicodeString)"Offset round trip failed; tz=" + *tzid
    311                                     + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
    312                                     + ", time=" + DATES[datidx] + ", str=" + tzstr
    313                                     + ", inOffset=" + inOffset + ", outOffset=" + outOffset);
    314                             }
    315                         } else {
    316                             // Specific or generic: raw offset must be preserved.
    317                             if (inRaw != outRaw) {
    318                                 errln((UnicodeString)"Raw offset round trip failed; tz=" + *tzid
    319                                     + ", locale=" + LOCALES[locidx].getName() + ", pattern=" + PATTERNS[patidx]
    320                                     + ", time=" + DATES[datidx] + ", str=" + tzstr
    321                                     + ", inRawOffset=" + inRaw + ", outRawOffset=" + outRaw);
    322                             }
    323                         }
    324                     }
    325                     delete outcal;
    326                 }
    327                 delete tz;
    328             }
    329             delete sdf;
    330         }
    331     }
    332     delete cal;
    333     delete tzids;
    334 }
    335 
    336 struct LocaleData {
    337     int32_t index;
    338     int32_t testCounts;
    339     UDate *times;
    340     const Locale* locales; // Static
    341     int32_t nLocales; // Static
    342     UBool quick; // Static
    343     UDate START_TIME; // Static
    344     UDate END_TIME; // Static
    345     int32_t numDone;
    346 };
    347 
    348 class TestTimeRoundTripThread: public SimpleThread {
    349 public:
    350     TestTimeRoundTripThread(IntlTest& tlog, LocaleData &ld, int32_t i)
    351         : log(tlog), data(ld), index(i) {}
    352     virtual void run() {
    353         UErrorCode status = U_ZERO_ERROR;
    354         UBool REALLY_VERBOSE = FALSE;
    355 
    356         // These patterns are ambiguous at DST->STD local time overlap
    357         const char* AMBIGUOUS_DST_DECESSION[] = { "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
    358 
    359         // These patterns are ambiguous at STD->STD/DST->DST local time overlap
    360         const char* AMBIGUOUS_NEGATIVE_SHIFT[] = { "z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV", 0 };
    361 
    362         // These patterns only support integer minutes offset
    363         const char* MINUTES_OFFSET[] = { "X", "XX", "XXX", "x", "xx", "xxx", 0 };
    364 
    365         // Workaround for #6338
    366         //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");
    367         UnicodeString BASEPATTERN("yyyy.MM.dd HH:mm:ss.SSS");
    368 
    369         // timer for performance analysis
    370         UDate timer;
    371         UDate testTimes[4];
    372         UBool expectedRoundTrip[4];
    373         int32_t testLen = 0;
    374 
    375         StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status);
    376         if (U_FAILURE(status)) {
    377             if (status == U_MISSING_RESOURCE_ERROR) {
    378                 /* This error is generally caused by data not being present. However, an infinite loop will occur
    379                  * because the thread thinks that the test data is never done so we should treat the data as done.
    380                  */
    381                 log.dataerrln("TimeZone::createTimeZoneIDEnumeration failed - %s", u_errorName(status));
    382                 data.numDone = data.nLocales;
    383             } else {
    384                 log.errln("TimeZone::createTimeZoneIDEnumeration failed: %s", u_errorName(status));
    385             }
    386             return;
    387         }
    388 
    389         int32_t locidx = -1;
    390         UDate times[NUM_PATTERNS];
    391         for (int32_t i = 0; i < NUM_PATTERNS; i++) {
    392             times[i] = 0;
    393         }
    394 
    395         int32_t testCounts = 0;
    396 
    397         while (true) {
    398             umtx_lock(NULL); // Lock to increment the index
    399             for (int32_t i = 0; i < NUM_PATTERNS; i++) {
    400                 data.times[i] += times[i];
    401                 data.testCounts += testCounts;
    402             }
    403             if (data.index < data.nLocales) {
    404                 locidx = data.index;
    405                 data.index++;
    406             } else {
    407                 locidx = -1;
    408             }
    409             umtx_unlock(NULL); // Unlock for other threads to use
    410 
    411             if (locidx == -1) {
    412                 log.logln((UnicodeString) "Thread " + index + " is done.");
    413                 break;
    414             }
    415 
    416             log.logln((UnicodeString) "\nThread " + index + ": Locale: " + UnicodeString(data.locales[locidx].getName()));
    417 
    418             for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) {
    419                 log.logln((UnicodeString) "    Pattern: " + PATTERNS[patidx]);
    420                 times[patidx] = 0;
    421 
    422                 UnicodeString pattern(BASEPATTERN);
    423                 pattern.append(" ").append(PATTERNS[patidx]);
    424 
    425                 SimpleDateFormat *sdf = new SimpleDateFormat(pattern, data.locales[locidx], status);
    426                 if (U_FAILURE(status)) {
    427                     log.errcheckln(status, (UnicodeString) "new SimpleDateFormat failed for pattern " +
    428                         pattern + " for locale " + data.locales[locidx].getName() + " - " + u_errorName(status));
    429                     status = U_ZERO_ERROR;
    430                     continue;
    431                 }
    432 
    433                 UBool minutesOffset = contains(MINUTES_OFFSET, PATTERNS[patidx]);
    434 
    435                 tzids->reset(status);
    436                 const UnicodeString *tzid;
    437 
    438                 timer = Calendar::getNow();
    439 
    440                 while ((tzid = tzids->snext(status))) {
    441                     if (uprv_strcmp(PATTERNS[patidx], "V") == 0) {
    442                         // Some zones do not have short ID assigned, such as Asia/Riyadh87.
    443                         // The time roundtrip will fail for such zones with pattern "V" (short zone ID).
    444                         // This is expected behavior.
    445                         const UChar* shortZoneID = ZoneMeta::getShortID(*tzid);
    446                         if (shortZoneID == NULL) {
    447                             continue;
    448                         }
    449                     } else if (uprv_strcmp(PATTERNS[patidx], "VVV") == 0) {
    450                         // Some zones are not associated with any region, such as Etc/GMT+8.
    451                         // The time roundtrip will fail for such zone with pattern "VVV" (exemplar location).
    452                         // This is expected behavior.
    453                         if (tzid->indexOf((UChar)0x2F) < 0 || tzid->indexOf(ETC_SLASH, -1, 0) >= 0
    454                             || tzid->indexOf(SYSTEMV_SLASH, -1, 0) >= 0 || tzid->indexOf(RIYADH8, -1, 0) >= 0) {
    455                             continue;
    456                         }
    457                     }
    458 
    459                     BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid);
    460                     sdf->setTimeZone(*tz);
    461 
    462                     UDate t = data.START_TIME;
    463                     TimeZoneTransition tzt;
    464                     UBool tztAvail = FALSE;
    465                     UBool middle = TRUE;
    466 
    467                     while (t < data.END_TIME) {
    468                         if (!tztAvail) {
    469                             testTimes[0] = t;
    470                             expectedRoundTrip[0] = TRUE;
    471                             testLen = 1;
    472                         } else {
    473                             int32_t fromOffset = tzt.getFrom()->getRawOffset() + tzt.getFrom()->getDSTSavings();
    474                             int32_t toOffset = tzt.getTo()->getRawOffset() + tzt.getTo()->getDSTSavings();
    475                             int32_t delta = toOffset - fromOffset;
    476                             if (delta < 0) {
    477                                 UBool isDstDecession = tzt.getFrom()->getDSTSavings() > 0 && tzt.getTo()->getDSTSavings() == 0;
    478                                 testTimes[0] = t + delta - 1;
    479                                 expectedRoundTrip[0] = TRUE;
    480                                 testTimes[1] = t + delta;
    481                                 expectedRoundTrip[1] = isDstDecession ?
    482                                     !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
    483                                     !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
    484                                 testTimes[2] = t - 1;
    485                                 expectedRoundTrip[2] = isDstDecession ?
    486                                     !contains(AMBIGUOUS_DST_DECESSION, PATTERNS[patidx]) :
    487                                     !contains(AMBIGUOUS_NEGATIVE_SHIFT, PATTERNS[patidx]);
    488                                 testTimes[3] = t;
    489                                 expectedRoundTrip[3] = TRUE;
    490                                 testLen = 4;
    491                             } else {
    492                                 testTimes[0] = t - 1;
    493                                 expectedRoundTrip[0] = TRUE;
    494                                 testTimes[1] = t;
    495                                 expectedRoundTrip[1] = TRUE;
    496                                 testLen = 2;
    497                             }
    498                         }
    499                         for (int32_t testidx = 0; testidx < testLen; testidx++) {
    500                             if (data.quick) {
    501                                 // reduce regular test time
    502                                 if (!expectedRoundTrip[testidx]) {
    503                                     continue;
    504                                 }
    505                             }
    506 
    507                             testCounts++;
    508 
    509                             UnicodeString text;
    510                             FieldPosition fpos(0);
    511                             sdf->format(testTimes[testidx], text, fpos);
    512 
    513                             UDate parsedDate = sdf->parse(text, status);
    514                             if (U_FAILURE(status)) {
    515                                 log.errln((UnicodeString) "Parse failure for text=" + text + ", tzid=" + *tzid + ", locale=" + data.locales[locidx].getName()
    516                                         + ", pattern=" + PATTERNS[patidx] + ", time=" + testTimes[testidx]);
    517                                 status = U_ZERO_ERROR;
    518                                 continue;
    519                             }
    520 
    521                             int32_t timeDiff = (int32_t)(parsedDate - testTimes[testidx]);
    522                             UBool bTimeMatch = minutesOffset ?
    523                                 (timeDiff/60000)*60000 == 0 : timeDiff == 0;
    524                             if (!bTimeMatch) {
    525                                 UnicodeString msg = (UnicodeString) "Time round trip failed for " + "tzid=" + *tzid + ", locale=" + data.locales[locidx].getName() + ", pattern=" + PATTERNS[patidx]
    526                                         + ", text=" + text + ", time=" + testTimes[testidx] + ", restime=" + parsedDate + ", diff=" + (parsedDate - testTimes[testidx]);
    527                                 // Timebomb for TZData update
    528                                 if (expectedRoundTrip[testidx]) {
    529                                     log.errln((UnicodeString) "FAIL: " + msg);
    530                                 } else if (REALLY_VERBOSE) {
    531                                     log.logln(msg);
    532                                 }
    533                             }
    534                         }
    535                         tztAvail = tz->getNextTransition(t, FALSE, tzt);
    536                         if (!tztAvail) {
    537                             break;
    538                         }
    539                         if (middle) {
    540                             // Test the date in the middle of two transitions.
    541                             t += (int64_t) ((tzt.getTime() - t) / 2);
    542                             middle = FALSE;
    543                             tztAvail = FALSE;
    544                         } else {
    545                             t = tzt.getTime();
    546                         }
    547                     }
    548                     delete tz;
    549                 }
    550                 times[patidx] += (Calendar::getNow() - timer);
    551                 delete sdf;
    552             }
    553             umtx_lock(NULL);
    554             data.numDone++;
    555             umtx_unlock(NULL);
    556         }
    557         delete tzids;
    558     }
    559 private:
    560     IntlTest& log;
    561     LocaleData& data;
    562     int32_t index;
    563 };
    564 
    565 void
    566 TimeZoneFormatTest::TestTimeRoundTrip(void) {
    567     int32_t nThreads = threadCount;
    568     const Locale *LOCALES;
    569     int32_t nLocales;
    570     int32_t testCounts = 0;
    571 
    572     UErrorCode status = U_ZERO_ERROR;
    573     Calendar *cal = Calendar::createInstance(TimeZone::createTimeZone((UnicodeString) "UTC"), status);
    574     if (U_FAILURE(status)) {
    575         dataerrln("Calendar::createInstance failed: %s", u_errorName(status));
    576         return;
    577     }
    578 
    579     const char* testAllProp = getProperty("TimeZoneRoundTripAll");
    580     UBool bTestAll = (testAllProp && uprv_strcmp(testAllProp, "true") == 0);
    581 
    582     UDate START_TIME, END_TIME;
    583     if (bTestAll || !quick) {
    584         cal->set(1900, UCAL_JANUARY, 1);
    585     } else {
    586         cal->set(1990, UCAL_JANUARY, 1);
    587     }
    588     START_TIME = cal->getTime(status);
    589 
    590     cal->set(2015, UCAL_JANUARY, 1);
    591     END_TIME = cal->getTime(status);
    592 
    593     if (U_FAILURE(status)) {
    594         errln("getTime failed");
    595         return;
    596     }
    597 
    598     UDate times[NUM_PATTERNS];
    599     for (int32_t i = 0; i < NUM_PATTERNS; i++) {
    600         times[i] = 0;
    601     }
    602 
    603     // Set up test locales
    604     const Locale locales1[] = {Locale("en")};
    605     const Locale locales2[] = {
    606         Locale("ar_EG"), Locale("bg_BG"), Locale("ca_ES"), Locale("da_DK"), Locale("de"),
    607         Locale("de_DE"), Locale("el_GR"), Locale("en"), Locale("en_AU"), Locale("en_CA"),
    608         Locale("en_US"), Locale("es"), Locale("es_ES"), Locale("es_MX"), Locale("fi_FI"),
    609         Locale("fr"), Locale("fr_CA"), Locale("fr_FR"), Locale("he_IL"), Locale("hu_HU"),
    610         Locale("it"), Locale("it_IT"), Locale("ja"), Locale("ja_JP"), Locale("ko"),
    611         Locale("ko_KR"), Locale("nb_NO"), Locale("nl_NL"), Locale("nn_NO"), Locale("pl_PL"),
    612         Locale("pt"), Locale("pt_BR"), Locale("pt_PT"), Locale("ru_RU"), Locale("sv_SE"),
    613         Locale("th_TH"), Locale("tr_TR"), Locale("zh"), Locale("zh_Hans"), Locale("zh_Hans_CN"),
    614         Locale("zh_Hant"), Locale("zh_Hant_TW")
    615     };
    616 
    617     if (bTestAll) {
    618         LOCALES = Locale::getAvailableLocales(nLocales);
    619     } else if (quick) {
    620         LOCALES = locales1;
    621         nLocales = sizeof(locales1)/sizeof(Locale);
    622     } else {
    623         LOCALES = locales2;
    624         nLocales = sizeof(locales2)/sizeof(Locale);
    625     }
    626 
    627     LocaleData data;
    628     data.index = 0;
    629     data.testCounts = testCounts;
    630     data.times = times;
    631     data.locales = LOCALES;
    632     data.nLocales = nLocales;
    633     data.quick = quick;
    634     data.START_TIME = START_TIME;
    635     data.END_TIME = END_TIME;
    636     data.numDone = 0;
    637 
    638 #if (ICU_USE_THREADS==0)
    639     TestTimeRoundTripThread fakeThread(*this, data, 0);
    640     fakeThread.run();
    641 #else
    642     TestTimeRoundTripThread **threads = new TestTimeRoundTripThread*[threadCount];
    643     int32_t i;
    644     for (i = 0; i < nThreads; i++) {
    645         threads[i] = new TestTimeRoundTripThread(*this, data, i);
    646         if (threads[i]->start() != 0) {
    647             errln("Error starting thread %d", i);
    648         }
    649     }
    650 
    651     UBool done = false;
    652     while (true) {
    653         umtx_lock(NULL);
    654         if (data.numDone == nLocales) {
    655             done = true;
    656         }
    657         umtx_unlock(NULL);
    658         if (done)
    659             break;
    660         SimpleThread::sleep(1000);
    661     }
    662 
    663     for (i = 0; i < nThreads; i++) {
    664         delete threads[i];
    665     }
    666     delete [] threads;
    667 
    668 #endif
    669     UDate total = 0;
    670     logln("### Elapsed time by patterns ###");
    671     for (int32_t i = 0; i < NUM_PATTERNS; i++) {
    672         logln(UnicodeString("") + data.times[i] + "ms (" + PATTERNS[i] + ")");
    673         total += data.times[i];
    674     }
    675     logln((UnicodeString) "Total: " + total + "ms");
    676     logln((UnicodeString) "Iteration: " + data.testCounts);
    677 
    678     delete cal;
    679 }
    680 
    681 
    682 typedef struct {
    683     const char*     text;
    684     int32_t         inPos;
    685     const char*     locale;
    686     UTimeZoneFormatStyle    style;
    687     UBool           parseAll;
    688     const char*     expected;
    689     int32_t         outPos;
    690     UTimeZoneFormatTimeType timeType;
    691 } ParseTestData;
    692 
    693 void
    694 TimeZoneFormatTest::TestParse(void) {
    695     const ParseTestData DATA[] = {
    696         //   text               inPos   locale      style                               parseAll    expected            outPos  timeType
    697             {"Z",               0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     false,      "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
    698             {"Z",               0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,         false,      "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
    699             {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     true,       "Etc/GMT",          1,      UTZFMT_TIME_TYPE_UNKNOWN},
    700             {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_GENERIC_LOCATION,      false,      "Africa/Lusaka",    11,     UTZFMT_TIME_TYPE_UNKNOWN},
    701             {"Zambia time",     0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  true,       "Africa/Lusaka",    11,     UTZFMT_TIME_TYPE_UNKNOWN},
    702             {"+00:00",          0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     false,      "Etc/GMT",          6,      UTZFMT_TIME_TYPE_UNKNOWN},
    703             {"-01:30:45",       0,      "en_US",    UTZFMT_STYLE_ISO_EXTENDED_FULL,     false,      "GMT-01:30:45",     9,      UTZFMT_TIME_TYPE_UNKNOWN},
    704             {"-7",              0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  false,      "GMT-07:00",        2,      UTZFMT_TIME_TYPE_UNKNOWN},
    705             {"-2222",           0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  false,      "GMT-22:22",        5,      UTZFMT_TIME_TYPE_UNKNOWN},
    706             {"-3333",           0,      "en_US",    UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,  false,      "GMT-03:33",        4,      UTZFMT_TIME_TYPE_UNKNOWN},
    707             {"XXX+01:30YYY",    3,      "en_US",    UTZFMT_STYLE_LOCALIZED_GMT,         false,      "GMT+01:30",        9,      UTZFMT_TIME_TYPE_UNKNOWN},
    708             {"GMT0",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "Etc/GMT",          3,      UTZFMT_TIME_TYPE_UNKNOWN},
    709             {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
    710             {"ESTx",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
    711             {"EDTx",            0,      "en_US",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/New_York", 3,      UTZFMT_TIME_TYPE_DAYLIGHT},
    712             {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,         false,      NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN},
    713             {"EST",             0,      "en_US",    UTZFMT_STYLE_SPECIFIC_LONG,         true,       "America/New_York", 3,      UTZFMT_TIME_TYPE_STANDARD},
    714             {"EST",             0,      "en_CA",    UTZFMT_STYLE_SPECIFIC_SHORT,        false,      "America/Toronto",  3,      UTZFMT_TIME_TYPE_STANDARD},
    715             {NULL,              0,      NULL,       UTZFMT_STYLE_GENERIC_LOCATION,      false,      NULL,               0,      UTZFMT_TIME_TYPE_UNKNOWN}
    716     };
    717 
    718     for (int32_t i = 0; DATA[i].text; i++) {
    719         UErrorCode status = U_ZERO_ERROR;
    720         LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
    721         if (U_FAILURE(status)) {
    722             dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
    723             continue;
    724         }
    725         UTimeZoneFormatTimeType ttype = UTZFMT_TIME_TYPE_UNKNOWN;
    726         ParsePosition pos(DATA[i].inPos);
    727         int32_t parseOptions = DATA[i].parseAll ? UTZFMT_PARSE_OPTION_ALL_STYLES : UTZFMT_PARSE_OPTION_NONE;
    728         TimeZone* tz = tzfmt->parse(DATA[i].style, DATA[i].text, pos, parseOptions, &ttype);
    729 
    730         UnicodeString errMsg;
    731         if (tz) {
    732             UnicodeString outID;
    733             tz->getID(outID);
    734             if (outID != UnicodeString(DATA[i].expected)) {
    735                 errMsg = (UnicodeString)"Time zone ID: " + outID + " - expected: " + DATA[i].expected;
    736             } else if (pos.getIndex() != DATA[i].outPos) {
    737                 errMsg = (UnicodeString)"Parsed pos: " + pos.getIndex() + " - expected: " + DATA[i].outPos;
    738             } else if (ttype != DATA[i].timeType) {
    739                 errMsg = (UnicodeString)"Time type: " + ttype + " - expected: " + DATA[i].timeType;
    740             }
    741             delete tz;
    742         } else {
    743             if (DATA[i].expected) {
    744                 errln((UnicodeString)"Fail: Parse failure - expected: " + DATA[i].expected);
    745             }
    746         }
    747         if (errMsg.length() > 0) {
    748             errln((UnicodeString)"Fail: " + errMsg + " [text=" + DATA[i].text + ", pos=" + DATA[i].inPos + ", style=" + DATA[i].style + "]");
    749         }
    750     }
    751 }
    752 
    753 void
    754 TimeZoneFormatTest::TestISOFormat(void) {
    755     const int32_t OFFSET[] = {
    756         0,          // 0
    757         999,        // 0.999s
    758         -59999,     // -59.999s
    759         60000,      // 1m
    760         -77777,     // -1m 17.777s
    761         1800000,    // 30m
    762         -3600000,   // -1h
    763         36000000,   // 10h
    764         -37800000,  // -10h 30m
    765         -37845000,  // -10h 30m 45s
    766         108000000,  // 30h
    767     };
    768 
    769     const char* ISO_STR[][11] = {
    770         // 0
    771         {
    772             "Z", "Z", "Z", "Z", "Z",
    773             "+00", "+0000", "+00:00", "+0000", "+00:00",
    774             "+0000"
    775         },
    776         // 999
    777         {
    778             "Z", "Z", "Z", "Z", "Z",
    779             "+00", "+0000", "+00:00", "+0000", "+00:00",
    780             "+0000"
    781         },
    782         // -59999
    783         {
    784             "Z", "Z", "Z", "-000059", "-00:00:59",
    785             "+00", "+0000", "+00:00", "-000059", "-00:00:59",
    786             "-000059"
    787         },
    788         // 60000
    789         {
    790             "+0001", "+0001", "+00:01", "+0001", "+00:01",
    791             "+0001", "+0001", "+00:01", "+0001", "+00:01",
    792             "+0001"
    793         },
    794         // -77777
    795         {
    796             "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
    797             "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
    798             "-000117"
    799         },
    800         // 1800000
    801         {
    802             "+0030", "+0030", "+00:30", "+0030", "+00:30",
    803             "+0030", "+0030", "+00:30", "+0030", "+00:30",
    804             "+0030"
    805         },
    806         // -3600000
    807         {
    808             "-01", "-0100", "-01:00", "-0100", "-01:00",
    809             "-01", "-0100", "-01:00", "-0100", "-01:00",
    810             "-0100"
    811         },
    812         // 36000000
    813         {
    814             "+10", "+1000", "+10:00", "+1000", "+10:00",
    815             "+10", "+1000", "+10:00", "+1000", "+10:00",
    816             "+1000"
    817         },
    818         // -37800000
    819         {
    820             "-1030", "-1030", "-10:30", "-1030", "-10:30",
    821             "-1030", "-1030", "-10:30", "-1030", "-10:30",
    822             "-1030"
    823         },
    824         // -37845000
    825         {
    826             "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
    827             "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
    828             "-103045"
    829         },
    830         // 108000000
    831         {
    832             0, 0, 0, 0, 0,
    833             0, 0, 0, 0, 0,
    834             0
    835         }
    836     };
    837 
    838     const char* PATTERN[] = {
    839         "X", "XX", "XXX", "XXXX", "XXXXX",
    840         "x", "xx", "xxx", "xxxx", "xxxxx",
    841         "Z", // equivalent to "xxxx"
    842         0
    843     };
    844 
    845     const int32_t MIN_OFFSET_UNIT[] = {
    846         60000, 60000, 60000, 1000, 1000,
    847         60000, 60000, 60000, 1000, 1000,
    848         1000,
    849     };
    850 
    851     // Formatting
    852     UErrorCode status = U_ZERO_ERROR;
    853     LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status));
    854     if (U_FAILURE(status)) {
    855         dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status));
    856         return;
    857     }
    858     UDate d = Calendar::getNow();
    859 
    860     for (uint32_t i = 0; i < sizeof(OFFSET)/sizeof(OFFSET[0]); i++) {
    861         SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms");
    862         sdf->adoptTimeZone(tz);
    863         for (int32_t j = 0; PATTERN[j] != 0; j++) {
    864             sdf->applyPattern(UnicodeString(PATTERN[j]));
    865             UnicodeString result;
    866             sdf->format(d, result);
    867 
    868             if (ISO_STR[i][j]) {
    869                 if (result != UnicodeString(ISO_STR[i][j])) {
    870                     errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
    871                         + result + " (expected: " + ISO_STR[i][j] + ")");
    872                 }
    873             } else {
    874                 // Offset out of range
    875                 // Note: for now, there is no way to propagate the error status through
    876                 // the SimpleDateFormat::format above.
    877                 if (result.length() > 0) {
    878                     errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
    879                         + " (expected: empty result)");
    880                 }
    881             }
    882         }
    883     }
    884 
    885     // Parsing
    886     LocalPointer<Calendar> outcal(Calendar::createInstance(status));
    887     if (U_FAILURE(status)) {
    888         dataerrln("Fail new Calendar: %s", u_errorName(status));
    889         return;
    890     }
    891     for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) {
    892         for (int32_t j = 0; PATTERN[j] != 0; j++) {
    893             if (ISO_STR[i][j] == 0) {
    894                 continue;
    895             }
    896             ParsePosition pos(0);
    897             SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
    898             outcal->adoptTimeZone(bogusTZ);
    899             sdf->applyPattern(PATTERN[j]);
    900 
    901             sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos);
    902 
    903             if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) {
    904                 errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
    905             }
    906 
    907             const TimeZone& outtz = outcal->getTimeZone();
    908             int32_t outOffset = outtz.getRawOffset();
    909             int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
    910             if (outOffset != adjustedOffset) {
    911                 errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
    912                     + " (expected:" + adjustedOffset + "ms)");
    913             }
    914         }
    915     }
    916 }
    917 
    918 
    919 #endif /* #if !UCONFIG_NO_FORMATTING */
    920