Coverage Report

Created: 2026-03-31 06:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/icu/icu4c/source/i18n/chnsecal.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) 2007-2014, International Business Machines Corporation
6
 * and others. All Rights Reserved.
7
 ******************************************************************************
8
 *
9
 * File CHNSECAL.CPP
10
 *
11
 * Modification History:
12
 *
13
 *   Date        Name        Description
14
 *   9/18/2007  ajmacher         ported from java ChineseCalendar
15
 *****************************************************************************
16
 */
17
18
#include "chnsecal.h"
19
20
#include <cstdint>
21
22
#if !UCONFIG_NO_FORMATTING
23
24
#include "umutex.h"
25
#include <float.h>
26
#include "gregoimp.h" // Math
27
#include "astro.h" // CalendarAstronomer and CalendarCache
28
#include "unicode/simpletz.h"
29
#include "uhash.h"
30
#include "ucln_in.h"
31
#include "cstring.h"
32
33
// Debugging
34
#ifdef U_DEBUG_CHNSECAL
35
# include <stdio.h>
36
# include <stdarg.h>
37
static void debug_chnsecal_loc(const char *f, int32_t l)
38
{
39
    fprintf(stderr, "%s:%d: ", f, l);
40
}
41
42
static void debug_chnsecal_msg(const char *pat, ...)
43
{
44
    va_list ap;
45
    va_start(ap, pat);
46
    vfprintf(stderr, pat, ap);
47
    fflush(stderr);
48
}
49
// must use double parens, i.e.:  U_DEBUG_CHNSECAL_MSG(("four is: %d",4));
50
#define U_DEBUG_CHNSECAL_MSG(x) {debug_chnsecal_loc(__FILE__,__LINE__);debug_chnsecal_msg x;}
51
#else
52
#define U_DEBUG_CHNSECAL_MSG(x)
53
#endif
54
55
56
// Lazy Creation & Access synchronized by class CalendarCache with a mutex.
57
static icu::CalendarCache *gWinterSolsticeCache = nullptr;
58
static icu::CalendarCache *gNewYearCache = nullptr;
59
60
static icu::TimeZone *gAstronomerTimeZone = nullptr;
61
static icu::UInitOnce gAstronomerTimeZoneInitOnce {};
62
63
/*
64
 * The start year of the Chinese calendar, 1CE.
65
 */
66
static const int32_t CHINESE_EPOCH_YEAR = 1; // Gregorian year
67
                                             //
68
/**
69
 * The start year of the Chinese calendar for cycle calculation,
70
 * the 61st year of the reign of Huang Di.
71
 * Some sources use the first year of his reign,
72
 * resulting in ERA (cycle) values one greater.
73
 */
74
static const int32_t CYCLE_EPOCH = -2636; // Gregorian year
75
76
/**
77
 * The offset from GMT in milliseconds at which we perform astronomical
78
 * computations.  Some sources use a different historically accurate
79
 * offset of GMT+7:45:40 for years before 1929; we do not do this.
80
 */
81
static const int32_t CHINA_OFFSET = 8 * kOneHour;
82
83
/**
84
 * Value to be added or subtracted from the local days of a new moon to
85
 * get close to the next or prior new moon, but not cross it.  Must be
86
 * >= 1 and < CalendarAstronomer.SYNODIC_MONTH.
87
 */
88
static const int32_t SYNODIC_GAP = 25;
89
90
91
U_CDECL_BEGIN
92
0
static UBool calendar_chinese_cleanup() {
93
0
    if (gWinterSolsticeCache) {
94
0
        delete gWinterSolsticeCache;
95
0
        gWinterSolsticeCache = nullptr;
96
0
    }
97
0
    if (gNewYearCache) {
98
0
        delete gNewYearCache;
99
0
        gNewYearCache = nullptr;
100
0
    }
101
0
    if (gAstronomerTimeZone) {
102
0
        delete gAstronomerTimeZone;
103
0
        gAstronomerTimeZone = nullptr;
104
0
    }
105
0
    gAstronomerTimeZoneInitOnce.reset();
106
0
    return true;
107
0
}
108
U_CDECL_END
109
110
U_NAMESPACE_BEGIN
111
112
113
// Implementation of the ChineseCalendar class
114
115
116
//-------------------------------------------------------------------------
117
// Constructors...
118
//-------------------------------------------------------------------------
119
120
121
namespace {
122
123
const TimeZone* getAstronomerTimeZone();
124
int32_t newMoonNear(const TimeZone*, double, UBool, UErrorCode&);
125
int32_t newYear(const icu::ChineseCalendar::Setting&, int32_t, UErrorCode&);
126
UBool isLeapMonthBetween(const TimeZone*, int32_t, int32_t, UErrorCode&);
127
128
} // namespace
129
130
2.39k
ChineseCalendar* ChineseCalendar::clone() const {
131
2.39k
    return new ChineseCalendar(*this);
132
2.39k
}
133
134
ChineseCalendar::ChineseCalendar(const Locale& aLocale, UErrorCode& success)
135
577
:   Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success),
136
577
    hasLeapMonthBetweenWinterSolstices(false)
