]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/i18n/calendar.cpp
ICU-491.11.1.tar.gz
[apple/icu.git] / icuSources / i18n / calendar.cpp
index aad8d2d36ab0bc8a5551ee9dc9c12b4259c59400..a8c014f858be883a9f81d118e5bb01ce4d7cbd44 100644 (file)
@@ -1,6 +1,6 @@
 /*
 *******************************************************************************
-* Copyright (C) 1997-2011, International Business Machines Corporation and    *
+* Copyright (C) 1997-2012, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 *
 #if !UCONFIG_NO_FORMATTING
 
 #include "unicode/gregocal.h"
+#include "unicode/basictz.h"
+#include "unicode/simpletz.h"
+#include "unicode/rbtz.h"
+#include "unicode/vtzone.h"
 #include "gregoimp.h"
 #include "buddhcal.h"
 #include "taiwncal.h"
 #include "locbased.h"
 #include "uresimp.h"
 #include "ustrenum.h"
+#include "uassert.h"
+#include "olsontz.h"
 
 #if !UCONFIG_NO_SERVICE
-static U_NAMESPACE_QUALIFIER ICULocaleService* gService = NULL;
+static icu::ICULocaleService* gService = NULL;
 #endif
 
 // INTERNAL - for cleanup
@@ -82,9 +88,11 @@ U_CDECL_END
 /** 
  * fldName was removed as a duplicate implementation. 
  * use  udbg_ services instead, 
- * which depend on include files and library from ../tools/ctestfw
+ * which depend on include files and library from ../tools/toolutil, the following circular link:
+ *   CPPFLAGS+=-I$(top_srcdir)/tools/toolutil
+ *   LIBS+=$(LIBICUTOOLUTIL)
  */
-#include "unicode/udbgutil.h"
+#include "udbgutil.h"
 #include <stdio.h>
 
 /**
@@ -138,6 +146,9 @@ U_CFUNC void ucal_dump(UCalendar* cal) {
 
 #endif
 
+/* Max value for stamp allowable before recalculation */
+#define STAMP_MAX 10000
+
 static const char * const gCalTypes[] = {
     "gregorian",
     "japanese",
@@ -152,6 +163,7 @@ static const char * const gCalTypes[] = {
     "coptic",
     "ethiopic",
     "ethiopic-amete-alem",
+    "iso8601",
     NULL
 };
 
@@ -170,7 +182,8 @@ typedef enum ECalType {
     CALTYPE_INDIAN,
     CALTYPE_COPTIC,
     CALTYPE_ETHIOPIC,
-    CALTYPE_ETHIOPIC_AMETE_ALEM
+    CALTYPE_ETHIOPIC_AMETE_ALEM,
+    CALTYPE_ISO8601
 } ECalType;
 
 U_NAMESPACE_BEGIN
@@ -241,8 +254,7 @@ static ECalType getCalendarTypeForLocale(const char *locid) {
     regionLen = uloc_getCountry(canonicalName, region, sizeof(region) - 1, &status);
     if (regionLen == 0) {
         char fullLoc[256];
-        int32_t fullLocLen = 0;
-        fullLocLen = uloc_addLikelySubtags(locid, fullLoc, sizeof(fullLoc) - 1, &status);
+        uloc_addLikelySubtags(locid, fullLoc, sizeof(fullLoc) - 1, &status);
         regionLen = uloc_getCountry(fullLoc, region, sizeof(region) - 1, &status);
     }
     if (U_FAILURE(status)) {
@@ -324,6 +336,11 @@ static Calendar *createStandardCalendar(ECalType calType, const Locale &loc, UEr
         case CALTYPE_ETHIOPIC_AMETE_ALEM:
             cal = new EthiopicCalendar(loc, status, EthiopicCalendar::AMETE_ALEM_ERA);
             break;
+        case CALTYPE_ISO8601:
+            cal = new GregorianCalendar(loc, status);
+            cal->setFirstDayOfWeek(UCAL_MONDAY);
+            cal->setMinimalDaysInFirstWeek(4);
+            break;
         default:
             status = U_UNSUPPORTED_ERROR;
     }
@@ -347,7 +364,7 @@ public:
     BasicCalendarFactory()
         : LocaleKeyFactory(LocaleKeyFactory::INVISIBLE) { }
 
-        virtual ~BasicCalendarFactory() {}
+    virtual ~BasicCalendarFactory();
 
 protected:
     //virtual UBool isSupportedID( const UnicodeString& id, UErrorCode& status) const { 
@@ -406,6 +423,7 @@ protected:
     }
 };
 
+BasicCalendarFactory::~BasicCalendarFactory() {}
 
 /** 
 * A factory which looks up the DefaultCalendar resource to determine which class of calendar to use
@@ -413,7 +431,8 @@ protected:
 
 class DefaultCalendarFactory : public ICUResourceBundleFactory {
 public:
-    DefaultCalendarFactory():  ICUResourceBundleFactory() { } 
+    DefaultCalendarFactory() : ICUResourceBundleFactory() { }
+    virtual ~DefaultCalendarFactory();
 protected:
     virtual UObject* create(const ICUServiceKey& key, const ICUService* /*service*/, UErrorCode& status) const  {
 
@@ -427,12 +446,14 @@ protected:
         } else {
             ret->append((UChar)0x40); // '@' is a variant character
             ret->append(UNICODE_STRING("calendar=", 9));
-            ret->append(UnicodeString(gCalTypes[getCalendarTypeForLocale(loc.getName())]));
+            ret->append(UnicodeString(gCalTypes[getCalendarTypeForLocale(loc.getName())], -1, US_INV));
         }
         return ret;
     }
 };
 
