diff --git a/icu4c/source/i18n/calendar.cpp b/icu4c/source/i18n/calendar.cpp index 4756571df80..eae29c886fc 100644 --- a/icu4c/source/i18n/calendar.cpp +++ b/icu4c/source/i18n/calendar.cpp @@ -1532,7 +1532,7 @@ uint8_t Calendar::julianDayToDayOfWeek(int32_t julian) { // If julian is negative, then julian%7 will be negative, so we adjust // accordingly. We add 1 because Julian day 0 is Monday. - int8_t dayOfWeek = (int8_t) ((julian + 1) % 7); + int8_t dayOfWeek = (int8_t) ((julian + 1LL) % 7); uint8_t result = (uint8_t)(dayOfWeek + ((dayOfWeek < 0) ? (7+UCAL_SUNDAY ) : UCAL_SUNDAY)); return result; @@ -1764,8 +1764,8 @@ void Calendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& statu int32_t max = getActualMaximum(field,status); int32_t gap = max - min + 1; - int32_t value = internalGet(field) + amount; - value = (value - min) % gap; + int64_t value = internalGet(field); + value = (value + amount - min) % gap; if (value < 0) { value += gap; } @@ -1787,7 +1787,7 @@ void Calendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& statu { // Assume min == 0 in calculations below double start = getTimeInMillis(status); - int32_t oldHour = internalGet(field); + int64_t oldHour = internalGet(field); int32_t max = getMaximum(field); int32_t newHour = (oldHour + amount) % (max + 1); if (newHour < 0) { @@ -1804,11 +1804,12 @@ void Calendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& statu // DAY_OF_MONTH if, after updating the MONTH field, it is illegal. // E.g., .roll(MONTH, 1) -> or . { - int32_t max = getActualMaximum(UCAL_MONTH, status); - int32_t mon = (internalGet(UCAL_MONTH) + amount) % (max+1); + int32_t max = getActualMaximum(UCAL_MONTH, status) + 1; + int64_t mon = internalGet(UCAL_MONTH); + mon = (mon + amount) % max; if (mon < 0) { - mon += (max + 1); + mon += max; } set(UCAL_MONTH, mon); @@ -1830,11 +1831,19 @@ void Calendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& statu if (era == 0) { const char * calType = getType(); if ( uprv_strcmp(calType,"gregorian")==0 || uprv_strcmp(calType,"roc")==0 || uprv_strcmp(calType,"coptic")==0 ) { - amount = -amount; + if (uprv_mul32_overflow(amount, -1, &amount)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } era0WithYearsThatGoBackwards = true; } } - int32_t newYear = internalGet(field) + amount; + int32_t newYear; + if (uprv_add32_overflow( + amount, internalGet(field), &newYear)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } if (era > 0 || newYear >= 1) { int32_t maxYear = getActualMaximum(field, status); if (maxYear < 32768) { @@ -1862,7 +1871,12 @@ void Calendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& statu case UCAL_EXTENDED_YEAR: // Rolling the year can involve pinning the DAY_OF_MONTH. - set(field, internalGet(field) + amount); + if (uprv_add32_overflow( + amount, internalGet(field), &amount)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + set(field, amount); pinField(UCAL_MONTH,status); pinField(UCAL_DAY_OF_MONTH,status); return; @@ -1934,7 +1948,7 @@ void Calendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& statu status = U_INTERNAL_PROGRAM_ERROR; return; } - int32_t day_of_month = (internalGet(UCAL_DAY_OF_MONTH) + amount*7 - + int32_t day_of_month = (internalGet(UCAL_DAY_OF_MONTH) + amount*7LL - start) % gap; if (day_of_month < 0) day_of_month += gap; day_of_month += start; @@ -1996,7 +2010,7 @@ void Calendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& statu status = U_INTERNAL_PROGRAM_ERROR; return; } - int32_t day_of_year = (internalGet(UCAL_DAY_OF_YEAR) + amount*7 - + int32_t day_of_year = (internalGet(UCAL_DAY_OF_YEAR) + amount*7LL - start) % gap; if (day_of_year < 0) day_of_year += gap; day_of_year += start; diff --git a/icu4c/source/i18n/chnsecal.cpp b/icu4c/source/i18n/chnsecal.cpp index dad28a9022f..701a776d398 100644 --- a/icu4c/source/i18n/chnsecal.cpp +++ b/icu4c/source/i18n/chnsecal.cpp @@ -285,11 +285,11 @@ int32_t ChineseCalendar::handleGetMonthLength(int32_t extendedYear, int32_t mont *

Compute the ChineseCalendar-specific field IS_LEAP_MONTH. * @stable ICU 2.8 */ -void ChineseCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/) { +void ChineseCalendar::handleComputeFields(int32_t julianDay, UErrorCode & status) { computeChineseFields(julianDay - kEpochStartAsJulianDay, // local days getGregorianYear(), getGregorianMonth(), - true); // set all fields + true, status); // set all fields } /** @@ -364,8 +364,11 @@ int64_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U } int32_t theNewYear = newYear(gyear); int32_t newMoon = newMoonNear(theNewYear + month * 29, true); - - int64_t julianDay = newMoon + kEpochStartAsJulianDay; + int32_t julianDay; + if (uprv_add32_overflow(newMoon, kEpochStartAsJulianDay, &julianDay)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } // Ignore IS_LEAP_MONTH field if useMonth is false int32_t isLeapMonth = useMonth ? internalGet(UCAL_IS_LEAP_MONTH) : 0; @@ -383,15 +386,22 @@ int64_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U // This will modify the MONTH and IS_LEAP_MONTH fields (only) work->computeChineseFields(newMoon, work->getGregorianYear(), - work->getGregorianMonth(), false); + work->getGregorianMonth(), false, status); if (month != work->internalGet(UCAL_MONTH) || isLeapMonth != work->internalGet(UCAL_IS_LEAP_MONTH)) { newMoon = newMoonNear(newMoon + SYNODIC_GAP, true); - julianDay = newMoon + kEpochStartAsJulianDay; + if (uprv_add32_overflow(newMoon, kEpochStartAsJulianDay, &julianDay)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + } + if (uprv_add32_overflow(julianDay, -1, &julianDay)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; } - return julianDay - 1; + return julianDay; } @@ -714,7 +724,10 @@ UBool ChineseCalendar::isLeapMonthBetween(int32_t newMoon1, int32_t newMoon2) co * and IS_LEAP_MONTH fields. */ void ChineseCalendar::computeChineseFields(int32_t days, int32_t gyear, int32_t gmonth, - UBool setAllFields) { + UBool setAllFields, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } // Find the winter solstices before and after the target date. // These define the boundaries of this Chinese year, specifically, // the position of month 11, which always contains the solstice. @@ -774,6 +787,22 @@ void ChineseCalendar::computeChineseFields(int32_t days, int32_t gyear, int32_t } int32_t dayOfMonth = days - thisMoon + 1; + int32_t min_eyear = handleGetLimit(UCAL_EXTENDED_YEAR, UCAL_LIMIT_MINIMUM); + if (extended_year < min_eyear) { + if (!isLenient()) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + extended_year = min_eyear; + } + int32_t max_eyear = handleGetLimit(UCAL_EXTENDED_YEAR, UCAL_LIMIT_MAXIMUM); + if (max_eyear < extended_year) { + if (!isLenient()) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + extended_year = max_eyear; + } internalSet(UCAL_EXTENDED_YEAR, extended_year); // 0->0,60 1->1,1 60->1,60 61->2,1 etc. @@ -893,7 +922,11 @@ int32_t ChineseCalendar::getRelatedYear(UErrorCode &status) const if (U_FAILURE(status)) { return 0; } - return year + kChineseRelatedYearDiff; + if (uprv_add32_overflow(year, kChineseRelatedYearDiff, &year)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + return year; } void ChineseCalendar::setRelatedYear(int32_t year) diff --git a/icu4c/source/i18n/chnsecal.h b/icu4c/source/i18n/chnsecal.h index 1d0e9b0700d..c83247c0ab3 100644 --- a/icu4c/source/i18n/chnsecal.h +++ b/icu4c/source/i18n/chnsecal.h @@ -250,7 +250,7 @@ class U_I18N_API ChineseCalendar : public Calendar { virtual UBool hasNoMajorSolarTerm(int32_t newMoon) const; virtual UBool isLeapMonthBetween(int32_t newMoon1, int32_t newMoon2) const; virtual void computeChineseFields(int32_t days, int32_t gyear, - int32_t gmonth, UBool setAllFields); + int32_t gmonth, UBool setAllFields, UErrorCode& status); virtual int32_t newYear(int32_t gyear) const; virtual void offsetMonth(int32_t newMoon, int32_t dom, int32_t delta, UErrorCode& status); const TimeZone* getChineseCalZoneAstroCalc() const; diff --git a/icu4c/source/i18n/dangical.cpp b/icu4c/source/i18n/dangical.cpp index 020855c88f8..3280b687fd5 100644 --- a/icu4c/source/i18n/dangical.cpp +++ b/icu4c/source/i18n/dangical.cpp @@ -151,7 +151,11 @@ int32_t DangiCalendar::getRelatedYear(UErrorCode &status) const if (U_FAILURE(status)) { return 0; } - return year + kDangiRelatedYearDiff; + if (uprv_add32_overflow(year, kDangiRelatedYearDiff, &year)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + return year; } void DangiCalendar::setRelatedYear(int32_t year) diff --git a/icu4c/source/i18n/gregocal.cpp b/icu4c/source/i18n/gregocal.cpp index d164b529499..e140b5223d5 100644 --- a/icu4c/source/i18n/gregocal.cpp +++ b/icu4c/source/i18n/gregocal.cpp @@ -879,7 +879,10 @@ GregorianCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& s isoDoy -= handleGetYearLength(isoYear - 1); } } - woy += amount; + if (uprv_add32_overflow(woy, amount, &woy)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } // Do fast checks to avoid unnecessary computation: if (woy < 1 || woy > 52) { // Determine the last week of the ISO year. diff --git a/icu4c/source/i18n/hebrwcal.cpp b/icu4c/source/i18n/hebrwcal.cpp index 3a3b3808681..72c5d17a48e 100644 --- a/icu4c/source/i18n/hebrwcal.cpp +++ b/icu4c/source/i18n/hebrwcal.cpp @@ -136,6 +136,10 @@ static const int16_t LEAP_MONTH_START[][3] = { { 383, 384, 385 }, // Elul }; +// There are 235 months in 19 years cycle. +static const int32_t MONTHS_IN_CYCLE = 235; +static const int32_t YEARS_IN_CYCLE = 19; + static icu::CalendarCache *gCache = nullptr; U_CDECL_BEGIN @@ -227,12 +231,23 @@ void HebrewCalendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& // ADAR_1 -- then we have to bump to ADAR_2 aka ADAR. But // if amount is -2 and we land in ADAR_1, then we have to // bump the other way -- down to SHEVAT. - Alan 11/00 - int32_t month = get(UCAL_MONTH, status); + int64_t month = get(UCAL_MONTH, status); int32_t year = get(UCAL_YEAR, status); UBool acrossAdar1; if (amount > 0) { acrossAdar1 = (month < ADAR_1); // started before ADAR_1? month += amount; + // We know there are total 235 months in every 19 years. To speed + // up the iteration, we first fast forward in the multiple of 235 + // months for 19 years before the iteration which check the leap year. + if (month >= MONTHS_IN_CYCLE) { + if (uprv_add32_overflow(year, (month / MONTHS_IN_CYCLE) * YEARS_IN_CYCLE, &year)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + month %= MONTHS_IN_CYCLE; + } + for (;;) { if (acrossAdar1 && month>=ADAR_1 && !isLeapYear(year)) { ++month; @@ -247,6 +262,16 @@ void HebrewCalendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& } else { acrossAdar1 = (month > ADAR_1); // started after ADAR_1? month += amount; + // We know there are total 235 months in every 19 years. To speed + // up the iteration, we first fast forward in the multiple of 235 + // months for 19 years before the iteration which check the leap year. + if (month <= -MONTHS_IN_CYCLE) { + if (uprv_add32_overflow(year, (month / MONTHS_IN_CYCLE) * YEARS_IN_CYCLE, &year)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + month %= MONTHS_IN_CYCLE; + } for (;;) { if (acrossAdar1 && month<=ADAR_1 && !isLeapYear(year)) { --month; @@ -485,7 +510,7 @@ int32_t HebrewCalendar::yearType(int32_t year) const */ UBool HebrewCalendar::isLeapYear(int32_t year) { //return (year * 12 + 17) % 19 >= 12; - int64_t x = (year*12LL + 17) % 19; + int64_t x = (year*12LL + 17) % YEARS_IN_CYCLE; return x >= ((x < 0) ? -7 : 12); } @@ -620,6 +645,23 @@ void HebrewCalendar::handleComputeFields(int32_t julianDay, UErrorCode &status) int dayOfMonth = dayOfYear - (isLeap ? LEAP_MONTH_START[month][type] : MONTH_START[month][type]); internalSet(UCAL_ERA, 0); + // Check out of bound year + int32_t min_year = handleGetLimit(UCAL_EXTENDED_YEAR, UCAL_LIMIT_MINIMUM); + if (year < min_year) { + if (!isLenient()) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + year = min_year; + } + int32_t max_year = handleGetLimit(UCAL_EXTENDED_YEAR, UCAL_LIMIT_MAXIMUM); + if (max_year < year) { + if (!isLenient()) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + year = max_year; + } internalSet(UCAL_YEAR, year); internalSet(UCAL_EXTENDED_YEAR, year); int32_t ordinal_month = month; @@ -667,6 +709,16 @@ int64_t HebrewCalendar::handleComputeMonthStart( // on the year) but since we _always_ number from 0..12, and // the leap year determines whether or not month 5 (Adar 1) // is present, we allow 0..12 in any given year. + + // The month could be in large value, we first roll 235 months to 19 years + // before the while loop. + if (month <= -MONTHS_IN_CYCLE || month >= MONTHS_IN_CYCLE) { + if (uprv_add32_overflow(eyear, (month / MONTHS_IN_CYCLE) * YEARS_IN_CYCLE, &eyear)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + month %= MONTHS_IN_CYCLE; + } while (month < 0) { if (uprv_add32_overflow(eyear, -1, &eyear) || uprv_add32_overflow(month, monthsInYear(eyear), &month)) { diff --git a/icu4c/source/i18n/indiancal.cpp b/icu4c/source/i18n/indiancal.cpp index 0d9b4c1322b..7630aa425d5 100644 --- a/icu4c/source/i18n/indiancal.cpp +++ b/icu4c/source/i18n/indiancal.cpp @@ -316,7 +316,11 @@ int32_t IndianCalendar::getRelatedYear(UErrorCode &status) const if (U_FAILURE(status)) { return 0; } - return year + kIndianRelatedYearDiff; + if (uprv_add32_overflow(year, kIndianRelatedYearDiff, &year)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + return year; } void IndianCalendar::setRelatedYear(int32_t year) diff --git a/icu4c/source/i18n/persncal.cpp b/icu4c/source/i18n/persncal.cpp index 4190d901168..7af3f5f8af3 100644 --- a/icu4c/source/i18n/persncal.cpp +++ b/icu4c/source/i18n/persncal.cpp @@ -265,7 +265,11 @@ int32_t PersianCalendar::getRelatedYear(UErrorCode &status) const if (U_FAILURE(status)) { return 0; } - return year + kPersianRelatedYearDiff; + if (uprv_add32_overflow(year, kPersianRelatedYearDiff, &year)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + return year; } void PersianCalendar::setRelatedYear(int32_t year) diff --git a/icu4c/source/test/intltest/caltest.cpp b/icu4c/source/test/intltest/caltest.cpp index ee6aabcbe64..483d33ef49f 100644 --- a/icu4c/source/test/intltest/caltest.cpp +++ b/icu4c/source/test/intltest/caltest.cpp @@ -198,6 +198,11 @@ void CalendarTest::runIndexedTest( int32_t index, UBool exec, const char* &name, TESTCASE_AUTO(Test22633HebrewOverflow); TESTCASE_AUTO(Test22633AMPMOverflow); TESTCASE_AUTO(Test22633SetGetTimeOverflow); + TESTCASE_AUTO(Test22633Set2FieldsGetTimeOverflow); + TESTCASE_AUTO(Test22633SetAddGetTimeOverflow); + TESTCASE_AUTO(Test22633SetRollGetTimeOverflow); + TESTCASE_AUTO(Test22633AddTwiceGetTimeOverflow); + TESTCASE_AUTO(Test22633RollTwiceGetTimeOverflow); TESTCASE_AUTO(TestAddOverflow); @@ -5720,56 +5725,123 @@ void CalendarTest::Test22633AMPMOverflow() { assertTrue("Should return success", U_SUCCESS(status)); } -void CalendarTest::Test22633SetGetTimeOverflow() { +void CalendarTest::RunTestOnCalendars(void(TestFunc)(Calendar*, UCalendarDateFields)) { UErrorCode status = U_ZERO_ERROR; Locale locale = Locale::getEnglish(); LocalPointer values( Calendar::getKeywordValuesForLocale("calendar", locale, false, status), status); assertTrue("Should return success", U_SUCCESS(status)); - if (U_SUCCESS(status)) { - const char* value = nullptr; - while ((value = values->next(nullptr, status)) != nullptr && U_SUCCESS(status)) { - bool isHebrew = strcmp("hebrew", value) == 0; - locale.setKeywordValue("calendar", value, status); - assertTrue("Should return success", U_SUCCESS(status)); + if (U_FAILURE(status)) { + return; + } + const char* value = nullptr; + while ((value = values->next(nullptr, status)) != nullptr && U_SUCCESS(status)) { + locale.setKeywordValue("calendar", value, status); + assertTrue("Should return success", U_SUCCESS(status)); - LocalPointer cal(Calendar::createInstance(*TimeZone::getGMT(), locale, status), status); - assertTrue("Should return success", U_SUCCESS(status)); - - assertTrue("Should return success", U_SUCCESS(status)); - for (int32_t i = 0; i < UCAL_FIELD_COUNT; i++) { - status = U_ZERO_ERROR; - cal->clear(); - cal->set(static_cast(i), INT32_MAX); - cal->getTime(status); - - status = U_ZERO_ERROR; - cal->clear(); - cal->set(static_cast(i), INT32_MIN); - cal->getTime(status); - - if (!isHebrew) { - for (int32_t j = 0; j < UCAL_FIELD_COUNT; j++) { - status = U_ZERO_ERROR; - cal->clear(); - cal->set(static_cast(i), INT32_MAX); - cal->set(static_cast(j), INT32_MAX); - cal->getTime(status); - - status = U_ZERO_ERROR; - cal->clear(); - cal->set(static_cast(i), INT32_MIN); - cal->set(static_cast(j), INT32_MIN); - cal->getTime(status); - } - } - } - status = U_ZERO_ERROR; + LocalPointer cal(Calendar::createInstance(*TimeZone::getGMT(), locale, status), status); + assertTrue("Should return success", U_SUCCESS(status)); + for (int32_t i = 0; i < UCAL_FIELD_COUNT; i++) { + TestFunc(cal.getAlias(), static_cast(i)); } } } +// This test is designed to work with undefined behavior sanitizer UBSAN to +// ensure we do not have math operation overflow int32_t. +void CalendarTest::Test22633SetGetTimeOverflow() { + RunTestOnCalendars([](Calendar* cal, UCalendarDateFields field) { + auto f = [](Calendar* cal, UCalendarDateFields field, int32_t value) { + UErrorCode status = U_ZERO_ERROR; + cal->clear(); + cal->set(field, value); + cal->getTime(status); + }; + f(cal, field, INT32_MAX); + f(cal, field, INT32_MIN); + }); +} + +void CalendarTest::Test22633Set2FieldsGetTimeOverflow() { + RunTestOnCalendars([](Calendar* cal, UCalendarDateFields field) { + auto f = [](Calendar* cal, UCalendarDateFields field, int32_t value) { + for (int32_t j = 0; j < UCAL_FIELD_COUNT; j++) { + UCalendarDateFields field2 = static_cast(j); + UErrorCode status = U_ZERO_ERROR; + cal->clear(); + cal->set(field, value); + cal->set(field2, value); + cal->getTime(status); + } + }; + f(cal, field, INT32_MAX); + f(cal, field, INT32_MIN); + }); +} + +void CalendarTest::Test22633SetAddGetTimeOverflow() { + RunTestOnCalendars([](Calendar* cal, UCalendarDateFields field) { + auto f = [](Calendar* cal, UCalendarDateFields field, int32_t value) { + UErrorCode status = U_ZERO_ERROR; + cal->clear(); + cal->set(field, value); + cal->add(field, value, status); + status = U_ZERO_ERROR; + cal->getTime(status); + }; + f(cal, field, INT32_MAX); + f(cal, field, INT32_MIN); + }); +} + +void CalendarTest::Test22633AddTwiceGetTimeOverflow() { + RunTestOnCalendars([](Calendar* cal, UCalendarDateFields field) { + auto f = [](Calendar* cal, UCalendarDateFields field, int32_t value) { + UErrorCode status = U_ZERO_ERROR; + cal->clear(); + cal->add(field, value, status); + status = U_ZERO_ERROR; + cal->add(field, value, status); + status = U_ZERO_ERROR; + cal->getTime(status); + }; + f(cal, field, INT32_MAX); + f(cal, field, INT32_MIN); + }); +} + +void CalendarTest::Test22633SetRollGetTimeOverflow() { + RunTestOnCalendars([](Calendar* cal, UCalendarDateFields field) { + auto f = [](Calendar* cal, UCalendarDateFields field, int32_t value) { + UErrorCode status = U_ZERO_ERROR; + cal->clear(); + cal->set(field, value); + cal->roll(field, value, status); + status = U_ZERO_ERROR; + cal->getTime(status); + }; + f(cal, field, INT32_MAX); + f(cal, field, INT32_MIN); + }); +} + +void CalendarTest::Test22633RollTwiceGetTimeOverflow() { + RunTestOnCalendars([](Calendar* cal, UCalendarDateFields field) { + auto f = [](Calendar* cal, UCalendarDateFields field, int32_t value) { + UErrorCode status = U_ZERO_ERROR; + cal->clear(); + cal->roll(field, value, status); + status = U_ZERO_ERROR; + cal->roll(field, value, status); + status = U_ZERO_ERROR; + cal->getTime(status); + }; + f(cal, field, INT32_MAX); + f(cal, field, INT32_MIN); + }); +} + void CalendarTest::TestChineseCalendarComputeMonthStart() { // ICU-22639 UErrorCode status = U_ZERO_ERROR; diff --git a/icu4c/source/test/intltest/caltest.h b/icu4c/source/test/intltest/caltest.h index e1581f95df1..e13ebcef215 100644 --- a/icu4c/source/test/intltest/caltest.h +++ b/icu4c/source/test/intltest/caltest.h @@ -341,6 +341,12 @@ public: // package void Test22633HebrewOverflow(); void Test22633AMPMOverflow(); void Test22633SetGetTimeOverflow(); + void Test22633Set2FieldsGetTimeOverflow(); + void Test22633SetAddGetTimeOverflow(); + void Test22633SetRollGetTimeOverflow(); + void Test22633AddTwiceGetTimeOverflow(); + void Test22633RollTwiceGetTimeOverflow(); + void RunTestOnCalendars(void(TestFunc)(Calendar*, UCalendarDateFields)); void verifyFirstDayOfWeek(const char* locale, UCalendarDaysOfWeek expected); void TestFirstDayOfWeek(); diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/util/HebrewCalendar.java b/icu4j/main/core/src/main/java/com/ibm/icu/util/HebrewCalendar.java index d600a1cb554..c3bf4a5a82c 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/util/HebrewCalendar.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/util/HebrewCalendar.java @@ -267,6 +267,8 @@ public class HebrewCalendar extends Calendar { { 383, 384, 385 }, // Elul }; + private static final int MONTHS_IN_CYCLE = 235; + private static final int YEARS_IN_CYCLE = 19; //------------------------------------------------------------------------- // Data Members... //------------------------------------------------------------------------- @@ -615,7 +617,7 @@ public class HebrewCalendar extends Calendar { if (day == CalendarCache.EMPTY) { // # of months before year - int months = (int)floorDivide((235 * (long)year - 234), 19); + int months = (int)floorDivide((MONTHS_IN_CYCLE * (long)year - (MONTHS_IN_CYCLE-1)), YEARS_IN_CYCLE); long frac = months * MONTH_FRACT + BAHARAD; // Fractional part of day # day = months * 29 + (frac / DAY_PARTS); // Whole # part of calculation @@ -698,7 +700,7 @@ public class HebrewCalendar extends Calendar { @Deprecated public static boolean isLeapYear(int year) { //return (year * 12 + 17) % 19 >= 12; - int x = (year*12 + 17) % 19; + int x = (year*12 + 17) % YEARS_IN_CYCLE; return x >= ((x < 0) ? -7 : 12); } @@ -730,6 +732,10 @@ public class HebrewCalendar extends Calendar { // on the year) but since we _always_ number from 0..12, and // the leap year determines whether or not month 5 (Adar 1) // is present, we allow 0..12 in any given year. + if (month <= -MONTHS_IN_CYCLE || MONTHS_IN_CYCLE <= month) { + extendedYear += (month / MONTHS_IN_CYCLE) * YEARS_IN_CYCLE; + month = month % MONTHS_IN_CYCLE; + } while (month < 0) { month += monthsInYear(--extendedYear); } @@ -807,7 +813,7 @@ public class HebrewCalendar extends Calendar { protected void handleComputeFields(int julianDay) { long d = julianDay - 347997; long m = floorDivide((d * DAY_PARTS), MONTH_PARTS); // Months (approx) - int year = (int)(floorDivide((19 * m + 234), 235) + 1); // Years (approx) + int year = (int)(floorDivide((YEARS_IN_CYCLE * m + (MONTHS_IN_CYCLE-1)), MONTHS_IN_CYCLE) + 1); // Years (approx) long ys = startOfYear(year); // 1st day of year int dayOfYear = (int)(d - ys); @@ -874,6 +880,10 @@ public class HebrewCalendar extends Calendar { // on the year) but since we _always_ number from 0..12, and // the leap year determines whether or not month 5 (Adar 1) // is present, we allow 0..12 in any given year. + if (month <= -MONTHS_IN_CYCLE || MONTHS_IN_CYCLE <= month) { + eyear += (month / MONTHS_IN_CYCLE) * YEARS_IN_CYCLE; + month = month % MONTHS_IN_CYCLE; + } while (month < 0) { month += monthsInYear(--eyear); }