137
577
{
138
577
}
139
140
5.20k
ChineseCalendar::ChineseCalendar(const ChineseCalendar& other) : Calendar(other) {
141
5.20k
    hasLeapMonthBetweenWinterSolstices = other.hasLeapMonthBetweenWinterSolstices;
142
5.20k
}
143
144
ChineseCalendar::~ChineseCalendar()
145
5.57k
{
146
5.57k
}
147
148
2
const char *ChineseCalendar::getType() const { 
149
2
    return "chinese";
150
2
}
151
152
namespace { // anonymous
153
154
1
static void U_CALLCONV initAstronomerTimeZone() {
155
1
    gAstronomerTimeZone = new SimpleTimeZone(CHINA_OFFSET, UNICODE_STRING_SIMPLE("CHINA_ZONE") );
156
1
    ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup);
157
1
}
158
159
45.2k
const TimeZone* getAstronomerTimeZone() {
160
45.2k
    umtx_initOnce(gAstronomerTimeZoneInitOnce, &initAstronomerTimeZone);
161
45.2k
    return gAstronomerTimeZone;
162
45.2k
}
163
164
} // namespace anonymous
165
166
//-------------------------------------------------------------------------
167
// Minimum / Maximum access functions
168
//-------------------------------------------------------------------------
169
170
171
static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = {
172
    // Minimum  Greatest     Least    Maximum
173
    //           Minimum   Maximum
174
    {        1,        1,    83333,    83333}, // ERA
175
    {        1,        1,       60,       60}, // YEAR
176
    {        0,        0,       11,       11}, // MONTH
177
    {        1,        1,       50,       55}, // WEEK_OF_YEAR
178
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH
179
    {        1,        1,       29,       30}, // DAY_OF_MONTH
180
    {        1,        1,      353,      385}, // DAY_OF_YEAR
181
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK
182
    {       -1,       -1,        5,        5}, // DAY_OF_WEEK_IN_MONTH
183
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM
184
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR
185
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY
186
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE
187
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND
188
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND
189
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET
190
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET
191
    { -5000000, -5000000,  5000000,  5000000}, // YEAR_WOY
192
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL
193
    { -5000000, -5000000,  5000000,  5000000}, // EXTENDED_YEAR
194
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY
195
    {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY
196
    {        0,        0,        1,        1}, // IS_LEAP_MONTH
197
    {        0,        0,       11,       12}, // ORDINAL_MONTH
198
};
199
200
201
/**
202
* @draft ICU 2.4
203
*/
204
70.8k
int32_t ChineseCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const {
205
70.8k
    return LIMITS[field][limitType];
206
70.8k
}
207
208
209
//----------------------------------------------------------------------
210
// Calendar framework
211
//----------------------------------------------------------------------
212
213
/**
214
 * Implement abstract Calendar method to return the extended year
215
 * defined by the current fields.  This will use either the ERA and
216
 * YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR
217
 * field as the continuous year count, depending on which is newer.
218
 * @stable ICU 2.8
219
 */
220
9.55k
int32_t ChineseCalendar::handleGetExtendedYear(UErrorCode& status) {
221
9.55k
    if (U_FAILURE(status)) {
222
0
        return 0;
223
0
    }
224
225
9.55k
    int32_t year;
226
    // if UCAL_EXTENDED_YEAR is not older than UCAL_ERA nor UCAL_YEAR
227
9.55k
    if (newerField(UCAL_EXTENDED_YEAR, newerField(UCAL_ERA, UCAL_YEAR)) ==
228
9.55k
        UCAL_EXTENDED_YEAR) {
229
5.39k
        year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1
230
5.39k
    } else {
231
        // adjust to the instance specific epoch
232
4.16k
        int32_t cycle = internalGet(UCAL_ERA, 1);
233
4.16k
        year = internalGet(UCAL_YEAR, 1);
234
        // Handle int32 overflow calculation for
235
        // year = year + (cycle-1) * 60 + CYCLE_EPOCH - CHINESE_EPOCH_YEAR
236
4.16k
        if (uprv_add32_overflow(cycle, -1, &cycle) || // 0-based cycle
237
4.14k
            uprv_mul32_overflow(cycle, 60, &cycle) ||
238
4.12k
            uprv_add32_overflow(year, cycle, &year) ||
239
4.10k
            uprv_add32_overflow(year, CYCLE_EPOCH-CHINESE_EPOCH_YEAR,
240
4.10k
                                &year)) {
241
80
            status = U_ILLEGAL_ARGUMENT_ERROR;
242
80
            return 0;
243
80
        }
244
4.16k
    }
245
9.47k
    return year;
246
9.55k
}
247
248
/**
249
 * Override Calendar method to return the number of days in the given
250
 * extended year and month.
251
 *
252
 * <p>Note: This method also reads the IS_LEAP_MONTH field to determine
253
 * whether or not the given month is a leap month.
254
 * @stable ICU 2.8
255
 */
256
152
int32_t ChineseCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month, UErrorCode& status) const {
257
152
    bool isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) == 1;
258
152
    return handleGetMonthLengthWithLeap(extendedYear, month, isLeapMonth, status);
259
152
}
260
261
4.05k
int32_t ChineseCalendar::handleGetMonthLengthWithLeap(int32_t extendedYear, int32_t month, bool leap, UErrorCode& status) const {
262
4.05k
    const Setting setting = getSetting(status);
263
4.05k
    if (U_FAILURE(status)) {
264
125
        return 0;
265
125
    }
266
3.93k
    int32_t thisStart = handleComputeMonthStartWithLeap(extendedYear, month, leap, status);
267
3.93k
    if (U_FAILURE(status)) {
268
18
        return 0;
269
18
    }
270
3.91k
    thisStart = thisStart -
271
3.91k
        kEpochStartAsJulianDay + 1; // Julian day -> local days
272
3.91k
    int32_t nextStart = newMoonNear(setting.zoneAstroCalc, thisStart + SYNODIC_GAP, true, status);
273
3.91k
    return nextStart - thisStart;
274
3.93k
}
275
276
/**
277
 * Field resolution table that incorporates IS_LEAP_MONTH.
278
 */
279
const UFieldResolutionTable ChineseCalendar::CHINESE_DATE_PRECEDENCE[] =
280
{
281
    {
282
        { UCAL_DAY_OF_MONTH, kResolveSTOP },
283
        { UCAL_WEEK_OF_YEAR, UCAL_DAY_OF_WEEK, kResolveSTOP },
284
        { UCAL_WEEK_OF_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP },
285
        { UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP },
286
        { UCAL_WEEK_OF_YEAR, UCAL_DOW_LOCAL, kResolveSTOP },
287
        { UCAL_WEEK_OF_MONTH, UCAL_DOW_LOCAL, kResolveSTOP },
288
        { UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DOW_LOCAL, kResolveSTOP },
289
        { UCAL_DAY_OF_YEAR, kResolveSTOP },
290
        { kResolveRemap | UCAL_DAY_OF_MONTH, UCAL_IS_LEAP_MONTH, kResolveSTOP },
291
        { kResolveSTOP }
292
    },
293
    {
294
        { UCAL_WEEK_OF_YEAR, kResolveSTOP },
295
        { UCAL_WEEK_OF_MONTH, kResolveSTOP },
296
        { UCAL_DAY_OF_WEEK_IN_MONTH, kResolveSTOP },
297
        { kResolveRemap | UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP },
298
        { kResolveRemap | UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DOW_LOCAL, kResolveSTOP },
299
        { kResolveSTOP }
300
    },
301
    {{kResolveSTOP}}
302
};
303
304
/**
305
 * Override Calendar to add IS_LEAP_MONTH to the field resolution
306
 * table.
307
 * @stable ICU 2.8
308
 */
309
9.62k
const UFieldResolutionTable* ChineseCalendar::getFieldResolutionTable() const {
310
9.62k
    return CHINESE_DATE_PRECEDENCE;
311
9.62k
}
312
313
namespace {
314
315
struct MonthInfo {
316
  int32_t month;
317
  int32_t ordinalMonth;
318
  int32_t thisMoon;
319
  bool isLeapMonth;
320
  bool hasLeapMonthBetweenWinterSolstices;
321
};
322
struct MonthInfo computeMonthInfo(
323
    const icu::ChineseCalendar::Setting& setting,
324
    int32_t gyear, int32_t days, UErrorCode& status);
325
326
}  // namespace
327
328
/**
329
 * Return the Julian day number of day before the first day of the
330
 * given month in the given extended year.
331
 * 
332
 * <p>Note: This method reads the IS_LEAP_MONTH field to determine
333
 * whether the given month is a leap month.
334
 * @param eyear the extended year
335
 * @param month the zero-based month.  The month is also determined
336
 * by reading the IS_LEAP_MONTH field.
337
 * @return the Julian day number of the day before the first
338
 * day of the given month and year
339
 * @stable ICU 2.8
340
 */
341
64.9k
int64_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool useMonth, UErrorCode& status) const {
342
64.9k
    bool isLeapMonth = false;
343
64.9k
    if (useMonth) {
344
9.21k
        isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) != 0;
345
9.21k
    }
