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 ) |