Home | History | Annotate | Download | only in intltest
      1 // Copyright (C) 2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html
      3 /***********************************************************************
      4  * COPYRIGHT:
      5  * Copyright (c) 1997-2015, International Business Machines Corporation
      6  * and others. All Rights Reserved.
      7  ***********************************************************************/
      8 
      9 #include "unicode/utypes.h"
     10 
     11 #if !UCONFIG_NO_FORMATTING
     12 
     13 #include "callimts.h"
     14 #include "caltest.h"
     15 #include "unicode/calendar.h"
     16 #include "unicode/gregocal.h"
     17 #include "unicode/datefmt.h"
     18 #include "unicode/smpdtfmt.h"
     19 #include "cstring.h"
     20 #include "mutex.h"
     21 #include "putilimp.h"
     22 #include "simplethread.h"
     23 
     24 U_NAMESPACE_USE
     25 void CalendarLimitTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
     26 {
     27     if (exec) logln("TestSuite TestCalendarLimit");
     28     switch (index) {
     29         // Re-enable this later
     30         case 0:
     31             name = "TestCalendarExtremeLimit";
     32             if (exec) {
     33                 logln("TestCalendarExtremeLimit---"); logln("");
     34                 TestCalendarExtremeLimit();
     35             }
     36             break;
     37         case 1:
     38             name = "TestLimits";
     39             if (exec) {
     40                 logln("TestLimits---"); logln("");
     41                 TestLimits();
     42             }
     43             break;
     44 
     45         default: name = ""; break;
     46     }
     47 }
     48 
     49 
     50 // *****************************************************************************
     51 // class CalendarLimitTest
     52 // *****************************************************************************
     53 
     54 // -------------------------------------
     55 void
     56 CalendarLimitTest::test(UDate millis, icu::Calendar* cal, icu::DateFormat* fmt)
     57 {
     58   static const UDate kDrift = 1e-10;
     59     UErrorCode exception = U_ZERO_ERROR;
     60     UnicodeString theDate;
     61     UErrorCode status = U_ZERO_ERROR;
     62     cal->setTime(millis, exception);
     63     if (U_SUCCESS(exception)) {
     64         fmt->format(millis, theDate);
     65         UDate dt = fmt->parse(theDate, status);
     66         // allow a small amount of error (drift)
     67         if(! withinErr(dt, millis, kDrift)) {
     68           errln("FAIL:round trip for large milli, got: %.1lf wanted: %.1lf. (delta %.2lf greater than %.2lf)",
     69                 dt, millis, uprv_fabs(millis-dt), uprv_fabs(dt*kDrift));
     70           logln(UnicodeString("   ") + theDate + " " + CalendarTest::calToStr(*cal));
     71           } else {
     72             logln(UnicodeString("OK: got ") + dt + ", wanted " + millis);
     73             logln(UnicodeString("    ") + theDate);
     74         }
     75     }
     76 }
     77 
     78 // -------------------------------------
     79 
     80 // bug 986c: deprecate nextDouble/previousDouble
     81 //|double
     82 //|CalendarLimitTest::nextDouble(double a)
     83 //|{
     84 //|    return uprv_nextDouble(a, TRUE);
     85 //|}
     86 //|
     87 //|double
     88 //|CalendarLimitTest::previousDouble(double a)
     89 //|{
     90 //|    return uprv_nextDouble(a, FALSE);
     91 //|}
     92 
     93 UBool
     94 CalendarLimitTest::withinErr(double a, double b, double err)
     95 {
     96     return ( uprv_fabs(a - b) < uprv_fabs(a * err) );
     97 }
     98 
     99 void
    100 CalendarLimitTest::TestCalendarExtremeLimit()
    101 {
    102     UErrorCode status = U_ZERO_ERROR;
    103     Calendar *cal = Calendar::createInstance(status);
    104     if (failure(status, "Calendar::createInstance", TRUE)) return;
    105     cal->adoptTimeZone(TimeZone::createTimeZone("GMT"));
    106     DateFormat *fmt = DateFormat::createDateTimeInstance();
    107     if(!fmt || !cal) {
    108        dataerrln("can't open cal and/or fmt");
    109        return;
    110     }
    111     fmt->adoptCalendar(cal);
    112     ((SimpleDateFormat*) fmt)->applyPattern("HH:mm:ss.SSS Z, EEEE, MMMM d, yyyy G");
    113 
    114 
    115     // This test used to test the algorithmic limits of the dates that
    116     // GregorianCalendar could handle.  However, the algorithm has
    117     // been rewritten completely since then and the prior limits no
    118     // longer apply.  Instead, we now do basic round-trip testing of
    119     // some extreme (but still manageable) dates.
    120     UDate m;
    121     logln("checking 1e16..1e17");
    122     for ( m = 1e16; m < 1e17; m *= 1.1) {
    123         test(m, cal, fmt);
    124     }
    125     logln("checking -1e14..-1e15");
    126     for ( m = -1e14; m > -1e15; m *= 1.1) {
    127         test(m, cal, fmt);
    128     }
    129 
    130     // This is 2^52 - 1, the largest allowable mantissa with a 0
    131     // exponent in a 64-bit double
    132     UDate VERY_EARLY_MILLIS = - 4503599627370495.0;
    133     UDate VERY_LATE_MILLIS  =   4503599627370495.0;
    134 
    135     // I am removing the previousDouble and nextDouble calls below for
    136     // two reasons: 1. As part of jitterbug 986, I am deprecating
    137     // these methods and removing calls to them.  2. This test is a
    138     // non-critical boundary behavior test.
    139     test(VERY_EARLY_MILLIS, cal, fmt);
    140     //test(previousDouble(VERY_EARLY_MILLIS), cal, fmt);
    141     test(VERY_LATE_MILLIS, cal, fmt);
    142     //test(nextDouble(VERY_LATE_MILLIS), cal, fmt);
    143     delete fmt;
    144 }
    145 
    146 namespace {
    147 
    148 struct TestCase {
    149     const char *type;
    150     UBool hasLeapMonth;
    151     UDate actualTestStart;
    152     int32_t actualTestEnd;
    153 };
    154 
    155 const UDate DEFAULT_START = 944006400000.0; // 1999-12-01T00:00Z
    156 const int32_t DEFAULT_END = -120; // Default for non-quick is run 2 minutes
    157 
    158 TestCase TestCases[] = {
    159         {"gregorian",       FALSE,      DEFAULT_START, DEFAULT_END},
    160         {"japanese",        FALSE,      596937600000.0, DEFAULT_END}, // 1988-12-01T00:00Z, Showa 63
    161         {"buddhist",        FALSE,      DEFAULT_START, DEFAULT_END},
    162         {"roc",             FALSE,      DEFAULT_START, DEFAULT_END},
    163         {"persian",         FALSE,      DEFAULT_START, DEFAULT_END},
    164         {"islamic-civil",   FALSE,      DEFAULT_START, DEFAULT_END},
    165         {"islamic",         FALSE,      DEFAULT_START, 800000}, // Approx. 2250 years from now, after which
    166                                                                 // some rounding errors occur in Islamic calendar
    167         {"hebrew",          TRUE,       DEFAULT_START, DEFAULT_END},
    168         {"chinese",         TRUE,       DEFAULT_START, DEFAULT_END},
    169         {"dangi",           TRUE,       DEFAULT_START, DEFAULT_END},
    170         {"indian",          FALSE,      DEFAULT_START, DEFAULT_END},
    171         {"coptic",          FALSE,      DEFAULT_START, DEFAULT_END},
    172         {"ethiopic",        FALSE,      DEFAULT_START, DEFAULT_END},
    173         {"ethiopic-amete-alem", FALSE,  DEFAULT_START, DEFAULT_END}
    174 };
    175 
    176 struct {
    177     int32_t fIndex;
    178     UBool next (int32_t &rIndex) {
    179         Mutex lock;
    180         if (fIndex >= UPRV_LENGTHOF(TestCases)) {
    181             return FALSE;
    182         }
    183         rIndex = fIndex++;
    184         return TRUE;
    185     }
    186     void reset() {
    187         fIndex = 0;
    188     }
    189 } gTestCaseIterator;
    190 
    191 }  // anonymous name space
    192 
    193 void
    194 CalendarLimitTest::TestLimits(void) {
    195     gTestCaseIterator.reset();
    196 
    197     ThreadPool<CalendarLimitTest> threads(this, threadCount, &CalendarLimitTest::TestLimitsThread);
    198     threads.start();
    199     threads.join();
    200 }
    201 
    202 
    203 void CalendarLimitTest::TestLimitsThread(int32_t threadNum) {
    204     logln("thread %d starting", threadNum);
    205     int32_t testIndex = 0;
    206     LocalPointer<Calendar> cal;
    207     while (gTestCaseIterator.next(testIndex)) {
    208         TestCase &testCase = TestCases[testIndex];
    209         logln("begin test of %s calendar.", testCase.type);
    210         UErrorCode status = U_ZERO_ERROR;
    211         char buf[64];
    212         uprv_strcpy(buf, "root@calendar=");
    213         strcat(buf, testCase.type);
    214         cal.adoptInstead(Calendar::createInstance(buf, status));
    215         if (failure(status, "Calendar::createInstance", TRUE)) {
    216             continue;
    217         }
    218         if (uprv_strcmp(cal->getType(), testCase.type) != 0) {
    219             errln((UnicodeString)"FAIL: Wrong calendar type: " + cal->getType()
    220                 + " Requested: " + testCase.type);
    221             continue;
    222         }
    223         doTheoreticalLimitsTest(*(cal.getAlias()), testCase.hasLeapMonth);
    224         doLimitsTest(*(cal.getAlias()), testCase.actualTestStart, testCase.actualTestEnd);
    225         logln("end test of %s calendar.", testCase.type);
    226     }
    227 }
    228 
    229 
    230 void
    231 CalendarLimitTest::doTheoreticalLimitsTest(Calendar& cal, UBool leapMonth) {
    232     const char* calType = cal.getType();
    233 
    234     int32_t nDOW = cal.getMaximum(UCAL_DAY_OF_WEEK);
    235     int32_t maxDOY = cal.getMaximum(UCAL_DAY_OF_YEAR);
    236     int32_t lmaxDOW = cal.getLeastMaximum(UCAL_DAY_OF_YEAR);
    237     int32_t maxWOY = cal.getMaximum(UCAL_WEEK_OF_YEAR);
    238     int32_t lmaxWOY = cal.getLeastMaximum(UCAL_WEEK_OF_YEAR);
    239     int32_t maxM = cal.getMaximum(UCAL_MONTH) + 1;
    240     int32_t lmaxM = cal.getLeastMaximum(UCAL_MONTH) + 1;
    241     int32_t maxDOM = cal.getMaximum(UCAL_DAY_OF_MONTH);
    242     int32_t lmaxDOM = cal.getLeastMaximum(UCAL_DAY_OF_MONTH);
    243     int32_t maxDOWIM = cal.getMaximum(UCAL_DAY_OF_WEEK_IN_MONTH);
    244     int32_t lmaxDOWIM = cal.getLeastMaximum(UCAL_DAY_OF_WEEK_IN_MONTH);
    245     int32_t maxWOM = cal.getMaximum(UCAL_WEEK_OF_MONTH);
    246     int32_t lmaxWOM = cal.getLeastMaximum(UCAL_WEEK_OF_MONTH);
    247     int32_t minDaysInFirstWeek = cal.getMinimalDaysInFirstWeek();
    248 
    249     // Day of year
    250     int32_t expected;
    251     if (!leapMonth) {
    252         expected = maxM*maxDOM;
    253         if (maxDOY > expected) {
    254             errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of DAY_OF_YEAR is too big: "
    255                 + maxDOY + "/expected: <=" + expected);
    256         }
    257         expected = lmaxM*lmaxDOM;
    258         if (lmaxDOW < expected) {
    259             errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of DAY_OF_YEAR is too small: "
    260                 + lmaxDOW + "/expected: >=" + expected);
    261         }
    262     }
    263 
    264     // Week of year
    265     expected = maxDOY/nDOW + 1;
    266     if (maxWOY > expected) {
    267         errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of WEEK_OF_YEAR is too big: "
    268             + maxWOY + "/expected: <=" + expected);
    269     }
    270     expected = lmaxDOW/nDOW;
    271     if (lmaxWOY < expected) {
    272         errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of WEEK_OF_YEAR is too small: "
    273             + lmaxWOY + "/expected >=" + expected);
    274     }
    275 
    276     // Day of week in month
    277     expected = (maxDOM + nDOW - 1)/nDOW;
    278     if (maxDOWIM != expected) {
    279         errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: "
    280             + maxDOWIM + "/expected: " + expected);
    281     }
    282     expected = (lmaxDOM + nDOW - 1)/nDOW;
    283     if (lmaxDOWIM != expected) {
    284         errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: "
    285             + lmaxDOWIM + "/expected: " + expected);
    286     }
    287 
    288     // Week of month
    289     expected = (maxDOM + (nDOW - 1) + (nDOW - minDaysInFirstWeek)) / nDOW;
    290     if (maxWOM != expected) {
    291         errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of WEEK_OF_MONTH is incorrect: "
    292             + maxWOM + "/expected: " + expected);
    293     }
    294     expected = (lmaxDOM + (nDOW - minDaysInFirstWeek)) / nDOW;
    295     if (lmaxWOM != expected) {
    296         errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of WEEK_OF_MONTH is incorrect: "
    297             + lmaxWOM + "/expected: " + expected);
    298     }
    299 }
    300 
    301 void
    302 CalendarLimitTest::doLimitsTest(Calendar& cal, UDate startDate, int32_t endTime) {
    303     int32_t testTime = quick ? ( endTime / 40 ) : endTime;
    304     doLimitsTest(cal, NULL /*default fields*/, startDate, testTime);
    305 }
    306 
    307 void
    308 CalendarLimitTest::doLimitsTest(Calendar& cal,
    309                                 const int32_t* fieldsToTest,
    310                                 UDate startDate,
    311                                 int32_t testDuration) {
    312     static const int32_t FIELDS[] = {
    313         UCAL_ERA,
    314         UCAL_YEAR,
    315         UCAL_MONTH,
    316         UCAL_WEEK_OF_YEAR,
    317         UCAL_WEEK_OF_MONTH,
    318         UCAL_DAY_OF_MONTH,
    319         UCAL_DAY_OF_YEAR,
    320         UCAL_DAY_OF_WEEK_IN_MONTH,
    321         UCAL_YEAR_WOY,
    322         UCAL_EXTENDED_YEAR,
    323         -1,
    324     };
    325 
    326     static const char* FIELD_NAME[] = {
    327         "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
    328         "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
    329         "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
    330         "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
    331         "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
    332         "JULIAN_DAY", "MILLISECONDS_IN_DAY",
    333         "IS_LEAP_MONTH"
    334     };
    335 
    336     UErrorCode status = U_ZERO_ERROR;
    337     int32_t i, j;
    338     UnicodeString ymd;
    339 
    340     GregorianCalendar greg(status);
    341     if (failure(status, "new GregorianCalendar")) {
    342         return;
    343     }
    344     greg.setTime(startDate, status);
    345     if (failure(status, "GregorianCalendar::setTime")) {
    346         return;
    347     }
    348     logln((UnicodeString)"Start: " + startDate);
    349 
    350     if (fieldsToTest == NULL) {
    351         fieldsToTest = FIELDS;
    352     }
    353 
    354 
    355     // Keep a record of minima and maxima that we actually see.
    356     // These are kept in an array of arrays of hashes.
    357     int32_t limits[UCAL_FIELD_COUNT][4];
    358     for (j = 0; j < UCAL_FIELD_COUNT; j++) {
    359         limits[j][0] = INT32_MAX;
    360         limits[j][1] = INT32_MIN;
    361         limits[j][2] = INT32_MAX;
    362         limits[j][3] = INT32_MIN;
    363     }
    364 
    365     // This test can run for a long time; show progress.
    366     UDate millis = ucal_getNow();
    367     UDate mark = millis + 5000; // 5 sec
    368     millis -= testDuration * 1000; // stop time if testDuration<0
    369 
    370     for (i = 0;
    371          testDuration > 0 ? i < testDuration
    372                         : ucal_getNow() < millis;
    373          ++i) {
    374         if (ucal_getNow() >= mark) {
    375             logln((UnicodeString)"(" + i + " days)");
    376             mark += 5000; // 5 sec
    377         }
    378         UDate testMillis = greg.getTime(status);
    379         cal.setTime(testMillis, status);
    380         cal.setMinimalDaysInFirstWeek(1);
    381         if (failure(status, "Calendar set/getTime")) {
    382             return;
    383         }
    384         for (j = 0; fieldsToTest[j] >= 0; ++j) {
    385             UCalendarDateFields f = (UCalendarDateFields)fieldsToTest[j];
    386             int32_t v = cal.get(f, status);
    387             int32_t minActual = cal.getActualMinimum(f, status);
    388             int32_t maxActual = cal.getActualMaximum(f, status);
    389             int32_t minLow = cal.getMinimum(f);
    390             int32_t minHigh = cal.getGreatestMinimum(f);
    391             int32_t maxLow = cal.getLeastMaximum(f);
    392             int32_t maxHigh = cal.getMaximum(f);
    393 
    394             if (limits[j][0] > minActual) {
    395                 // the minimum
    396                 limits[j][0] = minActual;
    397             }
    398             if (limits[j][1] < minActual) {
    399                 // the greatest minimum
    400                 limits[j][1] = minActual;
    401             }
    402             if (limits[j][2] > maxActual) {
    403                 // the least maximum
    404                 limits[j][2] = maxActual;
    405             }
    406             if (limits[j][3] < maxActual) {
    407                 // the maximum
    408                 limits[j][3] = maxActual;
    409             }
    410 
    411             if (minActual < minLow || minActual > minHigh) {
    412                 errln((UnicodeString)"Fail: [" + cal.getType() + "] " +
    413                       ymdToString(cal, ymd) +
    414                       " Range for min of " + FIELD_NAME[f] + "(" + f +
    415                       ")=" + minLow + ".." + minHigh +
    416                       ", actual_min=" + minActual);
    417             }
    418             if (maxActual < maxLow || maxActual > maxHigh) {
    419                 if ( uprv_strcmp(cal.getType(), "chinese") == 0 &&
    420                         testMillis >= 2842992000000.0  && testMillis <= 2906668800000.0 &&
    421                      logKnownIssue("12620", "chinese calendar failures for some actualMax tests")) {
    422                     logln((UnicodeString)"KnownFail: [" + cal.getType() + "] " +
    423                           ymdToString(cal, ymd) +
    424                           " Range for max of " + FIELD_NAME[f] + "(" + f +
    425                           ")=" + maxLow + ".." + maxHigh +
    426                           ", actual_max=" + maxActual);
    427                 } else {
    428                     errln((UnicodeString)"Fail: [" + cal.getType() + "] " +
    429                           ymdToString(cal, ymd) +
    430                           " Range for max of " + FIELD_NAME[f] + "(" + f +
    431                           ")=" + maxLow + ".." + maxHigh +
    432                           ", actual_max=" + maxActual);
    433                 }
    434             }
    435             if (v < minActual || v > maxActual) {
    436                 // timebomb per #9967, fix with #9972
    437                 if ( uprv_strcmp(cal.getType(), "dangi") == 0 &&
    438                         testMillis >= 1865635198000.0  &&
    439                      logKnownIssue("9972", "as per #9967")) { // Feb 2029 gregorian, end of dangi 4361
    440                     logln((UnicodeString)"KnownFail: [" + cal.getType() + "] " +
    441                           ymdToString(cal, ymd) +
    442                           " " + FIELD_NAME[f] + "(" + f + ")=" + v +
    443                           ", actual=" + minActual + ".." + maxActual +
    444                           ", allowed=(" + minLow + ".." + minHigh + ")..(" +
    445                           maxLow + ".." + maxHigh + ")");
    446                 } else if ( uprv_strcmp(cal.getType(), "chinese") == 0 &&
    447                         testMillis >= 2842992000000.0  && testMillis <= 2906668800000.0 &&
    448                      logKnownIssue("12620", "chinese calendar failures for some actualMax tests")) {
    449                     logln((UnicodeString)"KnownFail: [" + cal.getType() + "] " +
    450                           ymdToString(cal, ymd) +
    451                           " " + FIELD_NAME[f] + "(" + f + ")=" + v +
    452                           ", actual=" + minActual + ".." + maxActual +
    453                           ", allowed=(" + minLow + ".." + minHigh + ")..(" +
    454                           maxLow + ".." + maxHigh + ")");
    455                 } else {
    456                     errln((UnicodeString)"Fail: [" + cal.getType() + "] " +
    457                           ymdToString(cal, ymd) +
    458                           " " + FIELD_NAME[f] + "(" + f + ")=" + v +
    459                           ", actual=" + minActual + ".." + maxActual +
    460                           ", allowed=(" + minLow + ".." + minHigh + ")..(" +
    461                           maxLow + ".." + maxHigh + ")");
    462                 }
    463             }
    464         }
    465         greg.add(UCAL_DAY_OF_YEAR, 1, status);
    466         if (failure(status, "Calendar::add")) {
    467             return;
    468         }
    469     }
    470 
    471     // Check actual maxima and minima seen against ranges returned
    472     // by API.
    473     UnicodeString buf;
    474     for (j = 0; fieldsToTest[j] >= 0; ++j) {
    475         int32_t rangeLow, rangeHigh;
    476         UBool fullRangeSeen = TRUE;
    477         UCalendarDateFields f = (UCalendarDateFields)fieldsToTest[j];
    478 
    479         buf.remove();
    480         buf.append((UnicodeString)"[" + cal.getType() + "] " + FIELD_NAME[f]);
    481 
    482         // Minumum
    483         rangeLow = cal.getMinimum(f);
    484         rangeHigh = cal.getGreatestMinimum(f);
    485         if (limits[j][0] != rangeLow || limits[j][1] != rangeHigh) {
    486             fullRangeSeen = FALSE;
    487         }
    488         buf.append((UnicodeString)" minima range=" + rangeLow + ".." + rangeHigh);
    489         buf.append((UnicodeString)" minima actual=" + limits[j][0] + ".." + limits[j][1]);
    490 
    491         // Maximum
    492         rangeLow = cal.getLeastMaximum(f);
    493         rangeHigh = cal.getMaximum(f);
    494         if (limits[j][2] != rangeLow || limits[j][3] != rangeHigh) {
    495             fullRangeSeen = FALSE;
    496         }
    497         buf.append((UnicodeString)" maxima range=" + rangeLow + ".." + rangeHigh);
    498         buf.append((UnicodeString)" maxima actual=" + limits[j][2] + ".." + limits[j][3]);
    499 
    500         if (fullRangeSeen) {
    501             logln((UnicodeString)"OK: " + buf);
    502         } else {
    503             // This may or may not be an error -- if the range of dates
    504             // we scan over doesn't happen to contain a minimum or
    505             // maximum, it doesn't mean some other range won't.
    506             logln((UnicodeString)"Warning: " + buf);
    507         }
    508     }
    509 
    510     logln((UnicodeString)"End: " + greg.getTime(status));
    511 }
    512 
    513 UnicodeString&
    514 CalendarLimitTest::ymdToString(const Calendar& cal, UnicodeString& str) {
    515     UErrorCode status = U_ZERO_ERROR;
    516     str.remove();
    517     str.append((UnicodeString)"" + cal.get(UCAL_EXTENDED_YEAR, status)
    518         + "/" + (cal.get(UCAL_MONTH, status) + 1)
    519         + (cal.get(UCAL_IS_LEAP_MONTH, status) == 1 ? "(leap)" : "")
    520         + "/" + cal.get(UCAL_DATE, status)
    521         + " " + cal.get(UCAL_HOUR_OF_DAY, status)
    522         + ":" + cal.get(UCAL_MINUTE, status)
    523         + " zone(hrs) " + cal.get(UCAL_ZONE_OFFSET, status)/(60.0*60.0*1000.0)
    524         + " dst(hrs) " + cal.get(UCAL_DST_OFFSET, status)/(60.0*60.0*1000.0)
    525         + ", time(millis)=" + cal.getTime(status));
    526     return str;
    527 }
    528 
    529 #endif /* #if !UCONFIG_NO_FORMATTING */
    530 
    531 // eof
    532