Home | History | Annotate | Download | only in intltest
      1 /***********************************************************************
      2  * COPYRIGHT:
      3  * Copyright (c) 1997-2014, International Business Machines Corporation
      4  * and others. All Rights Reserved.
      5  ***********************************************************************/
      6 
      7 #include "unicode/utypes.h"
      8 
      9 #if !UCONFIG_NO_FORMATTING
     10 
     11 #include "unicode/datefmt.h"
     12 #include "unicode/smpdtfmt.h"
     13 #include "unicode/gregocal.h"
     14 #include "dtfmtrtts.h"
     15 #include "caltest.h"
     16 
     17 #include <stdio.h>
     18 #include <string.h>
     19 
     20 // *****************************************************************************
     21 // class DateFormatRoundTripTest
     22 // *****************************************************************************
     23 
     24 // Useful for turning up subtle bugs: Change the following to TRUE, recompile,
     25 // and run while at lunch.
     26 // Warning -- makes test run infinite loop!!!
     27 #ifndef INFINITE
     28 #define INFINITE 0
     29 #endif
     30 
     31 // Define this to test just a single locale
     32 //#define TEST_ONE_LOC  "cs_CZ"
     33 
     34 // If SPARSENESS is > 0, we don't run each exhaustive possibility.
     35 // There are 24 total possible tests per each locale.  A SPARSENESS
     36 // of 12 means we run half of them.  A SPARSENESS of 23 means we run
     37 // 1 of them.  SPARSENESS _must_ be in the range 0..23.
     38 int32_t DateFormatRoundTripTest::SPARSENESS = 0;
     39 int32_t DateFormatRoundTripTest::TRIALS = 4;
     40 int32_t DateFormatRoundTripTest::DEPTH = 5;
     41 
     42 DateFormatRoundTripTest::DateFormatRoundTripTest() : dateFormat(0) {
     43 }
     44 
     45 DateFormatRoundTripTest::~DateFormatRoundTripTest() {
     46     delete dateFormat;
     47 }
     48 
     49 #define CASE(id,test) case id: name = #test; if (exec) { logln(#test "---"); logln((UnicodeString)""); test(); } break;
     50 
     51 void
     52 DateFormatRoundTripTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* par )
     53 {
     54     optionv = (par && *par=='v');
     55     switch (index) {
     56         CASE(0,TestDateFormatRoundTrip)
     57         CASE(1, TestCentury)
     58         default: name = ""; break;
     59     }
     60 }
     61 
     62 UBool
     63 DateFormatRoundTripTest::failure(UErrorCode status, const char* msg)
     64 {
     65     if(U_FAILURE(status)) {
     66         errln(UnicodeString("FAIL: ") + msg + " failed, error " + u_errorName(status));
     67         return TRUE;
     68     }
     69 
     70     return FALSE;
     71 }
     72 
     73 UBool
     74 DateFormatRoundTripTest::failure(UErrorCode status, const char* msg, const UnicodeString& str)
     75 {
     76     if(U_FAILURE(status)) {
     77         UnicodeString escaped;
     78         escape(str,escaped);
     79         errln(UnicodeString("FAIL: ") + msg + " failed, error " + u_errorName(status) + ", str=" + escaped);
     80         return TRUE;
     81     }
     82 
     83     return FALSE;
     84 }
     85 
     86 void DateFormatRoundTripTest::TestCentury()
     87 {
     88     UErrorCode status = U_ZERO_ERROR;
     89     Locale locale("es_PA");
     90     UnicodeString pattern = "MM/dd/yy hh:mm:ss a z";
     91     SimpleDateFormat fmt(pattern, locale, status);
     92     if (U_FAILURE(status)) {
     93         dataerrln("Fail: construct SimpleDateFormat: %s", u_errorName(status));
     94         return;
     95     }
     96     UDate date[] = {-55018555891590.05, 0, 0};
     97     UnicodeString result[2];
     98 
     99     fmt.format(date[0], result[0]);
    100     date[1] = fmt.parse(result[0], status);
    101     fmt.format(date[1], result[1]);
    102     date[2] = fmt.parse(result[1], status);
    103 
    104     /* This test case worked OK by accident before.  date[1] != date[0],
    105      * because we use -80/+20 year window for 2-digit year parsing.
    106      * (date[0] is in year 1926, date[1] is in year 2026.)  result[1] set
    107      * by the first format call returns "07/13/26 07:48:28 p.m. PST",
    108      * which is correct, because DST was not used in year 1926 in zone
    109      * America/Los_Angeles.  When this is parsed, date[1] becomes a time
    110      * in 2026, which is "07/13/26 08:48:28 p.m. PDT".  There was a zone
    111      * offset calculation bug that observed DST in 1926, which was resolved.
    112      * Before the bug was resolved, result[0] == result[1] was true,
    113      * but after the bug fix, the expected result is actually
    114      * result[0] != result[1]. -Yoshito
    115      */
    116     /* TODO: We need to review this code and clarify what we really
    117      * want to test here.
    118      */
    119     //if (date[1] != date[2] || result[0] != result[1]) {
    120     if (date[1] != date[2]) {
    121         errln("Round trip failure: \"%S\" (%f), \"%S\" (%f)", result[0].getBuffer(), date[1], result[1].getBuffer(), date[2]);
    122     }
    123 }
    124 
    125 // ==
    126 
    127 void DateFormatRoundTripTest::TestDateFormatRoundTrip()
    128 {
    129     UErrorCode status = U_ZERO_ERROR;
    130 
    131     getFieldCal = Calendar::createInstance(status);
    132     if (U_FAILURE(status)) {
    133         dataerrln("Fail: Calendar::createInstance: %s", u_errorName(status));
    134         return;
    135     }
    136 
    137 
    138     int32_t locCount = 0;
    139     const Locale *avail = DateFormat::getAvailableLocales(locCount);
    140     logln("DateFormat available locales: %d", locCount);
    141     if(quick) {
    142         SPARSENESS = 18;
    143         logln("Quick mode: only testing SPARSENESS = 18");
    144     }
    145     TimeZone *tz = TimeZone::createDefault();
    146     UnicodeString temp;
    147     logln("Default TimeZone:             " + tz->getID(temp));
    148     delete tz;
    149 
    150 #ifdef TEST_ONE_LOC // define this to just test ONE locale.
    151     Locale loc(TEST_ONE_LOC);
    152     test(loc);
    153 #if INFINITE
    154     for(;;) {
    155       test(loc);
    156     }
    157 #endif
    158 
    159 #else
    160 # if INFINITE
    161     // Special infinite loop test mode for finding hard to reproduce errors
    162     Locale loc = Locale::getDefault();
    163     logln("ENTERING INFINITE TEST LOOP FOR Locale: " + loc.getDisplayName(temp));
    164     for(;;)
    165         test(loc);
    166 # else
    167     test(Locale::getDefault());
    168 
    169 #if 1
    170     // installed locales
    171     for (int i=0; i < locCount; ++i) {
    172             test(avail[i]);
    173     }
    174 #endif
    175 
    176 #if 1
    177     // special locales
    178     int32_t jCount = CalendarTest::testLocaleCount();
    179     for (int32_t j=0; j < jCount; ++j) {
    180         test(Locale(CalendarTest::testLocaleID(j)));
    181     }
    182 #endif
    183 
    184 # endif
    185 #endif
    186 
    187     delete getFieldCal;
    188 }
    189 
    190 static const char *styleName(DateFormat::EStyle s)
    191 {
    192     switch(s)
    193     {
    194     case DateFormat::SHORT: return "SHORT";
    195     case DateFormat::MEDIUM: return "MEDIUM";
    196     case DateFormat::LONG: return "LONG";
    197     case DateFormat::FULL: return "FULL";
    198 //  case DateFormat::DEFAULT: return "DEFAULT";
    199     case DateFormat::DATE_OFFSET: return "DATE_OFFSET";
    200     case DateFormat::NONE: return "NONE";
    201     case DateFormat::DATE_TIME: return "DATE_TIME";
    202     default: return "Unknown";
    203     }
    204 }
    205 
    206 void DateFormatRoundTripTest::test(const Locale& loc)
    207 {
    208     UnicodeString temp;
    209 #if !INFINITE
    210     logln("Locale: " + loc.getDisplayName(temp));
    211 #endif
    212 
    213     // Total possibilities = 24
    214     //  4 date
    215     //  4 time
    216     //  16 date-time
    217     UBool TEST_TABLE [24];//= new boolean[24];
    218     int32_t i = 0;
    219     for(i = 0; i < 24; ++i)
    220         TEST_TABLE[i] = TRUE;
    221 
    222     // If we have some sparseness, implement it here.  Sparseness decreases
    223     // test time by eliminating some tests, up to 23.
    224     for(i = 0; i < SPARSENESS; ) {
    225         int random = (int)(randFraction() * 24);
    226         if (random >= 0 && random < 24 && TEST_TABLE[i]) {
    227             TEST_TABLE[i] = FALSE;
    228             ++i;
    229         }
    230     }
    231 
    232     int32_t itable = 0;
    233     int32_t style = 0;
    234     for(style = DateFormat::FULL; style <= DateFormat::SHORT; ++style) {
    235         if(TEST_TABLE[itable++]) {
    236             logln("Testing style " + UnicodeString(styleName((DateFormat::EStyle)style)));
    237             DateFormat *df = DateFormat::createDateInstance((DateFormat::EStyle)style, loc);
    238             if(df == NULL) {
    239               errln(UnicodeString("Could not DF::createDateInstance ") + UnicodeString(styleName((DateFormat::EStyle)style)) +      " Locale: " + loc.getDisplayName(temp));
    240             } else {
    241               test(df, loc);
    242               delete df;
    243             }
    244         }
    245     }
    246 
    247     for(style = DateFormat::FULL; style <= DateFormat::SHORT; ++style) {
    248         if (TEST_TABLE[itable++]) {
    249           logln("Testing style " + UnicodeString(styleName((DateFormat::EStyle)style)));
    250             DateFormat *df = DateFormat::createTimeInstance((DateFormat::EStyle)style, loc);
    251             if(df == NULL) {
    252               errln(UnicodeString("Could not DF::createTimeInstance ") + UnicodeString(styleName((DateFormat::EStyle)style)) + " Locale: " + loc.getDisplayName(temp));
    253             } else {
    254               test(df, loc, TRUE);
    255               delete df;
    256             }
    257         }
    258     }
    259 
    260     for(int32_t dstyle = DateFormat::FULL; dstyle <= DateFormat::SHORT; ++dstyle) {
    261         for(int32_t tstyle = DateFormat::FULL; tstyle <= DateFormat::SHORT; ++tstyle) {
    262             if(TEST_TABLE[itable++]) {
    263                 logln("Testing dstyle" + UnicodeString(styleName((DateFormat::EStyle)dstyle)) + ", tstyle" + UnicodeString(styleName((DateFormat::EStyle)tstyle)) );
    264                 DateFormat *df = DateFormat::createDateTimeInstance((DateFormat::EStyle)dstyle, (DateFormat::EStyle)tstyle, loc);
    265                 if(df == NULL) {
    266                     dataerrln(UnicodeString("Could not DF::createDateTimeInstance ") + UnicodeString(styleName((DateFormat::EStyle)dstyle)) + ", tstyle" + UnicodeString(styleName((DateFormat::EStyle)tstyle))    + "Locale: " + loc.getDisplayName(temp));
    267                 } else {
    268                     test(df, loc);
    269                     delete df;
    270                 }
    271             }
    272         }
    273     }
    274 }
    275 
    276 void DateFormatRoundTripTest::test(DateFormat *fmt, const Locale &origLocale, UBool timeOnly)
    277 {
    278     UnicodeString pat;
    279     if(fmt->getDynamicClassID() != SimpleDateFormat::getStaticClassID()) {
    280         errln("DateFormat wasn't a SimpleDateFormat");
    281         return;
    282     }
    283 
    284     UBool isGregorian = FALSE;
    285     UErrorCode minStatus = U_ZERO_ERROR;
    286     UDate minDate = CalendarTest::minDateOfCalendar(*fmt->getCalendar(), isGregorian, minStatus);
    287     if(U_FAILURE(minStatus)) {
    288       errln((UnicodeString)"Failure getting min date for " + origLocale.getName());
    289       return;
    290     }
    291     //logln(UnicodeString("Min date is ") + fullFormat(minDate)  + " for " + origLocale.getName());
    292 
    293     pat = ((SimpleDateFormat*)fmt)->toPattern(pat);
    294 
    295     // NOTE TO MAINTAINER
    296     // This indexOf check into the pattern needs to be refined to ignore
    297     // quoted characters.  Currently, this isn't a problem with the locale
    298     // patterns we have, but it may be a problem later.
    299 
    300     UBool hasEra = (pat.indexOf(UnicodeString("G")) != -1);
    301     UBool hasZoneDisplayName = (pat.indexOf(UnicodeString("z")) != -1) || (pat.indexOf(UnicodeString("v")) != -1)
    302         || (pat.indexOf(UnicodeString("V")) != -1);
    303 
    304     // Because patterns contain incomplete data representing the Date,
    305     // we must be careful of how we do the roundtrip.  We start with
    306     // a randomly generated Date because they're easier to generate.
    307     // From this we get a string.  The string is our real starting point,
    308     // because this string should parse the same way all the time.  Note
    309     // that it will not necessarily parse back to the original date because
    310     // of incompleteness in patterns.  For example, a time-only pattern won't
    311     // parse back to the same date.
    312 
    313     //try {
    314         for(int i = 0; i < TRIALS; ++i) {
    315             UDate *d                = new UDate    [DEPTH];
    316             UnicodeString *s    = new UnicodeString[DEPTH];
    317 
    318             if(isGregorian == TRUE) {
    319               d[0] = generateDate();
    320             } else {
    321               d[0] = generateDate(minDate);
    322             }
    323 
    324             UErrorCode status = U_ZERO_ERROR;
    325 
    326             // We go through this loop until we achieve a match or until
    327             // the maximum loop count is reached.  We record the points at
    328             // which the date and the string starts to match.  Once matching
    329             // starts, it should continue.
    330             int loop;
    331             int dmatch = 0; // d[dmatch].getTime() == d[dmatch-1].getTime()
    332             int smatch = 0; // s[smatch].equals(s[smatch-1])
    333             for(loop = 0; loop < DEPTH; ++loop) {
    334                 if (loop > 0)  {
    335                     d[loop] = fmt->parse(s[loop-1], status);
    336                     failure(status, "fmt->parse", s[loop-1]+" in locale: " + origLocale.getName() + " with pattern: " + pat);
    337                     status = U_ZERO_ERROR; /* any error would have been reported */
    338                 }
    339 
    340                 s[loop] = fmt->format(d[loop], s[loop]);
    341 
    342                 // For displaying which date is being tested
    343                 //logln(s[loop] + " = " + fullFormat(d[loop]));
    344 
    345                 if(s[loop].length() == 0) {
    346                   errln("FAIL: fmt->format gave 0-length string in " + pat + " with number " + d[loop] + " in locale " + origLocale.getName());
    347                 }
    348 
    349                 if(loop > 0) {
    350                     if(smatch == 0) {
    351                         UBool match = s[loop] == s[loop-1];
    352                         if(smatch == 0) {
    353                             if(match)
    354                                 smatch = loop;
    355                         }
    356                         else if( ! match)
    357                             errln("FAIL: String mismatch after match");
    358                     }
    359 
    360                     if(dmatch == 0) {
    361                         // {sfb} watch out here, this might not work
    362                         UBool match = d[loop]/*.getTime()*/ == d[loop-1]/*.getTime()*/;
    363                         if(dmatch == 0) {
    364                             if(match)
    365                                 dmatch = loop;
    366                         }
    367                         else if( ! match)
    368                             errln("FAIL: Date mismatch after match");
    369                     }
    370 
    371                     if(smatch != 0 && dmatch != 0)
    372                         break;
    373                 }
    374             }
    375             // At this point loop == DEPTH if we've failed, otherwise loop is the
    376             // max(smatch, dmatch), that is, the index at which we have string and
    377             // date matching.
    378 
    379             // Date usually matches in 2.  Exceptions handled below.
    380             int maxDmatch = 2;
    381             int maxSmatch = 1;
    382             if (dmatch > maxDmatch) {
    383                 // Time-only pattern with zone information and a starting date in PST.
    384                 if(timeOnly && hasZoneDisplayName) {
    385                     int32_t startRaw, startDst;
    386                     fmt->getTimeZone().getOffset(d[0], FALSE, startRaw, startDst, status);
    387                     failure(status, "TimeZone::getOffset");
    388                     // if the start offset is greater than the offset on Jan 1, 1970
    389                     // in PST, then need one more round trip.  There are two cases
    390                     // fall into this category.  The start date is 1) DST or
    391                     // 2) LMT (GMT-07:52:58).
    392                     if (startRaw + startDst > -28800000) {
    393                         maxDmatch = 3;
    394                         maxSmatch = 2;
    395                     }
    396                 }
    397             }
    398 
    399             // String usually matches in 1.  Exceptions are checked for here.
    400             if(smatch > maxSmatch) { // Don't compute unless necessary
    401                 UBool in0;
    402                 // Starts in BC, with no era in pattern
    403                 if( ! hasEra && getField(d[0], UCAL_ERA) == GregorianCalendar::BC)
    404                     maxSmatch = 2;
    405                 // Starts in DST, no year in pattern
    406                 else if((in0=fmt->getTimeZone().inDaylightTime(d[0], status)) && ! failure(status, "gettingDaylightTime") &&
    407                          pat.indexOf(UnicodeString("yyyy")) == -1)
    408                     maxSmatch = 2;
    409                 // If we start not in DST, but transition into DST
    410                 else if (!in0 &&
    411                          fmt->getTimeZone().inDaylightTime(d[1], status) && !failure(status, "gettingDaylightTime"))
    412                     maxSmatch = 2;
    413                 // Two digit year with no time zone change,
    414                 // unless timezone isn't used or we aren't close to the DST changover
    415                 else if (pat.indexOf(UnicodeString("y")) != -1
    416                         && pat.indexOf(UnicodeString("yyyy")) == -1
    417                         && getField(d[0], UCAL_YEAR)
    418                             != getField(d[dmatch], UCAL_YEAR)
    419                         && !failure(status, "error status [smatch>maxSmatch]")
    420                         && ((hasZoneDisplayName
    421                          && (fmt->getTimeZone().inDaylightTime(d[0], status)
    422                                 == fmt->getTimeZone().inDaylightTime(d[dmatch], status)
    423                             || getField(d[0], UCAL_MONTH) == UCAL_APRIL
    424                             || getField(d[0], UCAL_MONTH) == UCAL_OCTOBER))
    425                          || !hasZoneDisplayName)
    426                          )
    427                 {
    428                     maxSmatch = 2;
    429                 }
    430                 // If zone display name is used, fallback format might be used before 1970
    431                 else if (hasZoneDisplayName && d[0] < 0) {
    432                     maxSmatch = 2;
    433                 }
    434             }
    435 
    436             /*
    437              * Special case for Japanese and Buddhist (could have large negative years)
    438              * Also, Hebrew calendar need help handling leap month.
    439              */
    440             if(dmatch > maxDmatch || smatch > maxSmatch) {
    441               const char *type = fmt->getCalendar()->getType();
    442               if(!strcmp(type,"japanese") || (!strcmp(type,"buddhist"))) {
    443                 maxSmatch = 4;
    444                 maxDmatch = 4;
    445               } else if(!strcmp(type,"hebrew")) {
    446                   maxSmatch = 3;
    447                   maxDmatch = 3;
    448                 }
    449             }
    450 
    451             // Use @v to see verbose results on successful cases
    452             UBool fail = (dmatch > maxDmatch || smatch > maxSmatch);
    453             if (optionv || fail) {
    454                 if (fail) {
    455                     errln(UnicodeString("\nFAIL: Pattern: ") + pat +
    456                           " in Locale: " + origLocale.getName());
    457                 } else {
    458                     errln(UnicodeString("\nOk: Pattern: ") + pat +
    459                           " in Locale: " + origLocale.getName());
    460                 }
    461 
    462                 logln("Date iters until match=%d (max allowed=%d), string iters until match=%d (max allowed=%d)",
    463                       dmatch,maxDmatch, smatch, maxSmatch);
    464 
    465                 for(int j = 0; j <= loop && j < DEPTH; ++j) {
    466                     UnicodeString temp;
    467                     FieldPosition pos(FieldPosition::DONT_CARE);
    468                     errln((j>0?" P> ":"    ") + fullFormat(d[j]) + " F> " +
    469                           escape(s[j], temp) + UnicodeString(" d=") + d[j] +
    470                           (j > 0 && d[j]/*.getTime()*/==d[j-1]/*.getTime()*/?" d==":"") +
    471                           (j > 0 && s[j] == s[j-1]?" s==":""));
    472                 }
    473             }
    474             delete[] d;
    475             delete[] s;
    476         }
    477     /*}
    478     catch (ParseException e) {
    479         errln("Exception: " + e.getMessage());
    480         logln(e.toString());
    481     }*/
    482 }
    483 
    484 const UnicodeString& DateFormatRoundTripTest::fullFormat(UDate d) {
    485     UErrorCode ec = U_ZERO_ERROR;
    486     if (dateFormat == 0) {
    487         dateFormat = new SimpleDateFormat((UnicodeString)"EEE MMM dd HH:mm:ss.SSS zzz yyyy G", ec);
    488         if (U_FAILURE(ec) || dateFormat == 0) {
    489             fgStr = "[FAIL: SimpleDateFormat constructor]";
    490             delete dateFormat;
    491             dateFormat = 0;
    492             return fgStr;
    493         }
    494     }
    495     fgStr.truncate(0);
    496     dateFormat->format(d, fgStr);
    497     return fgStr;
    498 }
    499 
    500 /**
    501  * Return a field of the given date
    502  */
    503 int32_t DateFormatRoundTripTest::getField(UDate d, int32_t f) {
    504     // Should be synchronized, but we're single threaded so it's ok
    505     UErrorCode status = U_ZERO_ERROR;
    506     getFieldCal->setTime(d, status);
    507     failure(status, "getfieldCal->setTime");
    508     int32_t ret = getFieldCal->get((UCalendarDateFields)f, status);
    509     failure(status, "getfieldCal->get");
    510     return ret;
    511 }
    512 
    513 UnicodeString& DateFormatRoundTripTest::escape(const UnicodeString& src, UnicodeString& dst )
    514 {
    515     dst.remove();
    516     for (int32_t i = 0; i < src.length(); ++i) {
    517         UChar c = src[i];
    518         if(c < 0x0080)
    519             dst += c;
    520         else {
    521             dst += UnicodeString("[");
    522             char buf [8];
    523             sprintf(buf, "%#x", c);
    524             dst += UnicodeString(buf);
    525             dst += UnicodeString("]");
    526         }
    527     }
    528 
    529     return dst;
    530 }
    531 
    532 #define U_MILLIS_PER_YEAR (365.25 * 24 * 60 * 60 * 1000)
    533 
    534 UDate DateFormatRoundTripTest::generateDate(UDate minDate)
    535 {
    536   // Bring range in conformance to generateDate() below.
    537   if(minDate < (U_MILLIS_PER_YEAR * -(4000-1970))) {
    538     minDate = (U_MILLIS_PER_YEAR * -(4000-1970));
    539   }
    540   for(int i=0;i<8;i++) {
    541     double a = randFraction();
    542 
    543     // Range from (min) to  (8000-1970) AD
    544     double dateRange = (0.0 - minDate) + (U_MILLIS_PER_YEAR + (8000-1970));
    545 
    546     a *= dateRange;
    547 
    548     // Now offset from minDate
    549     a += minDate;
    550 
    551     // Last sanity check
    552     if(a>=minDate) {
    553       return a;
    554     }
    555   }
    556   return minDate;
    557 }
    558 
    559 UDate DateFormatRoundTripTest::generateDate()
    560 {
    561     double a = randFraction();
    562 
    563     // Now 'a' ranges from 0..1; scale it to range from 0 to 8000 years
    564     a *= 8000;
    565 
    566     // Range from (4000-1970) BC to (8000-1970) AD
    567     a -= 4000;
    568 
    569     // Now scale up to ms
    570     a *= 365.25 * 24 * 60 * 60 * 1000;
    571 
    572     //return new Date((long)a);
    573     return a;
    574 }
    575 
    576 #endif /* #if !UCONFIG_NO_FORMATTING */
    577 
    578 //eof
    579