diff --git a/icu4c/source/i18n/calendar.cpp b/icu4c/source/i18n/calendar.cpp index 3f7b9f5445a5..b8f14760bdda 100644 --- a/icu4c/source/i18n/calendar.cpp +++ b/icu4c/source/i18n/calendar.cpp @@ -1461,9 +1461,15 @@ void Calendar::computeFields(UErrorCode &ec) // 11/6/00 int32_t millisInDay; - int32_t days = ClockMath::floorDivide(localMillis, U_MILLIS_PER_DAY, &millisInDay); + double days = ClockMath::floorDivide( + localMillis, U_MILLIS_PER_DAY, &millisInDay) + + kEpochStartAsJulianDay; + if (days > INT32_MAX || days < INT32_MIN) { + ec = U_ILLEGAL_ARGUMENT_ERROR; + return; + } - internalSet(UCAL_JULIAN_DAY,days + kEpochStartAsJulianDay); + internalSet(UCAL_JULIAN_DAY, static_cast(days)); #if defined (U_DEBUG_CAL) //fprintf(stderr, "%s:%d- Hmm! Jules @ %d, as per %.0lf millis\n", @@ -1579,7 +1585,14 @@ void Calendar::computeGregorianFields(int32_t julianDay, UErrorCode& ec) { return; } int32_t gregorianDayOfWeekUnused; - Grego::dayToFields(julianDay - kEpochStartAsJulianDay, fGregorianYear, fGregorianMonth, fGregorianDayOfMonth, gregorianDayOfWeekUnused, fGregorianDayOfYear); + if (uprv_add32_overflow( + julianDay, -kEpochStartAsJulianDay, &julianDay)) { + ec = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + Grego::dayToFields(julianDay, fGregorianYear, fGregorianMonth, + fGregorianDayOfMonth, gregorianDayOfWeekUnused, + fGregorianDayOfYear); } /** diff --git a/icu4c/source/i18n/cecal.cpp b/icu4c/source/i18n/cecal.cpp index dde08cdf1612..b85fcd2de794 100644 --- a/icu4c/source/i18n/cecal.cpp +++ b/icu4c/source/i18n/cecal.cpp @@ -93,7 +93,7 @@ CECalendar::handleComputeMonthStart(int32_t eyear,int32_t emonth, UBool /*useMon return ( getJDEpochOffset() // difference from Julian epoch to 1,1,1 + 365LL * year64 // number of days from years - + ClockMath::floorDivide(year64, 4LL) // extra day of leap year + + ClockMath::floorDivideInt64(year64, 4LL) // extra day of leap year + 30 * emonth // number of days from months (months are 0-based) - 1 // number of days for present month (1 based) ); diff --git a/icu4c/source/i18n/gregocal.cpp b/icu4c/source/i18n/gregocal.cpp index 44792c726826..6b80e7ed42b9 100644 --- a/icu4c/source/i18n/gregocal.cpp +++ b/icu4c/source/i18n/gregocal.cpp @@ -550,7 +550,8 @@ int64_t GregorianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool isLeap = eyear%4 == 0; int64_t y = (int64_t)eyear-1; - int64_t julianDay = 365*y + ClockMath::floorDivide(y, (int64_t)4) + (kJan1_1JulianDay - 3); + int64_t julianDay = 365LL * y + + ClockMath::floorDivideInt64(y, 4LL) + kJan1_1JulianDay - 3LL; nonConstThis->fIsGregorian = (eyear >= fGregorianCutoverYear); #if defined (U_DEBUG_CAL) diff --git a/icu4c/source/i18n/gregoimp.cpp b/icu4c/source/i18n/gregoimp.cpp index a5fa71052927..57499bd30857 100644 --- a/icu4c/source/i18n/gregoimp.cpp +++ b/icu4c/source/i18n/gregoimp.cpp @@ -27,7 +27,7 @@ int32_t ClockMath::floorDivide(int32_t numerator, int32_t denominator) { numerator / denominator : ((numerator + 1) / denominator) - 1; } -int64_t ClockMath::floorDivide(int64_t numerator, int64_t denominator) { +int64_t ClockMath::floorDivideInt64(int64_t numerator, int64_t denominator) { return (numerator >= 0) ? numerator / denominator : ((numerator + 1) / denominator) - 1; } @@ -108,8 +108,10 @@ int64_t Grego::fieldsToDay(int32_t year, int32_t month, int32_t dom) { int64_t y = year - 1; - int64_t julian = 365LL * y + ClockMath::floorDivide(y, 4LL) + (JULIAN_1_CE - 3) + // Julian cal - ClockMath::floorDivide(y, 400LL) - ClockMath::floorDivide(y, 100LL) + 2 + // => Gregorian cal + int64_t julian = 365LL * y + + ClockMath::floorDivideInt64(y, 4LL) + (JULIAN_1_CE - 3) + // Julian cal + ClockMath::floorDivideInt64(y, 400LL) - + ClockMath::floorDivideInt64(y, 100LL) + 2 + // => Gregorian cal DAYS_BEFORE[month + (isLeapYear(year) ? 12 : 0)] + dom; // => month/dom return julian - JULIAN_1970_CE; // JD => epoch day diff --git a/icu4c/source/i18n/gregoimp.h b/icu4c/source/i18n/gregoimp.h index 8a508164560d..a3e237d7eaf6 100644 --- a/icu4c/source/i18n/gregoimp.h +++ b/icu4c/source/i18n/gregoimp.h @@ -49,7 +49,7 @@ class ClockMath { * @param denominator a divisor which must be != 0 * @return the floor of the quotient */ - static int64_t floorDivide(int64_t numerator, int64_t denominator); + static int64_t floorDivideInt64(int64_t numerator, int64_t denominator); /** * Divide two numbers, returning the floor of the quotient. @@ -309,7 +309,7 @@ inline void Grego::dayToFields(int32_t day, int32_t& year, int32_t& month, inline double Grego::julianDayToMillis(int32_t julian) { - return (julian - kEpochStartAsJulianDay) * kOneDay; + return (static_cast(julian) - kEpochStartAsJulianDay) * kOneDay; } inline int32_t Grego::millisToJulianDay(double millis) { @@ -318,8 +318,8 @@ inline int32_t Grego::millisToJulianDay(double millis) { inline int32_t Grego::gregorianShift(int32_t eyear) { int64_t y = (int64_t)eyear-1; - int32_t gregShift = static_cast(ClockMath::floorDivide(y, (int64_t)400) - ClockMath::floorDivide(y, (int64_t)100) + 2); - return gregShift; + int64_t gregShift = ClockMath::floorDivideInt64(y, 400LL) - ClockMath::floorDivideInt64(y, 100LL) + 2; + return static_cast(gregShift); } U_NAMESPACE_END diff --git a/icu4c/source/i18n/hebrwcal.cpp b/icu4c/source/i18n/hebrwcal.cpp index 147d3f5b6f1d..88ecb827a133 100644 --- a/icu4c/source/i18n/hebrwcal.cpp +++ b/icu4c/source/i18n/hebrwcal.cpp @@ -398,7 +398,8 @@ int32_t HebrewCalendar::startOfYear(int32_t year, UErrorCode &status) if (day == 0) { // # of months before year - int64_t months = ClockMath::floorDivide((235 * (int64_t)year - 234), (int64_t)19); + int64_t months = ClockMath::floorDivideInt64( + (235LL * (int64_t)year - 234LL), 19LL); int64_t frac = months * MONTH_FRACT + BAHARAD; // Fractional part of day # day = months * 29LL + frac / DAY_PARTS; // Whole # part of calculation diff --git a/icu4c/source/i18n/islamcal.cpp b/icu4c/source/i18n/islamcal.cpp index 88fc88f0fe2b..cba49848a3fb 100644 --- a/icu4c/source/i18n/islamcal.cpp +++ b/icu4c/source/i18n/islamcal.cpp @@ -346,7 +346,7 @@ int64_t IslamicCalendar::monthStart(int32_t year, int32_t month) const { int32_t IslamicCalendar::trueMonthStart(int32_t month) const { UErrorCode status = U_ZERO_ERROR; - int32_t start = CalendarCache::get(&gMonthCache, month, status); + int64_t start = CalendarCache::get(&gMonthCache, month, status); if (start==0) { // Make a guess at when the month started, using the average length @@ -379,8 +379,8 @@ int32_t IslamicCalendar::trueMonthStart(int32_t month) const } } while (age < 0); } - start = (int32_t)(ClockMath::floorDivide( - (int64_t)((int64_t)origin - HIJRA_MILLIS), (int64_t)kOneDay) + 1); + start = ClockMath::floorDivideInt64( + (int64_t)((int64_t)origin - HIJRA_MILLIS), (int64_t)kOneDay) + 1; CalendarCache::put(&gMonthCache, month, start, status); } trueMonthStartEnd : @@ -694,9 +694,7 @@ IslamicCivilCalendar* IslamicCivilCalendar::clone() const { * from the Hijri epoch, origin 0. */ int64_t IslamicCivilCalendar::yearStart(int32_t year) const{ - return static_cast( - (year-1)*354LL + ClockMath::floorDivide((3+11*static_cast(year)), - static_cast(30))); + return 354LL * (year-1) + ClockMath::floorDivideInt64(3 + 11LL * year, 30LL); } /** @@ -709,10 +707,9 @@ int64_t IslamicCivilCalendar::yearStart(int32_t year) const{ int64_t IslamicCivilCalendar::monthStart(int32_t year, int32_t month) const { // This does not handle months out of the range 0..11 return static_cast( - uprv_ceil(29.5*month) + (year-1)*354LL + - static_cast(ClockMath::floorDivide( - 3+11*static_cast(year), - static_cast(30)))); + uprv_ceil(29.5*month) + 354LL*(year-1) + + ClockMath::floorDivideInt64( + 11LL*static_cast(year) + 3LL, 30LL)); } /** @@ -759,9 +756,8 @@ void IslamicCivilCalendar::handleComputeFields(int32_t julianDay, UErrorCode &st int32_t days = julianDay - getEpoc(); // Use the civil calendar approximation, which is just arithmetic - int32_t year = static_cast( - ClockMath::floorDivide(30 * static_cast(days) + 10646, - static_cast(10631))); + int64_t year = + ClockMath::floorDivideInt64(30LL * days + 10646LL, 10631LL); int32_t month = static_cast( uprv_ceil((days - 29 - yearStart(year)) / 29.5 )); month = month<11?month:11; @@ -837,9 +833,8 @@ IslamicUmalquraCalendar* IslamicUmalquraCalendar::clone() const { */ int64_t IslamicUmalquraCalendar::yearStart(int32_t year) const { if (year < UMALQURA_YEAR_START || year > UMALQURA_YEAR_END) { - return static_cast( - (year-1)*354LL + ClockMath::floorDivide((3+11*static_cast(year)), - static_cast(30))); + return 354LL * (year-1) + + ClockMath::floorDivideInt64((11LL*year+3LL), 30LL); } year -= UMALQURA_YEAR_START; // rounded least-squares fit of the dates previously calculated from UMALQURA_MONTHLENGTH iteration @@ -915,15 +910,16 @@ int32_t IslamicUmalquraCalendar::handleGetYearLength(int32_t extendedYear) const */ void IslamicUmalquraCalendar::handleComputeFields(int32_t julianDay, UErrorCode &status) { if (U_FAILURE(status)) return; - int32_t year, month, dayOfYear; + int64_t year; + int32_t month, dayOfYear; int64_t dayOfMonth; int32_t days = julianDay - getEpoc(); int64_t umalquraStartdays = yearStart(UMALQURA_YEAR_START) ; if (days < umalquraStartdays) { //Use Civil calculation - year = (int32_t)ClockMath::floorDivide( - (30 * (int64_t)days + 10646) , (int64_t)10631.0 ); + year = ClockMath::floorDivideInt64( + (30LL * days + 10646LL) , 10631LL ); month = (int32_t)uprv_ceil((days - 29 - yearStart(year)) / 29.5 ); month = month < 11 ? month : 11; } else { diff --git a/icu4c/source/i18n/persncal.cpp b/icu4c/source/i18n/persncal.cpp index b2378ad68f40..8aac2352cbd3 100644 --- a/icu4c/source/i18n/persncal.cpp +++ b/icu4c/source/i18n/persncal.cpp @@ -21,6 +21,7 @@ #if !UCONFIG_NO_FORMATTING +#include "uassert.h" #include "umutex.h" #include "gregoimp.h" // Math #include @@ -173,7 +174,7 @@ int64_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U eyear += ClockMath::floorDivide(month, 12, &month); } - int64_t julianDay = PERSIAN_EPOCH - 1 + 365LL * (eyear - 1) + ClockMath::floorDivide(8 * eyear + 21, 33); + int64_t julianDay = PERSIAN_EPOCH - 1 + 365LL * (eyear - 1) + ClockMath::floorDivide(8LL * eyear + 21, 33); if (month != 0) { julianDay += kPersianNumDays[month]; @@ -210,20 +211,33 @@ int32_t PersianCalendar::handleGetExtendedYear() { * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this * method is called. */ -void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/) { - int32_t year, month, dayOfMonth, dayOfYear; - - int32_t daysSinceEpoch = julianDay - PERSIAN_EPOCH; - year = 1 + (int32_t)ClockMath::floorDivide(33 * (int64_t)daysSinceEpoch + 3, (int64_t)12053); +void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) { + int64_t daysSinceEpoch = julianDay - PERSIAN_EPOCH; + int64_t year = ClockMath::floorDivideInt64( + 33LL * daysSinceEpoch + 3LL, 12053LL) + 1LL; + if (year > INT32_MAX || year < INT32_MIN) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } - int32_t farvardin1 = 365 * (year - 1) + ClockMath::floorDivide(8 * year + 21, 33); - dayOfYear = (daysSinceEpoch - farvardin1); // 0-based + int64_t farvardin1 = 365LL * (year - 1) + ClockMath::floorDivide(8LL * year + 21, 33); + int32_t dayOfYear = daysSinceEpoch - farvardin1; // 0-based + U_ASSERT(dayOfYear >= 0); + U_ASSERT(dayOfYear < 366); + // + int32_t month; if (dayOfYear < 216) { // Compute 0-based month month = dayOfYear / 31; } else { month = (dayOfYear - 6) / 30; } - dayOfMonth = dayOfYear - kPersianNumDays[month] + 1; + U_ASSERT(month >= 0); + U_ASSERT(month < 12); + + int32_t dayOfMonth = dayOfYear - kPersianNumDays[month] + 1; + U_ASSERT(dayOfMonth > 0); + U_ASSERT(dayOfMonth <= 31); + ++dayOfYear; // Make it 1-based now internalSet(UCAL_ERA, 0); diff --git a/icu4c/source/test/intltest/caltest.cpp b/icu4c/source/test/intltest/caltest.cpp index b777f381896d..13aa985f88be 100644 --- a/icu4c/source/test/intltest/caltest.cpp +++ b/icu4c/source/test/intltest/caltest.cpp @@ -5670,6 +5670,12 @@ void CalendarTest::Test22633PersianOverflow() { U_ASSERT(U_SUCCESS(status)); cal->add(UCAL_ORDINAL_MONTH, 1594095615, status); assertTrue("Should return success", U_SUCCESS(status)); + + cal->clear(); + cal->fieldDifference( + -874417153152678003697180890506448687181167523704194267774844295805672585701302166100950793070884718009504322601688549650298776623158701367393457997817732662883592665106020013730689242515513560464852918376875667091108609655859551000798163265126400.000000, + UCAL_YEAR, status); + assertFalse("Should not return success", U_SUCCESS(status)); } void CalendarTest::TestChineseCalendarComputeMonthStart() { // ICU-22639