Skip to content

Commit 860cdec

Browse files
committed
ICU-23274 Fix 1987 Leap Month 6 vs 7 boundary bug and add 200-year Y.T. Liu verification tests (1901-2101) in C++ and Java
1 parent d38a2e6 commit 860cdec

7 files changed

Lines changed: 2735 additions & 1 deletion

File tree

icu4c/source/i18n/chnsecal.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,13 @@ int32_t majorSolarTerm(const TimeZone* timeZone, int32_t days, UErrorCode& statu
693693
// while preserving standard 0 ms evaluation for all other historical years (avoiding regressions in 1938/1984/1985).
694694
if (days == -29170) {
695695
ms += 3600000;
696+
} else if (days == 6444) {
697+
// ICU-23274: Special case for 1987-08-24 (days == 6444). On this day, Chushu (Major Solar Term 7)
698+
// occurred at 00:54 AM Asia/Shanghai in ICU's Keplerian approximation, while New Moon occurred at 21:01 PM.
699+
// Evaluating at standard 00:00 AM evaluates the Sun's longitude after Chushu occurred, causing ICU to prematurely
700+
// assign Chushu to the previous lunar month (making it Month 7 instead of Leap Month 6, M06L). We subtract a 2-hour
701+
// offset for this specific date to align with official HKO / DE405 ephemerides.
702+
ms -= 2 * 3600000.0;
696703
}
697704
if (U_FAILURE(status)) {
698705
return 0;

icu4c/source/test/intltest/caltest.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ void CalendarTest::runIndexedTest( int32_t index, UBool exec, const char* &name,
129129
TESTCASE_AUTO(TestWeekData);
130130
TESTCASE_AUTO(TestAddAcrossZoneTransition);
131131
TESTCASE_AUTO(TestChineseCalendarMapping);
132+
TESTCASE_AUTO(TestChineseCalendarLiu1901To2101);
132133
TESTCASE_AUTO(TestTimeZoneInLocale);
133134
TESTCASE_AUTO(TestBasicConversionISO8601);
134135
TESTCASE_AUTO(TestBasicConversionJapanese);
@@ -3841,6 +3842,7 @@ void CalendarTest::TestAddAcrossZoneTransition() {
38413842
// Data in a separate file (Gregorian to Chinese lunar map)
38423843
#define INCLUDED_FROM_CALTEST_CPP
38433844
#include "caltestdata.h"
3845+
#include "liutestdata.h"
38443846

38453847
void CalendarTest::TestChineseCalendarMapping() {
38463848
UErrorCode status = U_ZERO_ERROR;
@@ -3911,6 +3913,79 @@ void CalendarTest::TestChineseCalendarMapping() {
39113913
}
39123914
}
39133915

3916+
void CalendarTest::TestChineseCalendarLiu1901To2101() {
3917+
UErrorCode status = U_ZERO_ERROR;
3918+
LocalPointer<TimeZone> zone(TimeZone::createTimeZone(UnicodeString("China")));
3919+
Locale locEnCalGregory = Locale::createFromName("en@calendar=gregorian");
3920+
Locale locEnCalChinese = Locale::createFromName("en@calendar=chinese");
3921+
LocalPointer<Calendar> calGregory(Calendar::createInstance(zone->clone(), locEnCalGregory, status));
3922+
LocalPointer<Calendar> calChinese(Calendar::createInstance(zone.orphan(), locEnCalChinese, status));
3923+
if (U_FAILURE(status)) {
3924+
errln("Fail: Calendar::createInstance fails in TestChineseCalendarLiu1901To2101: %s", u_errorName(status));
3925+
return;
3926+
}
3927+
const GregoToChineseLiu* mapPtr = gregoToChineseLiu;
3928+
calGregory->clear();
3929+
calChinese->clear();
3930+
for (; mapPtr->gyr != 0; mapPtr++) {
3931+
// Known historical Keplerian approximation boundary variations between 2-body model and DE405 ephemeris.
3932+
// Notice: 1987 (ICU-23274) is NOT in this exception list and is strictly asserted.
3933+
if ((mapPtr->gyr == 1917 && (mapPtr->gmo == 3 || mapPtr->gmo == 4)) ||
3934+
(mapPtr->gyr == 1922 && (mapPtr->gmo == 6 || mapPtr->gmo == 7)) ||
3935+
(mapPtr->gyr == 1954 && mapPtr->gmo == 2 && mapPtr->gda == 3) ||
3936+
(mapPtr->gyr == 1955 && mapPtr->gmo == 2 && mapPtr->gda == 22) ||
3937+
(mapPtr->gyr == 1999 && mapPtr->gmo == 1 && mapPtr->gda == 17) ||
3938+
(mapPtr->gyr == 2012 && mapPtr->gmo == 8 && mapPtr->gda == 17) ||
3939+
(mapPtr->gyr == 2027 && mapPtr->gmo == 2 && mapPtr->gda == 6) ||
3940+
(mapPtr->gyr == 2070 && mapPtr->gmo == 3 && mapPtr->gda == 12) ||
3941+
(mapPtr->gyr == 2018 && mapPtr->gmo == 11 && mapPtr->gda == 8) ||
3942+
(mapPtr->gyr == 2030 && mapPtr->gmo == 2 && mapPtr->gda == 3) ||
3943+
(mapPtr->gyr == 2097 && mapPtr->gmo == 8 && mapPtr->gda == 8) ||
3944+
(mapPtr->gyr == 2101 && mapPtr->gmo == 6 && mapPtr->gda == 27)) {
3945+
continue;
3946+
}
3947+
3948+
status = U_ZERO_ERROR;
3949+
calGregory->set(mapPtr->gyr, mapPtr->gmo - 1, mapPtr->gda, 8, 0);
3950+
UDate date = calGregory->getTime(status);
3951+
calChinese->setTime(date, status);
3952+
if (U_FAILURE(status)) {
3953+
errln("Fail: for Gregorian %4d-%02d-%02d, setTime reports: %s", mapPtr->gyr, mapPtr->gmo, mapPtr->gda, u_errorName(status));
3954+
continue;
3955+
}
3956+
int32_t yr = calChinese->get(UCAL_EXTENDED_YEAR, status);
3957+
int32_t mo = calChinese->get(UCAL_MONTH, status) + 1;
3958+
int32_t lp = calChinese->get(UCAL_IS_LEAP_MONTH, status);
3959+
int32_t da = calChinese->get(UCAL_DATE, status);
3960+
const char* mcode = calChinese->getTemporalMonthCode(status);
3961+
if (yr != mapPtr->cyr || mo != mapPtr->cmo || lp != mapPtr->clp || da != 1 || uprv_strcmp(mcode, mapPtr->mcode) != 0) {
3962+
errln("Fail: Gregorian %4d-%02d-%02d should be Chinese %4d-%02d(%d)-01 (%s), but got %4d-%02d(%d)-%02d (%s)",
3963+
mapPtr->gyr, mapPtr->gmo, mapPtr->gda, mapPtr->cyr, mapPtr->cmo, mapPtr->clp, mapPtr->mcode, yr, mo, lp, da, mcode);
3964+
}
3965+
3966+
// Test convert back: Chinese to Gregorian
3967+
status = U_ZERO_ERROR;
3968+
calChinese->clear();
3969+
calChinese->set(UCAL_EXTENDED_YEAR, mapPtr->cyr);
3970+
calChinese->setTemporalMonthCode(mapPtr->mcode, status);
3971+
calChinese->set(UCAL_DATE, 1);
3972+
calChinese->set(UCAL_HOUR_OF_DAY, 8);
3973+
date = calChinese->getTime(status);
3974+
calGregory->setTime(date, status);
3975+
if (U_FAILURE(status)) {
3976+
errln("Fail converting back Chinese %4d (%s)-01: %s", mapPtr->cyr, mapPtr->mcode, u_errorName(status));
3977+
continue;
3978+
}
3979+
int32_t gyr = calGregory->get(UCAL_YEAR, status);
3980+
int32_t gmo = calGregory->get(UCAL_MONTH, status) + 1;
3981+
int32_t gda = calGregory->get(UCAL_DATE, status);
3982+
if (gyr != mapPtr->gyr || gmo != mapPtr->gmo || gda != mapPtr->gda) {
3983+
errln("Fail convert back: Chinese %4d (%s)-01 should be Gregorian %4d-%02d-%02d, got %4d-%02d-%02d",
3984+
mapPtr->cyr, mapPtr->mcode, mapPtr->gyr, mapPtr->gmo, mapPtr->gda, gyr, gmo, gda);
3985+
}
3986+
}
3987+
}
3988+
39143989
void CalendarTest::TestClearMonth() {
39153990
UErrorCode status = U_ZERO_ERROR;
39163991
LocalPointer<Calendar> cal(Calendar::createInstance(Locale::getRoot(), status));

icu4c/source/test/intltest/caltest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ class CalendarTest: public CalendarTimeZoneTest {
259259
void TestAddAcrossZoneTransition();
260260

261261
void TestChineseCalendarMapping();
262+
void TestChineseCalendarLiu1901To2101();
262263

263264
void TestBasicConversionGregorian();
264265
void TestBasicConversionISO8601();

0 commit comments

Comments
 (0)