346
64.9k
    return handleComputeMonthStartWithLeap(eyear, month, isLeapMonth, status);
347
64.9k
}
348
349
68.9k
int64_t ChineseCalendar::handleComputeMonthStartWithLeap(int32_t eyear, int32_t month, bool isLeapMonth, UErrorCode& status) const {
350
68.9k
    if (U_FAILURE(status)) {
351
135
       return 0;
352
135
    }
353
    // If the month is out of range, adjust it into range, and
354
    // modify the extended year value accordingly.
355
68.7k
    if (month < 0 || month > 11) {
356
405
        if (uprv_add32_overflow(eyear, ClockMath::floorDivide(month, 12, &month), &eyear)) {
357
10
            status = U_ILLEGAL_ARGUMENT_ERROR;
358
10
            return 0;
359
10
        }
360
405
    }
361
362
68.7k
    const Setting setting = getSetting(status);
363
68.7k
    if (U_FAILURE(status)) {
364
0
       return 0;
365
0
    }
366
68.7k
    int32_t gyear = eyear;
367
68.7k
    int32_t theNewYear = newYear(setting, gyear, status);
368
68.7k
    int32_t newMoon = newMoonNear(setting.zoneAstroCalc, theNewYear + month * 29, true, status);
369
68.7k
    if (U_FAILURE(status)) {
370
137
       return 0;
371
137
    }
372
373
68.6k
    int32_t newMonthYear = Grego::dayToYear(newMoon, status);
374
375
68.6k
    struct MonthInfo monthInfo = computeMonthInfo(setting, newMonthYear, newMoon, status);
376
68.6k
    if (U_FAILURE(status)) {
377
231
       return 0;
378
231
    }
379
68.4k
    if (month != monthInfo.month-1 || isLeapMonth != monthInfo.isLeapMonth) {
380
1.46k
        newMoon = newMoonNear(setting.zoneAstroCalc, newMoon + SYNODIC_GAP, true, status);
381
1.46k
        if (U_FAILURE(status)) {
382
0
           return 0;
383
0
        }
384
1.46k
    }
385
68.4k
    int32_t julianDay;
386
68.4k
    if (uprv_add32_overflow(newMoon-1, kEpochStartAsJulianDay, &julianDay)) {
387
18
        status = U_ILLEGAL_ARGUMENT_ERROR;
388
18
        return 0;
389
18
    }
390
391
68.3k
    return julianDay;
392
68.4k
}
393
394
395
/**
396
 * Override Calendar to handle leap months properly.
397
 * @stable ICU 2.8
398
 */
399
12.1k
void ChineseCalendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status) {
400
12.1k
    switch (field) {
401
1.69k
    case UCAL_MONTH:
402
3.14k
    case UCAL_ORDINAL_MONTH:
403
3.14k
        if (amount != 0) {
404
3.11k
            int32_t dom = get(UCAL_DAY_OF_MONTH, status);
405
3.11k
            if (U_FAILURE(status)) break;
406
3.01k
            int32_t day = get(UCAL_JULIAN_DAY, status) - kEpochStartAsJulianDay; // Get local day
407
3.01k
            if (U_FAILURE(status)) break;
408
3.01k
            int32_t moon = day - dom + 1; // New moon 
409
3.01k
            offsetMonth(moon, dom, amount, status);
410
3.01k
        }
411
3.04k
        break;
412
9.01k
    default:
413
9.01k
        Calendar::add(field, amount, status);
414
9.01k
        break;
415
12.1k
    }
416
12.1k
}
417
418
/**
419
 * Override Calendar to handle leap months properly.
420
 * @stable ICU 2.8
421
 */
422
0
void ChineseCalendar::add(EDateFields field, int32_t amount, UErrorCode& status) {
423
0
    add(static_cast<UCalendarDateFields>(field), amount, status);
424
0
}
425
426
namespace {
427
428
struct RollMonthInfo {
429
    int32_t month;
430
    int32_t newMoon;
431
    int32_t thisMoon;
432
};
433
434
struct RollMonthInfo rollMonth(const TimeZone* timeZone, int32_t amount, int32_t day, int32_t month, int32_t dayOfMonth,
435
                               bool isLeapMonth, bool hasLeapMonthBetweenWinterSolstices,
436
352
                               UErrorCode& status) {
437
352
    struct RollMonthInfo output = {0, 0, 0};
438
352
    if (U_FAILURE(status)) {
439
0
        return output;
440
0
    }
441
442
352
    output.thisMoon = day - dayOfMonth + 1; // New moon (start of this month)
443
444
    // Note throughout the following:  Months 12 and 1 are never
445
    // followed by a leap month (D&R p. 185).
446
447
    // Compute the adjusted month number m.  This is zero-based
448
    // value from 0..11 in a non-leap year, and from 0..12 in a
449
    // leap year.
450
352
    if (hasLeapMonthBetweenWinterSolstices) { // (member variable)
451
112
        if (isLeapMonth) {
452
22
            ++month;
453
90
        } else {
454
            // Check for a prior leap month.  (In the
455
            // following, month 0 is the first month of the
456
            // year.)  Month 0 is never followed by a leap
457
            // month, and we know month m is not a leap month.
458
            // moon1 will be the start of month 0 if there is
459
            // no leap month between month 0 and month m;
460
            // otherwise it will be the start of month 1.
461
90
            int prevMoon = output.thisMoon -
462
90
                static_cast<int>(CalendarAstronomer::SYNODIC_MONTH * (month - 0.5));
463
90
            prevMoon = newMoonNear(timeZone, prevMoon, true, status);
464
90
            if (U_FAILURE(status)) {
465
0
               return output;
466
0
            }
467
90
            if (isLeapMonthBetween(timeZone, prevMoon, output.thisMoon, status)) {
468
36
                ++month;
469
36
            }
470
90
            if (U_FAILURE(status)) {
471
0
               return output;
472
0
            }
473
90
        }
474
112
    }
475
    // Now do the standard roll computation on month, with the
476
    // allowed range of 0..n-1, where n is 12 or 13.
477
352
    int32_t numberOfMonths = hasLeapMonthBetweenWinterSolstices ? 13 : 12; // Months in this year
478
352
    if (uprv_add32_overflow(amount, month, &amount)) {
479
22
        status = U_ILLEGAL_ARGUMENT_ERROR;
480
22
        return output;
481
22
    }
482
330
    output.newMoon = amount % numberOfMonths;
483
330
    if (output.newMoon < 0) {
484
114
        output.newMoon += numberOfMonths;
485
114
    }
486
330
    output.month = month;
487
330
    return output;
488
352
}
489
490
}  // namespace
491
492
/**
493
 * Override Calendar to handle leap months properly.
494
 * @stable ICU 2.8
495
 */
496
970
void ChineseCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& status) {
497
970
    switch (field) {
498
356
    case UCAL_MONTH:
499
456
    case UCAL_ORDINAL_MONTH:
500
456
        if (amount != 0) {
501
424
            const Setting setting = getSetting(status);
502
424
            int32_t day = get(UCAL_JULIAN_DAY, status) - kEpochStartAsJulianDay; // Get local day
503
424
            int32_t month = get(UCAL_MONTH, status); // 0-based month
504
424
            int32_t dayOfMonth = get(UCAL_DAY_OF_MONTH, status);
505
424
            bool isLeapMonth = get(UCAL_IS_LEAP_MONTH, status) == 1;
506
424
            if (U_FAILURE(status)) break;
507
352
            struct RollMonthInfo r = rollMonth(
508
352
                setting.zoneAstroCalc, amount, day, month, dayOfMonth, isLeapMonth,
509
352
                hasLeapMonthBetweenWinterSolstices, status);
510
352
            if (U_FAILURE(status)) break;
511
330
            if (r.newMoon != r.month) {
512
286
                offsetMonth(r.thisMoon, dayOfMonth, r.newMoon - r.month, status);
513
286
            }
514
330
        }
515
362
        break;
516
514
    default:
517
514
        Calendar::roll(field, amount, status);
518
514
        break;
519
970
    }
520
970
}
521
522
0
void ChineseCalendar::roll(EDateFields field, int32_t amount, UErrorCode& status) {
523
0
    roll(static_cast<UCalendarDateFields>(field), amount, status);
524
0
}
525
526
527
//------------------------------------------------------------------
528
// Support methods and constants
529
//------------------------------------------------------------------
530
531
namespace {
532
/**
533
 * Convert local days to UTC epoch milliseconds.
534
 * This is not an accurate conversion in that getTimezoneOffset
535
 * takes the milliseconds in GMT (not local time). In theory, more
536
 * accurate algorithm can be implemented but practically we do not need
537
 * to go through that complication as long as the historical timezone
538
 * changes did not happen around the 'tricky' new moon (new moon around
539
 * midnight).
540
 *
541
 * @param timeZone time zone for the Astro calculation.
542
 * @param days days after January 1, 1970 0:00 in the astronomical base zone
543
 * @return milliseconds after January 1, 1970 0:00 GMT
544
 */
545
839k
double daysToMillis(const TimeZone* timeZone, double days, UErrorCode& status) {
546
839k
    if (U_FAILURE(status)) {
547
0
        return 0;
548
0
    }
549
839k
    double millis = days * kOneDay;
550
839k
    if (timeZone != nullptr) {
551
839k
        int32_t rawOffset, dstOffset;
552
839k
        timeZone->getOffset(millis, false, rawOffset, dstOffset, status);
553
839k
        if (U_FAILURE(status)) {
554
97
            return 0;
555
97
        }
556
839k
        return millis - static_cast<double>(rawOffset + dstOffset);
557
839k
    }
558
0
    return millis - static_cast<double>(CHINA_OFFSET);
559
839k
}
560
561
/**
562
 * Convert UTC epoch milliseconds to local days.
563
 * @param timeZone time zone for the Astro calculation.
564
 * @param millis milliseconds after January 1, 1970 0:00 GMT
565
 * @return days after January 1, 1970 0:00 in the astronomical base zone
566
 */
567
592k
double millisToDays(const TimeZone* timeZone, double millis, UErrorCode& status) {
568
592k
    if (U_FAILURE(status)) {
569
0
        return 0;
570
0
    }
571
592k
    if (timeZone != nullptr) {
572
592k
        int32_t rawOffset, dstOffset;
573
592k
        timeZone->getOffset(millis, false, rawOffset, dstOffset, status);
574
592k
        if (U_FAILURE(status)) {
575
20
            return 0;
576
20
        }
577
592k
        return ClockMath::floorDivide(millis + static_cast<double>(rawOffset + dstOffset), kOneDay);
578
592k
    }
579
0
    return ClockMath::floorDivide(millis + static_cast<double>(CHINA_OFFSET), kOneDay);
580
592k
}
581
582
//------------------------------------------------------------------
583
// Astronomical computations
584
//------------------------------------------------------------------
585
586
587
/**
588
 * Return the major solar term on or after December 15 of the given
589
 * Gregorian year, that is, the winter solstice of the given year.
590
 * Computations are relative to Asia/Shanghai time zone.
591
 * @param setting setting (time zone and caches) for the Astro calculation.
592
 * @param gyear a Gregorian year
593
 * @return days after January 1, 1970 0:00 Asia/Shanghai of the
594
 * winter solstice of the given year
595
 */
596
int32_t winterSolstice(const icu::ChineseCalendar::Setting& setting,
597
201k
                       int32_t gyear, UErrorCode& status) {
598
201k
    if (U_FAILURE(status)) {
599
139
        return 0;
600
139
    }
601
201k
    const TimeZone* timeZone = setting.zoneAstroCalc;
602
603
201k
    int32_t cacheValue = CalendarCache::get(setting.winterSolsticeCache, gyear, status);
604
201k
    if (U_FAILURE(status)) {
605
0
        return 0;
606
0
    }
607
608
201k
    if (cacheValue == 0) {
609
        // In books December 15 is used, but it fails for some years
610
        // using our algorithms, e.g.: 1298 1391 1492 1553 1560.  That
611
        // is, winterSolstice(1298) starts search at Dec 14 08:00:00
612
        // PST 1298 with a final result of Dec 14 10:31:59 PST 1299.
613
6.43k
        double ms = daysToMillis(timeZone, Grego::fieldsToDay(gyear, UCAL_DECEMBER, 1), status);
614
6.43k
        if (U_FAILURE(status)) {
615
79
            return 0;
616
79
        }
617
618
        // Winter solstice is 270 degrees solar longitude aka Dongzhi
619
6.35k
        double days = millisToDays(timeZone,
620
6.35k
                                   CalendarAstronomer(ms)
621
6.35k
                                       .getSunTime(CalendarAstronomer::WINTER_SOLSTICE(), true),
622
6.35k
                                   status);
623
6.35k
        if (U_FAILURE(status)) {
624
0
            return 0;
625
0
        }
626
6.35k
        if (days < INT32_MIN || days > INT32_MAX) {
627
62
            status = U_ILLEGAL_ARGUMENT_ERROR;
628
62
            return 0;
629
62
        }
630
6.29k
        cacheValue = static_cast<int32_t>(days);
631
6.29k
        CalendarCache::put(setting.winterSolsticeCache, gyear, cacheValue, status);
632
6.29k
    }
633
201k
    if(U_FAILURE(status)) {
634
0
        cacheValue = 0;
635
0
    }
636
201k
    return cacheValue;
637
201k
}
638
639
/**
640
 * Return the closest new moon to the given date, searching either
641
 * forward or backward in time.
642
 * @param timeZone time zone for the Astro calculation.
643
 * @param days days after January 1, 1970 0:00 Asia/Shanghai
644
 * @param after if true, search for a new moon on or after the given
645
 * date; otherwise, search for a new moon before it
646
 * @param status
647
 * @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest
648
 * new moon after or before <code>days</code>
649
 */
650
586k
int32_t newMoonNear(const TimeZone* timeZone, double days, UBool after, UErrorCode& status) {
651
586k
    if (U_FAILURE(status)) {
652
554
        return 0;
653
554
    }
654
585k
    double ms = daysToMillis(timeZone, days, status);
655
585k
    if (U_FAILURE(status)) {
656
18
        return 0;
657
18
    }
658
585k
    return static_cast<int32_t>(millisToDays(
659
585k
        timeZone,
660
585k
        CalendarAstronomer(ms)
661
585k
              .getMoonTime(CalendarAstronomer::NEW_MOON, after),
662
585k
              status));
663
585k
}
664
665
/**
666
 * Return the nearest integer number of synodic months between
667
 * two dates.
668
 * @param day1 days after January 1, 1970 0:00 Asia/Shanghai
669
 * @param day2 days after January 1, 1970 0:00 Asia/Shanghai
670
 * @return the nearest integer number of months between day1 and day2
671
 */
672
292k
int32_t synodicMonthsBetween(int32_t day1, int32_t day2) {
673
292k
    double roundme = ((day2 - day1) / CalendarAstronomer::SYNODIC_MONTH);
674
292k
    return static_cast<int32_t>(roundme + (roundme >= 0 ? .5 : -.5));
675
292k
}
676
677
/**
678
 * Return the major solar term on or before a given date.  This
679
 * will be an integer from 1..12, with 1 corresponding to 330 degrees,
680
 * 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees.
681
 * @param timeZone time zone for the Astro calculation.
682
 * @param days days after January 1, 1970 0:00 Asia/Shanghai
683
 */
684
247k
int32_t majorSolarTerm(const TimeZone* timeZone, int32_t days, UErrorCode& status) {
685
247k
    if (U_FAILURE(status)) {
686
0
        return 0;
687
0
    }
688
    // Compute (floor(solarLongitude / (pi/6)) + 2) % 12
689
247k
    double ms = daysToMillis(timeZone, days, status);
690
247k
    if (U_FAILURE(status)) {
691
0
        return 0;
692
0
    }
693
247k
    int32_t term = ((static_cast<int32_t>(6 * CalendarAstronomer(ms)
694
247k
                                .getSunLongitude() / CalendarAstronomer::PI)) + 2 ) % 12;
695
247k
    if (U_FAILURE(status)) {
696
0
        return 0;
697
0
    }
698
247k
    if (term < 1) {
699
85.7k
        term += 12;
700
85.7k
    }
701
247k
    return term;
702
247k
}
703
704
/**
705
 * Return true if the given month lacks a major solar term.
706
 * @param timeZone time zone for the Astro calculation.
707
 * @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new
708
 * moon
709
 */
710
123k
UBool hasNoMajorSolarTerm(const TimeZone* timeZone, int32_t newMoon, UErrorCode& status) {
711
123k
    if (U_FAILURE(status)) {
712
0
        return false;
713
0
    }
714
123k
    int32_t term1 = majorSolarTerm(timeZone, newMoon, status);
715
123k
    int32_t term2 = majorSolarTerm(
716
123k
        timeZone, newMoonNear(timeZone, newMoon + SYNODIC_GAP, true, status), status);
717
123k
    if (U_FAILURE(status)) {
718
0
        return false;
719
0
    }
720
123k
    return term1 == term2;
721
123k
}
722
723
724
//------------------------------------------------------------------
725
// Time to fields
726
//------------------------------------------------------------------
727
728
/**
729
 * Return true if there is a leap month on or after month newMoon1 and
730
 * at or before month newMoon2.
731
 * @param timeZone time zone for the Astro calculation.
732
 * @param newMoon1 days after January 1, 1970 0:00 astronomical base zone
733
 * of a new moon
734
 * @param newMoon2 days after January 1, 1970 0:00 astronomical base zone
735
 * of a new moon
736
 */
737
33.8k
UBool isLeapMonthBetween(const TimeZone* timeZone, int32_t newMoon1, int32_t newMoon2, UErrorCode& status) {
738
33.8k
    if (U_FAILURE(status)) {
739
0
        return false;
740
0
    }
741
742
#ifdef U_DEBUG_CHNSECAL
743
    // This is only needed to debug the timeOfAngle divergence bug.
744
    // Remove this later. Liu 11/9/00
745
    if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) {
746
        U_DEBUG_CHNSECAL_MSG((
747
            "isLeapMonthBetween(%d, %d): Invalid parameters", newMoon1, newMoon2
748
            ));
749
    }
750
#endif
751
752
116k
    while (newMoon2 >= newMoon1) {
753
88.0k
        if (hasNoMajorSolarTerm(timeZone, newMoon2, status)) {
754
5.12k
            return true;
755
5.12k
        }
756
82.9k
        newMoon2 = newMoonNear(timeZone, newMoon2 - SYNODIC_GAP, false, status);
757
82.9k
        if (U_FAILURE(status)) {
758
0
            return false;
759
0
        }
760
82.9k
    }
761
28.6k
    return false;
762
33.8k
}
763
764
765
/**
766
 * Compute the information about the year.
767
 * @param setting setting (time zone and caches) for the Astro calculation.
768
 * @param gyear the Gregorian year of the given date
769
 * @param days days after January 1, 1970 0:00 astronomical base zone
770
 * of the date to compute fields for
771
 * @return The MonthInfo result.
772
 */
773
struct MonthInfo computeMonthInfo(
774
    const icu::ChineseCalendar::Setting& setting,
775
96.4k
    int32_t gyear, int32_t days, UErrorCode& status) {
776
96.4k
    struct MonthInfo output = {0, 0, 0, false, false};
777
96.4k
    if (U_FAILURE(status)) {
778
19
        return output;
779
19
    }
780
    // Find the winter solstices before and after the target date.
781
    // These define the boundaries of this Chinese year, specifically,
782
    // the position of month 11, which always contains the solstice.
783
    // We want solsticeBefore <= date < solsticeAfter.
784
96.4k
    int32_t solsticeBefore;
785
96.4k
    int32_t solsticeAfter = winterSolstice(setting, gyear, status);
786
96.4k
    if (U_FAILURE(status)) {
787
0
        return output;
788
0
    }
789
96.4k
    if (days < solsticeAfter) {
790
95.3k
        int32_t gprevious_year;
791
95.3k
        if (uprv_add32_overflow(gyear, -1, &gprevious_year)) {
792
0
            status = U_ILLEGAL_ARGUMENT_ERROR;
793
0
            return output;
794
0
        }
795
95.3k
        solsticeBefore = winterSolstice(setting, gprevious_year, status);
796
95.3k
    } else {
797
1.09k
        solsticeBefore = solsticeAfter;
798
1.09k
        int32_t gnext_year;
799
1.09k
        if (uprv_add32_overflow(gyear, 1, &gnext_year)) {
800
0
            status = U_ILLEGAL_ARGUMENT_ERROR;
801
0
            return output;
802
0
        }
803
1.09k
        solsticeAfter = winterSolstice(setting, gnext_year, status);
804
1.09k
    }
805
96.4k
    if (!(solsticeBefore <= days && days < solsticeAfter)) {
806
409
        status = U_ILLEGAL_ARGUMENT_ERROR;
807
409
    }
808
96.4k
    if (U_FAILURE(status)) {
809
409
        return output;
810
409
    }
811
812
96.0k
    const TimeZone* timeZone = setting.zoneAstroCalc;
813
    // Find the start of the month after month 11.  This will be either
814
    // the prior month 12 or leap month 11 (very rare).  Also find the
815
    // start of the following month 11.
816
96.0k
    int32_t firstMoon = newMoonNear(timeZone, solsticeBefore + 1, true, status);
817
96.0k
    int32_t lastMoon = newMoonNear(timeZone, solsticeAfter + 1, false, status);
818
96.0k
    if (U_FAILURE(status)) {
819
0
        return output;
820
0
    }
821
96.0k
    output.thisMoon = newMoonNear(timeZone, days + 1, false, status); // Start of this month
822
96.0k
    if (U_FAILURE(status)) {
823
0
        return output;
824
0
    }
825
96.0k
    output.hasLeapMonthBetweenWinterSolstices = synodicMonthsBetween(firstMoon, lastMoon) == 12;
826
827
96.0k
    output.month = synodicMonthsBetween(firstMoon, output.thisMoon);
828
96.0k
    int32_t theNewYear = newYear(setting, gyear, status);
829
96.0k
    if (U_FAILURE(status)) {
830
10
        return output;
831
10
    }
832
96.0k
    if (days < theNewYear) {
833
3.20k
        int32_t gprevious_year;
834
3.20k
        if (uprv_add32_overflow(gyear, -1, &gprevious_year)) {
835
0
            status = U_ILLEGAL_ARGUMENT_ERROR;
836
0
            return output;
837
0
        }
838
3.20k
        theNewYear = newYear(setting, gprevious_year, status);
839
3.20k
        if (U_FAILURE(status)) {
840
10
            return output;
841
10
        }
842
3.20k
    }
843
95.9k
    if (output.hasLeapMonthBetweenWinterSolstices &&
844
32.7k
        isLeapMonthBetween(timeZone, firstMoon, output.thisMoon, status)) {
845
4.94k
        output.month--;
846
4.94k
    }
847
95.9k
    if (U_FAILURE(status)) {
848
0
        return output;
849
0
    }
850
95.9k
    if (output.month < 1) {
851
4.07k
        output.month += 12;
852
4.07k
    }
853
95.9k
    output.ordinalMonth = synodicMonthsBetween(theNewYear, output.thisMoon);
854
95.9k
    if (output.ordinalMonth < 0) {
855
293
        output.ordinalMonth += 12;
856
293
    }
857
95.9k
    output.isLeapMonth = output.hasLeapMonthBetweenWinterSolstices &&
858
32.7k
        hasNoMajorSolarTerm(timeZone, output.thisMoon, status) &&
859
1.01k
        !isLeapMonthBetween(timeZone, firstMoon,
860
1.01k
                            newMoonNear(timeZone, output.thisMoon - SYNODIC_GAP, false, status),
861
1.01k
                            status);
862
95.9k
    if (U_FAILURE(status)) {
863
0
        return output;
864
0
    }
865
95.9k
    return output;
866
95.9k
}
867
868
}  // namespace
869
870
/**
871
 * Override Calendar to compute several fields specific to the Chinese
872
 * calendar system.  These are:
873
 *
874
 * <ul><li>ERA
875
 * <li>YEAR
876
 * <li>MONTH
877
 * <li>DAY_OF_MONTH
878
 * <li>DAY_OF_YEAR
879
 * <li>EXTENDED_YEAR</ul>
880
 * 
881
 * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
882
 * method is called.  The getGregorianXxx() methods return Gregorian
883
 * calendar equivalents for the given Julian day.
884
 *
885
 * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH.
886
 * @stable ICU 2.8
887
 */
