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
24U_NAMESPACE_USE
25void 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// -------------------------------------
55void
56CalendarLimitTest::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
93UBool
94CalendarLimitTest::withinErr(double a, double b, double err)
95{
96    return ( uprv_fabs(a - b) < uprv_fabs(a * err) );
97}
98
99void
100CalendarLimitTest::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
146namespace {
147
148struct TestCase {
149    const char *type;
150    UBool hasLeapMonth;
151    UDate actualTestStart;
152    int32_t actualTestEnd;
153};
154
155const UDate DEFAULT_START = 944006400000.0; // 1999-12-01T00:00Z
156const int32_t DEFAULT_END = -120; // Default for non-quick is run 2 minutes
157
158TestCase 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
176struct {
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
193void
194CalendarLimitTest::TestLimits(void) {
195    gTestCaseIterator.reset();
196
197    ThreadPool<CalendarLimitTest> threads(this, threadCount, &CalendarLimitTest::TestLimitsThread);
198    threads.start();
199    threads.join();
200}
201
202
203void 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
230void
231CalendarLimitTest::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
301void
302CalendarLimitTest::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
307void
308CalendarLimitTest::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
513UnicodeString&
514CalendarLimitTest::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