diff --git a/icu4c/source/i18n/gregoimp.cpp b/icu4c/source/i18n/gregoimp.cpp
index e62044b361a..537aa19d8a4 100644
--- a/icu4c/source/i18n/gregoimp.cpp
+++ b/icu4c/source/i18n/gregoimp.cpp
@@ -27,6 +27,11 @@ 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) {
+ return (numerator >= 0) ?
+ numerator / denominator : ((numerator + 1) / denominator) - 1;
+}
+
int32_t ClockMath::floorDivide(double numerator, int32_t denominator,
int32_t& remainder) {
double quotient;
diff --git a/icu4c/source/i18n/gregoimp.h b/icu4c/source/i18n/gregoimp.h
index b30741679df..afaacda0b41 100644
--- a/icu4c/source/i18n/gregoimp.h
+++ b/icu4c/source/i18n/gregoimp.h
@@ -40,6 +40,17 @@ class ClockMath {
*/
static int32_t floorDivide(int32_t numerator, int32_t denominator);
+ /**
+ * Divide two integers, returning the floor of the quotient.
+ * Unlike the built-in division, this is mathematically
+ * well-behaved. E.g., -1/4
=> 0 but
+ * floorDivide(-1,4)
=> -1.
+ * @param numerator the numerator
+ * @param denominator a divisor which must be != 0
+ * @return the floor of the quotient
+ */
+ static int64_t floorDivide(int64_t numerator, int64_t denominator);
+
/**
* Divide two numbers, returning the floor of the quotient.
* Unlike the built-in division, this is mathematically
diff --git a/icu4c/source/i18n/persncal.cpp b/icu4c/source/i18n/persncal.cpp
index f66ac676a4a..f38f779fea9 100644
--- a/icu4c/source/i18n/persncal.cpp
+++ b/icu4c/source/i18n/persncal.cpp
@@ -213,7 +213,7 @@ void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*statu
int32_t year, month, dayOfMonth, dayOfYear;
int32_t daysSinceEpoch = julianDay - PERSIAN_EPOCH;
- year = 1 + ClockMath::floorDivide(33 * daysSinceEpoch + 3, 12053);
+ year = 1 + (int32_t)ClockMath::floorDivide(33 * (int64_t)daysSinceEpoch + 3, (int64_t)12053);
int32_t farvardin1 = 365 * (year - 1) + ClockMath::floorDivide(8 * year + 21, 33);
dayOfYear = (daysSinceEpoch - farvardin1); // 0-based
diff --git a/icu4c/source/test/intltest/calregts.cpp b/icu4c/source/test/intltest/calregts.cpp
index 17fc81425c4..f1eb17bbed3 100644
--- a/icu4c/source/test/intltest/calregts.cpp
+++ b/icu4c/source/test/intltest/calregts.cpp
@@ -12,6 +12,7 @@
#include "calregts.h"
+#include "unicode/calendar.h"
#include "unicode/gregocal.h"
#include "unicode/simpletz.h"
#include "unicode/smpdtfmt.h"
@@ -91,6 +92,7 @@ CalendarRegressionTest::runIndexedTest( int32_t index, UBool exec, const char* &
CASE(49,Test9019);
CASE(50,TestT9452);
CASE(51,TestT11632);
+ CASE(52,TestPersianCalOverflow);
default: name = ""; break;
}
}
@@ -2985,6 +2987,36 @@ void CalendarRegressionTest::TestT11632(void) {
assertEquals("correct datetime displayed for hour value", UnicodeString("1970-01-13T12:00:00"), dstr);
}
}
-}
+}
+
+/**
+ * @bug ticket 13454
+ */
+void CalendarRegressionTest::TestPersianCalOverflow(void) {
+ const char* localeID = "bs_Cyrl@calendar=persian";
+ UErrorCode status = U_ZERO_ERROR;
+ Calendar* cal = Calendar::createInstance(Locale(localeID), status);
+ if(U_FAILURE(status)) {
+ dataerrln("FAIL: Calendar::createInstance for localeID %s: %s", localeID, u_errorName(status));
+ } else {
+ int32_t maxMonth = cal->getMaximum(UCAL_MONTH);
+ int32_t maxDayOfMonth = cal->getMaximum(UCAL_DATE);
+ int32_t jd, month, dayOfMonth;
+ for (jd = 67023580; jd <= 67023584; jd++) { // year 178171, int32_t overflow if jd >= 67023582
+ status = U_ZERO_ERROR;
+ cal->clear();
+ cal->set(UCAL_JULIAN_DAY, jd);
+ month = cal->get(UCAL_MONTH, status);
+ dayOfMonth = cal->get(UCAL_DATE, status);
+ if ( U_FAILURE(status) ) {
+ errln("FAIL: Calendar->get MONTH/DATE for localeID %s, julianDay %d, status %s\n", localeID, jd, u_errorName(status));
+ } else if (month > maxMonth || dayOfMonth > maxDayOfMonth) {
+ errln("FAIL: localeID %s, julianDay %d; maxMonth %d, got month %d; maxDayOfMonth %d, got dayOfMonth %d\n",
+ localeID, jd, maxMonth, month, maxDayOfMonth, dayOfMonth);
+ }
+ }
+ delete cal;
+ }
+}
#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/test/intltest/calregts.h b/icu4c/source/test/intltest/calregts.h
index e576b972631..15d55029093 100644
--- a/icu4c/source/test/intltest/calregts.h
+++ b/icu4c/source/test/intltest/calregts.h
@@ -78,6 +78,7 @@ public:
void Test9019(void);
void TestT9452(void);
void TestT11632(void);
+ void TestPersianCalOverflow(void);
void printdate(GregorianCalendar *cal, const char *string);
void dowTest(UBool lenient) ;
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java
index e0ca7f7ffdd..062ac0bd7a4 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java
@@ -2503,5 +2503,23 @@ public class CalendarRegressionTest extends com.ibm.icu.dev.test.TestFmwk {
assertEquals("Incorrect time for long range milliseconds","Sun Jan 25 21:00:00 PST 1970", cal.getTime().toString());
}
-}
+ @Test
+ public void TestPersianCalOverflow() {
+ String localeID = "bs_Cyrl@calendar=persian";
+ Calendar cal = Calendar.getInstance(new ULocale(localeID));
+ int maxMonth = cal.getMaximum(Calendar.MONTH);
+ int maxDayOfMonth = cal.getMaximum(Calendar.DATE);
+ int jd, month, dayOfMonth;
+ for (jd = 67023580; jd <= 67023584; jd++) { // year 178171, int32_t overflow if jd >= 67023582
+ cal.clear();
+ cal.set(Calendar.JULIAN_DAY, jd);
+ month = cal.get(Calendar.MONTH);
+ dayOfMonth = cal.get(Calendar.DATE);
+ if (month > maxMonth || dayOfMonth > maxDayOfMonth) {
+ errln("Error: localeID " + localeID + ", julianDay " + jd + "; maxMonth " + maxMonth + ", got month " + month +
+ "; maxDayOfMonth " + maxDayOfMonth + ", got dayOfMonth " + dayOfMonth);
+ }
+ }
+ }
+ }
//eof