diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/util/IslamicCalendar.java b/icu4j/main/core/src/main/java/com/ibm/icu/util/IslamicCalendar.java index 14a507ecbfdb..7d1182f69b70 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/util/IslamicCalendar.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/util/IslamicCalendar.java @@ -11,6 +11,7 @@ import java.io.ObjectInputStream; import java.util.Date; import java.util.Locale; +import java.util.function.IntConsumer; import com.ibm.icu.impl.CalendarAstronomer; import com.ibm.icu.impl.CalendarCache; @@ -171,13 +172,14 @@ public class IslamicCalendar extends Calendar { private static final long HIJRA_MILLIS = -42521587200000L; // 7/16/622 AD 00:00 /** - * Friday EPOC + * Friday EPOCH */ - private static final long CIVIL_EPOC = 1948440; // CE 622 July 16 Friday (Julian calendar) / CE 622 July 19 (Gregorian calendar) + private static final long CIVIL_EPOCH = 1948440; // CE 622 July 16 Friday (Julian calendar) / CE 622 July 19 (Gregorian calendar) + // /** - * Thursday EPOC + * Thursday EPOCH */ - private static final long ASTRONOMICAL_EPOC = 1948439; // CE 622 July 15 Thursday (Julian calendar) + private static final long ASTRONOMICAL_EPOCH = 1948439; // CE 622 July 15 Thursday (Julian calendar) //------------------------------------------------------------------------- // Constructors... @@ -319,6 +321,301 @@ public IslamicCalendar(int year, int month, int date, int hour, this.set(Calendar.SECOND, second); } + // Private interface for different Islamic calendar algorithms. + private interface Algorithm { + /** + * Returns <code>true</code> if this object is using the fixed-cycle civil + * calendar, or <code>false</code> if using the religious, astronomical + * calendar. + */ + public boolean isCivil(); + + /** + * Return the type the algorithm implement. + */ + public CalculationType getType(); + + /** + * Return the epoch used by this algorithm. + */ + public long epoch(); + + /** + * Return the day # on which the given year starts. Days are counted + * from the Hijri epoch, origin 0. + * + * @param year The hijri year + */ + public long yearStart(int year); + + /** + * Return the day # on which the given month starts. Days are counted + * from the Hijri epoch, origin 0. + * + * @param year The hijri year + * @param month The hijri month, 0-based + */ + public long monthStart(int year, int month); + + /** + * Return the length (in days) of the given month. + * + * @param year The hijri year + * @param month The hijri month, 0-based + */ + public int monthLength(int year, int month); + + /** + * Return the length (in days) of the given year. + * + * @param year The hijri year + */ + public int yearLength(int year); + + /** + * Compute the year, month, dayOfMonth, and dayOfYear of the given julian days + * and current time and feed the caculuated results to the consumers. + * @param julianDays + * @param current the time in millisecond. + * @param yearConsumer consumer to take the year result. + * @param monthConsumer consumer to take the month result. + * @param dayOfMonthConsumer consumer to take the dayOfMonth result. + * @param dayOfYearConsumer consumer to take the dayOfYear result. + */ + public void compute(long julianDays, long current, + IntConsumer yearConsumer, IntConsumer monthConsumer, + IntConsumer dayOfMonthConsumer, IntConsumer dayOfYearConsumer); + }; + + /** + * Algorithm which implement the rules for CalculationType.ISLAMIC. + */ + static private class IslamicAlgorithm implements Algorithm { + public boolean isCivil() { + return false; + } + public CalculationType getType() { + return CalculationType.ISLAMIC; + } + public long epoch() { + return CIVIL_EPOCH; + } + public long yearStart(int year) { + return monthStart(year, 0); + } + public long monthStart(int year, int month) { + // Normalize year/month in case month is outside the normal bounds, which may occur + // in the case of an add operation + return trueMonthStart(12*((year + month / 12)-1) + (month % 12)); + } + public int monthLength(int year, int month) { + month += 12*(year-1); + return (int)(trueMonthStart(month+1) - trueMonthStart(month)); + } + public int yearLength(int year) { + int month = 12*(year-1); + return (int)(trueMonthStart(month + 12) - trueMonthStart(month)); + } + public void compute(long julianDays, long current, + IntConsumer yearConsumer, IntConsumer monthConsumer, + IntConsumer dayOfMonthConsumer, IntConsumer dayOfYearConsumer) { + long days = julianDays - epoch(); + // Guess at the number of elapsed full months since the epoch + int month = (int)Math.floor(days / CalendarAstronomer.SYNODIC_MONTH); + long monthStart = (long)Math.floor(month * CalendarAstronomer.SYNODIC_MONTH - 1); + if (days - monthStart >= 25 && moonAge(current) > 0) { + // If we're near the end of the month, assume next month and search backwards + month++; + } + // Find out the last time that the new moon was actually visible at this longitude + // This returns midnight the night that the moon was visible at sunset. + while ((monthStart = trueMonthStart(month)) > days) { + // If it was after the date in question, back up a month and try again + month--; + } + int year = month >= 0 ? ((month / 12) + 1) : ((month + 1 ) / 12); + month = ((month % 12) + 12 ) % 12; + yearConsumer.accept(year); + monthConsumer.accept(month); + dayOfMonthConsumer.accept((int)(days - monthStart(year, month)) + 1); + dayOfYearConsumer.accept((int)(days - yearStart(year) + 1)); + } + }; + + /** + * Algorithm which implement the rules for CalculationType.ISLAMIC_CIVIL. + */ + static private class CivilAlgorithm implements Algorithm { + public boolean isCivil() { + return true; + } + public CalculationType getType() { + return CalculationType.ISLAMIC_CIVIL; + } + public long epoch() { + return CIVIL_EPOCH; + } + public long yearStart(int year) { + return (year-1)*354 + (long)Math.floor((3+11*year)/30.0); + } + public long monthStart(int year, int month) { + // Normalize year/month in case month is outside the normal bounds, which may occur + // in the case of an add operation + return (long)Math.ceil(29.5*(month % 12)) + yearStart(year + month / 12); + } + public int monthLength(int year, int month) { + int length = 29; + if (month % 2 == 0) { + ++length; + } + if (month == DHU_AL_HIJJAH && civilLeapYear(year)) { + ++length; + } + return length; + } + public int yearLength(int year) { + return 354 + (civilLeapYear(year) ? 1 : 0); + } + public void compute(long julianDays, long current, + IntConsumer yearConsumer, IntConsumer monthConsumer, + IntConsumer dayOfMonthConsumer, IntConsumer dayOfYearConsumer) { + long days = julianDays - epoch(); + // Use the civil calendar approximation, which is just arithmetic + int year = (int)Math.floor( (30 * days + 10646) / 10631.0 ); + int month = (int)Math.ceil((days - 29 - yearStart(year)) / 29.5 ); + month = Math.min(month, 11); + yearConsumer.accept(year); + monthConsumer.accept(month); + dayOfMonthConsumer.accept((int)(days - monthStart(year, month)) + 1); + dayOfYearConsumer.accept((int)(days - yearStart(year) + 1)); + } + }; + + /** + * Algorithm which implement the rules for CalculationType.ISLAMIC_TBLA. + * Mostly the same as CivilAlgorithm, except it return false for isCivil and use different + * epoch value. + */ + static private class TBLAAlgorithm extends CivilAlgorithm { + public boolean isCivil() { + return false; + } + public CalculationType getType() { + return CalculationType.ISLAMIC_TBLA; + } + public long epoch() { + return ASTRONOMICAL_EPOCH; + } + }; + + /** + * Algorithm which implement the rules for CalculationType.ISLAMIC_UMALQURA. + */ + static private class UmalquraAlgorithm implements Algorithm { + public boolean isCivil() { + return false; + } + public CalculationType getType() { + return CalculationType.ISLAMIC_UMALQURA; + } + public long epoch() { + return CIVIL_EPOCH; + } + public long yearStart(int year) { + if (year < UMALQURA_YEAR_START || year > UMALQURA_YEAR_END) { + return CIVIL_ALGORITHM.yearStart(year); + } + int index = year - UMALQURA_YEAR_START; + // rounded least-squares fit of the dates previously calculated from UMALQURA_MONTHLENGTH iteration + int yrStartLinearEstimate = (int)((354.36720 * index) + 460322.05 + 0.5); + // need a slight correction to some + return yrStartLinearEstimate + UMALQURA_YEAR_START_ESTIMATE_FIX[index]; + } + public long monthStart(int year, int month) { + // Normalize year/month in case month is outside the normal bounds, which may occur + // in the case of an add operation + year += month / 12; + month %= 12; + if (year < UMALQURA_YEAR_START) { + return CIVIL_ALGORITHM.monthStart(year, month); + } + long ms = yearStart(year); + for(int i=0; i< month; i++) { + ms+= monthLength(year, i); + } + return ms; + } + public int monthLength(int year, int month) { + if (year < UMALQURA_YEAR_START || year > UMALQURA_YEAR_END) { + return CIVIL_ALGORITHM.monthLength(year, month); + } + int index = (year - UMALQURA_YEAR_START); // calculate year offset into bit map array + int mask = (0x01 << (11 - month)); // set mask for bit corresponding to month + if((UMALQURA_MONTHLENGTH[index] & mask) != 0) { + return 30; + } + return 29; + } + public int yearLength(int year) { + if (year < UMALQURA_YEAR_START || year > UMALQURA_YEAR_END) { + return CIVIL_ALGORITHM.yearLength(year); + } + int length = 0; + for(int i = 0; i < 12; i++) { + length += monthLength(year, i); + } + return length; + } + public void compute(long julianDays, long current, + IntConsumer yearConsumer, IntConsumer monthConsumer, + IntConsumer dayOfMonthConsumer, IntConsumer dayOfYearConsumer) { + long days = julianDays - epoch(); + if( days < yearStart(UMALQURA_YEAR_START)) { + CIVIL_ALGORITHM.compute(julianDays, current, + yearConsumer, monthConsumer, dayOfMonthConsumer, dayOfYearConsumer); + return; + } + // Estimate a value y which is closer to but not greater than the year. + // It is the inverse function of the logic inside yearStart() about the + // linear estimate. + int year = (int)((days - (460322.05 + 0.5)) / 354.36720) + UMALQURA_YEAR_START - 1; + int month = 0; + long monthStart; + long d = 1; + while (d > 0) { + year++; + d = days - yearStart(year) +1; + int yearLength = yearLength(year); + if (d == yearLength) { + month = 11; + break; + } else if (d < yearLength) { + int monthLen = monthLength(year, month); + for (month = 0; d > monthLen; monthLen = monthLength(year, ++month)) { + d -= monthLen; + } + break; + } + } + yearConsumer.accept(year); + monthConsumer.accept(month); + dayOfMonthConsumer.accept((int)(days - monthStart(year, month)) + 1); + dayOfYearConsumer.accept((int)(days - yearStart(year) + 1)); + } + }; + + private static Algorithm ISLAMIC_ALGORITHM; + private static Algorithm CIVIL_ALGORITHM; + private static Algorithm TBLA_ALGORITHM; + private static Algorithm UMALQURA_ALGORITHM; + + static { + ISLAMIC_ALGORITHM = new IslamicAlgorithm(); + CIVIL_ALGORITHM = new CivilAlgorithm(); + TBLA_ALGORITHM = new TBLAAlgorithm(); + UMALQURA_ALGORITHM = new UmalquraAlgorithm(); + }; + /** * Determines whether this object uses the fixed-cycle Islamic civil calendar * or an approximation of the religious, astronomical calendar. @@ -330,13 +627,12 @@ public IslamicCalendar(int year, int month, int date, int hour, */ public void setCivil(boolean beCivil) { - civil = beCivil; - if (beCivil && cType != CalculationType.ISLAMIC_CIVIL) { // The fields of the calendar will become invalid, because the calendar // rules are different long m = getTimeInMillis(); cType = CalculationType.ISLAMIC_CIVIL; + algorithm = CIVIL_ALGORITHM; clear(); setTimeInMillis(m); } else if(!beCivil && cType != CalculationType.ISLAMIC) { @@ -344,9 +640,11 @@ public void setCivil(boolean beCivil) // rules are different long m = getTimeInMillis(); cType = CalculationType.ISLAMIC; + algorithm = ISLAMIC_ALGORITHM; clear(); setTimeInMillis(m); } + civil = algorithm.isCivil(); } /** @@ -357,10 +655,7 @@ public void setCivil(boolean beCivil) * @discouraged ICU 57 use getCalculationType() instead */ public boolean isCivil() { - if(cType == CalculationType.ISLAMIC_CIVIL) { - return true; - } - return false; + return algorithm.isCivil(); } //------------------------------------------------------------------------- @@ -587,51 +882,7 @@ private final static boolean civilLeapYear(int year) * from the Hijri epoch, origin 0. */ private long yearStart(int year) { - long ys = 0; - if (cType == CalculationType.ISLAMIC_CIVIL - || cType == CalculationType.ISLAMIC_TBLA - || (cType == CalculationType.ISLAMIC_UMALQURA && (year < UMALQURA_YEAR_START || year > UMALQURA_YEAR_END))) { - ys = (year-1)*354 + (long)Math.floor((3+11*year)/30.0); - } else if(cType == CalculationType.ISLAMIC) { - ys = trueMonthStart(12*(year-1)); - } else if(cType == CalculationType.ISLAMIC_UMALQURA){ - year -= UMALQURA_YEAR_START; - // rounded least-squares fit of the dates previously calculated from UMALQURA_MONTHLENGTH iteration - int yrStartLinearEstimate = (int)((354.36720 * year) + 460322.05 + 0.5); - // need a slight correction to some - ys = yrStartLinearEstimate + UMALQURA_YEAR_START_ESTIMATE_FIX[year]; - } - return ys; - } - - /** - * Return the day # on which the given month starts. Days are counted - * from the Hijri epoch, origin 0. - * - * @param year The hijri year - * @param month The hijri month, 0-based - */ - private long monthStart(int year, int month) { - // Normalize year/month in case month is outside the normal bounds, which may occur - // in the case of an add operation - int realYear = year + month / 12; - int realMonth = month % 12; - long ms = 0; - if (cType == CalculationType.ISLAMIC_CIVIL - || cType == CalculationType.ISLAMIC_TBLA - || (cType == CalculationType.ISLAMIC_UMALQURA && year < UMALQURA_YEAR_START )) { - ms = (long)Math.ceil(29.5*realMonth) - + (realYear-1)*354 + (long)Math.floor((3+11*realYear)/30.0); - } else if(cType == CalculationType.ISLAMIC) { - ms = trueMonthStart(12*(realYear-1) + realMonth); - } else if(cType == CalculationType.ISLAMIC_UMALQURA) { - ms = yearStart(year); - for(int i=0; i< month; i++) { - ms+= handleGetMonthLength(year, i); - } - } - - return ms; + return algorithm.yearStart(year); } /** @@ -728,6 +979,8 @@ static final double moonAge(long time) */ private CalculationType cType = CalculationType.ISLAMIC_CIVIL; + private transient Algorithm algorithm = CIVIL_ALGORITHM; + //---------------------------------------------------------------------- // Calendar framework //---------------------------------------------------------------------- @@ -741,32 +994,7 @@ static final double moonAge(long time) */ @Override protected int handleGetMonthLength(int extendedYear, int month) { - - int length; - - if (cType == CalculationType.ISLAMIC_CIVIL - || cType == CalculationType.ISLAMIC_TBLA - || (cType == CalculationType.ISLAMIC_UMALQURA && (extendedYear < UMALQURA_YEAR_START || extendedYear > UMALQURA_YEAR_END) )) { - length = 29 + (month+1) % 2; - if (month == DHU_AL_HIJJAH && civilLeapYear(extendedYear)) { - length++; - } - } - else if (cType == CalculationType.ISLAMIC) { - month = 12*(extendedYear-1) + month; - length = (int)( trueMonthStart(month+1) - trueMonthStart(month) ); - } - else { // cType == CalculationType.ISLAMIC_UMALQURA should be true at this point and not null. - int idx = (extendedYear - UMALQURA_YEAR_START); // calculate year offset into bit map array - int mask = (0x01 << (11 - month)); // set mask for bit corresponding to month - if((UMALQURA_MONTHLENGTH[idx] & mask) == 0 ) { - length = 29; - } - else { - length = 30; - } - } - return length; + return algorithm.monthLength(extendedYear, month); } /** @@ -775,20 +1003,7 @@ else if (cType == CalculationType.ISLAMIC) { */ @Override protected int handleGetYearLength(int extendedYear) { - int length =0; - if (cType == CalculationType.ISLAMIC_CIVIL - || cType == CalculationType.ISLAMIC_TBLA - || (cType == CalculationType.ISLAMIC_UMALQURA && (extendedYear < UMALQURA_YEAR_START || extendedYear > UMALQURA_YEAR_END) )) { - length = 354 + (civilLeapYear(extendedYear) ? 1 : 0); - } else if (cType == CalculationType.ISLAMIC) { - int month = 12*(extendedYear-1); - length = (int)(trueMonthStart(month + 12) - trueMonthStart(month)); - } else if (cType == CalculationType.ISLAMIC_UMALQURA) { - for(int i=0; i<12; i++) - length += handleGetMonthLength(extendedYear, i); - } - - return length; + return algorithm.yearLength(extendedYear); } //------------------------------------------------------------------------- @@ -805,7 +1020,7 @@ protected int handleGetYearLength(int extendedYear) { */ @Override protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) { - return (int)(monthStart(eyear, month) + ((cType == CalculationType.ISLAMIC_TBLA)? ASTRONOMICAL_EPOC: CIVIL_EPOC) - 1); + return (int)(algorithm.monthStart(eyear, month) + algorithm.epoch()- 1); } //------------------------------------------------------------------------- @@ -844,84 +1059,18 @@ protected int handleGetExtendedYear() { */ @Override protected void handleComputeFields(int julianDay) { - int year =0, month=0, dayOfMonth=0, dayOfYear=0; - long monthStart; - long days = julianDay - CIVIL_EPOC; - - if (cType == CalculationType.ISLAMIC_CIVIL || cType == CalculationType.ISLAMIC_TBLA) { - if (cType == CalculationType.ISLAMIC_TBLA) { - days = julianDay - ASTRONOMICAL_EPOC; - } - // Use the civil calendar approximation, which is just arithmetic - year = (int)Math.floor( (30 * days + 10646) / 10631.0 ); - month = (int)Math.ceil((days - 29 - yearStart(year)) / 29.5 ); - month = Math.min(month, 11); - } else if (cType == CalculationType.ISLAMIC){ - // Guess at the number of elapsed full months since the epoch - int months = (int)Math.floor(days / CalendarAstronomer.SYNODIC_MONTH); - - monthStart = (long)Math.floor(months * CalendarAstronomer.SYNODIC_MONTH - 1); - - if ( days - monthStart >= 25 && moonAge(internalGetTimeInMillis()) > 0) { - // If we're near the end of the month, assume next month and search backwards - months++; - } - - // Find out the last time that the new moon was actually visible at this longitude - // This returns midnight the night that the moon was visible at sunset. - while ((monthStart = trueMonthStart(months)) > days) { - // If it was after the date in question, back up a month and try again - months--; - } - - 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) { - // Use Civil calculation - year = (int)Math.floor( (30 * days + 10646) / 10631.0 ); - month = (int)Math.ceil((days - 29 - yearStart(year)) / 29.5 ); - month = Math.min(month, 11); - } else { - int y =UMALQURA_YEAR_START-1, m =0; - long d = 1; - while(d > 0) { - y++; - d = days - yearStart(y) +1; - if(d == handleGetYearLength(y)) { - m=11; - break; - } else if(d < handleGetYearLength(y) ) { - int monthLen = handleGetMonthLength(y, m); - m=0; - while(d > monthLen) { - d -= monthLen; - m++; - monthLen = handleGetMonthLength(y, m); - } - break; - } - } - year = y; - month = m; - } - } - - - dayOfMonth = (int)(days - monthStart(year, month)) + 1; - - // Now figure out the day of the year. - dayOfYear = (int)(days - monthStart(year, 0) + 1); - - - internalSet(ERA, 0); - internalSet(YEAR, year); - internalSet(EXTENDED_YEAR, year); - internalSet(MONTH, month); - internalSet(ORDINAL_MONTH, month); - internalSet(DAY_OF_MONTH, dayOfMonth); - internalSet(DAY_OF_YEAR, dayOfYear); + algorithm.compute(julianDay, internalGetTimeInMillis(), + year -> { + internalSet(ERA, 0); + internalSet(YEAR, year); + internalSet(EXTENDED_YEAR, year); + }, + month -> { + internalSet(MONTH, month); + internalSet(ORDINAL_MONTH, month); + }, + dayOfMonth -> { internalSet(DAY_OF_MONTH, dayOfMonth); }, + dayOfYear -> { internalSet(DAY_OF_YEAR, dayOfYear); }); } /** @@ -971,12 +1120,22 @@ String bcpType() { */ public void setCalculationType(CalculationType type) { cType = type; - - // ensure civil property is up-to-date - if(cType == CalculationType.ISLAMIC_CIVIL) - civil = true; - else - civil = false; + switch (cType) { + case ISLAMIC_UMALQURA: + algorithm = UMALQURA_ALGORITHM; + break; + case ISLAMIC: + algorithm = ISLAMIC_ALGORITHM; + break; + case ISLAMIC_TBLA: + algorithm = TBLA_ALGORITHM; + break; + case ISLAMIC_CIVIL: + default: + algorithm = CIVIL_ALGORITHM; + break; + } + civil = algorithm.isCivil(); } /** @@ -985,7 +1144,7 @@ public void setCalculationType(CalculationType type) { * @stable ICU 55 */ public CalculationType getCalculationType() { - return cType; + return algorithm.getType(); } /** @@ -1012,26 +1171,17 @@ else if(localeCalType.startsWith("islamic")) */ @Override public String getType() { - if (cType == null) { - // TODO: getType() is called during Islamic calendar - // construction and might be null at that point. We should - // check the initialization sequence. See ticket#10425. - return "islamic"; - } - return cType.bcpType(); + return algorithm.getType().bcpType(); } private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException { in.defaultReadObject(); - if (cType == null) { // The serialized data was created by an ICU version before CalculationType // was introduced. cType = civil ? CalculationType.ISLAMIC_CIVIL : CalculationType.ISLAMIC; - } else { - // Make sure 'civil' is consistent with CalculationType - civil = (cType == CalculationType.ISLAMIC_CIVIL); } + setCalculationType(cType); } //-------------------------------------------------------------------------