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