From cc335962590323443fec300b314266a733038c11 Mon Sep 17 00:00:00 2001 From: Frank Tang Date: Mon, 10 Jul 2023 16:28:10 -0700 Subject: [PATCH] ICU-22407 Implement Java Temporal Calendar API --- .../core/src/com/ibm/icu/util/CECalendar.java | 47 +++++ .../core/src/com/ibm/icu/util/Calendar.java | 185 +++++++++++++++++- .../src/com/ibm/icu/util/ChineseCalendar.java | 137 +++++++++++-- .../src/com/ibm/icu/util/CopticCalendar.java | 1 + .../com/ibm/icu/util/EthiopicCalendar.java | 1 + .../com/ibm/icu/util/GregorianCalendar.java | 4 +- .../src/com/ibm/icu/util/HebrewCalendar.java | 2 + .../src/com/ibm/icu/util/IndianCalendar.java | 3 + .../src/com/ibm/icu/util/IslamicCalendar.java | 26 +++ .../src/com/ibm/icu/util/PersianCalendar.java | 4 + .../test/calendar/CalendarRegressionTest.java | 3 +- 11 files changed, 393 insertions(+), 20 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/CECalendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/CECalendar.java index ce3bd888be40..d85c49d82b7d 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/CECalendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/CECalendar.java @@ -45,6 +45,8 @@ abstract class CECalendar extends Calendar { { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR {/* */}, // JULIAN_DAY {/* */}, // MILLISECONDS_IN_DAY + {/* */}, // IS_LEAP_YEAR + { 0, 0, 12, 12 }, // ORDINAL_MONTH }; //------------------------------------------------------------------------- @@ -273,4 +275,49 @@ public static void jdToCE(int julianDay, int jdEpochOffset, int[] fields) { // day fields[2] = (doy % 30) + 1; // 1-based days in a month } + + + //------------------------------------------------------------------------- + // Temporal Calendar API. + //------------------------------------------------------------------------- + private static String [] gTemporalMonthCodes = { + "M01", "M02", "M03", "M04", "M05", "M06", "M07", "M08", "M09", "M10", "M11", "M12" + }; + + /** + * Gets The Temporal monthCode value corresponding to the month for the date. + * The value is a string identifier that starts with the literal grapheme + * "M" followed by two graphemes representing the zero-padded month number + * of the current month in a normal (non-leap) year. For the short thirteen + * month in each year in the CECalendar, the value is "M13". + * + * @return One of 13 possible strings in {"M01".. "M12", "M13"}. + * @draft ICU 74 + */ + public String getTemporalMonthCode() { + if (get(MONTH) == 12) return "M13"; + return super.getTemporalMonthCode(); + } + + /** + * Sets The Temporal monthCode which is a string identifier that starts + * with the literal grapheme "M" followed by two graphemes representing + * the zero-padded month number of the current month in a normal + * (non-leap) year. For CECalendar calendar, the values are "M01" .. "M13" + * while the "M13" is represent the short thirteen month in each year. + * @param temporalMonth One of 13 possible strings in {"M01".. "M12", "M13"}. + * @draft ICU 74 + */ + public void setTemporalMonthCode( String temporalMonth ) { + if (temporalMonth.equals("M13")) { + set(MONTH, 12); + set(IS_LEAP_MONTH, 0); + return; + } + super.setTemporalMonthCode(temporalMonth); + } + + //------------------------------------------------------------------------- + // End of Temporal Calendar API + //------------------------------------------------------------------------- } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java index 038d483f99c7..47c48aeaa594 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/Calendar.java @@ -955,13 +955,37 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable UNSET) ? fields[field] : defaultValue; } + /* + * @internal + * @deprecated This API is ICU internal only. + * Use this function instead of internalGet(MONTH). The implementation + * check the timestamp of MONTH and ORDINAL_MONTH and use the + * one set later. The subclass should override it to conver the value of ORDINAL_MONTH + * to MONTH correctly if ORDINAL_MONTH has higher priority. + * @return the value for the given time field. + */ + protected int internalGetMonth() + { + if (resolveFields(MONTH_PRECEDENCE) == MONTH) { + return internalGet(MONTH); + } + return internalGet(ORDINAL_MONTH); + } + /** + * @internal + * @deprecated This API is ICU internal only. + * Use this function instead of internalGet(MONTH, defaultValue). The implementation + * check the timestamp of MONTH and ORDINAL_MONTH and use the + * one set later. The subclass should override it to conver the value of ORDINAL_MONTH + * to MONTH correctly if ORDINAL_MONTH has higher priority. + * @param defaultValue a default value used if the MONTH and + * ORDINAL_MONTH are both unset. + * @return the value for the MONTH. + */ + protected int internalGetMonth(int defaultValue) { + if (resolveFields(MONTH_PRECEDENCE) == MONTH) { + return internalGet(MONTH, defaultValue); + } + return internalGet(ORDINAL_MONTH, defaultValue); + } + + /** * Sets the time field with the given value. * @param field the given time field. * @param value the value to be set for the given time field. @@ -2320,6 +2463,14 @@ public final void clear(int field) } fields[field] = 0; stamp[field] = UNSET; + if (field == MONTH) { + fields[ORDINAL_MONTH] = 0; + stamp[ORDINAL_MONTH] = UNSET; + } + if (field == ORDINAL_MONTH) { + fields[MONTH] = 0; + stamp[MONTH] = UNSET; + } isTimeSet = areFieldsSet = areAllFieldsSet = areFieldsVirtuallySet = false; } @@ -2518,6 +2669,10 @@ public int getActualMaximum(int field) { result = getMaximum(field); break; + case ORDINAL_MONTH: + result = inTemporalLeapYear() ? getMaximum(ORDINAL_MONTH) : getLeastMaximum(ORDINAL_MONTH); + break; + default: // For all other fields, do it the hard way.... result = getActualHelper(field, getLeastMaximum(field), getMaximum(field)); @@ -2880,13 +3035,14 @@ public void roll(int field, int amount) { } case MONTH: + case ORDINAL_MONTH: // Rolling the month involves both pinning the final value // and adjusting the DAY_OF_MONTH if necessary. We only adjust the // DAY_OF_MONTH if, after updating the MONTH field, it is illegal. // E.g., .roll(MONTH, 1) -> or . { int max = getActualMaximum(MONTH); - int mon = (internalGet(MONTH) + amount) % (max+1); + int mon = (internalGetMonth() + amount) % (max+1); if (mon < 0) { mon += (max + 1); @@ -2937,6 +3093,7 @@ public void roll(int field, int amount) { } set(field, newYear); pinField(MONTH); + pinField(ORDINAL_MONTH); pinField(DAY_OF_MONTH); return; } @@ -2944,6 +3101,7 @@ public void roll(int field, int amount) { // Rolling the year can involve pinning the DAY_OF_MONTH. set(field, internalGet(field) + amount); pinField(MONTH); + pinField(ORDINAL_MONTH); pinField(DAY_OF_MONTH); return; @@ -3083,6 +3241,7 @@ public void roll(int field, int amount) { // have to be updated as well. set(DAY_OF_YEAR, day_of_year); clear(MONTH); + clear(ORDINAL_MONTH); return; } case DAY_OF_YEAR: @@ -3258,6 +3417,7 @@ public void add(int field, int amount) { // Fall through into standard handling case EXTENDED_YEAR: case MONTH: + case ORDINAL_MONTH: { boolean oldLenient = isLenient(); setLenient(true); @@ -4409,6 +4569,7 @@ public int getMinimalDaysInFirstWeek() { -0x7F000000, -0x7F000000, 0x7F000000, 0x7F000000 }, // JULIAN_DAY { 0, 0, 24*ONE_HOUR-1, 24*ONE_HOUR-1 }, // MILLISECONDS_IN_DAY { 0, 0, 1, 1 }, // IS_LEAP_MONTH + { 0, 0, 12, 12 }, // ORDINAL_MONTH }; /** @@ -5295,6 +5456,13 @@ private final void computeWeekFields() { }, }; + static final int[][][] MONTH_PRECEDENCE = { + { + { MONTH }, + { ORDINAL_MONTH }, + }, + }; + /** * Given a precedence table, return the newest field combination in * the table, or -1 if none is found. @@ -5427,7 +5595,7 @@ protected void validateField(int field) { switch (field) { case DAY_OF_MONTH: y = handleGetExtendedYear(); - validateField(field, 1, handleGetMonthLength(y, internalGet(MONTH))); + validateField(field, 1, handleGetMonthLength(y, internalGetMonth())); break; case DAY_OF_YEAR: y = handleGetExtendedYear(); @@ -5898,6 +6066,7 @@ protected int computeJulianDay() { if (stamp[JULIAN_DAY] >= MINIMUM_USER_STAMP) { int bestStamp = newestStamp(ERA, DAY_OF_WEEK_IN_MONTH, UNSET); bestStamp = newestStamp(YEAR_WOY, EXTENDED_YEAR, bestStamp); + bestStamp = newestStamp(ORDINAL_MONTH, ORDINAL_MONTH, bestStamp); if (bestStamp <= stamp[JULIAN_DAY]) { return internalGet(JULIAN_DAY); } @@ -6043,7 +6212,7 @@ protected int handleComputeJulianDay(int bestField) { internalSet(EXTENDED_YEAR, year); - int month = useMonth ? internalGet(MONTH, getDefaultMonthInYear(year)) : 0; + int month = useMonth ? internalGetMonth(getDefaultMonthInYear(year)) : 0; // Get the Julian day of the day BEFORE the start of this year. // If useMonth is true, get the day before the start of the month. @@ -6121,7 +6290,7 @@ protected int handleComputeJulianDay(int bestField) { // past the first of the given day-of-week in this month. // Note that we handle -2, -3, etc. correctly, even though // values < -1 are technically disallowed. - int m = internalGet(MONTH, JANUARY); + int m = internalGetMonth(JANUARY); int monthLength = handleGetMonthLength(year, m); date += ((monthLength - date) / 7 + dim + 1) * 7; } @@ -6212,7 +6381,9 @@ protected int computeGregorianMonthStart(int year, int month) { * @stable ICU 2.0 */ protected void handleComputeFields(int julianDay) { - internalSet(MONTH, getGregorianMonth()); + int gmonth = getGregorianMonth(); + internalSet(MONTH, gmonth); + internalSet(ORDINAL_MONTH, gmonth); internalSet(DAY_OF_MONTH, getGregorianDayOfMonth()); internalSet(DAY_OF_YEAR, getGregorianDayOfYear()); int eyear = getGregorianYear(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/ChineseCalendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/ChineseCalendar.java index 23d400e5a025..657bc2949ce2 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/ChineseCalendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/ChineseCalendar.java @@ -129,11 +129,14 @@ public class ChineseCalendar extends Calendar { private transient CalendarCache newYearCache = new CalendarCache(); /** - * True if the current year is a leap year. Updated with each time to - * fields resolution. + * True if there is a leap month between the Winter Solstice before and after the + * current date.This is different from leap year because in some year, such as + * 1813 and 2033, the leap month is after the Winter Solstice of that year. So + * this value could be false for a date prior to the Winter Solstice of that + * year but that year still has a leap month and therefor is a leap year. * @see #computeChineseFields */ - private transient boolean isLeapYear; + private transient boolean hasLeapMonthBetweenWinterSolstices; //------------------------------------------------------------------ // Constructors @@ -420,6 +423,7 @@ protected ChineseCalendar(TimeZone zone, ULocale locale, int epochYear, TimeZone {/* */}, // JULIAN_DAY {/* */}, // MILLISECONDS_IN_DAY { 0, 0, 1, 1 }, // IS_LEAP_MONTH + { 0, 0, 11, 12 }, // ORDINAL_MONTH }; /** @@ -557,6 +561,7 @@ private void offsetMonth(int newMoon, int dom, int delta) { public void add(int field, int amount) { switch (field) { case MONTH: + case ORDINAL_MONTH: if (amount != 0) { int dom = get(DAY_OF_MONTH); int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day @@ -577,6 +582,7 @@ public void add(int field, int amount) { public void roll(int field, int amount) { switch (field) { case MONTH: + case ORDINAL_MONTH: if (amount != 0) { int dom = get(DAY_OF_MONTH); int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day @@ -589,7 +595,7 @@ public void roll(int field, int amount) { // value from 0..11 in a non-leap year, and from 0..12 in a // leap year. int m = get(MONTH); // 0-based month - if (isLeapYear) { // (member variable) + if (hasLeapMonthBetweenWinterSolstices) { // (member variable) if (get(IS_LEAP_MONTH) == 1) { ++m; } else { @@ -611,7 +617,7 @@ public void roll(int field, int amount) { // Now do the standard roll computation on m, with the // allowed range of 0..n-1, where n is 12 or 13. - int n = isLeapYear ? 13 : 12; // Months in this year + int n = hasLeapMonthBetweenWinterSolstices ? 13 : 12; // Months in this year int newM = (m + amount) % n; if (newM < 0) { newM += n; @@ -837,7 +843,7 @@ protected void handleComputeFields(int julianDay) { * IS_LEAP_MONTH fields, as required by * handleComputeMonthStart(). * - *

As a side effect, this method sets {@link #isLeapYear}. + *

As a side effect, this method sets {@link #hasLeapMonthBetweenWinterSolstices}. * @param days days after January 1, 1970 0:00 astronomical base zone of the * date to compute fields for * @param gyear the Gregorian year of the given date @@ -868,22 +874,31 @@ private void computeChineseFields(int days, int gyear, int gmonth, int firstMoon = newMoonNear(solsticeBefore + 1, true); int lastMoon = newMoonNear(solsticeAfter + 1, false); int thisMoon = newMoonNear(days + 1, false); // Start of this month - // Note: isLeapYear is a member variable - isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12; + // Note: hasLeapMonthBetweenWinterSolstices is a member variable + hasLeapMonthBetweenWinterSolstices = synodicMonthsBetween(firstMoon, lastMoon) == 12; int month = synodicMonthsBetween(firstMoon, thisMoon); - if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) { + int theNewYear = newYear(gyear); + if (days < theNewYear) { + theNewYear = newYear(gyear-1); + } + if (hasLeapMonthBetweenWinterSolstices && isLeapMonthBetween(firstMoon, thisMoon)) { month--; } if (month < 1) { month += 12; } + int ordinalMonth = synodicMonthsBetween(theNewYear, thisMoon); + if (ordinalMonth < 0) { + ordinalMonth += 12; + } - boolean isLeapMonth = isLeapYear && + boolean isLeapMonth = hasLeapMonthBetweenWinterSolstices && hasNoMajorSolarTerm(thisMoon) && !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false)); internalSet(MONTH, month-1); // Convert from 1-based to 0-based + internalSet(ORDINAL_MONTH, ordinalMonth); internalSet(IS_LEAP_MONTH, isLeapMonth?1:0); if (setAllFields) { @@ -985,6 +1000,7 @@ protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) { // Save fields for later restoration int saveMonth = internalGet(MONTH); + int saveOrdinalMonth = internalGet(ORDINAL_MONTH); int saveIsLeapMonth = internalGet(IS_LEAP_MONTH); // Ignore IS_LEAP_MONTH field if useMonth is false @@ -1003,6 +1019,7 @@ protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) { } internalSet(MONTH, saveMonth); + internalSet(ORDINAL_MONTH, saveOrdinalMonth); internalSet(IS_LEAP_MONTH, saveIsLeapMonth); return julianDay - 1; @@ -1042,7 +1059,105 @@ private void readObject(ObjectInputStream stream) winterSolsticeCache = new CalendarCache(); newYearCache = new CalendarCache(); } - + + //------------------------------------------------------------------------- + // Temporal Calendar API. + //------------------------------------------------------------------------- + /** + * {@icu} Returns true if the date is in a leap year. Recalculate the current time + * field values if the time value has been changed by a call to setTime(). + * This method is semantically const, but may alter the object in memory. + * A "leap year" is a year that contains more days than other years (for + * solar or lunar calendars) or more months than other years (for lunisolar + * calendars like Hebrew or Chinese), as defined in the ECMAScript Temporal + * proposal. + * @return true if the date in the fields is in a Temporal proposal + * defined leap year. False otherwise. + * @draft ICU 74 + */ + public boolean inTemporalLeapYear() { + return getActualMaximum(DAY_OF_YEAR) > 360; + } + + private static String [] gTemporalLeapMonthCodes = { + "M01L", "M02L", "M03L", "M04L", "M05L", "M06L", "M07L", "M08L", "M09L", "M10L", "M11L", "M12L" + }; + + /** + * Gets The Temporal monthCode value corresponding to the month for the date. + * The value is a string identifier that starts with the literal grapheme + * "M" followed by two graphemes representing the zero-padded month number + * of the current month in a normal (non-leap) year and suffixed by an + * optional literal grapheme "L" if this is a leap month in a lunisolar + * calendar. For the Chinese calendar, the values are "M01" .. "M12" for + * non-leap year and * in leap year with another monthCode in "M01L" .. "M12L". + * + * @return One of 24 possible strings in {"M01".."M12", "M01L".."M12L"}. + * @draft ICU 74 + */ + public String getTemporalMonthCode() { + // We need to call get, not internalGet, to force the calculation + // from ORDINAL_MONTH. + int is_leap = get(IS_LEAP_MONTH); + if (is_leap != 0) { + return gTemporalLeapMonthCodes[get(MONTH)]; + } + return super.getTemporalMonthCode(); + } + + /** + * Sets The Temporal monthCode which is a string identifier that starts + * with the literal grapheme "M" followed by two graphemes representing + * the zero-padded month number of the current month in a normal + * (non-leap) year and suffixed by an optional literal grapheme "L" if this + * is a leap month in a lunisolar calendar. + * For the Chinese calendar, the values are "M01" .. "M12" for non-leap year and + * in leap year with another monthCode in "M01L" .. "M12L". + * @param temporalMonth One of 25 possible strings in {"M01".. "M12", "M13", "M01L", + * "M12L"}. + * @draft ICU 74 + */ + public void setTemporalMonthCode( String temporalMonth ) { + if (temporalMonth.length() != 4 || temporalMonth.charAt(0) != 'M' || temporalMonth.charAt(3) != 'L') { + set(IS_LEAP_MONTH, 0); + super.setTemporalMonthCode(temporalMonth); + return; + } + for (int m = 0; m < gTemporalLeapMonthCodes.length; m++) { + if (temporalMonth.equals(gTemporalLeapMonthCodes[m])) { + set(MONTH, m); + set(IS_LEAP_MONTH, 1); + return; + } + } + throw new IllegalArgumentException("Incorrect temporal Month code: " + temporalMonth); + } + + //------------------------------------------------------------------------- + // End of Temporal Calendar API + //------------------------------------------------------------------------- + + /** + * {@inheritDoc} + * @internal + */ + protected int internalGetMonth() + { + if (resolveFields(MONTH_PRECEDENCE) == MONTH) { + return internalGet(MONTH); + } + Calendar temp = (Calendar) clone(); + temp.set(Calendar.MONTH, 0); + temp.set(Calendar.IS_LEAP_MONTH, 0); + temp.set(Calendar.DATE, 1); + // Calculate the MONTH and IS_LEAP_MONTH by adding number of months. + temp.roll(Calendar.MONTH, internalGet(Calendar.ORDINAL_MONTH)); + internalSet(Calendar.IS_LEAP_MONTH, temp.get(Calendar.IS_LEAP_MONTH)); + int month = temp.get(Calendar.MONTH); + internalSet(Calendar.MONTH, month); + return month; + } + /* private static CalendarFactory factory; public static CalendarFactory factory() { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/CopticCalendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/CopticCalendar.java index 9f38324755df..9dbcd6b42e69 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/CopticCalendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/CopticCalendar.java @@ -293,6 +293,7 @@ protected void handleComputeFields(int julianDay) { internalSet(ERA, era); internalSet(YEAR, year); internalSet(MONTH, fields[1]); + internalSet(ORDINAL_MONTH, fields[1]); internalSet(DAY_OF_MONTH, fields[2]); internalSet(DAY_OF_YEAR, (30 * fields[1]) + fields[2]); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/EthiopicCalendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/EthiopicCalendar.java index 356365b5fe63..9f6bdba1b80e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/EthiopicCalendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/EthiopicCalendar.java @@ -353,6 +353,7 @@ protected void handleComputeFields(int julianDay) { internalSet(ERA, era); internalSet(YEAR, year); internalSet(MONTH, fields[1]); + internalSet(ORDINAL_MONTH, fields[1]); internalSet(DAY_OF_MONTH, fields[2]); internalSet(DAY_OF_YEAR, (30 * fields[1]) + fields[2]); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/GregorianCalendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/GregorianCalendar.java index aa76c0279d07..70e36a868777 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/GregorianCalendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/GregorianCalendar.java @@ -280,6 +280,7 @@ public class GregorianCalendar extends Calendar { {/* */}, // JULIAN_DAY {/* */}, // MILLISECONDS_IN_DAY {/* */}, // IS_LEAP_MONTH + { 0, 0, 11, 11 }, // ORDINAL_MONTH }; /** @@ -572,7 +573,7 @@ public void roll(int field, int amount) { // may be one year before or after the calendar year. int isoYear = get(YEAR_WOY); int isoDoy = internalGet(DAY_OF_YEAR); - if (internalGet(MONTH) == Calendar.JANUARY) { + if (internalGetMonth() == Calendar.JANUARY) { if (woy >= 52) { isoDoy += handleGetYearLength(isoYear); } @@ -792,6 +793,7 @@ protected void handleComputeFields(int julianDay) { ++dayOfYear; } internalSet(MONTH, month); + internalSet(ORDINAL_MONTH, month); internalSet(DAY_OF_MONTH, dayOfMonth); internalSet(DAY_OF_YEAR, dayOfYear); internalSet(EXTENDED_YEAR, eyear); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/HebrewCalendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/HebrewCalendar.java index 73322d112d8b..214e1bcaa688 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/HebrewCalendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/HebrewCalendar.java @@ -195,6 +195,8 @@ public class HebrewCalendar extends Calendar { { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR {/* */}, // JULIAN_DAY {/* */}, // MILLISECONDS_IN_DAY + {/* */}, // IS_LEAP_MONTH + { 0, 0, 11, 12 }, // ORDINAL_MONTH }; /** 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 23e5be97c1c8..b5d6d8eecb4a 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 @@ -394,6 +394,7 @@ protected void handleComputeFields(int julianDay){ internalSet(ERA, 0); internalSet(EXTENDED_YEAR, IndianYear); internalSet(YEAR, IndianYear); + internalSet(ORDINAL_MONTH, IndianMonth); internalSet(MONTH, IndianMonth); internalSet(DAY_OF_MONTH, IndianDayOfMonth ); internalSet(DAY_OF_YEAR, yday + 1); // yday is 0-based @@ -424,6 +425,8 @@ protected void handleComputeFields(int julianDay){ { -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR {/* */}, // JULIAN_DAY {/* */}, // MILLISECONDS_IN_DAY + {/* */}, // IS_LEAP_MONTH + { 0, 0, 11, 11 }, // ORDINAL_MONTH }; 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 b8ae53c792c1..14a507ecbfdb 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 @@ -395,6 +395,8 @@ public boolean isCivil() { { 1, 1, 5000000, 5000000}, // EXTENDED_YEAR {/* */}, // JULIAN_DAY {/* */}, // MILLISECONDS_IN_DAY + {/* */}, // IS_LEAP_MONTH + { 0, 0, 11, 11 }, // ORDINAL_MONTH }; /* @@ -917,6 +919,7 @@ protected void handleComputeFields(int julianDay) { internalSet(YEAR, year); internalSet(EXTENDED_YEAR, year); internalSet(MONTH, month); + internalSet(ORDINAL_MONTH, month); internalSet(DAY_OF_MONTH, dayOfMonth); internalSet(DAY_OF_YEAR, dayOfYear); } @@ -1031,6 +1034,29 @@ private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundEx } } + //------------------------------------------------------------------------- + // Temporal Calendar API. + //------------------------------------------------------------------------- + /** + * {@icu} Returns true if the date is in a leap year. Recalculate the current time + * field values if the time value has been changed by a call to setTime(). + * This method is semantically const, but may alter the object in memory. + * A "leap year" is a year that contains more days than other years (for + * solar or lunar calendars) or more months than other years (for lunisolar + * calendars like Hebrew or Chinese), as defined in the ECMAScript Temporal + * proposal. + * @return true if the date in the fields is in a Temporal proposal + * defined leap year. False otherwise. + * @draft ICU 74 + */ + public boolean inTemporalLeapYear() { + return getActualMaximum(DAY_OF_YEAR) == 355; + } + + //------------------------------------------------------------------------- + // End of Temporal Calendar API + //------------------------------------------------------------------------- + /* private static CalendarFactory factory; public static CalendarFactory factory() { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/PersianCalendar.java b/icu4j/main/classes/core/src/com/ibm/icu/util/PersianCalendar.java index 68919b580eb3..b43121997cd0 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/PersianCalendar.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/PersianCalendar.java @@ -289,6 +289,9 @@ public PersianCalendar(int year, int month, int date, int hour, { -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR {/* */}, // JULIAN_DAY {/* */}, // MILLISECONDS_IN_DAY + {/* */}, // IS_LEAP_MONTH + { 0, 0, 11, 11 }, // ORDINAL_MONTH + }; /** @@ -436,6 +439,7 @@ protected void handleComputeFields(int julianDay) { internalSet(YEAR, year); internalSet(EXTENDED_YEAR, year); internalSet(MONTH, month); + internalSet(ORDINAL_MONTH, month); internalSet(DAY_OF_MONTH, dayOfMonth); internalSet(DAY_OF_YEAR, dayOfYear); } 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 7ec63c5c37a3..b7e82b668732 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 @@ -53,7 +53,7 @@ public class CalendarRegressionTest extends com.ibm.icu.dev.test.TestFmwk { "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY", "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET", "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR", - "JULIAN_DAY", "MILLISECONDS_IN_DAY" + "JULIAN_DAY", "MILLISECONDS_IN_DAY", "IS_LEAP_YEAR", "ORDINAL_MONTH" }; @@ -1427,6 +1427,7 @@ public void Test4173516() { cal.get(Calendar.SECOND) != fields[5] || cal.get(Calendar.MILLISECOND) != fields[6]) { errln("Field " + field + + " " + (op==0 ? "add" : "roll") + " (" + FIELD_NAME[field] + ") FAIL, expected " + fields[0] +