diff --git a/icu4c/source/i18n/calendar.cpp b/icu4c/source/i18n/calendar.cpp index 0c736681248..8fd7781b792 100644 --- a/icu4c/source/i18n/calendar.cpp +++ b/icu4c/source/i18n/calendar.cpp @@ -660,6 +660,7 @@ static const int32_t kCalendarLimits[UCAL_FIELD_COUNT][4] = { { -0x7F000000, -0x7F000000, 0x7F000000, 0x7F000000 }, // JULIAN_DAY { 0, 0, 24*kOneHour-1, 24*kOneHour-1 }, // MILLISECONDS_IN_DAY { 0, 0, 1, 1 }, // IS_LEAP_MONTH + { 0, 0, 11, 11 } // ORDINAL_MONTH }; // Resource bundle tags read by this class @@ -1421,7 +1422,8 @@ void Calendar::computeFields(UErrorCode &ec) (1 << UCAL_MONTH) | (1 << UCAL_DAY_OF_MONTH) | // = UCAL_DATE (1 << UCAL_DAY_OF_YEAR) | - (1 << UCAL_EXTENDED_YEAR); + (1 << UCAL_EXTENDED_YEAR) | + (1 << UCAL_ORDINAL_MONTH); for (int32_t i=0; i(uprv_strlen(code)); + if (len == 3 && code[0] == 'M') { + for (int m = 0; gTemporalMonthCodes[m] != nullptr; m++) { + if (uprv_strcmp(code, gTemporalMonthCodes[m]) == 0) { + set(UCAL_MONTH, m); + set(UCAL_IS_LEAP_MONTH, 0); + return; + } + } + } + status = U_ILLEGAL_ARGUMENT_ERROR; +} + // ------------------------------------- /** @@ -2799,7 +2848,7 @@ void Calendar::validateField(UCalendarDateFields field, UErrorCode &status) { switch (field) { case UCAL_DAY_OF_MONTH: y = handleGetExtendedYear(); - validateField(field, 1, handleGetMonthLength(y, internalGet(UCAL_MONTH)), status); + validateField(field, 1, handleGetMonthLength(y, internalGetMonth()), status); break; case UCAL_DAY_OF_YEAR: y = handleGetExtendedYear(); @@ -2860,7 +2909,7 @@ UCalendarDateFields Calendar::newerField(UCalendarDateFields defaultField, UCale return defaultField; } -UCalendarDateFields Calendar::resolveFields(const UFieldResolutionTable* precedenceTable) { +UCalendarDateFields Calendar::resolveFields(const UFieldResolutionTable* precedenceTable) const { int32_t bestField = UCAL_FIELD_COUNT; int32_t tempBestField; for (int32_t g=0; precedenceTable[g][0][0] != -1 && (bestField == UCAL_FIELD_COUNT); ++g) { @@ -2929,6 +2978,16 @@ const UFieldResolutionTable Calendar::kDatePrecedence[] = }; +const UFieldResolutionTable Calendar::kMonthPrecedence[] = +{ + { + { UCAL_MONTH,kResolveSTOP, kResolveSTOP }, + { UCAL_ORDINAL_MONTH,kResolveSTOP, kResolveSTOP }, + {kResolveSTOP} + }, + {{kResolveSTOP}} +}; + const UFieldResolutionTable Calendar::kDOWPrecedence[] = { { @@ -3211,6 +3270,7 @@ int32_t Calendar::computeJulianDay() if (fStamp[UCAL_JULIAN_DAY] >= (int32_t)kMinimumUserStamp) { int32_t bestStamp = newestStamp(UCAL_ERA, UCAL_DAY_OF_WEEK_IN_MONTH, kUnset); bestStamp = newestStamp(UCAL_YEAR_WOY, UCAL_EXTENDED_YEAR, bestStamp); + bestStamp = newestStamp(UCAL_ORDINAL_MONTH, UCAL_ORDINAL_MONTH, bestStamp); if (bestStamp <= fStamp[UCAL_JULIAN_DAY]) { return internalGet(UCAL_JULIAN_DAY); } @@ -3250,8 +3310,8 @@ int32_t Calendar::handleComputeJulianDay(UCalendarDateFields bestField) { // give calendar subclass a chance to have a default 'first' month int32_t month; - if(isSet(UCAL_MONTH)) { - month = internalGet(UCAL_MONTH); + if(isSet(UCAL_MONTH) || isSet(UCAL_ORDINAL_MONTH)) { + month = internalGetMonth(); } else { month = getDefaultMonthInYear(year); } @@ -3319,7 +3379,7 @@ int32_t Calendar::handleComputeJulianDay(UCalendarDateFields 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. - int32_t m = internalGet(UCAL_MONTH, UCAL_JANUARY); + int32_t m = internalGetMonth(UCAL_JANUARY); int32_t monthLength = handleGetMonthLength(year, m); date += ((monthLength - date) / 7 + dim + 1) * 7; } @@ -3544,23 +3604,25 @@ int32_t Calendar::handleGetExtendedYearFromWeekFields(int32_t yearWoy, int32_t w } case UCAL_DATE: - if((internalGet(UCAL_MONTH)==0) && + { + int32_t m = internalGetMonth(); + if((m == 0) && (woy >= getLeastMaximum(UCAL_WEEK_OF_YEAR))) { return yearWoy+1; // month 0, late woy = in the next year } else if(woy==1) { //if(nextJan1InPrevYear) { - if(internalGet(UCAL_MONTH)==0) { + if(m == 0) { return yearWoy; } else { return yearWoy-1; } //} } - - //(internalGet(UCAL_DATE) <= (7-first)) /* && in minDow */ ) { - //within 1st week and in this month.. - //return yearWoy+1; - return yearWoy; + } + //(internalGet(UCAL_DATE) <= (7-first)) /* && in minDow */ ) { + //within 1st week and in this month.. + //return yearWoy+1; + return yearWoy; default: // assume the year is appropriate return yearWoy; @@ -3626,6 +3688,10 @@ Calendar::getActualMaximum(UCalendarDateFields field, UErrorCode& status) const result = getMaximum(field); break; + case UCAL_ORDINAL_MONTH: + result = inTemporalLeapYear(status) ? getMaximum(UCAL_ORDINAL_MONTH) : getLeastMaximum(UCAL_ORDINAL_MONTH); + break; + default: // For all other fields, do it the hard way.... result = getActualHelper(field, getLeastMaximum(field), getMaximum(field),status); @@ -3955,6 +4021,20 @@ Calendar::internalSet(EDateFields field, int32_t value) internalSet((UCalendarDateFields) field, value); } +int32_t Calendar::internalGetMonth() const { + if (resolveFields(kMonthPrecedence) == UCAL_MONTH) { + return internalGet(UCAL_MONTH); + } + return internalGet(UCAL_ORDINAL_MONTH); +} + +int32_t Calendar::internalGetMonth(int32_t defaultValue) const { + if (resolveFields(kMonthPrecedence) == UCAL_MONTH) { + return internalGet(UCAL_MONTH, defaultValue); + } + return internalGet(UCAL_ORDINAL_MONTH); +} + BasicTimeZone* Calendar::getBasicTimeZone(void) const { if (dynamic_cast(fZone) != NULL diff --git a/icu4c/source/i18n/cecal.cpp b/icu4c/source/i18n/cecal.cpp index 2621056c497..456801ee370 100644 --- a/icu4c/source/i18n/cecal.cpp +++ b/icu4c/source/i18n/cecal.cpp @@ -13,6 +13,7 @@ #include "cecal.h" #include "gregoimp.h" //Math +#include "cstring.h" U_NAMESPACE_BEGIN @@ -42,6 +43,7 @@ static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH + { 0, 0, 12, 12}, // ORDINAL_MONTH }; //------------------------------------------------------------------------- @@ -132,6 +134,24 @@ CECalendar::jdToCE(int32_t julianDay, int32_t jdEpochOffset, int32_t& year, int3 day = (doy % 30) + 1; // 1-based days in a month } +static const char* kMonthCode13 = "M13"; + +const char* CECalendar::getTemporalMonthCode(UErrorCode& status) const { + if (get(UCAL_MONTH, status) == 12) return kMonthCode13; + return Calendar::getTemporalMonthCode(status); +} + +void +CECalendar::setTemporalMonthCode(const char* code, UErrorCode& status) { + if (U_FAILURE(status)) return; + if (uprv_strcmp(code, kMonthCode13) == 0) { + set(UCAL_MONTH, 12); + set(UCAL_IS_LEAP_MONTH, 0); + return; + } + Calendar::setTemporalMonthCode(code, status); +} + U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/cecal.h b/icu4c/source/i18n/cecal.h index 711e2aa2526..16f36a7976c 100644 --- a/icu4c/source/i18n/cecal.h +++ b/icu4c/source/i18n/cecal.h @@ -24,6 +24,36 @@ U_NAMESPACE_BEGIN */ class U_I18N_API CECalendar : public Calendar { +public: + + /** + * 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". + * + * @param status ICU Error Code + * @return One of 13 possible strings in {"M01".. "M12", "M13"}. + * @draft ICU 73 + */ + virtual const char* getTemporalMonthCode(UErrorCode& status) const override; + + /** + * 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 The value to be set for temporal monthCode. + * @param status ICU Error Code + * + * @draft ICU 73 + */ + virtual void setTemporalMonthCode(const char* code, UErrorCode& status) override; + protected: //------------------------------------------------------------------------- // Constructors... diff --git a/icu4c/source/i18n/chnsecal.cpp b/icu4c/source/i18n/chnsecal.cpp index c1e131247cb..01ddbd1ee41 100644 --- a/icu4c/source/i18n/chnsecal.cpp +++ b/icu4c/source/i18n/chnsecal.cpp @@ -26,6 +26,7 @@ #include "unicode/simpletz.h" #include "uhash.h" #include "ucln_in.h" +#include "cstring.h" // Debugging #ifdef U_DEBUG_CHNSECAL @@ -124,7 +125,7 @@ ChineseCalendar* ChineseCalendar::clone() const { ChineseCalendar::ChineseCalendar(const Locale& aLocale, UErrorCode& success) : Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success), - isLeapYear(false), + hasLeapMonthBetweenWinterSolstices(false), fEpochYear(CHINESE_EPOCH_YEAR), fZoneAstroCalc(getChineseCalZoneAstroCalc()) { @@ -134,7 +135,7 @@ ChineseCalendar::ChineseCalendar(const Locale& aLocale, UErrorCode& success) ChineseCalendar::ChineseCalendar(const Locale& aLocale, int32_t epochYear, const TimeZone* zoneAstroCalc, UErrorCode &success) : Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success), - isLeapYear(false), + hasLeapMonthBetweenWinterSolstices(false), fEpochYear(epochYear), fZoneAstroCalc(zoneAstroCalc) { @@ -142,7 +143,7 @@ ChineseCalendar::ChineseCalendar(const Locale& aLocale, int32_t epochYear, } ChineseCalendar::ChineseCalendar(const ChineseCalendar& other) : Calendar(other) { - isLeapYear = other.isLeapYear; + hasLeapMonthBetweenWinterSolstices = other.hasLeapMonthBetweenWinterSolstices; fEpochYear = other.fEpochYear; fZoneAstroCalc = other.fZoneAstroCalc; } @@ -196,6 +197,7 @@ static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY { 0, 0, 1, 1}, // IS_LEAP_MONTH + { 0, 0, 11, 12}, // ORDINAL_MONTH }; @@ -321,7 +323,6 @@ const UFieldResolutionTable* ChineseCalendar::getFieldResolutionTable() const { * @stable ICU 2.8 */ int32_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool useMonth) const { - ChineseCalendar *nonConstThis = (ChineseCalendar*)this; // cast away const // If the month is out of range, adjust it into range, and @@ -340,6 +341,7 @@ int32_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U // Save fields for later restoration int32_t saveMonth = internalGet(UCAL_MONTH); + int32_t saveOrdinalMonth = internalGet(UCAL_ORDINAL_MONTH); int32_t saveIsLeapMonth = internalGet(UCAL_IS_LEAP_MONTH); // Ignore IS_LEAP_MONTH field if useMonth is false @@ -361,8 +363,8 @@ int32_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U } nonConstThis->internalSet(UCAL_MONTH, saveMonth); + nonConstThis->internalSet(UCAL_ORDINAL_MONTH, saveOrdinalMonth); nonConstThis->internalSet(UCAL_IS_LEAP_MONTH, saveIsLeapMonth); - return julianDay - 1; } @@ -374,6 +376,7 @@ int32_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U void ChineseCalendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status) { switch (field) { case UCAL_MONTH: + case UCAL_ORDINAL_MONTH: if (amount != 0) { int32_t dom = get(UCAL_DAY_OF_MONTH, status); if (U_FAILURE(status)) break; @@ -404,6 +407,7 @@ void ChineseCalendar::add(EDateFields field, int32_t amount, UErrorCode& status) void ChineseCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& status) { switch (field) { case UCAL_MONTH: + case UCAL_ORDINAL_MONTH: if (amount != 0) { int32_t dom = get(UCAL_DAY_OF_MONTH, status); if (U_FAILURE(status)) break; @@ -419,7 +423,7 @@ void ChineseCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode // leap year. int32_t m = get(UCAL_MONTH, status); // 0-based month if (U_FAILURE(status)) break; - if (isLeapYear) { // (member variable) + if (hasLeapMonthBetweenWinterSolstices) { // (member variable) if (get(UCAL_IS_LEAP_MONTH, status) == 1) { ++m; } else { @@ -442,7 +446,7 @@ void ChineseCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode // Now do the standard roll computation on m, with the // allowed range of 0..n-1, where n is 12 or 13. - int32_t n = isLeapYear ? 13 : 12; // Months in this year + int32_t n = hasLeapMonthBetweenWinterSolstices ? 13 : 12; // Months in this year int32_t newM = (m + amount) % n; if (newM < 0) { newM += n; @@ -661,7 +665,7 @@ UBool ChineseCalendar::isLeapMonthBetween(int32_t newMoon1, int32_t newMoon2) co * 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 @@ -672,7 +676,6 @@ UBool ChineseCalendar::isLeapMonthBetween(int32_t newMoon1, int32_t newMoon2) co */ void ChineseCalendar::computeChineseFields(int32_t days, int32_t gyear, int32_t gmonth, UBool setAllFields) { - // Find the winter solstices before and after the target date. // These define the boundaries of this Chinese year, specifically, // the position of month 11, which always contains the solstice. @@ -692,24 +695,33 @@ void ChineseCalendar::computeChineseFields(int32_t days, int32_t gyear, int32_t int32_t firstMoon = newMoonNear(solsticeBefore + 1, true); int32_t lastMoon = newMoonNear(solsticeAfter + 1, false); int32_t 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; int32_t month = synodicMonthsBetween(firstMoon, thisMoon); - if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) { + int32_t theNewYear = newYear(gyear); + if (days < theNewYear) { + theNewYear = newYear(gyear-1); + } + if (hasLeapMonthBetweenWinterSolstices && isLeapMonthBetween(firstMoon, thisMoon)) { month--; } if (month < 1) { month += 12; } - - UBool isLeapMonth = isLeapYear && + int32_t ordinalMonth = synodicMonthsBetween(theNewYear, thisMoon); + if (ordinalMonth < 0) { + ordinalMonth += 12; + } + UBool isLeapMonth = hasLeapMonthBetweenWinterSolstices && hasNoMajorSolarTerm(thisMoon) && !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false)); internalSet(UCAL_MONTH, month-1); // Convert from 1-based to 0-based + internalSet(UCAL_ORDINAL_MONTH, ordinalMonth); // Convert from 1-based to 0-based internalSet(UCAL_IS_LEAP_MONTH, isLeapMonth?1:0); + if (setAllFields) { // Extended year and cycle year is based on the epoch year @@ -897,8 +909,83 @@ ChineseCalendar::internalGetDefaultCenturyStartYear() const return gSystemDefaultCenturyStartYear; } +bool +ChineseCalendar::inTemporalLeapYear(UErrorCode &status) const +{ + int32_t days = getActualMaximum(UCAL_DAY_OF_YEAR, status); + if (U_FAILURE(status)) return false; + return days > 360; +} + UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ChineseCalendar) + +static const char * const gTemporalLeapMonthCodes[] = { + "M01L", "M02L", "M03L", "M04L", "M05L", "M06L", + "M07L", "M08L", "M09L", "M10L", "M11L", "M12L", nullptr +}; + +const char* ChineseCalendar::getTemporalMonthCode(UErrorCode &status) const { + // We need to call get, not internalGet, to force the calculation + // from UCAL_ORDINAL_MONTH. + int32_t is_leap = get(UCAL_IS_LEAP_MONTH, status); + if (U_FAILURE(status)) return nullptr; + if (is_leap != 0) { + int32_t month = get(UCAL_MONTH, status); + if (U_FAILURE(status)) return nullptr; + return gTemporalLeapMonthCodes[month]; + } + return Calendar::getTemporalMonthCode(status); +} + +void +ChineseCalendar::setTemporalMonthCode(const char* code, UErrorCode& status ) +{ + if (U_FAILURE(status)) return; + int32_t len = static_cast(uprv_strlen(code)); + if (len != 4 || code[0] != 'M' || code[3] != 'L') { + set(UCAL_IS_LEAP_MONTH, 0); + return Calendar::setTemporalMonthCode(code, status); + } + for (int m = 0; gTemporalLeapMonthCodes[m] != nullptr; m++) { + if (uprv_strcmp(code, gTemporalLeapMonthCodes[m]) == 0) { + set(UCAL_MONTH, m); + set(UCAL_IS_LEAP_MONTH, 1); + return; + } + } + status = U_ILLEGAL_ARGUMENT_ERROR; +} + +int32_t ChineseCalendar::internalGetMonth() const { + if (resolveFields(kMonthPrecedence) == UCAL_MONTH) { + return internalGet(UCAL_MONTH); + } + LocalPointer temp(this->clone()); + temp->set(UCAL_MONTH, 0); + temp->set(UCAL_IS_LEAP_MONTH, 0); + temp->set(UCAL_DATE, 1); + // Calculate the UCAL_MONTH and UCAL_IS_LEAP_MONTH by adding number of + // months. + UErrorCode status = U_ZERO_ERROR; + temp->roll(UCAL_MONTH, internalGet(UCAL_ORDINAL_MONTH), status); + + + ChineseCalendar *nonConstThis = (ChineseCalendar*)this; // cast away const + nonConstThis->internalSet(UCAL_IS_LEAP_MONTH, temp->get(UCAL_IS_LEAP_MONTH, status)); + int32_t month = temp->get(UCAL_MONTH, status); + U_ASSERT(U_SUCCESS(status)); + nonConstThis->internalSet(UCAL_MONTH, month); + return month; +} + +int32_t ChineseCalendar::internalGetMonth(int32_t defaultValue) const { + if (resolveFields(kMonthPrecedence) == UCAL_MONTH) { + return internalGet(UCAL_MONTH, defaultValue); + } + return internalGetMonth(); +} + U_NAMESPACE_END #endif diff --git a/icu4c/source/i18n/chnsecal.h b/icu4c/source/i18n/chnsecal.h index 8e45fb20426..cc4aa8026ab 100644 --- a/icu4c/source/i18n/chnsecal.h +++ b/icu4c/source/i18n/chnsecal.h @@ -113,6 +113,49 @@ class U_I18N_API ChineseCalendar : public Calendar { */ ChineseCalendar(const Locale& aLocale, UErrorCode &success); + /** + * Returns true if the date is in a leap year. + * + * @param status ICU Error Code + * @return True if the date in the fields is in a Temporal proposal + * defined leap year. False otherwise. + */ + virtual bool inTemporalLeapYear(UErrorCode &status) const override; + + /** + * 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 Chinese calendars (including Dangi), the values are + * "M01" .. "M12" for non-leap year, and "M01" .. "M12" with one of + * "M01L" .. "M12L" for leap year. + * + * @param status ICU Error Code + * @return One of 24 possible strings in + * {"M01" .. "M12", "M01L" .. "M12L"}. + * @draft ICU 73 + */ + virtual const char* getTemporalMonthCode(UErrorCode &status) const override; + + /** + * 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 Chinese calendars, the values + * are "M01" .. "M12" for non-leap years, and "M01" .. "M12" plus one in + * "M01L" .. "M12L" for leap year. + * + * @param temporalMonth The value to be set for temporal monthCode. One of + * 24 possible strings in {"M01" .. "M12", "M01L" .. "M12L"}. + * @param status ICU Error Code + * + * @draft ICU 73 + */ + virtual void setTemporalMonthCode(const char* code, UErrorCode& status) override; + protected: /** @@ -152,7 +195,12 @@ class U_I18N_API ChineseCalendar : public Calendar { // Internal data.... //------------------------------------------------------------------------- - UBool isLeapYear; + // 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. + UBool hasLeapMonthBetweenWinterSolstices; int32_t fEpochYear; // Start year of this Chinese calendar instance. const TimeZone* fZoneAstroCalc; // Zone used for the astronomical calculation // of this Chinese calendar instance. @@ -241,6 +289,10 @@ class U_I18N_API ChineseCalendar : public Calendar { */ virtual const char * getType() const override; + protected: + virtual int32_t internalGetMonth(int32_t defaultValue) const override; + + virtual int32_t internalGetMonth() const override; protected: /** diff --git a/icu4c/source/i18n/coptccal.cpp b/icu4c/source/i18n/coptccal.cpp index 59609f63c21..a957f8f2c56 100644 --- a/icu4c/source/i18n/coptccal.cpp +++ b/icu4c/source/i18n/coptccal.cpp @@ -92,6 +92,7 @@ CopticCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/) internalSet(UCAL_ERA, era); internalSet(UCAL_YEAR, year); internalSet(UCAL_MONTH, month); + internalSet(UCAL_ORDINAL_MONTH, month); internalSet(UCAL_DATE, day); internalSet(UCAL_DAY_OF_YEAR, (30 * month) + day); } diff --git a/icu4c/source/i18n/ethpccal.cpp b/icu4c/source/i18n/ethpccal.cpp index b9ec17b46fe..be4010843a5 100644 --- a/icu4c/source/i18n/ethpccal.cpp +++ b/icu4c/source/i18n/ethpccal.cpp @@ -80,6 +80,7 @@ EthiopicCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/) internalSet(UCAL_ERA, (eyear > 0) ? AMETE_MIHRET : AMETE_ALEM); internalSet(UCAL_YEAR, (eyear > 0) ? eyear : (eyear + AMETE_MIHRET_DELTA)); internalSet(UCAL_MONTH, month); + internalSet(UCAL_ORDINAL_MONTH, month); internalSet(UCAL_DATE, day); internalSet(UCAL_DAY_OF_YEAR, (30 * month) + day); } @@ -216,6 +217,7 @@ EthiopicAmeteAlemCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/* internalSet(UCAL_ERA, AMETE_ALEM); internalSet(UCAL_YEAR, eyear + AMETE_MIHRET_DELTA); internalSet(UCAL_MONTH, month); + internalSet(UCAL_ORDINAL_MONTH, month); internalSet(UCAL_DATE, day); internalSet(UCAL_DAY_OF_YEAR, (30 * month) + day); } diff --git a/icu4c/source/i18n/gregocal.cpp b/icu4c/source/i18n/gregocal.cpp index 0e00127e314..481f410bad7 100644 --- a/icu4c/source/i18n/gregocal.cpp +++ b/icu4c/source/i18n/gregocal.cpp @@ -101,6 +101,7 @@ static const int32_t kGregorianCalendarLimits[UCAL_FIELD_COUNT][4] = { {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH + { 0, 0, 11, 11}, // ORDINAL_MONTH }; /* @@ -435,6 +436,7 @@ void GregorianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& statu } internalSet(UCAL_MONTH, month); + internalSet(UCAL_ORDINAL_MONTH, month); internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); internalSet(UCAL_DAY_OF_YEAR, dayOfYear); internalSet(UCAL_EXTENDED_YEAR, eyear); @@ -633,7 +635,7 @@ GregorianCalendar::yearLength() const void GregorianCalendar::pinDayOfMonth() { - int32_t monthLen = monthLength(internalGet(UCAL_MONTH)); + int32_t monthLen = monthLength(internalGetMonth()); int32_t dom = internalGet(UCAL_DATE); if(dom > monthLen) set(UCAL_DATE, monthLen); @@ -659,7 +661,7 @@ GregorianCalendar::validateFields() const if (isSet(UCAL_DATE)) { int32_t date = internalGet(UCAL_DATE); if (date < getMinimum(UCAL_DATE) || - date > monthLength(internalGet(UCAL_MONTH))) { + date > monthLength(internalGetMonth())) { return false; } } @@ -839,7 +841,7 @@ GregorianCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& s case UCAL_DAY_OF_MONTH: case UCAL_WEEK_OF_MONTH: { - int32_t max = monthLength(internalGet(UCAL_MONTH)); + int32_t max = monthLength(internalGetMonth()); UDate t = internalGetTime(); // We subtract 1 from the DAY_OF_MONTH to make it zero-based, and an // additional 10 if we are after the cutover. Thus the monthStart @@ -872,7 +874,7 @@ GregorianCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& s // may be one year before or after the calendar year. int32_t isoYear = get(UCAL_YEAR_WOY, status); int32_t isoDoy = internalGet(UCAL_DAY_OF_YEAR); - if (internalGet(UCAL_MONTH) == UCAL_JANUARY) { + if (internalGetMonth() == UCAL_JANUARY) { if (woy >= 52) { isoDoy += handleGetYearLength(isoYear); } diff --git a/icu4c/source/i18n/hebrwcal.cpp b/icu4c/source/i18n/hebrwcal.cpp index 6c47915fbe4..a7027e07a41 100644 --- a/icu4c/source/i18n/hebrwcal.cpp +++ b/icu4c/source/i18n/hebrwcal.cpp @@ -20,6 +20,7 @@ #if !UCONFIG_NO_FORMATTING #include "cmemory.h" +#include "cstring.h" #include "umutex.h" #include #include "gregoimp.h" // ClockMath @@ -63,6 +64,7 @@ static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH + { 0, 0, 11, 12}, // ORDINAL_MONTH }; /** @@ -217,7 +219,8 @@ void HebrewCalendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& return; } switch (field) { - case UCAL_MONTH: + case UCAL_MONTH: + case UCAL_ORDINAL_MONTH: { // We can't just do a set(MONTH, get(MONTH) + amount). The // reason is ADAR_1. Suppose amount is +2 and we land in @@ -315,6 +318,7 @@ void HebrewCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& } switch (field) { case UCAL_MONTH: + case UCAL_ORDINAL_MONTH: { int32_t month = get(UCAL_MONTH, status); int32_t year = get(UCAL_YEAR, status); @@ -534,7 +538,8 @@ int32_t HebrewCalendar::handleGetYearLength(int32_t eyear) const { } void HebrewCalendar::validateField(UCalendarDateFields field, UErrorCode &status) { - if (field == UCAL_MONTH && !isLeapYear(handleGetExtendedYear()) && internalGet(UCAL_MONTH) == ADAR_1) { + if ((field == UCAL_MONTH || field == UCAL_ORDINAL_MONTH) + && !isLeapYear(handleGetExtendedYear()) && internalGetMonth() == ADAR_1) { status = U_ILLEGAL_ARGUMENT_ERROR; return; } @@ -607,6 +612,11 @@ void HebrewCalendar::handleComputeFields(int32_t julianDay, UErrorCode &status) internalSet(UCAL_ERA, 0); internalSet(UCAL_YEAR, year); internalSet(UCAL_EXTENDED_YEAR, year); + int32_t ordinal_month = month; + if (!isLeap && ordinal_month > ADAR_1) { + ordinal_month--; + } + internalSet(UCAL_ORDINAL_MONTH, ordinal_month); internalSet(UCAL_MONTH, month); internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); internalSet(UCAL_DAY_OF_YEAR, dayOfYear); @@ -728,6 +738,49 @@ int32_t HebrewCalendar::defaultCenturyStartYear() const { return gSystemDefaultCenturyStartYear; } +bool HebrewCalendar::inTemporalLeapYear(UErrorCode& status) const { + if (U_FAILURE(status)) return false; + int32_t eyear = get(UCAL_EXTENDED_YEAR, status); + if (U_FAILURE(status)) return false; + return isLeapYear(eyear); +} + +static const char * const gTemporalMonthCodesForHebrew[] = { + "M01", "M02", "M03", "M04", "M05", "M05L", "M06", + "M07", "M08", "M09", "M10", "M11", "M12", nullptr +}; + +const char* HebrewCalendar::getTemporalMonthCode(UErrorCode& status) const { + int32_t month = get(UCAL_MONTH, status); + if (U_FAILURE(status)) return nullptr; + return gTemporalMonthCodesForHebrew[month]; +} + +void HebrewCalendar::setTemporalMonthCode(const char* code, UErrorCode& status ) +{ + if (U_FAILURE(status)) return; + int32_t len = static_cast(uprv_strlen(code)); + if (len == 3 || len == 4) { + for (int m = 0; gTemporalMonthCodesForHebrew[m] != nullptr; m++) { + if (uprv_strcmp(code, gTemporalMonthCodesForHebrew[m]) == 0) { + set(UCAL_MONTH, m); + return; + } + } + } + status = U_ILLEGAL_ARGUMENT_ERROR; +} + +int32_t HebrewCalendar::internalGetMonth() const { + if (resolveFields(kMonthPrecedence) == UCAL_ORDINAL_MONTH) { + int32_t ordinalMonth = internalGet(UCAL_ORDINAL_MONTH); + HebrewCalendar *nonConstThis = (HebrewCalendar*)this; // cast away const + + int32_t year = nonConstThis->handleGetExtendedYear(); + return ordinalMonth + ((isLeapYear(year) && (ordinalMonth > ADAR_1)) ? 1: 0); + } + return Calendar::internalGetMonth(); +} UOBJECT_DEFINE_RTTI_IMPLEMENTATION(HebrewCalendar) diff --git a/icu4c/source/i18n/hebrwcal.h b/icu4c/source/i18n/hebrwcal.h index 40a8f7a8f70..21c54e77092 100644 --- a/icu4c/source/i18n/hebrwcal.h +++ b/icu4c/source/i18n/hebrwcal.h @@ -399,6 +399,51 @@ public: */ virtual int32_t defaultCenturyStartYear() const override; + public: + /** + * Returns true if the date is in a leap year. + * + * @param status ICU Error Code + * @return True if the date in the fields is in a Temporal proposal + * defined leap year. False otherwise. + */ + virtual bool inTemporalLeapYear(UErrorCode& status) const override; + + /** + * 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 Hebrew calendar, the values are "M01" .. "M12" for + * non-leap year, and "M01" .. "M05", "M05L", "M06" .. "M12" for leap year. + * + * @param status ICU Error Code + * @return One of 13 possible strings in {"M01".. "M05", "M05L", + * "M06" .. "M12"}. + * @draft ICU 73 + */ + virtual const char* getTemporalMonthCode(UErrorCode& status) const override; + + /** + * 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 Hebrew calendar, the values + * are "M01" .. "M12" for non-leap years, and "M01" .. "M05", "M05L", "M06" + * .. "M12" for leap year. + * + * @param temporalMonth The value to be set for temporal monthCode. + * @param status ICU Error Code + * + * @draft ICU 73 + */ + virtual void setTemporalMonthCode(const char* code, UErrorCode& status ) override; + + protected: + virtual int32_t internalGetMonth() const override; + private: // Calendar-specific implementation /** * Finds the day # of the first day in the given Hebrew year. diff --git a/icu4c/source/i18n/indiancal.cpp b/icu4c/source/i18n/indiancal.cpp index 938b74ee0d2..29c2749f48c 100644 --- a/icu4c/source/i18n/indiancal.cpp +++ b/icu4c/source/i18n/indiancal.cpp @@ -80,6 +80,7 @@ static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH + { 0, 0, 11, 11}, // ORDINAL_MONTH }; static const int32_t INDIAN_ERA_START = 78; @@ -293,6 +294,7 @@ void IndianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& /* stat internalSet(UCAL_EXTENDED_YEAR, IndianYear); internalSet(UCAL_YEAR, IndianYear); internalSet(UCAL_MONTH, IndianMonth); + internalSet(UCAL_ORDINAL_MONTH, IndianMonth); internalSet(UCAL_DAY_OF_MONTH, IndianDayOfMonth); internalSet(UCAL_DAY_OF_YEAR, yday + 1); // yday is 0-based } diff --git a/icu4c/source/i18n/islamcal.cpp b/icu4c/source/i18n/islamcal.cpp index 2b6d32210be..7b4bf96c041 100644 --- a/icu4c/source/i18n/islamcal.cpp +++ b/icu4c/source/i18n/islamcal.cpp @@ -259,6 +259,7 @@ static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH + { 0, 0, 11, 11}, // ORDINAL_MONTH }; /** @@ -546,6 +547,7 @@ void IslamicCalendar::handleComputeFields(int32_t julianDay, UErrorCode &status) internalSet(UCAL_YEAR, year); internalSet(UCAL_EXTENDED_YEAR, year); internalSet(UCAL_MONTH, month); + internalSet(UCAL_ORDINAL_MONTH, month); internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); internalSet(UCAL_DAY_OF_YEAR, dayOfYear); } @@ -631,6 +633,14 @@ int32_t IslamicCalendar::defaultCenturyStartYear() const return gSystemDefaultCenturyStartYear; } +bool +IslamicCalendar::inTemporalLeapYear(UErrorCode &status) const +{ + int32_t days = getActualMaximum(UCAL_DAY_OF_YEAR, status); + if (U_FAILURE(status)) return false; + return days == 355; +} + U_CFUNC void U_CALLCONV IslamicCalendar::initializeSystemDefaultCentury() @@ -757,6 +767,7 @@ void IslamicCivilCalendar::handleComputeFields(int32_t julianDay, UErrorCode &st internalSet(UCAL_YEAR, year); internalSet(UCAL_EXTENDED_YEAR, year); internalSet(UCAL_MONTH, month); + internalSet(UCAL_ORDINAL_MONTH, month); internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); internalSet(UCAL_DAY_OF_YEAR, dayOfYear); } @@ -932,6 +943,7 @@ void IslamicUmalquraCalendar::handleComputeFields(int32_t julianDay, UErrorCode internalSet(UCAL_YEAR, year); internalSet(UCAL_EXTENDED_YEAR, year); internalSet(UCAL_MONTH, month); + internalSet(UCAL_ORDINAL_MONTH, month); internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); internalSet(UCAL_DAY_OF_YEAR, dayOfYear); } diff --git a/icu4c/source/i18n/islamcal.h b/icu4c/source/i18n/islamcal.h index 6d7c0503a2c..0ad241e60f4 100644 --- a/icu4c/source/i18n/islamcal.h +++ b/icu4c/source/i18n/islamcal.h @@ -350,6 +350,15 @@ class U_I18N_API IslamicCalendar : public Calendar { */ virtual void setRelatedYear(int32_t year) override; + /** + * Returns true if the date is in a leap year. + * + * @param status ICU Error Code + * @return True if the date in the fields is in a Temporal proposal + * defined leap year. False otherwise. + */ + virtual bool inTemporalLeapYear(UErrorCode &status) const override; + private: IslamicCalendar() = delete; // default constructor not implemented diff --git a/icu4c/source/i18n/japancal.cpp b/icu4c/source/i18n/japancal.cpp index ca9b0704a00..0af7e8bb6f4 100644 --- a/icu4c/source/i18n/japancal.cpp +++ b/icu4c/source/i18n/japancal.cpp @@ -219,7 +219,7 @@ void JapaneseCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status //Calendar::timeToFields(theTime, quick, status); GregorianCalendar::handleComputeFields(julianDay, status); int32_t year = internalGet(UCAL_EXTENDED_YEAR); // Gregorian year - int32_t eraIdx = gJapaneseEraRules->getEraIndex(year, internalGet(UCAL_MONTH) + 1, internalGet(UCAL_DAY_OF_MONTH), status); + int32_t eraIdx = gJapaneseEraRules->getEraIndex(year, internalGetMonth() + 1, internalGet(UCAL_DAY_OF_MONTH), status); internalSet(UCAL_ERA, eraIdx); internalSet(UCAL_YEAR, year - gJapaneseEraRules->getStartYear(eraIdx, status) + 1); diff --git a/icu4c/source/i18n/persncal.cpp b/icu4c/source/i18n/persncal.cpp index a3f5e6ad4f5..ab13f434348 100644 --- a/icu4c/source/i18n/persncal.cpp +++ b/icu4c/source/i18n/persncal.cpp @@ -58,6 +58,7 @@ static const int32_t kPersianCalendarLimits[UCAL_FIELD_COUNT][4] = { {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH + { 0, 0, 11, 11}, // ORDINAL_MONTH }; U_NAMESPACE_BEGIN @@ -229,6 +230,7 @@ void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*statu internalSet(UCAL_YEAR, year); internalSet(UCAL_EXTENDED_YEAR, year); internalSet(UCAL_MONTH, month); + internalSet(UCAL_ORDINAL_MONTH, month); internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); internalSet(UCAL_DAY_OF_YEAR, dayOfYear); } diff --git a/icu4c/source/i18n/unicode/calendar.h b/icu4c/source/i18n/unicode/calendar.h index f7cca52106e..ec754a75149 100644 --- a/icu4c/source/i18n/unicode/calendar.h +++ b/icu4c/source/i18n/unicode/calendar.h @@ -1349,6 +1349,66 @@ public: */ virtual UBool isWeekend(void) const; +#ifndef U_HIDE_DRAFT_API + /** + * 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. + * + * @param status ICU Error Code + * @return True if the date in the fields is in a Temporal proposal + * defined leap year. False otherwise. + * @draft ICU 73 + */ + virtual bool inTemporalLeapYear(UErrorCode& status) const; + + /** + * 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. The 25 possible values are "M01" .. "M13" and "M01L" .. "M12L". + * For the Hebrew calendar, the values are "M01" .. "M12" for non-leap year, and + * "M01" .. "M05", "M05L", "M06" .. "M12" for leap year. + * For the Chinese calendar, the values are "M01" .. "M12" for non-leap year and + * in leap year with another monthCode in "M01L" .. "M12L". + * For Coptic and Ethiopian calendar, the Temporal monthCode values for any + * years are "M01" to "M13". + * + * @param status ICU Error Code + * @return One of 25 possible strings in {"M01".."M13", "M01L".."M12L"}. + * @draft ICU 73 + */ + virtual const char* getTemporalMonthCode(UErrorCode& status) const; + + /** + * 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. The 25 possible values are + * "M01" .. "M13" and "M01L" .. "M12L". For Hebrew calendar, the values are + * "M01" .. "M12" for non-leap years, and "M01" .. "M05", "M05L", "M06" + * .. "M12" for leap year. + * For the Chinese calendar, the values are "M01" .. "M12" for non-leap year and + * in leap year with another monthCode in "M01L" .. "M12L". + * For Coptic and Ethiopian calendar, the Temporal monthCode values for any + * years are "M01" to "M13". + * + * @param temporalMonth The value to be set for temporal monthCode. + * @param status ICU Error Code + * + * @draft ICU 73 + */ + virtual void setTemporalMonthCode(const char* temporalMonth, UErrorCode& status); + +#endif /* U_HIDE_DRAFT_API */ + protected: /** @@ -1489,6 +1549,31 @@ protected: * @internal */ inline int32_t internalGet(UCalendarDateFields field) const {return fFields[field];} + + /** + * Use this function instead of internalGet(UCAL_MONTH). The implementation + * check the timestamp of UCAL_MONTH and UCAL_ORDINAL_MONTH and use the + * one set later. The subclass should override it to conver the value of UCAL_ORDINAL_MONTH + * to UCAL_MONTH correctly if UCAL_ORDINAL_MONTH has higher priority. + * + * @return The value for the UCAL_MONTH. + * @internal + */ + virtual int32_t internalGetMonth() const; + + /** + * Use this function instead of internalGet(UCAL_MONTH, defaultValue). The implementation + * check the timestamp of UCAL_MONTH and UCAL_ORDINAL_MONTH and use the + * one set later. The subclass should override it to conver the value of UCAL_ORDINAL_MONTH + * to UCAL_MONTH correctly if UCAL_ORDINAL_MONTH has higher priority. + * + * @param defaultValue a default value used if the UCAL_MONTH and + * UCAL_ORDINAL are both unset. + * @return The value for the UCAL_MONTH. + * @internal + */ + virtual int32_t internalGetMonth(int32_t defaultValue) const; + #endif /* U_HIDE_INTERNAL_API */ #ifndef U_HIDE_DEPRECATED_API @@ -1569,7 +1654,6 @@ protected: */ virtual int32_t getLimit(UCalendarDateFields field, ELimitType limitType) const; - /** * Return the Julian day number of day before the first day of the * given month in the given extended year. Subclasses should override @@ -1722,6 +1806,13 @@ protected: */ static const UFieldResolutionTable kDOWPrecedence[]; + /** + * Precedence table for Months + * @see #resolveFields + * @internal + */ + static const UFieldResolutionTable kMonthPrecedence[]; + /** * Given a precedence table, return the newest field combination in * the table, or UCAL_FIELD_COUNT if none is found. @@ -1749,7 +1840,7 @@ protected: * match, then UCAL_FIELD_COUNT is returned. * @internal */ - UCalendarDateFields resolveFields(const UFieldResolutionTable *precedenceTable); + UCalendarDateFields resolveFields(const UFieldResolutionTable *precedenceTable) const; #endif /* U_HIDE_INTERNAL_API */ diff --git a/icu4c/source/i18n/unicode/ucal.h b/icu4c/source/i18n/unicode/ucal.h index 3a4fb69fc3e..533e796db57 100644 --- a/icu4c/source/i18n/unicode/ucal.h +++ b/icu4c/source/i18n/unicode/ucal.h @@ -442,6 +442,33 @@ enum UCalendarDateFields { */ UCAL_IS_LEAP_MONTH, +#ifndef U_HIDE_DRAFT_API + /** + * Field number indicating the month. This is a calendar-specific value. + * Differ from UCAL_MONTH, this value is continuous and unique within a + * year and range from 0 to 11 or 0 to 12 depending on how many months in a + * year, the calendar system has leap month or not, and in leap year or not. + * It is the ordinal position of that month in the corresponding year of + * the calendar. For Chinese, Dangi, and Hebrew calendar, the range is + * 0 to 11 in non-leap years and 0 to 12 in leap years. For Coptic and Ethiopian + * calendar, the range is always 0 to 12. For other calendars supported by + * ICU now, the range is 0 to 11. When the number of months in a year of the + * identified calendar is variable, a different UCAL_ORDINAL_MONTH value can + * be used for dates that are part of the same named month in different years. + * For example, in the Hebrew calendar, "1 Nisan 5781" is associated with + * UCAL_ORDINAL_MONTH value 6 while "1 Nisan 5782" is associated with + * UCAL_ORDINAL_MONTH value 7 because 5782 is a leap year and Nisan follows + * the insertion of Adar I. In Chinese calendar, "Year 4664 Month 6 Day 2" + * is associated with UCAL_ORDINAL_MONTH value 5 while "Year 4665 Month 6 Day 2" + * is associated with UCAL_ORDINAL_MONTH value 6 because 4665 is a leap year + * and there is an extra "Leap Month 5" which associated with UCAL_ORDINAL_MONTH + * value 5 before "Month 6" of year 4664. + * + * @draft ICU 73 + */ + UCAL_ORDINAL_MONTH, +#endif // U_HIDE_DRAFT_API + /* Do not conditionalize the following with #ifndef U_HIDE_DEPRECATED_API, * it is needed for layout of Calendar, DateFormat, and other objects */ #ifndef U_FORCE_HIDE_DEPRECATED_API @@ -449,7 +476,12 @@ enum UCalendarDateFields { * One more than the highest normal UCalendarDateFields value. * @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420. */ - UCAL_FIELD_COUNT, +#ifdef U_HIDE_DRAFT_API + UCAL_FIELD_COUNT = UCAL_IS_LEAP_MONTH + 1, +#else // U_HIDE_DRAFT_API (for UCAL_ORDINAL_MONTH) + UCAL_FIELD_COUNT = UCAL_ORDINAL_MONTH + 1, +#endif // U_HIDE_DRAFT_API (for UCAL_ORDINAL_MONTH) + #endif // U_FORCE_HIDE_DEPRECATED_API /** diff --git a/icu4c/source/test/intltest/caltest.cpp b/icu4c/source/test/intltest/caltest.cpp index 96c1ff9c80e..eb5b7fd5879 100644 --- a/icu4c/source/test/intltest/caltest.cpp +++ b/icu4c/source/test/intltest/caltest.cpp @@ -21,6 +21,9 @@ #include "unicode/ustring.h" #include "cstring.h" #include "unicode/localpointer.h" +#include "intltest.h" +#include "coptccal.h" +#include "ethpccal.h" #include "islamcal.h" #define mkcstr(U) u_austrcpy(calloc(8, u_strlen(U) + 1), U) @@ -86,303 +89,101 @@ UnicodeString CalendarTest::calToStr(const Calendar & cal) void CalendarTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) { if (exec) logln("TestSuite TestCalendar"); - switch (index) { - case 0: - name = "TestDOW943"; - if (exec) { - logln("TestDOW943---"); logln(""); - TestDOW943(); - } - break; - case 1: - name = "TestClonesUnique908"; - if (exec) { - logln("TestClonesUnique908---"); logln(""); - TestClonesUnique908(); - } - break; - case 2: - name = "TestGregorianChange768"; - if (exec) { - logln("TestGregorianChange768---"); logln(""); - TestGregorianChange768(); - } - break; - case 3: - name = "TestDisambiguation765"; - if (exec) { - logln("TestDisambiguation765---"); logln(""); - TestDisambiguation765(); - } - break; - case 4: - name = "TestGMTvsLocal4064654"; - if (exec) { - logln("TestGMTvsLocal4064654---"); logln(""); - TestGMTvsLocal4064654(); - } - break; - case 5: - name = "TestAddSetOrder621"; - if (exec) { - logln("TestAddSetOrder621---"); logln(""); - TestAddSetOrder621(); - } - break; - case 6: - name = "TestAdd520"; - if (exec) { - logln("TestAdd520---"); logln(""); - TestAdd520(); - } - break; - case 7: - name = "TestFieldSet4781"; - if (exec) { - logln("TestFieldSet4781---"); logln(""); - TestFieldSet4781(); - } - break; - case 8: - name = "TestSerialize337"; - if (exec) { - logln("TestSerialize337---"); logln(""); - // TestSerialize337(); - } - break; - case 9: - name = "TestSecondsZero121"; - if (exec) { - logln("TestSecondsZero121---"); logln(""); - TestSecondsZero121(); - } - break; - case 10: - name = "TestAddSetGet0610"; - if (exec) { - logln("TestAddSetGet0610---"); logln(""); - TestAddSetGet0610(); - } - break; - case 11: - name = "TestFields060"; - if (exec) { - logln("TestFields060---"); logln(""); - TestFields060(); - } - break; - case 12: - name = "TestEpochStartFields"; - if (exec) { - logln("TestEpochStartFields---"); logln(""); - TestEpochStartFields(); - } - break; - case 13: - name = "TestDOWProgression"; - if (exec) { - logln("TestDOWProgression---"); logln(""); - TestDOWProgression(); - } - break; - case 14: - name = "TestGenericAPI"; - if (exec) { - logln("TestGenericAPI---"); logln(""); - TestGenericAPI(); - } - break; - case 15: - name = "TestAddRollExtensive"; - if (exec) { - logln("TestAddRollExtensive---"); logln(""); - TestAddRollExtensive(); - } - break; - case 16: - name = "TestDOW_LOCALandYEAR_WOY"; - if (exec) { - logln("TestDOW_LOCALandYEAR_WOY---"); logln(""); - TestDOW_LOCALandYEAR_WOY(); - } - break; - case 17: - name = "TestWOY"; - if (exec) { - logln("TestWOY---"); logln(""); - TestWOY(); - } - break; - case 18: - name = "TestRog"; - if (exec) { - logln("TestRog---"); logln(""); - TestRog(); - } - break; - case 19: - name = "TestYWOY"; - if (exec) { - logln("TestYWOY---"); logln(""); - TestYWOY(); - } - break; - case 20: - name = "TestJD"; - if(exec) { - logln("TestJD---"); logln(""); - TestJD(); - } - break; - case 21: - name = "TestDebug"; - if(exec) { - logln("TestDebug---"); logln(""); - TestDebug(); - } - break; - case 22: - name = "Test6703"; - if(exec) { - logln("Test6703---"); logln(""); - Test6703(); - } - break; - case 23: - name = "Test3785"; - if(exec) { - logln("Test3785---"); logln(""); - Test3785(); - } - break; - case 24: - name = "Test1624"; - if(exec) { - logln("Test1624---"); logln(""); - Test1624(); - } - break; - case 25: - name = "TestTimeStamp"; - if(exec) { - logln("TestTimeStamp---"); logln(""); - TestTimeStamp(); - } - break; - case 26: - name = "TestISO8601"; - if(exec) { - logln("TestISO8601---"); logln(""); - TestISO8601(); - } - break; - case 27: - name = "TestAmbiguousWallTimeAPIs"; - if(exec) { - logln("TestAmbiguousWallTimeAPIs---"); logln(""); - TestAmbiguousWallTimeAPIs(); - } - break; - case 28: - name = "TestRepeatedWallTime"; - if(exec) { - logln("TestRepeatedWallTime---"); logln(""); - TestRepeatedWallTime(); - } - break; - case 29: - name = "TestSkippedWallTime"; - if(exec) { - logln("TestSkippedWallTime---"); logln(""); - TestSkippedWallTime(); - } - break; - case 30: - name = "TestCloneLocale"; - if(exec) { - logln("TestCloneLocale---"); logln(""); - TestCloneLocale(); - } - break; - case 31: - name = "TestIslamicUmAlQura"; - if(exec) { - logln("TestIslamicUmAlQura---"); logln(""); - TestIslamicUmAlQura(); - } - break; - case 32: - name = "TestIslamicTabularDates"; - if(exec) { - logln("TestIslamicTabularDates---"); logln(""); - TestIslamicTabularDates(); - } - break; - case 33: - name = "TestHebrewMonthValidation"; - if(exec) { - logln("TestHebrewMonthValidation---"); logln(""); - TestHebrewMonthValidation(); - } - break; - case 34: - name = "TestWeekData"; - if(exec) { - logln("TestWeekData---"); logln(""); - TestWeekData(); - } - break; - case 35: - name = "TestAddAcrossZoneTransition"; - if(exec) { - logln("TestAddAcrossZoneTransition---"); logln(""); - TestAddAcrossZoneTransition(); - } - break; - case 36: - name = "TestChineseCalendarMapping"; - if(exec) { - logln("TestChineseCalendarMapping---"); logln(""); - TestChineseCalendarMapping(); - } - break; - case 37: - name = "TestTimeZoneInLocale"; - if(exec) { - logln("TestTimeZoneInLocale---"); logln(""); - TestTimeZoneInLocale(); - } - break; -#define CASE(num, NAME) \ - case num: \ - name = #NAME; \ - if(exec) { \ - logln(#NAME "---"); logln(""); \ - NAME(); \ - } \ - break; - CASE(38, TestBasicConversionISO8601); - CASE(39, TestBasicConversionJapanese); - CASE(40, TestBasicConversionBuddhist); - CASE(41, TestBasicConversionTaiwan); - CASE(42, TestBasicConversionPersian); - CASE(43, TestBasicConversionIslamic); - CASE(44, TestBasicConversionIslamicTBLA); - CASE(45, TestBasicConversionIslamicCivil); - CASE(46, TestBasicConversionIslamicRGSA); - CASE(47, TestBasicConversionIslamicUmalqura); - CASE(48, TestBasicConversionHebrew); - CASE(49, TestBasicConversionChinese); - CASE(50, TestBasicConversionDangi); - CASE(51, TestBasicConversionIndian); - CASE(52, TestBasicConversionCoptic); - CASE(53, TestBasicConversionEthiopic); - CASE(54, TestBasicConversionEthiopicAmeteAlem); + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(TestDOW943); + TESTCASE_AUTO(TestClonesUnique908); + TESTCASE_AUTO(TestGregorianChange768); + TESTCASE_AUTO(TestDisambiguation765); + TESTCASE_AUTO(TestGMTvsLocal4064654); + TESTCASE_AUTO(TestAddSetOrder621); + TESTCASE_AUTO(TestAdd520); + TESTCASE_AUTO(TestFieldSet4781); + // TESTCASE_AUTO(TestSerialize337); + TESTCASE_AUTO(TestSecondsZero121); + TESTCASE_AUTO(TestAddSetGet0610); + TESTCASE_AUTO(TestFields060); + TESTCASE_AUTO(TestEpochStartFields); + TESTCASE_AUTO(TestDOWProgression); + TESTCASE_AUTO(TestGenericAPI); + TESTCASE_AUTO(TestAddRollExtensive); + TESTCASE_AUTO(TestDOW_LOCALandYEAR_WOY); + TESTCASE_AUTO(TestWOY); + TESTCASE_AUTO(TestRog); + TESTCASE_AUTO(TestYWOY); + TESTCASE_AUTO(TestJD); + TESTCASE_AUTO(TestDebug); + TESTCASE_AUTO(Test6703); + TESTCASE_AUTO(Test3785); + TESTCASE_AUTO(Test1624); + TESTCASE_AUTO(TestTimeStamp); + TESTCASE_AUTO(TestISO8601); + TESTCASE_AUTO(TestAmbiguousWallTimeAPIs); + TESTCASE_AUTO(TestRepeatedWallTime); + TESTCASE_AUTO(TestSkippedWallTime); + TESTCASE_AUTO(TestCloneLocale); + TESTCASE_AUTO(TestIslamicUmAlQura); + TESTCASE_AUTO(TestIslamicTabularDates); + TESTCASE_AUTO(TestHebrewMonthValidation); + TESTCASE_AUTO(TestWeekData); + TESTCASE_AUTO(TestAddAcrossZoneTransition); + TESTCASE_AUTO(TestChineseCalendarMapping); + TESTCASE_AUTO(TestTimeZoneInLocale); + TESTCASE_AUTO(TestBasicConversionISO8601); + TESTCASE_AUTO(TestBasicConversionJapanese); + TESTCASE_AUTO(TestBasicConversionBuddhist); + TESTCASE_AUTO(TestBasicConversionTaiwan); + TESTCASE_AUTO(TestBasicConversionPersian); + TESTCASE_AUTO(TestBasicConversionIslamic); + TESTCASE_AUTO(TestBasicConversionIslamicTBLA); + TESTCASE_AUTO(TestBasicConversionIslamicCivil); + TESTCASE_AUTO(TestBasicConversionIslamicRGSA); + TESTCASE_AUTO(TestBasicConversionIslamicUmalqura); + TESTCASE_AUTO(TestBasicConversionHebrew); + TESTCASE_AUTO(TestBasicConversionChinese); + TESTCASE_AUTO(TestBasicConversionDangi); + TESTCASE_AUTO(TestBasicConversionIndian); + TESTCASE_AUTO(TestBasicConversionCoptic); + TESTCASE_AUTO(TestBasicConversionEthiopic); + TESTCASE_AUTO(TestBasicConversionEthiopicAmeteAlem); + TESTCASE_AUTO(TestGregorianCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestChineseCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestDangiCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestHebrewCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestIslamicCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestIslamicCivilCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestIslamicUmalquraCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestIslamicRGSACalendarInTemporalLeapYear); + TESTCASE_AUTO(TestIslamicTBLACalendarInTemporalLeapYear); + TESTCASE_AUTO(TestPersianCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestIndianCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestTaiwanCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestJapaneseCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestBuddhistCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestCopticCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestEthiopicCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestEthiopicAmeteAlemCalendarInTemporalLeapYear); + TESTCASE_AUTO(TestChineseCalendarGetTemporalMonthCode); + TESTCASE_AUTO(TestDangiCalendarGetTemporalMonthCode); + TESTCASE_AUTO(TestHebrewCalendarGetTemporalMonthCode); + TESTCASE_AUTO(TestCopticCalendarGetTemporalMonthCode); + TESTCASE_AUTO(TestEthiopicCalendarGetTemporalMonthCode); + TESTCASE_AUTO(TestEthiopicAmeteAlemCalendarGetTemporalMonthCode); + TESTCASE_AUTO(TestGregorianCalendarSetTemporalMonthCode); + TESTCASE_AUTO(TestChineseCalendarSetTemporalMonthCode); + TESTCASE_AUTO(TestHebrewCalendarSetTemporalMonthCode); + TESTCASE_AUTO(TestCopticCalendarSetTemporalMonthCode); + TESTCASE_AUTO(TestEthiopicCalendarSetTemporalMonthCode); + TESTCASE_AUTO(TestMostCalendarsOrdinalMonthSet); + TESTCASE_AUTO(TestChineseCalendarOrdinalMonthSet); + TESTCASE_AUTO(TestDangiCalendarOrdinalMonthSet); + TESTCASE_AUTO(TestHebrewCalendarOrdinalMonthSet); + TESTCASE_AUTO(TestCalendarAddOrdinalMonth); + TESTCASE_AUTO(TestCalendarRollOrdinalMonth); + TESTCASE_AUTO(TestLimitsOrdinalMonth); + TESTCASE_AUTO(TestActualLimitsOrdinalMonth); -#undef CASE - default: name = ""; break; - } + TESTCASE_AUTO_END; } // --------------------------------------------------------------------------------- @@ -670,7 +471,7 @@ CalendarTest::TestGenericAPI() const char *charValue; int32_t valueLength; while ((charValue = values->next(&valueLength, status)) != NULL) { - if (valueLength == 6 && strcmp(charValue, "hebrew") == 0) { + if (valueLength == 6 && uprv_strcmp(charValue, "hebrew") == 0) { containsHebrew = true; } } @@ -3629,7 +3430,7 @@ void CalendarTest::TestIslamicUmAlQura() { Calendar* gregCal = Calendar::createInstance(*((const TimeZone *)tzSA), gregoLoc, status); IslamicCalendar* iCal = (IslamicCalendar*)tstCal; - if(strcmp(iCal->getType(), "islamic-umalqura") != 0) { + if(uprv_strcmp(iCal->getType(), "islamic-umalqura") != 0) { errln("wrong type of calendar created - %s", iCal->getType()); } @@ -4084,6 +3885,1502 @@ void CalendarTest::TestChineseCalendarMapping() { } } +void CalendarTest::TestGregorianCalendarInTemporalLeapYear(void) { + // test from year 1800 to 2500 + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + if (failure(status, "construct GregorianCalendar")) return; + for (int32_t year = 1900; year < 2400; ++year) { + gc.set(year, UCAL_MARCH, 7); + assertEquals("Calendar::inTemporalLeapYear", + gc.isLeapYear(year) == true, gc.inTemporalLeapYear(status) == true); + if (failure(status, "inTemporalLeapYear")) return; + } +} + +void CalendarTest::RunChineseCalendarInTemporalLeapYearTest(Calendar* cal) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + if (failure(status, "construct GregorianCalendar")) return; + LocalPointer leapTest(cal->clone()); + // Start our test from 1900, Jan 1. + // Check every 29 days in exhausted mode. + int32_t incrementDays = 29; + int32_t startYear = 1900; + int32_t stopYear = 2400; + + if (quick) { + incrementDays = 317; + stopYear = 2100; + } + int32_t yearForHasLeapMonth = -1; + bool hasLeapMonth = false; + for (gc.set(startYear, UCAL_JANUARY, 1); + gc.get(UCAL_YEAR, status) <= stopYear; + gc.add(UCAL_DATE, incrementDays, status)) { + cal->setTime(gc.getTime(status), status); + if (failure(status, "add/get/set/getTime/setTime incorrect")) return; + + int32_t cal_year = cal->get(UCAL_EXTENDED_YEAR, status); + if (yearForHasLeapMonth != cal_year) { + leapTest->set(UCAL_EXTENDED_YEAR, cal_year); + leapTest->set(UCAL_MONTH, 0); + leapTest->set(UCAL_DATE, 1); + // seek any leap month + // check any leap month in the next 12 months. + for (hasLeapMonth = false; + (!hasLeapMonth) && cal_year == leapTest->get(UCAL_EXTENDED_YEAR, status); + leapTest->add(UCAL_MONTH, 1, status)) { + hasLeapMonth = leapTest->get(UCAL_IS_LEAP_MONTH, status) != 0; + } + yearForHasLeapMonth = cal_year; + } + if (failure(status, "error while figure out expectation")) return; + + bool actualInLeap = cal->inTemporalLeapYear(status); + if (failure(status, "inTemporalLeapYear")) return; + if (hasLeapMonth != actualInLeap) { + logln("Gregorian y=%d m=%d d=%d => cal y=%d m=%s%d d=%d expected:%s actual:%s\n", + gc.get(UCAL_YEAR, status), + gc.get(UCAL_MONTH, status), + gc.get(UCAL_DATE, status), + cal->get(UCAL_EXTENDED_YEAR, status), + cal->get(UCAL_IS_LEAP_MONTH, status) == 1 ? "L" : "", + cal->get(UCAL_MONTH, status), + cal->get(UCAL_DAY_OF_MONTH, status), + hasLeapMonth ? "true" : "false", + actualInLeap ? "true" : "false"); + } + assertEquals("inTemporalLeapYear", hasLeapMonth, actualInLeap); + } +} + +void CalendarTest::TestChineseCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "chinese", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct ChineseCalendar")) return; + RunChineseCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestDangiCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "dangi", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct DangiCalendar")) return; + RunChineseCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestHebrewCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "hebrew", status); + LocalPointer cal(Calendar::createInstance(l, status)); + LocalPointer leapTest(Calendar::createInstance(l, status)); + if (failure(status, "construct HebrewCalendar")) return; + // Start our test from 1900, Jan 1. + // Check every 29 days in exhausted mode. + int32_t incrementDays = 29; + int32_t startYear = 1900; + int32_t stopYear = 2400; + + if (quick) { + incrementDays = 317; + stopYear = 2100; + } + int32_t yearForHasLeapMonth = -1; + bool hasLeapMonth = false; + for (gc.set(startYear, UCAL_JANUARY, 1); + gc.get(UCAL_YEAR, status) <= stopYear; + gc.add(UCAL_DATE, incrementDays, status)) { + if (failure(status, "add/get/set/getTime/setTime incorrect")) return; + + int32_t cal_year = cal->get(UCAL_EXTENDED_YEAR, status); + if (yearForHasLeapMonth != cal_year) { + leapTest->set(UCAL_EXTENDED_YEAR, cal_year); + leapTest->set(UCAL_MONTH, 0); + leapTest->set(UCAL_DATE, 1); + // If 10 months after TISHRI is TAMUZ, then it is a leap year. + hasLeapMonth = leapTest->get(UCAL_MONTH, status) == icu::HebrewCalendar::TAMUZ; + yearForHasLeapMonth = cal_year; + } + bool actualInLeap = cal->inTemporalLeapYear(status); + if (failure(status, "inTemporalLeapYear")) return; + if (hasLeapMonth != actualInLeap) { + logln("Gregorian y=%d m=%d d=7 => cal y=%d m=%d d=%d expected:%s actual:%s\n", + gc.get(UCAL_YEAR, status), + gc.get(UCAL_MONTH, status), + cal->get(UCAL_EXTENDED_YEAR, status), + cal->get(UCAL_MONTH, status), + cal->get(UCAL_DAY_OF_MONTH, status), + hasLeapMonth ? "true" : "false", + actualInLeap ? "true" : "false"); + } + assertEquals("inTemporalLeapYear", hasLeapMonth, actualInLeap); + } +} + +void CalendarTest::RunIslamicCalendarInTemporalLeapYearTest(Calendar* cal) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + if (failure(status, "construct GregorianCalendar")) return; + // Start our test from 1900, Jan 1. + // Check every 29 days in exhausted mode. + int32_t incrementDays = 29; + int32_t startYear = 1900; + int32_t stopYear = 2400; + + if (quick) { + incrementDays = 317; + stopYear = 2100; + } + int32_t yearForHasLeapMonth = -1; + bool hasLeapMonth = false; + for (gc.set(startYear, UCAL_JANUARY, 1); + gc.get(UCAL_YEAR, status) <= stopYear; + gc.add(UCAL_DATE, incrementDays, status)) { + cal->setTime(gc.getTime(status), status); + if (failure(status, "set/getTime/setTime incorrect")) return; + int32_t cal_year = cal->get(UCAL_EXTENDED_YEAR, status); + if (yearForHasLeapMonth != cal_year) { + // If that year has exactly 355 days, it is a leap year. + hasLeapMonth = cal->getActualMaximum(UCAL_DAY_OF_YEAR, status) == 355; + yearForHasLeapMonth = cal_year; + } + + bool actualInLeap = cal->inTemporalLeapYear(status); + if (failure(status, "inTemporalLeapYear")) return; + if (hasLeapMonth != actualInLeap) { + logln("Gregorian y=%d m=%d d=%d => cal y=%d m=%s%d d=%d expected:%s actual:%s\n", + gc.get(UCAL_EXTENDED_YEAR, status), + gc.get(UCAL_MONTH, status), + gc.get(UCAL_DAY_OF_MONTH, status), + cal->get(UCAL_EXTENDED_YEAR, status), + cal->get(UCAL_IS_LEAP_MONTH, status) == 1 ? "L" : "", + cal->get(UCAL_MONTH, status), + cal->get(UCAL_DAY_OF_MONTH, status), + hasLeapMonth ? "true" : "false", + actualInLeap ? "true" : "false"); + } + assertEquals("inTemporalLeapYear", hasLeapMonth, actualInLeap); + } +} + +void CalendarTest::TestIslamicCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "islamic", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct IslamicCalendar")) return; + RunIslamicCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestIslamicCivilCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "islamic-civil", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct IslamicCivilCalendar")) return; + RunIslamicCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestIslamicUmalquraCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "islamic-umalqura", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct IslamicUmalquraCalendar")) return; + RunIslamicCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestIslamicRGSACalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "islamic-rgsa", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct IslamicRGSACalendar")) return; + RunIslamicCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestIslamicTBLACalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "islamic-tbla", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct IslamicTBLACalendar")) return; + RunIslamicCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::Run366DaysIsLeapYearCalendarInTemporalLeapYearTest(Calendar* cal) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + if (failure(status, "construct GregorianCalendar")) return; + // Start our test from 1900, Jan 1. + // Check every 29 days in exhausted mode. + int32_t incrementDays = 29; + int32_t startYear = 1900; + int32_t stopYear = 2400; + + if (quick) { + incrementDays = 317; + stopYear = 2100; + } + int32_t yearForHasLeapMonth = -1; + bool hasLeapMonth = false; + for (gc.set(startYear, UCAL_JANUARY, 1); + gc.get(UCAL_YEAR, status) <= stopYear; + gc.add(UCAL_DATE, incrementDays, status)) { + cal->setTime(gc.getTime(status), status); + if (failure(status, "set/getTime/setTime incorrect")) return; + int32_t cal_year = cal->get(UCAL_EXTENDED_YEAR, status); + if (yearForHasLeapMonth != cal_year) { + // If that year has exactly 355 days, it is a leap year. + hasLeapMonth = cal->getActualMaximum(UCAL_DAY_OF_YEAR, status) == 366; + if (failure(status, "getActualMaximum incorrect")) return; + yearForHasLeapMonth = cal_year; + } + bool actualInLeap = cal->inTemporalLeapYear(status); + if (failure(status, "inTemporalLeapYear")) return; + if (hasLeapMonth != actualInLeap) { + logln("Gregorian y=%d m=%d d=%d => cal y=%d m=%s%d d=%d expected:%s actual:%s\n", + gc.get(UCAL_EXTENDED_YEAR, status), + gc.get(UCAL_MONTH, status), + gc.get(UCAL_DAY_OF_MONTH, status), + cal->get(UCAL_EXTENDED_YEAR, status), + cal->get(UCAL_IS_LEAP_MONTH, status) == 1 ? "L" : "", + cal->get(UCAL_MONTH, status), + cal->get(UCAL_DAY_OF_MONTH, status), + hasLeapMonth ? "true" : "false", + actualInLeap ? "true" : "false"); + } + assertEquals("inTemporalLeapYear", hasLeapMonth, actualInLeap); + } +} + +void CalendarTest::TestTaiwanCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "roc", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct TaiwanCalendar")) return; + Run366DaysIsLeapYearCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestJapaneseCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "japanese", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct JapaneseCalendar")) return; + Run366DaysIsLeapYearCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestBuddhistCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "buddhist", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct BuddhistCalendar")) return; + Run366DaysIsLeapYearCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestPersianCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "persian", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct PersianCalendar")) return; + Run366DaysIsLeapYearCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestIndianCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "indian", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct IndianCalendar")) return; + Run366DaysIsLeapYearCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestCopticCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "coptic", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct CopticCalendar")) return; + Run366DaysIsLeapYearCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestEthiopicCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "ethiopic", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct EthiopicCalendar")) return; + Run366DaysIsLeapYearCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +void CalendarTest::TestEthiopicAmeteAlemCalendarInTemporalLeapYear(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "ethiopic-amete-alem", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct EthiopicAmeteAlemCalendar")) return; + Run366DaysIsLeapYearCalendarInTemporalLeapYearTest(cal.getAlias()); +} + +std::string monthCode(int32_t month, bool leap) { + std::string code("M"); + if (month < 10) { + code += "0"; + code += ('0' + month); + } else { + code += "1"; + code += ('0' + (month % 10)); + } + if (leap) { + code += "L"; + } + return code; +} +std::string hebrewMonthCode(int32_t icuMonth) { + if (icuMonth == icu::HebrewCalendar::ADAR_1) { + return monthCode(icuMonth, true); + } + return monthCode(icuMonth < icu::HebrewCalendar::ADAR_1 ? icuMonth+1 : icuMonth, false); +} + +void CalendarTest::RunChineseCalendarGetTemporalMonthCode(Calendar* cal) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + if (failure(status, "construct GregorianCalendar")) return; + // Start our test from 1900, Jan 1. + // Check every 29 days in exhausted mode. + int32_t incrementDays = 29; + int32_t startYear = 1900; + int32_t stopYear = 2400; + + if (quick) { + incrementDays = 317; + startYear = 1950; + stopYear = 2050; + } + for (gc.set(startYear, UCAL_JANUARY, 1); + gc.get(UCAL_YEAR, status) <= stopYear; + gc.add(UCAL_DATE, incrementDays, status)) { + cal->setTime(gc.getTime(status), status); + if (failure(status, "set/getTime/setTime incorrect")) return; + int32_t cal_month = cal->get(UCAL_MONTH, status); + std::string expected = monthCode( + cal_month + 1, cal->get(UCAL_IS_LEAP_MONTH, status) != 0); + assertEquals("getTemporalMonthCode", expected.c_str(), cal->getTemporalMonthCode(status)); + if (failure(status, "getTemporalMonthCode")) return; + } +} + +void CalendarTest::TestChineseCalendarGetTemporalMonthCode(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "chinese", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct ChineseCalendar")) return; + RunChineseCalendarGetTemporalMonthCode(cal.getAlias()); +} + +void CalendarTest::TestDangiCalendarGetTemporalMonthCode(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "dangi", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct DangiCalendar")) return; + RunChineseCalendarGetTemporalMonthCode(cal.getAlias()); +} + +void CalendarTest::TestHebrewCalendarGetTemporalMonthCode(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "hebrew", status); + LocalPointer cal(Calendar::createInstance(l, status)); + GregorianCalendar gc(status); + if (failure(status, "construct Calendar")) return; + // Start our test from 1900, Jan 1. + // Check every 29 days in exhausted mode. + int32_t incrementDays = 29; + int32_t startYear = 1900; + int32_t stopYear = 2400; + + if (quick) { + incrementDays = 317; + stopYear = 2100; + } + for (gc.set(startYear, UCAL_JANUARY, 1); + gc.get(UCAL_YEAR, status) <= stopYear; + gc.add(UCAL_DATE, incrementDays, status)) { + cal->setTime(gc.getTime(status), status); + if (failure(status, "set/getTime/setTime incorrect")) return; + std::string expected = hebrewMonthCode(cal->get(UCAL_MONTH, status)); + if (failure(status, "get failed")) return; + assertEquals("getTemporalMonthCode", expected.c_str(), cal->getTemporalMonthCode(status)); + if (failure(status, "getTemporalMonthCode")) return; + } +} + +void CalendarTest::RunCECalendarGetTemporalMonthCode(Calendar* cal) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + if (failure(status, "construct Calendar")) return; + // Start testing from 1900 + gc.set(1900, UCAL_JANUARY, 1); + cal->setTime(gc.getTime(status), status); + int32_t year = cal->get(UCAL_YEAR, status); + for (int32_t m = 0; m < 13; m++) { + std::string expected = monthCode(m+1, false); + for (int32_t y = year; y < year + 500 ; y++) { + cal->set(y, m, 1); + assertEquals("getTemporalMonthCode", expected.c_str(), cal->getTemporalMonthCode(status)); + if (failure(status, "getTemporalMonthCode")) continue; + } + } + +} + +void CalendarTest::TestCopticCalendarGetTemporalMonthCode(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "coptic", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct CopticCalendar")) return; + RunCECalendarGetTemporalMonthCode(cal.getAlias()); +} + +void CalendarTest::TestEthiopicCalendarGetTemporalMonthCode(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "ethiopic", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct EthiopicCalendar")) return; + RunCECalendarGetTemporalMonthCode(cal.getAlias()); +} + +void CalendarTest::TestEthiopicAmeteAlemCalendarGetTemporalMonthCode(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "ethiopic-amete-alem", status); + LocalPointer cal(Calendar::createInstance(l, status)); + if (failure(status, "construct EthiopicAmeteAlemCalendar")) return; + RunCECalendarGetTemporalMonthCode(cal.getAlias()); +} + +void CalendarTest::TestGregorianCalendarSetTemporalMonthCode(void) { + + struct TestCase { + int32_t gYear; + int32_t gMonth; + int32_t gDate; + const char* monthCode; + int32_t ordinalMonth; + } cases[] = { + { 1911, UCAL_JANUARY, 31, "M01", 0 }, + { 1970, UCAL_FEBRUARY, 22, "M02", 1 }, + { 543, UCAL_MARCH, 3, "M03", 2 }, + { 2340, UCAL_APRIL, 21, "M04", 3 }, + { 1234, UCAL_MAY, 21, "M05", 4 }, + { 1931, UCAL_JUNE, 17, "M06", 5 }, + { 2000, UCAL_JULY, 1, "M07", 6 }, + { 2033, UCAL_AUGUST, 3, "M08", 7 }, + { 2013, UCAL_SEPTEMBER, 9, "M09", 8 }, + { 1849, UCAL_OCTOBER, 31, "M10", 9 }, + { 1433, UCAL_NOVEMBER, 30, "M11", 10 }, + { 2022, UCAL_DECEMBER, 25, "M12", 11 }, + }; + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc1(status); + GregorianCalendar gc2(status); + if (failure(status, "construct GregorianCalendar")) return; + for (auto& cas : cases) { + gc1.clear(); + gc2.clear(); + gc1.set(cas.gYear, cas.gMonth, cas.gDate); + + gc2.set(UCAL_YEAR, cas.gYear); + gc2.setTemporalMonthCode(cas.monthCode, status); + gc2.set(UCAL_DATE, cas.gDate); + if (failure(status, "set/setTemporalMonthCode")) return; + + assertTrue("by set and setTemporalMonthCode()", gc1.equals(gc2, status)); + const char* actualMonthCode1 = gc1.getTemporalMonthCode(status); + const char* actualMonthCode2 = gc2.getTemporalMonthCode(status); + if (failure(status, "getTemporalMonthCode")) continue; + assertEquals("getTemporalMonthCode()", 0, + uprv_strcmp(actualMonthCode1, actualMonthCode2)); + assertEquals("getTemporalMonthCode()", 0, + uprv_strcmp(cas.monthCode, actualMonthCode2)); + assertEquals("ordinalMonth", cas.ordinalMonth, gc2.get(UCAL_ORDINAL_MONTH, status)); + assertEquals("ordinalMonth", gc1.get(UCAL_ORDINAL_MONTH, status), + gc2.get(UCAL_ORDINAL_MONTH, status)); + } +} + +void CalendarTest::TestChineseCalendarSetTemporalMonthCode(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "chinese", status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "construct ChineseCalendar")) return; + LocalPointer cc2(cc1->clone()); + + struct TestCase { + int32_t gYear; + int32_t gMonth; + int32_t gDate; + int32_t cYear; + int32_t cMonth; + int32_t cDate; + const char* cMonthCode; + bool cLeapMonth; + int32_t cOrdinalMonth; + } cases[] = { + // https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/2022.pdf + { 2022, UCAL_DECEMBER, 15, 4659, UCAL_NOVEMBER, 22, "M11", false, 10}, + // M01L is very hard to find. Cannot find a year has M01L in these several + // centuries. + // M02L https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/2004.pdf + { 2004, UCAL_MARCH, 20, 4641, UCAL_FEBRUARY, 30, "M02", false, 1}, + { 2004, UCAL_MARCH, 21, 4641, UCAL_FEBRUARY, 1, "M02L", true, 2}, + { 2004, UCAL_APRIL, 18, 4641, UCAL_FEBRUARY, 29, "M02L", true, 2}, + { 2004, UCAL_APRIL, 19, 4641, UCAL_MARCH, 1, "M03", false, 3}, + // M03L https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/1995.pdf + { 1955, UCAL_APRIL, 21, 4592, UCAL_MARCH, 29, "M03", false, 2}, + { 1955, UCAL_APRIL, 22, 4592, UCAL_MARCH, 1, "M03L", true, 3}, + { 1955, UCAL_MAY, 21, 4592, UCAL_MARCH, 30, "M03L", true, 3}, + { 1955, UCAL_MAY, 22, 4592, UCAL_APRIL, 1, "M04", false, 4}, + // M12 https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/1996.pdf + { 1956, UCAL_FEBRUARY, 11, 4592, UCAL_DECEMBER, 30, "M12", false, 12}, + // M04L https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/2001.pdf + { 2001, UCAL_MAY, 22, 4638, UCAL_APRIL, 30, "M04", false, 3}, + { 2001, UCAL_MAY, 23, 4638, UCAL_APRIL, 1, "M04L", true, 4}, + { 2001, UCAL_JUNE, 20, 4638, UCAL_APRIL, 29, "M04L", true, 4}, + { 2001, UCAL_JUNE, 21, 4638, UCAL_MAY, 1, "M05", false, 5}, + // M05L https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/2009.pdf + { 2009, UCAL_JUNE, 22, 4646, UCAL_MAY, 30, "M05", false, 4}, + { 2009, UCAL_JUNE, 23, 4646, UCAL_MAY, 1, "M05L", true, 5}, + { 2009, UCAL_JULY, 21, 4646, UCAL_MAY, 29, "M05L", true, 5}, + { 2009, UCAL_JULY, 22, 4646, UCAL_JUNE, 1, "M06", false, 6}, + // M06L https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/2017.pdf + { 2017, UCAL_JULY, 22, 4654, UCAL_JUNE, 29, "M06", false, 5}, + { 2017, UCAL_JULY, 23, 4654, UCAL_JUNE, 1, "M06L", true, 6}, + { 2017, UCAL_AUGUST, 21, 4654, UCAL_JUNE, 30, "M06L", true, 6}, + { 2017, UCAL_AUGUST, 22, 4654, UCAL_JULY, 1, "M07", false, 7}, + // M07L https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/2006.pdf + { 2006, UCAL_AUGUST, 23, 4643, UCAL_JULY, 30, "M07", false, 6}, + { 2006, UCAL_AUGUST, 24, 4643, UCAL_JULY, 1, "M07L", true, 7}, + { 2006, UCAL_SEPTEMBER, 21, 4643, UCAL_JULY, 29, "M07L", true, 7}, + { 2006, UCAL_SEPTEMBER, 22, 4643, UCAL_AUGUST, 1, "M08", false, 8}, + // M08L https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/1995.pdf + { 1995, UCAL_SEPTEMBER, 24, 4632, UCAL_AUGUST, 30, "M08", false, 7}, + { 1995, UCAL_SEPTEMBER, 25, 4632, UCAL_AUGUST, 1, "M08L", true, 8}, + { 1995, UCAL_OCTOBER, 23, 4632, UCAL_AUGUST, 29, "M08L", true, 8}, + { 1995, UCAL_OCTOBER, 24, 4632, UCAL_SEPTEMBER, 1, "M09", false, 9}, + // M09L https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/2014.pdf + { 2014, UCAL_OCTOBER, 23, 4651, UCAL_SEPTEMBER, 30, "M09", false, 8}, + { 2014, UCAL_OCTOBER, 24, 4651, UCAL_SEPTEMBER, 1, "M09L", true, 9}, + { 2014, UCAL_NOVEMBER, 21, 4651, UCAL_SEPTEMBER, 29, "M09L", true, 9}, + { 2014, UCAL_NOVEMBER, 22, 4651, UCAL_OCTOBER, 1, "M10", false, 10}, + // M10L https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/1984.pdf + { 1984, UCAL_NOVEMBER, 22, 4621, UCAL_OCTOBER, 30, "M10", false, 9}, + { 1984, UCAL_NOVEMBER, 23, 4621, UCAL_OCTOBER, 1, "M10L", true, 10}, + { 1984, UCAL_DECEMBER, 21, 4621, UCAL_OCTOBER, 29, "M10L", true, 10}, + { 1984, UCAL_DECEMBER, 22, 4621, UCAL_NOVEMBER, 1, "M11", false, 11}, + // M11L https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/2033.pdf + // https://www.hko.gov.hk/tc/gts/time/calendar/pdf/files/2034.pdf + { 2033, UCAL_DECEMBER, 21, 4670, UCAL_NOVEMBER, 30, "M11", false, 10}, + { 2033, UCAL_DECEMBER, 22, 4670, UCAL_NOVEMBER, 1, "M11L", true, 11}, + { 2034, UCAL_JANUARY, 19, 4670, UCAL_NOVEMBER, 29, "M11L", true, 11}, + { 2034, UCAL_JANUARY, 20, 4670, UCAL_DECEMBER, 1, "M12", false, 12}, + // M12L is very hard to find. Cannot find a year has M01L in these several + // centuries. + }; + GregorianCalendar gc1(status); + if (failure(status, "construct Calendar")) return; + for (auto& cas : cases) { + gc1.clear(); + cc1->clear(); + cc2->clear(); + gc1.set(cas.gYear, cas.gMonth, cas.gDate); + cc1->setTime(gc1.getTime(status), status); + + cc2->set(UCAL_EXTENDED_YEAR, cas.cYear); + cc2->setTemporalMonthCode(cas.cMonthCode, status); + cc2->set(UCAL_DATE, cas.cDate); + + assertEquals("year", cas.cYear, cc1->get(UCAL_EXTENDED_YEAR, status)); + assertEquals("month", cas.cMonth, cc1->get(UCAL_MONTH, status)); + assertEquals("date", cas.cDate, cc1->get(UCAL_DATE, status)); + assertEquals("is_leap_month", cas.cLeapMonth ? 1 : 0, cc1->get(UCAL_IS_LEAP_MONTH, status)); + assertEquals("getTemporalMonthCode()", 0, + uprv_strcmp(cas.cMonthCode, cc1->getTemporalMonthCode(status))); + if (failure(status, "getTemporalMonthCode")) continue; + assertEquals("ordinalMonth", cas.cOrdinalMonth, cc1->get(UCAL_ORDINAL_MONTH, status)); + if (! cc2->equals(*cc1, status)) { + printf("g=%f %f vs %f. diff = %f %d/%d%s/%d vs %d/%d%s/%d\n", + gc1.getTime(status), cc1->getTime(status), cc2->getTime(status), + cc1->getTime(status) - cc2->getTime(status), + cc1->get(UCAL_EXTENDED_YEAR, status), + cc1->get(UCAL_MONTH, status)+1, + cc1->get(UCAL_IS_LEAP_MONTH, status) == 0 ? "" : "L", + cc1->get(UCAL_DATE, status), + cc2->get(UCAL_EXTENDED_YEAR, status), + cc2->get(UCAL_MONTH, status)+1, + cc2->get(UCAL_IS_LEAP_MONTH, status) == 0 ? "" : "L", + cc2->get(UCAL_DATE, status)); + } + assertTrue("by set() and setTemporalMonthCode()", cc2->equals(*cc1, status)); + } +} + +void CalendarTest::TestHebrewCalendarSetTemporalMonthCode(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "hebrew", status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "construct HebrewCalendar")) return; + LocalPointer cc2(cc1->clone()); + + struct TestCase { + int32_t gYear; + int32_t gMonth; + int32_t gDate; + int32_t cYear; + int32_t cMonth; + int32_t cDate; + const char* cMonthCode; + int32_t cOrdinalMonth; + } cases[] = { + { 2022, UCAL_JANUARY, 11, 5782, icu::HebrewCalendar::SHEVAT, 9, "M05", 4}, + { 2022, UCAL_FEBRUARY, 12, 5782, icu::HebrewCalendar::ADAR_1, 11, "M05L", 5}, + { 2022, UCAL_MARCH, 13, 5782, icu::HebrewCalendar::ADAR, 10, "M06", 6}, + { 2022, UCAL_APRIL, 14, 5782, icu::HebrewCalendar::NISAN, 13, "M07", 7}, + { 2022, UCAL_MAY, 15, 5782, icu::HebrewCalendar::IYAR, 14, "M08", 8}, + { 2022, UCAL_JUNE, 16, 5782, icu::HebrewCalendar::SIVAN, 17, "M09", 9}, + { 2022, UCAL_JULY, 17, 5782, icu::HebrewCalendar::TAMUZ, 18, "M10", 10}, + { 2022, UCAL_AUGUST, 18, 5782, icu::HebrewCalendar::AV, 21, "M11", 11}, + { 2022, UCAL_SEPTEMBER, 19, 5782, icu::HebrewCalendar::ELUL, 23, "M12", 12}, + { 2022, UCAL_OCTOBER, 20, 5783, icu::HebrewCalendar::TISHRI, 25, "M01", 0}, + { 2022, UCAL_NOVEMBER, 21, 5783, icu::HebrewCalendar::HESHVAN, 27, "M02", 1}, + { 2022, UCAL_DECEMBER, 22, 5783, icu::HebrewCalendar::KISLEV, 28, "M03", 2}, + { 2023, UCAL_JANUARY, 20, 5783, icu::HebrewCalendar::TEVET, 27, "M04", 3}, + }; + GregorianCalendar gc1(status); + if (failure(status, "construct Calendar")) return; + for (auto& cas : cases) { + gc1.clear(); + cc1->clear(); + cc2->clear(); + gc1.set(cas.gYear, cas.gMonth, cas.gDate); + cc1->setTime(gc1.getTime(status), status); + + cc2->set(UCAL_EXTENDED_YEAR, cas.cYear); + cc2->setTemporalMonthCode(cas.cMonthCode, status); + cc2->set(UCAL_DATE, cas.cDate); + + assertEquals("year", cas.cYear, cc1->get(UCAL_EXTENDED_YEAR, status)); + assertEquals("month", cas.cMonth, cc1->get(UCAL_MONTH, status)); + assertEquals("date", cas.cDate, cc1->get(UCAL_DATE, status)); + assertEquals("getTemporalMonthCode()", 0, + uprv_strcmp(cas.cMonthCode, cc1->getTemporalMonthCode(status))); + if (failure(status, "getTemporalMonthCode")) continue; + if (! cc2->equals(*cc1, status)) { + printf("g=%f %f vs %f. diff = %f %d/%d/%d vs %d/%d/%d\n", + gc1.getTime(status), cc1->getTime(status), cc2->getTime(status), + cc1->getTime(status) - cc2->getTime(status), + cc1->get(UCAL_EXTENDED_YEAR, status), + cc1->get(UCAL_MONTH, status)+1, + cc1->get(UCAL_DATE, status), + cc2->get(UCAL_EXTENDED_YEAR, status), + cc2->get(UCAL_MONTH, status)+1, + cc2->get(UCAL_DATE, status)); + } + assertTrue("by set() and setTemporalMonthCode()", cc2->equals(*cc1, status)); + assertEquals("ordinalMonth", cas.cOrdinalMonth, cc1->get(UCAL_ORDINAL_MONTH, status)); + assertEquals("ordinalMonth", cas.cOrdinalMonth, cc2->get(UCAL_ORDINAL_MONTH, status)); + } +} + +void CalendarTest::TestCopticCalendarSetTemporalMonthCode(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "coptic", status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "construct CopticCalendar")) return; + LocalPointer cc2(cc1->clone()); + + struct TestCase { + int32_t gYear; + int32_t gMonth; + int32_t gDate; + int32_t cYear; + int32_t cMonth; + int32_t cDate; + const char* cMonthCode; + int32_t cOrdinalMonth; + } cases[] = { + { 1900, UCAL_JANUARY, 1, 1616, icu::CopticCalendar::KIAHK, 23, "M04", 3}, + { 1900, UCAL_SEPTEMBER, 6, 1616, icu::CopticCalendar::NASIE, 1, "M13", 12}, + { 1900, UCAL_SEPTEMBER, 10, 1616, icu::CopticCalendar::NASIE, 5, "M13", 12}, + { 1900, UCAL_SEPTEMBER, 11, 1617, icu::CopticCalendar::TOUT, 1, "M01", 0}, + + { 2022, UCAL_JANUARY, 11, 1738, icu::CopticCalendar::TOBA, 3, "M05", 4}, + { 2022, UCAL_FEBRUARY, 12, 1738, icu::CopticCalendar::AMSHIR, 5, "M06", 5}, + { 2022, UCAL_MARCH, 13, 1738, icu::CopticCalendar::BARAMHAT, 4, "M07", 6}, + { 2022, UCAL_APRIL, 14, 1738, icu::CopticCalendar::BARAMOUDA, 6, "M08", 7}, + { 2022, UCAL_MAY, 15, 1738, icu::CopticCalendar::BASHANS, 7, "M09", 8}, + { 2022, UCAL_JUNE, 16, 1738, icu::CopticCalendar::PAONA, 9, "M10", 9}, + { 2022, UCAL_JULY, 17, 1738, icu::CopticCalendar::EPEP, 10, "M11", 10}, + { 2022, UCAL_AUGUST, 18, 1738, icu::CopticCalendar::MESRA, 12, "M12", 11}, + { 2022, UCAL_SEPTEMBER, 6, 1738, icu::CopticCalendar::NASIE, 1, "M13", 12}, + { 2022, UCAL_SEPTEMBER, 10, 1738, icu::CopticCalendar::NASIE, 5, "M13", 12}, + { 2022, UCAL_SEPTEMBER, 11, 1739, icu::CopticCalendar::TOUT, 1, "M01", 0}, + { 2022, UCAL_SEPTEMBER, 19, 1739, icu::CopticCalendar::TOUT, 9, "M01", 0}, + { 2022, UCAL_OCTOBER, 20, 1739, icu::CopticCalendar::BABA, 10, "M02", 1}, + { 2022, UCAL_NOVEMBER, 21, 1739, icu::CopticCalendar::HATOR, 12, "M03", 2}, + { 2022, UCAL_DECEMBER, 22, 1739, icu::CopticCalendar::KIAHK, 13, "M04", 3}, + + { 2023, UCAL_JANUARY, 1, 1739, icu::CopticCalendar::KIAHK, 23, "M04", 3}, + { 2023, UCAL_SEPTEMBER, 6, 1739, icu::CopticCalendar::NASIE, 1, "M13", 12}, + { 2023, UCAL_SEPTEMBER, 11, 1739, icu::CopticCalendar::NASIE, 6, "M13", 12}, + { 2023, UCAL_SEPTEMBER, 12, 1740, icu::CopticCalendar::TOUT, 1, "M01", 0}, + + { 2030, UCAL_JANUARY, 1, 1746, icu::CopticCalendar::KIAHK, 23, "M04", 3}, + { 2030, UCAL_SEPTEMBER, 6, 1746, icu::CopticCalendar::NASIE, 1, "M13", 12}, + { 2030, UCAL_SEPTEMBER, 10, 1746, icu::CopticCalendar::NASIE, 5, "M13", 12}, + { 2030, UCAL_SEPTEMBER, 11, 1747, icu::CopticCalendar::TOUT, 1, "M01", 0}, + }; + GregorianCalendar gc1(status); + if (failure(status, "construct Calendar")) return; + for (auto& cas : cases) { + gc1.clear(); + cc1->clear(); + cc2->clear(); + gc1.set(cas.gYear, cas.gMonth, cas.gDate); + cc1->setTime(gc1.getTime(status), status); + + cc2->set(UCAL_EXTENDED_YEAR, cas.cYear); + cc2->setTemporalMonthCode(cas.cMonthCode, status); + cc2->set(UCAL_DATE, cas.cDate); + + assertEquals("year", cas.cYear, cc1->get(UCAL_EXTENDED_YEAR, status)); + assertEquals("month", cas.cMonth, cc1->get(UCAL_MONTH, status)); + assertEquals("date", cas.cDate, cc1->get(UCAL_DATE, status)); + assertEquals("getTemporalMonthCode()", 0, + uprv_strcmp(cas.cMonthCode, cc1->getTemporalMonthCode(status))); + if (failure(status, "getTemporalMonthCode")) continue; + assertTrue("by set() and setTemporalMonthCode()", cc2->equals(*cc1, status)); + if (! cc2->equals(*cc1, status)) { + printf("g=%f %f vs %f. diff = %f %d/%d/%d vs %d/%d/%d\n", + gc1.getTime(status), cc1->getTime(status), cc2->getTime(status), + cc1->getTime(status) - cc2->getTime(status), + cc1->get(UCAL_EXTENDED_YEAR, status), + cc1->get(UCAL_MONTH, status)+1, + cc1->get(UCAL_DATE, status), + cc2->get(UCAL_EXTENDED_YEAR, status), + cc2->get(UCAL_MONTH, status)+1, + cc2->get(UCAL_DATE, status)); + } + assertEquals("ordinalMonth", cas.cOrdinalMonth, cc1->get(UCAL_ORDINAL_MONTH, status)); + assertEquals("ordinalMonth", cas.cOrdinalMonth, cc2->get(UCAL_ORDINAL_MONTH, status)); + } +} + +void CalendarTest::TestEthiopicCalendarSetTemporalMonthCode(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "ethiopic", status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "construct EthiopicCalendar")) return; + LocalPointer cc2(cc1->clone()); + + struct TestCase { + int32_t gYear; + int32_t gMonth; + int32_t gDate; + int32_t cYear; + int32_t cMonth; + int32_t cDate; + const char* cMonthCode; + int32_t cOrdinalMonth; + } cases[] = { + { 1900, UCAL_JANUARY, 1, 1892, icu::EthiopicCalendar::TAHSAS, 23, "M04", 3}, + { 1900, UCAL_SEPTEMBER, 6, 1892, icu::EthiopicCalendar::PAGUMEN, 1, "M13", 12}, + { 1900, UCAL_SEPTEMBER, 10, 1892, icu::EthiopicCalendar::PAGUMEN, 5, "M13", 12}, + { 1900, UCAL_SEPTEMBER, 11, 1893, icu::EthiopicCalendar::MESKEREM, 1, "M01", 0}, + + { 2022, UCAL_JANUARY, 11, 2014, icu::EthiopicCalendar::TER, 3, "M05", 4}, + { 2022, UCAL_FEBRUARY, 12, 2014, icu::EthiopicCalendar::YEKATIT, 5, "M06", 5}, + { 2022, UCAL_MARCH, 13, 2014, icu::EthiopicCalendar::MEGABIT, 4, "M07", 6}, + { 2022, UCAL_APRIL, 14, 2014, icu::EthiopicCalendar::MIAZIA, 6, "M08", 7}, + { 2022, UCAL_MAY, 15, 2014, icu::EthiopicCalendar::GENBOT, 7, "M09", 8}, + { 2022, UCAL_JUNE, 16, 2014, icu::EthiopicCalendar::SENE, 9, "M10", 9}, + { 2022, UCAL_JULY, 17, 2014, icu::EthiopicCalendar::HAMLE, 10, "M11", 10}, + { 2022, UCAL_AUGUST, 18, 2014, icu::EthiopicCalendar::NEHASSA, 12, "M12", 11}, + { 2022, UCAL_SEPTEMBER, 6, 2014, icu::EthiopicCalendar::PAGUMEN, 1, "M13", 12}, + { 2022, UCAL_SEPTEMBER, 10, 2014, icu::EthiopicCalendar::PAGUMEN, 5, "M13", 12}, + { 2022, UCAL_SEPTEMBER, 11, 2015, icu::EthiopicCalendar::MESKEREM, 1, "M01", 0}, + { 2022, UCAL_SEPTEMBER, 19, 2015, icu::EthiopicCalendar::MESKEREM, 9, "M01", 0}, + { 2022, UCAL_OCTOBER, 20, 2015, icu::EthiopicCalendar::TEKEMT, 10, "M02", 1}, + { 2022, UCAL_NOVEMBER, 21, 2015, icu::EthiopicCalendar::HEDAR, 12, "M03", 2}, + { 2022, UCAL_DECEMBER, 22, 2015, icu::EthiopicCalendar::TAHSAS, 13, "M04", 3}, + + { 2023, UCAL_JANUARY, 1, 2015, icu::EthiopicCalendar::TAHSAS, 23, "M04", 3}, + { 2023, UCAL_SEPTEMBER, 6, 2015, icu::EthiopicCalendar::PAGUMEN, 1, "M13", 12}, + { 2023, UCAL_SEPTEMBER, 11, 2015, icu::EthiopicCalendar::PAGUMEN, 6, "M13", 12}, + { 2023, UCAL_SEPTEMBER, 12, 2016, icu::EthiopicCalendar::MESKEREM, 1, "M01", 0}, + + { 2030, UCAL_JANUARY, 1, 2022, icu::EthiopicCalendar::TAHSAS, 23, "M04", 3}, + { 2030, UCAL_SEPTEMBER, 6, 2022, icu::EthiopicCalendar::PAGUMEN, 1, "M13", 12}, + { 2030, UCAL_SEPTEMBER, 10, 2022, icu::EthiopicCalendar::PAGUMEN, 5, "M13", 12}, + { 2030, UCAL_SEPTEMBER, 11, 2023, icu::EthiopicCalendar::MESKEREM, 1, "M01", 0}, + }; + GregorianCalendar gc1(status); + if (failure(status, "construct Calendar")) return; + for (auto& cas : cases) { + gc1.clear(); + cc1->clear(); + cc2->clear(); + gc1.set(cas.gYear, cas.gMonth, cas.gDate); + cc1->setTime(gc1.getTime(status), status); + + cc2->set(UCAL_EXTENDED_YEAR, cas.cYear); + cc2->setTemporalMonthCode(cas.cMonthCode, status); + cc2->set(UCAL_DATE, cas.cDate); + + assertEquals("year", cas.cYear, cc1->get(UCAL_EXTENDED_YEAR, status)); + assertEquals("month", cas.cMonth, cc1->get(UCAL_MONTH, status)); + assertEquals("date", cas.cDate, cc1->get(UCAL_DATE, status)); + assertEquals("getTemporalMonthCode()", 0, + uprv_strcmp(cas.cMonthCode, cc1->getTemporalMonthCode(status))); + if (failure(status, "getTemporalMonthCode")) continue; + if (! cc2->equals(*cc1, status)) { + printf("g=%f %f vs %f. diff = %f %d/%d/%d vs %d/%d/%d\n", + gc1.getTime(status), cc1->getTime(status), cc2->getTime(status), + cc1->getTime(status) - cc2->getTime(status), + cc1->get(UCAL_EXTENDED_YEAR, status), + cc1->get(UCAL_MONTH, status)+1, + cc1->get(UCAL_DATE, status), + cc2->get(UCAL_EXTENDED_YEAR, status), + cc2->get(UCAL_MONTH, status)+1, + cc2->get(UCAL_DATE, status)); + } + assertTrue("by set() and setTemporalMonthCode()", cc2->equals(*cc1, status)); + assertEquals("ordinalMonth", cas.cOrdinalMonth, cc1->get(UCAL_ORDINAL_MONTH, status)); + assertEquals("ordinalMonth", cas.cOrdinalMonth, cc2->get(UCAL_ORDINAL_MONTH, status)); + } +} + +void VerifyMonth(CalendarTest* test, const char* message, Calendar* cc, int32_t expectedMonth, + int32_t expectedOrdinalMonth, bool expectedLeapMonth, + const char* expectedMonthCode) { + UErrorCode status = U_ZERO_ERROR; + std::string buf(message); + buf.append(" get(UCAL_MONTH)"); + test->assertEquals(buf.c_str(), expectedMonth, cc->get(UCAL_MONTH, status)); + buf = message; + buf.append(" get(UCAL_ORDINAL_MONTH)"); + test->assertEquals(buf.c_str(), expectedOrdinalMonth, cc->get(UCAL_ORDINAL_MONTH, status)); + buf = message; + buf.append(" get(UCAL_IS_LEAP_MONTH)"); + test->assertEquals(buf.c_str(), expectedLeapMonth ? 1 : 0, cc->get(UCAL_IS_LEAP_MONTH, status)); + buf = message; + buf.append(" getTemporalMonthCode()"); + test->assertTrue(buf.c_str(), uprv_strcmp(cc->getTemporalMonthCode(status), expectedMonthCode) == 0); +} + +void CalendarTest::TestMostCalendarsOrdinalMonthSet(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + std::unique_ptr enumeration( + Calendar::getKeywordValuesForLocale("calendar", l, false, status)); + for (const char* name = enumeration->next(nullptr, status); + U_SUCCESS(status) && name != nullptr; + name = enumeration->next(nullptr, status)) { + + // Test these three calendars differently. + if (uprv_strcmp(name, "chinese") == 0) continue; + if (uprv_strcmp(name, "dangi") == 0) continue; + if (uprv_strcmp(name, "hebrew") == 0) continue; + + l.setKeywordValue("calendar", name, status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "Construct Calendar")) return; + + LocalPointer cc2(cc1->clone()); + LocalPointer cc3(cc1->clone()); + + cc1->set(UCAL_EXTENDED_YEAR, 2134); + cc2->set(UCAL_EXTENDED_YEAR, 2134); + cc3->set(UCAL_EXTENDED_YEAR, 2134); + cc1->set(UCAL_MONTH, 5); + cc2->set(UCAL_ORDINAL_MONTH, 5); + cc3->setTemporalMonthCode("M06", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc1->set(UCAL_DATE, 23); + cc2->set(UCAL_DATE, 23); + cc3->set(UCAL_DATE, 23); + assertTrue("M06 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("M06 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "cc1", cc1.getAlias(), 5, 5, false, "M06"); + VerifyMonth(this, "cc2", cc2.getAlias(), 5, 5, false, "M06"); + VerifyMonth(this, "cc3", cc3.getAlias(), 5, 5, false, "M06"); + + cc1->set(UCAL_ORDINAL_MONTH, 6); + cc2->setTemporalMonthCode("M07", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc3->set(UCAL_MONTH, 6); + assertTrue("M07 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("M07 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "cc1", cc1.getAlias(), 6, 6, false, "M07"); + VerifyMonth(this, "cc2", cc2.getAlias(), 6, 6, false, "M07"); + VerifyMonth(this, "cc3", cc3.getAlias(), 6, 6, false, "M07"); + + cc1->setTemporalMonthCode("M08", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc2->set(UCAL_MONTH, 7); + cc3->set(UCAL_ORDINAL_MONTH, 7); + assertTrue("M08 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("M08 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "cc1", cc1.getAlias(), 7, 7, false, "M08"); + VerifyMonth(this, "cc2", cc2.getAlias(), 7, 7, false, "M08"); + VerifyMonth(this, "cc3", cc3.getAlias(), 7, 7, false, "M08"); + + cc1->set(UCAL_DATE, 3); + // For "M13", do not return error for these three calendars. + if ((uprv_strcmp(name, "coptic") == 0) || + (uprv_strcmp(name, "ethiopic") == 0) || + (uprv_strcmp(name, "ethiopic-amete-alem") == 0)) { + + cc1->setTemporalMonthCode("M13", status); + assertEquals("setTemporalMonthCode(\"M13\")", U_ZERO_ERROR, status); + assertEquals("get(UCAL_MONTH) after setTemporalMonthCode(\"M13\")", + 12, cc1->get(UCAL_MONTH, status)); + assertEquals("get(UCAL_ORDINAL_MONTH) after setTemporalMonthCode(\"M13\")", + 12, cc1->get(UCAL_ORDINAL_MONTH, status)); + assertEquals("get", U_ZERO_ERROR, status); + } else { + cc1->setTemporalMonthCode("M13", status); + assertEquals("setTemporalMonthCode(\"M13\")", U_ILLEGAL_ARGUMENT_ERROR, status); + } + status = U_ZERO_ERROR; + + // Out of bound monthCodes should return error. + // These are not valid for calendar do not have a leap month + const char* kInvalidMonthCodes[] = { + "M00", "M14", "M01L", "M02L", "M03L", "M04L", "M05L", "M06L", "M07L", + "M08L", "M09L", "M10L", "M11L", "M12L"}; + for (auto& cas : kInvalidMonthCodes) { + cc1->setTemporalMonthCode(cas, status); + assertEquals("setTemporalMonthCode(\"M13\")", U_ILLEGAL_ARGUMENT_ERROR, status); + status = U_ZERO_ERROR; + } + + } +} + +void CalendarTest::TestChineseCalendarOrdinalMonthSet(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "chinese", status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "Construct Calendar")) return; + + LocalPointer cc2(cc1->clone()); + LocalPointer cc3(cc1->clone()); + + constexpr int32_t notLeapYear = 4591; + constexpr int32_t leapMarchYear = 4592; + + cc1->set(UCAL_EXTENDED_YEAR, leapMarchYear); + cc2->set(UCAL_EXTENDED_YEAR, leapMarchYear); + cc3->set(UCAL_EXTENDED_YEAR, leapMarchYear); + + cc1->set(UCAL_MONTH, UCAL_MARCH); cc1->set(UCAL_IS_LEAP_MONTH, 1); + cc2->set(UCAL_ORDINAL_MONTH, 3); + cc3->setTemporalMonthCode("M03L", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc1->set(UCAL_DATE, 1); + cc2->set(UCAL_DATE, 1); + cc3->set(UCAL_DATE, 1); + assertTrue("4592 M03L cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("4592 M03L cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "4592 M03L cc1", cc1.getAlias(), UCAL_MARCH, 3, true, "M03L"); + VerifyMonth(this, "4592 M03L cc2", cc2.getAlias(), UCAL_MARCH, 3, true, "M03L"); + VerifyMonth(this, "4592 M03L cc3", cc3.getAlias(), UCAL_MARCH, 3, true, "M03L"); + + cc1->set(UCAL_EXTENDED_YEAR, notLeapYear); + cc2->set(UCAL_EXTENDED_YEAR, notLeapYear); + cc3->set(UCAL_EXTENDED_YEAR, notLeapYear); + cc1->set(UCAL_ORDINAL_MONTH, 5); + cc2->setTemporalMonthCode("M06", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc3->set(UCAL_MONTH, UCAL_JUNE); cc3->set(UCAL_IS_LEAP_MONTH, 0); + assertTrue("4591 M06 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("4591 M06 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "4591 M06 cc1", cc1.getAlias(), UCAL_JUNE, 5, false, "M06"); + VerifyMonth(this, "4591 M06 cc2", cc2.getAlias(), UCAL_JUNE, 5, false, "M06"); + VerifyMonth(this, "4591 M06 cc3", cc3.getAlias(), UCAL_JUNE, 5, false, "M06"); + + cc1->set(UCAL_EXTENDED_YEAR, leapMarchYear); + cc2->set(UCAL_EXTENDED_YEAR, leapMarchYear); + cc3->set(UCAL_EXTENDED_YEAR, leapMarchYear); + cc1->setTemporalMonthCode("M04", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc2->set(UCAL_MONTH, UCAL_APRIL); cc2->set(UCAL_IS_LEAP_MONTH, 0); + cc3->set(UCAL_ORDINAL_MONTH, 4); + assertTrue("4592 M04 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("4592 M04 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + // 4592 has leap March so April is the 5th month in that year. + VerifyMonth(this, "4592 M04 cc1", cc1.getAlias(), UCAL_APRIL, 4, false, "M04"); + VerifyMonth(this, "4592 M04 cc2", cc2.getAlias(), UCAL_APRIL, 4, false, "M04"); + VerifyMonth(this, "4592 M04 cc3", cc3.getAlias(), UCAL_APRIL, 4, false, "M04"); + + cc1->set(UCAL_EXTENDED_YEAR, notLeapYear); + cc2->set(UCAL_EXTENDED_YEAR, notLeapYear); + cc3->set(UCAL_EXTENDED_YEAR, notLeapYear); + assertTrue("4591 M04 no leap month before cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("4591 M04 no leap month before cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + // 4592 has no leap month before April so April is the 4th month in that year. + VerifyMonth(this, "4591 M04 cc1", cc1.getAlias(), UCAL_APRIL, 3, false, "M04"); + VerifyMonth(this, "4591 M04 cc2", cc2.getAlias(), UCAL_APRIL, 3, false, "M04"); + VerifyMonth(this, "4591 M04 cc3", cc3.getAlias(), UCAL_APRIL, 3, false, "M04"); + + // Out of bound monthCodes should return error. + // These are not valid for calendar do not have a leap month + UErrorCode expectedStatus = U_ILLEGAL_ARGUMENT_ERROR; + const char* kInvalidMonthCodes[] = { "M00", "M13", "M14" }; + + + for (auto& cas : kInvalidMonthCodes) { + cc1->setTemporalMonthCode(cas, status); + if (status != expectedStatus) { + errln("setTemporalMonthCode(%s) should return U_ILLEGAL_ARGUMENT_ERROR", cas); + } + status = U_ZERO_ERROR; + } +} + +void CalendarTest::TestDangiCalendarOrdinalMonthSet(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + const char* name = "dangi"; + l.setKeywordValue("calendar", name, status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "Construct Calendar")) return; + + LocalPointer cc2(cc1->clone()); + LocalPointer cc3(cc1->clone()); + + constexpr int32_t notLeapYear = 4287; + constexpr int32_t leapMarchYear = 4288; + + cc1->set(UCAL_EXTENDED_YEAR, leapMarchYear); + cc2->set(UCAL_EXTENDED_YEAR, leapMarchYear); + cc3->set(UCAL_EXTENDED_YEAR, leapMarchYear); + + cc1->set(UCAL_MONTH, UCAL_MARCH); cc1->set(UCAL_IS_LEAP_MONTH, 1); + cc2->set(UCAL_ORDINAL_MONTH, 3); + cc3->setTemporalMonthCode("M03L", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc1->set(UCAL_DATE, 1); + cc2->set(UCAL_DATE, 1); + cc3->set(UCAL_DATE, 1); + assertTrue("4288 M03L cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("4288 M03L cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "4288 M03L cc1", cc1.getAlias(), UCAL_MARCH, 3, true, "M03L"); + VerifyMonth(this, "4288 M03L cc2", cc2.getAlias(), UCAL_MARCH, 3, true, "M03L"); + VerifyMonth(this, "4288 M03L cc3", cc3.getAlias(), UCAL_MARCH, 3, true, "M03L"); + + cc1->set(UCAL_EXTENDED_YEAR, notLeapYear); + cc2->set(UCAL_EXTENDED_YEAR, notLeapYear); + cc3->set(UCAL_EXTENDED_YEAR, notLeapYear); + cc1->set(UCAL_ORDINAL_MONTH, 5); + cc2->setTemporalMonthCode("M06", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc3->set(UCAL_MONTH, UCAL_JUNE); cc3->set(UCAL_IS_LEAP_MONTH, 0); + assertTrue("4287 M06 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("4287 M06 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "4287 M06 cc1", cc1.getAlias(), UCAL_JUNE, 5, false, "M06"); + VerifyMonth(this, "4287 M06 cc2", cc2.getAlias(), UCAL_JUNE, 5, false, "M06"); + VerifyMonth(this, "4287 M06 cc3", cc3.getAlias(), UCAL_JUNE, 5, false, "M06"); + + cc1->set(UCAL_EXTENDED_YEAR, leapMarchYear); + cc2->set(UCAL_EXTENDED_YEAR, leapMarchYear); + cc3->set(UCAL_EXTENDED_YEAR, leapMarchYear); + cc1->setTemporalMonthCode("M04", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc2->set(UCAL_MONTH, UCAL_APRIL); cc2->set(UCAL_IS_LEAP_MONTH, 0); + cc3->set(UCAL_ORDINAL_MONTH, 4); + assertTrue("4288 M04 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("4288 M04 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + // 4592 has leap March so April is the 5th month in that year. + VerifyMonth(this, "4288 M04 cc1", cc1.getAlias(), UCAL_APRIL, 4, false, "M04"); + VerifyMonth(this, "4288 M04 cc2", cc2.getAlias(), UCAL_APRIL, 4, false, "M04"); + VerifyMonth(this, "4288 M04 cc3", cc3.getAlias(), UCAL_APRIL, 4, false, "M04"); + + cc1->set(UCAL_EXTENDED_YEAR, notLeapYear); + cc2->set(UCAL_EXTENDED_YEAR, notLeapYear); + cc3->set(UCAL_EXTENDED_YEAR, notLeapYear); + assertTrue("4287 M04 no leap month before cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("4287 M04 no leap month before cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + // 4592 has no leap month before April so April is the 4th month in that year. + VerifyMonth(this, "4287 M04 cc1", cc1.getAlias(), UCAL_APRIL, 3, false, "M04"); + VerifyMonth(this, "4287 M04 cc2", cc2.getAlias(), UCAL_APRIL, 3, false, "M04"); + VerifyMonth(this, "4287 M04 cc3", cc3.getAlias(), UCAL_APRIL, 3, false, "M04"); + + // Out of bound monthCodes should return error. + // These are not valid for calendar do not have a leap month + UErrorCode expectedStatus = U_ILLEGAL_ARGUMENT_ERROR; + const char* kInvalidMonthCodes[] = { "M00", "M13", "M14" }; + + for (auto& cas : kInvalidMonthCodes) { + cc1->setTemporalMonthCode(cas, status); + if (status != expectedStatus) { + errln("setTemporalMonthCode(%s) should return U_ILLEGAL_ARGUMENT_ERROR", cas); + } + status = U_ZERO_ERROR; + } +} + +void CalendarTest::TestHebrewCalendarOrdinalMonthSet(void) { + UErrorCode status = U_ZERO_ERROR; + Locale l(Locale::getRoot()); + l.setKeywordValue("calendar", "hebrew", status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "Construct Calendar")) return; + + LocalPointer cc2(cc1->clone()); + LocalPointer cc3(cc1->clone()); + + // 5782 is leap year, 5781 is NOT. + cc1->set(UCAL_EXTENDED_YEAR, 5782); + cc2->set(UCAL_EXTENDED_YEAR, 5782); + cc3->set(UCAL_EXTENDED_YEAR, 5782); + cc1->set(UCAL_MONTH, icu::HebrewCalendar::ADAR_1); + cc2->set(UCAL_ORDINAL_MONTH, 5); + cc3->setTemporalMonthCode("M05L", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc1->set(UCAL_DATE, 1); + cc2->set(UCAL_DATE, 1); + cc3->set(UCAL_DATE, 1); + assertTrue("5782 M05L cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("5782 M05L cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "cc1", cc1.getAlias(), icu::HebrewCalendar::ADAR_1, 5, false, "M05L"); + VerifyMonth(this, "cc2", cc2.getAlias(), icu::HebrewCalendar::ADAR_1, 5, false, "M05L"); + VerifyMonth(this, "cc3", cc3.getAlias(), icu::HebrewCalendar::ADAR_1, 5, false, "M05L"); + + cc1->set(UCAL_ORDINAL_MONTH, 4); + cc2->setTemporalMonthCode("M05", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc3->set(UCAL_MONTH, icu::HebrewCalendar::SHEVAT); + assertTrue("5782 M05 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("5782 M05 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "cc1", cc1.getAlias(), icu::HebrewCalendar::SHEVAT, 4, false, "M05"); + VerifyMonth(this, "cc2", cc2.getAlias(), icu::HebrewCalendar::SHEVAT, 4, false, "M05"); + VerifyMonth(this, "cc3", cc3.getAlias(), icu::HebrewCalendar::SHEVAT, 4, false, "M05"); + + cc1->set(UCAL_EXTENDED_YEAR, 5781); + cc2->set(UCAL_EXTENDED_YEAR, 5781); + cc3->set(UCAL_EXTENDED_YEAR, 5781); + cc1->setTemporalMonthCode("M06", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc2->set(UCAL_MONTH, icu::HebrewCalendar::ADAR); + cc3->set(UCAL_ORDINAL_MONTH, 5); + assertTrue("5781 M06 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("5781 M06 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "cc1", cc1.getAlias(), icu::HebrewCalendar::ADAR, 5, false, "M06"); + VerifyMonth(this, "cc2", cc2.getAlias(), icu::HebrewCalendar::ADAR, 5, false, "M06"); + VerifyMonth(this, "cc3", cc3.getAlias(), icu::HebrewCalendar::ADAR, 5, false, "M06"); + + cc1->set(UCAL_EXTENDED_YEAR, 5782); + cc2->set(UCAL_EXTENDED_YEAR, 5782); + cc3->set(UCAL_EXTENDED_YEAR, 5782); + assertTrue("5782 M06 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("5782 M06 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "cc1", cc1.getAlias(), icu::HebrewCalendar::ADAR, 6, false, "M06"); + VerifyMonth(this, "cc2", cc2.getAlias(), icu::HebrewCalendar::ADAR, 6, false, "M06"); + VerifyMonth(this, "cc3", cc3.getAlias(), icu::HebrewCalendar::ADAR, 6, false, "M06"); + + cc1->set(UCAL_ORDINAL_MONTH, 6); + cc2->setTemporalMonthCode("M07", status); + if (failure(status, "setTemporalMonthCode failure")) return; + cc3->set(UCAL_MONTH, icu::HebrewCalendar::NISAN); + assertTrue("5782 M07 cc2==cc1 set month by UCAL_MONTH and UCAL_UCAL_ORDINAL_MONTH", cc2->equals(*cc1, status)); + assertTrue("5782 M07 cc2==cc3 set month by UCAL_MONTH and setTemporalMonthCode", cc2->equals(*cc3, status)); + if (failure(status, "equals failure")) return; + VerifyMonth(this, "cc1", cc1.getAlias(), icu::HebrewCalendar::NISAN, 7, false, "M07"); + VerifyMonth(this, "cc2", cc2.getAlias(), icu::HebrewCalendar::NISAN, 7, false, "M07"); + VerifyMonth(this, "cc3", cc3.getAlias(), icu::HebrewCalendar::NISAN, 7, false, "M07"); + + // Out of bound monthCodes should return error. + // These are not valid for calendar do not have a leap month + UErrorCode expectedStatus = U_ILLEGAL_ARGUMENT_ERROR; + const char* kInvalidMonthCodes[] = { "M00", "M13", "M14", + "M01L", "M02L", "M03L", "M04L", + /* M05L could be legal */ + "M06L", "M07L", "M08L", "M09L", "M10L", "M11L", "M12L", + }; + + for (auto& cas : kInvalidMonthCodes) { + cc1->setTemporalMonthCode(cas, status); + if (status != expectedStatus) { + errln("setTemporalMonthCode(%s) should return U_ILLEGAL_ARGUMENT_ERROR", cas); + } + status = U_ZERO_ERROR; + } +} + +void CalendarTest::TestCalendarAddOrdinalMonth(void) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + gc.set(2022, UCAL_DECEMBER, 16); + Locale l(Locale::getRoot()); + std::unique_ptr enumeration( + Calendar::getKeywordValuesForLocale("calendar", l, false, status)); + for (const char* name = enumeration->next(nullptr, status); + U_SUCCESS(status) && name != nullptr; + name = enumeration->next(nullptr, status)) { + + l.setKeywordValue("calendar", name, status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "Construct Calendar")) return; + + cc1->setTime(gc.getTime(status), status); + LocalPointer cc2(cc1->clone()); + + for (int i = 0; i < 8; i++) { + for (int j = 1; j < 8; j++) { + cc1->add(UCAL_MONTH, j, status); + cc2->add(UCAL_ORDINAL_MONTH, j, status); + if (failure(status, "add j")) return; + assertTrue("two add produce the same result", cc2->equals(*cc1, status)); + } + for (int j = 1; j < 8; j++) { + cc1->add(UCAL_MONTH, -j, status); + cc2->add(UCAL_ORDINAL_MONTH, -j, status); + if (failure(status, "add -j")) return; + assertTrue("two add produce the same result", cc2->equals(*cc1, status)); + } + } + } +} + +void CalendarTest::TestCalendarRollOrdinalMonth(void) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + gc.set(2022, UCAL_DECEMBER, 16); + Locale l(Locale::getRoot()); + std::unique_ptr enumeration( + Calendar::getKeywordValuesForLocale("calendar", l, false, status)); + for (const char* name = enumeration->next(nullptr, status); + U_SUCCESS(status) && name != nullptr; + name = enumeration->next(nullptr, status)) { + + l.setKeywordValue("calendar", name, status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "Construct Calendar")) return; + + cc1->setTime(gc.getTime(status), status); + LocalPointer cc2(cc1->clone()); + + for (int i = 0; i < 8; i++) { + for (int j = 1; j < 8; j++) { + cc1->roll(UCAL_MONTH, j, status); + cc2->roll(UCAL_ORDINAL_MONTH, j, status); + if (failure(status, "roll j")) return; + assertTrue("two add produce the same result", cc2->equals(*cc1, status)); + } + for (int j = 1; j < 8; j++) { + cc1->roll(UCAL_MONTH, -j, status); + cc2->roll(UCAL_ORDINAL_MONTH, -j, status); + if (failure(status, "roll -j")) return; + assertTrue("two add produce the same result", cc2->equals(*cc1, status)); + } + for (int j = 1; j < 3; j++) { + cc1->roll(UCAL_MONTH, true, status); + cc2->roll(UCAL_ORDINAL_MONTH, true, status); + if (failure(status, "roll true")) return; + assertTrue("two add produce the same result", cc2->equals(*cc1, status)); + } + for (int j = 1; j < 3; j++) { + cc1->roll(UCAL_MONTH, false, status); + cc2->roll(UCAL_ORDINAL_MONTH, false, status); + if (failure(status, "roll false")) return; + assertTrue("two add produce the same result", cc2->equals(*cc1, status)); + } + } + } +} + +void CalendarTest::TestLimitsOrdinalMonth(void) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + gc.set(2022, UCAL_DECEMBER, 16); + Locale l(Locale::getRoot()); + std::unique_ptr enumeration( + Calendar::getKeywordValuesForLocale("calendar", l, false, status)); + + struct Expectation { + const char* calendar; + int32_t min; + int32_t max; + int32_t greatestMin; + int32_t leastMax; + } kExpectations[] = { + { "gregorian", 0, 11, 0, 11 }, + { "japanese", 0, 11, 0, 11 }, + { "buddhist", 0, 11, 0, 11 }, + { "roc", 0, 11, 0, 11 }, + { "persian", 0, 11, 0, 11 }, + { "islamic-civil", 0, 11, 0, 11 }, + { "islamic", 0, 11, 0, 11 }, + { "hebrew", 0, 12, 0, 11 }, + { "chinese", 0, 12, 0, 11 }, + { "indian", 0, 11, 0, 11 }, + { "coptic", 0, 12, 0, 12 }, + { "ethiopic", 0, 12, 0, 12 }, + { "ethiopic-amete-alem", 0, 12, 0, 12 }, + { "iso8601", 0, 11, 0, 11 }, + { "dangi", 0, 12, 0, 11 }, + { "islamic-umalqura", 0, 11, 0, 11 }, + { "islamic-tbla", 0, 11, 0, 11 }, + { "islamic-rgsa", 0, 11, 0, 11 }, + }; + + for (const char* name = enumeration->next(nullptr, status); + U_SUCCESS(status) && name != nullptr; + name = enumeration->next(nullptr, status)) { + l.setKeywordValue("calendar", name, status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "Construct Calendar")) return; + bool found = false; + for (auto& exp : kExpectations) { + if (uprv_strcmp(exp.calendar, name) == 0) { + found = true; + assertEquals("getMinimum(UCAL_ORDINAL_MONTH)", + exp.min, cc1->getMinimum(UCAL_ORDINAL_MONTH)); + assertEquals("getMaximum(UCAL_ORDINAL_MONTH)", + exp.max, cc1->getMaximum(UCAL_ORDINAL_MONTH)); + assertEquals("getMinimum(UCAL_ORDINAL_MONTH)", + exp.greatestMin, cc1->getGreatestMinimum(UCAL_ORDINAL_MONTH)); + assertEquals("getMinimum(UCAL_ORDINAL_MONTH)", + exp.leastMax, cc1->getLeastMaximum(UCAL_ORDINAL_MONTH)); + break; + } + } + if (!found) { + errln("Cannot find expectation"); + } + } +} + +void CalendarTest::TestActualLimitsOrdinalMonth(void) { + UErrorCode status = U_ZERO_ERROR; + GregorianCalendar gc(status); + gc.set(2022, UCAL_DECEMBER, 16); + Locale l(Locale::getRoot()); + std::unique_ptr enumeration( + Calendar::getKeywordValuesForLocale("calendar", l, false, status)); + + struct TestCases { + const char* calendar; + int32_t extended_year; + int32_t actualMinOrdinalMonth; + int32_t actualMaxOrdinalMonth; + } cases[] = { + { "gregorian", 2021, 0, 11 }, + { "gregorian", 2022, 0, 11 }, + { "gregorian", 2023, 0, 11 }, + { "japanese", 2021, 0, 11 }, + { "japanese", 2022, 0, 11 }, + { "japanese", 2023, 0, 11 }, + { "buddhist", 2021, 0, 11 }, + { "buddhist", 2022, 0, 11 }, + { "buddhist", 2023, 0, 11 }, + { "roc", 2021, 0, 11 }, + { "roc", 2022, 0, 11 }, + { "roc", 2023, 0, 11 }, + { "persian", 1400, 0, 11 }, + { "persian", 1401, 0, 11 }, + { "persian", 1402, 0, 11 }, + { "hebrew", 5782, 0, 12 }, + { "hebrew", 5783, 0, 11 }, + { "hebrew", 5789, 0, 11 }, + { "hebrew", 5790, 0, 12 }, + { "chinese", 4645, 0, 11 }, + { "chinese", 4646, 0, 12 }, + { "chinese", 4647, 0, 11 }, + { "dangi", 4645 + 304, 0, 11 }, + { "dangi", 4646 + 304, 0, 12 }, + { "dangi", 4647 + 304, 0, 11 }, + { "indian", 1944, 0, 11 }, + { "indian", 1945, 0, 11 }, + { "indian", 1946, 0, 11 }, + { "coptic", 1737, 0, 12 }, + { "coptic", 1738, 0, 12 }, + { "coptic", 1739, 0, 12 }, + { "ethiopic", 2013, 0, 12 }, + { "ethiopic", 2014, 0, 12 }, + { "ethiopic", 2015, 0, 12 }, + { "ethiopic-amete-alem", 2014, 0, 12 }, + { "ethiopic-amete-alem", 2015, 0, 12 }, + { "ethiopic-amete-alem", 2016, 0, 12 }, + { "iso8601", 2022, 0, 11 }, + { "islamic-civil", 1443, 0, 11 }, + { "islamic-civil", 1444, 0, 11 }, + { "islamic-civil", 1445, 0, 11 }, + { "islamic", 1443, 0, 11 }, + { "islamic", 1444, 0, 11 }, + { "islamic", 1445, 0, 11 }, + { "islamic-umalqura", 1443, 0, 11 }, + { "islamic-umalqura", 1444, 0, 11 }, + { "islamic-umalqura", 1445, 0, 11 }, + { "islamic-tbla", 1443, 0, 11 }, + { "islamic-tbla", 1444, 0, 11 }, + { "islamic-tbla", 1445, 0, 11 }, + { "islamic-rgsa", 1443, 0, 11 }, + { "islamic-rgsa", 1444, 0, 11 }, + { "islamic-rgsa", 1445, 0, 11 }, + }; + + for (auto& cas : cases) { + l.setKeywordValue("calendar", cas.calendar, status); + LocalPointer cc1(Calendar::createInstance(l, status)); + if (failure(status, "Construct Calendar")) return; + cc1->set(UCAL_EXTENDED_YEAR, cas.extended_year); + cc1->set(UCAL_ORDINAL_MONTH, 0); + cc1->set(UCAL_DATE, 1); + assertEquals("getActualMinimum(UCAL_ORDINAL_MONTH)", + cas.actualMinOrdinalMonth, cc1->getActualMinimum(UCAL_ORDINAL_MONTH, status)); + assertEquals("getActualMaximum(UCAL_ORDINAL_MONTH)", + cas.actualMaxOrdinalMonth, cc1->getActualMaximum(UCAL_ORDINAL_MONTH, status)); + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ //eof diff --git a/icu4c/source/test/intltest/caltest.h b/icu4c/source/test/intltest/caltest.h index 55225ac828d..d484521ff13 100644 --- a/icu4c/source/test/intltest/caltest.h +++ b/icu4c/source/test/intltest/caltest.h @@ -285,6 +285,53 @@ public: // package int32_t minute, int32_t second, int32_t millisecond, int32_t zone_offset, int32_t dst_offset, int32_t year_woy, int32_t dow_local, int32_t extended_year, int32_t julian_day, int32_t milliseconds_in_day, int32_t is_leap_month); + + void TestGregorianCalendarInTemporalLeapYear(void); + void TestChineseCalendarInTemporalLeapYear(void); + void TestDangiCalendarInTemporalLeapYear(void); + void TestHebrewCalendarInTemporalLeapYear(void); + void TestIslamicCalendarInTemporalLeapYear(void); + void TestIslamicCivilCalendarInTemporalLeapYear(void); + void TestIslamicUmalquraCalendarInTemporalLeapYear(void); + void TestIslamicRGSACalendarInTemporalLeapYear(void); + void TestIslamicTBLACalendarInTemporalLeapYear(void); + void TestPersianCalendarInTemporalLeapYear(void); + void TestIndianCalendarInTemporalLeapYear(void); + void TestTaiwanCalendarInTemporalLeapYear(void); + void TestJapaneseCalendarInTemporalLeapYear(void); + void TestBuddhistCalendarInTemporalLeapYear(void); + void TestCopticCalendarInTemporalLeapYear(void); + void TestEthiopicCalendarInTemporalLeapYear(void); + void TestEthiopicAmeteAlemCalendarInTemporalLeapYear(void); + + void TestChineseCalendarGetTemporalMonthCode(void); + void TestDangiCalendarGetTemporalMonthCode(void); + void TestHebrewCalendarGetTemporalMonthCode(void); + void TestCopticCalendarGetTemporalMonthCode(void); + void TestEthiopicCalendarGetTemporalMonthCode(void); + void TestEthiopicAmeteAlemCalendarGetTemporalMonthCode(void); + + void TestGregorianCalendarSetTemporalMonthCode(void); + void TestChineseCalendarSetTemporalMonthCode(void); + void TestHebrewCalendarSetTemporalMonthCode(void); + void TestCopticCalendarSetTemporalMonthCode(void); + void TestEthiopicCalendarSetTemporalMonthCode(void); + + void TestMostCalendarsOrdinalMonthSet(void); + void TestChineseCalendarOrdinalMonthSet(void); + void TestDangiCalendarOrdinalMonthSet(void); + void TestHebrewCalendarOrdinalMonthSet(void); + + void TestCalendarAddOrdinalMonth(void); + void TestCalendarRollOrdinalMonth(void); + void TestLimitsOrdinalMonth(void); + void TestActualLimitsOrdinalMonth(void); + + void RunChineseCalendarInTemporalLeapYearTest(Calendar* cal); + void RunIslamicCalendarInTemporalLeapYearTest(Calendar* cal); + void Run366DaysIsLeapYearCalendarInTemporalLeapYearTest(Calendar* cal); + void RunChineseCalendarGetTemporalMonthCode(Calendar* cal); + void RunCECalendarGetTemporalMonthCode(Calendar* cal); }; #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/incaltst.cpp b/icu4c/source/test/intltest/incaltst.cpp index 250b61c7ac0..6636f7b590b 100644 --- a/icu4c/source/test/intltest/incaltst.cpp +++ b/icu4c/source/test/intltest/incaltst.cpp @@ -1091,7 +1091,14 @@ void IntlCalendarTest::checkConsistency(const char* locale) { " Gregorian(e=" + g->get(UCAL_ERA, status) + " " + g->get(UCAL_YEAR, status) + "/" + (g->get(UCAL_MONTH, status) + 1) + "/" + - g->get(UCAL_DATE, status) + ") "); + g->get(UCAL_DATE, status) + ") \n" + + " Calendar[" + base->getType() + + "](e=" + base->get(UCAL_ERA, status) + " " + + base->get(UCAL_YEAR, status) + "/" + + (base->get(UCAL_MONTH, status) + 1) + "/" + + base->get(UCAL_DATE, status) + ") ordinalMonth=" + + base->get(UCAL_ORDINAL_MONTH, status)); + status.errIfFailureAndReset(); return; } diff --git a/icu4c/source/tools/toolutil/udbgutil.cpp b/icu4c/source/tools/toolutil/udbgutil.cpp index dcf71b28872..c92c6e7d001 100644 --- a/icu4c/source/tools/toolutil/udbgutil.cpp +++ b/icu4c/source/tools/toolutil/udbgutil.cpp @@ -107,6 +107,9 @@ static const Field names_UCalendarDateFields[] = FIELD_NAME_STR( LEN_UCAL, UCAL_JULIAN_DAY ), FIELD_NAME_STR( LEN_UCAL, UCAL_MILLISECONDS_IN_DAY ), FIELD_NAME_STR( LEN_UCAL, UCAL_IS_LEAP_MONTH ), +#ifndef U_HIDE_DRAFT_API + FIELD_NAME_STR( LEN_UCAL, UCAL_ORDINAL_MONTH ), +#endif // U_HIDE_DRAFT_API };