+DefaultCalendarFactory::~DefaultCalendarFactory() {}
+
 // -------------------------------------
 class CalendarService : public ICULocaleService {
 public:
@@ -443,6 +464,8 @@ public:
         registerFactory(new DefaultCalendarFactory(), status);
     }
 
+    virtual ~CalendarService();
+
     virtual UObject* cloneInstance(UObject* instance) const {
         UnicodeString *s = dynamic_cast<UnicodeString *>(instance);
         if(s != NULL) {
@@ -482,6 +505,8 @@ public:
     }
 };
 
+CalendarService::~CalendarService() {}
+
 // -------------------------------------
 
 static inline UBool
@@ -584,8 +609,7 @@ static const int32_t kCalendarLimits[UCAL_FIELD_COUNT][4] = {
 };
 
 // Resource bundle tags read by this class
-static const char gDateTimeElements[] = "DateTimeElements";
-static const char gWeekend[] = "weekend";
+static const char gMonthNames[] = "monthNames";
 
 // Data flow in Calendar
 // ---------------------
@@ -648,7 +672,9 @@ fAreFieldsVirtuallySet(FALSE),
 fNextStamp((int32_t)kMinimumUserStamp),
 fTime(0),
 fLenient(TRUE),
-fZone(0)
+fZone(0),
+fRepeatedWallTime(UCAL_WALLTIME_LAST),
+fSkippedWallTime(UCAL_WALLTIME_LAST)
 {
     clear();
     fZone = TimeZone::createDefault();
@@ -669,7 +695,9 @@ fAreFieldsVirtuallySet(FALSE),
 fNextStamp((int32_t)kMinimumUserStamp),
 fTime(0),
 fLenient(TRUE),
-fZone(0)
+fZone(0),
+fRepeatedWallTime(UCAL_WALLTIME_LAST),
+fSkippedWallTime(UCAL_WALLTIME_LAST)
 {
     if(zone == 0) {
 #if defined (U_DEBUG_CAL)
@@ -697,7 +725,9 @@ fAreFieldsVirtuallySet(FALSE),
 fNextStamp((int32_t)kMinimumUserStamp),
 fTime(0),
 fLenient(TRUE),
-fZone(0)
+fZone(0),
+fRepeatedWallTime(UCAL_WALLTIME_LAST),
+fSkippedWallTime(UCAL_WALLTIME_LAST)
 {
     clear();
     fZone = zone.clone();
@@ -738,6 +768,8 @@ Calendar::operator=(const Calendar &right)
         fAreFieldsSet            = right.fAreFieldsSet;
         fAreFieldsVirtuallySet   = right.fAreFieldsVirtuallySet;
         fLenient                 = right.fLenient;
+        fRepeatedWallTime        = right.fRepeatedWallTime;
+        fSkippedWallTime         = right.fSkippedWallTime;
         if (fZone != NULL) {
             delete fZone;
         }
@@ -751,6 +783,8 @@ Calendar::operator=(const Calendar &right)
         fWeekendCease            = right.fWeekendCease;
         fWeekendCeaseMillis      = right.fWeekendCeaseMillis;
         fNextStamp               = right.fNextStamp;
+        uprv_strcpy(validLocale, right.validLocale);
+        uprv_strcpy(actualLocale, right.actualLocale);
     }
 
     return *this;
@@ -868,6 +902,14 @@ Calendar::createInstance(TimeZone* zone, const Locale& aLocale, UErrorCode& succ
         fprintf(stderr, "%p: setting week count data to locale %s, actual locale %s\n", c, (const char*)aLocale.getName(), (const char *)actualLoc.getName());
 #endif
         c->setWeekData(aLocale, c->getType(), success);  // set the correct locale (this was an indirected calendar)
+
+        char keyword[ULOC_FULLNAME_CAPACITY];
+        UErrorCode tmpStatus = U_ZERO_ERROR;
+        l.getKeywordValue("calendar", keyword, ULOC_FULLNAME_CAPACITY, tmpStatus);
+        if (U_SUCCESS(tmpStatus) && uprv_strcmp(keyword, "iso8601") == 0) {
+            c->setFirstDayOfWeek(UCAL_MONDAY);
+            c->setMinimalDaysInFirstWeek(4);
+        }
     }
     else
 #endif /* UCONFIG_NO_SERVICE */
@@ -911,6 +953,8 @@ Calendar::isEquivalentTo(const Calendar& other) const
 {
     return typeid(*this) == typeid(other) &&
         fLenient                == other.fLenient &&
+        fRepeatedWallTime       == other.fRepeatedWallTime &&
+        fSkippedWallTime        == other.fSkippedWallTime &&
         fFirstDayOfWeek         == other.fFirstDayOfWeek &&
         fMinimalDaysInFirstWeek == other.fMinimalDaysInFirstWeek &&
         fWeekendOnset           == other.fWeekendOnset &&
@@ -1054,6 +1098,10 @@ Calendar::set(UCalendarDateFields field, int32_t value)
         computeFields(ec);
     }
     fFields[field]     = value;
+    /* Ensure that the fNextStamp value doesn't go pass max value for int32_t */
+    if (fNextStamp == STAMP_MAX) {
+        recalculateStamp();
+    }
     fStamp[field]     = fNextStamp++;
     fIsSet[field]     = TRUE; // Remove later
     fIsTimeSet = fAreFieldsSet = fAreFieldsVirtuallySet = FALSE;
@@ -1784,7 +1832,8 @@ void Calendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status
 
     // We handle most fields in the same way.  The algorithm is to add
     // a computed amount of millis to the current millis.  The only
-    // wrinkle is with DST -- for some fields, like the DAY_OF_MONTH,
+    // 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
     // 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,
@@ -1815,9 +1864,17 @@ void Calendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status
     case UCAL_EXTENDED_YEAR:
     case UCAL_YEAR_WOY:
     case UCAL_MONTH:
+      {
+        UBool oldLenient = isLenient();
+        setLenient(TRUE);
         set(field, get(field, status) + amount);
         pinField(UCAL_DAY_OF_MONTH, status);
-        return;
+        if(oldLenient==FALSE) {
+          complete(status); /* force recalculate */
+          setLenient(oldLenient);
+        }
+      }
+      return;
 
     case UCAL_WEEK_OF_YEAR:
     case UCAL_WEEK_OF_MONTH:
@@ -1870,30 +1927,30 @@ void Calendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status
     }
 
     // In order to keep the hour invariant (for fields where this is
-    // appropriate), record the DST_OFFSET before and after the add()
-    // operation.  If it has changed, then adjust the millis to
-    // compensate.
-    int32_t dst = 0;
+    // 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) {
-        dst = get(UCAL_DST_OFFSET, status);
+        prevOffset = get(UCAL_DST_OFFSET, status) + get(UCAL_ZONE_OFFSET, status);
         hour = internalGet(UCAL_HOUR_OF_DAY);
     }
 
     setTimeInMillis(getTimeInMillis(status) + delta, status);
 
     if (keepHourInvariant) {
-        dst -= get(UCAL_DST_OFFSET, status);
-        if (dst != 0) {
+        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
-            // DST offset has altered.  We adjust millis to keep
-            // the hour constant.  In cases such as midnight after
+            // 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
+            // danger of adjusting into a different day. To avoid
             // this we make the adjustment only if it actually
             // maintains the hour.
             double t = internalGetTime();
-            setTimeInMillis(t + dst, status);
+            setTimeInMillis(t + prevOffset - newOffset, status);
             if (get(UCAL_HOUR_OF_DAY, status) != hour) {
                 setTimeInMillis(t, status);
             }
@@ -1927,22 +1984,24 @@ int32_t Calendar::fieldDifference(UDate targetMs, UCalendarDateFields field, UEr
                 return max;
             } else if (ms > targetMs) {
                 break;
-            } else {
+            } else if (max < INT32_MAX) {
                 min = max;
                 max <<= 1;
                 if (max < 0) {
-                    // Field difference too large to fit into int32_t
+                    max = INT32_MAX;
+                }
+            } else {
+                // Field difference too large to fit into int32_t
 #if defined (U_DEBUG_CAL)
-                    fprintf(stderr, "%s:%d: ILLEGAL ARG because field %s's max too large for int32_t\n",
-                        __FILE__, __LINE__, fldName(field));
+                fprintf(stderr, "%s:%d: ILLEGAL ARG because field %s's max too large for int32_t\n",
+                    __FILE__, __LINE__, fldName(field));
 #endif
-                    ec = U_ILLEGAL_ARGUMENT_ERROR;
-                }
+                ec = U_ILLEGAL_ARGUMENT_ERROR;
             }
         }
         // Do a binary search
         while ((max - min) > 1 && U_SUCCESS(ec)) {
-            int32_t t = (min + max) / 2;
+            int32_t t = min + (max - min)/2; // make sure intermediate values don't exceed INT32_MAX
             setTimeInMillis(startMs, ec);
             add(field, t, ec);
             double ms = getTimeInMillis(ec);
@@ -1980,7 +2039,7 @@ int32_t Calendar::fieldDifference(UDate targetMs, UCalendarDateFields field, UEr
         }
         // Do a binary search
         while ((min - max) > 1 && U_SUCCESS(ec)) {
-            int32_t t = (min + max) / 2;
+            int32_t t = min + (max - min)/2; // make sure intermediate values don't exceed INT32_MAX
             setTimeInMillis(startMs, ec);
             add(field, t, ec);
             double ms = getTimeInMillis(ec);
@@ -2064,6 +2123,40 @@ Calendar::isLenient() const
 
 // -------------------------------------
 
+void
+Calendar::setRepeatedWallTimeOption(UCalendarWallTimeOption option)
+{
+    if (option == UCAL_WALLTIME_LAST || option == UCAL_WALLTIME_FIRST) {
+        fRepeatedWallTime = option;
+    }
+}
+
+// -------------------------------------
+
+UCalendarWallTimeOption
+Calendar::getRepeatedWallTimeOption(void) const
+{
+    return fRepeatedWallTime;
+}
+
+// -------------------------------------
+
+void
+Calendar::setSkippedWallTimeOption(UCalendarWallTimeOption option)
+{
+    fSkippedWallTime = option;
+}
+
+// -------------------------------------
+
+UCalendarWallTimeOption
+Calendar::getSkippedWallTimeOption(void) const
+{
+    return fSkippedWallTime;
+}
+
+// -------------------------------------
+
 void
 Calendar::setFirstDayOfWeek(UCalendarDaysOfWeek value)
 {
@@ -2371,7 +2464,7 @@ Calendar::getActualMinimum(UCalendarDateFields field, UErrorCode& status) const
 */
 void Calendar::validateFields(UErrorCode &status) {
     for (int32_t field = 0; U_SUCCESS(status) && (field < UCAL_FIELD_COUNT); field++) {
-        if (isSet((UCalendarDateFields)field)) {
+        if (fStamp[field] >= kMinimumUserStamp) {
             validateField((UCalendarDateFields)field, status);
         }
     }
@@ -2455,6 +2548,7 @@ UCalendarDateFields Calendar::resolveFields(const UFieldResolutionTable* precede
             int32_t lineStamp = kUnset;
             // Skip over first entry if it is negative
             for (int32_t i=((precedenceTable[g][l][0]>=kResolveRemap)?1:0); precedenceTable[g][l][i]!=-1; ++i) {
+                U_ASSERT(precedenceTable[g][l][i] < UCAL_FIELD_COUNT);
                 int32_t s = fStamp[precedenceTable[g][l][i]];
                 // If any field is unset then don't use this line
                 if (s == kUnset) {
@@ -2558,33 +2652,97 @@ void Calendar::computeTime(UErrorCode& status) {
     // is legacy behavior.  Without this, clear(MONTH) has no effect,
     // since the internally set JULIAN_DAY is used.
     if (fStamp[UCAL_MILLISECONDS_IN_DAY] >= ((int32_t)kMinimumUserStamp) &&
-        newestStamp(UCAL_AM_PM, UCAL_MILLISECOND, kUnset) <= fStamp[UCAL_MILLISECONDS_IN_DAY]) {
-            millisInDay = internalGet(UCAL_MILLISECONDS_IN_DAY);
-        } else {
-            millisInDay = computeMillisInDay();
-        }
+            newestStamp(UCAL_AM_PM, UCAL_MILLISECOND, kUnset) <= fStamp[UCAL_MILLISECONDS_IN_DAY]) {
+        millisInDay = internalGet(UCAL_MILLISECONDS_IN_DAY);
+    } else {
+        millisInDay = computeMillisInDay();
+    }
 
+    UDate t = 0;
+    if (fStamp[UCAL_ZONE_OFFSET] >= ((int32_t)kMinimumUserStamp) || fStamp[UCAL_DST_OFFSET] >= ((int32_t)kMinimumUserStamp)) {
+        t = millis + millisInDay - (internalGet(UCAL_ZONE_OFFSET) + internalGet(UCAL_DST_OFFSET));
+    } else {
         // Compute the time zone offset and DST offset.  There are two potential
         // ambiguities here.  We'll assume a 2:00 am (wall time) switchover time
         // for discussion purposes here.
-        // 1. The transition into DST.  Here, a designated time of 2:00 am - 2:59 am
-        //    can be in standard or in DST depending.  However, 2:00 am is an invalid
-        //    representation (the representation jumps from 1:59:59 am Std to 3:00:00 am DST).
-        //    We assume standard time, that is, 2:30 am is interpreted as 3:30 am DST.
-        // 2. The transition out of DST.  Here, a designated time of 1:00 am - 1:59 am
-        //    can be in standard or DST.  Both are valid representations (the rep
-        //    jumps from 1:59:59 DST to 1:00:00 Std).
-        //    Again, we assume standard time, that is, 1:30 am is interpreted as 1:30 am Std.
+        //
+        // 1. The positive offset change such as transition into DST.
+        //    Here, a designated time of 2:00 am - 2:59 am does not actually exist.
+        //    For this case, skippedWallTime option specifies the behavior.
+        //    For example, 2:30 am is interpreted as;
+        //      - WALLTIME_LAST(default): 3:30 am (DST) (interpreting 2:30 am as 31 minutes after 1:59 am (STD))
+        //      - WALLTIME_FIRST: 1:30 am (STD) (interpreting 2:30 am as 30 minutes before 3:00 am (DST))
+        //      - WALLTIME_NEXT_VALID: 3:00 am (DST) (next valid time after 2:30 am on a wall clock)
+        // 2. The negative offset change such as transition out of DST.
+        //    Here, a designated time of 1:00 am - 1:59 am can be in standard or DST.  Both are valid
+        //    representations (the rep jumps from 1:59:59 DST to 1:00:00 Std).
+        //    For this case, repeatedWallTime option specifies the behavior.
+        //    For example, 1:30 am is interpreted as;
+        //      - WALLTIME_LAST(default): 1:30 am (STD) - latter occurrence
+        //      - WALLTIME_FIRST: 1:30 am (DST) - former occurrence
+        //
+        // In addition to above, when calendar is strict (not default), wall time falls into
+        // the skipped time range will be processed as an error case.
+        //
+        // These special cases are mostly handled in #computeZoneOffset(long), except WALLTIME_NEXT_VALID
+        // at positive offset change. The protected method computeZoneOffset(long) is exposed to Calendar
+        // subclass implementations and marked as @stable. Strictly speaking, WALLTIME_NEXT_VALID
+        // should be also handled in the same place, but we cannot change the code flow without deprecating
+        // the protected method.
+        //
         // We use the TimeZone object, unless the user has explicitly set the ZONE_OFFSET
         // or DST_OFFSET fields; then we use those fields.
-        if (fStamp[UCAL_ZONE_OFFSET] >= ((int32_t)kMinimumUserStamp) ||
-            fStamp[UCAL_DST_OFFSET] >= ((int32_t)kMinimumUserStamp)) {
-                millisInDay -= internalGet(UCAL_ZONE_OFFSET) + internalGet(UCAL_DST_OFFSET);
-            } else {
-                millisInDay -= computeZoneOffset(millis, millisInDay,status);
-            }
 
-            internalSetTime(millis + millisInDay);
+        if (!isLenient() || fSkippedWallTime == UCAL_WALLTIME_NEXT_VALID) {
+            // When strict, invalidate a wall time falls into a skipped wall time range.
+            // When lenient and skipped wall time option is WALLTIME_NEXT_VALID,
+            // the result time will be adjusted to the next valid time (on wall clock).
+            int32_t zoneOffset = computeZoneOffset(millis, millisInDay, status);
+            UDate tmpTime = millis + millisInDay - zoneOffset;
+
+            int32_t raw, dst;
+            fZone->getOffset(tmpTime, FALSE, raw, dst, status);
+
+            if (U_SUCCESS(status)) {
+                // zoneOffset != (raw + dst) only when the given wall time fall into
+                // a skipped wall time range caused by positive zone offset transition.
+                if (zoneOffset != (raw + dst)) {
+                    if (!isLenient()) {
+                        status = U_ILLEGAL_ARGUMENT_ERROR;
+                    } else {
+                        U_ASSERT(fSkippedWallTime == UCAL_WALLTIME_NEXT_VALID);
+                        // 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;
+                        }
+                    }
+                } else {
+                    t = tmpTime;
+                }
+            }
+        } else {
+            t = millis + millisInDay - computeZoneOffset(millis, millisInDay, status);
+        }
+    }
+    if (U_SUCCESS(status)) {
+        internalSetTime(t);
+    }
 }
 
 /**
@@ -2641,12 +2799,49 @@ int32_t Calendar::computeMillisInDay() {
 */
 int32_t Calendar::computeZoneOffset(double millis, int32_t millisInDay, UErrorCode &ec) {
     int32_t rawOffset, dstOffset;
-    getTimeZone().getOffset(millis+millisInDay, TRUE, rawOffset, dstOffset, ec);
+    UDate wall = millis + millisInDay;
+    BasicTimeZone* btz = getBasicTimeZone();
+    if (btz) {
+        int duplicatedTimeOpt = (fRepeatedWallTime == UCAL_WALLTIME_FIRST) ? BasicTimeZone::kFormer : BasicTimeZone::kLatter;
+        int nonExistingTimeOpt = (fSkippedWallTime == UCAL_WALLTIME_FIRST) ? BasicTimeZone::kLatter : BasicTimeZone::kFormer;
+        btz->getOffsetFromLocal(wall, nonExistingTimeOpt, duplicatedTimeOpt, rawOffset, dstOffset, ec);
+    } else {
+        const TimeZone& tz = getTimeZone();
+        // By default, TimeZone::getOffset behaves UCAL_WALLTIME_LAST for both.
+        tz.getOffset(wall, TRUE, rawOffset, dstOffset, ec);
+
+        UBool sawRecentNegativeShift = FALSE;
+        if (fRepeatedWallTime == UCAL_WALLTIME_FIRST) {
+            // Check if the given wall time falls into repeated time range
+            UDate tgmt = wall - (rawOffset + dstOffset);
+
+            // Any negative zone transition within last 6 hours?
+            // Note: The maximum historic negative zone transition is -3 hours in the tz database.
+            // 6 hour window would be sufficient for this purpose.
+            int32_t tmpRaw, tmpDst;
+            tz.getOffset(tgmt - 6*60*60*1000, FALSE, tmpRaw, tmpDst, ec);
+            int32_t offsetDelta = (rawOffset + dstOffset) - (tmpRaw + tmpDst);
+
+            U_ASSERT(offsetDelta < -6*60*60*1000);
+            if (offsetDelta < 0) {
+                sawRecentNegativeShift = TRUE;
+                // Negative shift within last 6 hours. When UCAL_WALLTIME_FIRST is used and the given wall time falls
+                // into the repeated time range, use offsets before the transition.
+                // Note: If it does not fall into the repeated time range, offsets remain unchanged below.
+                tz.getOffset(wall + offsetDelta, TRUE, rawOffset, dstOffset, ec);
+            }
+        }
+        if (!sawRecentNegativeShift && fSkippedWallTime == UCAL_WALLTIME_FIRST) {
+            // When skipped wall time option is WALLTIME_FIRST,
+            // recalculate offsets from the resolved time (non-wall).
+            // When the given wall time falls into skipped wall time,
+            // the offsets will be based on the zone offsets AFTER
+            // the transition (which means, earliest possibe interpretation).
+            UDate tgmt = wall - (rawOffset + dstOffset);
+            tz.getOffset(tgmt, FALSE, rawOffset, dstOffset, ec);
+        }
+    }
     return rawOffset + dstOffset;
-    // Note: Because we pass in wall millisInDay, rather than
-    // standard millisInDay, we interpret "1:00 am" on the day
-    // of cessation of DST as "1:00 am Std" (assuming the time
-    // of cessation is 2:00 am).
 }
 
 int32_t Calendar::computeJulianDay() 
@@ -3046,6 +3241,7 @@ Calendar::getActualMaximum(UCalendarDateFields field, UErrorCode& status) const
             if(U_FAILURE(status)) return 0;
             Calendar *cal = clone();
             if(!cal) { status = U_MEMORY_ALLOCATION_ERROR; return 0; }
+            cal->setLenient(TRUE);
             cal->prepareGetActual(field,FALSE,status);
             result = handleGetMonthLength(cal->get(UCAL_EXTENDED_YEAR, status), cal->get(UCAL_MONTH, status));
             delete cal;
@@ -3057,6 +3253,7 @@ Calendar::getActualMaximum(UCalendarDateFields field, UErrorCode& status) const
             if(U_FAILURE(status)) return 0;
             Calendar *cal = clone();
             if(!cal) { status = U_MEMORY_ALLOCATION_ERROR; return 0; }
+            cal->setLenient(TRUE);
             cal->prepareGetActual(field,FALSE,status);
             result = handleGetYearLength(cal->get(UCAL_EXTENDED_YEAR, status));
             delete cal;
@@ -3178,6 +3375,11 @@ int32_t Calendar::getActualHelper(UCalendarDateFields field, int32_t startValue,
     if(U_FAILURE(status)) return startValue;
     Calendar *work = clone();
     if(!work) { status = U_MEMORY_ALLOCATION_ERROR; return startValue; }
+
+    // need to resolve time here, otherwise, fields set for actual limit
+    // may cause conflict with fields previously set (but not yet resolved).
+    work->complete(status);
+
     work->setLenient(TRUE);
     work->prepareGetActual(field, delta < 0, status);
 
@@ -3225,15 +3427,6 @@ int32_t Calendar::getActualHelper(UCalendarDateFields field, int32_t startValue,
 void
 Calendar::setWeekData(const Locale& desiredLocale, const char *type, UErrorCode& status)
 {
-    // Read the week count data from the resource bundle.  This should
-    // have the form:
-    //
-    //   DateTimeElements:intvector {
-    //      1,    // first day of week
-    //      1     // min days in week
-    //   }
-    //   Both have a range of 1..7
-
 
     if (U_FAILURE(status)) return;
 
@@ -3259,7 +3452,7 @@ Calendar::setWeekData(const Locale& desiredLocale, const char *type, UErrorCode&
     Locale min = Locale::createFromName(minLocaleID);
     Locale useLocale;
     if ( uprv_strlen(desiredLocale.getCountry()) == 0 || 
-         uprv_strlen(desiredLocale.getScript()) > 0 && uprv_strlen(min.getScript()) == 0 ) {
+         (uprv_strlen(desiredLocale.getScript()) > 0 && uprv_strlen(min.getScript()) == 0) ) {
         char maxLocaleID[ULOC_FULLNAME_CAPACITY] = { 0 };
         myStatus = U_ZERO_ERROR;
         uloc_addLikelySubtags(desiredLocale.getName(),maxLocaleID,ULOC_FULLNAME_CAPACITY,&myStatus);
@@ -3269,62 +3462,58 @@ Calendar::setWeekData(const Locale& desiredLocale, const char *type, UErrorCode&
         useLocale = Locale(desiredLocale);
     }
  
-    CalendarData calData(useLocale, type, status);
-    // If the resource data doesn't seem to be present at all, then use last-resort
-    // hard-coded data.
-    UResourceBundle *dateTimeElements = calData.getByKey(gDateTimeElements, status);
+    /* The code here is somewhat of a hack, since week data and weekend data aren't really tied to 
+       a specific calendar, they aren't truly locale data.  But this is the only place where valid and
+       actual locale can be set, so we take a shot at it here by loading a representative resource
+       from the calendar data.  The code used to use the dateTimeElements resource to get first day
+       of week data, but this was moved to supplemental data under ticket 7755. (JCE) */
+
+    CalendarData calData(useLocale,type,status);
+    UResourceBundle *monthNames = calData.getByKey(gMonthNames,status);
+    if (U_SUCCESS(status)) {
+        U_LOCALE_BASED(locBased,*this);
+        locBased.setLocaleIDs(ures_getLocaleByType(monthNames, ULOC_VALID_LOCALE, &status),
+                              ures_getLocaleByType(monthNames, ULOC_ACTUAL_LOCALE, &status));
+    } else {
+        status = U_USING_FALLBACK_WARNING;
+        return;
+    }
+
+    
+    // Read week data values from supplementalData week data
+    UResourceBundle *rb = ures_openDirect(NULL, "supplementalData", &status);
+    ures_getByKey(rb, "weekData", rb, &status);
+    UResourceBundle *weekData = ures_getByKey(rb, useLocale.getCountry(), NULL, &status);
+    if (status == U_MISSING_RESOURCE_ERROR && rb != NULL) {
+        status = U_ZERO_ERROR;
+        weekData = ures_getByKey(rb, "001", NULL, &status);
+    }
 
     if (U_FAILURE(status)) {
 #if defined (U_DEBUG_CALDATA)
-        fprintf(stderr, " Failure loading dateTimeElements = %s\n", u_errorName(status));
+        fprintf(stderr, " Failure loading weekData from supplemental = %s\n", u_errorName(status));
 #endif
         status = U_USING_FALLBACK_WARNING;
     } else {
-        U_LOCALE_BASED(locBased, *this);
-        locBased.setLocaleIDs(ures_getLocaleByType(dateTimeElements, ULOC_VALID_LOCALE, &status),
-            ures_getLocaleByType(dateTimeElements, ULOC_ACTUAL_LOCALE, &status));
-        if (U_SUCCESS(status)) {
-#if defined (U_DEBUG_CAL)
-            fprintf(stderr, " Valid=%s, Actual=%s\n", validLocale, actualLocale);
-#endif
-            int32_t arrLen;
-            const int32_t *dateTimeElementsArr = ures_getIntVector(dateTimeElements, &arrLen, &status);
-    
-            if(U_SUCCESS(status) && arrLen == 2
-                && 1 <= dateTimeElementsArr[0] && dateTimeElementsArr[0] <= 7
-                && 1 <= dateTimeElementsArr[1] && dateTimeElementsArr[1] <= 7)
-            {
-                fFirstDayOfWeek = (UCalendarDaysOfWeek)dateTimeElementsArr[0];
-                fMinimalDaysInFirstWeek = (uint8_t)dateTimeElementsArr[1];
-            }
-            else {
-                status = U_INVALID_FORMAT_ERROR;
-            }
-        }
-    }
-    // do NOT close dateTimeElements
-    
-    if (U_SUCCESS(status)) {
-        UResourceBundle *weekend = calData.getByKey(gWeekend, status);
-        if (U_FAILURE(status)) {
-            status = U_USING_FALLBACK_WARNING;
+        int32_t arrLen;
+        const int32_t *weekDataArr = ures_getIntVector(weekData,&arrLen,&status);
+        if( U_SUCCESS(status) && arrLen == 6
+                && 1 <= weekDataArr[0] && weekDataArr[0] <= 7
+                && 1 <= weekDataArr[1] && weekDataArr[1] <= 7
+                && 1 <= weekDataArr[2] && weekDataArr[2] <= 7
+                && 1 <= weekDataArr[4] && weekDataArr[4] <= 7) {
+            fFirstDayOfWeek = (UCalendarDaysOfWeek)weekDataArr[0];
+            fMinimalDaysInFirstWeek = (uint8_t)weekDataArr[1];
+            fWeekendOnset = (UCalendarDaysOfWeek)weekDataArr[2];
+            fWeekendOnsetMillis = weekDataArr[3];
+            fWeekendCease = (UCalendarDaysOfWeek)weekDataArr[4];
+            fWeekendCeaseMillis = weekDataArr[5];
         } else {
-            int32_t arrLen;
-            const int32_t *weekendArr = ures_getIntVector(weekend, &arrLen, &status);
-            if(U_SUCCESS(status) && arrLen >= 4
-                && 1 <= weekendArr[0] && weekendArr[0] <= 7
-                && 1 <= weekendArr[2] && weekendArr[2] <= 7)
-            {
-                fWeekendOnset = (UCalendarDaysOfWeek)weekendArr[0];
-                fWeekendOnsetMillis = weekendArr[1];
-                fWeekendCease = (UCalendarDaysOfWeek)weekendArr[2];
-                fWeekendCeaseMillis = weekendArr[3];
-            }
-            else {
-                status = U_INVALID_FORMAT_ERROR;
-            }
+            status = U_INVALID_FORMAT_ERROR;
         }
     }
+    ures_close(weekData);
+    ures_close(rb);
 }
 
 /**
@@ -3361,6 +3550,33 @@ Calendar::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const {
     return locBased.getLocaleID(type, status);
 }
 
+void
+Calendar::recalculateStamp() {
+    int32_t index;
+    int32_t currentValue;
+    int32_t j, i;
+
+    fNextStamp = 1;
+
+    for (j = 0; j < UCAL_FIELD_COUNT; j++) {
+        currentValue = STAMP_MAX;
+        index = -1;
+        for (i = 0; i < UCAL_FIELD_COUNT; i++) {
+            if (fStamp[i] > fNextStamp && fStamp[i] < currentValue) {
+                currentValue = fStamp[i];
+                index = i;
+            }
+        }
+
+        if (index >= 0) {
+            fStamp[index] = ++fNextStamp;
+        } else {
+            break;
+        }
+    }
+    fNextStamp++;
+}
+
 // Deprecated function. This doesn't need to be inline.
 void
 Calendar::internalSet(EDateFields field, int32_t value)
@@ -3368,6 +3584,17 @@ Calendar::internalSet(EDateFields field, int32_t value)
     internalSet((UCalendarDateFields) field, value);
 }
 
+BasicTimeZone*
+Calendar::getBasicTimeZone(void) const {
+    if (dynamic_cast<const OlsonTimeZone *>(fZone) != NULL
+        || dynamic_cast<const SimpleTimeZone *>(fZone) != NULL
+        || dynamic_cast<const RuleBasedTimeZone *>(fZone) != NULL
+        || dynamic_cast<const VTimeZone *>(fZone) != NULL) {
+        return (BasicTimeZone*)fZone;
+    }
+    return NULL;
+}
+
 U_NAMESPACE_END
 
 #endif /* #if !UCONFIG_NO_FORMATTING */