]> git.saurik.com Git - apple/icu.git/blame - icuSources/i18n/calendar.cpp
ICU-511.32.tar.gz
[apple/icu.git] / icuSources / i18n / calendar.cpp
CommitLineData
b75a7d8f
A
1/*
2*******************************************************************************
e4f10fab 3* Copyright (C) 1997-2014, International Business Machines Corporation and *
b75a7d8f
A
4* others. All Rights Reserved. *
5*******************************************************************************
6*
7* File CALENDAR.CPP
8*
9* Modification History:
10*
11* Date Name Description
12* 02/03/97 clhuang Creation.
13* 04/22/97 aliu Cleaned up, fixed memory leak, made
14* setWeekCountData() more robust.
15* Moved platform code to TPlatformUtilities.
16* 05/01/97 aliu Made equals(), before(), after() arguments const.
17* 05/20/97 aliu Changed logic of when to compute fields and time
18* to fix bugs.
19* 08/12/97 aliu Added equivalentTo. Misc other fixes.
20* 07/28/98 stephen Sync up with JDK 1.2
21* 09/02/98 stephen Sync with JDK 1.2 8/31 build (getActualMin/Max)
22* 03/17/99 stephen Changed adoptTimeZone() - now fAreFieldsSet is
23* set to FALSE to force update of time.
24*******************************************************************************
25*/
26
51004dcb 27#include "utypeinfo.h" // for 'typeid' to work
729e4ab9 28
b75a7d8f
A
29#include "unicode/utypes.h"
30
31#if !UCONFIG_NO_FORMATTING
32
b75a7d8f 33#include "unicode/gregocal.h"
4388f060
A
34#include "unicode/basictz.h"
35#include "unicode/simpletz.h"
36#include "unicode/rbtz.h"
37#include "unicode/vtzone.h"
374ca955 38#include "gregoimp.h"
b75a7d8f 39#include "buddhcal.h"
46f4442e 40#include "taiwncal.h"
b75a7d8f 41#include "japancal.h"
374ca955
A
42#include "islamcal.h"
43#include "hebrwcal.h"
46f4442e
A
44#include "persncal.h"
45#include "indiancal.h"
46#include "chnsecal.h"
47#include "coptccal.h"
51004dcb 48#include "dangical.h"
46f4442e 49#include "ethpccal.h"
b75a7d8f
A
50#include "unicode/calendar.h"
51#include "cpputils.h"
73c04bcf 52#include "servloc.h"
b75a7d8f
A
53#include "ucln_in.h"
54#include "cstring.h"
374ca955
A
55#include "locbased.h"
56#include "uresimp.h"
729e4ab9 57#include "ustrenum.h"
4388f060
A
58#include "uassert.h"
59#include "olsontz.h"
b75a7d8f 60
374ca955 61#if !UCONFIG_NO_SERVICE
4388f060 62static icu::ICULocaleService* gService = NULL;
374ca955
A
63#endif
64
65// INTERNAL - for cleanup
66
67U_CDECL_BEGIN
68static UBool calendar_cleanup(void) {
69#if !UCONFIG_NO_SERVICE
46f4442e
A
70 if (gService) {
71 delete gService;
72 gService = NULL;
73 }
374ca955 74#endif
46f4442e 75 return TRUE;
374ca955
A
76}
77U_CDECL_END
b75a7d8f
A
78
79// ------------------------------------------
80//
81// Registration
82//
83//-------------------------------------------
84//#define U_DEBUG_CALSVC 1
85//
86
374ca955 87#if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL)
374ca955 88
46f4442e
A
89/**
90 * fldName was removed as a duplicate implementation.
91 * use udbg_ services instead,
4388f060
A
92 * which depend on include files and library from ../tools/toolutil, the following circular link:
93 * CPPFLAGS+=-I$(top_srcdir)/tools/toolutil
94 * LIBS+=$(LIBICUTOOLUTIL)
46f4442e 95 */
4388f060 96#include "udbgutil.h"
46f4442e 97#include <stdio.h>
374ca955
A
98
99/**
46f4442e
A
100* convert a UCalendarDateFields into a string - for debugging
101* @param f field enum
102* @return static string to the field name
103* @internal
104*/
374ca955 105
46f4442e
A
106const char* fldName(UCalendarDateFields f) {
107 return udbg_enumName(UDBG_UCalendarDateFields, (int32_t)f);
108}
73c04bcf
A
109
110#if UCAL_DEBUG_DUMP
111// from CalendarTest::calToStr - but doesn't modify contents.
112void ucal_dump(const Calendar &cal) {
113 cal.dump();
114}
115
116void Calendar::dump() const {
46f4442e
A
117 int i;
118 fprintf(stderr, "@calendar=%s, timeset=%c, fieldset=%c, allfields=%c, virtualset=%c, t=%.2f",
119 getType(), fIsTimeSet?'y':'n', fAreFieldsSet?'y':'n', fAreAllFieldsSet?'y':'n',
73c04bcf
A
120 fAreFieldsVirtuallySet?'y':'n',
121 fTime);
46f4442e
A
122
123 // can add more things here: DST, zone, etc.
73c04bcf 124 fprintf(stderr, "\n");
46f4442e
A
125 for(i = 0;i<UCAL_FIELD_COUNT;i++) {
126 int n;
127 const char *f = fldName((UCalendarDateFields)i);
128 fprintf(stderr, " %25s: %-11ld", f, fFields[i]);
129 if(fStamp[i] == kUnset) {
130 fprintf(stderr, " (unset) ");
131 } else if(fStamp[i] == kInternallySet) {
132 fprintf(stderr, " (internally set) ");
133 //} else if(fStamp[i] == kInternalDefault) {
134 // fprintf(stderr, " (internal default) ");
135 } else {
136 fprintf(stderr, " %%%d ", fStamp[i]);
137 }
138 fprintf(stderr, "\n");
139
140 }
73c04bcf
A
141}
142
143U_CFUNC void ucal_dump(UCalendar* cal) {
144 ucal_dump( *((Calendar*)cal) );
145}
b75a7d8f
A
146#endif
147
73c04bcf
A
148#endif
149
4388f060
A
150/* Max value for stamp allowable before recalculation */
151#define STAMP_MAX 10000
152
729e4ab9 153static const char * const gCalTypes[] = {
73c04bcf 154 "gregorian",
729e4ab9
A
155 "japanese",
156 "buddhist",
157 "roc",
158 "persian",
159 "islamic-civil",
160 "islamic",
161 "hebrew",
162 "chinese",
163 "indian",
164 "coptic",
165 "ethiopic",
166 "ethiopic-amete-alem",
4388f060 167 "iso8601",
51004dcb 168 "dangi",
729e4ab9 169 NULL
73c04bcf 170};
374ca955 171
729e4ab9
A
172// Must be in the order of gCalTypes above
173typedef enum ECalType {
174 CALTYPE_UNKNOWN = -1,
175 CALTYPE_GREGORIAN = 0,
176 CALTYPE_JAPANESE,
177 CALTYPE_BUDDHIST,
178 CALTYPE_ROC,
179 CALTYPE_PERSIAN,
180 CALTYPE_ISLAMIC_CIVIL,
181 CALTYPE_ISLAMIC,
182 CALTYPE_HEBREW,
183 CALTYPE_CHINESE,
184 CALTYPE_INDIAN,
185 CALTYPE_COPTIC,
186 CALTYPE_ETHIOPIC,
4388f060 187 CALTYPE_ETHIOPIC_AMETE_ALEM,
51004dcb
A
188 CALTYPE_ISO8601,
189 CALTYPE_DANGI
729e4ab9
A
190} ECalType;
191
374ca955
A
192U_NAMESPACE_BEGIN
193
729e4ab9
A
194static ECalType getCalendarType(const char *s) {
195 for (int i = 0; gCalTypes[i] != NULL; i++) {
196 if (uprv_stricmp(s, gCalTypes[i]) == 0) {
197 return (ECalType)i;
198 }
199 }
200 return CALTYPE_UNKNOWN;
201}
202
73c04bcf 203static UBool isStandardSupportedKeyword(const char *keyword, UErrorCode& status) {
374ca955
A
204 if(U_FAILURE(status)) {
205 return FALSE;
206 }
729e4ab9
A
207 ECalType calType = getCalendarType(keyword);
208 return (calType != CALTYPE_UNKNOWN);
374ca955
A
209}
210
73c04bcf
A
211static void getCalendarKeyword(const UnicodeString &id, char *targetBuffer, int32_t targetBufferSize) {
212 UnicodeString calendarKeyword = UNICODE_STRING_SIMPLE("calendar=");
213 int32_t calKeyLen = calendarKeyword.length();
214 int32_t keyLen = 0;
215
216 int32_t keywordIdx = id.indexOf((UChar)0x003D); /* '=' */
217 if (id[0] == 0x40/*'@'*/
218 && id.compareBetween(1, keywordIdx+1, calendarKeyword, 0, calKeyLen) == 0)
219 {
220 keyLen = id.extract(keywordIdx+1, id.length(), targetBuffer, targetBufferSize, US_INV);
221 }
222 targetBuffer[keyLen] = 0;
223}
224
729e4ab9
A
225static ECalType getCalendarTypeForLocale(const char *locid) {
226 UErrorCode status = U_ZERO_ERROR;
227 ECalType calType = CALTYPE_UNKNOWN;
374ca955 228
729e4ab9
A
229 //TODO: ULOC_FULL_NAME is out of date and too small..
230 char canonicalName[256];
231
232 // canonicalize, so grandfathered variant will be transformed to keywords
233 // e.g ja_JP_TRADITIONAL -> ja_JP@calendar=japanese
234 int32_t canonicalLen = uloc_canonicalize(locid, canonicalName, sizeof(canonicalName) - 1, &status);
235 if (U_FAILURE(status)) {
236 return CALTYPE_GREGORIAN;
237 }
238 canonicalName[canonicalLen] = 0; // terminate
239
240 char calTypeBuf[32];
241 int32_t calTypeBufLen;
242
243 calTypeBufLen = uloc_getKeywordValue(canonicalName, "calendar", calTypeBuf, sizeof(calTypeBuf) - 1, &status);
244 if (U_SUCCESS(status)) {
245 calTypeBuf[calTypeBufLen] = 0;
246 calType = getCalendarType(calTypeBuf);
247 if (calType != CALTYPE_UNKNOWN) {
248 return calType;
249 }
46f4442e 250 }
729e4ab9
A
251 status = U_ZERO_ERROR;
252
253 // when calendar keyword is not available or not supported, read supplementalData
254 // to get the default calendar type for the locale's region
255 char region[ULOC_COUNTRY_CAPACITY];
256 int32_t regionLen = 0;
257 regionLen = uloc_getCountry(canonicalName, region, sizeof(region) - 1, &status);
258 if (regionLen == 0) {
259 char fullLoc[256];
4388f060 260 uloc_addLikelySubtags(locid, fullLoc, sizeof(fullLoc) - 1, &status);
729e4ab9
A
261 regionLen = uloc_getCountry(fullLoc, region, sizeof(region) - 1, &status);
262 }
263 if (U_FAILURE(status)) {
264 return CALTYPE_GREGORIAN;
265 }
266 region[regionLen] = 0;
267
268 // Read preferred calendar values from supplementalData calendarPreference
269 UResourceBundle *rb = ures_openDirect(NULL, "supplementalData", &status);
270 ures_getByKey(rb, "calendarPreferenceData", rb, &status);
271 UResourceBundle *order = ures_getByKey(rb, region, NULL, &status);
272 if (status == U_MISSING_RESOURCE_ERROR && rb != NULL) {
273 status = U_ZERO_ERROR;
274 order = ures_getByKey(rb, "001", NULL, &status);
275 }
276
277 calTypeBuf[0] = 0;
278 if (U_SUCCESS(status) && order != NULL) {
279 // the first calender type is the default for the region
280 int32_t len = 0;
281 const UChar *uCalType = ures_getStringByIndex(order, 0, &len, &status);
282 if (len < (int32_t)sizeof(calTypeBuf)) {
283 u_UCharsToChars(uCalType, calTypeBuf, len);
284 *(calTypeBuf + len) = 0; // terminate;
285 calType = getCalendarType(calTypeBuf);
286 }
287 }
288
289 ures_close(order);
290 ures_close(rb);
291
292 if (calType == CALTYPE_UNKNOWN) {
293 // final fallback
294 calType = CALTYPE_GREGORIAN;
295 }
296 return calType;
374ca955
A
297}
298
729e4ab9
A
299static Calendar *createStandardCalendar(ECalType calType, const Locale &loc, UErrorCode& status) {
300 Calendar *cal = NULL;
301
302 switch (calType) {
303 case CALTYPE_GREGORIAN:
304 cal = new GregorianCalendar(loc, status);
305 break;
306 case CALTYPE_JAPANESE:
307 cal = new JapaneseCalendar(loc, status);
308 break;
309 case CALTYPE_BUDDHIST:
310 cal = new BuddhistCalendar(loc, status);
311 break;
312 case CALTYPE_ROC:
313 cal = new TaiwanCalendar(loc, status);
314 break;
315 case CALTYPE_PERSIAN:
316 cal = new PersianCalendar(loc, status);
317 break;
318 case CALTYPE_ISLAMIC_CIVIL:
319 cal = new IslamicCalendar(loc, status, IslamicCalendar::CIVIL);
320 break;
321 case CALTYPE_ISLAMIC:
322 cal = new IslamicCalendar(loc, status, IslamicCalendar::ASTRONOMICAL);
323 break;
324 case CALTYPE_HEBREW:
325 cal = new HebrewCalendar(loc, status);
326 break;
327 case CALTYPE_CHINESE:
328 cal = new ChineseCalendar(loc, status);
329 break;
330 case CALTYPE_INDIAN:
331 cal = new IndianCalendar(loc, status);
332 break;
333 case CALTYPE_COPTIC:
334 cal = new CopticCalendar(loc, status);
335 break;
336 case CALTYPE_ETHIOPIC:
337 cal = new EthiopicCalendar(loc, status, EthiopicCalendar::AMETE_MIHRET_ERA);
338 break;
339 case CALTYPE_ETHIOPIC_AMETE_ALEM:
340 cal = new EthiopicCalendar(loc, status, EthiopicCalendar::AMETE_ALEM_ERA);
341 break;
4388f060
A
342 case CALTYPE_ISO8601:
343 cal = new GregorianCalendar(loc, status);
344 cal->setFirstDayOfWeek(UCAL_MONDAY);
345 cal->setMinimalDaysInFirstWeek(4);
346 break;
51004dcb
A
347 case CALTYPE_DANGI:
348 cal = new DangiCalendar(loc, status);
349 break;
729e4ab9
A
350 default:
351 status = U_UNSUPPORTED_ERROR;
352 }
353 return cal;
354}
355
356
374ca955 357#if !UCONFIG_NO_SERVICE
b75a7d8f
A
358
359// -------------------------------------
360
361/**
46f4442e
A
362* a Calendar Factory which creates the "basic" calendar types, that is, those
363* shipped with ICU.
364*/
b75a7d8f
A
365class BasicCalendarFactory : public LocaleKeyFactory {
366public:
46f4442e
A
367 /**
368 * @param calendarType static const string (caller owns storage - will be aliased) to calendar type
369 */
370 BasicCalendarFactory()
371 : LocaleKeyFactory(LocaleKeyFactory::INVISIBLE) { }
372
4388f060 373 virtual ~BasicCalendarFactory();
b75a7d8f
A
374
375protected:
46f4442e
A
376 //virtual UBool isSupportedID( const UnicodeString& id, UErrorCode& status) const {
377 // if(U_FAILURE(status)) {
378 // return FALSE;
379 // }
380 // char keyword[ULOC_FULLNAME_CAPACITY];
381 // getCalendarKeyword(id, keyword, (int32_t)sizeof(keyword));
382 // return isStandardSupportedKeyword(keyword, status);
383 //}
384
385 virtual void updateVisibleIDs(Hashtable& result, UErrorCode& status) const
386 {
387 if (U_SUCCESS(status)) {
729e4ab9 388 for(int32_t i=0;gCalTypes[i] != NULL;i++) {
46f4442e
A
389 UnicodeString id((UChar)0x40); /* '@' a variant character */
390 id.append(UNICODE_STRING_SIMPLE("calendar="));
729e4ab9 391 id.append(UnicodeString(gCalTypes[i], -1, US_INV));
46f4442e
A
392 result.put(id, (void*)this, status);
393 }
394 }
b75a7d8f 395 }
b75a7d8f 396
46f4442e 397 virtual UObject* create(const ICUServiceKey& key, const ICUService* /*service*/, UErrorCode& status) const {
374ca955 398#ifdef U_DEBUG_CALSVC
729e4ab9 399 if(dynamic_cast<const LocaleKey*>(&key) == NULL) {
46f4442e
A
400 fprintf(stderr, "::create - not a LocaleKey!\n");
401 }
374ca955 402#endif
46f4442e
A
403 const LocaleKey& lkey = (LocaleKey&)key;
404 Locale curLoc; // current locale
405 Locale canLoc; // Canonical locale
b75a7d8f 406
46f4442e
A
407 lkey.currentLocale(curLoc);
408 lkey.canonicalLocale(canLoc);
b75a7d8f 409
46f4442e
A
410 char keyword[ULOC_FULLNAME_CAPACITY];
411 UnicodeString str;
b75a7d8f 412
46f4442e
A
413 key.currentID(str);
414 getCalendarKeyword(str, keyword, (int32_t) sizeof(keyword));
374ca955 415
b75a7d8f 416#ifdef U_DEBUG_CALSVC
46f4442e 417 fprintf(stderr, "BasicCalendarFactory::create() - cur %s, can %s\n", (const char*)curLoc.getName(), (const char*)canLoc.getName());
b75a7d8f
A
418#endif
419
46f4442e 420 if(!isStandardSupportedKeyword(keyword,status)) { // Do we handle this type?
b75a7d8f 421#ifdef U_DEBUG_CALSVC
46f4442e
A
422
423 fprintf(stderr, "BasicCalendarFactory - not handling %s.[%s]\n", (const char*) curLoc.getName(), tmp );
b75a7d8f 424#endif
46f4442e
A
425 return NULL;
426 }
b75a7d8f 427
729e4ab9 428 return createStandardCalendar(getCalendarType(keyword), canLoc, status);
46f4442e 429 }
b75a7d8f
A
430};
431
4388f060 432BasicCalendarFactory::~BasicCalendarFactory() {}
b75a7d8f
A
433
434/**
46f4442e
A
435* A factory which looks up the DefaultCalendar resource to determine which class of calendar to use
436*/
b75a7d8f
A
437
438class DefaultCalendarFactory : public ICUResourceBundleFactory {
46f4442e 439public:
4388f060
A
440 DefaultCalendarFactory() : ICUResourceBundleFactory() { }
441 virtual ~DefaultCalendarFactory();
46f4442e
A
442protected:
443 virtual UObject* create(const ICUServiceKey& key, const ICUService* /*service*/, UErrorCode& status) const {
444
445 LocaleKey &lkey = (LocaleKey&)key;
446 Locale loc;
447 lkey.currentLocale(loc);
448
729e4ab9
A
449 UnicodeString *ret = new UnicodeString();
450 if (ret == NULL) {
451 status = U_MEMORY_ALLOCATION_ERROR;
46f4442e 452 } else {
729e4ab9
A
453 ret->append((UChar)0x40); // '@' is a variant character
454 ret->append(UNICODE_STRING("calendar=", 9));
4388f060 455 ret->append(UnicodeString(gCalTypes[getCalendarTypeForLocale(loc.getName())], -1, US_INV));
46f4442e 456 }
729e4ab9 457 return ret;
374ca955 458 }
b75a7d8f
A
459};
460
4388f060
A
461DefaultCalendarFactory::~DefaultCalendarFactory() {}
462
b75a7d8f
A
463// -------------------------------------
464class CalendarService : public ICULocaleService {
465public:
46f4442e
A
466 CalendarService()
467 : ICULocaleService(UNICODE_STRING_SIMPLE("Calendar"))
468 {
469 UErrorCode status = U_ZERO_ERROR;
470 registerFactory(new DefaultCalendarFactory(), status);
471 }
b75a7d8f 472
4388f060
A
473 virtual ~CalendarService();
474
46f4442e 475 virtual UObject* cloneInstance(UObject* instance) const {
729e4ab9
A
476 UnicodeString *s = dynamic_cast<UnicodeString *>(instance);
477 if(s != NULL) {
478 return s->clone();
46f4442e 479 } else {
b75a7d8f 480#ifdef U_DEBUG_CALSVC_F
46f4442e
A
481 UErrorCode status2 = U_ZERO_ERROR;
482 fprintf(stderr, "Cloning a %s calendar with tz=%ld\n", ((Calendar*)instance)->getType(), ((Calendar*)instance)->get(UCAL_ZONE_OFFSET, status2));
b75a7d8f 483#endif
46f4442e
A
484 return ((Calendar*)instance)->clone();
485 }
b75a7d8f 486 }
b75a7d8f 487
46f4442e
A
488 virtual UObject* handleDefault(const ICUServiceKey& key, UnicodeString* /*actualID*/, UErrorCode& status) const {
489 LocaleKey& lkey = (LocaleKey&)key;
490 //int32_t kind = lkey.kind();
b75a7d8f 491
46f4442e
A
492 Locale loc;
493 lkey.canonicalLocale(loc);
b75a7d8f
A
494
495#ifdef U_DEBUG_CALSVC
496 Locale loc2;
497 lkey.currentLocale(loc2);
46f4442e 498 fprintf(stderr, "CalSvc:handleDefault for currentLoc %s, canloc %s\n", (const char*)loc.getName(), (const char*)loc2.getName());
b75a7d8f 499#endif
46f4442e 500 Calendar *nc = new GregorianCalendar(loc, status);
b75a7d8f
A
501
502#ifdef U_DEBUG_CALSVC
503 UErrorCode status2 = U_ZERO_ERROR;
504 fprintf(stderr, "New default calendar has tz=%d\n", ((Calendar*)nc)->get(UCAL_ZONE_OFFSET, status2));
505#endif
506 return nc;
46f4442e 507 }
b75a7d8f 508
46f4442e
A
509 virtual UBool isDefault() const {
510 return countFactories() == 1;
511 }
b75a7d8f
A
512};
513
4388f060
A
514CalendarService::~CalendarService() {}
515
b75a7d8f
A
516// -------------------------------------
517
374ca955
A
518static inline UBool
519isCalendarServiceUsed() {
46f4442e
A
520 UBool retVal;
521 UMTX_CHECK(NULL, gService != NULL, retVal);
522 return retVal;
374ca955
A
523}
524
525// -------------------------------------
526
b75a7d8f 527static ICULocaleService*
374ca955 528getCalendarService(UErrorCode &status)
b75a7d8f 529{
46f4442e
A
530 UBool needInit;
531 UMTX_CHECK(NULL, (UBool)(gService == NULL), needInit);
532 if (needInit) {
b75a7d8f 533#ifdef U_DEBUG_CALSVC
46f4442e 534 fprintf(stderr, "Spinning up Calendar Service\n");
b75a7d8f 535#endif
46f4442e
A
536 ICULocaleService * newservice = new CalendarService();
537 if (newservice == NULL) {
538 status = U_MEMORY_ALLOCATION_ERROR;
539 return newservice;
540 }
b75a7d8f 541#ifdef U_DEBUG_CALSVC
46f4442e 542 fprintf(stderr, "Registering classes..\n");
b75a7d8f
A
543#endif
544
46f4442e
A
545 // Register all basic instances.
546 newservice->registerFactory(new BasicCalendarFactory(),status);
b75a7d8f
A
547
548#ifdef U_DEBUG_CALSVC
46f4442e 549 fprintf(stderr, "Done..\n");
b75a7d8f
A
550#endif
551
46f4442e 552 if(U_FAILURE(status)) {
b75a7d8f 553#ifdef U_DEBUG_CALSVC
46f4442e 554 fprintf(stderr, "err (%s) registering classes, deleting service.....\n", u_errorName(status));
b75a7d8f 555#endif
46f4442e
A
556 delete newservice;
557 newservice = NULL;
558 }
559
560 if (newservice) {
561 umtx_lock(NULL);
562 if (gService == NULL) {
563 gService = newservice;
564 newservice = NULL;
565 }
566 umtx_unlock(NULL);
567 }
568 if (newservice) {
569 delete newservice;
570 } else {
571 // we won the contention - we can register the cleanup.
572 ucln_i18n_registerCleanup(UCLN_I18N_CALENDAR, calendar_cleanup);
573 }
b75a7d8f 574 }
46f4442e 575 return gService;
b75a7d8f
A
576}
577
374ca955
A
578URegistryKey Calendar::registerFactory(ICUServiceFactory* toAdopt, UErrorCode& status)
579{
46f4442e 580 return getCalendarService(status)->registerFactory(toAdopt, status);
374ca955
A
581}
582
583UBool Calendar::unregister(URegistryKey key, UErrorCode& status) {
46f4442e 584 return getCalendarService(status)->unregister(key, status);
374ca955
A
585}
586#endif /* UCONFIG_NO_SERVICE */
587
b75a7d8f
A
588// -------------------------------------
589
374ca955
A
590static const int32_t kCalendarLimits[UCAL_FIELD_COUNT][4] = {
591 // Minimum Greatest min Least max Greatest max
592 {/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // ERA
593 {/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // YEAR
594 {/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // MONTH
595 {/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // WEEK_OF_YEAR
596 {/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // WEEK_OF_MONTH
597 {/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // DAY_OF_MONTH
598 {/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // DAY_OF_YEAR
599 { 1, 1, 7, 7 }, // DAY_OF_WEEK
600 {/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // DAY_OF_WEEK_IN_MONTH
601 { 0, 0, 1, 1 }, // AM_PM
602 { 0, 0, 11, 11 }, // HOUR
603 { 0, 0, 23, 23 }, // HOUR_OF_DAY
604 { 0, 0, 59, 59 }, // MINUTE
605 { 0, 0, 59, 59 }, // SECOND
606 { 0, 0, 999, 999 }, // MILLISECOND
607 {-12*kOneHour, -12*kOneHour, 12*kOneHour, 15*kOneHour }, // ZONE_OFFSET
608 { 0, 0, 1*kOneHour, 1*kOneHour }, // DST_OFFSET
609 {/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // YEAR_WOY
610 { 1, 1, 7, 7 }, // DOW_LOCAL
611 {/*N/A*/-1, /*N/A*/-1, /*N/A*/-1, /*N/A*/-1}, // EXTENDED_YEAR
612 { -0x7F000000, -0x7F000000, 0x7F000000, 0x7F000000 }, // JULIAN_DAY
46f4442e
A
613 { 0, 0, 24*kOneHour-1, 24*kOneHour-1 }, // MILLISECONDS_IN_DAY
614 { 0, 0, 1, 1 }, // IS_LEAP_MONTH
374ca955 615};
b75a7d8f
A
616
617// Resource bundle tags read by this class
4388f060 618static const char gMonthNames[] = "monthNames";
b75a7d8f
A
619
620// Data flow in Calendar
621// ---------------------
622
623// The current time is represented in two ways by Calendar: as UTC
624// milliseconds from the epoch start (1 January 1970 0:00 UTC), and as local
625// fields such as MONTH, HOUR, AM_PM, etc. It is possible to compute the
626// millis from the fields, and vice versa. The data needed to do this
627// conversion is encapsulated by a TimeZone object owned by the Calendar.
628// The data provided by the TimeZone object may also be overridden if the
629// user sets the ZONE_OFFSET and/or DST_OFFSET fields directly. The class
630// keeps track of what information was most recently set by the caller, and
631// uses that to compute any other information as needed.
632
633// If the user sets the fields using set(), the data flow is as follows.
634// This is implemented by the Calendar subclass's computeTime() method.
635// During this process, certain fields may be ignored. The disambiguation
636// algorithm for resolving which fields to pay attention to is described
637// above.
638
639// local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.)
640// |
641// | Using Calendar-specific algorithm
642// V
643// local standard millis
644// |
645// | Using TimeZone or user-set ZONE_OFFSET / DST_OFFSET
646// V
647// UTC millis (in time data member)
648
649// If the user sets the UTC millis using setTime(), the data flow is as
650// follows. This is implemented by the Calendar subclass's computeFields()
651// method.
652
653// UTC millis (in time data member)
654// |
655// | Using TimeZone getOffset()
656// V
657// local standard millis
658// |
659// | Using Calendar-specific algorithm
660// V
661// local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.)
662
663// In general, a round trip from fields, through local and UTC millis, and
664// back out to fields is made when necessary. This is implemented by the
665// complete() method. Resolving a partial set of fields into a UTC millis
666// value allows all remaining fields to be generated from that value. If
667// the Calendar is lenient, the fields are also renormalized to standard
668// ranges when they are regenerated.
669
670// -------------------------------------
671
672Calendar::Calendar(UErrorCode& success)
673: UObject(),
46f4442e
A
674fIsTimeSet(FALSE),
675fAreFieldsSet(FALSE),
676fAreAllFieldsSet(FALSE),
677fAreFieldsVirtuallySet(FALSE),
678fNextStamp((int32_t)kMinimumUserStamp),
679fTime(0),
680fLenient(TRUE),
4388f060
A
681fZone(0),
682fRepeatedWallTime(UCAL_WALLTIME_LAST),
683fSkippedWallTime(UCAL_WALLTIME_LAST)
b75a7d8f
A
684{
685 clear();
686 fZone = TimeZone::createDefault();
46f4442e
A
687 if (fZone == NULL) {
688 success = U_MEMORY_ALLOCATION_ERROR;
689 }
729e4ab9 690 setWeekData(Locale::getDefault(), NULL, success);
b75a7d8f
A
691}
692
693// -------------------------------------
694
695Calendar::Calendar(TimeZone* zone, const Locale& aLocale, UErrorCode& success)
696: UObject(),
46f4442e
A
697fIsTimeSet(FALSE),
698fAreFieldsSet(FALSE),
699fAreAllFieldsSet(FALSE),
700fAreFieldsVirtuallySet(FALSE),
701fNextStamp((int32_t)kMinimumUserStamp),
702fTime(0),
703fLenient(TRUE),
4388f060
A
704fZone(0),
705fRepeatedWallTime(UCAL_WALLTIME_LAST),
706fSkippedWallTime(UCAL_WALLTIME_LAST)
b75a7d8f
A
707{
708 if(zone == 0) {
374ca955 709#if defined (U_DEBUG_CAL)
46f4442e
A
710 fprintf(stderr, "%s:%d: ILLEGAL ARG because timezone cannot be 0\n",
711 __FILE__, __LINE__);
374ca955 712#endif
46f4442e
A
713 success = U_ILLEGAL_ARGUMENT_ERROR;
714 return;
b75a7d8f
A
715 }
716
717 clear();
718 fZone = zone;
46f4442e 719
729e4ab9 720 setWeekData(aLocale, NULL, success);
b75a7d8f
A
721}
722
723// -------------------------------------
724
725Calendar::Calendar(const TimeZone& zone, const Locale& aLocale, UErrorCode& success)
726: UObject(),
46f4442e
A
727fIsTimeSet(FALSE),
728fAreFieldsSet(FALSE),
729fAreAllFieldsSet(FALSE),
730fAreFieldsVirtuallySet(FALSE),
731fNextStamp((int32_t)kMinimumUserStamp),
732fTime(0),
733fLenient(TRUE),
4388f060
A
734fZone(0),
735fRepeatedWallTime(UCAL_WALLTIME_LAST),
736fSkippedWallTime(UCAL_WALLTIME_LAST)
b75a7d8f
A
737{
738 clear();
739 fZone = zone.clone();
46f4442e
A
740 if (fZone == NULL) {
741 success = U_MEMORY_ALLOCATION_ERROR;
742 }
729e4ab9 743 setWeekData(aLocale, NULL, success);
b75a7d8f
A
744}
745
746// -------------------------------------
747
748Calendar::~Calendar()
749{
750 delete fZone;
751}
752
753// -------------------------------------
754
755Calendar::Calendar(const Calendar &source)
756: UObject(source)
757{
758 fZone = 0;
759 *this = source;
760}
761
762// -------------------------------------
763
764Calendar &
765Calendar::operator=(const Calendar &right)
766{
374ca955 767 if (this != &right) {
b75a7d8f
A
768 uprv_arrayCopy(right.fFields, fFields, UCAL_FIELD_COUNT);
769 uprv_arrayCopy(right.fIsSet, fIsSet, UCAL_FIELD_COUNT);
770 uprv_arrayCopy(right.fStamp, fStamp, UCAL_FIELD_COUNT);
374ca955
A
771 fTime = right.fTime;
772 fIsTimeSet = right.fIsTimeSet;
b75a7d8f 773 fAreAllFieldsSet = right.fAreAllFieldsSet;
374ca955
A
774 fAreFieldsSet = right.fAreFieldsSet;
775 fAreFieldsVirtuallySet = right.fAreFieldsVirtuallySet;
b75a7d8f 776 fLenient = right.fLenient;
4388f060
A
777 fRepeatedWallTime = right.fRepeatedWallTime;
778 fSkippedWallTime = right.fSkippedWallTime;
46f4442e
A
779 if (fZone != NULL) {
780 delete fZone;
781 }
782 if (right.fZone != NULL) {
783 fZone = right.fZone->clone();
784 }
374ca955
A
785 fFirstDayOfWeek = right.fFirstDayOfWeek;
786 fMinimalDaysInFirstWeek = right.fMinimalDaysInFirstWeek;
729e4ab9
A
787 fWeekendOnset = right.fWeekendOnset;
788 fWeekendOnsetMillis = right.fWeekendOnsetMillis;
789 fWeekendCease = right.fWeekendCease;
790 fWeekendCeaseMillis = right.fWeekendCeaseMillis;
374ca955 791 fNextStamp = right.fNextStamp;
4388f060
A
792 uprv_strcpy(validLocale, right.validLocale);
793 uprv_strcpy(actualLocale, right.actualLocale);
b75a7d8f 794 }
46f4442e 795
b75a7d8f
A
796 return *this;
797}
798
799// -------------------------------------
800
374ca955 801Calendar* U_EXPORT2
b75a7d8f
A
802Calendar::createInstance(UErrorCode& success)
803{
46f4442e 804 return createInstance(TimeZone::createDefault(), Locale::getDefault(), success);
b75a7d8f
A
805}
806
807// -------------------------------------
808
374ca955 809Calendar* U_EXPORT2
b75a7d8f
A
810Calendar::createInstance(const TimeZone& zone, UErrorCode& success)
811{
46f4442e 812 return createInstance(zone, Locale::getDefault(), success);
b75a7d8f
A
813}
814
815// -------------------------------------
816
374ca955 817Calendar* U_EXPORT2
b75a7d8f
A
818Calendar::createInstance(const Locale& aLocale, UErrorCode& success)
819{
46f4442e 820 return createInstance(TimeZone::createDefault(), aLocale, success);
b75a7d8f
A
821}
822
823// ------------------------------------- Adopting
824
825// Note: this is the bottleneck that actually calls the service routines.
826
374ca955 827Calendar* U_EXPORT2
b75a7d8f
A
828Calendar::createInstance(TimeZone* zone, const Locale& aLocale, UErrorCode& success)
829{
729e4ab9
A
830 if (U_FAILURE(success)) {
831 return NULL;
832 }
833
374ca955 834 Locale actualLoc;
729e4ab9
A
835 UObject* u = NULL;
836
374ca955
A
837#if !UCONFIG_NO_SERVICE
838 if (isCalendarServiceUsed()) {
839 u = getCalendarService(success)->get(aLocale, LocaleKey::KIND_ANY, &actualLoc, success);
840 }
841 else
842#endif
843 {
729e4ab9 844 u = createStandardCalendar(getCalendarTypeForLocale(aLocale.getName()), aLocale, success);
374ca955
A
845 }
846 Calendar* c = NULL;
b75a7d8f 847
46f4442e
A
848 if(U_FAILURE(success) || !u) {
849 delete zone;
850 if(U_SUCCESS(success)) { // Propagate some kind of err
851 success = U_INTERNAL_PROGRAM_ERROR;
852 }
853 return NULL;
b75a7d8f 854 }
374ca955
A
855
856#if !UCONFIG_NO_SERVICE
729e4ab9
A
857 const UnicodeString* str = dynamic_cast<const UnicodeString*>(u);
858 if(str != NULL) {
46f4442e 859 // It's a unicode string telling us what type of calendar to load ("gregorian", etc)
46f4442e
A
860 // Create a Locale over this string
861 Locale l("");
729e4ab9 862 LocaleUtility::initLocaleFromName(*str, l);
b75a7d8f 863
374ca955 864#ifdef U_DEBUG_CALSVC
46f4442e 865 fprintf(stderr, "Calendar::createInstance(%s), looking up [%s]\n", aLocale.getName(), l.getName());
374ca955
A
866#endif
867
46f4442e
A
868 Locale actualLoc2;
869 delete u;
870 u = NULL;
871
872 // Don't overwrite actualLoc, since the actual loc from this call
873 // may be something like "@calendar=gregorian" -- TODO investigate
874 // further...
875 c = (Calendar*)getCalendarService(success)->get(l, LocaleKey::KIND_ANY, &actualLoc2, success);
876
877 if(U_FAILURE(success) || !c) {
878 delete zone;
879 if(U_SUCCESS(success)) {
880 success = U_INTERNAL_PROGRAM_ERROR; // Propagate some err
881 }
882 return NULL;
883 }
884
729e4ab9
A
885 str = dynamic_cast<const UnicodeString*>(c);
886 if(str != NULL) {
46f4442e
A
887 // recursed! Second lookup returned a UnicodeString.
888 // Perhaps DefaultCalendar{} was set to another locale.
374ca955 889#ifdef U_DEBUG_CALSVC
46f4442e 890 char tmp[200];
46f4442e 891 // Extract a char* out of it..
729e4ab9 892 int32_t len = str->length();
46f4442e
A
893 int32_t actLen = sizeof(tmp)-1;
894 if(len > actLen) {
895 len = actLen;
896 }
729e4ab9 897 str->extract(0,len,tmp);
46f4442e
A
898 tmp[len]=0;
899
900 fprintf(stderr, "err - recursed, 2nd lookup was unistring %s\n", tmp);
374ca955 901#endif
46f4442e
A
902 success = U_MISSING_RESOURCE_ERROR; // requested a calendar type which could NOT be found.
903 delete c;
904 delete zone;
905 return NULL;
906 }
b75a7d8f 907#ifdef U_DEBUG_CALSVC
46f4442e 908 fprintf(stderr, "%p: setting week count data to locale %s, actual locale %s\n", c, (const char*)aLocale.getName(), (const char *)actualLoc.getName());
b75a7d8f 909#endif
729e4ab9 910 c->setWeekData(aLocale, c->getType(), success); // set the correct locale (this was an indirected calendar)
4388f060
A
911
912 char keyword[ULOC_FULLNAME_CAPACITY];
913 UErrorCode tmpStatus = U_ZERO_ERROR;
914 l.getKeywordValue("calendar", keyword, ULOC_FULLNAME_CAPACITY, tmpStatus);
915 if (U_SUCCESS(tmpStatus) && uprv_strcmp(keyword, "iso8601") == 0) {
916 c->setFirstDayOfWeek(UCAL_MONDAY);
917 c->setMinimalDaysInFirstWeek(4);
918 }
46f4442e
A
919 }
920 else
374ca955 921#endif /* UCONFIG_NO_SERVICE */
46f4442e
A
922 {
923 // a calendar was returned - we assume the factory did the right thing.
924 c = (Calendar*)u;
925 }
926
927 // Now, reset calendar to default state:
928 c->adoptTimeZone(zone); // Set the correct time zone
929 c->setTimeInMillis(getNow(), success); // let the new calendar have the current time.
b75a7d8f 930
46f4442e 931 return c;
b75a7d8f
A
932}
933
934// -------------------------------------
935
374ca955 936Calendar* U_EXPORT2
b75a7d8f
A
937Calendar::createInstance(const TimeZone& zone, const Locale& aLocale, UErrorCode& success)
938{
46f4442e
A
939 Calendar* c = createInstance(aLocale, success);
940 if(U_SUCCESS(success) && c) {
941 c->setTimeZone(zone);
942 }
943 return c;
b75a7d8f
A
944}
945
946// -------------------------------------
947
948UBool
949Calendar::operator==(const Calendar& that) const
950{
951 UErrorCode status = U_ZERO_ERROR;
952 return isEquivalentTo(that) &&
953 getTimeInMillis(status) == that.getTimeInMillis(status) &&
954 U_SUCCESS(status);
955}
956
957UBool
958Calendar::isEquivalentTo(const Calendar& other) const
959{
729e4ab9 960 return typeid(*this) == typeid(other) &&
b75a7d8f 961 fLenient == other.fLenient &&
4388f060
A
962 fRepeatedWallTime == other.fRepeatedWallTime &&
963 fSkippedWallTime == other.fSkippedWallTime &&
b75a7d8f
A
964 fFirstDayOfWeek == other.fFirstDayOfWeek &&
965 fMinimalDaysInFirstWeek == other.fMinimalDaysInFirstWeek &&
729e4ab9
A
966 fWeekendOnset == other.fWeekendOnset &&
967 fWeekendOnsetMillis == other.fWeekendOnsetMillis &&
968 fWeekendCease == other.fWeekendCease &&
969 fWeekendCeaseMillis == other.fWeekendCeaseMillis &&
b75a7d8f
A
970 *fZone == *other.fZone;
971}
972
973// -------------------------------------
974
975UBool
976Calendar::equals(const Calendar& when, UErrorCode& status) const
977{
978 return (this == &when ||
46f4442e 979 getTime(status) == when.getTime(status));
b75a7d8f
A
980}
981
982// -------------------------------------
983
984UBool
985Calendar::before(const Calendar& when, UErrorCode& status) const
986{
987 return (this != &when &&
46f4442e 988 getTimeInMillis(status) < when.getTimeInMillis(status));
b75a7d8f
A
989}
990
991// -------------------------------------
992
993UBool
994Calendar::after(const Calendar& when, UErrorCode& status) const
995{
996 return (this != &when &&
46f4442e 997 getTimeInMillis(status) > when.getTimeInMillis(status));
b75a7d8f
A
998}
999
1000// -------------------------------------
1001
1002
374ca955 1003const Locale* U_EXPORT2
b75a7d8f
A
1004Calendar::getAvailableLocales(int32_t& count)
1005{
1006 return Locale::getAvailableLocales(count);
1007}
1008
1009// -------------------------------------
1010
729e4ab9
A
1011StringEnumeration* U_EXPORT2
1012Calendar::getKeywordValuesForLocale(const char* key,
1013 const Locale& locale, UBool commonlyUsed, UErrorCode& status)
1014{
1015 // This is a wrapper over ucal_getKeywordValuesForLocale
1016 UEnumeration *uenum = ucal_getKeywordValuesForLocale(key, locale.getName(),
1017 commonlyUsed, &status);
1018 if (U_FAILURE(status)) {
1019 uenum_close(uenum);
1020 return NULL;
1021 }
1022 return new UStringEnumeration(uenum);
1023}
1024
1025// -------------------------------------
1026
374ca955 1027UDate U_EXPORT2
b75a7d8f
A
1028Calendar::getNow()
1029{
374ca955 1030 return uprv_getUTCtime(); // return as milliseconds
b75a7d8f
A
1031}
1032
1033// -------------------------------------
1034
1035/**
46f4442e
A
1036* Gets this Calendar's current time as a long.
1037* @return the current time as UTC milliseconds from the epoch.
1038*/
b75a7d8f
A
1039double
1040Calendar::getTimeInMillis(UErrorCode& status) const
1041{
1042 if(U_FAILURE(status))
1043 return 0.0;
1044
1045 if ( ! fIsTimeSet)
1046 ((Calendar*)this)->updateTime(status);
1047
1048 /* Test for buffer overflows */
1049 if(U_FAILURE(status)) {
1050 return 0.0;
1051 }
1052 return fTime;
1053}
1054
1055// -------------------------------------
1056
1057/**
46f4442e 1058* Sets this Calendar's current time from the given long value.
51004dcb
A
1059* A status of U_ILLEGAL_ARGUMENT_ERROR is set when millis is
1060* outside the range permitted by a Calendar object when not in lenient mode.
1061* when in lenient mode the out of range values are pinned to their respective min/max.
46f4442e
A
1062* @param date the new time in UTC milliseconds from the epoch.
1063*/
b75a7d8f
A
1064void
1065Calendar::setTimeInMillis( double millis, UErrorCode& status ) {
1066 if(U_FAILURE(status))
1067 return;
1068
374ca955 1069 if (millis > MAX_MILLIS) {
51004dcb
A
1070 if(isLenient()) {
1071 millis = MAX_MILLIS;
1072 } else {
1073 status = U_ILLEGAL_ARGUMENT_ERROR;
1074 return;
1075 }
374ca955 1076 } else if (millis < MIN_MILLIS) {
51004dcb
A
1077 if(isLenient()) {
1078 millis = MIN_MILLIS;
1079 } else {
1080 status = U_ILLEGAL_ARGUMENT_ERROR;
1081 return;
1082 }
b75a7d8f 1083 }
374ca955
A
1084
1085 fTime = millis;
1086 fAreFieldsSet = fAreAllFieldsSet = FALSE;
1087 fIsTimeSet = fAreFieldsVirtuallySet = TRUE;
729e4ab9
A
1088
1089 for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) {
1090 fFields[i] = 0;
1091 fStamp[i] = kUnset;
1092 fIsSet[i] = FALSE;
1093 }
1094
1095
b75a7d8f
A
1096}
1097
1098// -------------------------------------
1099
1100int32_t
1101Calendar::get(UCalendarDateFields field, UErrorCode& status) const
1102{
1103 // field values are only computed when actually requested; for more on when computation
1104 // of various things happens, see the "data flow in Calendar" description at the top
1105 // of this file
1106 if (U_SUCCESS(status)) ((Calendar*)this)->complete(status); // Cast away const
1107 return U_SUCCESS(status) ? fFields[field] : 0;
1108}
1109
1110// -------------------------------------
1111
1112void
1113Calendar::set(UCalendarDateFields field, int32_t value)
1114{
374ca955
A
1115 if (fAreFieldsVirtuallySet) {
1116 UErrorCode ec = U_ZERO_ERROR;
1117 computeFields(ec);
1118 }
b75a7d8f 1119 fFields[field] = value;
4388f060
A
1120 /* Ensure that the fNextStamp value doesn't go pass max value for int32_t */
1121 if (fNextStamp == STAMP_MAX) {
1122 recalculateStamp();
1123 }
b75a7d8f 1124 fStamp[field] = fNextStamp++;
b75a7d8f 1125 fIsSet[field] = TRUE; // Remove later
374ca955 1126 fIsTimeSet = fAreFieldsSet = fAreFieldsVirtuallySet = FALSE;
b75a7d8f
A
1127}
1128
1129// -------------------------------------
1130
1131void
1132Calendar::set(int32_t year, int32_t month, int32_t date)
1133{
1134 set(UCAL_YEAR, year);
1135 set(UCAL_MONTH, month);
1136 set(UCAL_DATE, date);
1137}
1138
1139// -------------------------------------
1140
1141void
1142Calendar::set(int32_t year, int32_t month, int32_t date, int32_t hour, int32_t minute)
1143{
1144 set(UCAL_YEAR, year);
1145 set(UCAL_MONTH, month);
1146 set(UCAL_DATE, date);
1147 set(UCAL_HOUR_OF_DAY, hour);
1148 set(UCAL_MINUTE, minute);
1149}
1150
1151// -------------------------------------
1152
1153void
1154Calendar::set(int32_t year, int32_t month, int32_t date, int32_t hour, int32_t minute, int32_t second)
1155{
1156 set(UCAL_YEAR, year);
1157 set(UCAL_MONTH, month);
1158 set(UCAL_DATE, date);
1159 set(UCAL_HOUR_OF_DAY, hour);
1160 set(UCAL_MINUTE, minute);
1161 set(UCAL_SECOND, second);
1162}
1163
1164// -------------------------------------
1165
1166void
1167Calendar::clear()
1168{
1169 for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) {
1170 fFields[i] = 0; // Must do this; other code depends on it
b75a7d8f 1171 fStamp[i] = kUnset;
374ca955 1172 fIsSet[i] = FALSE; // Remove later
b75a7d8f 1173 }
374ca955 1174 fIsTimeSet = fAreFieldsSet = fAreAllFieldsSet = fAreFieldsVirtuallySet = FALSE;
73c04bcf 1175 // fTime is not 'cleared' - may be used if no fields are set.
b75a7d8f
A
1176}
1177
1178// -------------------------------------
1179
1180void
1181Calendar::clear(UCalendarDateFields field)
1182{
374ca955
A
1183 if (fAreFieldsVirtuallySet) {
1184 UErrorCode ec = U_ZERO_ERROR;
1185 computeFields(ec);
1186 }
b75a7d8f
A
1187 fFields[field] = 0;
1188 fStamp[field] = kUnset;
b75a7d8f 1189 fIsSet[field] = FALSE; // Remove later
374ca955 1190 fIsTimeSet = fAreFieldsSet = fAreAllFieldsSet = fAreFieldsVirtuallySet = FALSE;
b75a7d8f
A
1191}
1192
1193// -------------------------------------
1194
1195UBool
1196Calendar::isSet(UCalendarDateFields field) const
1197{
374ca955
A
1198 return fAreFieldsVirtuallySet || (fStamp[field] != kUnset);
1199}
1200
1201
1202int32_t Calendar::newestStamp(UCalendarDateFields first, UCalendarDateFields last, int32_t bestStampSoFar) const
1203{
46f4442e
A
1204 int32_t bestStamp = bestStampSoFar;
1205 for (int32_t i=(int32_t)first; i<=(int32_t)last; ++i) {
1206 if (fStamp[i] > bestStamp) {
1207 bestStamp = fStamp[i];
1208 }
374ca955 1209 }
46f4442e 1210 return bestStamp;
b75a7d8f
A
1211}
1212
374ca955 1213
b75a7d8f
A
1214// -------------------------------------
1215
1216void
1217Calendar::complete(UErrorCode& status)
1218{
1219 if (!fIsTimeSet) {
1220 updateTime(status);
1221 /* Test for buffer overflows */
1222 if(U_FAILURE(status)) {
1223 return;
1224 }
1225 }
1226 if (!fAreFieldsSet) {
1227 computeFields(status); // fills in unset fields
1228 /* Test for buffer overflows */
1229 if(U_FAILURE(status)) {
1230 return;
1231 }
1232 fAreFieldsSet = TRUE;
1233 fAreAllFieldsSet = TRUE;
1234 }
1235}
1236
46f4442e
A
1237//-------------------------------------------------------------------------
1238// Protected utility methods for use by subclasses. These are very handy
1239// for implementing add, roll, and computeFields.
1240//-------------------------------------------------------------------------
374ca955 1241
46f4442e
A
1242/**
1243* Adjust the specified field so that it is within
1244* the allowable range for the date to which this calendar is set.
1245* For example, in a Gregorian calendar pinning the {@link #DAY_OF_MONTH DAY_OF_MONTH}
1246* field for a calendar set to April 31 would cause it to be set
1247* to April 30.
1248* <p>
1249* <b>Subclassing:</b>
1250* <br>
1251* This utility method is intended for use by subclasses that need to implement
1252* their own overrides of {@link #roll roll} and {@link #add add}.
1253* <p>
1254* <b>Note:</b>
1255* <code>pinField</code> is implemented in terms of
1256* {@link #getActualMinimum getActualMinimum}
1257* and {@link #getActualMaximum getActualMaximum}. If either of those methods uses
1258* a slow, iterative algorithm for a particular field, it would be
1259* unwise to attempt to call <code>pinField</code> for that field. If you
1260* really do need to do so, you should override this method to do
1261* something more efficient for that field.
1262* <p>
1263* @param field The calendar field whose value should be pinned.
1264*
1265* @see #getActualMinimum
1266* @see #getActualMaximum
1267* @stable ICU 2.0
1268*/
374ca955 1269void Calendar::pinField(UCalendarDateFields field, UErrorCode& status) {
46f4442e
A
1270 int32_t max = getActualMaximum(field, status);
1271 int32_t min = getActualMinimum(field, status);
1272
1273 if (fFields[field] > max) {
1274 set(field, max);
1275 } else if (fFields[field] < min) {
1276 set(field, min);
1277 }
374ca955
A
1278}
1279
1280
1281void Calendar::computeFields(UErrorCode &ec)
1282{
1283 if (U_FAILURE(ec)) {
46f4442e
A
1284 return;
1285 }
1286 // Compute local wall millis
1287 double localMillis = internalGetTime();
1288 int32_t rawOffset, dstOffset;
1289 getTimeZone().getOffset(localMillis, FALSE, rawOffset, dstOffset, ec);
1290 localMillis += (rawOffset + dstOffset);
1291
1292 // Mark fields as set. Do this before calling handleComputeFields().
1293 uint32_t mask = //fInternalSetMask;
1294 (1 << UCAL_ERA) |
1295 (1 << UCAL_YEAR) |
1296 (1 << UCAL_MONTH) |
1297 (1 << UCAL_DAY_OF_MONTH) | // = UCAL_DATE
1298 (1 << UCAL_DAY_OF_YEAR) |
1299 (1 << UCAL_EXTENDED_YEAR);
1300
1301 for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) {
1302 if ((mask & 1) == 0) {
1303 fStamp[i] = kInternallySet;
1304 fIsSet[i] = TRUE; // Remove later
1305 } else {
1306 fStamp[i] = kUnset;
1307 fIsSet[i] = FALSE; // Remove later
1308 }
1309 mask >>= 1;
1310 }
1311
1312 // We used to check for and correct extreme millis values (near
1313 // Long.MIN_VALUE or Long.MAX_VALUE) here. Such values would cause
1314 // overflows from positive to negative (or vice versa) and had to
1315 // be manually tweaked. We no longer need to do this because we
1316 // have limited the range of supported dates to those that have a
1317 // Julian day that fits into an int. This allows us to implement a
1318 // JULIAN_DAY field and also removes some inelegant code. - Liu
1319 // 11/6/00
1320
729e4ab9 1321 int32_t days = (int32_t)ClockMath::floorDivide(localMillis, (double)kOneDay);
46f4442e
A
1322
1323 internalSet(UCAL_JULIAN_DAY,days + kEpochStartAsJulianDay);
374ca955
A
1324
1325#if defined (U_DEBUG_CAL)
46f4442e
A
1326 //fprintf(stderr, "%s:%d- Hmm! Jules @ %d, as per %.0lf millis\n",
1327 //__FILE__, __LINE__, fFields[UCAL_JULIAN_DAY], localMillis);
374ca955
A
1328#endif
1329
46f4442e
A
1330 computeGregorianAndDOWFields(fFields[UCAL_JULIAN_DAY], ec);
1331
1332 // Call framework method to have subclass compute its fields.
1333 // These must include, at a minimum, MONTH, DAY_OF_MONTH,
1334 // EXTENDED_YEAR, YEAR, DAY_OF_YEAR. This method will call internalSet(),
1335 // which will update stamp[].
1336 handleComputeFields(fFields[UCAL_JULIAN_DAY], ec);
1337
1338 // Compute week-related fields, based on the subclass-computed
1339 // fields computed by handleComputeFields().
1340 computeWeekFields(ec);
1341
1342 // Compute time-related fields. These are indepent of the date and
1343 // of the subclass algorithm. They depend only on the local zone
1344 // wall milliseconds in day.
1345 int32_t millisInDay = (int32_t) (localMillis - (days * kOneDay));
1346 fFields[UCAL_MILLISECONDS_IN_DAY] = millisInDay;
1347 fFields[UCAL_MILLISECOND] = millisInDay % 1000;
1348 millisInDay /= 1000;
1349 fFields[UCAL_SECOND] = millisInDay % 60;
1350 millisInDay /= 60;
1351 fFields[UCAL_MINUTE] = millisInDay % 60;
1352 millisInDay /= 60;
1353 fFields[UCAL_HOUR_OF_DAY] = millisInDay;
1354 fFields[UCAL_AM_PM] = millisInDay / 12; // Assume AM == 0
1355 fFields[UCAL_HOUR] = millisInDay % 12;
1356 fFields[UCAL_ZONE_OFFSET] = rawOffset;
1357 fFields[UCAL_DST_OFFSET] = dstOffset;
374ca955
A
1358}
1359
1360uint8_t Calendar::julianDayToDayOfWeek(double julian)
1361{
46f4442e
A
1362 // If julian is negative, then julian%7 will be negative, so we adjust
1363 // accordingly. We add 1 because Julian day 0 is Monday.
1364 int8_t dayOfWeek = (int8_t) uprv_fmod(julian + 1, 7);
374ca955 1365
46f4442e
A
1366 uint8_t result = (uint8_t)(dayOfWeek + ((dayOfWeek < 0) ? (7+UCAL_SUNDAY ) : UCAL_SUNDAY));
1367 return result;
374ca955
A
1368}
1369
1370/**
46f4442e
A
1371* Compute the Gregorian calendar year, month, and day of month from
1372* the given Julian day. These values are not stored in fields, but in
1373* member variables gregorianXxx. Also compute the DAY_OF_WEEK and
1374* DOW_LOCAL fields.
1375*/
374ca955
A
1376void Calendar::computeGregorianAndDOWFields(int32_t julianDay, UErrorCode &ec)
1377{
46f4442e 1378 computeGregorianFields(julianDay, ec);
374ca955 1379
46f4442e
A
1380 // Compute day of week: JD 0 = Monday
1381 int32_t dow = julianDayToDayOfWeek(julianDay);
1382 internalSet(UCAL_DAY_OF_WEEK,dow);
1383
1384 // Calculate 1-based localized day of week
1385 int32_t dowLocal = dow - getFirstDayOfWeek() + 1;
1386 if (dowLocal < 1) {
1387 dowLocal += 7;
1388 }
1389 internalSet(UCAL_DOW_LOCAL,dowLocal);
1390 fFields[UCAL_DOW_LOCAL] = dowLocal;
374ca955
A
1391}
1392
1393/**
46f4442e
A
1394* Compute the Gregorian calendar year, month, and day of month from the
1395* Julian day. These values are not stored in fields, but in member
1396* variables gregorianXxx. They are used for time zone computations and by
1397* subclasses that are Gregorian derivatives. Subclasses may call this
1398* method to perform a Gregorian calendar millis->fields computation.
46f4442e 1399*/
374ca955 1400void Calendar::computeGregorianFields(int32_t julianDay, UErrorCode & /* ec */) {
46f4442e
A
1401 int32_t gregorianDayOfWeekUnused;
1402 Grego::dayToFields(julianDay - kEpochStartAsJulianDay, fGregorianYear, fGregorianMonth, fGregorianDayOfMonth, gregorianDayOfWeekUnused, fGregorianDayOfYear);
374ca955
A
1403}
1404
1405/**
46f4442e
A
1406* Compute the fields WEEK_OF_YEAR, YEAR_WOY, WEEK_OF_MONTH,
1407* DAY_OF_WEEK_IN_MONTH, and DOW_LOCAL from EXTENDED_YEAR, YEAR,
1408* DAY_OF_WEEK, and DAY_OF_YEAR. The latter fields are computed by the
1409* subclass based on the calendar system.
1410*
1411* <p>The YEAR_WOY field is computed simplistically. It is equal to YEAR
1412* most of the time, but at the year boundary it may be adjusted to YEAR-1
1413* or YEAR+1 to reflect the overlap of a week into an adjacent year. In
1414* this case, a simple increment or decrement is performed on YEAR, even
1415* though this may yield an invalid YEAR value. For instance, if the YEAR
1416* is part of a calendar system with an N-year cycle field CYCLE, then
1417* incrementing the YEAR may involve incrementing CYCLE and setting YEAR
1418* back to 0 or 1. This is not handled by this code, and in fact cannot be
1419* simply handled without having subclasses define an entire parallel set of
1420* fields for fields larger than or equal to a year. This additional
1421* complexity is not warranted, since the intention of the YEAR_WOY field is
1422* to support ISO 8601 notation, so it will typically be used with a
1423* proleptic Gregorian calendar, which has no field larger than a year.
1424*/
374ca955 1425void Calendar::computeWeekFields(UErrorCode &ec) {
46f4442e
A
1426 if(U_FAILURE(ec)) {
1427 return;
1428 }
1429 int32_t eyear = fFields[UCAL_EXTENDED_YEAR];
1430 int32_t dayOfWeek = fFields[UCAL_DAY_OF_WEEK];
1431 int32_t dayOfYear = fFields[UCAL_DAY_OF_YEAR];
1432
1433 // WEEK_OF_YEAR start
1434 // Compute the week of the year. For the Gregorian calendar, valid week
1435 // numbers run from 1 to 52 or 53, depending on the year, the first day
1436 // of the week, and the minimal days in the first week. For other
1437 // calendars, the valid range may be different -- it depends on the year
1438 // length. Days at the start of the year may fall into the last week of
1439 // the previous year; days at the end of the year may fall into the
1440 // first week of the next year. ASSUME that the year length is less than
1441 // 7000 days.
1442 int32_t yearOfWeekOfYear = eyear;
1443 int32_t relDow = (dayOfWeek + 7 - getFirstDayOfWeek()) % 7; // 0..6
1444 int32_t relDowJan1 = (dayOfWeek - dayOfYear + 7001 - getFirstDayOfWeek()) % 7; // 0..6
1445 int32_t woy = (dayOfYear - 1 + relDowJan1) / 7; // 0..53
1446 if ((7 - relDowJan1) >= getMinimalDaysInFirstWeek()) {
1447 ++woy;
1448 }
1449
1450 // Adjust for weeks at the year end that overlap into the previous or
1451 // next calendar year.
1452 if (woy == 0) {
1453 // We are the last week of the previous year.
1454 // Check to see if we are in the last week; if so, we need
1455 // to handle the case in which we are the first week of the
1456 // next year.
1457
1458 int32_t prevDoy = dayOfYear + handleGetYearLength(eyear - 1);
1459 woy = weekNumber(prevDoy, dayOfWeek);
1460 yearOfWeekOfYear--;
1461 } else {
1462 int32_t lastDoy = handleGetYearLength(eyear);
1463 // Fast check: For it to be week 1 of the next year, the DOY
1464 // must be on or after L-5, where L is yearLength(), then it
1465 // cannot possibly be week 1 of the next year:
1466 // L-5 L
1467 // doy: 359 360 361 362 363 364 365 001
1468 // dow: 1 2 3 4 5 6 7
1469 if (dayOfYear >= (lastDoy - 5)) {
1470 int32_t lastRelDow = (relDow + lastDoy - dayOfYear) % 7;
1471 if (lastRelDow < 0) {
1472 lastRelDow += 7;
1473 }
1474 if (((6 - lastRelDow) >= getMinimalDaysInFirstWeek()) &&
1475 ((dayOfYear + 7 - relDow) > lastDoy)) {
1476 woy = 1;
1477 yearOfWeekOfYear++;
1478 }
1479 }
1480 }
1481 fFields[UCAL_WEEK_OF_YEAR] = woy;
1482 fFields[UCAL_YEAR_WOY] = yearOfWeekOfYear;
1483 // WEEK_OF_YEAR end
1484
1485 int32_t dayOfMonth = fFields[UCAL_DAY_OF_MONTH];
1486 fFields[UCAL_WEEK_OF_MONTH] = weekNumber(dayOfMonth, dayOfWeek);
1487 fFields[UCAL_DAY_OF_WEEK_IN_MONTH] = (dayOfMonth-1) / 7 + 1;
374ca955 1488#if defined (U_DEBUG_CAL)
46f4442e
A
1489 if(fFields[UCAL_DAY_OF_WEEK_IN_MONTH]==0) fprintf(stderr, "%s:%d: DOWIM %d on %g\n",
1490 __FILE__, __LINE__,fFields[UCAL_DAY_OF_WEEK_IN_MONTH], fTime);
374ca955
A
1491#endif
1492}
1493
1494
1495int32_t Calendar::weekNumber(int32_t desiredDay, int32_t dayOfPeriod, int32_t dayOfWeek)
1496{
46f4442e
A
1497 // Determine the day of the week of the first day of the period
1498 // in question (either a year or a month). Zero represents the
1499 // first day of the week on this calendar.
1500 int32_t periodStartDayOfWeek = (dayOfWeek - getFirstDayOfWeek() - dayOfPeriod + 1) % 7;
1501 if (periodStartDayOfWeek < 0) periodStartDayOfWeek += 7;
1502
1503 // Compute the week number. Initially, ignore the first week, which
1504 // may be fractional (or may not be). We add periodStartDayOfWeek in
1505 // order to fill out the first week, if it is fractional.
1506 int32_t weekNo = (desiredDay + periodStartDayOfWeek - 1)/7;
1507
1508 // If the first week is long enough, then count it. If
1509 // the minimal days in the first week is one, or if the period start
1510 // is zero, we always increment weekNo.
1511 if ((7 - periodStartDayOfWeek) >= getMinimalDaysInFirstWeek()) ++weekNo;
1512
1513 return weekNo;
374ca955
A
1514}
1515
1516void Calendar::handleComputeFields(int32_t /* julianDay */, UErrorCode &/* status */)
1517{
46f4442e
A
1518 internalSet(UCAL_MONTH, getGregorianMonth());
1519 internalSet(UCAL_DAY_OF_MONTH, getGregorianDayOfMonth());
1520 internalSet(UCAL_DAY_OF_YEAR, getGregorianDayOfYear());
1521 int32_t eyear = getGregorianYear();
1522 internalSet(UCAL_EXTENDED_YEAR, eyear);
1523 int32_t era = GregorianCalendar::AD;
1524 if (eyear < 1) {
1525 era = GregorianCalendar::BC;
1526 eyear = 1 - eyear;
1527 }
1528 internalSet(UCAL_ERA, era);
1529 internalSet(UCAL_YEAR, eyear);
374ca955 1530}
b75a7d8f 1531// -------------------------------------
b75a7d8f 1532
b75a7d8f 1533
374ca955
A
1534void Calendar::roll(EDateFields field, int32_t amount, UErrorCode& status)
1535{
46f4442e 1536 roll((UCalendarDateFields)field, amount, status);
b75a7d8f
A
1537}
1538
374ca955
A
1539void Calendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& status)
1540{
46f4442e
A
1541 if (amount == 0) {
1542 return; // Nothing to do
1543 }
1544
1545 complete(status);
1546
1547 if(U_FAILURE(status)) {
1548 return;
1549 }
1550 switch (field) {
1551 case UCAL_DAY_OF_MONTH:
1552 case UCAL_AM_PM:
1553 case UCAL_MINUTE:
1554 case UCAL_SECOND:
1555 case UCAL_MILLISECOND:
1556 case UCAL_MILLISECONDS_IN_DAY:
1557 case UCAL_ERA:
1558 // These are the standard roll instructions. These work for all
1559 // simple cases, that is, cases in which the limits are fixed, such
1560 // as the hour, the day of the month, and the era.
1561 {
1562 int32_t min = getActualMinimum(field,status);
1563 int32_t max = getActualMaximum(field,status);
1564 int32_t gap = max - min + 1;
1565
1566 int32_t value = internalGet(field) + amount;
1567 value = (value - min) % gap;
1568 if (value < 0) {
1569 value += gap;
1570 }
1571 value += min;
1572
1573 set(field, value);
1574 return;
1575 }
1576
1577 case UCAL_HOUR:
1578 case UCAL_HOUR_OF_DAY:
1579 // Rolling the hour is difficult on the ONSET and CEASE days of
1580 // daylight savings. For example, if the change occurs at
1581 // 2 AM, we have the following progression:
1582 // ONSET: 12 Std -> 1 Std -> 3 Dst -> 4 Dst
1583 // CEASE: 12 Dst -> 1 Dst -> 1 Std -> 2 Std
1584 // To get around this problem we don't use fields; we manipulate
1585 // the time in millis directly.
1586 {
1587 // Assume min == 0 in calculations below
1588 double start = getTimeInMillis(status);
1589 int32_t oldHour = internalGet(field);
1590 int32_t max = getMaximum(field);
1591 int32_t newHour = (oldHour + amount) % (max + 1);
1592 if (newHour < 0) {
1593 newHour += max + 1;
1594 }
1595 setTimeInMillis(start + kOneHour * (newHour - oldHour),status);
1596 return;
1597 }
1598
1599 case UCAL_MONTH:
1600 // Rolling the month involves both pinning the final value
1601 // and adjusting the DAY_OF_MONTH if necessary. We only adjust the
1602 // DAY_OF_MONTH if, after updating the MONTH field, it is illegal.
1603 // E.g., <jan31>.roll(MONTH, 1) -> <feb28> or <feb29>.
1604 {
1605 int32_t max = getActualMaximum(UCAL_MONTH, status);
1606 int32_t mon = (internalGet(UCAL_MONTH) + amount) % (max+1);
1607
1608 if (mon < 0) {
1609 mon += (max + 1);
1610 }
1611 set(UCAL_MONTH, mon);
1612
1613 // Keep the day of month in range. We don't want to spill over
1614 // into the next month; e.g., we don't want jan31 + 1 mo -> feb31 ->
1615 // mar3.
1616 pinField(UCAL_DAY_OF_MONTH,status);
1617 return;
1618 }
1619
1620 case UCAL_YEAR:
1621 case UCAL_YEAR_WOY:
51004dcb
A
1622 {
1623 // * If era==0 and years go backwards in time, change sign of amount.
1624 // * Until we have new API per #9393, we temporarily hardcode knowledge of
1625 // which calendars have era 0 years that go backwards.
1626 UBool era0WithYearsThatGoBackwards = FALSE;
1627 int32_t era = get(UCAL_ERA, status);
1628 if (era == 0) {
1629 const char * calType = getType();
1630 if ( uprv_strcmp(calType,"gregorian")==0 || uprv_strcmp(calType,"roc")==0 || uprv_strcmp(calType,"coptic")==0 ) {
1631 amount = -amount;
1632 era0WithYearsThatGoBackwards = TRUE;
1633 }
1634 }
1635 int32_t newYear = internalGet(field) + amount;
1636 if (era > 0 || newYear >= 1) {
1637 int32_t maxYear = getActualMaximum(field, status);
1638 if (maxYear < 32768) {
1639 // this era has real bounds, roll should wrap years
1640 if (newYear < 1) {
1641 newYear = maxYear - ((-newYear) % maxYear);
1642 } else if (newYear > maxYear) {
1643 newYear = ((newYear - 1) % maxYear) + 1;
1644 }
1645 // else era is unbounded, just pin low year instead of wrapping
1646 } else if (newYear < 1) {
1647 newYear = 1;
1648 }
1649 // else we are in era 0 with newYear < 1;
1650 // calendars with years that go backwards must pin the year value at 0,
1651 // other calendars can have years < 0 in era 0
1652 } else if (era0WithYearsThatGoBackwards) {
1653 newYear = 1;
1654 }
1655 set(field, newYear);
1656 pinField(UCAL_MONTH,status);
1657 pinField(UCAL_DAY_OF_MONTH,status);
1658 return;
1659 }
1660
46f4442e
A
1661 case UCAL_EXTENDED_YEAR:
1662 // Rolling the year can involve pinning the DAY_OF_MONTH.
1663 set(field, internalGet(field) + amount);
1664 pinField(UCAL_MONTH,status);
1665 pinField(UCAL_DAY_OF_MONTH,status);
1666 return;
1667
1668 case UCAL_WEEK_OF_MONTH:
1669 {
1670 // This is tricky, because during the roll we may have to shift
1671 // to a different day of the week. For example:
1672
1673 // s m t w r f s
1674 // 1 2 3 4 5
1675 // 6 7 8 9 10 11 12
1676
1677 // When rolling from the 6th or 7th back one week, we go to the
1678 // 1st (assuming that the first partial week counts). The same
1679 // thing happens at the end of the month.
1680
1681 // The other tricky thing is that we have to figure out whether
1682 // the first partial week actually counts or not, based on the
1683 // minimal first days in the week. And we have to use the
1684 // correct first day of the week to delineate the week
1685 // boundaries.
1686
1687 // Here's our algorithm. First, we find the real boundaries of
1688 // the month. Then we discard the first partial week if it
1689 // doesn't count in this locale. Then we fill in the ends with
1690 // phantom days, so that the first partial week and the last
1691 // partial week are full weeks. We then have a nice square
1692 // block of weeks. We do the usual rolling within this block,
1693 // as is done elsewhere in this method. If we wind up on one of
1694 // the phantom days that we added, we recognize this and pin to
1695 // the first or the last day of the month. Easy, eh?
1696
1697 // Normalize the DAY_OF_WEEK so that 0 is the first day of the week
1698 // in this locale. We have dow in 0..6.
1699 int32_t dow = internalGet(UCAL_DAY_OF_WEEK) - getFirstDayOfWeek();
1700 if (dow < 0) dow += 7;
1701
1702 // Find the day of the week (normalized for locale) for the first
1703 // of the month.
1704 int32_t fdm = (dow - internalGet(UCAL_DAY_OF_MONTH) + 1) % 7;
1705 if (fdm < 0) fdm += 7;
1706
1707 // Get the first day of the first full week of the month,
1708 // including phantom days, if any. Figure out if the first week
1709 // counts or not; if it counts, then fill in phantom days. If
1710 // not, advance to the first real full week (skip the partial week).
1711 int32_t start;
1712 if ((7 - fdm) < getMinimalDaysInFirstWeek())
1713 start = 8 - fdm; // Skip the first partial week
1714 else
1715 start = 1 - fdm; // This may be zero or negative
1716
1717 // Get the day of the week (normalized for locale) for the last
1718 // day of the month.
1719 int32_t monthLen = getActualMaximum(UCAL_DAY_OF_MONTH, status);
1720 int32_t ldm = (monthLen - internalGet(UCAL_DAY_OF_MONTH) + dow) % 7;
1721 // We know monthLen >= DAY_OF_MONTH so we skip the += 7 step here.
1722
1723 // Get the limit day for the blocked-off rectangular month; that
1724 // is, the day which is one past the last day of the month,
1725 // after the month has already been filled in with phantom days
1726 // to fill out the last week. This day has a normalized DOW of 0.
1727 int32_t limit = monthLen + 7 - ldm;
1728
1729 // Now roll between start and (limit - 1).
1730 int32_t gap = limit - start;
1731 int32_t day_of_month = (internalGet(UCAL_DAY_OF_MONTH) + amount*7 -
1732 start) % gap;
1733 if (day_of_month < 0) day_of_month += gap;
1734 day_of_month += start;
1735
1736 // Finally, pin to the real start and end of the month.
1737 if (day_of_month < 1) day_of_month = 1;
1738 if (day_of_month > monthLen) day_of_month = monthLen;
1739
1740 // Set the DAY_OF_MONTH. We rely on the fact that this field
1741 // takes precedence over everything else (since all other fields
1742 // are also set at this point). If this fact changes (if the
1743 // disambiguation algorithm changes) then we will have to unset
1744 // the appropriate fields here so that DAY_OF_MONTH is attended
1745 // to.
1746 set(UCAL_DAY_OF_MONTH, day_of_month);
1747 return;
1748 }
1749 case UCAL_WEEK_OF_YEAR:
1750 {
1751 // This follows the outline of WEEK_OF_MONTH, except it applies
1752 // to the whole year. Please see the comment for WEEK_OF_MONTH
1753 // for general notes.
1754
1755 // Normalize the DAY_OF_WEEK so that 0 is the first day of the week
1756 // in this locale. We have dow in 0..6.
1757 int32_t dow = internalGet(UCAL_DAY_OF_WEEK) - getFirstDayOfWeek();
1758 if (dow < 0) dow += 7;
1759
1760 // Find the day of the week (normalized for locale) for the first
1761 // of the year.
1762 int32_t fdy = (dow - internalGet(UCAL_DAY_OF_YEAR) + 1) % 7;
1763 if (fdy < 0) fdy += 7;
1764
1765 // Get the first day of the first full week of the year,
1766 // including phantom days, if any. Figure out if the first week
1767 // counts or not; if it counts, then fill in phantom days. If
1768 // not, advance to the first real full week (skip the partial week).
1769 int32_t start;
1770 if ((7 - fdy) < getMinimalDaysInFirstWeek())
1771 start = 8 - fdy; // Skip the first partial week
1772 else
1773 start = 1 - fdy; // This may be zero or negative
1774
1775 // Get the day of the week (normalized for locale) for the last
1776 // day of the year.
1777 int32_t yearLen = getActualMaximum(UCAL_DAY_OF_YEAR,status);
1778 int32_t ldy = (yearLen - internalGet(UCAL_DAY_OF_YEAR) + dow) % 7;
1779 // We know yearLen >= DAY_OF_YEAR so we skip the += 7 step here.
1780
1781 // Get the limit day for the blocked-off rectangular year; that
1782 // is, the day which is one past the last day of the year,
1783 // after the year has already been filled in with phantom days
1784 // to fill out the last week. This day has a normalized DOW of 0.
1785 int32_t limit = yearLen + 7 - ldy;
1786
1787 // Now roll between start and (limit - 1).
1788 int32_t gap = limit - start;
1789 int32_t day_of_year = (internalGet(UCAL_DAY_OF_YEAR) + amount*7 -
1790 start) % gap;
1791 if (day_of_year < 0) day_of_year += gap;
1792 day_of_year += start;
1793
1794 // Finally, pin to the real start and end of the month.
1795 if (day_of_year < 1) day_of_year = 1;
1796 if (day_of_year > yearLen) day_of_year = yearLen;
1797
1798 // Make sure that the year and day of year are attended to by
1799 // clearing other fields which would normally take precedence.
1800 // If the disambiguation algorithm is changed, this section will
1801 // have to be updated as well.
1802 set(UCAL_DAY_OF_YEAR, day_of_year);
1803 clear(UCAL_MONTH);
1804 return;
1805 }
1806 case UCAL_DAY_OF_YEAR:
1807 {
1808 // Roll the day of year using millis. Compute the millis for
1809 // the start of the year, and get the length of the year.
1810 double delta = amount * kOneDay; // Scale up from days to millis
1811 double min2 = internalGet(UCAL_DAY_OF_YEAR)-1;
1812 min2 *= kOneDay;
1813 min2 = internalGetTime() - min2;
1814
1815 // double min2 = internalGetTime() - (internalGet(UCAL_DAY_OF_YEAR) - 1.0) * kOneDay;
1816 double newtime;
1817
1818 double yearLength = getActualMaximum(UCAL_DAY_OF_YEAR,status);
1819 double oneYear = yearLength;
1820 oneYear *= kOneDay;
1821 newtime = uprv_fmod((internalGetTime() + delta - min2), oneYear);
1822 if (newtime < 0) newtime += oneYear;
1823 setTimeInMillis(newtime + min2, status);
1824 return;
1825 }
1826 case UCAL_DAY_OF_WEEK:
1827 case UCAL_DOW_LOCAL:
1828 {
1829 // Roll the day of week using millis. Compute the millis for
1830 // the start of the week, using the first day of week setting.
1831 // Restrict the millis to [start, start+7days).
1832 double delta = amount * kOneDay; // Scale up from days to millis
1833 // Compute the number of days before the current day in this
1834 // week. This will be a value 0..6.
1835 int32_t leadDays = internalGet(field);
1836 leadDays -= (field == UCAL_DAY_OF_WEEK) ? getFirstDayOfWeek() : 1;
1837 if (leadDays < 0) leadDays += 7;
1838 double min2 = internalGetTime() - leadDays * kOneDay;
1839 double newtime = uprv_fmod((internalGetTime() + delta - min2), kOneWeek);
1840 if (newtime < 0) newtime += kOneWeek;
1841 setTimeInMillis(newtime + min2, status);
1842 return;
1843 }
1844 case UCAL_DAY_OF_WEEK_IN_MONTH:
1845 {
1846 // Roll the day of week in the month using millis. Determine
1847 // the first day of the week in the month, and then the last,
1848 // and then roll within that range.
1849 double delta = amount * kOneWeek; // Scale up from weeks to millis
1850 // Find the number of same days of the week before this one
1851 // in this month.
1852 int32_t preWeeks = (internalGet(UCAL_DAY_OF_MONTH) - 1) / 7;
1853 // Find the number of same days of the week after this one
1854 // in this month.
1855 int32_t postWeeks = (getActualMaximum(UCAL_DAY_OF_MONTH,status) -
1856 internalGet(UCAL_DAY_OF_MONTH)) / 7;
1857 // From these compute the min and gap millis for rolling.
1858 double min2 = internalGetTime() - preWeeks * kOneWeek;
1859 double gap2 = kOneWeek * (preWeeks + postWeeks + 1); // Must add 1!
1860 // Roll within this range
1861 double newtime = uprv_fmod((internalGetTime() + delta - min2), gap2);
1862 if (newtime < 0) newtime += gap2;
1863 setTimeInMillis(newtime + min2, status);
1864 return;
1865 }
1866 case UCAL_JULIAN_DAY:
1867 set(field, internalGet(field) + amount);
1868 return;
1869 default:
1870 // Other fields cannot be rolled by this method
374ca955 1871#if defined (U_DEBUG_CAL)
46f4442e
A
1872 fprintf(stderr, "%s:%d: ILLEGAL ARG because of roll on non-rollable field %s\n",
1873 __FILE__, __LINE__,fldName(field));
374ca955 1874#endif
46f4442e
A
1875 status = U_ILLEGAL_ARGUMENT_ERROR;
1876 }
374ca955
A
1877}
1878
1879void Calendar::add(EDateFields field, int32_t amount, UErrorCode& status)
1880{
46f4442e 1881 Calendar::add((UCalendarDateFields)field, amount, status);
374ca955
A
1882}
1883
1884// -------------------------------------
1885void Calendar::add(UCalendarDateFields field, int32_t amount, UErrorCode& status)
1886{
46f4442e
A
1887 if (amount == 0) {
1888 return; // Do nothing!
1889 }
1890
1891 // We handle most fields in the same way. The algorithm is to add
1892 // a computed amount of millis to the current millis. The only
4388f060
A
1893 // wrinkle is with DST (and/or a change to the zone's UTC offset, which
1894 // we'll include with DST) -- for some fields, like the DAY_OF_MONTH,
e4f10fab 1895 // we don't want the wall time to shift due to changes in DST. If the
46f4442e
A
1896 // result of the add operation is to move from DST to Standard, or
1897 // vice versa, we need to adjust by an hour forward or back,
e4f10fab 1898 // respectively. For such fields we set keepWallTimeInvariant to TRUE.
46f4442e
A
1899
1900 // We only adjust the DST for fields larger than an hour. For
1901 // fields smaller than an hour, we cannot adjust for DST without
1902 // causing problems. for instance, if you add one hour to April 5,
1903 // 1998, 1:00 AM, in PST, the time becomes "2:00 AM PDT" (an
1904 // illegal value), but then the adjustment sees the change and
1905 // compensates by subtracting an hour. As a result the time
1906 // doesn't advance at all.
1907
1908 // For some fields larger than a day, such as a UCAL_MONTH, we pin the
1909 // UCAL_DAY_OF_MONTH. This allows <March 31>.add(UCAL_MONTH, 1) to be
1910 // <April 30>, rather than <April 31> => <May 1>.
1911
1912 double delta = amount; // delta in ms
e4f10fab 1913 UBool keepWallTimeInvariant = TRUE;
46f4442e
A
1914
1915 switch (field) {
1916 case UCAL_ERA:
1917 set(field, get(field, status) + amount);
1918 pinField(UCAL_ERA, status);
1919 return;
1920
1921 case UCAL_YEAR:
46f4442e 1922 case UCAL_YEAR_WOY:
51004dcb
A
1923 {
1924 // * If era=0 and years go backwards in time, change sign of amount.
1925 // * Until we have new API per #9393, we temporarily hardcode knowledge of
1926 // which calendars have era 0 years that go backwards.
1927 // * Note that for UCAL_YEAR (but not UCAL_YEAR_WOY) we could instead handle
1928 // this by applying the amount to the UCAL_EXTENDED_YEAR field; but since
1929 // we would still need to handle UCAL_YEAR_WOY as below, might as well
1930 // also handle UCAL_YEAR the same way.
1931 int32_t era = get(UCAL_ERA, status);
1932 if (era == 0) {
1933 const char * calType = getType();
1934 if ( uprv_strcmp(calType,"gregorian")==0 || uprv_strcmp(calType,"roc")==0 || uprv_strcmp(calType,"coptic")==0 ) {
1935 amount = -amount;
1936 }
1937 }
1938 }
1939 // Fall through into normal handling
1940 case UCAL_EXTENDED_YEAR:
46f4442e 1941 case UCAL_MONTH:
4388f060
A
1942 {
1943 UBool oldLenient = isLenient();
1944 setLenient(TRUE);
46f4442e
A
1945 set(field, get(field, status) + amount);
1946 pinField(UCAL_DAY_OF_MONTH, status);
4388f060
A
1947 if(oldLenient==FALSE) {
1948 complete(status); /* force recalculate */
1949 setLenient(oldLenient);
1950 }
1951 }
1952 return;
46f4442e
A
1953
1954 case UCAL_WEEK_OF_YEAR:
1955 case UCAL_WEEK_OF_MONTH:
1956 case UCAL_DAY_OF_WEEK_IN_MONTH:
1957 delta *= kOneWeek;
1958 break;
1959
1960 case UCAL_AM_PM:
1961 delta *= 12 * kOneHour;
1962 break;
1963
1964 case UCAL_DAY_OF_MONTH:
1965 case UCAL_DAY_OF_YEAR:
1966 case UCAL_DAY_OF_WEEK:
1967 case UCAL_DOW_LOCAL:
1968 case UCAL_JULIAN_DAY:
1969 delta *= kOneDay;
1970 break;
1971
1972 case UCAL_HOUR_OF_DAY:
1973 case UCAL_HOUR:
1974 delta *= kOneHour;
e4f10fab 1975 keepWallTimeInvariant = FALSE;
46f4442e
A
1976 break;
1977
1978 case UCAL_MINUTE:
1979 delta *= kOneMinute;
e4f10fab 1980 keepWallTimeInvariant = FALSE;
46f4442e
A
1981 break;
1982
1983 case UCAL_SECOND:
1984 delta *= kOneSecond;
e4f10fab 1985 keepWallTimeInvariant = FALSE;
46f4442e
A
1986 break;
1987
1988 case UCAL_MILLISECOND:
1989 case UCAL_MILLISECONDS_IN_DAY:
e4f10fab 1990 keepWallTimeInvariant = FALSE;
46f4442e
A
1991 break;
1992
1993 default:
374ca955 1994#if defined (U_DEBUG_CAL)
46f4442e 1995 fprintf(stderr, "%s:%d: ILLEGAL ARG because field %s not addable",
374ca955
A
1996 __FILE__, __LINE__, fldName(field));
1997#endif
46f4442e
A
1998 status = U_ILLEGAL_ARGUMENT_ERROR;
1999 return;
2000 // throw new IllegalArgumentException("Calendar.add(" + fieldName(field) +
2001 // ") not supported");
2002 }
2003
e4f10fab 2004 // In order to keep the wall time invariant (for fields where this is
4388f060
A
2005 // appropriate), check the combined DST & ZONE offset before and
2006 // after the add() operation. If it changes, then adjust the millis
2007 // to compensate.
2008 int32_t prevOffset = 0;
e4f10fab
A
2009 int32_t prevWallTime = 0;
2010 if (keepWallTimeInvariant) {
4388f060 2011 prevOffset = get(UCAL_DST_OFFSET, status) + get(UCAL_ZONE_OFFSET, status);
e4f10fab 2012 prevWallTime = get(UCAL_MILLISECONDS_IN_DAY, status);
46f4442e
A
2013 }
2014
2015 setTimeInMillis(getTimeInMillis(status) + delta, status);
2016
e4f10fab
A
2017 if (keepWallTimeInvariant) {
2018 int32_t newWallTime = get(UCAL_MILLISECONDS_IN_DAY, status);
2019 if (newWallTime != prevWallTime) {
2020 // There is at least one zone transition between the base
2021 // time and the result time. As the result, wall time has
2022 // changed.
2023 UDate t = internalGetTime();
2024 int32_t newOffset = get(UCAL_DST_OFFSET, status) + get(UCAL_ZONE_OFFSET, status);
2025 if (newOffset != prevOffset) {
2026 // When the difference of the previous UTC offset and
2027 // the new UTC offset exceeds 1 full day, we do not want
2028 // to roll over/back the date. For now, this only happens
2029 // in Samoa (Pacific/Apia) on Dec 30, 2011. See ticket:9452.
2030 int32_t adjAmount = prevOffset - newOffset;
2031 adjAmount = adjAmount >= 0 ? adjAmount % (int32_t)kOneDay : -(-adjAmount % (int32_t)kOneDay);
2032 if (adjAmount != 0) {
2033 setTimeInMillis(t + adjAmount, status);
2034 newWallTime = get(UCAL_MILLISECONDS_IN_DAY, status);
2035 }
2036 if (newWallTime != prevWallTime) {
2037 // The result wall time or adjusted wall time was shifted because
2038 // the target wall time does not exist on the result date.
2039 switch (fSkippedWallTime) {
2040 case UCAL_WALLTIME_FIRST:
2041 if (adjAmount > 0) {
2042 setTimeInMillis(t, status);
2043 }
2044 break;
2045 case UCAL_WALLTIME_LAST:
2046 if (adjAmount < 0) {
2047 setTimeInMillis(t, status);
2048 }
2049 break;
2050 case UCAL_WALLTIME_NEXT_VALID:
2051 UDate tmpT = adjAmount > 0 ? internalGetTime() : t;
2052 UDate immediatePrevTrans;
2053 UBool hasTransition = getImmediatePreviousZoneTransition(tmpT, &immediatePrevTrans, status);
2054 if (U_SUCCESS(status) && hasTransition) {
2055 setTimeInMillis(immediatePrevTrans, status);
2056 }
2057 break;
2058 }
51004dcb 2059 }
46f4442e
A
2060 }
2061 }
2062 }
374ca955
A
2063}
2064
2065// -------------------------------------
2066int32_t Calendar::fieldDifference(UDate when, EDateFields field, UErrorCode& status) {
46f4442e 2067 return fieldDifference(when, (UCalendarDateFields) field, status);
374ca955
A
2068}
2069
2070int32_t Calendar::fieldDifference(UDate targetMs, UCalendarDateFields field, UErrorCode& ec) {
2071 if (U_FAILURE(ec)) return 0;
2072 int32_t min = 0;
2073 double startMs = getTimeInMillis(ec);
2074 // Always add from the start millis. This accomodates
2075 // operations like adding years from February 29, 2000 up to
2076 // February 29, 2004. If 1, 1, 1, 1 is added to the year
2077 // field, the DOM gets pinned to 28 and stays there, giving an
2078 // incorrect DOM difference of 1. We have to add 1, reset, 2,
2079 // reset, 3, reset, 4.
2080 if (startMs < targetMs) {
2081 int32_t max = 1;
2082 // Find a value that is too large
2083 while (U_SUCCESS(ec)) {
2084 setTimeInMillis(startMs, ec);
2085 add(field, max, ec);
2086 double ms = getTimeInMillis(ec);
2087 if (ms == targetMs) {
2088 return max;
2089 } else if (ms > targetMs) {
2090 break;
4388f060 2091 } else if (max < INT32_MAX) {
729e4ab9 2092 min = max;
374ca955 2093 max <<= 1;
b75a7d8f 2094 if (max < 0) {
4388f060
A
2095 max = INT32_MAX;
2096 }
2097 } else {
2098 // Field difference too large to fit into int32_t
374ca955 2099#if defined (U_DEBUG_CAL)
4388f060
A
2100 fprintf(stderr, "%s:%d: ILLEGAL ARG because field %s's max too large for int32_t\n",
2101 __FILE__, __LINE__, fldName(field));
374ca955 2102#endif
4388f060 2103 ec = U_ILLEGAL_ARGUMENT_ERROR;
b75a7d8f
A
2104 }
2105 }
2106 // Do a binary search
2107 while ((max - min) > 1 && U_SUCCESS(ec)) {
4388f060 2108 int32_t t = min + (max - min)/2; // make sure intermediate values don't exceed INT32_MAX
b75a7d8f
A
2109 setTimeInMillis(startMs, ec);
2110 add(field, t, ec);
2111 double ms = getTimeInMillis(ec);
2112 if (ms == targetMs) {
2113 return t;
2114 } else if (ms > targetMs) {
2115 max = t;
2116 } else {
2117 min = t;
2118 }
2119 }
2120 } else if (startMs > targetMs) {
2121 int32_t max = -1;
2122 // Find a value that is too small
2123 while (U_SUCCESS(ec)) {
2124 setTimeInMillis(startMs, ec);
2125 add(field, max, ec);
2126 double ms = getTimeInMillis(ec);
2127 if (ms == targetMs) {
2128 return max;
2129 } else if (ms < targetMs) {
2130 break;
2131 } else {
729e4ab9 2132 min = max;
b75a7d8f
A
2133 max <<= 1;
2134 if (max == 0) {
2135 // Field difference too large to fit into int32_t
374ca955 2136#if defined (U_DEBUG_CAL)
46f4442e
A
2137 fprintf(stderr, "%s:%d: ILLEGAL ARG because field %s's max too large for int32_t\n",
2138 __FILE__, __LINE__, fldName(field));
374ca955 2139#endif
b75a7d8f
A
2140 ec = U_ILLEGAL_ARGUMENT_ERROR;
2141 }
2142 }
2143 }
2144 // Do a binary search
2145 while ((min - max) > 1 && U_SUCCESS(ec)) {
4388f060 2146 int32_t t = min + (max - min)/2; // make sure intermediate values don't exceed INT32_MAX
b75a7d8f
A
2147 setTimeInMillis(startMs, ec);
2148 add(field, t, ec);
2149 double ms = getTimeInMillis(ec);
2150 if (ms == targetMs) {
2151 return t;
2152 } else if (ms < targetMs) {
2153 max = t;
2154 } else {
2155 min = t;
2156 }
2157 }
2158 }
2159 // Set calendar to end point
2160 setTimeInMillis(startMs, ec);
2161 add(field, min, ec);
46f4442e 2162
b75a7d8f
A
2163 /* Test for buffer overflows */
2164 if(U_FAILURE(ec)) {
2165 return 0;
2166 }
2167 return min;
2168}
2169
2170// -------------------------------------
2171
2172void
2173Calendar::adoptTimeZone(TimeZone* zone)
2174{
2175 // Do nothing if passed-in zone is NULL
2176 if (zone == NULL) return;
2177
2178 // fZone should always be non-null
2179 if (fZone != NULL) delete fZone;
2180 fZone = zone;
2181
2182 // if the zone changes, we need to recompute the time fields
2183 fAreFieldsSet = FALSE;
2184}
2185
2186// -------------------------------------
2187void
2188Calendar::setTimeZone(const TimeZone& zone)
2189{
2190 adoptTimeZone(zone.clone());
2191}
2192
2193// -------------------------------------
2194
2195const TimeZone&
2196Calendar::getTimeZone() const
2197{
2198 return *fZone;
2199}
2200
2201// -------------------------------------
2202
2203TimeZone*
2204Calendar::orphanTimeZone()
2205{
2206 TimeZone *z = fZone;
2207 // we let go of the time zone; the new time zone is the system default time zone
2208 fZone = TimeZone::createDefault();
2209 return z;
2210}
2211
2212// -------------------------------------
2213
2214void
2215Calendar::setLenient(UBool lenient)
2216{
2217 fLenient = lenient;
2218}
2219
2220// -------------------------------------
2221
2222UBool
2223Calendar::isLenient() const
2224{
2225 return fLenient;
2226}
2227
2228// -------------------------------------
2229
4388f060
A
2230void
2231Calendar::setRepeatedWallTimeOption(UCalendarWallTimeOption option)
2232{
2233 if (option == UCAL_WALLTIME_LAST || option == UCAL_WALLTIME_FIRST) {
2234 fRepeatedWallTime = option;
2235 }
2236}
2237
2238// -------------------------------------
2239
2240UCalendarWallTimeOption
2241Calendar::getRepeatedWallTimeOption(void) const
2242{
2243 return fRepeatedWallTime;
2244}
2245
2246// -------------------------------------
2247
2248void
2249Calendar::setSkippedWallTimeOption(UCalendarWallTimeOption option)
2250{
2251 fSkippedWallTime = option;
2252}
2253
2254// -------------------------------------
2255
2256UCalendarWallTimeOption
2257Calendar::getSkippedWallTimeOption(void) const
2258{
2259 return fSkippedWallTime;
2260}
2261
2262// -------------------------------------
2263
b75a7d8f
A
2264void
2265Calendar::setFirstDayOfWeek(UCalendarDaysOfWeek value)
2266{
46f4442e 2267 if (fFirstDayOfWeek != value &&
b75a7d8f 2268 value >= UCAL_SUNDAY && value <= UCAL_SATURDAY) {
46f4442e
A
2269 fFirstDayOfWeek = value;
2270 fAreFieldsSet = FALSE;
2271 }
b75a7d8f
A
2272}
2273
2274// -------------------------------------
2275
2276Calendar::EDaysOfWeek
2277Calendar::getFirstDayOfWeek() const
2278{
2279 return (Calendar::EDaysOfWeek)fFirstDayOfWeek;
2280}
2281
2282UCalendarDaysOfWeek
2283Calendar::getFirstDayOfWeek(UErrorCode & /*status*/) const
2284{
2285 return fFirstDayOfWeek;
2286}
2287// -------------------------------------
2288
2289void
2290Calendar::setMinimalDaysInFirstWeek(uint8_t value)
2291{
2292 // Values less than 1 have the same effect as 1; values greater
2293 // than 7 have the same effect as 7. However, we normalize values
2294 // so operator== and so forth work.
2295 if (value < 1) {
2296 value = 1;
2297 } else if (value > 7) {
2298 value = 7;
2299 }
2300 if (fMinimalDaysInFirstWeek != value) {
2301 fMinimalDaysInFirstWeek = value;
2302 fAreFieldsSet = FALSE;
2303 }
2304}
2305
2306// -------------------------------------
2307
2308uint8_t
2309Calendar::getMinimalDaysInFirstWeek() const
2310{
2311 return fMinimalDaysInFirstWeek;
2312}
2313
729e4ab9
A
2314// -------------------------------------
2315// weekend functions, just dummy implementations for now (for API freeze)
2316
2317UCalendarWeekdayType
2318Calendar::getDayOfWeekType(UCalendarDaysOfWeek dayOfWeek, UErrorCode &status) const
2319{
2320 if (U_FAILURE(status)) {
2321 return UCAL_WEEKDAY;
2322 }
2323 if (dayOfWeek < UCAL_SUNDAY || dayOfWeek > UCAL_SATURDAY) {
2324 status = U_ILLEGAL_ARGUMENT_ERROR;
2325 return UCAL_WEEKDAY;
2326 }
51004dcb
A
2327 if (fWeekendOnset == fWeekendCease) {
2328 if (dayOfWeek != fWeekendOnset)
2329 return UCAL_WEEKDAY;
2330 return (fWeekendOnsetMillis == 0) ? UCAL_WEEKEND : UCAL_WEEKEND_ONSET;
2331 }
729e4ab9
A
2332 if (fWeekendOnset < fWeekendCease) {
2333 if (dayOfWeek < fWeekendOnset || dayOfWeek > fWeekendCease) {
2334 return UCAL_WEEKDAY;
2335 }
2336 } else {
2337 if (dayOfWeek > fWeekendCease && dayOfWeek < fWeekendOnset) {
2338 return UCAL_WEEKDAY;
2339 }
2340 }
2341 if (dayOfWeek == fWeekendOnset) {
2342 return (fWeekendOnsetMillis == 0) ? UCAL_WEEKEND : UCAL_WEEKEND_ONSET;
2343 }
2344 if (dayOfWeek == fWeekendCease) {
51004dcb 2345 return (fWeekendCeaseMillis >= 86400000) ? UCAL_WEEKEND : UCAL_WEEKEND_CEASE;
729e4ab9
A
2346 }
2347 return UCAL_WEEKEND;
2348}
2349
2350int32_t
2351Calendar::getWeekendTransition(UCalendarDaysOfWeek dayOfWeek, UErrorCode &status) const
2352{
2353 if (U_FAILURE(status)) {
2354 return 0;
2355 }
2356 if (dayOfWeek == fWeekendOnset) {
2357 return fWeekendOnsetMillis;
2358 } else if (dayOfWeek == fWeekendCease) {
2359 return fWeekendCeaseMillis;
2360 }
2361 status = U_ILLEGAL_ARGUMENT_ERROR;
2362 return 0;
2363}
2364
2365UBool
2366Calendar::isWeekend(UDate date, UErrorCode &status) const
2367{
2368 if (U_FAILURE(status)) {
2369 return FALSE;
2370 }
2371 // clone the calendar so we don't mess with the real one.
2372 Calendar *work = (Calendar*)this->clone();
2373 if (work == NULL) {
2374 status = U_MEMORY_ALLOCATION_ERROR;
2375 return FALSE;
2376 }
2377 UBool result = FALSE;
2378 work->setTime(date, status);
2379 if (U_SUCCESS(status)) {
2380 result = work->isWeekend();
2381 }
2382 delete work;
2383 return result;
2384}
2385
2386UBool
2387Calendar::isWeekend(void) const
2388{
2389 UErrorCode status = U_ZERO_ERROR;
2390 UCalendarDaysOfWeek dayOfWeek = (UCalendarDaysOfWeek)get(UCAL_DAY_OF_WEEK, status);
2391 UCalendarWeekdayType dayType = getDayOfWeekType(dayOfWeek, status);
2392 if (U_SUCCESS(status)) {
2393 switch (dayType) {
2394 case UCAL_WEEKDAY:
2395 return FALSE;
2396 case UCAL_WEEKEND:
2397 return TRUE;
2398 case UCAL_WEEKEND_ONSET:
2399 case UCAL_WEEKEND_CEASE:
2400 // Use internalGet() because the above call to get() populated all fields.
2401 {
2402 int32_t millisInDay = internalGet(UCAL_MILLISECONDS_IN_DAY);
2403 int32_t transitionMillis = getWeekendTransition(dayOfWeek, status);
2404 if (U_SUCCESS(status)) {
2405 return (dayType == UCAL_WEEKEND_ONSET)?
2406 (millisInDay >= transitionMillis):
2407 (millisInDay < transitionMillis);
2408 }
2409 // else fall through, return FALSE
2410 }
2411 default:
2412 break;
2413 }
2414 }
2415 return FALSE;
2416}
2417
374ca955
A
2418// ------------------------------------- limits
2419
2420int32_t
2421Calendar::getMinimum(EDateFields field) const {
2422 return getLimit((UCalendarDateFields) field,UCAL_LIMIT_MINIMUM);
2423}
2424
2425int32_t
2426Calendar::getMinimum(UCalendarDateFields field) const
2427{
2428 return getLimit(field,UCAL_LIMIT_MINIMUM);
2429}
2430
2431// -------------------------------------
2432int32_t
2433Calendar::getMaximum(EDateFields field) const
2434{
2435 return getLimit((UCalendarDateFields) field,UCAL_LIMIT_MAXIMUM);
2436}
2437
2438int32_t
2439Calendar::getMaximum(UCalendarDateFields field) const
2440{
2441 return getLimit(field,UCAL_LIMIT_MAXIMUM);
2442}
2443
b75a7d8f 2444// -------------------------------------
374ca955
A
2445int32_t
2446Calendar::getGreatestMinimum(EDateFields field) const
2447{
2448 return getLimit((UCalendarDateFields)field,UCAL_LIMIT_GREATEST_MINIMUM);
2449}
2450
2451int32_t
2452Calendar::getGreatestMinimum(UCalendarDateFields field) const
2453{
2454 return getLimit(field,UCAL_LIMIT_GREATEST_MINIMUM);
2455}
2456
2457// -------------------------------------
2458int32_t
2459Calendar::getLeastMaximum(EDateFields field) const
2460{
2461 return getLimit((UCalendarDateFields) field,UCAL_LIMIT_LEAST_MAXIMUM);
2462}
2463
2464int32_t
2465Calendar::getLeastMaximum(UCalendarDateFields field) const
2466{
2467 return getLimit( field,UCAL_LIMIT_LEAST_MAXIMUM);
2468}
2469
2470// -------------------------------------
2471int32_t
2472Calendar::getActualMinimum(EDateFields field, UErrorCode& status) const
2473{
2474 return getActualMinimum((UCalendarDateFields) field, status);
2475}
2476
2477int32_t Calendar::getLimit(UCalendarDateFields field, ELimitType limitType) const {
2478 switch (field) {
2479 case UCAL_DAY_OF_WEEK:
2480 case UCAL_AM_PM:
2481 case UCAL_HOUR:
2482 case UCAL_HOUR_OF_DAY:
2483 case UCAL_MINUTE:
2484 case UCAL_SECOND:
2485 case UCAL_MILLISECOND:
2486 case UCAL_ZONE_OFFSET:
2487 case UCAL_DST_OFFSET:
2488 case UCAL_DOW_LOCAL:
2489 case UCAL_JULIAN_DAY:
2490 case UCAL_MILLISECONDS_IN_DAY:
46f4442e 2491 case UCAL_IS_LEAP_MONTH:
374ca955 2492 return kCalendarLimits[field][limitType];
46f4442e
A
2493
2494 case UCAL_WEEK_OF_MONTH:
2495 {
2496 int32_t limit;
2497 if (limitType == UCAL_LIMIT_MINIMUM) {
2498 limit = getMinimalDaysInFirstWeek() == 1 ? 1 : 0;
2499 } else if (limitType == UCAL_LIMIT_GREATEST_MINIMUM) {
2500 limit = 1;
2501 } else {
2502 int32_t minDaysInFirst = getMinimalDaysInFirstWeek();
2503 int32_t daysInMonth = handleGetLimit(UCAL_DAY_OF_MONTH, limitType);
2504 if (limitType == UCAL_LIMIT_LEAST_MAXIMUM) {
2505 limit = (daysInMonth + (7 - minDaysInFirst)) / 7;
2506 } else { // limitType == UCAL_LIMIT_MAXIMUM
2507 limit = (daysInMonth + 6 + (7 - minDaysInFirst)) / 7;
2508 }
2509 }
2510 return limit;
2511 }
374ca955
A
2512 default:
2513 return handleGetLimit(field, limitType);
2514 }
2515}
2516
b75a7d8f
A
2517
2518int32_t
2519Calendar::getActualMinimum(UCalendarDateFields field, UErrorCode& status) const
2520{
2521 int32_t fieldValue = getGreatestMinimum(field);
2522 int32_t endValue = getMinimum(field);
2523
2524 // if we know that the minimum value is always the same, just return it
2525 if (fieldValue == endValue) {
2526 return fieldValue;
2527 }
2528
2529 // clone the calendar so we don't mess with the real one, and set it to
2530 // accept anything for the field values
2531 Calendar *work = (Calendar*)this->clone();
46f4442e
A
2532 if (work == NULL) {
2533 status = U_MEMORY_ALLOCATION_ERROR;
2534 return 0;
2535 }
b75a7d8f
A
2536 work->setLenient(TRUE);
2537
2538 // now try each value from getLeastMaximum() to getMaximum() one by one until
2539 // we get a value that normalizes to another value. The last value that
2540 // normalizes to itself is the actual minimum for the current date
2541 int32_t result = fieldValue;
2542
2543 do {
2544 work->set(field, fieldValue);
2545 if (work->get(field, status) != fieldValue) {
2546 break;
2547 }
2548 else {
2549 result = fieldValue;
2550 fieldValue--;
2551 }
2552 } while (fieldValue >= endValue);
2553
2554 delete work;
2555
2556 /* Test for buffer overflows */
2557 if(U_FAILURE(status)) {
2558 return 0;
2559 }
2560 return result;
2561}
2562
2563// -------------------------------------
2564
374ca955
A
2565
2566
2567/**
46f4442e
A
2568* Ensure that each field is within its valid range by calling {@link
2569* #validateField(int)} on each field that has been set. This method
2570* should only be called if this calendar is not lenient.
2571* @see #isLenient
2572* @see #validateField(int)
2573*/
374ca955 2574void Calendar::validateFields(UErrorCode &status) {
46f4442e 2575 for (int32_t field = 0; U_SUCCESS(status) && (field < UCAL_FIELD_COUNT); field++) {
4388f060 2576 if (fStamp[field] >= kMinimumUserStamp) {
46f4442e
A
2577 validateField((UCalendarDateFields)field, status);
2578 }
374ca955 2579 }
374ca955
A
2580}
2581
2582/**
46f4442e
A
2583* Validate a single field of this calendar. Subclasses should
2584* override this method to validate any calendar-specific fields.
2585* Generic fields can be handled by
2586* <code>Calendar.validateField()</code>.
2587* @see #validateField(int, int, int)
2588*/
374ca955 2589void Calendar::validateField(UCalendarDateFields field, UErrorCode &status) {
46f4442e
A
2590 int32_t y;
2591 switch (field) {
2592 case UCAL_DAY_OF_MONTH:
2593 y = handleGetExtendedYear();
2594 validateField(field, 1, handleGetMonthLength(y, internalGet(UCAL_MONTH)), status);
2595 break;
2596 case UCAL_DAY_OF_YEAR:
2597 y = handleGetExtendedYear();
2598 validateField(field, 1, handleGetYearLength(y), status);
2599 break;
2600 case UCAL_DAY_OF_WEEK_IN_MONTH:
2601 if (internalGet(field) == 0) {
374ca955 2602#if defined (U_DEBUG_CAL)
46f4442e
A
2603 fprintf(stderr, "%s:%d: ILLEGAL ARG because DOW in month cannot be 0\n",
2604 __FILE__, __LINE__);
374ca955 2605#endif
46f4442e
A
2606 status = U_ILLEGAL_ARGUMENT_ERROR; // "DAY_OF_WEEK_IN_MONTH cannot be zero"
2607 return;
2608 }
2609 validateField(field, getMinimum(field), getMaximum(field), status);
2610 break;
2611 default:
2612 validateField(field, getMinimum(field), getMaximum(field), status);
2613 break;
374ca955 2614 }
374ca955
A
2615}
2616
2617/**
46f4442e
A
2618* Validate a single field of this calendar given its minimum and
2619* maximum allowed value. If the field is out of range, throw a
2620* descriptive <code>IllegalArgumentException</code>. Subclasses may
2621* use this method in their implementation of {@link
2622* #validateField(int)}.
2623*/
374ca955
A
2624void Calendar::validateField(UCalendarDateFields field, int32_t min, int32_t max, UErrorCode& status)
2625{
46f4442e
A
2626 int32_t value = fFields[field];
2627 if (value < min || value > max) {
374ca955 2628#if defined (U_DEBUG_CAL)
46f4442e
A
2629 fprintf(stderr, "%s:%d: ILLEGAL ARG because of field %s out of range %d..%d at %d\n",
2630 __FILE__, __LINE__,fldName(field),min,max,value);
374ca955 2631#endif
46f4442e
A
2632 status = U_ILLEGAL_ARGUMENT_ERROR;
2633 return;
2634 }
374ca955
A
2635}
2636
2637// -------------------------
2638
2639const UFieldResolutionTable* Calendar::getFieldResolutionTable() const {
46f4442e 2640 return kDatePrecedence;
374ca955
A
2641}
2642
2643
2644UCalendarDateFields Calendar::newerField(UCalendarDateFields defaultField, UCalendarDateFields alternateField) const
2645{
46f4442e
A
2646 if (fStamp[alternateField] > fStamp[defaultField]) {
2647 return alternateField;
2648 }
2649 return defaultField;
374ca955
A
2650}
2651
2652UCalendarDateFields Calendar::resolveFields(const UFieldResolutionTable* precedenceTable) {
46f4442e 2653 int32_t bestField = UCAL_FIELD_COUNT;
51004dcb
A
2654 int32_t tempBestField;
2655 UBool restoreWeekOfInternalSet = FALSE;
2656 if (fStamp[UCAL_DAY_OF_WEEK] >= kMinimumUserStamp &&
2657 fStamp[UCAL_DATE] >= kMinimumUserStamp &&
2658 fStamp[UCAL_MONTH] >= kMinimumUserStamp &&
2659 fStamp[UCAL_WEEK_OF_YEAR] == kInternallySet &&
2660 fStamp[UCAL_WEEK_OF_MONTH] == kInternallySet &&
2661 fStamp[UCAL_DAY_OF_WEEK_IN_MONTH] == kInternallySet) {
2662 int32_t monthStampDelta = fStamp[UCAL_DAY_OF_WEEK] - fStamp[UCAL_MONTH];
2663 int32_t dateStampDelta = fStamp[UCAL_DAY_OF_WEEK] - fStamp[UCAL_DATE];
2664 if ( monthStampDelta >= 1 && monthStampDelta <= 3 && dateStampDelta >= 1 && dateStampDelta <= 3 ) {
2665 // If UCAL_MONTH, UCAL_DATE and UCAL_DAY_OF_WEEK are all explicitly set nearly one after the
2666 // other (as when parsing a single date format), with UCAL_DAY_OF_WEEK set most recently, and
2667 // if UCAL_WEEK_OF_YEAR, UCAL_WEEK_OF_MONTH, and UCAL_DAY_OF_WEEK_IN_MONTH are all only
2668 // implicitly set (as from setTimeInMillis), then for the calculations in this call temporarily
2669 // treat UCAL_WEEK_OF_YEAR, UCAL_WEEK_OF_MONTH, and UCAL_DAY_OF_WEEK_IN_MONTH as unset so they
2670 // don't combine with UCAL_DAY_OF_WEEK to override the date in UCAL_MONTH & UCAL_DATE. All of
2671 // these conditions are to avoid messing up the case of parsing a format with UCAL_DAY_OF_WEEK
2672 // alone or in combination with other fields besides UCAL_MONTH, UCAL_DATE. Note: the current
2673 // stamp value is incremented each time Calendar::set is called to explicitly set a field value.
2674 fStamp[UCAL_WEEK_OF_YEAR] = kUnset;
2675 fStamp[UCAL_WEEK_OF_MONTH] = kUnset;
2676 fStamp[UCAL_DAY_OF_WEEK_IN_MONTH] = kUnset;
2677 restoreWeekOfInternalSet = TRUE;
2678 }
2679 }
46f4442e
A
2680 for (int32_t g=0; precedenceTable[g][0][0] != -1 && (bestField == UCAL_FIELD_COUNT); ++g) {
2681 int32_t bestStamp = kUnset;
2682 for (int32_t l=0; precedenceTable[g][l][0] != -1; ++l) {
2683 int32_t lineStamp = kUnset;
2684 // Skip over first entry if it is negative
2685 for (int32_t i=((precedenceTable[g][l][0]>=kResolveRemap)?1:0); precedenceTable[g][l][i]!=-1; ++i) {
4388f060 2686 U_ASSERT(precedenceTable[g][l][i] < UCAL_FIELD_COUNT);
46f4442e
A
2687 int32_t s = fStamp[precedenceTable[g][l][i]];
2688 // If any field is unset then don't use this line
2689 if (s == kUnset) {
2690 goto linesInGroup;
2691 } else if(s > lineStamp) {
2692 lineStamp = s;
2693 }
2694 }
2695 // Record new maximum stamp & field no.
2696 if (lineStamp > bestStamp) {
51004dcb
A
2697 tempBestField = precedenceTable[g][l][0]; // First field refers to entire line
2698 if (tempBestField >= kResolveRemap) {
2699 tempBestField &= (kResolveRemap-1);
2700 // This check is needed to resolve some issues with UCAL_YEAR precedence mapping
2701 if (tempBestField != UCAL_DATE || (fStamp[UCAL_WEEK_OF_MONTH] < fStamp[tempBestField])) {
2702 bestField = tempBestField;
2703 }
2704 } else {
2705 bestField = tempBestField;
2706 }
2707
2708 if (bestField == tempBestField) {
2709 bestStamp = lineStamp;
2710 }
46f4442e
A
2711 }
2712linesInGroup:
2713 ;
374ca955 2714 }
374ca955 2715 }
51004dcb
A
2716 if (restoreWeekOfInternalSet) {
2717 // Restore the field stamps temporarily unset above.
2718 fStamp[UCAL_WEEK_OF_YEAR] = kInternallySet;
2719 fStamp[UCAL_WEEK_OF_MONTH] = kInternallySet;
2720 fStamp[UCAL_DAY_OF_WEEK_IN_MONTH] = kInternallySet;
2721 }
2722 return (UCalendarDateFields)bestField;
374ca955
A
2723}
2724
2725const UFieldResolutionTable Calendar::kDatePrecedence[] =
46f4442e 2726{
374ca955 2727 {
46f4442e
A
2728 { UCAL_DAY_OF_MONTH, kResolveSTOP },
2729 { UCAL_WEEK_OF_YEAR, UCAL_DAY_OF_WEEK, kResolveSTOP },
2730 { UCAL_WEEK_OF_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP },
2731 { UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP },
2732 { UCAL_WEEK_OF_YEAR, UCAL_DOW_LOCAL, kResolveSTOP },
2733 { UCAL_WEEK_OF_MONTH, UCAL_DOW_LOCAL, kResolveSTOP },
2734 { UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DOW_LOCAL, kResolveSTOP },
2735 { UCAL_DAY_OF_YEAR, kResolveSTOP },
2736 { kResolveRemap | UCAL_DAY_OF_MONTH, UCAL_YEAR, kResolveSTOP }, // if YEAR is set over YEAR_WOY use DAY_OF_MONTH
2737 { kResolveRemap | UCAL_WEEK_OF_YEAR, UCAL_YEAR_WOY, kResolveSTOP }, // if YEAR_WOY is set, calc based on WEEK_OF_YEAR
2738 { kResolveSTOP }
374ca955
A
2739 },
2740 {
46f4442e
A
2741 { UCAL_WEEK_OF_YEAR, kResolveSTOP },
2742 { UCAL_WEEK_OF_MONTH, kResolveSTOP },
2743 { UCAL_DAY_OF_WEEK_IN_MONTH, kResolveSTOP },
2744 { kResolveRemap | UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DAY_OF_WEEK, kResolveSTOP },
2745 { kResolveRemap | UCAL_DAY_OF_WEEK_IN_MONTH, UCAL_DOW_LOCAL, kResolveSTOP },
2746 { kResolveSTOP }
374ca955
A
2747 },
2748 {{kResolveSTOP}}
46f4442e 2749};
374ca955
A
2750
2751
2752const UFieldResolutionTable Calendar::kDOWPrecedence[] =
2753{
46f4442e
A
2754 {
2755 { UCAL_DAY_OF_WEEK,kResolveSTOP, kResolveSTOP },
2756 { UCAL_DOW_LOCAL,kResolveSTOP, kResolveSTOP },
2757 {kResolveSTOP}
2758 },
2759 {{kResolveSTOP}}
374ca955
A
2760};
2761
2762// precedence for calculating a year
2763const UFieldResolutionTable Calendar::kYearPrecedence[] =
2764{
46f4442e
A
2765 {
2766 { UCAL_YEAR, kResolveSTOP },
2767 { UCAL_EXTENDED_YEAR, kResolveSTOP },
2768 { UCAL_YEAR_WOY, UCAL_WEEK_OF_YEAR, kResolveSTOP }, // YEAR_WOY is useless without WEEK_OF_YEAR
2769 { kResolveSTOP }
2770 },
2771 {{kResolveSTOP}}
374ca955
A
2772};
2773
2774
2775// -------------------------
2776
2777
2778void Calendar::computeTime(UErrorCode& status) {
46f4442e
A
2779 if (!isLenient()) {
2780 validateFields(status);
2781 if (U_FAILURE(status)) {
2782 return;
2783 }
2784 }
374ca955 2785
46f4442e
A
2786 // Compute the Julian day
2787 int32_t julianDay = computeJulianDay();
374ca955 2788
46f4442e 2789 double millis = Grego::julianDayToMillis(julianDay);
374ca955
A
2790
2791#if defined (U_DEBUG_CAL)
729e4ab9 2792 // int32_t julianInsanityCheck = (int32_t)ClockMath::floorDivide(millis, kOneDay);
46f4442e
A
2793 // julianInsanityCheck += kEpochStartAsJulianDay;
2794 // if(1 || julianInsanityCheck != julianDay) {
2795 // fprintf(stderr, "%s:%d- D'oh- computed jules %d, to mills (%s)%.lf, recomputed %d\n",
2796 // __FILE__, __LINE__, julianDay, millis<0.0?"NEG":"", millis, julianInsanityCheck);
2797 // }
374ca955
A
2798#endif
2799
46f4442e
A
2800 int32_t millisInDay;
2801
2802 // We only use MILLISECONDS_IN_DAY if it has been set by the user.
2803 // This makes it possible for the caller to set the calendar to a
2804 // time and call clear(MONTH) to reset the MONTH to January. This
2805 // is legacy behavior. Without this, clear(MONTH) has no effect,
2806 // since the internally set JULIAN_DAY is used.
2807 if (fStamp[UCAL_MILLISECONDS_IN_DAY] >= ((int32_t)kMinimumUserStamp) &&
4388f060
A
2808 newestStamp(UCAL_AM_PM, UCAL_MILLISECOND, kUnset) <= fStamp[UCAL_MILLISECONDS_IN_DAY]) {
2809 millisInDay = internalGet(UCAL_MILLISECONDS_IN_DAY);
2810 } else {
2811 millisInDay = computeMillisInDay();
2812 }
46f4442e 2813
4388f060
A
2814 UDate t = 0;
2815 if (fStamp[UCAL_ZONE_OFFSET] >= ((int32_t)kMinimumUserStamp) || fStamp[UCAL_DST_OFFSET] >= ((int32_t)kMinimumUserStamp)) {
2816 t = millis + millisInDay - (internalGet(UCAL_ZONE_OFFSET) + internalGet(UCAL_DST_OFFSET));
2817 } else {
46f4442e
A
2818 // Compute the time zone offset and DST offset. There are two potential
2819 // ambiguities here. We'll assume a 2:00 am (wall time) switchover time
2820 // for discussion purposes here.
4388f060
A
2821 //
2822 // 1. The positive offset change such as transition into DST.
2823 // Here, a designated time of 2:00 am - 2:59 am does not actually exist.
2824 // For this case, skippedWallTime option specifies the behavior.
2825 // For example, 2:30 am is interpreted as;
2826 // - WALLTIME_LAST(default): 3:30 am (DST) (interpreting 2:30 am as 31 minutes after 1:59 am (STD))
2827 // - WALLTIME_FIRST: 1:30 am (STD) (interpreting 2:30 am as 30 minutes before 3:00 am (DST))
2828 // - WALLTIME_NEXT_VALID: 3:00 am (DST) (next valid time after 2:30 am on a wall clock)
2829 // 2. The negative offset change such as transition out of DST.
2830 // Here, a designated time of 1:00 am - 1:59 am can be in standard or DST. Both are valid
2831 // representations (the rep jumps from 1:59:59 DST to 1:00:00 Std).
2832 // For this case, repeatedWallTime option specifies the behavior.
2833 // For example, 1:30 am is interpreted as;
2834 // - WALLTIME_LAST(default): 1:30 am (STD) - latter occurrence
2835 // - WALLTIME_FIRST: 1:30 am (DST) - former occurrence
2836 //
2837 // In addition to above, when calendar is strict (not default), wall time falls into
2838 // the skipped time range will be processed as an error case.
2839 //
2840 // These special cases are mostly handled in #computeZoneOffset(long), except WALLTIME_NEXT_VALID
2841 // at positive offset change. The protected method computeZoneOffset(long) is exposed to Calendar
2842 // subclass implementations and marked as @stable. Strictly speaking, WALLTIME_NEXT_VALID
2843 // should be also handled in the same place, but we cannot change the code flow without deprecating
2844 // the protected method.
2845 //
46f4442e
A
2846 // We use the TimeZone object, unless the user has explicitly set the ZONE_OFFSET
2847 // or DST_OFFSET fields; then we use those fields.
46f4442e 2848
4388f060
A
2849 if (!isLenient() || fSkippedWallTime == UCAL_WALLTIME_NEXT_VALID) {
2850 // When strict, invalidate a wall time falls into a skipped wall time range.
2851 // When lenient and skipped wall time option is WALLTIME_NEXT_VALID,
2852 // the result time will be adjusted to the next valid time (on wall clock).
2853 int32_t zoneOffset = computeZoneOffset(millis, millisInDay, status);
2854 UDate tmpTime = millis + millisInDay - zoneOffset;
2855
2856 int32_t raw, dst;
2857 fZone->getOffset(tmpTime, FALSE, raw, dst, status);
2858
2859 if (U_SUCCESS(status)) {
2860 // zoneOffset != (raw + dst) only when the given wall time fall into
2861 // a skipped wall time range caused by positive zone offset transition.
2862 if (zoneOffset != (raw + dst)) {
2863 if (!isLenient()) {
2864 status = U_ILLEGAL_ARGUMENT_ERROR;
2865 } else {
2866 U_ASSERT(fSkippedWallTime == UCAL_WALLTIME_NEXT_VALID);
2867 // Adjust time to the next valid wall clock time.
2868 // At this point, tmpTime is on or after the zone offset transition causing
2869 // the skipped time range.
e4f10fab
A
2870 UDate immediatePrevTransition;
2871 UBool hasTransition = getImmediatePreviousZoneTransition(tmpTime, &immediatePrevTransition, status);
2872 if (U_SUCCESS(status) && hasTransition) {
2873 t = immediatePrevTransition;
4388f060
A
2874 }
2875 }
2876 } else {
2877 t = tmpTime;
2878 }
2879 }
2880 } else {
2881 t = millis + millisInDay - computeZoneOffset(millis, millisInDay, status);
2882 }
2883 }
2884 if (U_SUCCESS(status)) {
2885 internalSetTime(t);
2886 }
374ca955
A
2887}
2888
e4f10fab
A
2889/**
2890 * Find the previous zone transtion near the given time.
2891 */
2892UBool Calendar::getImmediatePreviousZoneTransition(UDate base, UDate *transitionTime, UErrorCode& status) const {
2893 BasicTimeZone *btz = getBasicTimeZone();
2894 if (btz) {
2895 TimeZoneTransition trans;
2896 UBool hasTransition = btz->getPreviousTransition(base, TRUE, trans);
2897 if (hasTransition) {
2898 *transitionTime = trans.getTime();
2899 return TRUE;
2900 } else {
2901 // Could not find any transitions.
2902 // Note: This should never happen.
2903 status = U_INTERNAL_PROGRAM_ERROR;
2904 }
2905 } else {
2906 // If not BasicTimeZone, return unsupported error for now.
2907 // TODO: We may support non-BasicTimeZone in future.
2908 status = U_UNSUPPORTED_ERROR;
2909 }
2910 return FALSE;
2911}
2912
374ca955 2913/**
46f4442e
A
2914* Compute the milliseconds in the day from the fields. This is a
2915* value from 0 to 23:59:59.999 inclusive, unless fields are out of
2916* range, in which case it can be an arbitrary value. This value
2917* reflects local zone wall time.
2918* @stable ICU 2.0
2919*/
374ca955
A
2920int32_t Calendar::computeMillisInDay() {
2921 // Do the time portion of the conversion.
46f4442e
A
2922
2923 int32_t millisInDay = 0;
2924
2925 // Find the best set of fields specifying the time of day. There
2926 // are only two possibilities here; the HOUR_OF_DAY or the
2927 // AM_PM and the HOUR.
2928 int32_t hourOfDayStamp = fStamp[UCAL_HOUR_OF_DAY];
2929 int32_t hourStamp = (fStamp[UCAL_HOUR] > fStamp[UCAL_AM_PM])?fStamp[UCAL_HOUR]:fStamp[UCAL_AM_PM];
2930 int32_t bestStamp = (hourStamp > hourOfDayStamp) ? hourStamp : hourOfDayStamp;
2931
2932 // Hours
2933 if (bestStamp != kUnset) {
2934 if (bestStamp == hourOfDayStamp) {
2935 // Don't normalize here; let overflow bump into the next period.
2936 // This is consistent with how we handle other fields.
2937 millisInDay += internalGet(UCAL_HOUR_OF_DAY);
2938 } else {
2939 // Don't normalize here; let overflow bump into the next period.
2940 // This is consistent with how we handle other fields.
2941 millisInDay += internalGet(UCAL_HOUR);
2942 millisInDay += 12 * internalGet(UCAL_AM_PM); // Default works for unset AM_PM
2943 }
2944 }
2945
2946 // We use the fact that unset == 0; we start with millisInDay
2947 // == HOUR_OF_DAY.
2948 millisInDay *= 60;
2949 millisInDay += internalGet(UCAL_MINUTE); // now have minutes
2950 millisInDay *= 60;
2951 millisInDay += internalGet(UCAL_SECOND); // now have seconds
2952 millisInDay *= 1000;
2953 millisInDay += internalGet(UCAL_MILLISECOND); // now have millis
2954
2955 return millisInDay;
374ca955
A
2956}
2957
2958/**
46f4442e
A
2959* This method can assume EXTENDED_YEAR has been set.
2960* @param millis milliseconds of the date fields
2961* @param millisInDay milliseconds of the time fields; may be out
2962* or range.
2963* @stable ICU 2.0
2964*/
374ca955 2965int32_t Calendar::computeZoneOffset(double millis, int32_t millisInDay, UErrorCode &ec) {
46f4442e 2966 int32_t rawOffset, dstOffset;
4388f060
A
2967 UDate wall = millis + millisInDay;
2968 BasicTimeZone* btz = getBasicTimeZone();
2969 if (btz) {
2970 int duplicatedTimeOpt = (fRepeatedWallTime == UCAL_WALLTIME_FIRST) ? BasicTimeZone::kFormer : BasicTimeZone::kLatter;
2971 int nonExistingTimeOpt = (fSkippedWallTime == UCAL_WALLTIME_FIRST) ? BasicTimeZone::kLatter : BasicTimeZone::kFormer;
2972 btz->getOffsetFromLocal(wall, nonExistingTimeOpt, duplicatedTimeOpt, rawOffset, dstOffset, ec);
2973 } else {
2974 const TimeZone& tz = getTimeZone();
2975 // By default, TimeZone::getOffset behaves UCAL_WALLTIME_LAST for both.
2976 tz.getOffset(wall, TRUE, rawOffset, dstOffset, ec);
2977
2978 UBool sawRecentNegativeShift = FALSE;
2979 if (fRepeatedWallTime == UCAL_WALLTIME_FIRST) {
2980 // Check if the given wall time falls into repeated time range
2981 UDate tgmt = wall - (rawOffset + dstOffset);
2982
2983 // Any negative zone transition within last 6 hours?
2984 // Note: The maximum historic negative zone transition is -3 hours in the tz database.
2985 // 6 hour window would be sufficient for this purpose.
2986 int32_t tmpRaw, tmpDst;
2987 tz.getOffset(tgmt - 6*60*60*1000, FALSE, tmpRaw, tmpDst, ec);
2988 int32_t offsetDelta = (rawOffset + dstOffset) - (tmpRaw + tmpDst);
2989
2990 U_ASSERT(offsetDelta < -6*60*60*1000);
2991 if (offsetDelta < 0) {
2992 sawRecentNegativeShift = TRUE;
2993 // Negative shift within last 6 hours. When UCAL_WALLTIME_FIRST is used and the given wall time falls
2994 // into the repeated time range, use offsets before the transition.
2995 // Note: If it does not fall into the repeated time range, offsets remain unchanged below.
2996 tz.getOffset(wall + offsetDelta, TRUE, rawOffset, dstOffset, ec);
2997 }
2998 }
2999 if (!sawRecentNegativeShift && fSkippedWallTime == UCAL_WALLTIME_FIRST) {
3000 // When skipped wall time option is WALLTIME_FIRST,
3001 // recalculate offsets from the resolved time (non-wall).
3002 // When the given wall time falls into skipped wall time,
3003 // the offsets will be based on the zone offsets AFTER
3004 // the transition (which means, earliest possibe interpretation).
3005 UDate tgmt = wall - (rawOffset + dstOffset);
3006 tz.getOffset(tgmt, FALSE, rawOffset, dstOffset, ec);
3007 }
3008 }
46f4442e 3009 return rawOffset + dstOffset;
374ca955
A
3010}
3011
3012int32_t Calendar::computeJulianDay()
3013{
46f4442e
A
3014 // We want to see if any of the date fields is newer than the
3015 // JULIAN_DAY. If not, then we use JULIAN_DAY. If so, then we do
3016 // the normal resolution. We only use JULIAN_DAY if it has been
3017 // set by the user. This makes it possible for the caller to set
3018 // the calendar to a time and call clear(MONTH) to reset the MONTH
3019 // to January. This is legacy behavior. Without this,
3020 // clear(MONTH) has no effect, since the internally set JULIAN_DAY
3021 // is used.
3022 if (fStamp[UCAL_JULIAN_DAY] >= (int32_t)kMinimumUserStamp) {
3023 int32_t bestStamp = newestStamp(UCAL_ERA, UCAL_DAY_OF_WEEK_IN_MONTH, kUnset);
3024 bestStamp = newestStamp(UCAL_YEAR_WOY, UCAL_EXTENDED_YEAR, bestStamp);
3025 if (bestStamp <= fStamp[UCAL_JULIAN_DAY]) {
3026 return internalGet(UCAL_JULIAN_DAY);
3027 }
374ca955 3028 }
374ca955 3029
46f4442e
A
3030 UCalendarDateFields bestField = resolveFields(getFieldResolutionTable());
3031 if (bestField == UCAL_FIELD_COUNT) {
3032 bestField = UCAL_DAY_OF_MONTH;
3033 }
374ca955 3034
46f4442e 3035 return handleComputeJulianDay(bestField);
374ca955
A
3036}
3037
3038// -------------------------------------------
3039
3040int32_t Calendar::handleComputeJulianDay(UCalendarDateFields bestField) {
46f4442e
A
3041 UBool useMonth = (bestField == UCAL_DAY_OF_MONTH ||
3042 bestField == UCAL_WEEK_OF_MONTH ||
3043 bestField == UCAL_DAY_OF_WEEK_IN_MONTH);
3044 int32_t year;
3045
3046 if (bestField == UCAL_WEEK_OF_YEAR) {
3047 year = internalGet(UCAL_YEAR_WOY, handleGetExtendedYear());
3048 internalSet(UCAL_EXTENDED_YEAR, year);
3049 } else {
3050 year = handleGetExtendedYear();
3051 internalSet(UCAL_EXTENDED_YEAR, year);
3052 }
374ca955
A
3053
3054#if defined (U_DEBUG_CAL)
46f4442e 3055 fprintf(stderr, "%s:%d: bestField= %s - y=%d\n", __FILE__, __LINE__, fldName(bestField), year);
374ca955
A
3056#endif
3057
46f4442e
A
3058 // Get the Julian day of the day BEFORE the start of this year.
3059 // If useMonth is true, get the day before the start of the month.
374ca955 3060
46f4442e
A
3061 // give calendar subclass a chance to have a default 'first' month
3062 int32_t month;
374ca955 3063
46f4442e
A
3064 if(isSet(UCAL_MONTH)) {
3065 month = internalGet(UCAL_MONTH);
3066 } else {
729e4ab9 3067 month = getDefaultMonthInYear(year);
46f4442e 3068 }
374ca955 3069
46f4442e 3070 int32_t julianDay = handleComputeMonthStart(year, useMonth ? month : 0, useMonth);
374ca955 3071
46f4442e
A
3072 if (bestField == UCAL_DAY_OF_MONTH) {
3073
3074 // give calendar subclass a chance to have a default 'first' dom
3075 int32_t dayOfMonth;
3076 if(isSet(UCAL_DAY_OF_MONTH)) {
3077 dayOfMonth = internalGet(UCAL_DAY_OF_MONTH,1);
3078 } else {
729e4ab9 3079 dayOfMonth = getDefaultDayInMonth(year, month);
46f4442e
A
3080 }
3081 return julianDay + dayOfMonth;
3082 }
3083
3084 if (bestField == UCAL_DAY_OF_YEAR) {
3085 return julianDay + internalGet(UCAL_DAY_OF_YEAR);
3086 }
3087
3088 int32_t firstDayOfWeek = getFirstDayOfWeek(); // Localized fdw
3089
3090 // At this point julianDay is the 0-based day BEFORE the first day of
3091 // January 1, year 1 of the given calendar. If julianDay == 0, it
3092 // specifies (Jan. 1, 1) - 1, in whatever calendar we are using (Julian
3093 // or Gregorian). (or it is before the month we are in, if useMonth is True)
3094
3095 // At this point we need to process the WEEK_OF_MONTH or
3096 // WEEK_OF_YEAR, which are similar, or the DAY_OF_WEEK_IN_MONTH.
3097 // First, perform initial shared computations. These locate the
3098 // first week of the period.
3099
3100 // Get the 0-based localized DOW of day one of the month or year.
3101 // Valid range 0..6.
3102 int32_t first = julianDayToDayOfWeek(julianDay + 1) - firstDayOfWeek;
3103 if (first < 0) {
3104 first += 7;
3105 }
3106
3107 int32_t dowLocal = getLocalDOW();
3108
3109 // Find the first target DOW (dowLocal) in the month or year.
3110 // Actually, it may be just before the first of the month or year.
3111 // It will be an integer from -5..7.
3112 int32_t date = 1 - first + dowLocal;
3113
3114 if (bestField == UCAL_DAY_OF_WEEK_IN_MONTH) {
3115 // Adjust the target DOW to be in the month or year.
3116 if (date < 1) {
3117 date += 7;
3118 }
3119
3120 // The only trickiness occurs if the day-of-week-in-month is
3121 // negative.
3122 int32_t dim = internalGet(UCAL_DAY_OF_WEEK_IN_MONTH, 1);
3123 if (dim >= 0) {
3124 date += 7*(dim - 1);
3125
3126 } else {
3127 // Move date to the last of this day-of-week in this month,
3128 // then back up as needed. If dim==-1, we don't back up at
3129 // all. If dim==-2, we back up once, etc. Don't back up
3130 // past the first of the given day-of-week in this month.
3131 // Note that we handle -2, -3, etc. correctly, even though
3132 // values < -1 are technically disallowed.
3133 int32_t m = internalGet(UCAL_MONTH, UCAL_JANUARY);
3134 int32_t monthLength = handleGetMonthLength(year, m);
3135 date += ((monthLength - date) / 7 + dim + 1) * 7;
3136 }
374ca955 3137 } else {
374ca955 3138#if defined (U_DEBUG_CAL)
46f4442e 3139 fprintf(stderr, "%s:%d - bf= %s\n", __FILE__, __LINE__, fldName(bestField));
374ca955
A
3140#endif
3141
46f4442e
A
3142 if(bestField == UCAL_WEEK_OF_YEAR) { // ------------------------------------- WOY -------------
3143 if(!isSet(UCAL_YEAR_WOY) || // YWOY not set at all or
3144 ( (resolveFields(kYearPrecedence) != UCAL_YEAR_WOY) // YWOY doesn't have precedence
3145 && (fStamp[UCAL_YEAR_WOY]!=kInternallySet) ) ) // (excluding where all fields are internally set - then YWOY is used)
3146 {
3147 // need to be sure to stay in 'real' year.
3148 int32_t woy = internalGet(bestField);
3149
3150 int32_t nextJulianDay = handleComputeMonthStart(year+1, 0, FALSE); // jd of day before jan 1
3151 int32_t nextFirst = julianDayToDayOfWeek(nextJulianDay + 1) - firstDayOfWeek;
3152
3153 if (nextFirst < 0) { // 0..6 ldow of Jan 1
3154 nextFirst += 7;
3155 }
374ca955 3156
46f4442e 3157 if(woy==1) { // FIRST WEEK ---------------------------------
374ca955 3158#if defined (U_DEBUG_CAL)
46f4442e
A
3159 fprintf(stderr, "%s:%d - woy=%d, yp=%d, nj(%d)=%d, nf=%d", __FILE__, __LINE__,
3160 internalGet(bestField), resolveFields(kYearPrecedence), year+1,
3161 nextJulianDay, nextFirst);
3162
3163 fprintf(stderr, " next: %d DFW, min=%d \n", (7-nextFirst), getMinimalDaysInFirstWeek() );
374ca955 3164#endif
46f4442e
A
3165
3166 // nextFirst is now the localized DOW of Jan 1 of y-woy+1
3167 if((nextFirst > 0) && // Jan 1 starts on FDOW
3168 (7-nextFirst) >= getMinimalDaysInFirstWeek()) // or enough days in the week
3169 {
3170 // Jan 1 of (yearWoy+1) is in yearWoy+1 - recalculate JD to next year
374ca955 3171#if defined (U_DEBUG_CAL)
46f4442e
A
3172 fprintf(stderr, "%s:%d - was going to move JD from %d to %d [d%d]\n", __FILE__, __LINE__,
3173 julianDay, nextJulianDay, (nextJulianDay-julianDay));
374ca955 3174#endif
46f4442e
A
3175 julianDay = nextJulianDay;
3176
3177 // recalculate 'first' [0-based local dow of jan 1]
3178 first = julianDayToDayOfWeek(julianDay + 1) - firstDayOfWeek;
3179 if (first < 0) {
3180 first += 7;
3181 }
3182 // recalculate date.
3183 date = 1 - first + dowLocal;
3184 }
3185 } else if(woy>=getLeastMaximum(bestField)) {
3186 // could be in the last week- find out if this JD would overstep
3187 int32_t testDate = date;
3188 if ((7 - first) < getMinimalDaysInFirstWeek()) {
3189 testDate += 7;
3190 }
3191
3192 // Now adjust for the week number.
3193 testDate += 7 * (woy - 1);
3194
374ca955 3195#if defined (U_DEBUG_CAL)
46f4442e
A
3196 fprintf(stderr, "%s:%d - y=%d, y-1=%d doy%d, njd%d (C.F. %d)\n",
3197 __FILE__, __LINE__, year, year-1, testDate, julianDay+testDate, nextJulianDay);
374ca955 3198#endif
46f4442e
A
3199 if(julianDay+testDate > nextJulianDay) { // is it past Dec 31? (nextJulianDay is day BEFORE year+1's Jan 1)
3200 // Fire up the calculating engines.. retry YWOY = (year-1)
3201 julianDay = handleComputeMonthStart(year-1, 0, FALSE); // jd before Jan 1 of previous year
3202 first = julianDayToDayOfWeek(julianDay + 1) - firstDayOfWeek; // 0 based local dow of first week
3203
3204 if(first < 0) { // 0..6
3205 first += 7;
3206 }
3207 date = 1 - first + dowLocal;
374ca955
A
3208
3209#if defined (U_DEBUG_CAL)
46f4442e
A
3210 fprintf(stderr, "%s:%d - date now %d, jd%d, ywoy%d\n",
3211 __FILE__, __LINE__, date, julianDay, year-1);
374ca955
A
3212#endif
3213
46f4442e
A
3214
3215 } /* correction needed */
3216 } /* leastmaximum */
3217 } /* resolvefields(year) != year_woy */
3218 } /* bestfield != week_of_year */
3219
3220 // assert(bestField == WEEK_OF_MONTH || bestField == WEEK_OF_YEAR)
3221 // Adjust for minimal days in first week
3222 if ((7 - first) < getMinimalDaysInFirstWeek()) {
3223 date += 7;
3224 }
3225
3226 // Now adjust for the week number.
3227 date += 7 * (internalGet(bestField) - 1);
3228 }
3229
3230 return julianDay + date;
374ca955
A
3231}
3232
3233int32_t
729e4ab9 3234Calendar::getDefaultMonthInYear(int32_t /*eyear*/)
374ca955
A
3235{
3236 return 0;
3237}
3238
3239int32_t
729e4ab9 3240Calendar::getDefaultDayInMonth(int32_t /*eyear*/, int32_t /*month*/)
374ca955
A
3241{
3242 return 1;
3243}
3244
3245
3246int32_t Calendar::getLocalDOW()
3247{
3248 // Get zero-based localized DOW, valid range 0..6. This is the DOW
46f4442e
A
3249 // we are looking for.
3250 int32_t dowLocal = 0;
3251 switch (resolveFields(kDOWPrecedence)) {
3252 case UCAL_DAY_OF_WEEK:
3253 dowLocal = internalGet(UCAL_DAY_OF_WEEK) - fFirstDayOfWeek;
3254 break;
3255 case UCAL_DOW_LOCAL:
3256 dowLocal = internalGet(UCAL_DOW_LOCAL) - 1;
3257 break;
3258 default:
3259 break;
3260 }
3261 dowLocal = dowLocal % 7;
3262 if (dowLocal < 0) {
3263 dowLocal += 7;
3264 }
3265 return dowLocal;
374ca955
A
3266}
3267
3268int32_t Calendar::handleGetExtendedYearFromWeekFields(int32_t yearWoy, int32_t woy)
3269{
46f4442e
A
3270 // We have UCAL_YEAR_WOY and UCAL_WEEK_OF_YEAR - from those, determine
3271 // what year we fall in, so that other code can set it properly.
3272 // (code borrowed from computeWeekFields and handleComputeJulianDay)
3273 //return yearWoy;
3274
3275 // First, we need a reliable DOW.
3276 UCalendarDateFields bestField = resolveFields(kDatePrecedence); // !! Note: if subclasses have a different table, they should override handleGetExtendedYearFromWeekFields
3277
3278 // Now, a local DOW
3279 int32_t dowLocal = getLocalDOW(); // 0..6
3280 int32_t firstDayOfWeek = getFirstDayOfWeek(); // Localized fdw
3281 int32_t jan1Start = handleComputeMonthStart(yearWoy, 0, FALSE);
3282 int32_t nextJan1Start = handleComputeMonthStart(yearWoy+1, 0, FALSE); // next year's Jan1 start
3283
3284 // At this point julianDay is the 0-based day BEFORE the first day of
3285 // January 1, year 1 of the given calendar. If julianDay == 0, it
3286 // specifies (Jan. 1, 1) - 1, in whatever calendar we are using (Julian
3287 // or Gregorian). (or it is before the month we are in, if useMonth is True)
3288
3289 // At this point we need to process the WEEK_OF_MONTH or
3290 // WEEK_OF_YEAR, which are similar, or the DAY_OF_WEEK_IN_MONTH.
3291 // First, perform initial shared computations. These locate the
3292 // first week of the period.
3293
3294 // Get the 0-based localized DOW of day one of the month or year.
3295 // Valid range 0..6.
3296 int32_t first = julianDayToDayOfWeek(jan1Start + 1) - firstDayOfWeek;
3297 if (first < 0) {
3298 first += 7;
3299 }
3300 int32_t nextFirst = julianDayToDayOfWeek(nextJan1Start + 1) - firstDayOfWeek;
3301 if (nextFirst < 0) {
3302 nextFirst += 7;
3303 }
3304
3305 int32_t minDays = getMinimalDaysInFirstWeek();
3306 UBool jan1InPrevYear = FALSE; // January 1st in the year of WOY is the 1st week? (i.e. first week is < minimal )
3307 //UBool nextJan1InPrevYear = FALSE; // January 1st of Year of WOY + 1 is in the first week?
3308
3309 if((7 - first) < minDays) {
3310 jan1InPrevYear = TRUE;
3311 }
3312
3313 // if((7 - nextFirst) < minDays) {
3314 // nextJan1InPrevYear = TRUE;
3315 // }
3316
3317 switch(bestField) {
3318 case UCAL_WEEK_OF_YEAR:
3319 if(woy == 1) {
3320 if(jan1InPrevYear == TRUE) {
3321 // the first week of January is in the previous year
3322 // therefore WOY1 is always solidly within yearWoy
3323 return yearWoy;
3324 } else {
3325 // First WOY is split between two years
3326 if( dowLocal < first) { // we are prior to Jan 1
3327 return yearWoy-1; // previous year
3328 } else {
3329 return yearWoy; // in this year
3330 }
3331 }
3332 } else if(woy >= getLeastMaximum(bestField)) {
3333 // we _might_ be in the last week..
3334 int32_t jd = // Calculate JD of our target day:
3335 jan1Start + // JD of Jan 1
3336 (7-first) + // days in the first week (Jan 1.. )
3337 (woy-1)*7 + // add the weeks of the year
3338 dowLocal; // the local dow (0..6) of last week
3339 if(jan1InPrevYear==FALSE) {
3340 jd -= 7; // woy already includes Jan 1's week.
3341 }
3342
3343 if( (jd+1) >= nextJan1Start ) {
3344 // we are in week 52 or 53 etc. - actual year is yearWoy+1
3345 return yearWoy+1;
3346 } else {
3347 // still in yearWoy;
3348 return yearWoy;
3349 }
3350 } else {
3351 // we're not possibly in the last week -must be ywoy
3352 return yearWoy;
3353 }
374ca955
A
3354
3355 case UCAL_DATE:
46f4442e
A
3356 if((internalGet(UCAL_MONTH)==0) &&
3357 (woy >= getLeastMaximum(UCAL_WEEK_OF_YEAR))) {
3358 return yearWoy+1; // month 0, late woy = in the next year
3359 } else if(woy==1) {
3360 //if(nextJan1InPrevYear) {
3361 if(internalGet(UCAL_MONTH)==0) {
3362 return yearWoy;
3363 } else {
3364 return yearWoy-1;
3365 }
3366 //}
3367 }
3368
3369 //(internalGet(UCAL_DATE) <= (7-first)) /* && in minDow */ ) {
3370 //within 1st week and in this month..
3371 //return yearWoy+1;
3372 return yearWoy;
46f4442e
A
3373
3374 default: // assume the year is appropriate
3375 return yearWoy;
46f4442e 3376 }
374ca955
A
3377}
3378
3379int32_t Calendar::handleGetMonthLength(int32_t extendedYear, int32_t month) const
3380{
3381 return handleComputeMonthStart(extendedYear, month+1, TRUE) -
3382 handleComputeMonthStart(extendedYear, month, TRUE);
3383}
3384
3385int32_t Calendar::handleGetYearLength(int32_t eyear) const {
3386 return handleComputeMonthStart(eyear+1, 0, FALSE) -
46f4442e 3387 handleComputeMonthStart(eyear, 0, FALSE);
374ca955
A
3388}
3389
b75a7d8f
A
3390int32_t
3391Calendar::getActualMaximum(UCalendarDateFields field, UErrorCode& status) const
3392{
374ca955
A
3393 int32_t result;
3394 switch (field) {
3395 case UCAL_DATE:
3396 {
3397 if(U_FAILURE(status)) return 0;
3398 Calendar *cal = clone();
3399 if(!cal) { status = U_MEMORY_ALLOCATION_ERROR; return 0; }
4388f060 3400 cal->setLenient(TRUE);
374ca955
A
3401 cal->prepareGetActual(field,FALSE,status);
3402 result = handleGetMonthLength(cal->get(UCAL_EXTENDED_YEAR, status), cal->get(UCAL_MONTH, status));
3403 delete cal;
3404 }
3405 break;
b75a7d8f 3406
374ca955
A
3407 case UCAL_DAY_OF_YEAR:
3408 {
3409 if(U_FAILURE(status)) return 0;
3410 Calendar *cal = clone();
3411 if(!cal) { status = U_MEMORY_ALLOCATION_ERROR; return 0; }
4388f060 3412 cal->setLenient(TRUE);
374ca955
A
3413 cal->prepareGetActual(field,FALSE,status);
3414 result = handleGetYearLength(cal->get(UCAL_EXTENDED_YEAR, status));
3415 delete cal;
3416 }
3417 break;
3418
46f4442e
A
3419 case UCAL_DAY_OF_WEEK:
3420 case UCAL_AM_PM:
3421 case UCAL_HOUR:
3422 case UCAL_HOUR_OF_DAY:
3423 case UCAL_MINUTE:
3424 case UCAL_SECOND:
3425 case UCAL_MILLISECOND:
3426 case UCAL_ZONE_OFFSET:
3427 case UCAL_DST_OFFSET:
3428 case UCAL_DOW_LOCAL:
374ca955
A
3429 case UCAL_JULIAN_DAY:
3430 case UCAL_MILLISECONDS_IN_DAY:
3431 // These fields all have fixed minima/maxima
3432 result = getMaximum(field);
3433 break;
3434
3435 default:
3436 // For all other fields, do it the hard way....
3437 result = getActualHelper(field, getLeastMaximum(field), getMaximum(field),status);
3438 break;
3439 }
3440 return result;
3441}
3442
3443
3444/**
3445* Prepare this calendar for computing the actual minimum or maximum.
3446* This method modifies this calendar's fields; it is called on a
3447* temporary calendar.
3448*
3449* <p>Rationale: The semantics of getActualXxx() is to return the
3450* maximum or minimum value that the given field can take, taking into
3451* account other relevant fields. In general these other fields are
3452* larger fields. For example, when computing the actual maximum
3453* DATE, the current value of DATE itself is ignored,
3454* as is the value of any field smaller.
3455*
3456* <p>The time fields all have fixed minima and maxima, so we don't
3457* need to worry about them. This also lets us set the
3458* MILLISECONDS_IN_DAY to zero to erase any effects the time fields
3459* might have when computing date fields.
3460*
3461* <p>DAY_OF_WEEK is adjusted specially for the WEEK_OF_MONTH and
3462* WEEK_OF_YEAR fields to ensure that they are computed correctly.
3463* @internal
3464*/
3465void Calendar::prepareGetActual(UCalendarDateFields field, UBool isMinimum, UErrorCode &status)
46f4442e 3466{
374ca955
A
3467 set(UCAL_MILLISECONDS_IN_DAY, 0);
3468
3469 switch (field) {
3470 case UCAL_YEAR:
374ca955
A
3471 case UCAL_EXTENDED_YEAR:
3472 set(UCAL_DAY_OF_YEAR, getGreatestMinimum(UCAL_DAY_OF_YEAR));
3473 break;
3474
46f4442e
A
3475 case UCAL_YEAR_WOY:
3476 set(UCAL_WEEK_OF_YEAR, getGreatestMinimum(UCAL_WEEK_OF_YEAR));
3477
374ca955
A
3478 case UCAL_MONTH:
3479 set(UCAL_DATE, getGreatestMinimum(UCAL_DATE));
3480 break;
3481
3482 case UCAL_DAY_OF_WEEK_IN_MONTH:
3483 // For dowim, the maximum occurs for the DOW of the first of the
3484 // month.
3485 set(UCAL_DATE, 1);
3486 set(UCAL_DAY_OF_WEEK, get(UCAL_DAY_OF_WEEK, status)); // Make this user set
3487 break;
3488
3489 case UCAL_WEEK_OF_MONTH:
3490 case UCAL_WEEK_OF_YEAR:
3491 // If we're counting weeks, set the day of the week to either the
3492 // first or last localized DOW. We know the last week of a month
3493 // or year will contain the first day of the week, and that the
3494 // first week will contain the last DOW.
3495 {
3496 int32_t dow = fFirstDayOfWeek;
3497 if (isMinimum) {
3498 dow = (dow + 6) % 7; // set to last DOW
3499 if (dow < UCAL_SUNDAY) {
3500 dow += 7;
3501 }
3502 }
3503#if defined (U_DEBUG_CAL)
3504 fprintf(stderr, "prepareGetActualHelper(WOM/WOY) - dow=%d\n", dow);
3505#endif
3506 set(UCAL_DAY_OF_WEEK, dow);
3507 }
3508 break;
3509 default:
46f4442e 3510 break;
b75a7d8f
A
3511 }
3512
374ca955
A
3513 // Do this last to give it the newest time stamp
3514 set(field, getGreatestMinimum(field));
3515}
3516
3517int32_t Calendar::getActualHelper(UCalendarDateFields field, int32_t startValue, int32_t endValue, UErrorCode &status) const
3518{
3519#if defined (U_DEBUG_CAL)
46f4442e 3520 fprintf(stderr, "getActualHelper(%d,%d .. %d, %s)\n", field, startValue, endValue, u_errorName(status));
374ca955
A
3521#endif
3522 if (startValue == endValue) {
3523 // if we know that the maximum value is always the same, just return it
3524 return startValue;
3525 }
3526
3527 int32_t delta = (endValue > startValue) ? 1 : -1;
3528
b75a7d8f
A
3529 // clone the calendar so we don't mess with the real one, and set it to
3530 // accept anything for the field values
374ca955
A
3531 if(U_FAILURE(status)) return startValue;
3532 Calendar *work = clone();
3533 if(!work) { status = U_MEMORY_ALLOCATION_ERROR; return startValue; }
4388f060
A
3534
3535 // need to resolve time here, otherwise, fields set for actual limit
3536 // may cause conflict with fields previously set (but not yet resolved).
3537 work->complete(status);
3538
b75a7d8f 3539 work->setLenient(TRUE);
46f4442e 3540 work->prepareGetActual(field, delta < 0, status);
b75a7d8f 3541
374ca955 3542 // now try each value from the start to the end one by one until
b75a7d8f
A
3543 // we get a value that normalizes to another value. The last value that
3544 // normalizes to itself is the actual maximum for the current date
46f4442e
A
3545 work->set(field, startValue);
3546
3547 // prepareGetActual sets the first day of week in the same week with
3548 // the first day of a month. Unlike WEEK_OF_YEAR, week number for the
3549 // week which contains days from both previous and current month is
3550 // not unique. For example, last several days in the previous month
3551 // is week 5, and the rest of week is week 1.
374ca955 3552 int32_t result = startValue;
729e4ab9
A
3553 if ((work->get(field, status) != startValue
3554 && field != UCAL_WEEK_OF_MONTH && delta > 0 ) || U_FAILURE(status)) {
374ca955 3555#if defined (U_DEBUG_CAL)
46f4442e 3556 fprintf(stderr, "getActualHelper(fld %d) - got %d (not %d) - %s\n", field, work->get(field,status), startValue, u_errorName(status));
374ca955 3557#endif
46f4442e
A
3558 } else {
3559 do {
374ca955 3560 startValue += delta;
46f4442e
A
3561 work->add(field, delta, status);
3562 if (work->get(field, status) != startValue || U_FAILURE(status)) {
3563#if defined (U_DEBUG_CAL)
3564 fprintf(stderr, "getActualHelper(fld %d) - got %d (not %d), BREAK - %s\n", field, work->get(field,status), startValue, u_errorName(status));
374ca955 3565#endif
46f4442e
A
3566 break;
3567 }
3568 result = startValue;
3569 } while (startValue != endValue);
3570 }
b75a7d8f 3571 delete work;
374ca955
A
3572#if defined (U_DEBUG_CAL)
3573 fprintf(stderr, "getActualHelper(%d) = %d\n", field, result);
3574#endif
b75a7d8f
A
3575 return result;
3576}
3577
374ca955
A
3578
3579
3580
b75a7d8f
A
3581// -------------------------------------
3582
3583void
729e4ab9 3584Calendar::setWeekData(const Locale& desiredLocale, const char *type, UErrorCode& status)
b75a7d8f 3585{
b75a7d8f
A
3586
3587 if (U_FAILURE(status)) return;
3588
3589 fFirstDayOfWeek = UCAL_SUNDAY;
3590 fMinimalDaysInFirstWeek = 1;
729e4ab9
A
3591 fWeekendOnset = UCAL_SATURDAY;
3592 fWeekendOnsetMillis = 0;
3593 fWeekendCease = UCAL_SUNDAY;
3594 fWeekendCeaseMillis = 86400000; // 24*60*60*1000
3595
3596 // Since week and weekend data is territory based instead of language based,
3597 // we may need to tweak the locale that we are using to try to get the appropriate
3598 // values, using the following logic:
3599 // 1). If the locale has a language but no territory, use the territory as defined by
3600 // the likely subtags.
3601 // 2). If the locale has a script designation then we ignore it,
3602 // then remove it ( i.e. "en_Latn_US" becomes "en_US" )
3603
3604 char minLocaleID[ULOC_FULLNAME_CAPACITY] = { 0 };
3605 UErrorCode myStatus = U_ZERO_ERROR;
3606
3607 uloc_minimizeSubtags(desiredLocale.getName(),minLocaleID,ULOC_FULLNAME_CAPACITY,&myStatus);
3608 Locale min = Locale::createFromName(minLocaleID);
3609 Locale useLocale;
3610 if ( uprv_strlen(desiredLocale.getCountry()) == 0 ||
4388f060 3611 (uprv_strlen(desiredLocale.getScript()) > 0 && uprv_strlen(min.getScript()) == 0) ) {
729e4ab9
A
3612 char maxLocaleID[ULOC_FULLNAME_CAPACITY] = { 0 };
3613 myStatus = U_ZERO_ERROR;
3614 uloc_addLikelySubtags(desiredLocale.getName(),maxLocaleID,ULOC_FULLNAME_CAPACITY,&myStatus);
3615 Locale max = Locale::createFromName(maxLocaleID);
3616 useLocale = Locale(max.getLanguage(),max.getCountry());
3617 } else {
3618 useLocale = Locale(desiredLocale);
3619 }
3620
4388f060
A
3621 /* The code here is somewhat of a hack, since week data and weekend data aren't really tied to
3622 a specific calendar, they aren't truly locale data. But this is the only place where valid and
3623 actual locale can be set, so we take a shot at it here by loading a representative resource
3624 from the calendar data. The code used to use the dateTimeElements resource to get first day
3625 of week data, but this was moved to supplemental data under ticket 7755. (JCE) */
3626
3627 CalendarData calData(useLocale,type,status);
3628 UResourceBundle *monthNames = calData.getByKey(gMonthNames,status);
3629 if (U_SUCCESS(status)) {
3630 U_LOCALE_BASED(locBased,*this);
3631 locBased.setLocaleIDs(ures_getLocaleByType(monthNames, ULOC_VALID_LOCALE, &status),
3632 ures_getLocaleByType(monthNames, ULOC_ACTUAL_LOCALE, &status));
3633 } else {
3634 status = U_USING_FALLBACK_WARNING;
3635 return;
3636 }
3637
3638
3639 // Read week data values from supplementalData week data
3640 UResourceBundle *rb = ures_openDirect(NULL, "supplementalData", &status);
3641 ures_getByKey(rb, "weekData", rb, &status);
3642 UResourceBundle *weekData = ures_getByKey(rb, useLocale.getCountry(), NULL, &status);
3643 if (status == U_MISSING_RESOURCE_ERROR && rb != NULL) {
3644 status = U_ZERO_ERROR;
3645 weekData = ures_getByKey(rb, "001", NULL, &status);
3646 }
374ca955 3647
729e4ab9 3648 if (U_FAILURE(status)) {
374ca955 3649#if defined (U_DEBUG_CALDATA)
4388f060 3650 fprintf(stderr, " Failure loading weekData from supplemental = %s\n", u_errorName(status));
374ca955 3651#endif
b75a7d8f 3652 status = U_USING_FALLBACK_WARNING;
729e4ab9 3653 } else {
4388f060
A
3654 int32_t arrLen;
3655 const int32_t *weekDataArr = ures_getIntVector(weekData,&arrLen,&status);
3656 if( U_SUCCESS(status) && arrLen == 6
3657 && 1 <= weekDataArr[0] && weekDataArr[0] <= 7
3658 && 1 <= weekDataArr[1] && weekDataArr[1] <= 7
3659 && 1 <= weekDataArr[2] && weekDataArr[2] <= 7
3660 && 1 <= weekDataArr[4] && weekDataArr[4] <= 7) {
3661 fFirstDayOfWeek = (UCalendarDaysOfWeek)weekDataArr[0];
3662 fMinimalDaysInFirstWeek = (uint8_t)weekDataArr[1];
3663 fWeekendOnset = (UCalendarDaysOfWeek)weekDataArr[2];
3664 fWeekendOnsetMillis = weekDataArr[3];
3665 fWeekendCease = (UCalendarDaysOfWeek)weekDataArr[4];
3666 fWeekendCeaseMillis = weekDataArr[5];
729e4ab9 3667 } else {
4388f060 3668 status = U_INVALID_FORMAT_ERROR;
729e4ab9
A
3669 }
3670 }
4388f060
A
3671 ures_close(weekData);
3672 ures_close(rb);
b75a7d8f
A
3673}
3674
3675/**
46f4442e
A
3676* Recompute the time and update the status fields isTimeSet
3677* and areFieldsSet. Callers should check isTimeSet and only
3678* call this method if isTimeSet is false.
3679*/
b75a7d8f
A
3680void
3681Calendar::updateTime(UErrorCode& status)
3682{
3683 computeTime(status);
3684 if(U_FAILURE(status))
3685 return;
46f4442e 3686
b75a7d8f
A
3687 // If we are lenient, we need to recompute the fields to normalize
3688 // the values. Also, if we haven't set all the fields yet (i.e.,
3689 // in a newly-created object), we need to fill in the fields. [LIU]
3690 if (isLenient() || ! fAreAllFieldsSet)
3691 fAreFieldsSet = FALSE;
46f4442e 3692
b75a7d8f 3693 fIsTimeSet = TRUE;
374ca955 3694 fAreFieldsVirtuallySet = FALSE;
b75a7d8f
A
3695}
3696
374ca955
A
3697Locale
3698Calendar::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
3699 U_LOCALE_BASED(locBased, *this);
3700 return locBased.getLocale(type, status);
3701}
b75a7d8f 3702
374ca955
A
3703const char *
3704Calendar::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const {
3705 U_LOCALE_BASED(locBased, *this);
3706 return locBased.getLocaleID(type, status);
b75a7d8f
A
3707}
3708
4388f060
A
3709void
3710Calendar::recalculateStamp() {
3711 int32_t index;
3712 int32_t currentValue;
3713 int32_t j, i;
3714
3715 fNextStamp = 1;
3716
3717 for (j = 0; j < UCAL_FIELD_COUNT; j++) {
3718 currentValue = STAMP_MAX;
3719 index = -1;
3720 for (i = 0; i < UCAL_FIELD_COUNT; i++) {
3721 if (fStamp[i] > fNextStamp && fStamp[i] < currentValue) {
3722 currentValue = fStamp[i];
3723 index = i;
3724 }
3725 }
3726
3727 if (index >= 0) {
3728 fStamp[index] = ++fNextStamp;
3729 } else {
3730 break;
3731 }
3732 }
3733 fNextStamp++;
3734}
3735
46f4442e
A
3736// Deprecated function. This doesn't need to be inline.
3737void
3738Calendar::internalSet(EDateFields field, int32_t value)
3739{
3740 internalSet((UCalendarDateFields) field, value);
3741}
3742
4388f060
A
3743BasicTimeZone*
3744Calendar::getBasicTimeZone(void) const {
3745 if (dynamic_cast<const OlsonTimeZone *>(fZone) != NULL
3746 || dynamic_cast<const SimpleTimeZone *>(fZone) != NULL
3747 || dynamic_cast<const RuleBasedTimeZone *>(fZone) != NULL
3748 || dynamic_cast<const VTimeZone *>(fZone) != NULL) {
3749 return (BasicTimeZone*)fZone;
3750 }
3751 return NULL;
3752}
3753
374ca955 3754U_NAMESPACE_END
b75a7d8f
A
3755
3756#endif /* #if !UCONFIG_NO_FORMATTING */
3757
374ca955 3758
b75a7d8f 3759//eof