ICU-13745 fix undefined behavior: GregorianCalendar::setGregorianChange()

- Julian days outside of INT32_MIN..INT32_MAX are normalized
- Add a test case
diff --git a/icu4c/source/i18n/gregocal.cpp b/icu4c/source/i18n/gregocal.cpp
index ee15b52..6b15171 100644
--- a/icu4c/source/i18n/gregocal.cpp
+++ b/icu4c/source/i18n/gregocal.cpp
@@ -324,26 +324,26 @@
     if (U_FAILURE(status)) 
         return;
 
-    fGregorianCutover = date;
-
     // Precompute two internal variables which we use to do the actual
     // cutover computations.  These are the normalized cutover, which is the
     // midnight at or before the cutover, and the cutover year.  The
     // normalized cutover is in pure date milliseconds; it contains no time
     // of day or timezone component, and it used to compare against other
     // pure date values.
-    int32_t cutoverDay = (int32_t)ClockMath::floorDivide(fGregorianCutover, (double)kOneDay);
-    fNormalizedGregorianCutover = cutoverDay * kOneDay;
+    double cutoverDay = ClockMath::floorDivide(date, (double)kOneDay);
 
-    // Handle the rare case of numeric overflow.  If the user specifies a
-    // change of UDate(Long.MIN_VALUE), in order to get a pure Gregorian
-    // calendar, then the epoch day is -106751991168, which when multiplied
-    // by ONE_DAY gives 9223372036794351616 -- the negative value is too
-    // large for 64 bits, and overflows into a positive value.  We correct
-    // this by using the next day, which for all intents is semantically
-    // equivalent.
-    if (cutoverDay < 0 && fNormalizedGregorianCutover > 0) {
-        fNormalizedGregorianCutover = (cutoverDay + 1) * kOneDay;
+    // Handle the rare case of numeric overflow where the user specifies a time
+    // outside of INT32_MIN .. INT32_MAX number of days.
+    
+    if (cutoverDay <= INT32_MIN) {
+        cutoverDay = INT32_MIN;
+        fGregorianCutover = fNormalizedGregorianCutover = cutoverDay * kOneDay;
+    } else if (cutoverDay >= INT32_MAX) {
+        cutoverDay = INT32_MAX;
+        fGregorianCutover = fNormalizedGregorianCutover = cutoverDay * kOneDay;
+    } else {
+        fNormalizedGregorianCutover = cutoverDay * kOneDay;
+        fGregorianCutover = date;
     }
 
     // Normalize the year so BC values are represented as 0 and negative
@@ -360,7 +360,7 @@
     fGregorianCutoverYear = cal->get(UCAL_YEAR, status);
     if (cal->get(UCAL_ERA, status) == BC) 
         fGregorianCutoverYear = 1 - fGregorianCutoverYear;
-    fCutoverJulianDay = cutoverDay;
+    fCutoverJulianDay = (int32_t)cutoverDay;
     delete cal;
 }
 
diff --git a/icu4c/source/test/intltest/calregts.cpp b/icu4c/source/test/intltest/calregts.cpp
index 8806d04..744ca69 100644
--- a/icu4c/source/test/intltest/calregts.cpp
+++ b/icu4c/source/test/intltest/calregts.cpp
@@ -96,6 +96,7 @@
         CASE(52,TestPersianCalOverflow);
         CASE(53,TestIslamicCalOverflow);
         CASE(54,TestWeekOfYear13548);
+        CASE(55,Test13745);
     default: name = ""; break;
     }
 }
@@ -1536,6 +1537,41 @@
     delete cal2;
 }
 
+const UDate MILLIS_IN_DAY = 86400000.0;
+/**
+ * ICU-13745
+ * GregorianCalendar::setGregorianChange() overflow
+ */
+void CalendarRegressionTest::Test13745()
+{
+    UErrorCode status = U_ZERO_ERROR;
+    GregorianCalendar *cal = new GregorianCalendar(status);
+    if(U_FAILURE(status)) {
+      dataerrln("Error creating calendar %s", u_errorName(status));
+      delete cal;
+      return;
+    }
+
+    // this line would overflow before fix 13745
+    cal->setGregorianChange(((double)INT32_MAX+1.0) * MILLIS_IN_DAY, status);
+    if(U_FAILURE(status)) {
+        errln("%s:%d Failure setting INT32_MAX+1 change on calendar: %s\n", __FILE__, __LINE__, u_errorName(status));
+        return;
+    }
+    assertEquals("getGregorianChange()", (double)INT32_MAX * MILLIS_IN_DAY, cal->getGregorianChange());
+
+    // test underflow
+    cal->setGregorianChange(((double)INT32_MIN-1.0) * MILLIS_IN_DAY, status);
+    if(U_FAILURE(status)) {
+        errln("%s:%d Failure setting INT32_MAX-1 change on calendar: %s\n", __FILE__, __LINE__, u_errorName(status));
+        return;
+    }
+    assertEquals("getGregorianChange()", (double)INT32_MIN * MILLIS_IN_DAY, cal->getGregorianChange());
+
+    delete cal;
+}
+
+
 /**
  * @bug 4142933
  * Bug states that ArrayIndexOutOfBoundsException is thrown by GregorianCalendar::roll()
diff --git a/icu4c/source/test/intltest/calregts.h b/icu4c/source/test/intltest/calregts.h
index b4166a0..6fc4bdd 100644
--- a/icu4c/source/test/intltest/calregts.h
+++ b/icu4c/source/test/intltest/calregts.h
@@ -82,6 +82,8 @@
     void TestIslamicCalOverflow(void);
     void TestWeekOfYear13548(void);
 
+    void Test13745(void);
+
     void printdate(GregorianCalendar *cal, const char *string);
     void dowTest(UBool lenient) ;