Skip to content

Commit

Permalink
ICU-22736 Fix Persian calendar
Browse files Browse the repository at this point in the history
ICU-22736 Add tests for java and make correction

Update icu4c/source/i18n/persncal.cpp

Co-authored-by: Markus Scherer <[email protected]>
  • Loading branch information
FrankYFTang and markusicu committed Dec 7, 2024
1 parent 36b5527 commit 73956e9
Show file tree
Hide file tree
Showing 6 changed files with 985 additions and 13 deletions.
76 changes: 70 additions & 6 deletions icu4c/source/i18n/persncal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
#include "umutex.h"
#include "gregoimp.h" // Math
#include <float.h>
#include "cmemory.h"
#include "ucln_in.h"
#include "unicode/uniset.h"

static const int16_t kPersianNumDays[]
= {0,31,62,93,124,155,186,216,246,276,306,336}; // 0-based, for day-in-year
Expand Down Expand Up @@ -62,6 +65,45 @@ static const int32_t kPersianCalendarLimits[UCAL_FIELD_COUNT][4] = {
{ 0, 0, 11, 11}, // ORDINAL_MONTH
};

namespace { // anonymous

static icu::UnicodeSet *gLeapCorrection = nullptr;
static icu::UInitOnce gCorrectionInitOnce {};
static int32_t gMinCorrection;
} // namespace
U_CDECL_BEGIN
static UBool calendar_persian_cleanup() {
if (gLeapCorrection) {
delete gLeapCorrection;
gLeapCorrection = nullptr;
}
gCorrectionInitOnce.reset();
return true;
}
U_CDECL_END

namespace { // anonymous
static void U_CALLCONV initLeapCorrection() {
static int16_t nonLeapYears[] = {
1502, 1601, 1634, 1667, 1700, 1733, 1766, 1799, 1832, 1865, 1898, 1931, 1964, 1997, 2030, 2059,
2063, 2096, 2129, 2158, 2162, 2191, 2195, 2224, 2228, 2257, 2261, 2290, 2294, 2323, 2327, 2356,
2360, 2389, 2393, 2422, 2426, 2455, 2459, 2488, 2492, 2521, 2525, 2554, 2558, 2587, 2591, 2620,
2624, 2653, 2657, 2686, 2690, 2719, 2723, 2748, 2752, 2756, 2781, 2785, 2789, 2818, 2822, 2847,
2851, 2855, 2880, 2884, 2888, 2913, 2917, 2921, 2946, 2950, 2954, 2979, 2983, 2987,
};
gMinCorrection = nonLeapYears[0];
icu::UnicodeSet prefab;
for (auto year : nonLeapYears) {
prefab.add(year);
}
gLeapCorrection = prefab.cloneAsThawed();
ucln_i18n_registerCleanup(UCLN_I18N_PERSIAN_CALENDAR, calendar_persian_cleanup);
}
const icu::UnicodeSet* getLeapCorrection() {
umtx_initOnce(gCorrectionInitOnce, &initLeapCorrection);
return gLeapCorrection;
}
} // namespace anonymous
U_NAMESPACE_BEGIN

static const int32_t PERSIAN_EPOCH = 1948320;
Expand Down Expand Up @@ -111,8 +153,15 @@ int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field, ELimitType li
*/
UBool PersianCalendar::isLeapYear(int32_t year)
{
if (year >= gMinCorrection && getLeapCorrection()->contains(year)) {
return false;
}
if (year > gMinCorrection && getLeapCorrection()->contains(year-1)) {
return true;
}
int64_t y = static_cast<int64_t>(year) * 25LL + 11LL;
return (y % 33L < 8);
bool res = (y % 33L < 8);
return res;
}

/**
Expand Down Expand Up @@ -165,6 +214,15 @@ int32_t PersianCalendar::handleGetYearLength(int32_t extendedYear) const {
// Functions for converting from field values to milliseconds....
//-------------------------------------------------------------------------

static int64_t firstJulianOfYear(int64_t year) {
int64_t julianDay = 365LL * (year - 1LL) + ClockMath::floorDivide(8LL * year + 21, 33);
if (year > gMinCorrection && getLeapCorrection()->contains(year-1)) {
julianDay--;
}
return julianDay;
}


// Return JD of start of given month/year
int64_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool /*useMonth*/, UErrorCode& status) const {
if (U_FAILURE(status)) {
Expand All @@ -179,7 +237,7 @@ int64_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U
}
}

int64_t julianDay = PERSIAN_EPOCH - 1LL + 365LL * (eyear - 1LL) + ClockMath::floorDivide(8LL * eyear + 21, 33);
int64_t julianDay = PERSIAN_EPOCH - 1LL + firstJulianOfYear(eyear);

if (month != 0) {
julianDay += kPersianNumDays[month];
Expand Down Expand Up @@ -219,18 +277,24 @@ int32_t PersianCalendar::handleGetExtendedYear(UErrorCode& status) {
void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) {
int64_t daysSinceEpoch = julianDay;
daysSinceEpoch -= PERSIAN_EPOCH;

int64_t year = ClockMath::floorDivideInt64(
33LL * daysSinceEpoch + 3LL, 12053LL) + 1LL;
if (year > INT32_MAX || year < INT32_MIN) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}

int64_t farvardin1 = 365LL * (year - 1) + ClockMath::floorDivide(8LL * year + 21, 33);
int64_t farvardin1 = firstJulianOfYear(year);

int32_t dayOfYear = daysSinceEpoch - farvardin1; // 0-based
U_ASSERT(dayOfYear >= 0);
U_ASSERT(dayOfYear < 366);
//

if (dayOfYear == 365 && year >= gMinCorrection && getLeapCorrection()->contains(year)) {
year++;
dayOfYear = 0;
}
int32_t month;
if (dayOfYear < 216) { // Compute 0-based month
month = dayOfYear / 31;
Expand All @@ -240,11 +304,11 @@ void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status)
U_ASSERT(month >= 0);
U_ASSERT(month < 12);

int32_t dayOfMonth = dayOfYear - kPersianNumDays[month] + 1;
++dayOfYear; // Make it 1-based now
int32_t dayOfMonth = dayOfYear - kPersianNumDays[month];
U_ASSERT(dayOfMonth > 0);
U_ASSERT(dayOfMonth <= 31);

++dayOfYear; // Make it 1-based now

internalSet(UCAL_ERA, 0);
internalSet(UCAL_YEAR, year);
Expand Down
1 change: 1 addition & 0 deletions icu4c/source/i18n/ucln_in.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ typedef enum ECleanupI18NType {
UCLN_I18N_HEBREW_CALENDAR,
UCLN_I18N_ASTRO_CALENDAR,
UCLN_I18N_DANGI_CALENDAR,
UCLN_I18N_PERSIAN_CALENDAR,
UCLN_I18N_CALENDAR,
UCLN_I18N_TIMEZONEFORMAT,
UCLN_I18N_TZDBTIMEZONENAMES,
Expand Down
Loading

0 comments on commit 73956e9

Please sign in to comment.