From 157ed28fc9f05c9a7fe2b678caa57fad9d5b0365 Mon Sep 17 00:00:00 2001 From: Peter Edberg Date: Mon, 18 Feb 2013 17:51:25 +0000 Subject: [PATCH] ICU-9616 ChineseCalendar changes to support Dangi subclass, port from J to C X-SVN-Rev: 33265 --- icu4c/source/i18n/chnsecal.cpp | 114 ++++++++++++++++++++++++++------- icu4c/source/i18n/chnsecal.h | 43 +++++++++---- 2 files changed, 122 insertions(+), 35 deletions(-) diff --git a/icu4c/source/i18n/chnsecal.cpp b/icu4c/source/i18n/chnsecal.cpp index 9c9dc79dd24..4c75119b018 100644 --- a/icu4c/source/i18n/chnsecal.cpp +++ b/icu4c/source/i18n/chnsecal.cpp @@ -1,6 +1,6 @@ /* ****************************************************************************** - * Copyright (C) 2007-2012, International Business Machines Corporation + * Copyright (C) 2007-2013, International Business Machines Corporation * and others. All Rights Reserved. ****************************************************************************** * @@ -21,6 +21,7 @@ #include #include "gregoimp.h" // Math #include "astro.h" // CalendarAstronomer +#include "unicode/simpletz.h" #include "uhash.h" #include "ucln_in.h" @@ -52,6 +53,8 @@ static UMutex astroLock = U_MUTEX_INITIALIZER; // pod bay door lock static icu::CalendarAstronomer *gChineseCalendarAstro = NULL; static icu::CalendarCache *gChineseCalendarWinterSolsticeCache = NULL; static icu::CalendarCache *gChineseCalendarNewYearCache = NULL; +static icu::TimeZone *gChineseCalendarZoneAstroCalc = NULL; +static UBool gChineseCalendarZoneAstroCalcInitialized = FALSE; /** * The start year of the Chinese calendar, the 61st year of the reign @@ -66,7 +69,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,6 +93,11 @@ static UBool calendar_chinese_cleanup(void) { delete gChineseCalendarNewYearCache; gChineseCalendarNewYearCache = NULL; } + if (gChineseCalendarZoneAstroCalc) { + delete gChineseCalendarZoneAstroCalc; + gChineseCalendarZoneAstroCalc = NULL; + } + gChineseCalendarZoneAstroCalcInitialized = FALSE; return TRUE; } U_CDECL_END @@ -110,14 +118,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, + 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() @@ -128,6 +150,23 @@ const char *ChineseCalendar::getType() const { return "chinese"; } +const TimeZone* ChineseCalendar::getChineseCalZoneAstroCalc(void) const { + UBool initialized; + UMTX_CHECK(&astroLock, gChineseCalendarZoneAstroCalcInitialized, initialized); + if (!initialized) { + umtx_lock(&astroLock); + { + if (!gChineseCalendarZoneAstroCalcInitialized) { + gChineseCalendarZoneAstroCalc = new SimpleTimeZone(CHINA_OFFSET, UnicodeString("CHINA_ZONE") ); + gChineseCalendarZoneAstroCalcInitialized = TRUE; + ucln_i18n_registerCleanup(UCLN_I18N_CHINESE_CALENDAR, calendar_chinese_cleanup); + } + } + umtx_unlock(&astroLock); + } + return gChineseCalendarZoneAstroCalc; +} + //------------------------------------------------------------------------- // Minimum / Maximum access functions //------------------------------------------------------------------------- @@ -187,7 +226,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; } @@ -294,7 +334,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); @@ -432,20 +472,44 @@ 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); } //------------------------------------------------------------------ @@ -570,10 +634,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 { @@ -600,8 +664,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, @@ -650,18 +714,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); @@ -687,7 +755,7 @@ void ChineseCalendar::computeChineseFields(int32_t days, int32_t gyear, int32_t /** * 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 { diff --git a/icu4c/source/i18n/chnsecal.h b/icu4c/source/i18n/chnsecal.h index d379770e174..4c67a0aee8f 100644 --- a/icu4c/source/i18n/chnsecal.h +++ b/icu4c/source/i18n/chnsecal.h @@ -1,6 +1,6 @@ /* ***************************************************************************** - * Copyright (C) 2007-2008, International Business Machines Corporation + * Copyright (C) 2007-2013, International Business Machines Corporation * and others. All Rights Reserved. ***************************************************************************** * @@ -21,6 +21,7 @@ #if !UCONFIG_NO_FORMATTING #include "unicode/calendar.h" +#include "unicode/timezone.h" U_NAMESPACE_BEGIN @@ -82,7 +83,7 @@ U_NAMESPACE_BEGIN * * *

- * This class should not be subclassed.

+ * This class should only be subclassed to implement variants of the Chinese lunar calendar.

*

* ChineseCalendar usually should be instantiated using * {@link com.ibm.icu.util.Calendar#getInstance(ULocale)} passing in a ULocale @@ -91,7 +92,7 @@ U_NAMESPACE_BEGIN * @see com.ibm.icu.text.ChineseDateFormat * @see com.ibm.icu.util.Calendar * @author Alan Liu - * @stable ICU 2.8 + * @internal */ class ChineseCalendar : public Calendar { public: @@ -100,7 +101,7 @@ class ChineseCalendar : public Calendar { //------------------------------------------------------------------------- /** - * Constructs an ChineseCalendar based on the current time in the default time zone + * Constructs a ChineseCalendar based on the current time in the default time zone * with the given locale. * * @param aLocale The given locale. @@ -110,6 +111,24 @@ class ChineseCalendar : public Calendar { */ ChineseCalendar(const Locale& aLocale, UErrorCode &success); + protected: + + /** + * Constructs a ChineseCalendar based on the current time in the default time zone + * with the given locale, using the specified epoch year and time zone for + * astronomical calculations. + * + * @param aLocale The given locale. + * @param epochYear The epoch year to use for calculation. + * @param zoneAstroCalc The TimeZone to use for astronomical calculations. If null, + * will be set appropriately for Chinese calendar (UTC + 8:00). + * @param success Indicates the status of ChineseCalendar object construction; + * if successful, will not be changed to an error value. + * @internal + */ + ChineseCalendar(const Locale& aLocale, int32_t epochYear, TimeZone* zoneAstroCalc, UErrorCode &success); + + public: /** * Copy Constructor * @internal @@ -132,6 +151,9 @@ class ChineseCalendar : public Calendar { //------------------------------------------------------------------------- UBool isLeapYear; + int32_t fEpochYear; // Start year of this Chinese calendar instance. + const TimeZone* fZoneAstroCalc; // Zone used for the astronomical calculation + // of this Chinese calendar instance. //---------------------------------------------------------------------- // Calendar framework @@ -145,25 +167,22 @@ class ChineseCalendar : public Calendar { virtual void handleComputeFields(int32_t julianDay, UErrorCode &status); virtual const UFieldResolutionTable* getFieldResolutionTable() const; - - -public: + public: virtual void add(UCalendarDateFields field, int32_t amount, UErrorCode &status); virtual void add(EDateFields field, int32_t amount, UErrorCode &status); virtual void roll(UCalendarDateFields field, int32_t amount, UErrorCode &status); virtual void roll(EDateFields field, int32_t amount, UErrorCode &status); - //---------------------------------------------------------------------- // Internal methods & astronomical calculations //---------------------------------------------------------------------- -private: + private: static const UFieldResolutionTable CHINESE_DATE_PRECEDENCE[]; - static double daysToMillis(double days); - static double millisToDays(double millis); + double daysToMillis(double days) const; + double millisToDays(double millis) const; virtual int32_t winterSolstice(int32_t gyear) const; virtual int32_t newMoonNear(double days, UBool after) const; virtual int32_t synodicMonthsBetween(int32_t day1, int32_t day2) const; @@ -174,7 +193,7 @@ private: int32_t gmonth, UBool setAllFields); virtual int32_t newYear(int32_t gyear) const; virtual void offsetMonth(int32_t newMoon, int32_t dom, int32_t delta); - + const TimeZone* getChineseCalZoneAstroCalc(void) const; // UObject stuff public: