1/* 2 ****************************************************************************** 3 * Copyright (C) 2007-2013, International Business Machines Corporation 4 * and others. All Rights Reserved. 5 ****************************************************************************** 6 * 7 * File CHNSECAL.CPP 8 * 9 * Modification History: 10 * 11 * Date Name Description 12 * 9/18/2007 ajmacher ported from java ChineseCalendar 13 ***************************************************************************** 14 */ 15 16#include "chnsecal.h" 17 18#if !UCONFIG_NO_FORMATTING 19 20#include "umutex.h" 21#include <float.h> 22#include "gregoimp.h" // Math 23#include "astro.h" // CalendarAstronomer 24#include "unicode/simpletz.h" 25#include "uhash.h" 26#include "ucln_in.h" 27 28// Debugging 29#ifdef U_DEBUG_CHNSECAL 30# include <stdio.h> 31# include <stdarg.h> 32static void debug_chnsecal_loc(const char *f, int32_t l) 33{ 34 fprintf(stderr, "%s:%d: ", f, l); 35} 36 37static void debug_chnsecal_msg(const char *pat, ...) 38{ 39 va_list ap; 40 va_start(ap, pat); 41 vfprintf(stderr, pat, ap); 42 fflush(stderr); 43} 44// must use double parens, i.e.: U_DEBUG_CHNSECAL_MSG(("four is: %d",4)); 45#define U_DEBUG_CHNSECAL_MSG(x) {debug_chnsecal_loc(__FILE__,__LINE__);debug_chnsecal_msg x;} 46#else 47#define U_DEBUG_CHNSECAL_MSG(x) 48#endif 49 50 51// --- The cache -- 52static UMutex astroLock = U_MUTEX_INITIALIZER; // pod bay door lock 53static icu::CalendarAstronomer *gChineseCalendarAstro = NULL; 54static icu::CalendarCache *gChineseCalendarWinterSolsticeCache = NULL; 55static icu::CalendarCache *gChineseCalendarNewYearCache = NULL; 56static icu::TimeZone *gChineseCalendarZoneAstroCalc = NULL; 57static icu::UInitOnce gChineseCalendarZoneAstroCalcInitOnce = U_INITONCE_INITIALIZER; 58 59/** 60 * The start year of the Chinese calendar, the 61st year of the reign 61 * of Huang Di. Some sources use the first year of his reign, 62 * resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle) 63 * values one greater. 64 */ 65static const int32_t CHINESE_EPOCH_YEAR = -2636; // Gregorian year 66 67/** 68 * The offset from GMT in milliseconds at which we perform astronomical 69 * computations. Some sources use a different historically accurate 70 * offset of GMT+7:45:40 for years before 1929; we do not do this. 71 */ 72static const int32_t CHINA_OFFSET = 8 * kOneHour; 73 74/** 75 * Value to be added or subtracted from the local days of a new moon to 76 * get close to the next or prior new moon, but not cross it. Must be 77 * >= 1 and < CalendarAstronomer.SYNODIC_MONTH. 78 */ 79static const int32_t SYNODIC_GAP = 25; 80 81 82U_CDECL_BEGIN 83static UBool calendar_chinese_cleanup(void) { 84 if (gChineseCalendarAstro) { 85 delete gChineseCalendarAstro; 86 gChineseCalendarAstro = NULL; 87 } 88 if (gChineseCalendarWinterSolsticeCache) { 89 delete gChineseCalendarWinterSolsticeCache; 90 gChineseCalendarWinterSolsticeCache = NULL; 91 } 92 if (gChineseCalendarNewYearCache) { 93 delete gChineseCalendarNewYearCache; 94 gChineseCalendarNewYearCache = NULL; 95 } 96 if (gChineseCalendarZoneAstroCalc) { 97 delete gChineseCalendarZoneAstroCalc; 98 gChineseCalendarZoneAstroCalc = NULL; 99 } 100 gChineseCalendarZoneAstroCalcInitOnce.reset(); 101 return TRUE; 102} 103U_CDECL_END 104 105U_NAMESPACE_BEGIN 106 107 108// Implementation of the ChineseCalendar class 109 110 111//------------------------------------------------------------------------- 112// Constructors... 113//------------------------------------------------------------------------- 114 115 116Calendar* ChineseCalendar::clone() const { 117 return new ChineseCalendar(*this); 118} 119 120ChineseCalendar::ChineseCalendar(const Locale& aLocale, UErrorCode& success) 121: Calendar(TimeZone::createDefault(), aLocale, success), 122 isLeapYear(FALSE), 123 fEpochYear(CHINESE_EPOCH_YEAR), 124 fZoneAstroCalc(getChineseCalZoneAstroCalc()) 125{ 126 setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. 127} 128 129ChineseCalendar::ChineseCalendar(const Locale& aLocale, int32_t epochYear, 130 const TimeZone* zoneAstroCalc, UErrorCode &success) 131: Calendar(TimeZone::createDefault(), aLocale, success), 132 isLeapYear(FALSE), 133 fEpochYear(epochYear), 134 fZoneAstroCalc(zoneAstroCalc) 135{ 136 setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. 137} 138 139ChineseCalendar::ChineseCalendar(const ChineseCalendar& other) : Calendar(other) { 140 isLeapYear = other.isLeapYear; 141 fEpochYear = other.fEpochYear; 142 fZoneAstroCalc = other.fZoneAstroCalc; 143} 144 145ChineseCalendar::~ChineseCalendar() 146{ 147} 148 149const char *ChineseCalendar::getType() const { 150 return "chinese"; 151} 152 153static void U_CALLCONV initChineseCalZoneAstroCalc() { 154 gChineseCalendarZoneAstroCalc = new SimpleTimeZone(CHINA_OFFSET, UNICODE_STRING_SIMPLE("CHINA_ZONE") ); 155 ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); 156} 157 158const TimeZone* ChineseCalendar::getChineseCalZoneAstroCalc(void) const { 159 umtx_initOnce(gChineseCalendarZoneAstroCalcInitOnce, &initChineseCalZoneAstroCalc); 160 return gChineseCalendarZoneAstroCalc; 161} 162 163//------------------------------------------------------------------------- 164// Minimum / Maximum access functions 165//------------------------------------------------------------------------- 166 167 168static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { 169 // Minimum Greatest Least Maximum 170 // Minimum Maximum 171 { 1, 1, 83333, 83333}, // ERA 172 { 1, 1, 60, 60}, // YEAR 173 { 0, 0, 11, 11}, // MONTH 174 { 1, 1, 50, 55}, // WEEK_OF_YEAR 175 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH 176 { 1, 1, 29, 30}, // DAY_OF_MONTH 177 { 1, 1, 353, 385}, // DAY_OF_YEAR 178 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK 179 { -1, -1, 5, 5}, // DAY_OF_WEEK_IN_MONTH 180 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM 181 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR 182 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY 183 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE 184 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND 185 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND 186 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET 187 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET 188 { -5000000, -5000000, 5000000, 5000000}, // YEAR_WOY 189 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL 190 { -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR 191 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY 192 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY 193 { 0, 0, 1, 1}, // IS_LEAP_MONTH 194}; 195 196 197/** 198* @draft ICU 2.4 199*/ 200int32_t ChineseCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const { 201 return LIMITS[field][limitType]; 202} 203 204 205//---------------------------------------------------------------------- 206// Calendar framework 207//---------------------------------------------------------------------- 208 209/** 210 * Implement abstract Calendar method to return the extended year 211 * defined by the current fields. This will use either the ERA and 212 * YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR 213 * field as the continuous year count, depending on which is newer. 214 * @stable ICU 2.8 215 */ 216int32_t ChineseCalendar::handleGetExtendedYear() { 217 int32_t year; 218 if (newestStamp(UCAL_ERA, UCAL_YEAR, kUnset) <= fStamp[UCAL_EXTENDED_YEAR]) { 219 year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1 220 } else { 221 int32_t cycle = internalGet(UCAL_ERA, 1) - 1; // 0-based cycle 222 // adjust to the instance specific epoch 223 year = cycle * 60 + internalGet(UCAL_YEAR, 1) - (fEpochYear - CHINESE_EPOCH_YEAR); 224 } 225 return year; 226} 227 228/** 229 * Override Calendar method to return the number of days in the given 230 * extended year and month. 231 * 232 * <p>Note: This method also reads the IS_LEAP_MONTH field to determine 233 * whether or not the given month is a leap month. 234 * @stable ICU 2.8 235 */ 236int32_t ChineseCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month) const { 237 int32_t thisStart = handleComputeMonthStart(extendedYear, month, TRUE) - 238 kEpochStartAsJulianDay + 1; // Julian day -> local days 239 int32_t nextStart = newMoonNear(thisStart + SYNODIC_GAP, TRUE); 240 return nextStart - thisStart; 241} 242 243/** 244 * Override Calendar to compute several fields specific to the Chinese 245 * calendar system. These are: 246 * 247 * <ul><li>ERA 248 * <li>YEAR 249 * <li>MONTH 250 * <li>DAY_OF_MONTH 251 * <li>DAY_OF_YEAR 252 * <li>EXTENDED_YEAR</ul> 253 * 254 * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this 255 * method is called. The getGregorianXxx() methods return Gregorian 256 * calendar equivalents for the given Julian day. 257 * 258 * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH. 259 * @stable ICU 2.8 260 */ 261void ChineseCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/) { 262 263 computeChineseFields(julianDay - kEpochStartAsJulianDay, // local days 264 getGregorianYear(), getGregorianMonth(), 265 TRUE); // set all fields 266} 267 268/** 269 * Field resolution table that incorporates IS_LEAP_MONTH. 270 */ 271const UFieldResolutionTable ChineseCalendar::CHINESE_DATE_PRECEDENCE[] = 272{ 273 { 274 { UCAL_DAY_OF_MONTH, kResolveSTOP }, 275 { UCAL_WEEK_OF_YEAR, UCAL_DAY_OF_WEEK, kResolveSTOP }, 276 { UCAL_WEEK_OF_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP }, 277 { UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP }, 278 { UCAL_WEEK_OF_YEAR, UCAL_DOW_LOCAL, kResolveSTOP }, 279 { UCAL_WEEK_OF_MONTH, UCAL_DOW_LOCAL, kResolveSTOP }, 280 { UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DOW_LOCAL, kResolveSTOP }, 281 { UCAL_DAY_OF_YEAR, kResolveSTOP }, 282 { kResolveRemap | UCAL_DAY_OF_MONTH, UCAL_IS_LEAP_MONTH, kResolveSTOP }, 283 { kResolveSTOP } 284 }, 285 { 286 { UCAL_WEEK_OF_YEAR, kResolveSTOP }, 287 { UCAL_WEEK_OF_MONTH, kResolveSTOP }, 288 { UCAL_DAY_OF_WEEK_IN_MONTH, kResolveSTOP }, 289 { kResolveRemap | UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP }, 290 { kResolveRemap | UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DOW_LOCAL, kResolveSTOP }, 291 { kResolveSTOP } 292 }, 293 {{kResolveSTOP}} 294}; 295 296/** 297 * Override Calendar to add IS_LEAP_MONTH to the field resolution 298 * table. 299 * @stable ICU 2.8 300 */ 301const UFieldResolutionTable* ChineseCalendar::getFieldResolutionTable() const { 302 return CHINESE_DATE_PRECEDENCE; 303} 304 305/** 306 * Return the Julian day number of day before the first day of the 307 * given month in the given extended year. 308 * 309 * <p>Note: This method reads the IS_LEAP_MONTH field to determine 310 * whether the given month is a leap month. 311 * @param eyear the extended year 312 * @param month the zero-based month. The month is also determined 313 * by reading the IS_LEAP_MONTH field. 314 * @return the Julian day number of the day before the first 315 * day of the given month and year 316 * @stable ICU 2.8 317 */ 318int32_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool useMonth) const { 319 320 ChineseCalendar *nonConstThis = (ChineseCalendar*)this; // cast away const 321 322 // If the month is out of range, adjust it into range, and 323 // modify the extended year value accordingly. 324 if (month < 0 || month > 11) { 325 double m = month; 326 eyear += (int32_t)ClockMath::floorDivide(m, 12.0, m); 327 month = (int32_t)m; 328 } 329 330 int32_t gyear = eyear + fEpochYear - 1; // Gregorian year 331 int32_t theNewYear = newYear(gyear); 332 int32_t newMoon = newMoonNear(theNewYear + month * 29, TRUE); 333 334 int32_t julianDay = newMoon + kEpochStartAsJulianDay; 335 336 // Save fields for later restoration 337 int32_t saveMonth = internalGet(UCAL_MONTH); 338 int32_t saveIsLeapMonth = internalGet(UCAL_IS_LEAP_MONTH); 339 340 // Ignore IS_LEAP_MONTH field if useMonth is false 341 int32_t isLeapMonth = useMonth ? saveIsLeapMonth : 0; 342 343 UErrorCode status = U_ZERO_ERROR; 344 nonConstThis->computeGregorianFields(julianDay, status); 345 if (U_FAILURE(status)) 346 return 0; 347 348 // This will modify the MONTH and IS_LEAP_MONTH fields (only) 349 nonConstThis->computeChineseFields(newMoon, getGregorianYear(), 350 getGregorianMonth(), FALSE); 351 352 if (month != internalGet(UCAL_MONTH) || 353 isLeapMonth != internalGet(UCAL_IS_LEAP_MONTH)) { 354 newMoon = newMoonNear(newMoon + SYNODIC_GAP, TRUE); 355 julianDay = newMoon + kEpochStartAsJulianDay; 356 } 357 358 nonConstThis->internalSet(UCAL_MONTH, saveMonth); 359 nonConstThis->internalSet(UCAL_IS_LEAP_MONTH, saveIsLeapMonth); 360 361 return julianDay - 1; 362} 363 364 365/** 366 * Override Calendar to handle leap months properly. 367 * @stable ICU 2.8 368 */ 369void ChineseCalendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status) { 370 switch (field) { 371 case UCAL_MONTH: 372 if (amount != 0) { 373 int32_t dom = get(UCAL_DAY_OF_MONTH, status); 374 if (U_FAILURE(status)) break; 375 int32_t day = get(UCAL_JULIAN_DAY, status) - kEpochStartAsJulianDay; // Get local day 376 if (U_FAILURE(status)) break; 377 int32_t moon = day - dom + 1; // New moon 378 offsetMonth(moon, dom, amount); 379 } 380 break; 381 default: 382 Calendar::add(field, amount, status); 383 break; 384 } 385} 386 387/** 388 * Override Calendar to handle leap months properly. 389 * @stable ICU 2.8 390 */ 391void ChineseCalendar::add(EDateFields field, int32_t amount, UErrorCode& status) { 392 add((UCalendarDateFields)field, amount, status); 393} 394 395/** 396 * Override Calendar to handle leap months properly. 397 * @stable ICU 2.8 398 */ 399void ChineseCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& status) { 400 switch (field) { 401 case UCAL_MONTH: 402 if (amount != 0) { 403 int32_t dom = get(UCAL_DAY_OF_MONTH, status); 404 if (U_FAILURE(status)) break; 405 int32_t day = get(UCAL_JULIAN_DAY, status) - kEpochStartAsJulianDay; // Get local day 406 if (U_FAILURE(status)) break; 407 int32_t moon = day - dom + 1; // New moon (start of this month) 408 409 // Note throughout the following: Months 12 and 1 are never 410 // followed by a leap month (D&R p. 185). 411 412 // Compute the adjusted month number m. This is zero-based 413 // value from 0..11 in a non-leap year, and from 0..12 in a 414 // leap year. 415 int32_t m = get(UCAL_MONTH, status); // 0-based month 416 if (U_FAILURE(status)) break; 417 if (isLeapYear) { // (member variable) 418 if (get(UCAL_IS_LEAP_MONTH, status) == 1) { 419 ++m; 420 } else { 421 // Check for a prior leap month. (In the 422 // following, month 0 is the first month of the 423 // year.) Month 0 is never followed by a leap 424 // month, and we know month m is not a leap month. 425 // moon1 will be the start of month 0 if there is 426 // no leap month between month 0 and month m; 427 // otherwise it will be the start of month 1. 428 int moon1 = moon - 429 (int) (CalendarAstronomer::SYNODIC_MONTH * (m - 0.5)); 430 moon1 = newMoonNear(moon1, TRUE); 431 if (isLeapMonthBetween(moon1, moon)) { 432 ++m; 433 } 434 } 435 if (U_FAILURE(status)) break; 436 } 437 438 // Now do the standard roll computation on m, with the 439 // allowed range of 0..n-1, where n is 12 or 13. 440 int32_t n = isLeapYear ? 13 : 12; // Months in this year 441 int32_t newM = (m + amount) % n; 442 if (newM < 0) { 443 newM += n; 444 } 445 446 if (newM != m) { 447 offsetMonth(moon, dom, newM - m); 448 } 449 } 450 break; 451 default: 452 Calendar::roll(field, amount, status); 453 break; 454 } 455} 456 457void ChineseCalendar::roll(EDateFields field, int32_t amount, UErrorCode& status) { 458 roll((UCalendarDateFields)field, amount, status); 459} 460 461 462//------------------------------------------------------------------ 463// Support methods and constants 464//------------------------------------------------------------------ 465 466/** 467 * Convert local days to UTC epoch milliseconds. 468 * This is not an accurate conversion in that getTimezoneOffset 469 * takes the milliseconds in GMT (not local time). In theory, more 470 * accurate algorithm can be implemented but practically we do not need 471 * to go through that complication as long as the historical timezone 472 * changes did not happen around the 'tricky' new moon (new moon around 473 * midnight). 474 * 475 * @param days days after January 1, 1970 0:00 in the astronomical base zone 476 * @return milliseconds after January 1, 1970 0:00 GMT 477 */ 478double ChineseCalendar::daysToMillis(double days) const { 479 double millis = days * (double)kOneDay; 480 if (fZoneAstroCalc != NULL) { 481 int32_t rawOffset, dstOffset; 482 UErrorCode status = U_ZERO_ERROR; 483 fZoneAstroCalc->getOffset(millis, FALSE, rawOffset, dstOffset, status); 484 if (U_SUCCESS(status)) { 485 return millis - (double)(rawOffset + dstOffset); 486 } 487 } 488 return millis - (double)CHINA_OFFSET; 489} 490 491/** 492 * Convert UTC epoch milliseconds to local days. 493 * @param millis milliseconds after January 1, 1970 0:00 GMT 494 * @return days after January 1, 1970 0:00 in the astronomical base zone 495 */ 496double ChineseCalendar::millisToDays(double millis) const { 497 if (fZoneAstroCalc != NULL) { 498 int32_t rawOffset, dstOffset; 499 UErrorCode status = U_ZERO_ERROR; 500 fZoneAstroCalc->getOffset(millis, FALSE, rawOffset, dstOffset, status); 501 if (U_SUCCESS(status)) { 502 return ClockMath::floorDivide(millis + (double)(rawOffset + dstOffset), kOneDay); 503 } 504 } 505 return ClockMath::floorDivide(millis + (double)CHINA_OFFSET, kOneDay); 506} 507 508//------------------------------------------------------------------ 509// Astronomical computations 510//------------------------------------------------------------------ 511 512 513/** 514 * Return the major solar term on or after December 15 of the given 515 * Gregorian year, that is, the winter solstice of the given year. 516 * Computations are relative to Asia/Shanghai time zone. 517 * @param gyear a Gregorian year 518 * @return days after January 1, 1970 0:00 Asia/Shanghai of the 519 * winter solstice of the given year 520 */ 521int32_t ChineseCalendar::winterSolstice(int32_t gyear) const { 522 523 UErrorCode status = U_ZERO_ERROR; 524 int32_t cacheValue = CalendarCache::get(&gChineseCalendarWinterSolsticeCache, gyear, status); 525 526 if (cacheValue == 0) { 527 // In books December 15 is used, but it fails for some years 528 // using our algorithms, e.g.: 1298 1391 1492 1553 1560. That 529 // is, winterSolstice(1298) starts search at Dec 14 08:00:00 530 // PST 1298 with a final result of Dec 14 10:31:59 PST 1299. 531 double ms = daysToMillis(Grego::fieldsToDay(gyear, UCAL_DECEMBER, 1)); 532 533 umtx_lock(&astroLock); 534 if(gChineseCalendarAstro == NULL) { 535 gChineseCalendarAstro = new CalendarAstronomer(); 536 ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); 537 } 538 gChineseCalendarAstro->setTime(ms); 539 UDate solarLong = gChineseCalendarAstro->getSunTime(CalendarAstronomer::WINTER_SOLSTICE(), TRUE); 540 umtx_unlock(&astroLock); 541 542 // Winter solstice is 270 degrees solar longitude aka Dongzhi 543 cacheValue = (int32_t)millisToDays(solarLong); 544 CalendarCache::put(&gChineseCalendarWinterSolsticeCache, gyear, cacheValue, status); 545 } 546 if(U_FAILURE(status)) { 547 cacheValue = 0; 548 } 549 return cacheValue; 550} 551 552/** 553 * Return the closest new moon to the given date, searching either 554 * forward or backward in time. 555 * @param days days after January 1, 1970 0:00 Asia/Shanghai 556 * @param after if true, search for a new moon on or after the given 557 * date; otherwise, search for a new moon before it 558 * @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest 559 * new moon after or before <code>days</code> 560 */ 561int32_t ChineseCalendar::newMoonNear(double days, UBool after) const { 562 563 umtx_lock(&astroLock); 564 if(gChineseCalendarAstro == NULL) { 565 gChineseCalendarAstro = new CalendarAstronomer(); 566 ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); 567 } 568 gChineseCalendarAstro->setTime(daysToMillis(days)); 569 UDate newMoon = gChineseCalendarAstro->getMoonTime(CalendarAstronomer::NEW_MOON(), after); 570 umtx_unlock(&astroLock); 571 572 return (int32_t) millisToDays(newMoon); 573} 574 575/** 576 * Return the nearest integer number of synodic months between 577 * two dates. 578 * @param day1 days after January 1, 1970 0:00 Asia/Shanghai 579 * @param day2 days after January 1, 1970 0:00 Asia/Shanghai 580 * @return the nearest integer number of months between day1 and day2 581 */ 582int32_t ChineseCalendar::synodicMonthsBetween(int32_t day1, int32_t day2) const { 583 double roundme = ((day2 - day1) / CalendarAstronomer::SYNODIC_MONTH); 584 return (int32_t) (roundme + (roundme >= 0 ? .5 : -.5)); 585} 586 587/** 588 * Return the major solar term on or before a given date. This 589 * will be an integer from 1..12, with 1 corresponding to 330 degrees, 590 * 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees. 591 * @param days days after January 1, 1970 0:00 Asia/Shanghai 592 */ 593int32_t ChineseCalendar::majorSolarTerm(int32_t days) const { 594 595 umtx_lock(&astroLock); 596 if(gChineseCalendarAstro == NULL) { 597 gChineseCalendarAstro = new CalendarAstronomer(); 598 ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); 599 } 600 gChineseCalendarAstro->setTime(daysToMillis(days)); 601 UDate solarLongitude = gChineseCalendarAstro->getSunLongitude(); 602 umtx_unlock(&astroLock); 603 604 // Compute (floor(solarLongitude / (pi/6)) + 2) % 12 605 int32_t term = ( ((int32_t)(6 * solarLongitude / CalendarAstronomer::PI)) + 2 ) % 12; 606 if (term < 1) { 607 term += 12; 608 } 609 return term; 610} 611 612/** 613 * Return true if the given month lacks a major solar term. 614 * @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new 615 * moon 616 */ 617UBool ChineseCalendar::hasNoMajorSolarTerm(int32_t newMoon) const { 618 return majorSolarTerm(newMoon) == 619 majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, TRUE)); 620} 621 622 623//------------------------------------------------------------------ 624// Time to fields 625//------------------------------------------------------------------ 626 627/** 628 * Return true if there is a leap month on or after month newMoon1 and 629 * at or before month newMoon2. 630 * @param newMoon1 days after January 1, 1970 0:00 astronomical base zone 631 * of a new moon 632 * @param newMoon2 days after January 1, 1970 0:00 astronomical base zone 633 * of a new moon 634 */ 635UBool ChineseCalendar::isLeapMonthBetween(int32_t newMoon1, int32_t newMoon2) const { 636 637#ifdef U_DEBUG_CHNSECAL 638 // This is only needed to debug the timeOfAngle divergence bug. 639 // Remove this later. Liu 11/9/00 640 if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) { 641 U_DEBUG_CHNSECAL_MSG(( 642 "isLeapMonthBetween(%d, %d): Invalid parameters", newMoon1, newMoon2 643 )); 644 } 645#endif 646 647 return (newMoon2 >= newMoon1) && 648 (isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, FALSE)) || 649 hasNoMajorSolarTerm(newMoon2)); 650} 651 652/** 653 * Compute fields for the Chinese calendar system. This method can 654 * either set all relevant fields, as required by 655 * <code>handleComputeFields()</code>, or it can just set the MONTH and 656 * IS_LEAP_MONTH fields, as required by 657 * <code>handleComputeMonthStart()</code>. 658 * 659 * <p>As a side effect, this method sets {@link #isLeapYear}. 660 * @param days days after January 1, 1970 0:00 astronomical base zone 661 * of the date to compute fields for 662 * @param gyear the Gregorian year of the given date 663 * @param gmonth the Gregorian month of the given date 664 * @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR, 665 * DAY_OF_MONTH, and DAY_OF_YEAR fields. In either case set the MONTH 666 * and IS_LEAP_MONTH fields. 667 */ 668void ChineseCalendar::computeChineseFields(int32_t days, int32_t gyear, int32_t gmonth, 669 UBool setAllFields) { 670 671 // Find the winter solstices before and after the target date. 672 // These define the boundaries of this Chinese year, specifically, 673 // the position of month 11, which always contains the solstice. 674 // We want solsticeBefore <= date < solsticeAfter. 675 int32_t solsticeBefore; 676 int32_t solsticeAfter = winterSolstice(gyear); 677 if (days < solsticeAfter) { 678 solsticeBefore = winterSolstice(gyear - 1); 679 } else { 680 solsticeBefore = solsticeAfter; 681 solsticeAfter = winterSolstice(gyear + 1); 682 } 683 684 // Find the start of the month after month 11. This will be either 685 // the prior month 12 or leap month 11 (very rare). Also find the 686 // start of the following month 11. 687 int32_t firstMoon = newMoonNear(solsticeBefore + 1, TRUE); 688 int32_t lastMoon = newMoonNear(solsticeAfter + 1, FALSE); 689 int32_t thisMoon = newMoonNear(days + 1, FALSE); // Start of this month 690 // Note: isLeapYear is a member variable 691 isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12; 692 693 int32_t month = synodicMonthsBetween(firstMoon, thisMoon); 694 if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) { 695 month--; 696 } 697 if (month < 1) { 698 month += 12; 699 } 700 701 UBool isLeapMonth = isLeapYear && 702 hasNoMajorSolarTerm(thisMoon) && 703 !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, FALSE)); 704 705 internalSet(UCAL_MONTH, month-1); // Convert from 1-based to 0-based 706 internalSet(UCAL_IS_LEAP_MONTH, isLeapMonth?1:0); 707 708 if (setAllFields) { 709 710 // Extended year and cycle year is based on the epoch year 711 712 int32_t extended_year = gyear - fEpochYear; 713 int cycle_year = gyear - CHINESE_EPOCH_YEAR; 714 if (month < 11 || 715 gmonth >= UCAL_JULY) { 716 extended_year++; 717 cycle_year++; 718 } 719 int32_t dayOfMonth = days - thisMoon + 1; 720 721 internalSet(UCAL_EXTENDED_YEAR, extended_year); 722 723 // 0->0,60 1->1,1 60->1,60 61->2,1 etc. 724 int32_t yearOfCycle; 725 int32_t cycle = ClockMath::floorDivide(cycle_year - 1, 60, yearOfCycle); 726 internalSet(UCAL_ERA, cycle + 1); 727 internalSet(UCAL_YEAR, yearOfCycle + 1); 728 729 internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); 730 731 // Days will be before the first new year we compute if this 732 // date is in month 11, leap 11, 12. There is never a leap 12. 733 // New year computations are cached so this should be cheap in 734 // the long run. 735 int32_t theNewYear = newYear(gyear); 736 if (days < theNewYear) { 737 theNewYear = newYear(gyear-1); 738 } 739 internalSet(UCAL_DAY_OF_YEAR, days - theNewYear + 1); 740 } 741} 742 743 744//------------------------------------------------------------------ 745// Fields to time 746//------------------------------------------------------------------ 747 748/** 749 * Return the Chinese new year of the given Gregorian year. 750 * @param gyear a Gregorian year 751 * @return days after January 1, 1970 0:00 astronomical base zone of the 752 * Chinese new year of the given year (this will be a new moon) 753 */ 754int32_t ChineseCalendar::newYear(int32_t gyear) const { 755 UErrorCode status = U_ZERO_ERROR; 756 int32_t cacheValue = CalendarCache::get(&gChineseCalendarNewYearCache, gyear, status); 757 758 if (cacheValue == 0) { 759 760 int32_t solsticeBefore= winterSolstice(gyear - 1); 761 int32_t solsticeAfter = winterSolstice(gyear); 762 int32_t newMoon1 = newMoonNear(solsticeBefore + 1, TRUE); 763 int32_t newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, TRUE); 764 int32_t newMoon11 = newMoonNear(solsticeAfter + 1, FALSE); 765 766 if (synodicMonthsBetween(newMoon1, newMoon11) == 12 && 767 (hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) { 768 cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, TRUE); 769 } else { 770 cacheValue = newMoon2; 771 } 772 773 CalendarCache::put(&gChineseCalendarNewYearCache, gyear, cacheValue, status); 774 } 775 if(U_FAILURE(status)) { 776 cacheValue = 0; 777 } 778 return cacheValue; 779} 780 781/** 782 * Adjust this calendar to be delta months before or after a given 783 * start position, pinning the day of month if necessary. The start 784 * position is given as a local days number for the start of the month 785 * and a day-of-month. Used by add() and roll(). 786 * @param newMoon the local days of the first day of the month of the 787 * start position (days after January 1, 1970 0:00 Asia/Shanghai) 788 * @param dom the 1-based day-of-month of the start position 789 * @param delta the number of months to move forward or backward from 790 * the start position 791 */ 792void ChineseCalendar::offsetMonth(int32_t newMoon, int32_t dom, int32_t delta) { 793 UErrorCode status = U_ZERO_ERROR; 794 795 // Move to the middle of the month before our target month. 796 newMoon += (int32_t) (CalendarAstronomer::SYNODIC_MONTH * (delta - 0.5)); 797 798 // Search forward to the target month's new moon 799 newMoon = newMoonNear(newMoon, TRUE); 800 801 // Find the target dom 802 int32_t jd = newMoon + kEpochStartAsJulianDay - 1 + dom; 803 804 // Pin the dom. In this calendar all months are 29 or 30 days 805 // so pinning just means handling dom 30. 806 if (dom > 29) { 807 set(UCAL_JULIAN_DAY, jd-1); 808 // TODO Fix this. We really shouldn't ever have to 809 // explicitly call complete(). This is either a bug in 810 // this method, in ChineseCalendar, or in 811 // Calendar.getActualMaximum(). I suspect the last. 812 complete(status); 813 if (U_FAILURE(status)) return; 814 if (getActualMaximum(UCAL_DAY_OF_MONTH, status) >= dom) { 815 if (U_FAILURE(status)) return; 816 set(UCAL_JULIAN_DAY, jd); 817 } 818 } else { 819 set(UCAL_JULIAN_DAY, jd); 820 } 821} 822 823 824UBool 825ChineseCalendar::inDaylightTime(UErrorCode& status) const 826{ 827 // copied from GregorianCalendar 828 if (U_FAILURE(status) || !getTimeZone().useDaylightTime()) 829 return FALSE; 830 831 // Force an update of the state of the Calendar. 832 ((ChineseCalendar*)this)->complete(status); // cast away const 833 834 return (UBool)(U_SUCCESS(status) ? (internalGet(UCAL_DST_OFFSET) != 0) : FALSE); 835} 836 837// default century 838 839static UDate gSystemDefaultCenturyStart = DBL_MIN; 840static int32_t gSystemDefaultCenturyStartYear = -1; 841static icu::UInitOnce gSystemDefaultCenturyInitOnce = U_INITONCE_INITIALIZER; 842 843 844UBool ChineseCalendar::haveDefaultCentury() const 845{ 846 return TRUE; 847} 848 849UDate ChineseCalendar::defaultCenturyStart() const 850{ 851 return internalGetDefaultCenturyStart(); 852} 853 854int32_t ChineseCalendar::defaultCenturyStartYear() const 855{ 856 return internalGetDefaultCenturyStartYear(); 857} 858 859static void U_CALLCONV initializeSystemDefaultCentury() 860{ 861 // initialize systemDefaultCentury and systemDefaultCenturyYear based 862 // on the current time. They'll be set to 80 years before 863 // the current time. 864 UErrorCode status = U_ZERO_ERROR; 865 ChineseCalendar calendar(Locale("@calendar=chinese"),status); 866 if (U_SUCCESS(status)) { 867 calendar.setTime(Calendar::getNow(), status); 868 calendar.add(UCAL_YEAR, -80, status); 869 gSystemDefaultCenturyStart = calendar.getTime(status); 870 gSystemDefaultCenturyStartYear = calendar.get(UCAL_YEAR, status); 871 } 872 // We have no recourse upon failure unless we want to propagate the failure 873 // out. 874} 875 876UDate 877ChineseCalendar::internalGetDefaultCenturyStart() const 878{ 879 // lazy-evaluate systemDefaultCenturyStart 880 umtx_initOnce(gSystemDefaultCenturyInitOnce, &initializeSystemDefaultCentury); 881 return gSystemDefaultCenturyStart; 882} 883 884int32_t 885ChineseCalendar::internalGetDefaultCenturyStartYear() const 886{ 887 // lazy-evaluate systemDefaultCenturyStartYear 888 umtx_initOnce(gSystemDefaultCenturyInitOnce, &initializeSystemDefaultCentury); 889 return gSystemDefaultCenturyStartYear; 890} 891 892UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ChineseCalendar) 893 894U_NAMESPACE_END 895 896#endif 897 898