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
2932static 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
65107U_NAMESPACE_BEGIN
66108
67109static const int32_t PERSIAN_EPOCH = 1948320 ;
@@ -111,8 +153,15 @@ int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field, ELimitType li
111153 */
112154UBool 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
169227int64_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) {
219277void 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);
0 commit comments