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