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
20U_NAMESPACE_USE
21void 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// -------------------------------------
51void
52CalendarLimitTest::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
89UBool
90CalendarLimitTest::withinErr(double a, double b, double err)
91{
92    return ( uprv_fabs(a - b) < uprv_fabs(a * err) );
93}
94
95void
96CalendarLimitTest::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
142void
143CalendarLimitTest::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
194void
195CalendarLimitTest::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
265void
266CalendarLimitTest::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
271void
272CalendarLimitTest::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
458UnicodeString&
459CalendarLimitTest::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