Skip to content

Commit 73956e9

Browse files
ICU-22736 Fix Persian calendar
ICU-22736 Add tests for java and make correction Update icu4c/source/i18n/persncal.cpp Co-authored-by: Markus Scherer <[email protected]>
1 parent 36b5527 commit 73956e9

File tree

6 files changed

+985
-13
lines changed

6 files changed

+985
-13
lines changed

icu4c/source/i18n/persncal.cpp

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
#include "umutex.h"
2626
#include "gregoimp.h" // Math
2727
#include <float.h>
28+
#include "cmemory.h"
29+
#include "ucln_in.h"
30+
#include "unicode/uniset.h"
2831

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

68+
namespace { // anonymous
69+
70+
static icu::UnicodeSet *gLeapCorrection = nullptr;
71+
static icu::UInitOnce gCorrectionInitOnce {};
72+
static int32_t gMinCorrection;
73+
} // namespace
74+
U_CDECL_BEGIN
75+
static UBool calendar_persian_cleanup() {
76+
if (gLeapCorrection) {
77+
delete gLeapCorrection;
78+
gLeapCorrection = nullptr;
79+
}
80+
gCorrectionInitOnce.reset();
81+
return true;
82+
}
83+
U_CDECL_END
84+
85+
namespace { // anonymous
86+
static void U_CALLCONV initLeapCorrection() {
87+
static int16_t nonLeapYears[] = {
88+
1502, 1601, 1634, 1667, 1700, 1733, 1766, 1799, 1832, 1865, 1898, 1931, 1964, 1997, 2030, 2059,
89+
2063, 2096, 2129, 2158, 2162, 2191, 2195, 2224, 2228, 2257, 2261, 2290, 2294, 2323, 2327, 2356,
90+
2360, 2389, 2393, 2422, 2426, 2455, 2459, 2488, 2492, 2521, 2525, 2554, 2558, 2587, 2591, 2620,
91+
2624, 2653, 2657, 2686, 2690, 2719, 2723, 2748, 2752, 2756, 2781, 2785, 2789, 2818, 2822, 2847,
92+
2851, 2855, 2880, 2884, 2888, 2913, 2917, 2921, 2946, 2950, 2954, 2979, 2983, 2987,
93+
};
94+
gMinCorrection = nonLeapYears[0];
95+
icu::UnicodeSet prefab;
96+
for (auto year : nonLeapYears) {
97+
prefab.add(year);
98+
}
99+
gLeapCorrection = prefab.cloneAsThawed();
100+
ucln_i18n_registerCleanup(UCLN_I18N_PERSIAN_CALENDAR, calendar_persian_cleanup);
101+
}
102+
const icu::UnicodeSet* getLeapCorrection() {
103+
umtx_initOnce(gCorrectionInitOnce, &initLeapCorrection);
104+
return gLeapCorrection;
105+
}
106+
} // namespace anonymous
65107
U_NAMESPACE_BEGIN
66108

67109
static const int32_t PERSIAN_EPOCH = 1948320;
@@ -111,8 +153,15 @@ int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field, ELimitType li
111153
*/
112154
UBool PersianCalendar::isLeapYear(int32_t year)
113155
{
156+
if (year >= gMinCorrection && getLeapCorrection()->contains(year)) {
157+
return false;
158+
}
159+
if (year > gMinCorrection && getLeapCorrection()->contains(year-1)) {
160+
return true;
161+
}
114162
int64_t y = static_cast<int64_t>(year) * 25LL + 11LL;
115-
return (y % 33L < 8);
163+
bool res = (y % 33L < 8);
164+
return res;
116165
}
117166

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

217+
static int64_t firstJulianOfYear(int64_t year) {
218+
int64_t julianDay = 365LL * (year - 1LL) + ClockMath::floorDivide(8LL * year + 21, 33);
219+
if (year > gMinCorrection && getLeapCorrection()->contains(year-1)) {
220+
julianDay--;
221+
}
222+
return julianDay;
223+
}
224+
225+
168226
// Return JD of start of given month/year
169227
int64_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool /*useMonth*/, UErrorCode& status) const {
170228
if (U_FAILURE(status)) {
@@ -179,7 +237,7 @@ int64_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U
179237
}
180238
}
181239

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

184242
if (month != 0) {
185243
julianDay += kPersianNumDays[month];
@@ -219,18 +277,24 @@ int32_t PersianCalendar::handleGetExtendedYear(UErrorCode& status) {
219277
void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) {
220278
int64_t daysSinceEpoch = julianDay;
221279
daysSinceEpoch -= PERSIAN_EPOCH;
280+
222281
int64_t year = ClockMath::floorDivideInt64(
223282
33LL * daysSinceEpoch + 3LL, 12053LL) + 1LL;
224283
if (year > INT32_MAX || year < INT32_MIN) {
225284
status = U_ILLEGAL_ARGUMENT_ERROR;
226285
return;
227286
}
228287

229-
int64_t farvardin1 = 365LL * (year - 1) + ClockMath::floorDivide(8LL * year + 21, 33);
288+
int64_t farvardin1 = firstJulianOfYear(year);
289+
230290
int32_t dayOfYear = daysSinceEpoch - farvardin1; // 0-based
231291
U_ASSERT(dayOfYear >= 0);
232292
U_ASSERT(dayOfYear < 366);
233-
//
293+
294+
if (dayOfYear == 365 && year >= gMinCorrection && getLeapCorrection()->contains(year)) {
295+
year++;
296+
dayOfYear = 0;
297+
}
234298
int32_t month;
235299
if (dayOfYear < 216) { // Compute 0-based month
236300
month = dayOfYear / 31;
@@ -240,11 +304,11 @@ void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status)
240304
U_ASSERT(month >= 0);
241305
U_ASSERT(month < 12);
242306

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

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

249313
internalSet(UCAL_ERA, 0);
250314
internalSet(UCAL_YEAR, year);

icu4c/source/i18n/ucln_in.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ typedef enum ECleanupI18NType {
3939
UCLN_I18N_HEBREW_CALENDAR,
4040
UCLN_I18N_ASTRO_CALENDAR,
4141
UCLN_I18N_DANGI_CALENDAR,
42+
UCLN_I18N_PERSIAN_CALENDAR,
4243
UCLN_I18N_CALENDAR,
4344
UCLN_I18N_TIMEZONEFORMAT,
4445
UCLN_I18N_TZDBTIMEZONENAMES,

0 commit comments

Comments
 (0)