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
This commit is contained in:
parent
4583c1e77c
commit
5769803253
@ -341,7 +341,7 @@ public class IndianCalendar extends Calendar {
|
||||
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 @@ public class IndianCalendar extends Calendar {
|
||||
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 @@ public class IndianCalendar extends Calendar {
|
||||
* @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 @@ public class IndianCalendar extends Calendar {
|
||||
* @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
|
||||
|
@ -872,8 +872,8 @@ public class IslamicCalendar extends Calendar {
|
||||
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) {
|
||||
|
@ -28,6 +28,9 @@ import com.ibm.icu.util.BuddhistCalendar;
|
||||
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 @@ public class IBMCalendarTest extends CalendarTestFmwk {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user