1 // © 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
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)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
test(UDate millis,icu::Calendar * cal,icu::DateFormat * fmt)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
withinErr(double a,double b,double err)94 CalendarLimitTest::withinErr(double a, double b, double err)
95 {
96 return ( uprv_fabs(a - b) < uprv_fabs(a * err) );
97 }
98
99 void
TestCalendarExtremeLimit()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;
next__anone6e4d7180111::__anone6e4d7180208178 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 }
reset__anone6e4d7180111::__anone6e4d7180208186 void reset() {
187 fIndex = 0;
188 }
189 } gTestCaseIterator;
190
191 } // anonymous name space
192
193 void
TestLimits(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
TestLimitsThread(int32_t threadNum)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
doTheoreticalLimitsTest(Calendar & cal,UBool leapMonth)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
doLimitsTest(Calendar & cal,UDate startDate,int32_t endTime)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
doLimitsTest(Calendar & cal,const int32_t * fieldsToTest,UDate startDate,int32_t testDuration)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 >= 1802044800000.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 >= 1832544000000.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&
ymdToString(const Calendar & cal,UnicodeString & str)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