| Index: source/i18n/calendar.cpp
|
| diff --git a/source/i18n/calendar.cpp b/source/i18n/calendar.cpp
|
| index 27825c04a2c85ec8907ebcdd03c9e23d382d02fc..062a9e9c30ae0af4dffe7d7629b2002001e872f6 100644
|
| --- a/source/i18n/calendar.cpp
|
| +++ b/source/i18n/calendar.cpp
|
| @@ -1,6 +1,6 @@
|
| /*
|
| *******************************************************************************
|
| -* Copyright (C) 1997-2013, International Business Machines Corporation and *
|
| +* Copyright (C) 1997-2014, International Business Machines Corporation and *
|
| * others. All Rights Reserved. *
|
| *******************************************************************************
|
| *
|
| @@ -106,7 +106,7 @@ U_CDECL_END
|
| */
|
|
|
| const char* fldName(UCalendarDateFields f) {
|
| - return udbg_enumName(UDBG_UCalendarDateFields, (int32_t)f);
|
| + return udbg_enumName(UDBG_UCalendarDateFields, (int32_t)f);
|
| }
|
|
|
| #if UCAL_DEBUG_DUMP
|
| @@ -680,11 +680,14 @@ fAreFieldsVirtuallySet(FALSE),
|
| fNextStamp((int32_t)kMinimumUserStamp),
|
| fTime(0),
|
| fLenient(TRUE),
|
| -fZone(0),
|
| +fZone(NULL),
|
| fRepeatedWallTime(UCAL_WALLTIME_LAST),
|
| fSkippedWallTime(UCAL_WALLTIME_LAST)
|
| {
|
| clear();
|
| + if (U_FAILURE(success)) {
|
| + return;
|
| + }
|
| fZone = TimeZone::createDefault();
|
| if (fZone == NULL) {
|
| success = U_MEMORY_ALLOCATION_ERROR;
|
| @@ -703,10 +706,13 @@ fAreFieldsVirtuallySet(FALSE),
|
| fNextStamp((int32_t)kMinimumUserStamp),
|
| fTime(0),
|
| fLenient(TRUE),
|
| -fZone(0),
|
| +fZone(NULL),
|
| fRepeatedWallTime(UCAL_WALLTIME_LAST),
|
| fSkippedWallTime(UCAL_WALLTIME_LAST)
|
| {
|
| + if (U_FAILURE(success)) {
|
| + return;
|
| + }
|
| if(zone == 0) {
|
| #if defined (U_DEBUG_CAL)
|
| fprintf(stderr, "%s:%d: ILLEGAL ARG because timezone cannot be 0\n",
|
| @@ -718,7 +724,6 @@ fSkippedWallTime(UCAL_WALLTIME_LAST)
|
|
|
| clear();
|
| fZone = zone;
|
| -
|
| setWeekData(aLocale, NULL, success);
|
| }
|
|
|
| @@ -733,14 +738,17 @@ fAreFieldsVirtuallySet(FALSE),
|
| fNextStamp((int32_t)kMinimumUserStamp),
|
| fTime(0),
|
| fLenient(TRUE),
|
| -fZone(0),
|
| +fZone(NULL),
|
| fRepeatedWallTime(UCAL_WALLTIME_LAST),
|
| fSkippedWallTime(UCAL_WALLTIME_LAST)
|
| {
|
| + if (U_FAILURE(success)) {
|
| + return;
|
| + }
|
| clear();
|
| fZone = zone.clone();
|
| if (fZone == NULL) {
|
| - success = U_MEMORY_ALLOCATION_ERROR;
|
| + success = U_MEMORY_ALLOCATION_ERROR;
|
| }
|
| setWeekData(aLocale, NULL, success);
|
| }
|
| @@ -757,7 +765,7 @@ Calendar::~Calendar()
|
| Calendar::Calendar(const Calendar &source)
|
| : UObject(source)
|
| {
|
| - fZone = 0;
|
| + fZone = NULL;
|
| *this = source;
|
| }
|
|
|
| @@ -778,9 +786,8 @@ Calendar::operator=(const Calendar &right)
|
| fLenient = right.fLenient;
|
| fRepeatedWallTime = right.fRepeatedWallTime;
|
| fSkippedWallTime = right.fSkippedWallTime;
|
| - if (fZone != NULL) {
|
| - delete fZone;
|
| - }
|
| + delete fZone;
|
| + fZone = NULL;
|
| if (right.fZone != NULL) {
|
| fZone = right.fZone->clone();
|
| }
|
| @@ -1164,6 +1171,135 @@ Calendar::set(int32_t year, int32_t month, int32_t date, int32_t hour, int32_t m
|
| }
|
|
|
| // -------------------------------------
|
| +// For now the full getRelatedYear implementation is here;
|
| +// per #10752 move the non-default implementation to subclasses
|
| +// (default implementation will do no year adjustment)
|
| +
|
| +static int32_t gregoYearFromIslamicStart(int32_t year) {
|
| + // ad hoc conversion, improve under #10752
|
| + // rough est for now, ok for grego 1846-2138,
|
| + // otherwise occasionally wrong (for 3% of years)
|
| + int cycle, offset, shift = 0;
|
| + if (year >= 1397) {
|
| + cycle = (year - 1397) / 67;
|
| + offset = (year - 1397) % 67;
|
| + shift = 2*cycle + ((offset >= 33)? 1: 0);
|
| + } else {
|
| + cycle = (year - 1396) / 67 - 1;
|
| + offset = -(year - 1396) % 67;
|
| + shift = 2*cycle + ((offset <= 33)? 1: 0);
|
| + }
|
| + return year + 579 - shift;
|
| +}
|
| +
|
| +int32_t Calendar::getRelatedYear(UErrorCode &status) const
|
| +{
|
| + if (U_FAILURE(status)) {
|
| + return 0;
|
| + }
|
| + int32_t year = get(UCAL_EXTENDED_YEAR, status);
|
| + if (U_FAILURE(status)) {
|
| + return 0;
|
| + }
|
| + // modify for calendar type
|
| + ECalType type = getCalendarType(getType());
|
| + switch (type) {
|
| + case CALTYPE_PERSIAN:
|
| + year += 622; break;
|
| + case CALTYPE_HEBREW:
|
| + year -= 3760; break;
|
| + case CALTYPE_CHINESE:
|
| + year -= 2637; break;
|
| + case CALTYPE_INDIAN:
|
| + year += 79; break;
|
| + case CALTYPE_COPTIC:
|
| + year += 284; break;
|
| + case CALTYPE_ETHIOPIC:
|
| + year += 8; break;
|
| + case CALTYPE_ETHIOPIC_AMETE_ALEM:
|
| + year -=5492; break;
|
| + case CALTYPE_DANGI:
|
| + year -= 2333; break;
|
| + case CALTYPE_ISLAMIC_CIVIL:
|
| + case CALTYPE_ISLAMIC:
|
| + case CALTYPE_ISLAMIC_UMALQURA:
|
| + case CALTYPE_ISLAMIC_TBLA:
|
| + case CALTYPE_ISLAMIC_RGSA:
|
| + year = gregoYearFromIslamicStart(year); break;
|
| + default:
|
| + // CALTYPE_GREGORIAN
|
| + // CALTYPE_JAPANESE
|
| + // CALTYPE_BUDDHIST
|
| + // CALTYPE_ROC
|
| + // CALTYPE_ISO8601
|
| + // do nothing, EXTENDED_YEAR same as Gregorian
|
| + break;
|
| + }
|
| + return year;
|
| +}
|
| +
|
| +// -------------------------------------
|
| +// For now the full setRelatedYear implementation is here;
|
| +// per #10752 move the non-default implementation to subclasses
|
| +// (default implementation will do no year adjustment)
|
| +
|
| +static int32_t firstIslamicStartYearFromGrego(int32_t year) {
|
| + // ad hoc conversion, improve under #10752
|
| + // rough est for now, ok for grego 1846-2138,
|
| + // otherwise occasionally wrong (for 3% of years)
|
| + int cycle, offset, shift = 0;
|
| + if (year >= 1977) {
|
| + cycle = (year - 1977) / 65;
|
| + offset = (year - 1977) % 65;
|
| + shift = 2*cycle + ((offset >= 32)? 1: 0);
|
| + } else {
|
| + cycle = (year - 1976) / 65 - 1;
|
| + offset = -(year - 1976) % 65;
|
| + shift = 2*cycle + ((offset <= 32)? 1: 0);
|
| + }
|
| + return year - 579 + shift;
|
| +}
|
| +void Calendar::setRelatedYear(int32_t year)
|
| +{
|
| + // modify for calendar type
|
| + ECalType type = getCalendarType(getType());
|
| + switch (type) {
|
| + case CALTYPE_PERSIAN:
|
| + year -= 622; break;
|
| + case CALTYPE_HEBREW:
|
| + year += 3760; break;
|
| + case CALTYPE_CHINESE:
|
| + year += 2637; break;
|
| + case CALTYPE_INDIAN:
|
| + year -= 79; break;
|
| + case CALTYPE_COPTIC:
|
| + year -= 284; break;
|
| + case CALTYPE_ETHIOPIC:
|
| + year -= 8; break;
|
| + case CALTYPE_ETHIOPIC_AMETE_ALEM:
|
| + year +=5492; break;
|
| + case CALTYPE_DANGI:
|
| + year += 2333; break;
|
| + case CALTYPE_ISLAMIC_CIVIL:
|
| + case CALTYPE_ISLAMIC:
|
| + case CALTYPE_ISLAMIC_UMALQURA:
|
| + case CALTYPE_ISLAMIC_TBLA:
|
| + case CALTYPE_ISLAMIC_RGSA:
|
| + year = firstIslamicStartYearFromGrego(year); break;
|
| + default:
|
| + // CALTYPE_GREGORIAN
|
| + // CALTYPE_JAPANESE
|
| + // CALTYPE_BUDDHIST
|
| + // CALTYPE_ROC
|
| + // CALTYPE_ISO8601
|
| + // do nothing, EXTENDED_YEAR same as Gregorian
|
| + break;
|
| + }
|
| + // set extended year
|
| + set(UCAL_EXTENDED_YEAR, year);
|
| +}
|
| +
|
| +// -------------------------------------
|
|
|
| void
|
| Calendar::clear()
|
| @@ -1894,10 +2030,10 @@ void Calendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status
|
| // a computed amount of millis to the current millis. The only
|
| // wrinkle is with DST (and/or a change to the zone's UTC offset, which
|
| // we'll include with DST) -- for some fields, like the DAY_OF_MONTH,
|
| - // we don't want the HOUR to shift due to changes in DST. If the
|
| + // we don't want the wall time to shift due to changes in DST. If the
|
| // result of the add operation is to move from DST to Standard, or
|
| // vice versa, we need to adjust by an hour forward or back,
|
| - // respectively. For such fields we set keepHourInvariant to TRUE.
|
| + // respectively. For such fields we set keepWallTimeInvariant to TRUE.
|
|
|
| // We only adjust the DST for fields larger than an hour. For
|
| // fields smaller than an hour, we cannot adjust for DST without
|
| @@ -1912,7 +2048,7 @@ void Calendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status
|
| // <April 30>, rather than <April 31> => <May 1>.
|
|
|
| double delta = amount; // delta in ms
|
| - UBool keepHourInvariant = TRUE;
|
| + UBool keepWallTimeInvariant = TRUE;
|
|
|
| switch (field) {
|
| case UCAL_ERA:
|
| @@ -1974,22 +2110,22 @@ void Calendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status
|
| case UCAL_HOUR_OF_DAY:
|
| case UCAL_HOUR:
|
| delta *= kOneHour;
|
| - keepHourInvariant = FALSE;
|
| + keepWallTimeInvariant = FALSE;
|
| break;
|
|
|
| case UCAL_MINUTE:
|
| delta *= kOneMinute;
|
| - keepHourInvariant = FALSE;
|
| + keepWallTimeInvariant = FALSE;
|
| break;
|
|
|
| case UCAL_SECOND:
|
| delta *= kOneSecond;
|
| - keepHourInvariant = FALSE;
|
| + keepWallTimeInvariant = FALSE;
|
| break;
|
|
|
| case UCAL_MILLISECOND:
|
| case UCAL_MILLISECONDS_IN_DAY:
|
| - keepHourInvariant = FALSE;
|
| + keepWallTimeInvariant = FALSE;
|
| break;
|
|
|
| default:
|
| @@ -2003,41 +2139,61 @@ void Calendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status
|
| // ") not supported");
|
| }
|
|
|
| - // In order to keep the hour invariant (for fields where this is
|
| + // In order to keep the wall time invariant (for fields where this is
|
| // appropriate), check the combined DST & ZONE offset before and
|
| // after the add() operation. If it changes, then adjust the millis
|
| // to compensate.
|
| int32_t prevOffset = 0;
|
| - int32_t hour = 0;
|
| - if (keepHourInvariant) {
|
| + int32_t prevWallTime = 0;
|
| + if (keepWallTimeInvariant) {
|
| prevOffset = get(UCAL_DST_OFFSET, status) + get(UCAL_ZONE_OFFSET, status);
|
| - hour = internalGet(UCAL_HOUR_OF_DAY);
|
| + prevWallTime = get(UCAL_MILLISECONDS_IN_DAY, status);
|
| }
|
|
|
| setTimeInMillis(getTimeInMillis(status) + delta, status);
|
|
|
| - if (keepHourInvariant) {
|
| - int32_t newOffset = get(UCAL_DST_OFFSET, status) + get(UCAL_ZONE_OFFSET, status);
|
| - if (newOffset != prevOffset) {
|
| - // We have done an hour-invariant adjustment but the
|
| - // combined offset has changed. We adjust millis to keep
|
| - // the hour constant. In cases such as midnight after
|
| - // a DST change which occurs at midnight, there is the
|
| - // danger of adjusting into a different day. To avoid
|
| - // this we make the adjustment only if it actually
|
| - // maintains the hour.
|
| -
|
| - // When the difference of the previous UTC offset and
|
| - // the new UTC offset exceeds 1 full day, we do not want
|
| - // to roll over/back the date. For now, this only happens
|
| - // in Samoa (Pacific/Apia) on Dec 30, 2011. See ticket:9452.
|
| - int32_t adjAmount = prevOffset - newOffset;
|
| - adjAmount = adjAmount >= 0 ? adjAmount % (int32_t)kOneDay : -(-adjAmount % (int32_t)kOneDay);
|
| - if (adjAmount != 0) {
|
| - double t = internalGetTime();
|
| - setTimeInMillis(t + adjAmount, status);
|
| - if (get(UCAL_HOUR_OF_DAY, status) != hour) {
|
| - setTimeInMillis(t, status);
|
| + if (keepWallTimeInvariant) {
|
| + int32_t newWallTime = get(UCAL_MILLISECONDS_IN_DAY, status);
|
| + if (newWallTime != prevWallTime) {
|
| + // There is at least one zone transition between the base
|
| + // time and the result time. As the result, wall time has
|
| + // changed.
|
| + UDate t = internalGetTime();
|
| + int32_t newOffset = get(UCAL_DST_OFFSET, status) + get(UCAL_ZONE_OFFSET, status);
|
| + if (newOffset != prevOffset) {
|
| + // When the difference of the previous UTC offset and
|
| + // the new UTC offset exceeds 1 full day, we do not want
|
| + // to roll over/back the date. For now, this only happens
|
| + // in Samoa (Pacific/Apia) on Dec 30, 2011. See ticket:9452.
|
| + int32_t adjAmount = prevOffset - newOffset;
|
| + adjAmount = adjAmount >= 0 ? adjAmount % (int32_t)kOneDay : -(-adjAmount % (int32_t)kOneDay);
|
| + if (adjAmount != 0) {
|
| + setTimeInMillis(t + adjAmount, status);
|
| + newWallTime = get(UCAL_MILLISECONDS_IN_DAY, status);
|
| + }
|
| + if (newWallTime != prevWallTime) {
|
| + // The result wall time or adjusted wall time was shifted because
|
| + // the target wall time does not exist on the result date.
|
| + switch (fSkippedWallTime) {
|
| + case UCAL_WALLTIME_FIRST:
|
| + if (adjAmount > 0) {
|
| + setTimeInMillis(t, status);
|
| + }
|
| + break;
|
| + case UCAL_WALLTIME_LAST:
|
| + if (adjAmount < 0) {
|
| + setTimeInMillis(t, status);
|
| + }
|
| + break;
|
| + case UCAL_WALLTIME_NEXT_VALID:
|
| + UDate tmpT = adjAmount > 0 ? internalGetTime() : t;
|
| + UDate immediatePrevTrans;
|
| + UBool hasTransition = getImmediatePreviousZoneTransition(tmpT, &immediatePrevTrans, status);
|
| + if (U_SUCCESS(status) && hasTransition) {
|
| + setTimeInMillis(immediatePrevTrans, status);
|
| + }
|
| + break;
|
| + }
|
| }
|
| }
|
| }
|
| @@ -2158,7 +2314,7 @@ Calendar::adoptTimeZone(TimeZone* zone)
|
| if (zone == NULL) return;
|
|
|
| // fZone should always be non-null
|
| - if (fZone != NULL) delete fZone;
|
| + delete fZone;
|
| fZone = zone;
|
|
|
| // if the zone changes, we need to recompute the time fields
|
| @@ -2177,6 +2333,7 @@ Calendar::setTimeZone(const TimeZone& zone)
|
| const TimeZone&
|
| Calendar::getTimeZone() const
|
| {
|
| + U_ASSERT(fZone != NULL);
|
| return *fZone;
|
| }
|
|
|
| @@ -2185,9 +2342,14 @@ Calendar::getTimeZone() const
|
| TimeZone*
|
| Calendar::orphanTimeZone()
|
| {
|
| - TimeZone *z = fZone;
|
| // we let go of the time zone; the new time zone is the system default time zone
|
| - fZone = TimeZone::createDefault();
|
| + TimeZone *defaultZone = TimeZone::createDefault();
|
| + if (defaultZone == NULL) {
|
| + // No error handling available. Must keep fZone non-NULL, there are many unchecked uses.
|
| + return NULL;
|
| + }
|
| + TimeZone *z = fZone;
|
| + fZone = defaultZone;
|
| return z;
|
| }
|
|
|
| @@ -2306,11 +2468,11 @@ Calendar::getDayOfWeekType(UCalendarDaysOfWeek dayOfWeek, UErrorCode &status) co
|
| status = U_ILLEGAL_ARGUMENT_ERROR;
|
| return UCAL_WEEKDAY;
|
| }
|
| - if (fWeekendOnset == fWeekendCease) {
|
| - if (dayOfWeek != fWeekendOnset)
|
| - return UCAL_WEEKDAY;
|
| - return (fWeekendOnsetMillis == 0) ? UCAL_WEEKEND : UCAL_WEEKEND_ONSET;
|
| - }
|
| + if (fWeekendOnset == fWeekendCease) {
|
| + if (dayOfWeek != fWeekendOnset)
|
| + return UCAL_WEEKDAY;
|
| + return (fWeekendOnsetMillis == 0) ? UCAL_WEEKEND : UCAL_WEEKEND_ONSET;
|
| + }
|
| if (fWeekendOnset < fWeekendCease) {
|
| if (dayOfWeek < fWeekendOnset || dayOfWeek > fWeekendCease) {
|
| return UCAL_WEEKDAY;
|
| @@ -2818,22 +2980,10 @@ void Calendar::computeTime(UErrorCode& status) {
|
| // Adjust time to the next valid wall clock time.
|
| // At this point, tmpTime is on or after the zone offset transition causing
|
| // the skipped time range.
|
| -
|
| - BasicTimeZone *btz = getBasicTimeZone();
|
| - if (btz) {
|
| - TimeZoneTransition transition;
|
| - UBool hasTransition = btz->getPreviousTransition(tmpTime, TRUE, transition);
|
| - if (hasTransition) {
|
| - t = transition.getTime();
|
| - } else {
|
| - // Could not find any transitions.
|
| - // Note: This should never happen.
|
| - status = U_INTERNAL_PROGRAM_ERROR;
|
| - }
|
| - } else {
|
| - // If not BasicTimeZone, return unsupported error for now.
|
| - // TODO: We may support non-BasicTimeZone in future.
|
| - status = U_UNSUPPORTED_ERROR;
|
| + UDate immediatePrevTransition;
|
| + UBool hasTransition = getImmediatePreviousZoneTransition(tmpTime, &immediatePrevTransition, status);
|
| + if (U_SUCCESS(status) && hasTransition) {
|
| + t = immediatePrevTransition;
|
| }
|
| }
|
| } else {
|
| @@ -2850,6 +3000,30 @@ void Calendar::computeTime(UErrorCode& status) {
|
| }
|
|
|
| /**
|
| + * Find the previous zone transtion near the given time.
|
| + */
|
| +UBool Calendar::getImmediatePreviousZoneTransition(UDate base, UDate *transitionTime, UErrorCode& status) const {
|
| + BasicTimeZone *btz = getBasicTimeZone();
|
| + if (btz) {
|
| + TimeZoneTransition trans;
|
| + UBool hasTransition = btz->getPreviousTransition(base, TRUE, trans);
|
| + if (hasTransition) {
|
| + *transitionTime = trans.getTime();
|
| + return TRUE;
|
| + } else {
|
| + // Could not find any transitions.
|
| + // Note: This should never happen.
|
| + status = U_INTERNAL_PROGRAM_ERROR;
|
| + }
|
| + } else {
|
| + // If not BasicTimeZone, return unsupported error for now.
|
| + // TODO: We may support non-BasicTimeZone in future.
|
| + status = U_UNSUPPORTED_ERROR;
|
| + }
|
| + return FALSE;
|
| +}
|
| +
|
| +/**
|
| * Compute the milliseconds in the day from the fields. This is a
|
| * value from 0 to 23:59:59.999 inclusive, unless fields are out of
|
| * range, in which case it can be an arbitrary value. This value
|
| @@ -3236,10 +3410,12 @@ int32_t Calendar::handleGetExtendedYearFromWeekFields(int32_t yearWoy, int32_t w
|
| if (first < 0) {
|
| first += 7;
|
| }
|
| - int32_t nextFirst = julianDayToDayOfWeek(nextJan1Start + 1) - firstDayOfWeek;
|
| - if (nextFirst < 0) {
|
| - nextFirst += 7;
|
| - }
|
| +
|
| + //// (nextFirst was not used below)
|
| + // int32_t nextFirst = julianDayToDayOfWeek(nextJan1Start + 1) - firstDayOfWeek;
|
| + // if (nextFirst < 0) {
|
| + // nextFirst += 7;
|
| + //}
|
|
|
| int32_t minDays = getMinimalDaysInFirstWeek();
|
| UBool jan1InPrevYear = FALSE; // January 1st in the year of WOY is the 1st week? (i.e. first week is < minimal )
|
|
|