/** * The start year of the Chinese calendar, the 61st year of the reign * of Huang Di. Some sources use the first year of his reign, * resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle) * values one greater.
*/ staticconst int32_t CHINESE_EPOCH_YEAR = -2636; // Gregorian year
/** * The offset from GMT in milliseconds at which we perform astronomical * computations. Some sources use a different historically accurate * offset of GMT+7:45:40 for years before 1929; we do not do this.
*/ staticconst int32_t CHINA_OFFSET = 8 * kOneHour;
/** * Value to be added or subtracted from the local days of a new moon to * get close to the next or prior new moon, but not cross it. Must be * >= 1 and < CalendarAstronomer.SYNODIC_MONTH.
*/ staticconst int32_t SYNODIC_GAP = 25;
ChineseCalendar::ChineseCalendar(const Locale& aLocale, UErrorCode& success)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success),
hasLeapMonthBetweenWinterSolstices(false)
{
setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly.
}
/** * Implement abstract Calendar method to return the extended year * defined by the current fields. This will use either the ERA and * YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR * field as the continuous year count, depending on which is newer. * @stable ICU 2.8
*/
int32_t ChineseCalendar::handleGetExtendedYear(UErrorCode& status) { if (U_FAILURE(status)) { return 0;
}
int32_t year; if (newestStamp(UCAL_ERA, UCAL_YEAR, kUnset) <= fStamp[UCAL_EXTENDED_YEAR]) {
year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1
} else { // adjust to the instance specific epoch
int32_t cycle = internalGet(UCAL_ERA, 1);
year = internalGet(UCAL_YEAR, 1); const Setting setting = getSetting(status); if (U_FAILURE(status)) { return 0;
} // Handle int32 overflow calculation for // year = year + (cycle-1) * 60 -(fEpochYear - CHINESE_EPOCH_YEAR) if (uprv_add32_overflow(cycle, -1, &cycle) || // 0-based cycle
uprv_mul32_overflow(cycle, 60, &cycle) ||
uprv_add32_overflow(year, cycle, &year) ||
uprv_add32_overflow(year, -(setting.epochYear-CHINESE_EPOCH_YEAR),
&year)) {
status = U_ILLEGAL_ARGUMENT_ERROR; return 0;
}
} return year;
}
/** * Override Calendar method to return the number of days in the given * extended year and month. * * <p>Note: This method also reads the IS_LEAP_MONTH field to determine * whether or not the given month is a leap month. * @stable ICU 2.8
*/
int32_t ChineseCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month, UErrorCode& status) const { const Setting setting = getSetting(status); if (U_FAILURE(status)) { return 0;
}
int32_t thisStart = handleComputeMonthStart(extendedYear, month, true, status); if (U_FAILURE(status)) { return 0;
}
thisStart = thisStart -
kEpochStartAsJulianDay + 1; // Julian day -> local days
int32_t nextStart = newMoonNear(setting.zoneAstroCalc, thisStart + SYNODIC_GAP, true, status); return nextStart - thisStart;
}
/** * Return the Julian day number of day before the first day of the * given month in the given extended year. * * <p>Note: This method reads the IS_LEAP_MONTH field to determine * whether the given month is a leap month. * @param eyear the extended year * @param month the zero-based month. The month is also determined * by reading the IS_LEAP_MONTH field. * @return the Julian day number of the day before the first * day of the given month and year * @stable ICU 2.8
*/
int64_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool useMonth, UErrorCode& status) const { if (U_FAILURE(status)) { return 0;
} // If the month is out of range, adjust it into range, and // modify the extended year value accordingly. if (month < 0 || month > 11) { double m = month; if (uprv_add32_overflow(eyear, ClockMath::floorDivide(m, 12.0, &m), &eyear)) {
status = U_ILLEGAL_ARGUMENT_ERROR; return 0;
}
month = static_cast<int32_t>(m);
}
const Setting setting = getSetting(status); if (U_FAILURE(status)) { return 0;
}
int32_t gyear; if (uprv_add32_overflow(eyear, setting.epochYear - 1, &gyear)) {
status = U_ILLEGAL_ARGUMENT_ERROR; return 0;
}
output.thisMoon = day - dayOfMonth + 1; // New moon (start of this month)
// Note throughout the following: Months 12 and 1 are never // followed by a leap month (D&R p. 185).
// Compute the adjusted month number m. This is zero-based // value from 0..11 in a non-leap year, and from 0..12 in a // leap year. if (hasLeapMonthBetweenWinterSolstices) { // (member variable) if (isLeapMonth) {
++month;
} else { // Check for a prior leap month. (In the // following, month 0 is the first month of the // year.) Month 0 is never followed by a leap // month, and we know month m is not a leap month. // moon1 will be the start of month 0 if there is // no leap month between month 0 and month m; // otherwise it will be the start of month 1. int prevMoon = output.thisMoon - static_cast<int>(CalendarAstronomer::SYNODIC_MONTH * (month - 0.5));
prevMoon = newMoonNear(timeZone, prevMoon, true, status); if (U_FAILURE(status)) { return output;
} if (isLeapMonthBetween(timeZone, prevMoon, output.thisMoon, status)) {
++month;
} if (U_FAILURE(status)) { return output;
}
}
} // Now do the standard roll computation on month, with the // allowed range of 0..n-1, where n is 12 or 13.
int32_t numberOfMonths = hasLeapMonthBetweenWinterSolstices ? 13 : 12; // Months in this year if (uprv_add32_overflow(amount, month, &amount)) {
status = U_ILLEGAL_ARGUMENT_ERROR; return output;
}
output.newMoon = amount % numberOfMonths; if (output.newMoon < 0) {
output.newMoon += numberOfMonths;
}
output.month = month; return output;
}
} // namespace
/** * Override Calendar to handle leap months properly. * @stable ICU 2.8
*/ void ChineseCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& status) { switch (field) { case UCAL_MONTH: case UCAL_ORDINAL_MONTH: if (amount != 0) { const Setting setting = getSetting(status);
int32_t day = get(UCAL_JULIAN_DAY, status) - kEpochStartAsJulianDay; // Get local day
int32_t month = get(UCAL_MONTH, status); // 0-based month
int32_t dayOfMonth = get(UCAL_DAY_OF_MONTH, status); bool isLeapMonth = get(UCAL_IS_LEAP_MONTH, status) == 1; if (U_FAILURE(status)) break; struct RollMonthInfo r = rollMonth(
setting.zoneAstroCalc, amount, day, month, dayOfMonth, isLeapMonth,
hasLeapMonthBetweenWinterSolstices, status); if (U_FAILURE(status)) break; if (r.newMoon != r.month) {
offsetMonth(r.thisMoon, dayOfMonth, r.newMoon - r.month, status);
}
} break; default:
Calendar::roll(field, amount, status); break;
}
}
//------------------------------------------------------------------ // Support methods and constants //------------------------------------------------------------------
namespace { /** * Convert local days to UTC epoch milliseconds. * This is not an accurate conversion in that getTimezoneOffset * takes the milliseconds in GMT (not local time). In theory, more * accurate algorithm can be implemented but practically we do not need * to go through that complication as long as the historical timezone * changes did not happen around the 'tricky' new moon (new moon around * midnight). * * @param timeZone time zone for the Astro calculation. * @param days days after January 1, 1970 0:00 in the astronomical base zone * @return milliseconds after January 1, 1970 0:00 GMT
*/ double daysToMillis(const TimeZone* timeZone, double days, UErrorCode& status) { if (U_FAILURE(status)) { return 0;
} double millis = days * kOneDay; if (timeZone != nullptr) {
int32_t rawOffset, dstOffset;
timeZone->getOffset(millis, false, rawOffset, dstOffset, status); if (U_FAILURE(status)) { return 0;
} return millis - static_cast<double>(rawOffset + dstOffset);
} return millis - static_cast<double>(CHINA_OFFSET);
}
/** * Convert UTC epoch milliseconds to local days. * @param timeZone time zone for the Astro calculation. * @param millis milliseconds after January 1, 1970 0:00 GMT * @return days after January 1, 1970 0:00 in the astronomical base zone
*/ double millisToDays(const TimeZone* timeZone, double millis, UErrorCode& status) { if (U_FAILURE(status)) { return 0;
} if (timeZone != nullptr) {
int32_t rawOffset, dstOffset;
timeZone->getOffset(millis, false, rawOffset, dstOffset, status); if (U_FAILURE(status)) { return 0;
} return ClockMath::floorDivide(millis + static_cast<double>(rawOffset + dstOffset), kOneDay);
} return ClockMath::floorDivide(millis + static_cast<double>(CHINA_OFFSET), kOneDay);
}
/** * Return the major solar term on or after December 15 of the given * Gregorian year, that is, the winter solstice of the given year. * Computations are relative to Asia/Shanghai time zone. * @param setting setting (time zone and caches) for the Astro calculation. * @param gyear a Gregorian year * @return days after January 1, 1970 0:00 Asia/Shanghai of the * winter solstice of the given year
*/
int32_t winterSolstice(const icu::ChineseCalendar::Setting& setting,
int32_t gyear, UErrorCode& status) { if (U_FAILURE(status)) { return 0;
} const TimeZone* timeZone = setting.zoneAstroCalc;
if (cacheValue == 0) { // In books December 15 is used, but it fails for some years // using our algorithms, e.g.: 1298 1391 1492 1553 1560. That // is, winterSolstice(1298) starts search at Dec 14 08:00:00 // PST 1298 with a final result of Dec 14 10:31:59 PST 1299. double ms = daysToMillis(timeZone, Grego::fieldsToDay(gyear, UCAL_DECEMBER, 1), status); if (U_FAILURE(status)) { return 0;
}
// Winter solstice is 270 degrees solar longitude aka Dongzhi double days = millisToDays(timeZone,
CalendarAstronomer(ms)
.getSunTime(CalendarAstronomer::WINTER_SOLSTICE(), true),
status); if (U_FAILURE(status)) { return 0;
} if (days < INT32_MIN || days > INT32_MAX) {
status = U_ILLEGAL_ARGUMENT_ERROR; return 0;
}
cacheValue = static_cast<int32_t>(days);
CalendarCache::put(setting.winterSolsticeCache, gyear, cacheValue, status);
} if(U_FAILURE(status)) {
cacheValue = 0;
} return cacheValue;
}
/** * Return the closest new moon to the given date, searching either * forward or backward in time. * @param timeZone time zone for the Astro calculation. * @param days days after January 1, 1970 0:00 Asia/Shanghai * @param after if true, search for a new moon on or after the given * date; otherwise, search for a new moon before it * @param status * @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest * new moon after or before <code>days</code>
*/
int32_t newMoonNear(const TimeZone* timeZone, double days, UBool after, UErrorCode& status) { if (U_FAILURE(status)) { return 0;
} double ms = daysToMillis(timeZone, days, status); if (U_FAILURE(status)) { return 0;
} returnstatic_cast<int32_t>(millisToDays(
timeZone,
CalendarAstronomer(ms)
.getMoonTime(CalendarAstronomer::NEW_MOON(), after),
status));
}
/** * Return the nearest integer number of synodic months between * two dates. * @param day1 days after January 1, 1970 0:00 Asia/Shanghai * @param day2 days after January 1, 1970 0:00 Asia/Shanghai * @return the nearest integer number of months between day1 and day2
*/
int32_t synodicMonthsBetween(int32_t day1, int32_t day2) { double roundme = ((day2 - day1) / CalendarAstronomer::SYNODIC_MONTH); returnstatic_cast<int32_t>(roundme + (roundme >= 0 ? .5 : -.5));
}
/** * Return the major solar term on or before a given date. This * will be an integer from 1..12, with 1 corresponding to 330 degrees, * 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees. * @param timeZone time zone for the Astro calculation. * @param days days after January 1, 1970 0:00 Asia/Shanghai
*/
int32_t majorSolarTerm(const TimeZone* timeZone, int32_t days, UErrorCode& status) { if (U_FAILURE(status)) { return 0;
} // Compute (floor(solarLongitude / (pi/6)) + 2) % 12 double ms = daysToMillis(timeZone, days, status); if (U_FAILURE(status)) { return 0;
}
int32_t term = ((static_cast<int32_t>(6 * CalendarAstronomer(ms)
.getSunLongitude() / CalendarAstronomer::PI)) + 2 ) % 12; if (U_FAILURE(status)) { return 0;
} if (term < 1) {
term += 12;
} return term;
}
/** * Return true if the given month lacks a major solar term. * @param timeZone time zone for the Astro calculation. * @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new * moon
*/
UBool hasNoMajorSolarTerm(const TimeZone* timeZone, int32_t newMoon, UErrorCode& status) { if (U_FAILURE(status)) { returnfalse;
}
int32_t term1 = majorSolarTerm(timeZone, newMoon, status);
int32_t term2 = majorSolarTerm(
timeZone, newMoonNear(timeZone, newMoon + SYNODIC_GAP, true, status), status); if (U_FAILURE(status)) { returnfalse;
} return term1 == term2;
}
//------------------------------------------------------------------ // Time to fields //------------------------------------------------------------------
/** * Return true if there is a leap month on or after month newMoon1 and * at or before month newMoon2. * @param timeZone time zone for the Astro calculation. * @param newMoon1 days after January 1, 1970 0:00 astronomical base zone * of a new moon * @param newMoon2 days after January 1, 1970 0:00 astronomical base zone * of a new moon
*/
UBool isLeapMonthBetween(const TimeZone* timeZone, int32_t newMoon1, int32_t newMoon2, UErrorCode& status) { if (U_FAILURE(status)) { returnfalse;
}
#ifdef U_DEBUG_CHNSECAL // This is only needed to debug the timeOfAngle divergence bug. // Remove this later. Liu 11/9/00 if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) {
U_DEBUG_CHNSECAL_MSG(( "isLeapMonthBetween(%d, %d): Invalid parameters", newMoon1, newMoon2
));
} #endif
while (newMoon2 >= newMoon1) { if (hasNoMajorSolarTerm(timeZone, newMoon2, status)) { returntrue;
}
newMoon2 = newMoonNear(timeZone, newMoon2 - SYNODIC_GAP, false, status); if (U_FAILURE(status)) { returnfalse;
}
} returnfalse;
}
/** * Compute the information about the year. * @param setting setting (time zone and caches) for the Astro calculation. * @param gyear the Gregorian year of the given date * @param days days after January 1, 1970 0:00 astronomical base zone * of the date to compute fields for * @return The MonthInfo result.
*/ struct MonthInfo computeMonthInfo( const icu::ChineseCalendar::Setting& setting,
int32_t gyear, int32_t days, UErrorCode& status) { struct MonthInfo output = {0, 0, 0, false, false}; if (U_FAILURE(status)) { return output;
} // Find the winter solstices before and after the target date. // These define the boundaries of this Chinese year, specifically, // the position of month 11, which always contains the solstice. // We want solsticeBefore <= date < solsticeAfter.
int32_t solsticeBefore;
int32_t solsticeAfter = winterSolstice(setting, gyear, status); if (U_FAILURE(status)) { return output;
} if (days < solsticeAfter) {
solsticeBefore = winterSolstice(setting, gyear - 1, status);
} else {
solsticeBefore = solsticeAfter;
solsticeAfter = winterSolstice(setting, gyear + 1, status);
} if (U_FAILURE(status)) { return output;
}
const TimeZone* timeZone = setting.zoneAstroCalc; // Find the start of the month after month 11. This will be either // the prior month 12 or leap month 11 (very rare). Also find the // start of the following month 11.
int32_t firstMoon = newMoonNear(timeZone, solsticeBefore + 1, true, status);
int32_t lastMoon = newMoonNear(timeZone, solsticeAfter + 1, false, status); if (U_FAILURE(status)) { return output;
}
output.thisMoon = newMoonNear(timeZone, days + 1, false, status); // Start of this month if (U_FAILURE(status)) { return output;
}
output.hasLeapMonthBetweenWinterSolstices = synodicMonthsBetween(firstMoon, lastMoon) == 12;
/** * Override Calendar to compute several fields specific to the Chinese * calendar system. These are: * * <ul><li>ERA * <li>YEAR * <li>MONTH * <li>DAY_OF_MONTH * <li>DAY_OF_YEAR * <li>EXTENDED_YEAR</ul> * * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this * method is called. The getGregorianXxx() methods return Gregorian * calendar equivalents for the given Julian day. * * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH. * @stable ICU 2.8
*/ void ChineseCalendar::handleComputeFields(int32_t julianDay, UErrorCode & status) { if (U_FAILURE(status)) { return;
}
int32_t days; if (uprv_add32_overflow(julianDay, -kEpochStartAsJulianDay, &days)) {
status = U_ILLEGAL_ARGUMENT_ERROR; return;
}
int32_t gyear = getGregorianYear();
int32_t gmonth = getGregorianMonth();
// Extended year and cycle year is based on the epoch year
int32_t eyear = gyear - setting.epochYear;
int32_t cycle_year = gyear - CHINESE_EPOCH_YEAR; if (monthInfo.month < 11 ||
gmonth >= UCAL_JULY) {
eyear++;
cycle_year++;
}
int32_t dayOfMonth = days - monthInfo.thisMoon + 1;
// Days will be before the first new year we compute if this // date is in month 11, leap 11, 12. There is never a leap 12. // New year computations are cached so this should be cheap in // the long run.
int32_t theNewYear = newYear(setting, gyear, status); if (U_FAILURE(status)) { return;
} if (days < theNewYear) {
theNewYear = newYear(setting, gyear-1, status);
} if (U_FAILURE(status)) { return;
}
cycle++;
yearOfCycle++;
int32_t dayOfYear = days - theNewYear + 1;
int32_t minYear = this->handleGetLimit(UCAL_EXTENDED_YEAR, UCAL_LIMIT_MINIMUM); if (eyear < minYear) { if (!isLenient()) {
status = U_ILLEGAL_ARGUMENT_ERROR; return;
}
eyear = minYear;
}
int32_t maxYear = this->handleGetLimit(UCAL_EXTENDED_YEAR, UCAL_LIMIT_MAXIMUM); if (maxYear < eyear) { if (!isLenient()) {
status = U_ILLEGAL_ARGUMENT_ERROR; return;
}
eyear = maxYear;
}
internalSet(UCAL_MONTH, monthInfo.month-1); // Convert from 1-based to 0-based
internalSet(UCAL_ORDINAL_MONTH, monthInfo.ordinalMonth); // Convert from 1-based to 0-based
internalSet(UCAL_IS_LEAP_MONTH, monthInfo.isLeapMonth?1:0);
//------------------------------------------------------------------ // Fields to time //------------------------------------------------------------------
namespace {
/** * Return the Chinese new year of the given Gregorian year. * @param setting setting (time zone and caches) for the Astro calculation. * @param gyear a Gregorian year * @return days after January 1, 1970 0:00 astronomical base zone of the * Chinese new year of the given year (this will be a new moon)
*/
int32_t newYear(const icu::ChineseCalendar::Setting& setting,
int32_t gyear, UErrorCode& status) { if (U_FAILURE(status)) { return 0;
} const TimeZone* timeZone = setting.zoneAstroCalc;
int32_t cacheValue = CalendarCache::get(setting.newYearCache, gyear, status); if (U_FAILURE(status)) { return 0;
}
/** * Adjust this calendar to be delta months before or after a given * start position, pinning the day of month if necessary. The start * position is given as a local days number for the start of the month * and a day-of-month. Used by add() and roll(). * @param newMoon the local days of the first day of the month of the * start position (days after January 1, 1970 0:00 Asia/Shanghai) * @param dayOfMonth the 1-based day-of-month of the start position * @param delta the number of months to move forward or backward from * the start position * @param status The status.
*/ void ChineseCalendar::offsetMonth(int32_t newMoon, int32_t dayOfMonth, int32_t delta,
UErrorCode& status) { const Setting setting = getSetting(status); if (U_FAILURE(status)) { return;
}
// Move to the middle of the month before our target month. double value = newMoon;
value += (CalendarAstronomer::SYNODIC_MONTH *
(static_cast<double>(delta) - 0.5)); if (value < INT32_MIN || value > INT32_MAX) {
status = U_ILLEGAL_ARGUMENT_ERROR; return;
}
newMoon = static_cast<int32_t>(value);
// Search forward to the target month's new moon
newMoon = newMoonNear(setting.zoneAstroCalc, newMoon, true, status); if (U_FAILURE(status)) { return;
}
// Pin the dayOfMonth. In this calendar all months are 29 or 30 days // so pinning just means handling dayOfMonth 30. if (dayOfMonth > 29) {
set(UCAL_JULIAN_DAY, jd-1); // TODO Fix this. We really shouldn't ever have to // explicitly call complete(). This is either a bug in // this method, in ChineseCalendar, or in // Calendar.getActualMaximum(). I suspect the last.
complete(status); if (U_FAILURE(status)) return; if (getActualMaximum(UCAL_DAY_OF_MONTH, status) >= dayOfMonth) { if (U_FAILURE(status)) return;
set(UCAL_JULIAN_DAY, jd);
}
} else {
set(UCAL_JULIAN_DAY, jd);
}
}
constchar* ChineseCalendar::getTemporalMonthCode(UErrorCode &status) const { // We need to call get, not internalGet, to force the calculation // from UCAL_ORDINAL_MONTH.
int32_t is_leap = get(UCAL_IS_LEAP_MONTH, status); if (U_FAILURE(status)) return nullptr; if (is_leap != 0) {
int32_t month = get(UCAL_MONTH, status); if (U_FAILURE(status)) return nullptr; return gTemporalLeapMonthCodes[month];
} return Calendar::getTemporalMonthCode(status);
}
void
ChineseCalendar::setTemporalMonthCode(constchar* code, UErrorCode& status )
{ if (U_FAILURE(status)) return;
int32_t len = static_cast<int32_t>(uprv_strlen(code)); if (len != 4 || code[0] != 'M' || code[3] != 'L') {
set(UCAL_IS_LEAP_MONTH, 0); return Calendar::setTemporalMonthCode(code, status);
} for (int m = 0; gTemporalLeapMonthCodes[m] != nullptr; m++) { if (uprv_strcmp(code, gTemporalLeapMonthCodes[m]) == 0) {
set(UCAL_MONTH, m);
set(UCAL_IS_LEAP_MONTH, 1); return;
}
}
status = U_ILLEGAL_ARGUMENT_ERROR;
}
int32_t ChineseCalendar::internalGetMonth(UErrorCode& status) const { if (U_FAILURE(status)) { return 0;
} if (resolveFields(kMonthPrecedence) == UCAL_MONTH) { return internalGet(UCAL_MONTH);
}
LocalPointer<Calendar> temp(this->clone());
temp->set(UCAL_MONTH, 0);
temp->set(UCAL_IS_LEAP_MONTH, 0);
temp->set(UCAL_DATE, 1); // Calculate the UCAL_MONTH and UCAL_IS_LEAP_MONTH by adding number of // months.
temp->roll(UCAL_MONTH, internalGet(UCAL_ORDINAL_MONTH), status); if (U_FAILURE(status)) { return 0;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.