Coverage Report

Created: 2026-02-05 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/icu/icu4c/source/i18n/persncal.cpp
Line
Count
Source
1
// © 2016 and later: Unicode, Inc. and others.
2
// License & terms of use: http://www.unicode.org/copyright.html
3
/*
4
 ******************************************************************************
5
 * Copyright (C) 2003-2013, International Business Machines Corporation
6
 * and others. All Rights Reserved.
7
 ******************************************************************************
8
 *
9
 * File PERSNCAL.CPP
10
 *
11
 * Modification History:
12
 *
13
 *   Date        Name        Description
14
 *   9/23/2003   mehran      posted to icu-design
15
 *   10/1/2012   roozbeh     Fixed algorithm and heavily refactored and rewrote
16
 *                           based on the implementation of Gregorian
17
 *****************************************************************************
18
 */
19
20
#include "persncal.h"
21
22
#if !UCONFIG_NO_FORMATTING
23
24
#include "uassert.h"
25
#include "umutex.h"
26
#include "gregoimp.h" // Math
27
#include <float.h>
28
#include "cmemory.h"
29
#include "ucln_in.h"
30
#include "unicode/uniset.h"
31
32
static const int16_t kPersianNumDays[]
33
= {0,31,62,93,124,155,186,216,246,276,306,336}; // 0-based, for day-in-year
34
static const int8_t kPersianMonthLength[]
35
= {31,31,31,31,31,31,30,30,30,30,30,29}; // 0-based
36
static const int8_t kPersianLeapMonthLength[]
37
= {31,31,31,31,31,31,30,30,30,30,30,30}; // 0-based
38
39
static const int32_t kPersianCalendarLimits[UCAL_FIELD_COUNT][4] = {
40
    // Minimum  Greatest     Least   Maximum
41
    //           Minimum   Maximum
42
    {        0,        0,        0,        0}, // ERA
43
    { -5000000, -5000000,  5000000,  5000000}, // YEAR
44
    {        0,        0,       11,       11}, // MONTH
45
    {        1,        1,       52,       53}, // WEEK_OF_YEAR
46
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH
47
    {        1,       1,        29,       31}, // DAY_OF_MONTH
48
    {        1,       1,       365,      366}, // DAY_OF_YEAR
49
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK
50
    {        1,       1,         5,        5}, // DAY_OF_WEEK_IN_MONTH
51
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM
52
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR
53
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY
54
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE
55
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND
56
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND
57
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET
58
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET
59
    { -5000000, -5000000,  5000000,  5000000}, // YEAR_WOY
60
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL
61
    { -5000000, -5000000,  5000000,  5000000}, // EXTENDED_YEAR
62
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY
63
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY
64
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH
65
    {        0,        0,       11,       11}, // ORDINAL_MONTH
66
};
67
68
namespace { // anonymous
69
70
static const icu::UnicodeSet *gLeapCorrection = nullptr;
71
static icu::UInitOnce gCorrectionInitOnce {};
72
static int32_t gMinCorrection;
73
}  // namespace
74
U_CDECL_BEGIN
75
0
static UBool calendar_persian_cleanup() {
76
0
    if (gLeapCorrection) {
77
0
        delete gLeapCorrection;
78
0
        gLeapCorrection = nullptr;
79
0
    }
80
0
    gCorrectionInitOnce.reset();
81
0
    return true;
82
0
}
83
U_CDECL_END
84
85
namespace { // anonymous
86
2
static void U_CALLCONV initLeapCorrection() {
87
2
    static int16_t nonLeapYears[] = {
88
2
       1502, 1601, 1634, 1667, 1700, 1733, 1766, 1799, 1832, 1865, 1898, 1931, 1964, 1997, 2030, 2059,
89
2
       2063, 2096, 2129, 2158, 2162, 2191, 2195, 2224, 2228, 2257, 2261, 2290, 2294, 2323, 2327, 2356,
90
2
       2360, 2389, 2393, 2422, 2426, 2455, 2459, 2488, 2492, 2521, 2525, 2554, 2558, 2587, 2591, 2620,
91
2
       2624, 2653, 2657, 2686, 2690, 2719, 2723, 2748, 2752, 2756, 2781, 2785, 2789, 2818, 2822, 2847,
92
2
       2851, 2855, 2880, 2884, 2888, 2913, 2917, 2921, 2946, 2950, 2954, 2979, 2983, 2987,
93
2
    };
94
2
    gMinCorrection = nonLeapYears[0];
95
2
    icu::UnicodeSet prefab;
96
156
    for (auto year : nonLeapYears) {
97
156
        prefab.add(year);
98
156
    }
99
2
    gLeapCorrection = prefab.cloneAsThawed()->freeze();
100
2
    ucln_i18n_registerCleanup(UCLN_I18N_PERSIAN_CALENDAR, calendar_persian_cleanup);
101
2
}
102
20.2k
const icu::UnicodeSet* getLeapCorrection() {
103
20.2k
    umtx_initOnce(gCorrectionInitOnce, &initLeapCorrection);
104
20.2k
    return gLeapCorrection;
105
20.2k
}
106
} // namespace anonymous
107
U_NAMESPACE_BEGIN
108
109
static const int32_t PERSIAN_EPOCH = 1948320;
110
111
// Implementation of the PersianCalendar class
112
113
//-------------------------------------------------------------------------
114
// Constructors...
115
//-------------------------------------------------------------------------
116
117
1.60k
const char *PersianCalendar::getType() const { 
118
1.60k
    return "persian";
119
1.60k
}
120
121
6.94k
PersianCalendar* PersianCalendar::clone() const {
122
6.94k
    return new PersianCalendar(*this);
123
6.94k
}
124
125
PersianCalendar::PersianCalendar(const Locale& aLocale, UErrorCode& success)
126
451
  :   Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success)
