ICU-21283 Fix Java for calendar bugs

This is the java port of ICU-21043 (for C++)
This PR fixes
ICU-21043 Erroneous date display in indian calendar of all dates prior to 0001-01-01.
ICU-21044 Hebrew Calendar calculation is incorrect when the year < 1
ICU-21045 Erroneous date display in islamic and islamic-rgsa calendars of all dates prior to 0622-07-18.
ICU-21046 Erroneous date display in islamic-umalqura calendar of all dates prior to -195366-07-23.

The problem in the IndianCalendarl is
ICU-21043 the gregorian/julain convesion is wrong. Swith to use the
calculation function in the Calendar class.

The problem in the HebrewCalendar is
ICU-21044 the use of bulit in / is wrong when the year or month could be < 1.

The problem in the IslamicCalendar is

ICU-21045: The math of % negative number for year and month is wrong.
Also add tests to exhaust test 8000 years for all calendar. In quick
mode, only test 2.5 years.

reduce the number of date in quick mode
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/IndianCalendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/IndianCalendar.java
index e33ef90..23e5be9 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/util/IndianCalendar.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/util/IndianCalendar.java
@@ -341,7 +341,7 @@
             month = remainder[0];
         }
 
-        if(isGregorianLeap(extendedYear + INDIAN_ERA_START) && month == 0) {
+        if(isGregorianLeapYear(extendedYear + INDIAN_ERA_START) && month == 0) {
             return 31;
         }
 
@@ -359,20 +359,20 @@
     protected void handleComputeFields(int julianDay){
         double jdAtStartOfGregYear;
         int leapMonth, IndianYear, yday, IndianMonth, IndianDayOfMonth, mday;
-        int[] gregorianDay;          // Stores gregorian date corresponding to Julian day;
+        computeGregorianFields(julianDay);
+        int gregorianYear = getGregorianYear(); // Stores gregorian date corresponding to Julian day;
+        IndianYear = gregorianYear - INDIAN_ERA_START;            // Year in Saka era
 
-        gregorianDay = jdToGregorian(julianDay);                    // Gregorian date for Julian day
-        IndianYear = gregorianDay[0] - INDIAN_ERA_START;            // Year in Saka era
-        jdAtStartOfGregYear = gregorianToJD(gregorianDay[0], 1, 1); // JD at start of Gregorian year
+        jdAtStartOfGregYear = gregorianToJD(gregorianYear, 0 /* first month in 0 base */, 1); // JD at start of Gregorian year
         yday = (int)(julianDay - jdAtStartOfGregYear);              // Day number in Gregorian year (starting from 0)
 
         if (yday < INDIAN_YEAR_START) {
             //  Day is at the end of the preceding Saka year
             IndianYear -= 1;
-            leapMonth = isGregorianLeap(gregorianDay[0] - 1) ? 31 : 30; // Days in leapMonth this year, previous Gregorian year
+            leapMonth = isGregorianLeapYear(gregorianYear - 1) ? 31 : 30; // Days in leapMonth this year, previous Gregorian year
             yday += leapMonth + (31 * 5) + (30 * 3) + 10;
         } else {
-            leapMonth = isGregorianLeap(gregorianDay[0]) ? 31 : 30; // Days in leapMonth this year
+            leapMonth = isGregorianLeapYear(gregorianYear) ? 31 : 30; // Days in leapMonth this year
             yday -= INDIAN_YEAR_START;
         }
 
@@ -465,19 +465,19 @@
      * @param month  The month according to Indian calendar (between 1 to 12)
      * @param date   The date in month 
      */
-    private static double IndianToJD(int year, int month, int date) {
+    private double IndianToJD(int year, int month, int date) {
        int leapMonth, gyear, m;
        double start, jd;
 
        gyear = year + INDIAN_ERA_START;
 
 
-       if(isGregorianLeap(gyear)) {
+       if(isGregorianLeapYear(gyear)) {
           leapMonth = 31;
-          start = gregorianToJD(gyear, 3, 21);
+          start = gregorianToJD(gyear, 2 /* third month in 0 based */, 21);
        } else {
           leapMonth = 30;
-          start = gregorianToJD(gyear, 3, 22);
+          start = gregorianToJD(gyear, 2 /* third month in 0 based */, 22);
        }
 
        if (month == 1) {
@@ -504,74 +504,10 @@
      * @param month  The month according to Gregorian calendar (between 0 to 11)
      * @param date   The date in month 
      */
-    private static double gregorianToJD(int year, int month, int date) {
-       double JULIAN_EPOCH = 1721425.5;
-       int y = year - 1;
-       int result = (365 * y)
-                  + (y / 4)
-                  - (y / 100)
-                  + (y / 400)
-                  + (((367 * month) - 362) / 12)
-                  + ((month <= 2) ? 0 : (isGregorianLeap(year) ? -1 : -2))
-                  + date;
-       return result - 1 + JULIAN_EPOCH;
-    }
-    
-    /*
-     * The following function is not needed for basic calendar functioning.
-     * This routine converts a julian day (jd) to the corresponding date in Gregorian calendar"
-     * @param jd The Julian date in Julian Calendar which is to be converted to Indian date"
-     */
-    private static int[] jdToGregorian(double jd) {
-       double JULIAN_EPOCH = 1721425.5;
-       double wjd, depoch, quadricent, dqc, cent, dcent, quad, dquad, yindex, yearday, leapadj;
-       int year, month, day;
-       
-       wjd = Math.floor(jd - 0.5) + 0.5;
-       depoch = wjd - JULIAN_EPOCH;
-       quadricent = Math.floor(depoch / 146097);
-       dqc = depoch % 146097;
-       cent = Math.floor(dqc / 36524);
-       dcent = dqc % 36524;
-       quad = Math.floor(dcent / 1461);
-       dquad = dcent % 1461;
-       yindex = Math.floor(dquad / 365);
-       year = (int)((quadricent * 400) + (cent * 100) + (quad * 4) + yindex);
-       
-       if (!((cent == 4) || (yindex == 4))) {
-          year++;
-       }
-       
-       yearday = wjd - gregorianToJD(year, 1, 1);
-       leapadj = ((wjd < gregorianToJD(year, 3, 1)) ? 0
-             :
-             (isGregorianLeap(year) ? 1 : 2)
-             );
-       
-       month = (int)Math.floor((((yearday + leapadj) * 12) + 373) / 367);
-       day = (int)(wjd - gregorianToJD(year, month, 1)) + 1;
-
-       int[] julianDate = new int[3];
-       
-       julianDate[0] = year;
-       julianDate[1] = month;
-       julianDate[2] = day;
-       
-       return julianDate;
-    }
-    
-    /*
-     * The following function is not needed for basic calendar functioning.
-     * This routine checks if the Gregorian year is a leap year"
-     * @param year      The year in Gregorian Calendar
-     */
-    private static boolean isGregorianLeap(int year)
-    {
-       return ((year % 4) == 0) &&
-          (!(((year % 100) == 0) && ((year % 400) != 0)));
+    private double gregorianToJD(int year, int month, int date) {
+       return computeGregorianMonthStart(year, month) + date - 0.5;
     }
 
-    
     /**
      * {@inheritDoc}
      * @stable ICU 3.8
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/IslamicCalendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/IslamicCalendar.java
index ada0b59..e48e799 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/util/IslamicCalendar.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/util/IslamicCalendar.java
@@ -872,8 +872,8 @@
                 months--;
             }
 
-            year = months / 12 + 1;
-            month = months % 12;
+            year = months >=  0 ? ((months / 12) + 1) : ((months + 1 ) / 12);
+            month = ((months % 12) + 12 ) % 12;
         } else if (cType == CalculationType.ISLAMIC_UMALQURA) {
             long umalquraStartdays = yearStart(UMALQURA_YEAR_START);
             if( days < umalquraStartdays) {
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/IBMCalendarTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/IBMCalendarTest.java
index f547982..0e58f9d 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/IBMCalendarTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/calendar/IBMCalendarTest.java
@@ -28,6 +28,9 @@
 import com.ibm.icu.util.Calendar;
 import com.ibm.icu.util.ChineseCalendar;
 import com.ibm.icu.util.GregorianCalendar;
+import com.ibm.icu.util.HebrewCalendar;
+import com.ibm.icu.util.IndianCalendar;
+import com.ibm.icu.util.IslamicCalendar;
 import com.ibm.icu.util.JapaneseCalendar;
 import com.ibm.icu.util.TaiwanCalendar;
 import com.ibm.icu.util.TimeZone;
@@ -2013,4 +2016,221 @@
         StubSimpleDateFormat stub = new StubSimpleDateFormat("EEE MMM dd yyyy G HH:mm:ss.SSS", Locale.US);
         stub.run();
     }
+
+    @Test
+    public void TestConsistencyGregorian() {
+        checkConsistency("en@calendar=gregorian");
+    }
+
+    @Test
+    public void TestConsistencyIndian() {
+        checkConsistency("en@calendar=indian");
+    }
+
+    @Test
+    public void TestConsistencyHebrew() {
+        checkConsistency("en@calendar=hebrew");
+    }
+
+    @Test
+    public void TestConsistencyIslamic() {
+        checkConsistency("en@calendar=islamic");
+    }
+
+    @Test
+    public void TestConsistencyIslamicRGSA() {
+        checkConsistency("en@calendar=islamic-rgsa");
+    }
+
+    @Test
+    public void TestConsistencyIslamicTBLA() {
+        checkConsistency("en@calendar=islamic-tbla");
+    }
+
+    @Test
+    public void TestConsistencyIslamicUmalqura() {
+        checkConsistency("en@calendar=islamic-umalqura");
+    }
+
+    @Test
+    public void TestConsistencyIslamicCivil() {
+        checkConsistency("en@calendar=islamic-civil");
+    }
+
+    @Test
+    public void TestConsistencyCoptic() {
+        checkConsistency("en@calendar=coptic");
+    }
+
+    @Test
+    public void TestConsistencyEthiopic() {
+        checkConsistency("en@calendar=ethiopic");
+    }
+
+    @Test
+    public void TestConsistencyROC() {
+        checkConsistency("en@calendar=roc");
+    }
+
+    @Test
+    public void TestConsistencyChinese() {
+        checkConsistency("en@calendar=chinese");
+    }
+
+    @Test
+    public void TestConsistencyDangi() {
+        checkConsistency("en@calendar=dangi");
+    }
+
+    @Test
+    public void TestConsistencyPersian() {
+        checkConsistency("en@calendar=persian");
+    }
+
+    @Test
+    public void TestConsistencyBuddhist() {
+        checkConsistency("en@calendar=buddhist");
+    }
+
+    @Test
+    public void TestConsistencyJapanese() {
+        checkConsistency("en@calendar=japanese");
+    }
+
+    @Test
+    public void TestConsistencyEthiopicAmeteAlem() {
+        checkConsistency("en@calendar=ethiopic-amete-alem");
+    }
+
+    public void checkConsistency(String locale) {
+        boolean quick = getExhaustiveness() <= 5;
+        // Check 3 years in quick mode and 8000 years in exhaustive mode.
+        int numOfDaysToTest = (quick ? 3 * 365 : 8000 * 365);
+        int msInADay = 1000*60*60*24;
+
+        // g is just for debugging messages.
+        Calendar g = new GregorianCalendar(TimeZone.GMT_ZONE, ULocale.ENGLISH);
+        Calendar base = Calendar.getInstance(TimeZone.GMT_ZONE, new ULocale(locale));
+        Date test = Calendar.getInstance().getTime();
+
+        Calendar r = (Calendar)base.clone();
+        int lastDay = 1;
+        for (int j = 0; j < numOfDaysToTest; j++, test.setTime(test.getTime() - msInADay)) {
+            g.setTime(test);
+            base.clear();
+            base.setTime(test);
+            // First, we verify the date from base is decrease one day from the
+            // last day unless the last day is 1.
+            int cday = base.get(Calendar.DAY_OF_MONTH);
+            if (lastDay == 1) {
+                lastDay = cday;
+            } else {
+                if (cday != lastDay-1) {
+                    // Ignore if it is the last day before Gregorian Calendar switch on
+                    // 1582 Oct 4
+                    if (    g.get(Calendar.YEAR) == 1582 && (g.get(Calendar.MONTH) + 1) == 10 &&
+                            g.get(Calendar.DAY_OF_MONTH) == 4) {
+                        lastDay = 5;
+                    } else {
+                        errln("Day is not one less from previous date for Gregorian(e=" +
+                            g.get(Calendar.ERA) + " " + g.get(Calendar.YEAR) + "/" +
+                            (g.get(Calendar.MONTH) + 1) + "/" + g.get(Calendar.DAY_OF_MONTH) +
+                            ") " + locale + "(" +
+                            base.get(Calendar.ERA) + " " + base.get(Calendar.YEAR) + "/" +
+                            (base.get(Calendar.MONTH) + 1 ) + "/" + base.get(Calendar.DAY_OF_MONTH) +
+                            ")");
+                    }
+                }
+                lastDay--;
+            }
+            // Second, we verify the month is in reasonale range.
+            int cmonth = base.get(Calendar.MONTH);
+            if (cmonth < 0 || cmonth > 13) {
+                errln("Month is out of range Gregorian(e=" + g.get(Calendar.ERA) + " " +
+                    g.get(Calendar.YEAR) + "/" + (g.get(Calendar.MONTH) + 1) + "/" +
+                    g.get(Calendar.DAY_OF_MONTH) + ") " + locale + "(" + base.get(Calendar.ERA) +
+                    " " + base.get(Calendar.YEAR) + "/" + (base.get(Calendar.MONTH) + 1 ) + "/" +
+                    base.get(Calendar.DAY_OF_MONTH) + ")");
+            }
+            // Third, we verify the set function can round trip the time back.
+            r.clear();
+            for (int f = 0; f < base.getFieldCount(); f++) {
+                r.set(f, base.get(f));
+            }
+            Date result = r.getTime();
+            if (!test.equals(result)) {
+                errln("Round trip conversion produces different time from " + test + " to  " +
+                    result + " delta: " + (result.getTime() - test.getTime()) +
+                    " Gregorian(e=" + g.get(Calendar.ERA) + " " + g.get(Calendar.YEAR) + "/" +
+                    (g.get(Calendar.MONTH) + 1) + "/" + g.get(Calendar.DAY_OF_MONTH) + ") ");
+            }
+        }
+    }
+
+    @Test
+    public void TestBug21043Indian() {
+        Calendar cal = new IndianCalendar(ULocale.ENGLISH);
+        Calendar g = new GregorianCalendar(ULocale.ENGLISH);
+        // set to 10 BC
+        g.set(Calendar.ERA, GregorianCalendar.BC);
+        g.set(10, 1, 1);
+        cal.setTime(g.getTime());
+        int m = cal.get(Calendar.MONTH);
+        if (m < 0 || m > 11) {
+            errln("Month (" + m + ") should be between 0 and 11 in India calendar");
+        }
+    }
+
+    @Test
+    public void TestBug21044Hebrew() {
+        Calendar cal = new HebrewCalendar(ULocale.ENGLISH);
+        Calendar g = new GregorianCalendar(ULocale.ENGLISH);
+        // set to 3771/10/27 BC which is before 3760 BC.
+        g.set(Calendar.ERA, GregorianCalendar.BC);
+        g.set(3771, 9, 27);
+        cal.setTime(g.getTime());
+        int y = cal.get(Calendar.YEAR);
+        int m = cal.get(Calendar.MONTH);
+        int d = cal.get(Calendar.DATE);
+        if (y > 0 || m < 0 || m > 12 || d < 0 || d > 32) {
+            errln("Out of rage!\nYear " +  y + " should be " +
+              "negative number before 1AD.\nMonth " + m + " should " +
+              "be between 0 and 12 in Hebrew calendar.\nDate " + d +
+              " should be between 0 and 32 in Islamic calendar.");
+        }
+    }
+
+    @Test
+    public void TestBug21045Islamic() {
+        Calendar cal = new IslamicCalendar(ULocale.ENGLISH);
+        Calendar g = new GregorianCalendar(ULocale.ENGLISH);
+        // set to 500 AD before 622 AD.
+        g.set(Calendar.ERA, GregorianCalendar.AD);
+        g.set(500, 1, 1);
+        cal.setTime(g.getTime());
+        int m = cal.get(Calendar.MONTH);
+        if (m < 0 || m > 11) {
+            errln("Month (" + m + ") should be between 0 and 11 in Islamic calendar");
+        }
+    }
+
+    @Test
+    public void TestBug21046IslamicUmalqura() {
+        IslamicCalendar cal = new IslamicCalendar(ULocale.ENGLISH);
+        cal.setCalculationType(IslamicCalendar.CalculationType.ISLAMIC_UMALQURA);
+        Calendar g = new GregorianCalendar(ULocale.ENGLISH);
+        // set to 195366 BC
+        g.set(Calendar.ERA, GregorianCalendar.BC);
+        g.set(195366, 1, 1);
+        cal.setTime(g.getTime());
+        int y = cal.get(Calendar.YEAR);
+        int m = cal.get(Calendar.MONTH);
+        int d = cal.get(Calendar.DATE);
+        if (y > 0 || m < 0 || m > 11 || d < 0 || d > 32) {
+            errln("Out of rage!\nYear " +  y + " should be " +
+              "negative number before 1AD.\nMonth " + m + " should " +
+              "be between 0 and 11 in Islamic calendar.\nDate " + d +
+              " should be between 0 and 32 in Islamic calendar.");
+        }
+    }
 }