X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/4388f060552cc537e71e957d32f35e9d75a61233..a01113dcd0f39d5da295ef82785beff9ed86fe38:/icuSources/i18n/chnsecal.cpp diff --git a/icuSources/i18n/chnsecal.cpp b/icuSources/i18n/chnsecal.cpp index 2d67c683..a60a74f1 100644 --- a/icuSources/i18n/chnsecal.cpp +++ b/icuSources/i18n/chnsecal.cpp @@ -1,6 +1,8 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html /* ****************************************************************************** - * Copyright (C) 2007-2011, International Business Machines Corporation + * Copyright (C) 2007-2014, International Business Machines Corporation * and others. All Rights Reserved. ****************************************************************************** * @@ -21,6 +23,7 @@ #include #include "gregoimp.h" // Math #include "astro.h" // CalendarAstronomer +#include "unicode/simpletz.h" #include "uhash.h" #include "ucln_in.h" @@ -48,11 +51,19 @@ static void debug_chnsecal_msg(const char *pat, ...) // --- The cache -- -static UMTX astroLock = 0; // pod bay door lock +static icu::UMutex *astroLock() { // Protects access to gChineseCalendarAstro. + static icu::UMutex *m = STATIC_NEW(icu::UMutex); + return m; +} static icu::CalendarAstronomer *gChineseCalendarAstro = NULL; + +// Lazy Creation & Access synchronized by class CalendarCache with a mutex. static icu::CalendarCache *gChineseCalendarWinterSolsticeCache = NULL; static icu::CalendarCache *gChineseCalendarNewYearCache = NULL; +static icu::TimeZone *gChineseCalendarZoneAstroCalc = NULL; +static icu::UInitOnce gChineseCalendarZoneAstroCalcInitOnce = U_INITONCE_INITIALIZER; + /** * 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, @@ -66,7 +77,7 @@ static const int32_t CHINESE_EPOCH_YEAR = -2636; // Gregorian year * computations. Some sources use a different historically accurate * offset of GMT+7:45:40 for years before 1929; we do not do this. */ -static const double CHINA_OFFSET = 8 * kOneHour; +static const int32_t CHINA_OFFSET = 8 * kOneHour; /** * Value to be added or subtracted from the local days of a new moon to @@ -90,7 +101,11 @@ static UBool calendar_chinese_cleanup(void) { delete gChineseCalendarNewYearCache; gChineseCalendarNewYearCache = NULL; } - umtx_destroy(&astroLock); + if (gChineseCalendarZoneAstroCalc) { + delete gChineseCalendarZoneAstroCalc; + gChineseCalendarZoneAstroCalc = NULL; + } + gChineseCalendarZoneAstroCalcInitOnce.reset(); return TRUE; } U_CDECL_END @@ -111,14 +126,28 @@ Calendar* ChineseCalendar::clone() const { } ChineseCalendar::ChineseCalendar(const Locale& aLocale, UErrorCode& success) -: Calendar(TimeZone::createDefault(), aLocale, success) +: Calendar(TimeZone::createDefault(), aLocale, success), + isLeapYear(FALSE), + fEpochYear(CHINESE_EPOCH_YEAR), + fZoneAstroCalc(getChineseCalZoneAstroCalc()) +{ + setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. +} + +ChineseCalendar::ChineseCalendar(const Locale& aLocale, int32_t epochYear, + const TimeZone* zoneAstroCalc, UErrorCode &success) +: Calendar(TimeZone::createDefault(), aLocale, success), + isLeapYear(FALSE), + fEpochYear(epochYear), + fZoneAstroCalc(zoneAstroCalc) { - isLeapYear = FALSE; setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. } ChineseCalendar::ChineseCalendar(const ChineseCalendar& other) : Calendar(other) { isLeapYear = other.isLeapYear; + fEpochYear = other.fEpochYear; + fZoneAstroCalc = other.fZoneAstroCalc; } ChineseCalendar::~ChineseCalendar() @@ -129,6 +158,16 @@ const char *ChineseCalendar::getType() const { return "chinese"; } +static void U_CALLCONV initChineseCalZoneAstroCalc() { + gChineseCalendarZoneAstroCalc = new SimpleTimeZone(CHINA_OFFSET, UNICODE_STRING_SIMPLE("CHINA_ZONE") ); + ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); +} + +const TimeZone* ChineseCalendar::getChineseCalZoneAstroCalc(void) const { + umtx_initOnce(gChineseCalendarZoneAstroCalcInitOnce, &initChineseCalZoneAstroCalc); + return gChineseCalendarZoneAstroCalc; +} + //------------------------------------------------------------------------- // Minimum / Maximum access functions //------------------------------------------------------------------------- @@ -188,7 +227,8 @@ int32_t ChineseCalendar::handleGetExtendedYear() { year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1 } else { int32_t cycle = internalGet(UCAL_ERA, 1) - 1; // 0-based cycle - year = cycle * 60 + internalGet(UCAL_YEAR, 1); + // adjust to the instance specific epoch + year = cycle * 60 + internalGet(UCAL_YEAR, 1) - (fEpochYear - CHINESE_EPOCH_YEAR); } return year; } @@ -295,7 +335,7 @@ int32_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U month = (int32_t)m; } - int32_t gyear = eyear + CHINESE_EPOCH_YEAR - 1; // Gregorian year + int32_t gyear = eyear + fEpochYear - 1; // Gregorian year int32_t theNewYear = newYear(gyear); int32_t newMoon = newMoonNear(theNewYear + month * 29, TRUE); @@ -433,26 +473,67 @@ void ChineseCalendar::roll(EDateFields field, int32_t amount, UErrorCode& status /** * Convert local days to UTC epoch milliseconds. - * @param days days after January 1, 1970 0:00 Asia/Shanghai + * 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 days days after January 1, 1970 0:00 in the astronomical base zone * @return milliseconds after January 1, 1970 0:00 GMT */ -double ChineseCalendar::daysToMillis(double days) { - return (days * kOneDay) - CHINA_OFFSET; +double ChineseCalendar::daysToMillis(double days) const { + double millis = days * (double)kOneDay; + if (fZoneAstroCalc != NULL) { + int32_t rawOffset, dstOffset; + UErrorCode status = U_ZERO_ERROR; + fZoneAstroCalc->getOffset(millis, FALSE, rawOffset, dstOffset, status); + if (U_SUCCESS(status)) { + return millis - (double)(rawOffset + dstOffset); + } + } + return millis - (double)CHINA_OFFSET; } /** * Convert UTC epoch milliseconds to local days. * @param millis milliseconds after January 1, 1970 0:00 GMT - * @return days after January 1, 1970 0:00 Asia/Shanghai + * @return days after January 1, 1970 0:00 in the astronomical base zone */ -double ChineseCalendar::millisToDays(double millis) { - return ClockMath::floorDivide(millis + CHINA_OFFSET, kOneDay); +double ChineseCalendar::millisToDays(double millis) const { + if (fZoneAstroCalc != NULL) { + int32_t rawOffset, dstOffset; + UErrorCode status = U_ZERO_ERROR; + fZoneAstroCalc->getOffset(millis, FALSE, rawOffset, dstOffset, status); + if (U_SUCCESS(status)) { + return ClockMath::floorDivide(millis + (double)(rawOffset + dstOffset), kOneDay); + } + } + return ClockMath::floorDivide(millis + (double)CHINA_OFFSET, kOneDay); } //------------------------------------------------------------------ // Astronomical computations //------------------------------------------------------------------ +// bit array for gregorian 1900-2100 indicating years in +// which the linear estimate needs to be adjusted by -1 +static const uint16_t winterSolsticeAdj[] = { + 0x0001, // 1900-1915, deltas for 1900 + 0x0444, // 1916-1931, deltas for 1918, 1922, 1926 + 0x0000, // 1932-1947 + 0x8880, // 1948-1963, deltas for 1955, 1959, 1963 + 0x0000, // 1964-1979 + 0x1100, // 1980-1995, deltas for 1988, 1992 + 0x0011, // 1996-2011, deltas for 1996, 2000 + 0x2200, // 2012-2027, deltas for 2021, 2025 + 0x0022, // 2028-2043, deltas for 2029, 2033 + 0x4000, // 2044-2059, deltas for 2058 + 0x0444, // 2060-2075, deltas for 2062, 2066, 2070 + 0x8000, // 2076-2091, deltas for 2091 + 0x0088, // 2092-2100, deltas for 2095, 2099 +}; /** * Return the major solar term on or after December 15 of the given @@ -463,6 +544,19 @@ double ChineseCalendar::millisToDays(double millis) { * winter solstice of the given year */ int32_t ChineseCalendar::winterSolstice(int32_t gyear) const { + if (gyear >= 1900 && gyear <= 2100) { + // Don't use cache, just return linear estimate + table correction + int32_t gyearadj = gyear - 1900; + int32_t result = (int32_t)(365.243*((double)gyearadj) - 0.3) - 25211; + uint16_t bitmap = winterSolsticeAdj[gyearadj / 16]; + if (bitmap != 0) { + uint16_t bitmask = 1 << (gyearadj % 16); + if ((bitmask & bitmap) != 0) { + result--; + } + } + return result; + } UErrorCode status = U_ZERO_ERROR; int32_t cacheValue = CalendarCache::get(&gChineseCalendarWinterSolsticeCache, gyear, status); @@ -474,14 +568,14 @@ int32_t ChineseCalendar::winterSolstice(int32_t gyear) const { // PST 1298 with a final result of Dec 14 10:31:59 PST 1299. double ms = daysToMillis(Grego::fieldsToDay(gyear, UCAL_DECEMBER, 1)); - umtx_lock(&astroLock); + umtx_lock(astroLock()); if(gChineseCalendarAstro == NULL) { gChineseCalendarAstro = new CalendarAstronomer(); ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); } gChineseCalendarAstro->setTime(ms); UDate solarLong = gChineseCalendarAstro->getSunTime(CalendarAstronomer::WINTER_SOLSTICE(), TRUE); - umtx_unlock(&astroLock); + umtx_unlock(astroLock()); // Winter solstice is 270 degrees solar longitude aka Dongzhi cacheValue = (int32_t)millisToDays(solarLong); @@ -503,16 +597,24 @@ int32_t ChineseCalendar::winterSolstice(int32_t gyear) const { * new moon after or before days */ int32_t ChineseCalendar::newMoonNear(double days, UBool after) const { - - umtx_lock(&astroLock); - if(gChineseCalendarAstro == NULL) { - gChineseCalendarAstro = new CalendarAstronomer(); - ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); + double ms = daysToMillis(days); + // Try to get the new moon via static function directly from the table in + // CalendarAstronomer (for approx gregorian range 1900-2100) without having + // to use a CalendarAstronomer instance which requires a lock. This still + // involves extra conversion to/from millis. If static function returns 0 + // we are out of its range and need to use the full machinery. + UDate newMoon = CalendarAstronomer::getNewMoonTimeInRange(ms, after); + if (newMoon == 0.0) { + umtx_lock(astroLock()); + if(gChineseCalendarAstro == NULL) { + gChineseCalendarAstro = new CalendarAstronomer(); + ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); + } + gChineseCalendarAstro->setTime(ms); + newMoon = gChineseCalendarAstro->getMoonTime(CalendarAstronomer::NEW_MOON(), after); + umtx_unlock(astroLock()); } - gChineseCalendarAstro->setTime(daysToMillis(days)); - UDate newMoon = gChineseCalendarAstro->getMoonTime(CalendarAstronomer::NEW_MOON(), after); - umtx_unlock(&astroLock); - + return (int32_t) millisToDays(newMoon); } @@ -536,14 +638,16 @@ int32_t ChineseCalendar::synodicMonthsBetween(int32_t day1, int32_t day2) const */ int32_t ChineseCalendar::majorSolarTerm(int32_t days) const { - umtx_lock(&astroLock); - if(gChineseCalendarAstro == NULL) { - gChineseCalendarAstro = new CalendarAstronomer(); - ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); - } - gChineseCalendarAstro->setTime(daysToMillis(days)); - UDate solarLongitude = gChineseCalendarAstro->getSunLongitude(); - umtx_unlock(&astroLock); + double ms = daysToMillis(days); + UDate solarLongitude = CalendarAstronomer::getSunLongitudeForTime(ms); + + // There was almost never any benefit to using the CalendarAstronomer instance; + // it could cache intermediate results, but we rarely used it multiple times in + // succession for the same setTime value, so the intermediate results got + // discarded anyway. + // + // Deleted call to gChineseCalendarAstro->getSunLongitude() now that + // we use CalendarAstronomer::getSunLongitudeForTime() // Compute (floor(solarLongitude / (pi/6)) + 2) % 12 int32_t term = ( ((int32_t)(6 * solarLongitude / CalendarAstronomer::PI)) + 2 ) % 12; @@ -571,10 +675,10 @@ UBool ChineseCalendar::hasNoMajorSolarTerm(int32_t newMoon) const { /** * Return true if there is a leap month on or after month newMoon1 and * at or before month newMoon2. - * @param newMoon1 days after January 1, 1970 0:00 Asia/Shanghai of a - * new moon - * @param newMoon2 days after January 1, 1970 0:00 Asia/Shanghai of a - * new moon + * @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 ChineseCalendar::isLeapMonthBetween(int32_t newMoon1, int32_t newMoon2) const { @@ -601,8 +705,8 @@ UBool ChineseCalendar::isLeapMonthBetween(int32_t newMoon1, int32_t newMoon2) co * handleComputeMonthStart(). * *

As a side effect, this method sets {@link #isLeapYear}. - * @param days days after January 1, 1970 0:00 Asia/Shanghai of the - * date to compute fields for + * @param days days after January 1, 1970 0:00 astronomical base zone + * of the date to compute fields for * @param gyear the Gregorian year of the given date * @param gmonth the Gregorian month of the given date * @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR, @@ -651,18 +755,22 @@ void ChineseCalendar::computeChineseFields(int32_t days, int32_t gyear, int32_t if (setAllFields) { - int32_t year = gyear - CHINESE_EPOCH_YEAR; + // Extended year and cycle year is based on the epoch year + + int32_t extended_year = gyear - fEpochYear; + int cycle_year = gyear - CHINESE_EPOCH_YEAR; if (month < 11 || gmonth >= UCAL_JULY) { - year++; + extended_year++; + cycle_year++; } int32_t dayOfMonth = days - thisMoon + 1; - internalSet(UCAL_EXTENDED_YEAR, year); + internalSet(UCAL_EXTENDED_YEAR, extended_year); // 0->0,60 1->1,1 60->1,60 61->2,1 etc. int32_t yearOfCycle; - int32_t cycle = ClockMath::floorDivide(year - 1, 60, yearOfCycle); + int32_t cycle = ClockMath::floorDivide(cycle_year - 1, 60, yearOfCycle); internalSet(UCAL_ERA, cycle + 1); internalSet(UCAL_YEAR, yearOfCycle + 1); @@ -685,13 +793,34 @@ void ChineseCalendar::computeChineseFields(int32_t days, int32_t gyear, int32_t // Fields to time //------------------------------------------------------------------ +// for gyear 1900 through 2100, corrections to linear estimate of newYear +static const int8_t newYearAdj[] = { + -5, 14, 3, -7, 11, -1, -11, 8, -3, -14, 5, -6, 13, 1, -10, 9, -1, -13, 6, -4, // 1900-1919 + 15, 3, -8, 11, 0, -12, 8, -3, -13, 5, -6, 12, 1, -10, 9, -1, -12, 6, -5, 14, // 1920-1939 + 3, -9, 10, 0, -11, 9, -3, -14, 5, -6, 12, 1, -9, 10, -2, -12, 7, -4, 13, 3, // 1940-1959 + -8, 11, 0, -11, 8, -2, -15, 4, -6, 13, 1, -9, 10, -1, -13, 6, -5, 14, 2, -8, // 1960-1979 + 11, 1, -11, 8, -3, 16, 5, -7, 12, 2, -8, 10, -1, -12, 6, -5, 14, 3, -7, 11, // 1980-1999 + 0, -11, 8, -4, -14, 5, -6, 13, 2, -9, 10, -2, -13, 6, -4, 14, 3, -7, 12, 0, // 2000-2019 + -11, 8, -3, -14, 5, -6, 13, 2, -10, 9, -1, -12, 6, -4, 15, 4, -8, 11, 0, -11, // 2020-2039 + 7, -3, -13, 6, -6, 13, 2, -9, 9, -2, -12, 7, -4, 15, 4, -7, 10, 0, -11, 8, // 2040-2059 + -3, -14, 5, -6, 12, 1, -9, 10, -1, -12, 7, -4, 15, 3, -8, 11, 1, -11, 8, -2, // 2060-2079 + -13, 5, -6, 13, 2, -9, 10, -1, -11, 6, -5, 14, 3, -8, 11, 1, -10, 8, -3, -14, // 2080-2099 + 5 // 2100 +}; + /** * Return the Chinese new year of the given Gregorian year. * @param gyear a Gregorian year - * @return days after January 1, 1970 0:00 Asia/Shanghai of the + * @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 ChineseCalendar::newYear(int32_t gyear) const { + if (gyear >= 1900 && gyear <= 2100) { + // Don't use cache, just return linear estimate + table correction + int32_t gyearadj = gyear - 1900; + return (int32_t)(365.244*((double)gyearadj)) - 25532 + newYearAdj[gyearadj]; + } + UErrorCode status = U_ZERO_ERROR; int32_t cacheValue = CalendarCache::get(&gChineseCalendarNewYearCache, gyear, status); @@ -775,11 +904,10 @@ ChineseCalendar::inDaylightTime(UErrorCode& status) const } // default century -const UDate ChineseCalendar::fgSystemDefaultCentury = DBL_MIN; -const int32_t ChineseCalendar::fgSystemDefaultCenturyYear = -1; -UDate ChineseCalendar::fgSystemDefaultCenturyStart = DBL_MIN; -int32_t ChineseCalendar::fgSystemDefaultCenturyStartYear = -1; +static UDate gSystemDefaultCenturyStart = DBL_MIN; +static int32_t gSystemDefaultCenturyStartYear = -1; +static icu::UInitOnce gSystemDefaultCenturyInitOnce = U_INITONCE_INITIALIZER; UBool ChineseCalendar::haveDefaultCentury() const @@ -797,66 +925,39 @@ int32_t ChineseCalendar::defaultCenturyStartYear() const return internalGetDefaultCenturyStartYear(); } -UDate -ChineseCalendar::internalGetDefaultCenturyStart() const -{ - // lazy-evaluate systemDefaultCenturyStart - UBool needsUpdate; - UMTX_CHECK(NULL, (fgSystemDefaultCenturyStart == fgSystemDefaultCentury), needsUpdate); - - if (needsUpdate) { - initializeSystemDefaultCentury(); - } - - // use defaultCenturyStart unless it's the flag value; - // then use systemDefaultCenturyStart - - return fgSystemDefaultCenturyStart; -} - -int32_t -ChineseCalendar::internalGetDefaultCenturyStartYear() const -{ - // lazy-evaluate systemDefaultCenturyStartYear - UBool needsUpdate; - UMTX_CHECK(NULL, (fgSystemDefaultCenturyStart == fgSystemDefaultCentury), needsUpdate); - - if (needsUpdate) { - initializeSystemDefaultCentury(); - } - - // use defaultCenturyStart unless it's the flag value; - // then use systemDefaultCenturyStartYear - - return fgSystemDefaultCenturyStartYear; -} - -void -ChineseCalendar::initializeSystemDefaultCentury() +static void U_CALLCONV initializeSystemDefaultCentury() { // initialize systemDefaultCentury and systemDefaultCenturyYear based // on the current time. They'll be set to 80 years before // the current time. UErrorCode status = U_ZERO_ERROR; ChineseCalendar calendar(Locale("@calendar=chinese"),status); - if (U_SUCCESS(status)) - { + if (U_SUCCESS(status)) { calendar.setTime(Calendar::getNow(), status); calendar.add(UCAL_YEAR, -80, status); - UDate newStart = calendar.getTime(status); - int32_t newYear = calendar.get(UCAL_YEAR, status); - umtx_lock(NULL); - if (fgSystemDefaultCenturyStart == fgSystemDefaultCentury) - { - fgSystemDefaultCenturyStartYear = newYear; - fgSystemDefaultCenturyStart = newStart; - } - umtx_unlock(NULL); + gSystemDefaultCenturyStart = calendar.getTime(status); + gSystemDefaultCenturyStartYear = calendar.get(UCAL_YEAR, status); } // We have no recourse upon failure unless we want to propagate the failure // out. } +UDate +ChineseCalendar::internalGetDefaultCenturyStart() const +{ + // lazy-evaluate systemDefaultCenturyStart + umtx_initOnce(gSystemDefaultCenturyInitOnce, &initializeSystemDefaultCentury); + return gSystemDefaultCenturyStart; +} + +int32_t +ChineseCalendar::internalGetDefaultCenturyStartYear() const +{ + // lazy-evaluate systemDefaultCenturyStartYear + umtx_initOnce(gSystemDefaultCenturyInitOnce, &initializeSystemDefaultCentury); + return gSystemDefaultCenturyStartYear; +} + UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ChineseCalendar) U_NAMESPACE_END