+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
-* Copyright (C) 1997-2012, International Business Machines Corporation and *
+* Copyright (C) 1997-2016, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
* File CALENDAR.CPP
*
-* Modification History:
+* Modification History:
*
* Date Name Description
* 02/03/97 clhuang Creation.
*******************************************************************************
*/
-#include <typeinfo> // for 'typeid' to work
+#include "utypeinfo.h" // for 'typeid' to work
#include "unicode/utypes.h"
#include "indiancal.h"
#include "chnsecal.h"
#include "coptccal.h"
+#include "dangical.h"
#include "ethpccal.h"
#include "unicode/calendar.h"
#include "cpputils.h"
#include "ustrenum.h"
#include "uassert.h"
#include "olsontz.h"
+#include "sharedcalendar.h"
+#include "unifiedcache.h"
+#include "ulocimp.h"
#if !UCONFIG_NO_SERVICE
static icu::ICULocaleService* gService = NULL;
+static icu::UInitOnce gServiceInitOnce = U_INITONCE_INITIALIZER;
#endif
// INTERNAL - for cleanup
delete gService;
gService = NULL;
}
+ gServiceInitOnce.reset();
#endif
return TRUE;
}
*/
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
"ethiopic",
"ethiopic-amete-alem",
"iso8601",
+ "dangi",
+ "islamic-umalqura",
+ "islamic-tbla",
+ "islamic-rgsa",
NULL
};
CALTYPE_COPTIC,
CALTYPE_ETHIOPIC,
CALTYPE_ETHIOPIC_AMETE_ALEM,
- CALTYPE_ISO8601
+ CALTYPE_ISO8601,
+ CALTYPE_DANGI,
+ CALTYPE_ISLAMIC_UMALQURA,
+ CALTYPE_ISLAMIC_TBLA,
+ CALTYPE_ISLAMIC_RGSA
} ECalType;
U_NAMESPACE_BEGIN
+SharedCalendar::~SharedCalendar() {
+ delete ptr;
+}
+
+template<> U_I18N_API
+const SharedCalendar *LocaleCacheKey<SharedCalendar>::createObject(
+ const void * /*unusedCreationContext*/, UErrorCode &status) const {
+ Calendar *calendar = Calendar::makeInstance(fLoc, status);
+ if (U_FAILURE(status)) {
+ return NULL;
+ }
+ SharedCalendar *shared = new SharedCalendar(calendar);
+ if (shared == NULL) {
+ delete calendar;
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return NULL;
+ }
+ shared->addRef();
+ return shared;
+}
+
static ECalType getCalendarType(const char *s) {
for (int i = 0; gCalTypes[i] != NULL; i++) {
if (uprv_stricmp(s, gCalTypes[i]) == 0) {
// when calendar keyword is not available or not supported, read supplementalData
// to get the default calendar type for the locale's region
char region[ULOC_COUNTRY_CAPACITY];
- int32_t regionLen = 0;
- regionLen = uloc_getCountry(canonicalName, region, sizeof(region) - 1, &status);
- if (regionLen == 0) {
- char fullLoc[256];
- uloc_addLikelySubtags(locid, fullLoc, sizeof(fullLoc) - 1, &status);
- regionLen = uloc_getCountry(fullLoc, region, sizeof(region) - 1, &status);
- }
+ (void)ulocimp_getRegionForSupplementalData(canonicalName, TRUE, region, sizeof(region), &status);
if (U_FAILURE(status)) {
return CALTYPE_GREGORIAN;
}
- region[regionLen] = 0;
// Read preferred calendar values from supplementalData calendarPreference
UResourceBundle *rb = ures_openDirect(NULL, "supplementalData", &status);
case CALTYPE_PERSIAN:
cal = new PersianCalendar(loc, status);
break;
+ case CALTYPE_ISLAMIC_TBLA:
+ cal = new IslamicCalendar(loc, status, IslamicCalendar::TBLA);
+ break;
case CALTYPE_ISLAMIC_CIVIL:
cal = new IslamicCalendar(loc, status, IslamicCalendar::CIVIL);
break;
+ case CALTYPE_ISLAMIC_RGSA:
+ // default any region specific not handled individually to islamic
case CALTYPE_ISLAMIC:
cal = new IslamicCalendar(loc, status, IslamicCalendar::ASTRONOMICAL);
break;
+ case CALTYPE_ISLAMIC_UMALQURA:
+ cal = new IslamicCalendar(loc, status, IslamicCalendar::UMALQURA);
+ break;
case CALTYPE_HEBREW:
cal = new HebrewCalendar(loc, status);
break;
cal->setFirstDayOfWeek(UCAL_MONDAY);
cal->setMinimalDaysInFirstWeek(4);
break;
+ case CALTYPE_DANGI:
+ cal = new DangiCalendar(loc, status);
+ break;
default:
status = U_UNSUPPORTED_ERROR;
}
static inline UBool
isCalendarServiceUsed() {
- UBool retVal;
- UMTX_CHECK(NULL, gService != NULL, retVal);
- return retVal;
+ return !gServiceInitOnce.isReset();
}
// -------------------------------------
-static ICULocaleService*
-getCalendarService(UErrorCode &status)
+static void U_CALLCONV
+initCalendarService(UErrorCode &status)
{
- UBool needInit;
- UMTX_CHECK(NULL, (UBool)(gService == NULL), needInit);
- if (needInit) {
#ifdef U_DEBUG_CALSVC
fprintf(stderr, "Spinning up Calendar Service\n");
#endif
- ICULocaleService * newservice = new CalendarService();
- if (newservice == NULL) {
+ ucln_i18n_registerCleanup(UCLN_I18N_CALENDAR, calendar_cleanup);
+ gService = new CalendarService();
+ if (gService == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
- return newservice;
+ return;
}
#ifdef U_DEBUG_CALSVC
fprintf(stderr, "Registering classes..\n");
#endif
// Register all basic instances.
- newservice->registerFactory(new BasicCalendarFactory(),status);
+ gService->registerFactory(new BasicCalendarFactory(),status);
#ifdef U_DEBUG_CALSVC
fprintf(stderr, "Done..\n");
#ifdef U_DEBUG_CALSVC
fprintf(stderr, "err (%s) registering classes, deleting service.....\n", u_errorName(status));
#endif
- delete newservice;
- newservice = NULL;
+ delete gService;
+ gService = NULL;
+ }
}
- if (newservice) {
- umtx_lock(NULL);
- if (gService == NULL) {
- gService = newservice;
- newservice = NULL;
- }
- umtx_unlock(NULL);
- }
- if (newservice) {
- delete newservice;
- } else {
- // we won the contention - we can register the cleanup.
- ucln_i18n_registerCleanup(UCLN_I18N_CALENDAR, calendar_cleanup);
- }
- }
+static ICULocaleService*
+getCalendarService(UErrorCode &status)
+{
+ umtx_initOnce(gServiceInitOnce, &initCalendarService, status);
return gService;
}
{ 1, 1, 7, 7 }, // DOW_LOCAL
{/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // EXTENDED_YEAR
{ -0x7F000000, -0x7F000000, 0x7F000000, 0x7F000000 }, // JULIAN_DAY
- { 0, 0, 24*kOneHour-1, 24*kOneHour-1 }, // MILLISECONDS_IN_DAY
- { 0, 0, 1, 1 }, // IS_LEAP_MONTH
+ { 0, 0, 24*kOneHour-1, 24*kOneHour-1 }, // MILLISECONDS_IN_DAY
+ { 0, 0, 1, 1 }, // IS_LEAP_MONTH
};
// Resource bundle tags read by this class
+static const char gCalendar[] = "calendar";
static const char gMonthNames[] = "monthNames";
+static const char gGregorian[] = "gregorian";
// Data flow in Calendar
// ---------------------
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;
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",
clear();
fZone = zone;
-
setWeekData(aLocale, NULL, success);
}
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);
}
Calendar::Calendar(const Calendar &source)
: UObject(source)
{
- fZone = 0;
+ fZone = NULL;
*this = source;
}
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();
}
// Note: this is the bottleneck that actually calls the service routines.
-Calendar* U_EXPORT2
-Calendar::createInstance(TimeZone* zone, const Locale& aLocale, UErrorCode& success)
-{
+Calendar * U_EXPORT2
+Calendar::makeInstance(const Locale& aLocale, UErrorCode& success) {
if (U_FAILURE(success)) {
return NULL;
}
Calendar* c = NULL;
if(U_FAILURE(success) || !u) {
- delete zone;
if(U_SUCCESS(success)) { // Propagate some kind of err
success = U_INTERNAL_PROGRAM_ERROR;
}
c = (Calendar*)getCalendarService(success)->get(l, LocaleKey::KIND_ANY, &actualLoc2, success);
if(U_FAILURE(success) || !c) {
- delete zone;
if(U_SUCCESS(success)) {
success = U_INTERNAL_PROGRAM_ERROR; // Propagate some err
}
#endif
success = U_MISSING_RESOURCE_ERROR; // requested a calendar type which could NOT be found.
delete c;
- delete zone;
return NULL;
}
#ifdef U_DEBUG_CALSVC
c = (Calendar*)u;
}
+ return c;
+}
+
+Calendar* U_EXPORT2
+Calendar::createInstance(TimeZone* zone, const Locale& aLocale, UErrorCode& success)
+{
+ LocalPointer<TimeZone> zonePtr(zone);
+ const SharedCalendar *shared = NULL;
+ UnifiedCache::getByLocale(aLocale, shared, success);
+ if (U_FAILURE(success)) {
+ return NULL;
+ }
+ Calendar *c = (*shared)->clone();
+ shared->removeRef();
+ if (c == NULL) {
+ success = U_MEMORY_ALLOCATION_ERROR;
+ return NULL;
+ }
+
// Now, reset calendar to default state:
- c->adoptTimeZone(zone); // Set the correct time zone
+ c->adoptTimeZone(zonePtr.orphan()); // Set the correct time zone
c->setTimeInMillis(getNow(), success); // let the new calendar have the current time.
return c;
// -------------------------------------
+void U_EXPORT2
+Calendar::getCalendarTypeFromLocale(
+ const Locale &aLocale,
+ char *typeBuffer,
+ int32_t typeBufferSize,
+ UErrorCode &success) {
+ const SharedCalendar *shared = NULL;
+ UnifiedCache::getByLocale(aLocale, shared, success);
+ if (U_FAILURE(success)) {
+ return;
+ }
+ uprv_strncpy(typeBuffer, (*shared)->getType(), typeBufferSize);
+ shared->removeRef();
+ if (typeBuffer[typeBufferSize - 1]) {
+ success = U_BUFFER_OVERFLOW_ERROR;
+ }
+}
+
UBool
Calendar::operator==(const Calendar& that) const
{
/**
* Sets this Calendar's current time from the given long value.
+* A status of U_ILLEGAL_ARGUMENT_ERROR is set when millis is
+* outside the range permitted by a Calendar object when not in lenient mode.
+* when in lenient mode the out of range values are pinned to their respective min/max.
* @param date the new time in UTC milliseconds from the epoch.
*/
void
return;
if (millis > MAX_MILLIS) {
- millis = MAX_MILLIS;
+ if(isLenient()) {
+ millis = MAX_MILLIS;
+ } else {
+ status = U_ILLEGAL_ARGUMENT_ERROR;
+ return;
+ }
} else if (millis < MIN_MILLIS) {
- millis = MIN_MILLIS;
+ if(isLenient()) {
+ millis = MIN_MILLIS;
+ } else {
+ status = U_ILLEGAL_ARGUMENT_ERROR;
+ return;
+ }
}
fTime = millis;
set(UCAL_SECOND, second);
}
+// -------------------------------------
+// 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()
{
+ // special behavior for chinese/dangi to set to beginning of current era;
+ // need to do here and not in ChineseCalendar since clear is not virtual.
+ int32_t eraNow = 0;
+ if (dynamic_cast<const ChineseCalendar*>(this)!=NULL) {
+ UErrorCode status = U_ZERO_ERROR;
+ setTimeInMillis(getNow(), status);
+ eraNow = get(UCAL_ERA, status); // sets 0 if error
+ }
+
for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) {
fFields[i] = 0; // Must do this; other code depends on it
fStamp[i] = kUnset;
}
fIsTimeSet = fAreFieldsSet = fAreAllFieldsSet = fAreFieldsVirtuallySet = FALSE;
// fTime is not 'cleared' - may be used if no fields are set.
+ if (eraNow > 0) {
+ set(UCAL_ERA, eraNow);
+ }
}
// -------------------------------------
case UCAL_YEAR:
case UCAL_YEAR_WOY:
+ {
+ // * If era==0 and years go backwards in time, change sign of amount.
+ // * Until we have new API per #9393, we temporarily hardcode knowledge of
+ // which calendars have era 0 years that go backwards.
+ UBool era0WithYearsThatGoBackwards = FALSE;
+ int32_t era = get(UCAL_ERA, status);
+ if (era == 0) {
+ const char * calType = getType();
+ if ( uprv_strcmp(calType,"gregorian")==0 || uprv_strcmp(calType,"roc")==0 || uprv_strcmp(calType,"coptic")==0 ) {
+ amount = -amount;
+ era0WithYearsThatGoBackwards = TRUE;
+ }
+ }
+ int32_t newYear = internalGet(field) + amount;
+ if (era > 0 || newYear >= 1) {
+ int32_t maxYear = getActualMaximum(field, status);
+ if (maxYear < 32768) {
+ // this era has real bounds, roll should wrap years
+ if (newYear < 1) {
+ newYear = maxYear - ((-newYear) % maxYear);
+ } else if (newYear > maxYear) {
+ newYear = ((newYear - 1) % maxYear) + 1;
+ }
+ // else era is unbounded, just pin low year instead of wrapping
+ } else if (newYear < 1) {
+ newYear = 1;
+ }
+ // else we are in era 0 with newYear < 1;
+ // calendars with years that go backwards must pin the year value at 0,
+ // other calendars can have years < 0 in era 0
+ } else if (era0WithYearsThatGoBackwards) {
+ newYear = 1;
+ }
+ set(field, newYear);
+ pinField(UCAL_MONTH,status);
+ pinField(UCAL_DAY_OF_MONTH,status);
+ return;
+ }
+
case UCAL_EXTENDED_YEAR:
// Rolling the year can involve pinning the DAY_OF_MONTH.
set(field, internalGet(field) + amount);
// 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
// <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:
return;
case UCAL_YEAR:
- case UCAL_EXTENDED_YEAR:
case UCAL_YEAR_WOY:
+ {
+ // * If era=0 and years go backwards in time, change sign of amount.
+ // * Until we have new API per #9393, we temporarily hardcode knowledge of
+ // which calendars have era 0 years that go backwards.
+ // * Note that for UCAL_YEAR (but not UCAL_YEAR_WOY) we could instead handle
+ // this by applying the amount to the UCAL_EXTENDED_YEAR field; but since
+ // we would still need to handle UCAL_YEAR_WOY as below, might as well
+ // also handle UCAL_YEAR the same way.
+ int32_t era = get(UCAL_ERA, status);
+ if (era == 0) {
+ const char * calType = getType();
+ if ( uprv_strcmp(calType,"gregorian")==0 || uprv_strcmp(calType,"roc")==0 || uprv_strcmp(calType,"coptic")==0 ) {
+ amount = -amount;
+ }
+ }
+ }
+ // Fall through into normal handling
+ U_FALLTHROUGH;
+ case UCAL_EXTENDED_YEAR:
case UCAL_MONTH:
{
UBool oldLenient = isLenient();
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:
// ") 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.
- double t = internalGetTime();
- setTimeInMillis(t + prevOffset - newOffset, 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;
+ }
+ }
}
}
}
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
const TimeZone&
Calendar::getTimeZone() const
{
+ U_ASSERT(fZone != NULL);
return *fZone;
}
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;
}
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 || dayOfWeek > fWeekendCease) {
return UCAL_WEEKDAY;
return (fWeekendOnsetMillis == 0) ? UCAL_WEEKEND : UCAL_WEEKEND_ONSET;
}
if (dayOfWeek == fWeekendCease) {
- return (fWeekendCeaseMillis == 0) ? UCAL_WEEKDAY : UCAL_WEEKEND_CEASE;
+ return (fWeekendCeaseMillis >= 86400000) ? UCAL_WEEKEND : UCAL_WEEKEND_CEASE;
}
return UCAL_WEEKEND;
}
(millisInDay < transitionMillis);
}
// else fall through, return FALSE
+ U_FALLTHROUGH;
}
default:
break;
UCalendarDateFields Calendar::resolveFields(const UFieldResolutionTable* precedenceTable) {
int32_t bestField = UCAL_FIELD_COUNT;
+ int32_t tempBestField;
+ UBool restoreWeekOfInternalSet = FALSE;
+ if (fStamp[UCAL_DAY_OF_WEEK] >= kMinimumUserStamp &&
+ fStamp[UCAL_DATE] >= kMinimumUserStamp &&
+ fStamp[UCAL_MONTH] >= kMinimumUserStamp &&
+ fStamp[UCAL_WEEK_OF_YEAR] == kInternallySet &&
+ fStamp[UCAL_WEEK_OF_MONTH] == kInternallySet &&
+ fStamp[UCAL_DAY_OF_WEEK_IN_MONTH] == kInternallySet) {
+ int32_t monthStampDelta = fStamp[UCAL_DAY_OF_WEEK] - fStamp[UCAL_MONTH];
+ int32_t dateStampDelta = fStamp[UCAL_DAY_OF_WEEK] - fStamp[UCAL_DATE];
+ if ( monthStampDelta >= 1 && monthStampDelta <= 3 && dateStampDelta >= 1 && dateStampDelta <= 3 ) {
+ // If UCAL_MONTH, UCAL_DATE and UCAL_DAY_OF_WEEK are all explicitly set nearly one after the
+ // other (as when parsing a single date format), with UCAL_DAY_OF_WEEK set most recently, and
+ // if UCAL_WEEK_OF_YEAR, UCAL_WEEK_OF_MONTH, and UCAL_DAY_OF_WEEK_IN_MONTH are all only
+ // implicitly set (as from setTimeInMillis), then for the calculations in this call temporarily
+ // treat UCAL_WEEK_OF_YEAR, UCAL_WEEK_OF_MONTH, and UCAL_DAY_OF_WEEK_IN_MONTH as unset so they
+ // don't combine with UCAL_DAY_OF_WEEK to override the date in UCAL_MONTH & UCAL_DATE. All of
+ // these conditions are to avoid messing up the case of parsing a format with UCAL_DAY_OF_WEEK
+ // alone or in combination with other fields besides UCAL_MONTH, UCAL_DATE. Note: the current
+ // stamp value is incremented each time Calendar::set is called to explicitly set a field value.
+ fStamp[UCAL_WEEK_OF_YEAR] = kUnset;
+ fStamp[UCAL_WEEK_OF_MONTH] = kUnset;
+ fStamp[UCAL_DAY_OF_WEEK_IN_MONTH] = kUnset;
+ restoreWeekOfInternalSet = TRUE;
+ }
+ }
for (int32_t g=0; precedenceTable[g][0][0] != -1 && (bestField == UCAL_FIELD_COUNT); ++g) {
int32_t bestStamp = kUnset;
for (int32_t l=0; precedenceTable[g][l][0] != -1; ++l) {
}
// Record new maximum stamp & field no.
if (lineStamp > bestStamp) {
- bestStamp = lineStamp;
- bestField = precedenceTable[g][l][0]; // First field refers to entire line
+ tempBestField = precedenceTable[g][l][0]; // First field refers to entire line
+ if (tempBestField >= kResolveRemap) {
+ tempBestField &= (kResolveRemap-1);
+ // This check is needed to resolve some issues with UCAL_YEAR precedence mapping
+ if (tempBestField != UCAL_DATE || (fStamp[UCAL_WEEK_OF_MONTH] < fStamp[tempBestField])) {
+ bestField = tempBestField;
+ }
+ } else {
+ bestField = tempBestField;
+ }
+
+ if (bestField == tempBestField) {
+ bestStamp = lineStamp;
+ }
}
linesInGroup:
;
}
}
- return (UCalendarDateFields)( (bestField>=kResolveRemap)?(bestField&(kResolveRemap-1)):bestField );
+ if (restoreWeekOfInternalSet) {
+ // Restore the field stamps temporarily unset above.
+ fStamp[UCAL_WEEK_OF_YEAR] = kInternallySet;
+ fStamp[UCAL_WEEK_OF_MONTH] = kInternallySet;
+ fStamp[UCAL_DAY_OF_WEEK_IN_MONTH] = kInternallySet;
+ }
+ return (UCalendarDateFields)bestField;
}
const UFieldResolutionTable Calendar::kDatePrecedence[] =
// 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 {
}
}
+/**
+ * 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
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 )
// we're not possibly in the last week -must be ywoy
return yearWoy;
}
- break;
case UCAL_DATE:
if((internalGet(UCAL_MONTH)==0) &&
//within 1st week and in this month..
//return yearWoy+1;
return yearWoy;
- break;
default: // assume the year is appropriate
return yearWoy;
- break;
}
-
-#if defined (U_DEBUG_CAL)
- fprintf(stderr, "%s:%d - forgot a return on field %s\n", __FILE__, __LINE__, fldName(bestField));
-#endif
-
- return yearWoy;
}
int32_t Calendar::handleGetMonthLength(int32_t extendedYear, int32_t month) const
case UCAL_YEAR_WOY:
set(UCAL_WEEK_OF_YEAR, getGreatestMinimum(UCAL_WEEK_OF_YEAR));
-
+ U_FALLTHROUGH;
case UCAL_MONTH:
set(UCAL_DATE, getGreatestMinimum(UCAL_DATE));
break;
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);
+ // Get the monthNames resource bundle for the calendar 'type'. Fallback to gregorian if the resource is not
+ // found.
+ LocalUResourceBundlePointer calData(ures_open(NULL, useLocale.getBaseName(), &status));
+ ures_getByKey(calData.getAlias(), gCalendar, calData.getAlias(), &status);
+
+ LocalUResourceBundlePointer monthNames;
+ if (type != NULL && *type != '\0' && uprv_strcmp(type, gGregorian) != 0) {
+ monthNames.adoptInstead(ures_getByKeyWithFallback(calData.getAlias(), type, NULL, &status));
+ ures_getByKeyWithFallback(monthNames.getAlias(), gMonthNames,
+ monthNames.getAlias(), &status);
+ }
+
+ if (monthNames.isNull() || status == U_MISSING_RESOURCE_ERROR) {
+ status = U_ZERO_ERROR;
+ monthNames.adoptInstead(ures_getByKeyWithFallback(calData.getAlias(), gGregorian,
+ monthNames.orphan(), &status));
+ ures_getByKeyWithFallback(monthNames.getAlias(), gMonthNames,
+ monthNames.getAlias(), &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));
+ locBased.setLocaleIDs(ures_getLocaleByType(monthNames.getAlias(), ULOC_VALID_LOCALE, &status),
+ ures_getLocaleByType(monthNames.getAlias(), ULOC_ACTUAL_LOCALE, &status));
} else {
status = U_USING_FALLBACK_WARNING;
return;
}
-
+ char region[ULOC_COUNTRY_CAPACITY];
+ (void)ulocimp_getRegionForSupplementalData(desiredLocale.getName(), TRUE, region, sizeof(region), &status);
+
// 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);
+ UResourceBundle *weekData = ures_getByKey(rb, region, 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 weekData from supplemental = %s\n", u_errorName(status));
-#endif
status = U_USING_FALLBACK_WARNING;
} else {
int32_t arrLen;
//eof
+