1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/intl/icu/source/i18n/chnsecal.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,897 @@ 1.4 +/* 1.5 + ****************************************************************************** 1.6 + * Copyright (C) 2007-2013, International Business Machines Corporation 1.7 + * and others. All Rights Reserved. 1.8 + ****************************************************************************** 1.9 + * 1.10 + * File CHNSECAL.CPP 1.11 + * 1.12 + * Modification History: 1.13 + * 1.14 + * Date Name Description 1.15 + * 9/18/2007 ajmacher ported from java ChineseCalendar 1.16 + ***************************************************************************** 1.17 + */ 1.18 + 1.19 +#include "chnsecal.h" 1.20 + 1.21 +#if !UCONFIG_NO_FORMATTING 1.22 + 1.23 +#include "umutex.h" 1.24 +#include <float.h> 1.25 +#include "gregoimp.h" // Math 1.26 +#include "astro.h" // CalendarAstronomer 1.27 +#include "unicode/simpletz.h" 1.28 +#include "uhash.h" 1.29 +#include "ucln_in.h" 1.30 + 1.31 +// Debugging 1.32 +#ifdef U_DEBUG_CHNSECAL 1.33 +# include <stdio.h> 1.34 +# include <stdarg.h> 1.35 +static void debug_chnsecal_loc(const char *f, int32_t l) 1.36 +{ 1.37 + fprintf(stderr, "%s:%d: ", f, l); 1.38 +} 1.39 + 1.40 +static void debug_chnsecal_msg(const char *pat, ...) 1.41 +{ 1.42 + va_list ap; 1.43 + va_start(ap, pat); 1.44 + vfprintf(stderr, pat, ap); 1.45 + fflush(stderr); 1.46 +} 1.47 +// must use double parens, i.e.: U_DEBUG_CHNSECAL_MSG(("four is: %d",4)); 1.48 +#define U_DEBUG_CHNSECAL_MSG(x) {debug_chnsecal_loc(__FILE__,__LINE__);debug_chnsecal_msg x;} 1.49 +#else 1.50 +#define U_DEBUG_CHNSECAL_MSG(x) 1.51 +#endif 1.52 + 1.53 + 1.54 +// --- The cache -- 1.55 +static UMutex astroLock = U_MUTEX_INITIALIZER; // pod bay door lock 1.56 +static icu::CalendarAstronomer *gChineseCalendarAstro = NULL; 1.57 +static icu::CalendarCache *gChineseCalendarWinterSolsticeCache = NULL; 1.58 +static icu::CalendarCache *gChineseCalendarNewYearCache = NULL; 1.59 +static icu::TimeZone *gChineseCalendarZoneAstroCalc = NULL; 1.60 +static icu::UInitOnce gChineseCalendarZoneAstroCalcInitOnce = U_INITONCE_INITIALIZER; 1.61 + 1.62 +/** 1.63 + * The start year of the Chinese calendar, the 61st year of the reign 1.64 + * of Huang Di. Some sources use the first year of his reign, 1.65 + * resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle) 1.66 + * values one greater. 1.67 + */ 1.68 +static const int32_t CHINESE_EPOCH_YEAR = -2636; // Gregorian year 1.69 + 1.70 +/** 1.71 + * The offset from GMT in milliseconds at which we perform astronomical 1.72 + * computations. Some sources use a different historically accurate 1.73 + * offset of GMT+7:45:40 for years before 1929; we do not do this. 1.74 + */ 1.75 +static const int32_t CHINA_OFFSET = 8 * kOneHour; 1.76 + 1.77 +/** 1.78 + * Value to be added or subtracted from the local days of a new moon to 1.79 + * get close to the next or prior new moon, but not cross it. Must be 1.80 + * >= 1 and < CalendarAstronomer.SYNODIC_MONTH. 1.81 + */ 1.82 +static const int32_t SYNODIC_GAP = 25; 1.83 + 1.84 + 1.85 +U_CDECL_BEGIN 1.86 +static UBool calendar_chinese_cleanup(void) { 1.87 + if (gChineseCalendarAstro) { 1.88 + delete gChineseCalendarAstro; 1.89 + gChineseCalendarAstro = NULL; 1.90 + } 1.91 + if (gChineseCalendarWinterSolsticeCache) { 1.92 + delete gChineseCalendarWinterSolsticeCache; 1.93 + gChineseCalendarWinterSolsticeCache = NULL; 1.94 + } 1.95 + if (gChineseCalendarNewYearCache) { 1.96 + delete gChineseCalendarNewYearCache; 1.97 + gChineseCalendarNewYearCache = NULL; 1.98 + } 1.99 + if (gChineseCalendarZoneAstroCalc) { 1.100 + delete gChineseCalendarZoneAstroCalc; 1.101 + gChineseCalendarZoneAstroCalc = NULL; 1.102 + } 1.103 + gChineseCalendarZoneAstroCalcInitOnce.reset(); 1.104 + return TRUE; 1.105 +} 1.106 +U_CDECL_END 1.107 + 1.108 +U_NAMESPACE_BEGIN 1.109 + 1.110 + 1.111 +// Implementation of the ChineseCalendar class 1.112 + 1.113 + 1.114 +//------------------------------------------------------------------------- 1.115 +// Constructors... 1.116 +//------------------------------------------------------------------------- 1.117 + 1.118 + 1.119 +Calendar* ChineseCalendar::clone() const { 1.120 + return new ChineseCalendar(*this); 1.121 +} 1.122 + 1.123 +ChineseCalendar::ChineseCalendar(const Locale& aLocale, UErrorCode& success) 1.124 +: Calendar(TimeZone::createDefault(), aLocale, success), 1.125 + isLeapYear(FALSE), 1.126 + fEpochYear(CHINESE_EPOCH_YEAR), 1.127 + fZoneAstroCalc(getChineseCalZoneAstroCalc()) 1.128 +{ 1.129 + setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. 1.130 +} 1.131 + 1.132 +ChineseCalendar::ChineseCalendar(const Locale& aLocale, int32_t epochYear, 1.133 + const TimeZone* zoneAstroCalc, UErrorCode &success) 1.134 +: Calendar(TimeZone::createDefault(), aLocale, success), 1.135 + isLeapYear(FALSE), 1.136 + fEpochYear(epochYear), 1.137 + fZoneAstroCalc(zoneAstroCalc) 1.138 +{ 1.139 + setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. 1.140 +} 1.141 + 1.142 +ChineseCalendar::ChineseCalendar(const ChineseCalendar& other) : Calendar(other) { 1.143 + isLeapYear = other.isLeapYear; 1.144 + fEpochYear = other.fEpochYear; 1.145 + fZoneAstroCalc = other.fZoneAstroCalc; 1.146 +} 1.147 + 1.148 +ChineseCalendar::~ChineseCalendar() 1.149 +{ 1.150 +} 1.151 + 1.152 +const char *ChineseCalendar::getType() const { 1.153 + return "chinese"; 1.154 +} 1.155 + 1.156 +static void U_CALLCONV initChineseCalZoneAstroCalc() { 1.157 + gChineseCalendarZoneAstroCalc = new SimpleTimeZone(CHINA_OFFSET, UNICODE_STRING_SIMPLE("CHINA_ZONE") ); 1.158 + ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); 1.159 +} 1.160 + 1.161 +const TimeZone* ChineseCalendar::getChineseCalZoneAstroCalc(void) const { 1.162 + umtx_initOnce(gChineseCalendarZoneAstroCalcInitOnce, &initChineseCalZoneAstroCalc); 1.163 + return gChineseCalendarZoneAstroCalc; 1.164 +} 1.165 + 1.166 +//------------------------------------------------------------------------- 1.167 +// Minimum / Maximum access functions 1.168 +//------------------------------------------------------------------------- 1.169 + 1.170 + 1.171 +static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { 1.172 + // Minimum Greatest Least Maximum 1.173 + // Minimum Maximum 1.174 + { 1, 1, 83333, 83333}, // ERA 1.175 + { 1, 1, 60, 60}, // YEAR 1.176 + { 0, 0, 11, 11}, // MONTH 1.177 + { 1, 1, 50, 55}, // WEEK_OF_YEAR 1.178 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH 1.179 + { 1, 1, 29, 30}, // DAY_OF_MONTH 1.180 + { 1, 1, 353, 385}, // DAY_OF_YEAR 1.181 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK 1.182 + { -1, -1, 5, 5}, // DAY_OF_WEEK_IN_MONTH 1.183 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM 1.184 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR 1.185 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY 1.186 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE 1.187 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND 1.188 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND 1.189 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET 1.190 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET 1.191 + { -5000000, -5000000, 5000000, 5000000}, // YEAR_WOY 1.192 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL 1.193 + { -5000000, -5000000, 5000000, 5000000}, // EXTENDED_YEAR 1.194 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY 1.195 + {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY 1.196 + { 0, 0, 1, 1}, // IS_LEAP_MONTH 1.197 +}; 1.198 + 1.199 + 1.200 +/** 1.201 +* @draft ICU 2.4 1.202 +*/ 1.203 +int32_t ChineseCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const { 1.204 + return LIMITS[field][limitType]; 1.205 +} 1.206 + 1.207 + 1.208 +//---------------------------------------------------------------------- 1.209 +// Calendar framework 1.210 +//---------------------------------------------------------------------- 1.211 + 1.212 +/** 1.213 + * Implement abstract Calendar method to return the extended year 1.214 + * defined by the current fields. This will use either the ERA and 1.215 + * YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR 1.216 + * field as the continuous year count, depending on which is newer. 1.217 + * @stable ICU 2.8 1.218 + */ 1.219 +int32_t ChineseCalendar::handleGetExtendedYear() { 1.220 + int32_t year; 1.221 + if (newestStamp(UCAL_ERA, UCAL_YEAR, kUnset) <= fStamp[UCAL_EXTENDED_YEAR]) { 1.222 + year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1 1.223 + } else { 1.224 + int32_t cycle = internalGet(UCAL_ERA, 1) - 1; // 0-based cycle 1.225 + // adjust to the instance specific epoch 1.226 + year = cycle * 60 + internalGet(UCAL_YEAR, 1) - (fEpochYear - CHINESE_EPOCH_YEAR); 1.227 + } 1.228 + return year; 1.229 +} 1.230 + 1.231 +/** 1.232 + * Override Calendar method to return the number of days in the given 1.233 + * extended year and month. 1.234 + * 1.235 + * <p>Note: This method also reads the IS_LEAP_MONTH field to determine 1.236 + * whether or not the given month is a leap month. 1.237 + * @stable ICU 2.8 1.238 + */ 1.239 +int32_t ChineseCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month) const { 1.240 + int32_t thisStart = handleComputeMonthStart(extendedYear, month, TRUE) - 1.241 + kEpochStartAsJulianDay + 1; // Julian day -> local days 1.242 + int32_t nextStart = newMoonNear(thisStart + SYNODIC_GAP, TRUE); 1.243 + return nextStart - thisStart; 1.244 +} 1.245 + 1.246 +/** 1.247 + * Override Calendar to compute several fields specific to the Chinese 1.248 + * calendar system. These are: 1.249 + * 1.250 + * <ul><li>ERA 1.251 + * <li>YEAR 1.252 + * <li>MONTH 1.253 + * <li>DAY_OF_MONTH 1.254 + * <li>DAY_OF_YEAR 1.255 + * <li>EXTENDED_YEAR</ul> 1.256 + * 1.257 + * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this 1.258 + * method is called. The getGregorianXxx() methods return Gregorian 1.259 + * calendar equivalents for the given Julian day. 1.260 + * 1.261 + * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH. 1.262 + * @stable ICU 2.8 1.263 + */ 1.264 +void ChineseCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/) { 1.265 + 1.266 + computeChineseFields(julianDay - kEpochStartAsJulianDay, // local days 1.267 + getGregorianYear(), getGregorianMonth(), 1.268 + TRUE); // set all fields 1.269 +} 1.270 + 1.271 +/** 1.272 + * Field resolution table that incorporates IS_LEAP_MONTH. 1.273 + */ 1.274 +const UFieldResolutionTable ChineseCalendar::CHINESE_DATE_PRECEDENCE[] = 1.275 +{ 1.276 + { 1.277 + { UCAL_DAY_OF_MONTH, kResolveSTOP }, 1.278 + { UCAL_WEEK_OF_YEAR, UCAL_DAY_OF_WEEK, kResolveSTOP }, 1.279 + { UCAL_WEEK_OF_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP }, 1.280 + { UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP }, 1.281 + { UCAL_WEEK_OF_YEAR, UCAL_DOW_LOCAL, kResolveSTOP }, 1.282 + { UCAL_WEEK_OF_MONTH, UCAL_DOW_LOCAL, kResolveSTOP }, 1.283 + { UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DOW_LOCAL, kResolveSTOP }, 1.284 + { UCAL_DAY_OF_YEAR, kResolveSTOP }, 1.285 + { kResolveRemap | UCAL_DAY_OF_MONTH, UCAL_IS_LEAP_MONTH, kResolveSTOP }, 1.286 + { kResolveSTOP } 1.287 + }, 1.288 + { 1.289 + { UCAL_WEEK_OF_YEAR, kResolveSTOP }, 1.290 + { UCAL_WEEK_OF_MONTH, kResolveSTOP }, 1.291 + { UCAL_DAY_OF_WEEK_IN_MONTH, kResolveSTOP }, 1.292 + { kResolveRemap | UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP }, 1.293 + { kResolveRemap | UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DOW_LOCAL, kResolveSTOP }, 1.294 + { kResolveSTOP } 1.295 + }, 1.296 + {{kResolveSTOP}} 1.297 +}; 1.298 + 1.299 +/** 1.300 + * Override Calendar to add IS_LEAP_MONTH to the field resolution 1.301 + * table. 1.302 + * @stable ICU 2.8 1.303 + */ 1.304 +const UFieldResolutionTable* ChineseCalendar::getFieldResolutionTable() const { 1.305 + return CHINESE_DATE_PRECEDENCE; 1.306 +} 1.307 + 1.308 +/** 1.309 + * Return the Julian day number of day before the first day of the 1.310 + * given month in the given extended year. 1.311 + * 1.312 + * <p>Note: This method reads the IS_LEAP_MONTH field to determine 1.313 + * whether the given month is a leap month. 1.314 + * @param eyear the extended year 1.315 + * @param month the zero-based month. The month is also determined 1.316 + * by reading the IS_LEAP_MONTH field. 1.317 + * @return the Julian day number of the day before the first 1.318 + * day of the given month and year 1.319 + * @stable ICU 2.8 1.320 + */ 1.321 +int32_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool useMonth) const { 1.322 + 1.323 + ChineseCalendar *nonConstThis = (ChineseCalendar*)this; // cast away const 1.324 + 1.325 + // If the month is out of range, adjust it into range, and 1.326 + // modify the extended year value accordingly. 1.327 + if (month < 0 || month > 11) { 1.328 + double m = month; 1.329 + eyear += (int32_t)ClockMath::floorDivide(m, 12.0, m); 1.330 + month = (int32_t)m; 1.331 + } 1.332 + 1.333 + int32_t gyear = eyear + fEpochYear - 1; // Gregorian year 1.334 + int32_t theNewYear = newYear(gyear); 1.335 + int32_t newMoon = newMoonNear(theNewYear + month * 29, TRUE); 1.336 + 1.337 + int32_t julianDay = newMoon + kEpochStartAsJulianDay; 1.338 + 1.339 + // Save fields for later restoration 1.340 + int32_t saveMonth = internalGet(UCAL_MONTH); 1.341 + int32_t saveIsLeapMonth = internalGet(UCAL_IS_LEAP_MONTH); 1.342 + 1.343 + // Ignore IS_LEAP_MONTH field if useMonth is false 1.344 + int32_t isLeapMonth = useMonth ? saveIsLeapMonth : 0; 1.345 + 1.346 + UErrorCode status = U_ZERO_ERROR; 1.347 + nonConstThis->computeGregorianFields(julianDay, status); 1.348 + if (U_FAILURE(status)) 1.349 + return 0; 1.350 + 1.351 + // This will modify the MONTH and IS_LEAP_MONTH fields (only) 1.352 + nonConstThis->computeChineseFields(newMoon, getGregorianYear(), 1.353 + getGregorianMonth(), FALSE); 1.354 + 1.355 + if (month != internalGet(UCAL_MONTH) || 1.356 + isLeapMonth != internalGet(UCAL_IS_LEAP_MONTH)) { 1.357 + newMoon = newMoonNear(newMoon + SYNODIC_GAP, TRUE); 1.358 + julianDay = newMoon + kEpochStartAsJulianDay; 1.359 + } 1.360 + 1.361 + nonConstThis->internalSet(UCAL_MONTH, saveMonth); 1.362 + nonConstThis->internalSet(UCAL_IS_LEAP_MONTH, saveIsLeapMonth); 1.363 + 1.364 + return julianDay - 1; 1.365 +} 1.366 + 1.367 + 1.368 +/** 1.369 + * Override Calendar to handle leap months properly. 1.370 + * @stable ICU 2.8 1.371 + */ 1.372 +void ChineseCalendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status) { 1.373 + switch (field) { 1.374 + case UCAL_MONTH: 1.375 + if (amount != 0) { 1.376 + int32_t dom = get(UCAL_DAY_OF_MONTH, status); 1.377 + if (U_FAILURE(status)) break; 1.378 + int32_t day = get(UCAL_JULIAN_DAY, status) - kEpochStartAsJulianDay; // Get local day 1.379 + if (U_FAILURE(status)) break; 1.380 + int32_t moon = day - dom + 1; // New moon 1.381 + offsetMonth(moon, dom, amount); 1.382 + } 1.383 + break; 1.384 + default: 1.385 + Calendar::add(field, amount, status); 1.386 + break; 1.387 + } 1.388 +} 1.389 + 1.390 +/** 1.391 + * Override Calendar to handle leap months properly. 1.392 + * @stable ICU 2.8 1.393 + */ 1.394 +void ChineseCalendar::add(EDateFields field, int32_t amount, UErrorCode& status) { 1.395 + add((UCalendarDateFields)field, amount, status); 1.396 +} 1.397 + 1.398 +/** 1.399 + * Override Calendar to handle leap months properly. 1.400 + * @stable ICU 2.8 1.401 + */ 1.402 +void ChineseCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& status) { 1.403 + switch (field) { 1.404 + case UCAL_MONTH: 1.405 + if (amount != 0) { 1.406 + int32_t dom = get(UCAL_DAY_OF_MONTH, status); 1.407 + if (U_FAILURE(status)) break; 1.408 + int32_t day = get(UCAL_JULIAN_DAY, status) - kEpochStartAsJulianDay; // Get local day 1.409 + if (U_FAILURE(status)) break; 1.410 + int32_t moon = day - dom + 1; // New moon (start of this month) 1.411 + 1.412 + // Note throughout the following: Months 12 and 1 are never 1.413 + // followed by a leap month (D&R p. 185). 1.414 + 1.415 + // Compute the adjusted month number m. This is zero-based 1.416 + // value from 0..11 in a non-leap year, and from 0..12 in a 1.417 + // leap year. 1.418 + int32_t m = get(UCAL_MONTH, status); // 0-based month 1.419 + if (U_FAILURE(status)) break; 1.420 + if (isLeapYear) { // (member variable) 1.421 + if (get(UCAL_IS_LEAP_MONTH, status) == 1) { 1.422 + ++m; 1.423 + } else { 1.424 + // Check for a prior leap month. (In the 1.425 + // following, month 0 is the first month of the 1.426 + // year.) Month 0 is never followed by a leap 1.427 + // month, and we know month m is not a leap month. 1.428 + // moon1 will be the start of month 0 if there is 1.429 + // no leap month between month 0 and month m; 1.430 + // otherwise it will be the start of month 1. 1.431 + int moon1 = moon - 1.432 + (int) (CalendarAstronomer::SYNODIC_MONTH * (m - 0.5)); 1.433 + moon1 = newMoonNear(moon1, TRUE); 1.434 + if (isLeapMonthBetween(moon1, moon)) { 1.435 + ++m; 1.436 + } 1.437 + } 1.438 + if (U_FAILURE(status)) break; 1.439 + } 1.440 + 1.441 + // Now do the standard roll computation on m, with the 1.442 + // allowed range of 0..n-1, where n is 12 or 13. 1.443 + int32_t n = isLeapYear ? 13 : 12; // Months in this year 1.444 + int32_t newM = (m + amount) % n; 1.445 + if (newM < 0) { 1.446 + newM += n; 1.447 + } 1.448 + 1.449 + if (newM != m) { 1.450 + offsetMonth(moon, dom, newM - m); 1.451 + } 1.452 + } 1.453 + break; 1.454 + default: 1.455 + Calendar::roll(field, amount, status); 1.456 + break; 1.457 + } 1.458 +} 1.459 + 1.460 +void ChineseCalendar::roll(EDateFields field, int32_t amount, UErrorCode& status) { 1.461 + roll((UCalendarDateFields)field, amount, status); 1.462 +} 1.463 + 1.464 + 1.465 +//------------------------------------------------------------------ 1.466 +// Support methods and constants 1.467 +//------------------------------------------------------------------ 1.468 + 1.469 +/** 1.470 + * Convert local days to UTC epoch milliseconds. 1.471 + * This is not an accurate conversion in that getTimezoneOffset 1.472 + * takes the milliseconds in GMT (not local time). In theory, more 1.473 + * accurate algorithm can be implemented but practically we do not need 1.474 + * to go through that complication as long as the historical timezone 1.475 + * changes did not happen around the 'tricky' new moon (new moon around 1.476 + * midnight). 1.477 + * 1.478 + * @param days days after January 1, 1970 0:00 in the astronomical base zone 1.479 + * @return milliseconds after January 1, 1970 0:00 GMT 1.480 + */ 1.481 +double ChineseCalendar::daysToMillis(double days) const { 1.482 + double millis = days * (double)kOneDay; 1.483 + if (fZoneAstroCalc != NULL) { 1.484 + int32_t rawOffset, dstOffset; 1.485 + UErrorCode status = U_ZERO_ERROR; 1.486 + fZoneAstroCalc->getOffset(millis, FALSE, rawOffset, dstOffset, status); 1.487 + if (U_SUCCESS(status)) { 1.488 + return millis - (double)(rawOffset + dstOffset); 1.489 + } 1.490 + } 1.491 + return millis - (double)CHINA_OFFSET; 1.492 +} 1.493 + 1.494 +/** 1.495 + * Convert UTC epoch milliseconds to local days. 1.496 + * @param millis milliseconds after January 1, 1970 0:00 GMT 1.497 + * @return days after January 1, 1970 0:00 in the astronomical base zone 1.498 + */ 1.499 +double ChineseCalendar::millisToDays(double millis) const { 1.500 + if (fZoneAstroCalc != NULL) { 1.501 + int32_t rawOffset, dstOffset; 1.502 + UErrorCode status = U_ZERO_ERROR; 1.503 + fZoneAstroCalc->getOffset(millis, FALSE, rawOffset, dstOffset, status); 1.504 + if (U_SUCCESS(status)) { 1.505 + return ClockMath::floorDivide(millis + (double)(rawOffset + dstOffset), kOneDay); 1.506 + } 1.507 + } 1.508 + return ClockMath::floorDivide(millis + (double)CHINA_OFFSET, kOneDay); 1.509 +} 1.510 + 1.511 +//------------------------------------------------------------------ 1.512 +// Astronomical computations 1.513 +//------------------------------------------------------------------ 1.514 + 1.515 + 1.516 +/** 1.517 + * Return the major solar term on or after December 15 of the given 1.518 + * Gregorian year, that is, the winter solstice of the given year. 1.519 + * Computations are relative to Asia/Shanghai time zone. 1.520 + * @param gyear a Gregorian year 1.521 + * @return days after January 1, 1970 0:00 Asia/Shanghai of the 1.522 + * winter solstice of the given year 1.523 + */ 1.524 +int32_t ChineseCalendar::winterSolstice(int32_t gyear) const { 1.525 + 1.526 + UErrorCode status = U_ZERO_ERROR; 1.527 + int32_t cacheValue = CalendarCache::get(&gChineseCalendarWinterSolsticeCache, gyear, status); 1.528 + 1.529 + if (cacheValue == 0) { 1.530 + // In books December 15 is used, but it fails for some years 1.531 + // using our algorithms, e.g.: 1298 1391 1492 1553 1560. That 1.532 + // is, winterSolstice(1298) starts search at Dec 14 08:00:00 1.533 + // PST 1298 with a final result of Dec 14 10:31:59 PST 1299. 1.534 + double ms = daysToMillis(Grego::fieldsToDay(gyear, UCAL_DECEMBER, 1)); 1.535 + 1.536 + umtx_lock(&astroLock); 1.537 + if(gChineseCalendarAstro == NULL) { 1.538 + gChineseCalendarAstro = new CalendarAstronomer(); 1.539 + ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); 1.540 + } 1.541 + gChineseCalendarAstro->setTime(ms); 1.542 + UDate solarLong = gChineseCalendarAstro->getSunTime(CalendarAstronomer::WINTER_SOLSTICE(), TRUE); 1.543 + umtx_unlock(&astroLock); 1.544 + 1.545 + // Winter solstice is 270 degrees solar longitude aka Dongzhi 1.546 + cacheValue = (int32_t)millisToDays(solarLong); 1.547 + CalendarCache::put(&gChineseCalendarWinterSolsticeCache, gyear, cacheValue, status); 1.548 + } 1.549 + if(U_FAILURE(status)) { 1.550 + cacheValue = 0; 1.551 + } 1.552 + return cacheValue; 1.553 +} 1.554 + 1.555 +/** 1.556 + * Return the closest new moon to the given date, searching either 1.557 + * forward or backward in time. 1.558 + * @param days days after January 1, 1970 0:00 Asia/Shanghai 1.559 + * @param after if true, search for a new moon on or after the given 1.560 + * date; otherwise, search for a new moon before it 1.561 + * @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest 1.562 + * new moon after or before <code>days</code> 1.563 + */ 1.564 +int32_t ChineseCalendar::newMoonNear(double days, UBool after) const { 1.565 + 1.566 + umtx_lock(&astroLock); 1.567 + if(gChineseCalendarAstro == NULL) { 1.568 + gChineseCalendarAstro = new CalendarAstronomer(); 1.569 + ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); 1.570 + } 1.571 + gChineseCalendarAstro->setTime(daysToMillis(days)); 1.572 + UDate newMoon = gChineseCalendarAstro->getMoonTime(CalendarAstronomer::NEW_MOON(), after); 1.573 + umtx_unlock(&astroLock); 1.574 + 1.575 + return (int32_t) millisToDays(newMoon); 1.576 +} 1.577 + 1.578 +/** 1.579 + * Return the nearest integer number of synodic months between 1.580 + * two dates. 1.581 + * @param day1 days after January 1, 1970 0:00 Asia/Shanghai 1.582 + * @param day2 days after January 1, 1970 0:00 Asia/Shanghai 1.583 + * @return the nearest integer number of months between day1 and day2 1.584 + */ 1.585 +int32_t ChineseCalendar::synodicMonthsBetween(int32_t day1, int32_t day2) const { 1.586 + double roundme = ((day2 - day1) / CalendarAstronomer::SYNODIC_MONTH); 1.587 + return (int32_t) (roundme + (roundme >= 0 ? .5 : -.5)); 1.588 +} 1.589 + 1.590 +/** 1.591 + * Return the major solar term on or before a given date. This 1.592 + * will be an integer from 1..12, with 1 corresponding to 330 degrees, 1.593 + * 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees. 1.594 + * @param days days after January 1, 1970 0:00 Asia/Shanghai 1.595 + */ 1.596 +int32_t ChineseCalendar::majorSolarTerm(int32_t days) const { 1.597 + 1.598 + umtx_lock(&astroLock); 1.599 + if(gChineseCalendarAstro == NULL) { 1.600 + gChineseCalendarAstro = new CalendarAstronomer(); 1.601 + ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); 1.602 + } 1.603 + gChineseCalendarAstro->setTime(daysToMillis(days)); 1.604 + UDate solarLongitude = gChineseCalendarAstro->getSunLongitude(); 1.605 + umtx_unlock(&astroLock); 1.606 + 1.607 + // Compute (floor(solarLongitude / (pi/6)) + 2) % 12 1.608 + int32_t term = ( ((int32_t)(6 * solarLongitude / CalendarAstronomer::PI)) + 2 ) % 12; 1.609 + if (term < 1) { 1.610 + term += 12; 1.611 + } 1.612 + return term; 1.613 +} 1.614 + 1.615 +/** 1.616 + * Return true if the given month lacks a major solar term. 1.617 + * @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new 1.618 + * moon 1.619 + */ 1.620 +UBool ChineseCalendar::hasNoMajorSolarTerm(int32_t newMoon) const { 1.621 + return majorSolarTerm(newMoon) == 1.622 + majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, TRUE)); 1.623 +} 1.624 + 1.625 + 1.626 +//------------------------------------------------------------------ 1.627 +// Time to fields 1.628 +//------------------------------------------------------------------ 1.629 + 1.630 +/** 1.631 + * Return true if there is a leap month on or after month newMoon1 and 1.632 + * at or before month newMoon2. 1.633 + * @param newMoon1 days after January 1, 1970 0:00 astronomical base zone 1.634 + * of a new moon 1.635 + * @param newMoon2 days after January 1, 1970 0:00 astronomical base zone 1.636 + * of a new moon 1.637 + */ 1.638 +UBool ChineseCalendar::isLeapMonthBetween(int32_t newMoon1, int32_t newMoon2) const { 1.639 + 1.640 +#ifdef U_DEBUG_CHNSECAL 1.641 + // This is only needed to debug the timeOfAngle divergence bug. 1.642 + // Remove this later. Liu 11/9/00 1.643 + if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) { 1.644 + U_DEBUG_CHNSECAL_MSG(( 1.645 + "isLeapMonthBetween(%d, %d): Invalid parameters", newMoon1, newMoon2 1.646 + )); 1.647 + } 1.648 +#endif 1.649 + 1.650 + return (newMoon2 >= newMoon1) && 1.651 + (isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, FALSE)) || 1.652 + hasNoMajorSolarTerm(newMoon2)); 1.653 +} 1.654 + 1.655 +/** 1.656 + * Compute fields for the Chinese calendar system. This method can 1.657 + * either set all relevant fields, as required by 1.658 + * <code>handleComputeFields()</code>, or it can just set the MONTH and 1.659 + * IS_LEAP_MONTH fields, as required by 1.660 + * <code>handleComputeMonthStart()</code>. 1.661 + * 1.662 + * <p>As a side effect, this method sets {@link #isLeapYear}. 1.663 + * @param days days after January 1, 1970 0:00 astronomical base zone 1.664 + * of the date to compute fields for 1.665 + * @param gyear the Gregorian year of the given date 1.666 + * @param gmonth the Gregorian month of the given date 1.667 + * @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR, 1.668 + * DAY_OF_MONTH, and DAY_OF_YEAR fields. In either case set the MONTH 1.669 + * and IS_LEAP_MONTH fields. 1.670 + */ 1.671 +void ChineseCalendar::computeChineseFields(int32_t days, int32_t gyear, int32_t gmonth, 1.672 + UBool setAllFields) { 1.673 + 1.674 + // Find the winter solstices before and after the target date. 1.675 + // These define the boundaries of this Chinese year, specifically, 1.676 + // the position of month 11, which always contains the solstice. 1.677 + // We want solsticeBefore <= date < solsticeAfter. 1.678 + int32_t solsticeBefore; 1.679 + int32_t solsticeAfter = winterSolstice(gyear); 1.680 + if (days < solsticeAfter) { 1.681 + solsticeBefore = winterSolstice(gyear - 1); 1.682 + } else { 1.683 + solsticeBefore = solsticeAfter; 1.684 + solsticeAfter = winterSolstice(gyear + 1); 1.685 + } 1.686 + 1.687 + // Find the start of the month after month 11. This will be either 1.688 + // the prior month 12 or leap month 11 (very rare). Also find the 1.689 + // start of the following month 11. 1.690 + int32_t firstMoon = newMoonNear(solsticeBefore + 1, TRUE); 1.691 + int32_t lastMoon = newMoonNear(solsticeAfter + 1, FALSE); 1.692 + int32_t thisMoon = newMoonNear(days + 1, FALSE); // Start of this month 1.693 + // Note: isLeapYear is a member variable 1.694 + isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12; 1.695 + 1.696 + int32_t month = synodicMonthsBetween(firstMoon, thisMoon); 1.697 + if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) { 1.698 + month--; 1.699 + } 1.700 + if (month < 1) { 1.701 + month += 12; 1.702 + } 1.703 + 1.704 + UBool isLeapMonth = isLeapYear && 1.705 + hasNoMajorSolarTerm(thisMoon) && 1.706 + !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, FALSE)); 1.707 + 1.708 + internalSet(UCAL_MONTH, month-1); // Convert from 1-based to 0-based 1.709 + internalSet(UCAL_IS_LEAP_MONTH, isLeapMonth?1:0); 1.710 + 1.711 + if (setAllFields) { 1.712 + 1.713 + // Extended year and cycle year is based on the epoch year 1.714 + 1.715 + int32_t extended_year = gyear - fEpochYear; 1.716 + int cycle_year = gyear - CHINESE_EPOCH_YEAR; 1.717 + if (month < 11 || 1.718 + gmonth >= UCAL_JULY) { 1.719 + extended_year++; 1.720 + cycle_year++; 1.721 + } 1.722 + int32_t dayOfMonth = days - thisMoon + 1; 1.723 + 1.724 + internalSet(UCAL_EXTENDED_YEAR, extended_year); 1.725 + 1.726 + // 0->0,60 1->1,1 60->1,60 61->2,1 etc. 1.727 + int32_t yearOfCycle; 1.728 + int32_t cycle = ClockMath::floorDivide(cycle_year - 1, 60, yearOfCycle); 1.729 + internalSet(UCAL_ERA, cycle + 1); 1.730 + internalSet(UCAL_YEAR, yearOfCycle + 1); 1.731 + 1.732 + internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); 1.733 + 1.734 + // Days will be before the first new year we compute if this 1.735 + // date is in month 11, leap 11, 12. There is never a leap 12. 1.736 + // New year computations are cached so this should be cheap in 1.737 + // the long run. 1.738 + int32_t theNewYear = newYear(gyear); 1.739 + if (days < theNewYear) { 1.740 + theNewYear = newYear(gyear-1); 1.741 + } 1.742 + internalSet(UCAL_DAY_OF_YEAR, days - theNewYear + 1); 1.743 + } 1.744 +} 1.745 + 1.746 + 1.747 +//------------------------------------------------------------------ 1.748 +// Fields to time 1.749 +//------------------------------------------------------------------ 1.750 + 1.751 +/** 1.752 + * Return the Chinese new year of the given Gregorian year. 1.753 + * @param gyear a Gregorian year 1.754 + * @return days after January 1, 1970 0:00 astronomical base zone of the 1.755 + * Chinese new year of the given year (this will be a new moon) 1.756 + */ 1.757 +int32_t ChineseCalendar::newYear(int32_t gyear) const { 1.758 + UErrorCode status = U_ZERO_ERROR; 1.759 + int32_t cacheValue = CalendarCache::get(&gChineseCalendarNewYearCache, gyear, status); 1.760 + 1.761 + if (cacheValue == 0) { 1.762 + 1.763 + int32_t solsticeBefore= winterSolstice(gyear - 1); 1.764 + int32_t solsticeAfter = winterSolstice(gyear); 1.765 + int32_t newMoon1 = newMoonNear(solsticeBefore + 1, TRUE); 1.766 + int32_t newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, TRUE); 1.767 + int32_t newMoon11 = newMoonNear(solsticeAfter + 1, FALSE); 1.768 + 1.769 + if (synodicMonthsBetween(newMoon1, newMoon11) == 12 && 1.770 + (hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) { 1.771 + cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, TRUE); 1.772 + } else { 1.773 + cacheValue = newMoon2; 1.774 + } 1.775 + 1.776 + CalendarCache::put(&gChineseCalendarNewYearCache, gyear, cacheValue, status); 1.777 + } 1.778 + if(U_FAILURE(status)) { 1.779 + cacheValue = 0; 1.780 + } 1.781 + return cacheValue; 1.782 +} 1.783 + 1.784 +/** 1.785 + * Adjust this calendar to be delta months before or after a given 1.786 + * start position, pinning the day of month if necessary. The start 1.787 + * position is given as a local days number for the start of the month 1.788 + * and a day-of-month. Used by add() and roll(). 1.789 + * @param newMoon the local days of the first day of the month of the 1.790 + * start position (days after January 1, 1970 0:00 Asia/Shanghai) 1.791 + * @param dom the 1-based day-of-month of the start position 1.792 + * @param delta the number of months to move forward or backward from 1.793 + * the start position 1.794 + */ 1.795 +void ChineseCalendar::offsetMonth(int32_t newMoon, int32_t dom, int32_t delta) { 1.796 + UErrorCode status = U_ZERO_ERROR; 1.797 + 1.798 + // Move to the middle of the month before our target month. 1.799 + newMoon += (int32_t) (CalendarAstronomer::SYNODIC_MONTH * (delta - 0.5)); 1.800 + 1.801 + // Search forward to the target month's new moon 1.802 + newMoon = newMoonNear(newMoon, TRUE); 1.803 + 1.804 + // Find the target dom 1.805 + int32_t jd = newMoon + kEpochStartAsJulianDay - 1 + dom; 1.806 + 1.807 + // Pin the dom. In this calendar all months are 29 or 30 days 1.808 + // so pinning just means handling dom 30. 1.809 + if (dom > 29) { 1.810 + set(UCAL_JULIAN_DAY, jd-1); 1.811 + // TODO Fix this. We really shouldn't ever have to 1.812 + // explicitly call complete(). This is either a bug in 1.813 + // this method, in ChineseCalendar, or in 1.814 + // Calendar.getActualMaximum(). I suspect the last. 1.815 + complete(status); 1.816 + if (U_FAILURE(status)) return; 1.817 + if (getActualMaximum(UCAL_DAY_OF_MONTH, status) >= dom) { 1.818 + if (U_FAILURE(status)) return; 1.819 + set(UCAL_JULIAN_DAY, jd); 1.820 + } 1.821 + } else { 1.822 + set(UCAL_JULIAN_DAY, jd); 1.823 + } 1.824 +} 1.825 + 1.826 + 1.827 +UBool 1.828 +ChineseCalendar::inDaylightTime(UErrorCode& status) const 1.829 +{ 1.830 + // copied from GregorianCalendar 1.831 + if (U_FAILURE(status) || !getTimeZone().useDaylightTime()) 1.832 + return FALSE; 1.833 + 1.834 + // Force an update of the state of the Calendar. 1.835 + ((ChineseCalendar*)this)->complete(status); // cast away const 1.836 + 1.837 + return (UBool)(U_SUCCESS(status) ? (internalGet(UCAL_DST_OFFSET) != 0) : FALSE); 1.838 +} 1.839 + 1.840 +// default century 1.841 + 1.842 +static UDate gSystemDefaultCenturyStart = DBL_MIN; 1.843 +static int32_t gSystemDefaultCenturyStartYear = -1; 1.844 +static icu::UInitOnce gSystemDefaultCenturyInitOnce = U_INITONCE_INITIALIZER; 1.845 + 1.846 + 1.847 +UBool ChineseCalendar::haveDefaultCentury() const 1.848 +{ 1.849 + return TRUE; 1.850 +} 1.851 + 1.852 +UDate ChineseCalendar::defaultCenturyStart() const 1.853 +{ 1.854 + return internalGetDefaultCenturyStart(); 1.855 +} 1.856 + 1.857 +int32_t ChineseCalendar::defaultCenturyStartYear() const 1.858 +{ 1.859 + return internalGetDefaultCenturyStartYear(); 1.860 +} 1.861 + 1.862 +static void U_CALLCONV initializeSystemDefaultCentury() 1.863 +{ 1.864 + // initialize systemDefaultCentury and systemDefaultCenturyYear based 1.865 + // on the current time. They'll be set to 80 years before 1.866 + // the current time. 1.867 + UErrorCode status = U_ZERO_ERROR; 1.868 + ChineseCalendar calendar(Locale("@calendar=chinese"),status); 1.869 + if (U_SUCCESS(status)) { 1.870 + calendar.setTime(Calendar::getNow(), status); 1.871 + calendar.add(UCAL_YEAR, -80, status); 1.872 + gSystemDefaultCenturyStart = calendar.getTime(status); 1.873 + gSystemDefaultCenturyStartYear = calendar.get(UCAL_YEAR, status); 1.874 + } 1.875 + // We have no recourse upon failure unless we want to propagate the failure 1.876 + // out. 1.877 +} 1.878 + 1.879 +UDate 1.880 +ChineseCalendar::internalGetDefaultCenturyStart() const 1.881 +{ 1.882 + // lazy-evaluate systemDefaultCenturyStart 1.883 + umtx_initOnce(gSystemDefaultCenturyInitOnce, &initializeSystemDefaultCentury); 1.884 + return gSystemDefaultCenturyStart; 1.885 +} 1.886 + 1.887 +int32_t 1.888 +ChineseCalendar::internalGetDefaultCenturyStartYear() const 1.889 +{ 1.890 + // lazy-evaluate systemDefaultCenturyStartYear 1.891 + umtx_initOnce(gSystemDefaultCenturyInitOnce, &initializeSystemDefaultCentury); 1.892 + return gSystemDefaultCenturyStartYear; 1.893 +} 1.894 + 1.895 +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ChineseCalendar) 1.896 + 1.897 +U_NAMESPACE_END 1.898 + 1.899 +#endif 1.900 +