127
451
{
128
451
}
129
130
6.94k
PersianCalendar::PersianCalendar(const PersianCalendar& other) : Calendar(other) {
131
6.94k
}
132
133
PersianCalendar::~PersianCalendar()
134
7.31k
{
135
7.31k
}
136
137
//-------------------------------------------------------------------------
138
// Minimum / Maximum access functions
139
//-------------------------------------------------------------------------
140
141
142
10.7k
int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const {
143
10.7k
    return kPersianCalendarLimits[field][limitType];
144
10.7k
}
145
146
//-------------------------------------------------------------------------
147
// Assorted calculation utilities
148
//
149
150
/**
151
 * Determine whether a year is a leap year in the Persian calendar
152
 */
153
UBool PersianCalendar::isLeapYear(int32_t year)
154
18.8k
{
155
18.8k
    if (year >= gMinCorrection && getLeapCorrection()->contains(year)) {
156
357
        return false;
157
357
    }
158
18.4k
    if (year > gMinCorrection && getLeapCorrection()->contains(year-1)) {
159
943
        return true;
160
943
    }
161
17.5k
    int64_t y = static_cast<int64_t>(year) * 25LL + 11LL;
162
17.5k
    bool res = (y % 33L < 8);
163
17.5k
    return res;
164
18.4k
}
165
    
166
/**
167
 * Return the day # on which the given year starts.  Days are counted
168
 * from the Persian epoch, origin 0.
169
 */
170
0
int32_t PersianCalendar::yearStart(int32_t year, UErrorCode& status) {
171
0
    return handleComputeMonthStart(year,0,false, status);
172
0
}
173
    
174
/**
175
 * Return the day # on which the given month starts.  Days are counted
176
 * from the Persian epoch, origin 0.
177
 *
178
 * @param year  The Persian year
179
 * @param year  The Persian month, 0-based
180
 */
181
0
int32_t PersianCalendar::monthStart(int32_t year, int32_t month, UErrorCode& status) const {
182
0
    return handleComputeMonthStart(year,month,true, status);
183
0
}
184
    
185
//----------------------------------------------------------------------
186
// Calendar framework
187
//----------------------------------------------------------------------
188
189
/**
190
 * Return the length (in days) of the given month.
191
 *
192
 * @param year  The Persian year
193
 * @param year  The Persian month, 0-based
194
 */
195
3.28k
int32_t PersianCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month, UErrorCode& /*status*/) const {
196
    // If the month is out of range, adjust it into range, and
197
    // modify the extended year value accordingly.
198
3.28k
    if (month < 0 || month > 11) {
199
69
        extendedYear += ClockMath::floorDivide(month, 12, &month);
200
69
    }
201
202
3.28k
    return isLeapYear(extendedYear) ? kPersianLeapMonthLength[month] : kPersianMonthLength[month];
203
3.28k
}
204
205
/**
206
 * Return the number of days in the given Persian year
207
 */
208
15.5k
int32_t PersianCalendar::handleGetYearLength(int32_t extendedYear, UErrorCode& status) const {
209
15.5k
    if (U_FAILURE(status)) return 0;
210
15.5k
    return isLeapYear(extendedYear) ? 366 : 365;
211
15.5k
}
212
    
