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