888
27.8k
void ChineseCalendar::handleComputeFields(int32_t julianDay, UErrorCode & status) {
889
27.8k
    if (U_FAILURE(status)) {
890
21
        return;
891
21
    }
892
27.8k
    int32_t days;
893
27.8k
    if (uprv_add32_overflow(julianDay, -kEpochStartAsJulianDay, &days)) {
894
0
        status = U_ILLEGAL_ARGUMENT_ERROR;
895
0
        return;
896
0
    }
897
27.8k
    int32_t gyear = getGregorianYear();
898
27.8k
    int32_t gmonth = getGregorianMonth();
899
900
27.8k
    const Setting setting = getSetting(status);
901
27.8k
    if (U_FAILURE(status)) {
902
0
       return;
903
0
    }
904
27.8k
    struct MonthInfo monthInfo = computeMonthInfo(setting, gyear, days, status);
905
27.8k
    if (U_FAILURE(status)) {
906
217
       return;
907
217
    }
908
27.5k
    hasLeapMonthBetweenWinterSolstices = monthInfo.hasLeapMonthBetweenWinterSolstices;
909
910
    // Extended year and cycle year is based on the epoch year
911
27.5k
    int32_t eyear;
912
27.5k
    int32_t cycle_year;
913
27.5k
    if (uprv_add32_overflow(gyear, -CHINESE_EPOCH_YEAR, &eyear) ||
914
27.5k
        uprv_add32_overflow(gyear, -CYCLE_EPOCH, &cycle_year)) {
915
0
        status = U_ILLEGAL_ARGUMENT_ERROR;
916
0
        return;
917
0
    }
918
27.5k
    if (monthInfo.month < 11 ||
919
25.0k
        gmonth >= UCAL_JULY) {
920
        // forward to next year
921
25.0k
        if (uprv_add32_overflow(eyear, 1, &eyear) ||
922
25.0k
            uprv_add32_overflow(cycle_year, 1, &cycle_year)) {
923
0
            status = U_ILLEGAL_ARGUMENT_ERROR;
924
0
            return;
925
0
        }
926
25.0k
    }
927
27.5k
    int32_t dayOfMonth = days - monthInfo.thisMoon + 1;
928
929
    // 0->0,60  1->1,1  60->1,60  61->2,1  etc.
930
27.5k
    int32_t yearOfCycle;
931
27.5k
    int32_t cycle = ClockMath::floorDivide(cycle_year - 1, 60, &yearOfCycle);
932
933
    // Days will be before the first new year we compute if this
934
    // date is in month 11, leap 11, 12.  There is never a leap 12.
935
    // New year computations are cached so this should be cheap in
936
    // the long run.
937
27.5k
    int32_t theNewYear = newYear(setting, gyear, status);
938
27.5k
    if (U_FAILURE(status)) {
939
0
       return;
940
0
    }
941
27.5k
    if (days < theNewYear) {
942
2.96k
        int32_t gprevious_year;
943
2.96k
        if (uprv_add32_overflow(gyear, -1, &gprevious_year)) {
944
0
            status = U_ILLEGAL_ARGUMENT_ERROR;
945
0
            return;
946
0
        }
947
2.96k
        theNewYear = newYear(setting, gprevious_year, status);
948
2.96k
    }
949
27.5k
    if (U_FAILURE(status)) {
950
0
       return;
951
0
    }
952
27.5k
    cycle++;
953
27.5k
    yearOfCycle++;
954
27.5k
    int32_t dayOfYear = days - theNewYear + 1;
955
956
27.5k
    int32_t minYear = this->handleGetLimit(UCAL_EXTENDED_YEAR, UCAL_LIMIT_MINIMUM);
957
27.5k
    if (eyear < minYear) {
958
501
        if (!isLenient()) {
959
0
            status = U_ILLEGAL_ARGUMENT_ERROR;
960
0
            return;
961
0
        }
962
501
        eyear = minYear;
963
501
    }
964
27.5k
    int32_t maxYear = this->handleGetLimit(UCAL_EXTENDED_YEAR, UCAL_LIMIT_MAXIMUM);
965
27.5k
    if (maxYear < eyear) {
966
1.51k
        if (!isLenient()) {
967
0
            status = U_ILLEGAL_ARGUMENT_ERROR;
968
0
            return;
969
0
        }
970
1.51k
        eyear = maxYear;
971
1.51k
    }
972
973
27.5k
    internalSet(UCAL_MONTH, monthInfo.month-1); // Convert from 1-based to 0-based
974
27.5k
    internalSet(UCAL_ORDINAL_MONTH, monthInfo.ordinalMonth); // Convert from 1-based to 0-based
975
27.5k
    internalSet(UCAL_IS_LEAP_MONTH, monthInfo.isLeapMonth?1:0);
976
977
27.5k
    internalSet(UCAL_EXTENDED_YEAR, eyear);
978
27.5k
    internalSet(UCAL_ERA, cycle);
979
27.5k
    internalSet(UCAL_YEAR, yearOfCycle);
980
27.5k
    internalSet(UCAL_DAY_OF_MONTH, dayOfMonth);
981
27.5k
    internalSet(UCAL_DAY_OF_YEAR, dayOfYear);
982
27.5k
}
983
984
//------------------------------------------------------------------
985
// Fields to time
986
//------------------------------------------------------------------
987
988
namespace {
989
990
/**
991
 * Return the Chinese new year of the given Gregorian year.
992
 * @param setting setting (time zone and caches) for the Astro calculation.
993
 * @param gyear a Gregorian year
994
 * @return days after January 1, 1970 0:00 astronomical base zone of the
995
 * Chinese new year of the given year (this will be a new moon)
996
 */
997
int32_t newYear(const icu::ChineseCalendar::Setting& setting,
998
198k
                int32_t gyear, UErrorCode& status) {
999
198k
    if (U_FAILURE(status)) {
1000
0
        return 0;
1001
0
    }
1002
198k
    const TimeZone* timeZone = setting.zoneAstroCalc;
1003
198k
    int32_t cacheValue = CalendarCache::get(setting.newYearCache, gyear, status);
1004
198k
    if (U_FAILURE(status)) {
1005
0
        return 0;
1006
0
    }
1007
1008
198k
    if (cacheValue == 0) {
1009
1010
4.37k
        int32_t gprevious_year;
1011
4.37k
        if (uprv_add32_overflow(gyear, -1, &gprevious_year)) {
1012
18
            status = U_ILLEGAL_ARGUMENT_ERROR;
1013
18
            return 0;
1014
18
        }
1015
4.35k
        int32_t solsticeBefore= winterSolstice(setting, gprevious_year, status);
1016
4.35k
        int32_t solsticeAfter = winterSolstice(setting, gyear, status);
1017
4.35k
        int32_t newMoon1 = newMoonNear(timeZone, solsticeBefore + 1, true, status);
1018
4.35k
        int32_t newMoon2 = newMoonNear(timeZone, newMoon1 + SYNODIC_GAP, true, status);
1019
4.35k
        int32_t newMoon11 = newMoonNear(timeZone, solsticeAfter + 1, false, status);
1020
4.35k
        if (U_FAILURE(status)) {
1021
139
            return 0;
1022
139
        }
1023
1024
4.21k
        if (synodicMonthsBetween(newMoon1, newMoon11) == 12 &&
1025
1.52k
            (hasNoMajorSolarTerm(timeZone, newMoon1, status) ||
1026
1.49k
             hasNoMajorSolarTerm(timeZone, newMoon2, status))) {
1027
45
            cacheValue = newMoonNear(timeZone, newMoon2 + SYNODIC_GAP, true, status);
1028
4.17k
        } else {
1029
4.17k
            cacheValue = newMoon2;
1030
4.17k
        }
1031
4.21k
        if (U_FAILURE(status)) {
1032
0
            return 0;
1033
0
        }
1034
1035
4.21k
        CalendarCache::put(setting.newYearCache, gyear, cacheValue, status);
1036
4.21k
    }
1037
198k
    if(U_FAILURE(status)) {
1038
0
        cacheValue = 0;
1039
0
    }
1040
198k
    return cacheValue;
1041
198k
}
1042
1043
}  // namespace
1044
1045
/**
1046
 * Adjust this calendar to be delta months before or after a given
1047
 * start position, pinning the day of month if necessary.  The start
1048
 * position is given as a local days number for the start of the month
1049
 * and a day-of-month.  Used by add() and roll().
1050
 * @param newMoon the local days of the first day of the month of the
1051
 * start position (days after January 1, 1970 0:00 Asia/Shanghai)
1052
 * @param dayOfMonth the 1-based day-of-month of the start position
1053
 * @param delta the number of months to move forward or backward from
1054
 * the start position
1055
 * @param status The status.
1056
 */
