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
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)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
test(UDate millis,icu::Calendar * cal,icu::DateFormat * fmt)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
withinErr(double a,double b,double err)90 CalendarLimitTest::withinErr(double a, double b, double err)
91 {
92     return ( uprv_fabs(a - b) < uprv_fabs(a * err) );
93 }
94 
95 void
TestCalendarExtremeLimit()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
TestLimits(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
doTheoreticalLimitsTest(Calendar & cal,UBool leapMonth)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
doLimitsTest(Calendar & cal,UDate startDate,int32_t endTime)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
doLimitsTest(Calendar & cal,const int32_t * fieldsToTest,UDate startDate,int32_t testDuration)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&
ymdToString(const Calendar & cal,UnicodeString & str)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