Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ICU-22736 Improve Persian Calendar #3163

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 69 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 (int32_t i = 0; i < UPRV_LENGTHOF(nonLeapYears); i++) {
prefab.add(nonLeapYears[i]);
}
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 @@ -225,11 +283,16 @@ void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status)
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 @@ -239,11 +302,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
Loading