1057
void ChineseCalendar::offsetMonth(int32_t newMoon, int32_t dayOfMonth, int32_t delta,
1058
3.29k
                                  UErrorCode& status) {
1059
3.29k
    const Setting setting = getSetting(status);
1060
3.29k
    if (U_FAILURE(status)) {
1061
0
        return;
1062
0
    }
1063
1064
    // Move to the middle of the month before our target month.
1065
3.29k
    double value = newMoon;
1066
3.29k
    value += (CalendarAstronomer::SYNODIC_MONTH *
1067
3.29k
                          (static_cast<double>(delta) - 0.5));
1068
3.29k
    if (value < INT32_MIN || value > INT32_MAX) {
1069
54
        status = U_ILLEGAL_ARGUMENT_ERROR;
1070
54
        return;
1071
54
    }
1072
3.24k
    newMoon = static_cast<int32_t>(value);
1073
1074
    // Search forward to the target month's new moon
1075
3.24k
    newMoon = newMoonNear(setting.zoneAstroCalc, newMoon, true, status);
1076
3.24k
    if (U_FAILURE(status)) {
1077
38
        return;
1078
38
    }
1079
1080
    // Find the target dayOfMonth
1081
3.20k
    int32_t jd;
1082
3.20k
    if (uprv_add32_overflow(newMoon, kEpochStartAsJulianDay - 1, &jd) ||
1083
3.18k
        uprv_add32_overflow(jd, dayOfMonth, &jd)) {
1084
40
        status = U_ILLEGAL_ARGUMENT_ERROR;
1085
40
        return;
1086
40
    }
1087
1088
    // Pin the dayOfMonth.  In this calendar all months are 29 or 30 days
1089
    // so pinning just means handling dayOfMonth 30.
1090
3.16k
    if (dayOfMonth > 29) {
1091
783
        set(UCAL_JULIAN_DAY, jd-1);
1092
        // TODO Fix this.  We really shouldn't ever have to
1093
        // explicitly call complete().  This is either a bug in
1094
        // this method, in ChineseCalendar, or in
1095
        // Calendar.getActualMaximum().  I suspect the last.
1096
783
        complete(status);
1097
783
        if (U_FAILURE(status)) return;
1098
768
        if (getActualMaximum(UCAL_DAY_OF_MONTH, status) >= dayOfMonth) {
1099
404
            if (U_FAILURE(status)) return;
1100
404
            set(UCAL_JULIAN_DAY, jd);
1101
404
        }
1102
2.38k
    } else {
1103
2.38k
        set(UCAL_JULIAN_DAY, jd);
1104
2.38k
    }
1105
3.16k
}
1106
1107
IMPL_SYSTEM_DEFAULT_CENTURY(ChineseCalendar, "@calendar=chinese")
1108
1109
bool
1110
ChineseCalendar::inTemporalLeapYear(UErrorCode &status) const
1111
0
{
1112
0
    int32_t days = getActualMaximum(UCAL_DAY_OF_YEAR, status);
1113
0
    if (U_FAILURE(status)) return false;
1114
0
    return days > 360;
1115
0
}
1116
1117
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ChineseCalendar)
1118
1119
1120
static const char * const gTemporalLeapMonthCodes[] = {
1121
    "M01L", "M02L", "M03L", "M04L", "M05L", "M06L",
1122
    "M07L", "M08L", "M09L", "M10L", "M11L", "M12L", nullptr
1123
};
1124
1125
0
const char* ChineseCalendar::getTemporalMonthCode(UErrorCode &status) const {
1126
    // We need to call get, not internalGet, to force the calculation
1127
    // from UCAL_ORDINAL_MONTH.
1128
0
    int32_t is_leap = get(UCAL_IS_LEAP_MONTH, status);
1129
0
    if (U_FAILURE(status)) return nullptr;
1130
0
    if (is_leap != 0) {
1131
0
        int32_t month = get(UCAL_MONTH, status);
1132
0
        if (U_FAILURE(status)) return nullptr;
1133
0
        return gTemporalLeapMonthCodes[month];
1134
0
    }
1135
0
    return Calendar::getTemporalMonthCode(status);
1136
0
}
1137
1138
void
1139
ChineseCalendar::setTemporalMonthCode(const char* code, UErrorCode& status )
1140
0
{
1141
0
    if (U_FAILURE(status)) return;
1142
0
    int32_t len = static_cast<int32_t>(uprv_strlen(code));
1143
0
    if (len != 4 || code[0] != 'M' || code[3] != 'L') {
1144
0
        set(UCAL_IS_LEAP_MONTH, 0);
1145
0
        return Calendar::setTemporalMonthCode(code, status);
1146
0
    }
1147
0
    for (int m = 0; gTemporalLeapMonthCodes[m] != nullptr; m++) {
1148
0
        if (uprv_strcmp(code, gTemporalLeapMonthCodes[m]) == 0) {
1149
0
            set(UCAL_MONTH, m);
1150
0
            set(UCAL_IS_LEAP_MONTH, 1);
1151
0
            return;
1152
0
        }
1153
0
    }
1154
0
    status = U_ILLEGAL_ARGUMENT_ERROR;
1155
0
}
1156
1157
8.82k
int32_t ChineseCalendar::internalGetMonth(UErrorCode& status) const {
1158
8.82k
    if (U_FAILURE(status)) {
1159
0
        return 0;
1160
0
    }
1161
8.82k
    if (resolveFields(kMonthPrecedence) == UCAL_MONTH) {
1162
8.56k
        return internalGet(UCAL_MONTH);
1163
8.56k
    }
1164
267
    LocalPointer<Calendar> temp(this->clone());
1165
267
    temp->set(UCAL_MONTH, 0);
1166
267
    temp->set(UCAL_IS_LEAP_MONTH, 0);
1167
267
    temp->set(UCAL_DATE, 1);
1168
    // Calculate the UCAL_MONTH and UCAL_IS_LEAP_MONTH by adding number of
1169
    // months.
1170
267
    temp->roll(UCAL_MONTH, internalGet(UCAL_ORDINAL_MONTH), status);
1171
267
    if (U_FAILURE(status)) {
1172
29
        return 0;
1173
29
    }
1174
1175
238
    ChineseCalendar* nonConstThis = const_cast<ChineseCalendar*>(this); // cast away const
1176
238
    nonConstThis->internalSet(UCAL_IS_LEAP_MONTH, temp->get(UCAL_IS_LEAP_MONTH, status));
1177
238
    int32_t month = temp->get(UCAL_MONTH, status);
1178
238
    if (U_FAILURE(status)) {
1179
13
        return 0;
1180
13
    }
1181
225
    nonConstThis->internalSet(UCAL_MONTH, month);
1182
225
    return month;
1183
238
}
1184
1185
152
int32_t ChineseCalendar::internalGetMonth(int32_t defaultValue, UErrorCode& status) const {
1186
152
    if (U_FAILURE(status)) {
1187
0
        return 0;
1188
0
    }
1189
152
    switch (resolveFields(kMonthPrecedence)) {
1190
13
        case UCAL_MONTH:
1191
13
            return internalGet(UCAL_MONTH);
1192
107
        case UCAL_ORDINAL_MONTH:
1193
107
            return internalGetMonth(status);
1194
32
        default:
1195
32
            return defaultValue;
1196
152
    }
1197
152
}
1198
1199
45.2k
ChineseCalendar::Setting ChineseCalendar::getSetting(UErrorCode&) const {
1200
45.2k
  return {
1201
45.2k
        getAstronomerTimeZone(),
1202
45.2k
        &gWinterSolsticeCache,
1203
45.2k
        &gNewYearCache
1204
45.2k
  };
1205
45.2k
}
1206
1207
int32_t
1208
ChineseCalendar::getActualMaximum(UCalendarDateFields field, UErrorCode& status) const
1209
5.48k
{
1210
5.48k
    if (U_FAILURE(status)) {
1211
0
       return 0;
1212
0
    }
1213
5.48k
    if (field == UCAL_DATE) {
1214
3.90k
        LocalPointer<ChineseCalendar> cal(clone(), status);
1215
3.90k
        if(U_FAILURE(status)) {
1216
0
            return 0;
1217
0
        }
1218
3.90k
        cal->setLenient(true);
1219
3.90k
        cal->prepareGetActual(field,false,status);
1220
3.90k
        int32_t year = cal->get(UCAL_EXTENDED_YEAR, status);
1221
3.90k
        int32_t month = cal->get(UCAL_MONTH, status);
1222
3.90k
        bool leap = cal->get(UCAL_IS_LEAP_MONTH, status) != 0;
1223
3.90k
        return handleGetMonthLengthWithLeap(year, month, leap, status);
1224
3.90k
    }
1225
1.57k
    return Calendar::getActualMaximum(field, status);
1226
5.48k
}
1227
1228
U_NAMESPACE_END
1229
1230
#endif
1231