| /******************************************************************** | 
 |  * COPYRIGHT:  | 
 |  * Copyright (c) 1997-2003, International Business Machines Corporation and | 
 |  * others. All Rights Reserved. | 
 |  ********************************************************************/ | 
 |   | 
 | #include "unicode/utypes.h" | 
 |  | 
 | #if !UCONFIG_NO_FORMATTING | 
 |  | 
 | #include "unicode/datefmt.h" | 
 | #include "unicode/smpdtfmt.h" | 
 | #include "unicode/gregocal.h" | 
 | #include "dtfmtrtts.h" | 
 |  | 
 | #include <stdio.h> | 
 | #include <string.h> | 
 |  | 
 | // ***************************************************************************** | 
 | // class DateFormatRoundTripTest | 
 | // ***************************************************************************** | 
 |  | 
 | // Useful for turning up subtle bugs: Change the following to TRUE, recompile, | 
 | // and run while at lunch. | 
 | // Warning -- makes test run infinite loop!!! | 
 | #ifndef INFINITE | 
 | #define INFINITE 0 | 
 | #endif | 
 |  | 
 | // Define this to test just a single locale | 
 | //#define TEST_ONE_LOC  "ja_JP_TRADITIONAL" | 
 |  | 
 | // If SPARSENESS is > 0, we don't run each exhaustive possibility. | 
 | // There are 24 total possible tests per each locale.  A SPARSENESS | 
 | // of 12 means we run half of them.  A SPARSENESS of 23 means we run | 
 | // 1 of them.  SPARSENESS _must_ be in the range 0..23. | 
 | int32_t DateFormatRoundTripTest::SPARSENESS = 0; | 
 | int32_t DateFormatRoundTripTest::TRIALS = 4; | 
 | int32_t DateFormatRoundTripTest::DEPTH = 5; | 
 |  | 
 |  | 
 | #define CASE(id,test) case id: name = #test; if (exec) { logln(#test "---"); logln((UnicodeString)""); test(); } break; | 
 |  | 
 | void  | 
 | DateFormatRoundTripTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) | 
 | { | 
 |     // if (exec) logln((UnicodeString)"TestSuite NumberFormatRegressionTest"); | 
 |     switch (index) { | 
 |         CASE(0,TestDateFormatRoundTrip) | 
 |         default: name = ""; break; | 
 |     } | 
 | } | 
 |  | 
 | UBool  | 
 | DateFormatRoundTripTest::failure(UErrorCode status, const char* msg) | 
 | { | 
 |     if(U_FAILURE(status)) { | 
 |         errln(UnicodeString("FAIL: ") + msg + " failed, error " + u_errorName(status)); | 
 |         return TRUE; | 
 |     } | 
 |  | 
 |     return FALSE; | 
 | } | 
 |  | 
 | UBool  | 
 | DateFormatRoundTripTest::failure(UErrorCode status, const char* msg, const UnicodeString& str) | 
 | { | 
 |     if(U_FAILURE(status)) { | 
 |         UnicodeString escaped; | 
 |         escape(str,escaped); | 
 |         errln(UnicodeString("FAIL: ") + msg + " failed, error " + u_errorName(status) + ", str=" + escaped); | 
 |         return TRUE; | 
 |     } | 
 |  | 
 |     return FALSE; | 
 | } | 
 |  | 
 | // == | 
 |  | 
 | void DateFormatRoundTripTest::TestDateFormatRoundTrip()  | 
 | { | 
 |     UErrorCode status = U_ZERO_ERROR; | 
 |     dateFormat = new SimpleDateFormat((UnicodeString)"EEE MMM dd HH:mm:ss.SSS zzz yyyy G", status); | 
 |     failure(status, "new SimpleDateFormat"); | 
 |  | 
 |     getFieldCal = Calendar::createInstance(status); | 
 |     failure(status, "Calendar::createInstance"); | 
 |  | 
 |  | 
 |     int32_t locCount = 0; | 
 |     const Locale *avail = DateFormat::getAvailableLocales(locCount); | 
 |     logln("DateFormat available locales: %d", locCount); | 
 |     if(quick) { | 
 |         SPARSENESS = 18; | 
 |         logln("Quick mode: only testing SPARSENESS = 18"); | 
 |     } | 
 |     TimeZone *tz = TimeZone::createDefault(); | 
 |     UnicodeString temp; | 
 |     logln("Default TimeZone:             " + tz->getID(temp)); | 
 |     delete tz; | 
 |  | 
 | #ifdef TEST_ONE_LOC // define this to just test ONE locale. | 
 |     Locale loc(TEST_ONE_LOC); | 
 |     test(loc); | 
 | #if INFINITE | 
 |     for(;;) { | 
 |       test(loc); | 
 |     } | 
 | #endif | 
 |  | 
 | #else | 
 | # if INFINITE | 
 |     // Special infinite loop test mode for finding hard to reproduce errors | 
 |     Locale loc = Locale::getDefault(); | 
 |     logln("ENTERING INFINITE TEST LOOP FOR Locale: " + loc.getDisplayName(temp)); | 
 |     for(;;)  | 
 |         test(loc); | 
 | # else | 
 |     test(Locale::getDefault()); | 
 |  | 
 |     for (int i=0; i < locCount; ++i) { | 
 |         test(avail[i]); | 
 |     } | 
 | # endif | 
 | #endif | 
 |  | 
 |     delete dateFormat; | 
 |     delete getFieldCal; | 
 | } | 
 |  | 
 | static const char *styleName(DateFormat::EStyle s) | 
 | { | 
 |     switch(s) | 
 |     { | 
 |     case DateFormat::SHORT: return "SHORT"; | 
 |     case DateFormat::MEDIUM: return "MEDIUM"; | 
 |     case DateFormat::LONG: return "LONG"; | 
 |     case DateFormat::FULL: return "FULL"; | 
 | //  case DateFormat::DEFAULT: return "DEFAULT"; | 
 |     case DateFormat::DATE_OFFSET: return "DATE_OFFSET"; | 
 |     case DateFormat::NONE: return "NONE"; | 
 |     case DateFormat::DATE_TIME: return "DATE_TIME"; | 
 |     default: return "Unknown"; | 
 |     } | 
 | } | 
 |  | 
 | void DateFormatRoundTripTest::test(const Locale& loc)  | 
 | { | 
 |     UnicodeString temp; | 
 | #if !INFINITE | 
 |     logln("Locale: " + loc.getDisplayName(temp)); | 
 | #endif | 
 |  | 
 |     // Total possibilities = 24 | 
 |     //  4 date | 
 |     //  4 time | 
 |     //  16 date-time | 
 |     UBool TEST_TABLE [24];//= new boolean[24]; | 
 |     int32_t i = 0; | 
 |     for(i = 0; i < 24; ++i)  | 
 |         TEST_TABLE[i] = TRUE; | 
 |  | 
 |     // If we have some sparseness, implement it here.  Sparseness decreases | 
 |     // test time by eliminating some tests, up to 23. | 
 |     for(i = 0; i < SPARSENESS; ) { | 
 |         int random = (int)(randFraction() * 24); | 
 |         if (random >= 0 && random < 24 && TEST_TABLE[i]) { | 
 |             TEST_TABLE[i] = FALSE; | 
 |             ++i; | 
 |         } | 
 |     } | 
 |  | 
 |     int32_t itable = 0; | 
 |     int32_t style = 0; | 
 |     for(style = DateFormat::FULL; style <= DateFormat::SHORT; ++style) { | 
 |         if(TEST_TABLE[itable++]) { | 
 |             logln("Testing style " + UnicodeString(styleName((DateFormat::EStyle)style))); | 
 |             DateFormat *df = DateFormat::createDateInstance((DateFormat::EStyle)style, loc); | 
 |             if(df == NULL) { | 
 |               errln(UnicodeString("Could not DF::createDateInstance ") + UnicodeString(styleName((DateFormat::EStyle)style)) +      " Locale: " + loc.getDisplayName(temp)); | 
 |             } else { | 
 |               test(df, loc); | 
 |               delete df; | 
 |             } | 
 |         } | 
 |     } | 
 |      | 
 |     for(style = DateFormat::FULL; style <= DateFormat::SHORT; ++style) { | 
 |         if (TEST_TABLE[itable++]) { | 
 |           logln("Testing style " + UnicodeString(styleName((DateFormat::EStyle)style))); | 
 |             DateFormat *df = DateFormat::createTimeInstance((DateFormat::EStyle)style, loc); | 
 |             if(df == NULL) { | 
 |               errln(UnicodeString("Could not DF::createTimeInstance ") + UnicodeString(styleName((DateFormat::EStyle)style)) + " Locale: " + loc.getDisplayName(temp)); | 
 |             } else { | 
 |               test(df, loc, TRUE); | 
 |               delete df; | 
 |             } | 
 |         } | 
 |     } | 
 |      | 
 |     for(int32_t dstyle = DateFormat::FULL; dstyle <= DateFormat::SHORT; ++dstyle) { | 
 |         for(int32_t tstyle = DateFormat::FULL; tstyle <= DateFormat::SHORT; ++tstyle) { | 
 |             if(TEST_TABLE[itable++]) { | 
 |               logln("Testing dstyle" + UnicodeString(styleName((DateFormat::EStyle)dstyle)) + ", tstyle" + UnicodeString(styleName((DateFormat::EStyle)tstyle)) ); | 
 |                 DateFormat *df = DateFormat::createDateTimeInstance((DateFormat::EStyle)dstyle, (DateFormat::EStyle)tstyle, loc); | 
 |                 if(df == NULL) { | 
 |                   errln(UnicodeString("Could not DF::createDateTimeInstance ") + UnicodeString(styleName((DateFormat::EStyle)dstyle)) + ", tstyle" + UnicodeString(styleName((DateFormat::EStyle)tstyle))    + "Locale: " + loc.getDisplayName(temp)); | 
 |                 } else { | 
 |                   test(df, loc); | 
 |                   delete df; | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | void DateFormatRoundTripTest::test(DateFormat *fmt, const Locale &origLocale, UBool timeOnly)  | 
 | { | 
 |     UnicodeString pat; | 
 |     if(fmt->getDynamicClassID() != SimpleDateFormat::getStaticClassID()) { | 
 |         errln("DateFormat wasn't a SimpleDateFormat"); | 
 |         return; | 
 |     } | 
 |  | 
 |     pat = ((SimpleDateFormat*)fmt)->toPattern(pat); | 
 |  | 
 |     // NOTE TO MAINTAINER | 
 |     // This indexOf check into the pattern needs to be refined to ignore | 
 |     // quoted characters.  Currently, this isn't a problem with the locale | 
 |     // patterns we have, but it may be a problem later. | 
 |  | 
 |     UBool hasEra = (pat.indexOf(UnicodeString("G")) != -1); | 
 |     UBool hasZone = (pat.indexOf(UnicodeString("z")) != -1); | 
 |  | 
 |     // Because patterns contain incomplete data representing the Date, | 
 |     // we must be careful of how we do the roundtrip.  We start with | 
 |     // a randomly generated Date because they're easier to generate. | 
 |     // From this we get a string.  The string is our real starting point, | 
 |     // because this string should parse the same way all the time.  Note | 
 |     // that it will not necessarily parse back to the original date because | 
 |     // of incompleteness in patterns.  For example, a time-only pattern won't | 
 |     // parse back to the same date. | 
 |  | 
 |     //try { | 
 |         for(int i = 0; i < TRIALS; ++i) { | 
 |             UDate *d                = new UDate    [DEPTH]; | 
 |             UnicodeString *s    = new UnicodeString[DEPTH]; | 
 |  | 
 |             d[0] = generateDate(); | 
 |  | 
 |             UErrorCode status = U_ZERO_ERROR; | 
 |  | 
 |             // We go through this loop until we achieve a match or until | 
 |             // the maximum loop count is reached.  We record the points at | 
 |             // which the date and the string starts to match.  Once matching | 
 |             // starts, it should continue. | 
 |             int loop; | 
 |             int dmatch = 0; // d[dmatch].getTime() == d[dmatch-1].getTime() | 
 |             int smatch = 0; // s[smatch].equals(s[smatch-1]) | 
 |             for(loop = 0; loop < DEPTH; ++loop) { | 
 |                 if (loop > 0)  { | 
 |                     d[loop] = fmt->parse(s[loop-1], status); | 
 |                     failure(status, "fmt->parse", s[loop-1]); | 
 |                     status = U_ZERO_ERROR; /* any error would have been reported */ | 
 |                 } | 
 |  | 
 |                 s[loop] = fmt->format(d[loop], s[loop]); | 
 |                  | 
 |                 if(s[loop].length() == 0) { | 
 |                   errln("FAIL: fmt->format gave 0-length string in " + pat + " with number " + d[loop] + " in locale " + origLocale.getName()); | 
 |                 } | 
 |  | 
 |                 if(loop > 0) { | 
 |                     if(smatch == 0) { | 
 |                         UBool match = s[loop] == s[loop-1]; | 
 |                         if(smatch == 0) { | 
 |                             if(match)  | 
 |                                 smatch = loop; | 
 |                         } | 
 |                         else if( ! match)  | 
 |                             errln("FAIL: String mismatch after match"); | 
 |                     } | 
 |  | 
 |                     if(dmatch == 0) { | 
 |                         // {sfb} watch out here, this might not work | 
 |                         UBool match = d[loop]/*.getTime()*/ == d[loop-1]/*.getTime()*/; | 
 |                         if(dmatch == 0) { | 
 |                             if(match)  | 
 |                                 dmatch = loop; | 
 |                         } | 
 |                         else if( ! match)  | 
 |                             errln("FAIL: Date mismatch after match"); | 
 |                     } | 
 |  | 
 |                     if(smatch != 0 && dmatch != 0)  | 
 |                         break; | 
 |                 } | 
 |             } | 
 |             // At this point loop == DEPTH if we've failed, otherwise loop is the | 
 |             // max(smatch, dmatch), that is, the index at which we have string and | 
 |             // date matching. | 
 |  | 
 |             // Date usually matches in 2.  Exceptions handled below. | 
 |             int maxDmatch = 2; | 
 |             int maxSmatch = 1; | 
 |             if (dmatch > maxDmatch) { | 
 |                 // Time-only pattern with zone information and a starting date in PST. | 
 |                 if(timeOnly && hasZone && fmt->getTimeZone().inDaylightTime(d[0], status) && ! failure(status, "TimeZone::inDST()")) { | 
 |                     maxDmatch = 3; | 
 |                     maxSmatch = 2; | 
 |                 } | 
 |             } | 
 |  | 
 |             // String usually matches in 1.  Exceptions are checked for here. | 
 |             if(smatch > maxSmatch) { // Don't compute unless necessary | 
 |                 // Starts in BC, with no era in pattern | 
 |                 if( ! hasEra && getField(d[0], UCAL_ERA) == GregorianCalendar::BC) | 
 |                     maxSmatch = 2; | 
 |                 // Starts in DST, no year in pattern | 
 |                 else if(fmt->getTimeZone().inDaylightTime(d[0], status) && ! failure(status, "gettingDaylightTime") && | 
 |                          pat.indexOf(UnicodeString("yyyy")) == -1) | 
 |                     maxSmatch = 2; | 
 |                 // Two digit year with no time zone change, | 
 |                 // unless timezone isn't used or we aren't close to the DST changover | 
 |                 else if (pat.indexOf(UnicodeString("y")) != -1 | 
 |                         && pat.indexOf(UnicodeString("yyyy")) == -1 | 
 |                         && getField(d[0], UCAL_YEAR) | 
 |                             != getField(d[dmatch], UCAL_YEAR) | 
 |                         && !failure(status, "error status [smatch>maxSmatch]") | 
 |                         && ((hasZone | 
 |                          && (fmt->getTimeZone().inDaylightTime(d[0], status) | 
 |                                 == fmt->getTimeZone().inDaylightTime(d[dmatch], status) | 
 |                             || getField(d[0], UCAL_MONTH) == UCAL_APRIL | 
 |                             || getField(d[0], UCAL_MONTH) == UCAL_OCTOBER)) | 
 |                          || !hasZone) | 
 |                          ) | 
 |                 { | 
 |                     maxSmatch = 2; | 
 |                 }                 | 
 |             } | 
 |  | 
 |             if((dmatch > maxDmatch || smatch > maxSmatch) && // Special case for Japanese (could have large negative years) | 
 |                !strcmp(fmt->getCalendar()->getType(),"japanese")) { | 
 |                     maxSmatch = 4; | 
 |                     maxDmatch = 4; | 
 |             } | 
 |              | 
 |             if(dmatch > maxDmatch || smatch > maxSmatch) { | 
 |                 errln(UnicodeString("Pattern: ") + pat + UnicodeString(" failed to match in Locale: ") | 
 |                     + origLocale.getName()); | 
 |                 logln(UnicodeString(" Date ") + dmatch + "  String " + smatch); | 
 |  | 
 |                 printf("dmatch:%d maxD:%d smatch:%d maxS:%d\n", dmatch,maxDmatch, smatch, maxSmatch); | 
 |                  | 
 |                 for(int j = 0; j <= loop && j < DEPTH; ++j) { | 
 |                     UnicodeString temp; | 
 |                     FieldPosition pos(FieldPosition::DONT_CARE); | 
 |                     errln((j>0?" P> ":"    ") + dateFormat->format(d[j], temp, pos) + " F> " + | 
 |                           escape(s[j], temp) + UnicodeString(" d=") + d[j] +  | 
 |                           (j > 0 && d[j]/*.getTime()*/==d[j-1]/*.getTime()*/?" d==":"") + | 
 |                           (j > 0 && s[j] == s[j-1]?" s==":"")); | 
 |                 } | 
 |             } | 
 |             delete[] d; | 
 |             delete[] s; | 
 |         } | 
 |     /*} | 
 |     catch (ParseException e) { | 
 |         errln("Exception: " + e.getMessage()); | 
 |         logln(e.toString()); | 
 |     }*/ | 
 | } | 
 |  | 
 | /** | 
 |  * Return a field of the given date | 
 |  */ | 
 | int32_t DateFormatRoundTripTest::getField(UDate d, int32_t f) { | 
 |     // Should be synchronized, but we're single threaded so it's ok | 
 |     UErrorCode status = U_ZERO_ERROR; | 
 |     getFieldCal->setTime(d, status); | 
 |     failure(status, "getfieldCal->setTime"); | 
 |     int32_t ret = getFieldCal->get((UCalendarDateFields)f, status); | 
 |     failure(status, "getfieldCal->get"); | 
 |     return ret; | 
 | } | 
 |  | 
 | UnicodeString& DateFormatRoundTripTest::escape(const UnicodeString& src, UnicodeString& dst )  | 
 | { | 
 |     dst.remove(); | 
 |     for (int32_t i = 0; i < src.length(); ++i) { | 
 |         UChar c = src[i]; | 
 |         if(c < 0x0080)  | 
 |             dst += c; | 
 |         else { | 
 |             dst += UnicodeString("["); | 
 |             char buf [8]; | 
 |             sprintf(buf, "%#x", c); | 
 |             dst += UnicodeString(buf); | 
 |             dst += UnicodeString("]"); | 
 |         } | 
 |     } | 
 |  | 
 |     return dst; | 
 | } | 
 |  | 
 | UDate DateFormatRoundTripTest::generateDate()  | 
 | { | 
 |     double a = randFraction(); | 
 |      | 
 |     // Now 'a' ranges from 0..1; scale it to range from 0 to 8000 years | 
 |     a *= 8000; | 
 |      | 
 |     // Range from (4000-1970) BC to (8000-1970) AD | 
 |     a -= 4000; | 
 |      | 
 |     // Now scale up to ms | 
 |     a *= 365.25 * 24 * 60 * 60 * 1000; | 
 |  | 
 |     //return new Date((long)a); | 
 |     return a; | 
 | } | 
 |  | 
 | #endif /* #if !UCONFIG_NO_FORMATTING */ | 
 |  | 
 | //eof |