void CalendarRegressionTest::TestJ81() {
    UErrorCode status = U_ZERO_ERROR;
    UnicodeString temp, temp2, temp3;
    int32_t i;
    GregorianCalendar cal(TimeZone::createTimeZone("GMT"), Locale::getUS(), status);
    SimpleDateFormat fmt("HH:mm 'w'w 'd'D E d MMM yyyy", Locale::getUS(), status);
    if (U_FAILURE(status)) {
        dataerrln("Error: Cannot create calendar or format - %s", u_errorName(status));
        return;
    }
    fmt.setCalendar(cal);
    // Get the Gregorian cutover
    UDate cutover = cal.getGregorianChange();
    UDate days = ONE_DAY;
    days = cutover/days;
    logln(UnicodeString("Cutover: {") +
          fmt.format(cutover, temp) + "}(epoch days-" + (int)days + ", jd" + (2440588 + days) +")");

    // Check woy and doy handling.  Reference data:
    /* w40 d274 Mon 1 Oct 1582
       w40 d275 Tue 2 Oct 1582
       w40 d276 Wed 3 Oct 1582
       w40 d277 Thu 4 Oct 1582
       w40 d278 Fri 15 Oct 1582
       w40 d279 Sat 16 Oct 1582
       w41 d280 Sun 17 Oct 1582
       w41 d281 Mon 18 Oct 1582
       w41 d282 Tue 19 Oct 1582
       w41 d283 Wed 20 Oct 1582
       w41 d284 Thu 21 Oct 1582
       w41 d285 Fri 22 Oct 1582
       w41 d286 Sat 23 Oct 1582
       w42 d287 Sun 24 Oct 1582
       w42 d288 Mon 25 Oct 1582
       w42 d289 Tue 26 Oct 1582
       w42 d290 Wed 27 Oct 1582
       w42 d291 Thu 28 Oct 1582
       w42 d292 Fri 29 Oct 1582
       w42 d293 Sat 30 Oct 1582
       w43 d294 Sun 31 Oct 1582
       w43 d295 Mon 1 Nov 1582 */
    int32_t DOY_DATA[] = {
        // dom, woy, doy
        1, 40, 274, UCAL_MONDAY,
        4, 40, 277, UCAL_THURSDAY,
        15, 40, 278, UCAL_FRIDAY,
        17, 41, 280, UCAL_SUNDAY,
        24, 42, 287, UCAL_SUNDAY,
        25, 42, 288, UCAL_MONDAY,
        26, 42, 289, UCAL_TUESDAY,
        27, 42, 290, UCAL_WEDNESDAY,
        28, 42, 291, UCAL_THURSDAY,
        29, 42, 292, UCAL_FRIDAY,
        30, 42, 293, UCAL_SATURDAY,
        31, 43, 294, UCAL_SUNDAY
    };
    int32_t DOY_DATA_length = UPRV_LENGTHOF(DOY_DATA);

    for (i=0; i<DOY_DATA_length; i+=4) {
        // Test time->fields
        cal.set(1582, UCAL_OCTOBER, DOY_DATA[i]);
        int32_t woy = cal.get(UCAL_WEEK_OF_YEAR, status);
        int32_t doy = cal.get(UCAL_DAY_OF_YEAR, status);
        int32_t dow = cal.get(UCAL_DAY_OF_WEEK, status);
        if (U_FAILURE(status)) {
            errln("Error: get() failed");
            break;
        }
        if (woy != DOY_DATA[i+1] || doy != DOY_DATA[i+2] || dow != DOY_DATA[i+3]) {
            errln((UnicodeString)"Fail: expect woy=" + DOY_DATA[i+1] +
                  ", doy=" + DOY_DATA[i+2] + ", dow=" + DOY_DATA[i+3] + " on " +
                  fmt.format(cal.getTime(status), temp.remove()) +
                  " set(1582,OCTOBER, " + DOY_DATA[i] + ")");
            logln(CalendarTest::calToStr(cal));
            status = U_ZERO_ERROR;
        }  else {
          logln((UnicodeString)"PASS: expect woy=" + DOY_DATA[i+1] +
                ", doy=" + DOY_DATA[i+2] + ", dow=" + DOY_DATA[i+3] + " on " +
                fmt.format(cal.getTime(status), temp.remove()));
          logln(CalendarTest::calToStr(cal));
          status = U_ZERO_ERROR;
        }
        // Test fields->time for WOY
        cal.clear();
        cal.set(UCAL_YEAR, 1582);
        cal.set(UCAL_WEEK_OF_YEAR, DOY_DATA[i+1]);
        cal.set(UCAL_DAY_OF_WEEK, DOY_DATA[i+3]);
        int32_t dom = cal.get(UCAL_DATE, status);
        if (U_FAILURE(status)) {
            errln("Error: get() failed");
            break;
        }
        if (dom != DOY_DATA[i]) {
            errln((UnicodeString)"Fail: set woy=" + DOY_DATA[i+1] +
                  " dow=" + DOY_DATA[i+3] + " => " +
                  fmt.format(cal.getTime(status), temp.remove()) +
                  ", expected 1582 Oct " + DOY_DATA[i]);
            logln(CalendarTest::calToStr(cal));
            status = U_ZERO_ERROR;
        }

        // Test fields->time for DOY
        cal.clear();
        cal.set(UCAL_YEAR, 1582);
        cal.set(UCAL_DAY_OF_YEAR, DOY_DATA[i+2]);
        dom = cal.get(UCAL_DATE, status);
        if (U_FAILURE(status)) {
            errln("Error: get() failed");
            break;
        }
        if (dom != DOY_DATA[i]) {
            errln((UnicodeString)"Fail: set doy=" + DOY_DATA[i+2] +
                  " => " +
                  fmt.format(cal.getTime(status), temp.remove()) +
                  ", expected 1582 Oct " + DOY_DATA[i]);
            status = U_ZERO_ERROR;
        }
    }
    status = U_ZERO_ERROR;

#define ADD_ROLL  ADD|ROLL
#define PLUS_MINUS PLUS|MINUS
    // Test cases
    J81_DATA DATA[] = {
        { UCAL_WEEK_OF_YEAR, ADD_ROLL, PLUS_MINUS, 1, -ONE_DAY, +6*ONE_DAY },
        { UCAL_WEEK_OF_YEAR, ADD_ROLL, PLUS_MINUS, 1, -ONE_DAY, +6*ONE_DAY },
        { UCAL_WEEK_OF_MONTH, ADD|ROLL, PLUS|MINUS, 1, -ONE_DAY, +6*ONE_DAY },
        { UCAL_DATE, ADD|ROLL, PLUS|MINUS, 2, -ONE_DAY, +1*ONE_DAY },
        { UCAL_DATE, ROLL, PLUS, -6, -ONE_DAY, +14*ONE_DAY },
        { UCAL_DATE, ROLL, PLUS, -7, 0, +14*ONE_DAY },
        { UCAL_DATE, ROLL, PLUS, -7, +ONE_DAY, +15*ONE_DAY },
        { UCAL_DATE, ROLL, PLUS, +18, -ONE_DAY, -4*ONE_DAY },
        { UCAL_DAY_OF_YEAR, ADD|ROLL, PLUS|MINUS, 2, -ONE_DAY, +1*ONE_DAY },
        { UCAL_DAY_OF_WEEK, ADD|ROLL, PLUS|MINUS, 2, -ONE_DAY, +1*ONE_DAY },
        { UCAL_DAY_OF_WEEK_IN_MONTH, ADD|ROLL, PLUS|MINUS, 1, -ONE_DAY, +6*ONE_DAY },
        { UCAL_AM_PM, ADD, PLUS|MINUS, 4, -12*ONE_HOUR, +36*ONE_HOUR },
        { UCAL_HOUR, ADD, PLUS|MINUS, 48, -12*ONE_HOUR, +36*ONE_HOUR },
        { UCAL_HOUR_OF_DAY, ADD, PLUS|MINUS, 48, -12*ONE_HOUR, +36*ONE_HOUR },
        { UCAL_MINUTE, ADD, PLUS|MINUS, 48*60, -12*ONE_HOUR, +36*ONE_HOUR },
        { UCAL_SECOND, ADD, PLUS|MINUS, 48*60*60, -12*ONE_HOUR, +36*ONE_HOUR },
        { UCAL_MILLISECOND, ADD, PLUS|MINUS, 48*ONE_HOUR, -12*ONE_HOUR, +36*ONE_HOUR },
        // NOTE: These are not supported yet.  See jitterbug 180.
        // Uncomment these lines when add/roll supported on these fields.
        // { Calendar::YEAR_WOY, ADD|ROLL, 1, -ONE_DAY, +6*ONE_DAY },
        // { Calendar::DOW_LOCAL, ADD|ROLL, 2, -ONE_DAY, +1*ONE_DAY }
    };
    int32_t DATA_length = UPRV_LENGTHOF(DATA);

    // Now run the tests
    for (i=0; i<DATA_length; ++i) {
        for (Action action=ADD; action<=ROLL; action=(Action)(action+1)) {
            if (!(DATA[i].actionMask & action)) {
                continue;
            }
            for (Sign sign=PLUS; sign<=MINUS; sign=(Sign)(sign+1)) {
                if (!(DATA[i].signMask & sign)) {
                    continue;
                }
                status = U_ZERO_ERROR;
                int32_t amount = DATA[i].amount * (sign==MINUS?-1:1);
                UDate date = cutover +
                    (sign==PLUS ? DATA[i].before : DATA[i].after);
                UDate expected = cutover +
                    (sign==PLUS ? DATA[i].after : DATA[i].before);
                cal.setTime(date, status);
                if (U_FAILURE(status)) {
                    errln((UnicodeString)"FAIL: setTime returned error code " + u_errorName(status));
                    continue;
                }
                if (action == ADD) {
                    cal.add(DATA[i].field, amount, status);
                } else {
                    cal.roll(DATA[i].field, amount, status);
                }
                if (U_FAILURE(status)) {
                    errln((UnicodeString)"FAIL: " +
                          (action==ADD?"add ":"roll ") + FIELD_NAME[DATA[i].field] +
                          " returned error code " + u_errorName(status));
                    continue;
                }
                UDate result = cal.getTime(status);
                if (U_FAILURE(status)) {
                    errln((UnicodeString)"FAIL: getTime returned error code " + u_errorName(status));
                    continue;
                }
                if (result == expected) {
                    logln((UnicodeString)"Ok: {" +
                          fmt.format(date, temp.remove()) +
                          "}(" + date/ONE_DAY +
                          (action==ADD?") add ":") roll ") +
                          amount + " " + FIELD_NAME[DATA[i].field] + " -> {" +
                          fmt.format(result, temp2.remove()) +
                          "}(" + result/ONE_DAY + ")");
                } else {
                    errln((UnicodeString)"FAIL: {" +
                          fmt.format(date, temp.remove()) +
                          "}(" + date/ONE_DAY +
                          (action==ADD?") add ":") roll ") +
                          amount + " " + FIELD_NAME[DATA[i].field] + " -> {" +
                          fmt.format(result, temp2.remove()) +
                          "}(" + result/ONE_DAY + "), expect {" +
                          fmt.format(expected, temp3.remove()) +
                          "}(" + expected/ONE_DAY + ")");
                }
            }
        }
    }
}