From 7153a3db526736d654293135ea75e3745c0e805d Mon Sep 17 00:00:00 2001 From: Frank Tang Date: Tue, 28 Jan 2025 00:38:00 +0000 Subject: [PATCH] ICU-23006 Fix Chinese Calendar getActualMaximize See #3348 --- icu4c/source/i18n/chnsecal.cpp | 42 +++++++++++++++---- icu4c/source/i18n/chnsecal.h | 6 +++ icu4c/source/test/intltest/calregts.cpp | 2 +- icu4c/source/test/intltest/caltest.cpp | 25 +++++++++++ icu4c/source/test/intltest/caltest.h | 1 + .../main/java/com/ibm/icu/util/Calendar.java | 1 + .../com/ibm/icu/util/ChineseCalendar.java | 33 +++++++++++++-- .../test/calendar/CalendarRegressionTest.java | 28 +++++++++++++ 8 files changed, 126 insertions(+), 12 deletions(-) diff --git a/icu4c/source/i18n/chnsecal.cpp b/icu4c/source/i18n/chnsecal.cpp index fdb8915105ab..47a9fbabb67a 100644 --- a/icu4c/source/i18n/chnsecal.cpp +++ b/icu4c/source/i18n/chnsecal.cpp @@ -253,11 +253,16 @@ int32_t ChineseCalendar::handleGetExtendedYear(UErrorCode& status) { * @stable ICU 2.8 */ int32_t ChineseCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month, UErrorCode& status) const { + bool isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) == 1; + return handleGetMonthLengthWithLeap(extendedYear, month, isLeapMonth, status); +} + +int32_t ChineseCalendar::handleGetMonthLengthWithLeap(int32_t extendedYear, int32_t month, bool leap, UErrorCode& status) const { const Setting setting = getSetting(status); if (U_FAILURE(status)) { return 0; } - int32_t thisStart = handleComputeMonthStart(extendedYear, month, true, status); + int32_t thisStart = handleComputeMonthStartWithLeap(extendedYear, month, leap, status); if (U_FAILURE(status)) { return 0; } @@ -333,6 +338,14 @@ struct MonthInfo computeMonthInfo( * @stable ICU 2.8 */ int64_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool useMonth, UErrorCode& status) const { + bool isLeapMonth = false; + if (useMonth) { + isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) != 0; + } + return handleComputeMonthStartWithLeap(eyear, month, isLeapMonth, status); +} + +int64_t ChineseCalendar::handleComputeMonthStartWithLeap(int32_t eyear, int32_t month, bool isLeapMonth, UErrorCode& status) const { if (U_FAILURE(status)) { return 0; } @@ -361,12 +374,6 @@ int64_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U return 0; } - // Ignore IS_LEAP_MONTH field if useMonth is false - bool isLeapMonth = false; - if (useMonth) { - isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) != 0; - } - int32_t newMonthYear = Grego::dayToYear(newMoon, status); struct MonthInfo monthInfo = computeMonthInfo(setting, newMonthYear, newMoon, status); @@ -1182,6 +1189,27 @@ ChineseCalendar::Setting ChineseCalendar::getSetting(UErrorCode&) const { }; } +int32_t +ChineseCalendar::getActualMaximum(UCalendarDateFields field, UErrorCode& status) const +{ + if (U_FAILURE(status)) { + return 0; + } + if (field == UCAL_DATE) { + LocalPointer cal(clone(), status); + if(U_FAILURE(status)) { + return 0; + } + cal->setLenient(true); + cal->prepareGetActual(field,false,status); + int32_t year = cal->get(UCAL_EXTENDED_YEAR, status); + int32_t month = cal->get(UCAL_MONTH, status); + bool leap = cal->get(UCAL_IS_LEAP_MONTH, status) != 0; + return handleGetMonthLengthWithLeap(year, month, leap, status); + } + return Calendar::getActualMaximum(field, status); +} + U_NAMESPACE_END #endif diff --git a/icu4c/source/i18n/chnsecal.h b/icu4c/source/i18n/chnsecal.h index 41bd3557fcbf..410a5a0222cf 100644 --- a/icu4c/source/i18n/chnsecal.h +++ b/icu4c/source/i18n/chnsecal.h @@ -194,6 +194,10 @@ class U_I18N_API ChineseCalendar : public Calendar { virtual void handleComputeFields(int32_t julianDay, UErrorCode &status) override; virtual const UFieldResolutionTable* getFieldResolutionTable() const override; + private: + int32_t handleGetMonthLengthWithLeap(int32_t extendedYear, int32_t month, bool isLeap, UErrorCode& status) const; + int64_t handleComputeMonthStartWithLeap(int32_t eyear, int32_t month, bool isLeap, UErrorCode& status) const; + public: virtual void add(UCalendarDateFields field, int32_t amount, UErrorCode &status) override; virtual void add(EDateFields field, int32_t amount, UErrorCode &status) override; @@ -254,6 +258,8 @@ class U_I18N_API ChineseCalendar : public Calendar { */ virtual const char * getType() const override; + virtual int32_t getActualMaximum(UCalendarDateFields field, UErrorCode& status) const override; + struct Setting { int32_t epochYear; const TimeZone* zoneAstroCalc; diff --git a/icu4c/source/test/intltest/calregts.cpp b/icu4c/source/test/intltest/calregts.cpp index 82fb3eade63f..198901f88179 100644 --- a/icu4c/source/test/intltest/calregts.cpp +++ b/icu4c/source/test/intltest/calregts.cpp @@ -2433,7 +2433,7 @@ void CalendarRegressionTest::TestT5555() // Should be set to Wednesday, February 24, 2010 if (U_FAILURE(ec) || yy != 2010 || mm != UCAL_FEBRUARY || dd != 24 || ee != UCAL_WEDNESDAY) { - errln("FAIL: got date %4d/%02d/%02d, expected 210/02/24: ", yy, mm + 1, dd); + errln("FAIL: got date %4d/%02d/%02d (wd=%d), expected 2010/02/24: ", yy, mm + 1, dd, ee); } delete cal; } diff --git a/icu4c/source/test/intltest/caltest.cpp b/icu4c/source/test/intltest/caltest.cpp index 121170857798..d5a824ec6fa2 100644 --- a/icu4c/source/test/intltest/caltest.cpp +++ b/icu4c/source/test/intltest/caltest.cpp @@ -183,6 +183,7 @@ void CalendarTest::runIndexedTest( int32_t index, UBool exec, const char* &name, TESTCASE_AUTO(TestCalendarRollOrdinalMonth); TESTCASE_AUTO(TestLimitsOrdinalMonth); TESTCASE_AUTO(TestActualLimitsOrdinalMonth); + TESTCASE_AUTO(TestMaxActualLimitsWithoutGet23006); TESTCASE_AUTO(TestChineseCalendarMonthInSpecialYear); TESTCASE_AUTO(TestClearMonth); @@ -5354,6 +5355,30 @@ void CalendarTest::TestLimitsOrdinalMonth() { } } +void CalendarTest::TestMaxActualLimitsWithoutGet23006() { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + gc.set(2025, UCAL_AUGUST, 8); + LocalPointer cal(Calendar::createInstance(Locale("en@calendar=chinese"), status), status); + cal->setTime(gc.getTime(status), status); + int32_t beforeCallingGet = cal->getActualMaximum(UCAL_DAY_OF_MONTH, status); + cal->get(UCAL_DAY_OF_MONTH, status); + int32_t afterCallingGet = cal->getActualMaximum(UCAL_DAY_OF_MONTH, status); + assertEquals("getActualMaximum() should return same value before/after calling get()", + beforeCallingGet, afterCallingGet); + assertEquals("getActualMaximum() should return 29 before calling get()", + 29, beforeCallingGet); + + gc.set(2026, UCAL_AUGUST, 8); + cal->setTime(gc.getTime(status), status); + beforeCallingGet = cal->getActualMaximum(UCAL_DAY_OF_MONTH, status); + cal->get(UCAL_DAY_OF_MONTH, status); + afterCallingGet = cal->getActualMaximum(UCAL_DAY_OF_MONTH, status); + assertEquals("getActualMaximum() should return same value before/after calling get()", + beforeCallingGet, afterCallingGet); + assertEquals("getActualMaximum() should return 29 before calling get()", + 30, beforeCallingGet); +} void CalendarTest::TestActualLimitsOrdinalMonth() { UErrorCode status = U_ZERO_ERROR; GregorianCalendar gc(status); diff --git a/icu4c/source/test/intltest/caltest.h b/icu4c/source/test/intltest/caltest.h index 068db75b56f2..e1a505292e5a 100644 --- a/icu4c/source/test/intltest/caltest.h +++ b/icu4c/source/test/intltest/caltest.h @@ -329,6 +329,7 @@ class CalendarTest: public CalendarTimeZoneTest { void TestCalendarRollOrdinalMonth(); void TestLimitsOrdinalMonth(); void TestActualLimitsOrdinalMonth(); + void TestMaxActualLimitsWithoutGet23006(); void TestDangiOverflowIsLeapMonthBetween22507(); void TestFWWithISO8601(); diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/util/Calendar.java b/icu4j/main/core/src/main/java/com/ibm/icu/util/Calendar.java index 0eefb4a16b35..9fd460b014ec 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/util/Calendar.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/util/Calendar.java @@ -6157,6 +6157,7 @@ abstract protected int handleComputeMonthStart(int eyear, int month, * @stable ICU 2.0 */ protected int handleGetMonthLength(int extendedYear, int month) { + System.out.printf("handleGetMonthLength(ey=%d, m=%d)", extendedYear, month); return handleComputeMonthStart(extendedYear, month+1, true) - handleComputeMonthStart(extendedYear, month, true); } diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/util/ChineseCalendar.java b/icu4j/main/core/src/main/java/com/ibm/icu/util/ChineseCalendar.java index 7cfa4232ae5e..b6510f88e157 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/util/ChineseCalendar.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/util/ChineseCalendar.java @@ -456,7 +456,12 @@ protected int handleGetExtendedYear() { * @stable ICU 2.8 */ protected int handleGetMonthLength(int extendedYear, int month) { - int thisStart = handleComputeMonthStart(extendedYear, month, true) - + int isLeapMonth = internalGet(IS_LEAP_MONTH); + return handleGetMonthLengthWithLeap(extendedYear, month, isLeapMonth); + } + + private int handleGetMonthLengthWithLeap(int extendedYear, int month, int isLeap) { + int thisStart = handleComputeMonthStartWithLeap(extendedYear, month, isLeap) - EPOCH_JULIAN_DAY + 1; // Julian day -> local days int nextStart = newMoonNear(thisStart + SYNODIC_GAP, true); return nextStart - thisStart; @@ -971,6 +976,14 @@ private int newYear(int gyear) { * @stable ICU 2.8 */ protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) { + int isLeapMonth = 0; + if (useMonth) { + isLeapMonth = internalGet(IS_LEAP_MONTH); + } + return handleComputeMonthStartWithLeap(eyear, month, isLeapMonth); + } + + private int handleComputeMonthStartWithLeap(int eyear, int month, int isLeapMonth) { // If the month is out of range, adjust it into range, and // modify the extended year value accordingly. @@ -991,9 +1004,6 @@ protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) { int saveOrdinalMonth = internalGet(ORDINAL_MONTH); int saveIsLeapMonth = internalGet(IS_LEAP_MONTH); - // Ignore IS_LEAP_MONTH field if useMonth is false - int isLeapMonth = useMonth ? saveIsLeapMonth : 0; - computeGregorianFields(julianDay); // This will modify the MONTH and IS_LEAP_MONTH fields (only) @@ -1161,6 +1171,21 @@ protected int internalGetMonth(int defaultValue) return internalGetMonth(); } + public int getActualMaximum(int field) { + if (field == DAY_OF_MONTH) { + Calendar cal = (Calendar) clone(); + cal.setLenient(true); + cal.prepareGetActual(field, false); + int eyear = cal.get(EXTENDED_YEAR); + int month = cal.get(MONTH); + int isLeap = cal.get(IS_LEAP_MONTH); + + return handleGetMonthLengthWithLeap(eyear, month, isLeap); + } + return super.getActualMaximum(field); + + } + /* private static CalendarFactory factory; public static CalendarFactory factory() { diff --git a/icu4j/main/core/src/test/java/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java b/icu4j/main/core/src/test/java/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java index 5e247ad8050f..e24fc446d0e0 100644 --- a/icu4j/main/core/src/test/java/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java +++ b/icu4j/main/core/src/test/java/com/ibm/icu/dev/test/calendar/CalendarRegressionTest.java @@ -2718,5 +2718,33 @@ public void TestIslamicUmalquraCalendarSlow() { // ICU-22513 } + @Test + public void TestMaxActualLimitsWithoutGet23006() { + Calendar calendar = Calendar.getInstance(new Locale("zh_zh@calendar=chinese")); + // set day equal to 8th August 2025 in Gregorian calendar + // this is a leap month in Chinese calendar + GregorianCalendar gc = new GregorianCalendar(TimeZone.GMT_ZONE); + gc.clear(); + gc.set(2025, Calendar.AUGUST, 8); + calendar.setTimeInMillis(gc.getTimeInMillis()); + int actualMaximumBeforeCallingGet = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); + assertTrue("get(ERA)", calendar.get(Calendar.ERA) > 0); // calling get will cause to compute fields + int actualMaximumAfterCallingGet = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); + assertEquals("calling getActualMaximum before/after calling get should be the same", + actualMaximumBeforeCallingGet, actualMaximumAfterCallingGet); + assertEquals("calling getActualMaximum before should return 29", + 29, actualMaximumBeforeCallingGet); + + gc.set(2026, Calendar.AUGUST, 8); + calendar.setTimeInMillis(gc.getTimeInMillis()); + actualMaximumBeforeCallingGet = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); + assertTrue("get(ERA)", calendar.get(Calendar.ERA) > 0); // calling get will cause to compute fields + actualMaximumAfterCallingGet = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); + assertEquals("calling getActualMaximum before/after calling get should be the same", + actualMaximumBeforeCallingGet, actualMaximumAfterCallingGet); + assertEquals("calling getActualMaximum before should return 30", + 30, actualMaximumBeforeCallingGet); + + } } //eof