ICU-22633 Add more Calendar overflow tests

Test set+set, set+add, set+roll, add+add, roll+roll
Fix more int32_t overflow problems.

Optimize both Java and C++ Hebrew Calendar month/year advancement by first consider
every 235 months is 19 years before iteration.
This commit is contained in:
Frank Tang 2024-03-12 22:46:28 -07:00 committed by Frank Yung-Fong Tang
parent 2a1853c9a9
commit 9a912bb51b
11 changed files with 272 additions and 70 deletions

View file

@ -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., <jan31>.roll(MONTH, 1) -> <feb28> or <feb29>.
{
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;

View file

@ -285,11 +285,11 @@ int32_t ChineseCalendar::handleGetMonthLength(int32_t extendedYear, int32_t mont
* <p>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)

View file

@ -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;

View file

@ -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)

View file

@ -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.

View file

@ -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)) {

View file

@ -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)

View file

@ -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)

View file

@ -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<StringEnumeration> 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<Calendar> 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<UCalendarDateFields>(i), INT32_MAX);
cal->getTime(status);
status = U_ZERO_ERROR;
cal->clear();
cal->set(static_cast<UCalendarDateFields>(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<UCalendarDateFields>(i), INT32_MAX);
cal->set(static_cast<UCalendarDateFields>(j), INT32_MAX);
cal->getTime(status);
status = U_ZERO_ERROR;
cal->clear();
cal->set(static_cast<UCalendarDateFields>(i), INT32_MIN);
cal->set(static_cast<UCalendarDateFields>(j), INT32_MIN);
cal->getTime(status);
}
}
}
status = U_ZERO_ERROR;
LocalPointer<Calendar> 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<UCalendarDateFields>(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<UCalendarDateFields>(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;

View file

@ -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();

View file

@ -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);
}