Skip to content

Commit

Permalink
ICU-23006 Fix Chinese Calendar getActualMaximize
Browse files Browse the repository at this point in the history
See #3348
  • Loading branch information
FrankYFTang committed Jan 29, 2025
1 parent 6d8b63c commit 7153a3d
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 12 deletions.
42 changes: 35 additions & 7 deletions icu4c/source/i18n/chnsecal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<ChineseCalendar> 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
Expand Down
6 changes: 6 additions & 0 deletions icu4c/source/i18n/chnsecal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion icu4c/source/test/intltest/calregts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
25 changes: 25 additions & 0 deletions icu4c/source/test/intltest/caltest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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<Calendar> 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);
Expand Down
1 change: 1 addition & 0 deletions icu4c/source/test/intltest/caltest.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ class CalendarTest: public CalendarTimeZoneTest {
void TestCalendarRollOrdinalMonth();
void TestLimitsOrdinalMonth();
void TestActualLimitsOrdinalMonth();
void TestMaxActualLimitsWithoutGet23006();
void TestDangiOverflowIsLeapMonthBetween22507();

void TestFWWithISO8601();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 7153a3d

Please sign in to comment.