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