213
//-------------------------------------------------------------------------
214
// Functions for converting from field values to milliseconds....
215
//-------------------------------------------------------------------------
216
217
22.3k
static int64_t firstJulianOfYear(int64_t year) {
218
22.3k
    int64_t julianDay = 365LL * (year - 1LL) + ClockMath::floorDivide(8LL * year + 21, 33);
219
22.3k
    if (year > gMinCorrection && getLeapCorrection()->contains(year-1)) {
220
880
        julianDay--;
221
880
    }
222
22.3k
    return julianDay;
223
22.3k
}
224
225
226
// Return JD of start of given month/year
227
6.83k
int64_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool /*useMonth*/, UErrorCode& status) const {
228
6.83k
    if (U_FAILURE(status)) {
229
0
        return 0;
230
0
    }
231
    // If the month is out of range, adjust it into range, and
232
    // modify the extended year value accordingly.
233
6.83k
    if (month < 0 || month > 11) {
234
2.84k
        if (uprv_add32_overflow(eyear, ClockMath::floorDivide(month, 12, &month), &eyear)) {
235
10
            status = U_ILLEGAL_ARGUMENT_ERROR;
236
10
            return 0;
237
10
        }
238
2.84k
    }
239
240
6.82k
    int64_t julianDay = PERSIAN_EPOCH - 1LL + firstJulianOfYear(eyear);
241
242
6.82k
    if (month != 0) {
243
3.75k
        julianDay += kPersianNumDays[month];
244
3.75k
    }
245
246
6.82k
    return julianDay;
247
6.83k
}
248
249
//-------------------------------------------------------------------------
250
// Functions for converting from milliseconds to field values
251
//-------------------------------------------------------------------------
252
253
6.06k
int32_t PersianCalendar::handleGetExtendedYear(UErrorCode& status) {
254
6.06k
    if (U_FAILURE(status)) {
255
0
        return 0;
256
0
    }
257
6.06k
    if (newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR) == UCAL_EXTENDED_YEAR) {
258
4.88k
        return internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1
259
4.88k
    }
260
1.17k
    return internalGet(UCAL_YEAR, 1); // Default to year 1
261
6.06k
}
262
263
/**
264
 * Override Calendar to compute several fields specific to the Persian
265
 * calendar system.  These are:
266
 *
267
 * <ul><li>ERA
268
 * <li>YEAR
269
 * <li>MONTH
270
 * <li>DAY_OF_MONTH
271
 * <li>DAY_OF_YEAR
272
 * <li>EXTENDED_YEAR</ul>
273
 * 
274
 * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
275
 * method is called.
276
 */
277
15.5k
void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) {
278
15.5k
    int64_t daysSinceEpoch = julianDay;
279
15.5k
    daysSinceEpoch -= PERSIAN_EPOCH;
280
281
15.5k
    int64_t year = ClockMath::floorDivideInt64(
282
15.5k
        33LL * daysSinceEpoch + 3LL, 12053LL) + 1LL;
283
15.5k
    if (year > INT32_MAX || year < INT32_MIN) {
284
0
        status = U_ILLEGAL_ARGUMENT_ERROR;
285
0
        return;
286
0
    }
287
288
15.5k
    int64_t farvardin1 = firstJulianOfYear(year);
289
290
15.5k
    int32_t dayOfYear = daysSinceEpoch - farvardin1; // 0-based
291
15.5k
    U_ASSERT(dayOfYear >= 0);
292
15.5k
    U_ASSERT(dayOfYear < 366);
293
294
15.5k
    if (dayOfYear == 365 && year >= gMinCorrection && getLeapCorrection()->contains(year)) {
295
223
        year++;
296
223
        dayOfYear = 0;
297
223
    }
298
15.5k
    int32_t month;
299
15.5k
    if (dayOfYear < 216) { // Compute 0-based month
300
10.1k
        month = dayOfYear / 31;
301
10.1k
    } else {
302
5.39k
        month = (dayOfYear - 6) / 30;
303
5.39k
    }
304
15.5k
    U_ASSERT(month >= 0);
305
15.5k
    U_ASSERT(month < 12);
306
307
15.5k
    ++dayOfYear; // Make it 1-based now
308
15.5k
    int32_t dayOfMonth = dayOfYear - kPersianNumDays[month];
309
15.5k
    U_ASSERT(dayOfMonth > 0);
310
15.5k
    U_ASSERT(dayOfMonth <= 31);
311
312
313
15.5k
    internalSet(UCAL_ERA, 0);
314
15.5k
    internalSet(UCAL_YEAR, year);
315
15.5k
    internalSet(UCAL_EXTENDED_YEAR, year);
316
15.5k
    internalSet(UCAL_MONTH, month);
317
15.5k
    internalSet(UCAL_ORDINAL_MONTH, month);
318
15.5k
    internalSet(UCAL_DAY_OF_MONTH, dayOfMonth);
319
15.5k
    internalSet(UCAL_DAY_OF_YEAR, dayOfYear);
320
15.5k
}    
321
322
IMPL_SYSTEM_DEFAULT_CENTURY(PersianCalendar, "@calendar=persian")
323
324
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PersianCalendar)
325
int32_t
326
0
PersianCalendar::getRelatedYearDifference() const {
327
0
    constexpr int32_t kPersianCalendarRelatedYearDifference = 622;
328
0
    return kPersianCalendarRelatedYearDifference;
329
0
}
330
331
U_NAMESPACE_END
332
333
#endif