1/* 2****************************************************************************** 3* Copyright (C) 2003-2010, International Business Machines Corporation 4* and others. All Rights Reserved. 5****************************************************************************** 6* 7* File ISLAMCAL.H 8* 9* Modification History: 10* 11* Date Name Description 12* 10/14/2003 srl ported from java IslamicCalendar 13***************************************************************************** 14*/ 15 16#include "islamcal.h" 17 18#if !UCONFIG_NO_FORMATTING 19 20#include "umutex.h" 21#include <float.h> 22#include "gregoimp.h" // Math 23#include "astro.h" // CalendarAstronomer 24#include "uhash.h" 25#include "ucln_in.h" 26 27static const UDate HIJRA_MILLIS = -42521587200000.0; // 7/16/622 AD 00:00 28 29// Debugging 30#ifdef U_DEBUG_ISLAMCAL 31# include <stdio.h> 32# include <stdarg.h> 33static void debug_islamcal_loc(const char *f, int32_t l) 34{ 35 fprintf(stderr, "%s:%d: ", f, l); 36} 37 38static void debug_islamcal_msg(const char *pat, ...) 39{ 40 va_list ap; 41 va_start(ap, pat); 42 vfprintf(stderr, pat, ap); 43 fflush(stderr); 44} 45// must use double parens, i.e.: U_DEBUG_ISLAMCAL_MSG(("four is: %d",4)); 46#define U_DEBUG_ISLAMCAL_MSG(x) {debug_islamcal_loc(__FILE__,__LINE__);debug_islamcal_msg x;} 47#else 48#define U_DEBUG_ISLAMCAL_MSG(x) 49#endif 50 51 52// --- The cache -- 53// cache of months 54static UMTX astroLock = 0; // pod bay door lock 55static U_NAMESPACE_QUALIFIER CalendarCache *gMonthCache = NULL; 56static U_NAMESPACE_QUALIFIER CalendarAstronomer *gIslamicCalendarAstro = NULL; 57 58U_CDECL_BEGIN 59static UBool calendar_islamic_cleanup(void) { 60 if (gMonthCache) { 61 delete gMonthCache; 62 gMonthCache = NULL; 63 } 64 if (gIslamicCalendarAstro) { 65 delete gIslamicCalendarAstro; 66 gIslamicCalendarAstro = NULL; 67 } 68 umtx_destroy(&astroLock); 69 return TRUE; 70} 71U_CDECL_END 72 73U_NAMESPACE_BEGIN 74 75// Implementation of the IslamicCalendar class 76 77//------------------------------------------------------------------------- 78// Constructors... 79//------------------------------------------------------------------------- 80 81const char *IslamicCalendar::getType() const { 82 if(civil==CIVIL) { 83 return "islamic-civil"; 84 } else { 85 return "islamic"; 86 } 87} 88 89Calendar* IslamicCalendar::clone() const { 90 return new IslamicCalendar(*this); 91} 92 93IslamicCalendar::IslamicCalendar(const Locale& aLocale, UErrorCode& success, ECivil beCivil) 94: Calendar(TimeZone::createDefault(), aLocale, success), 95civil(beCivil) 96{ 97 setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. 98} 99 100IslamicCalendar::IslamicCalendar(const IslamicCalendar& other) : Calendar(other), civil(other.civil) { 101} 102 103IslamicCalendar::~IslamicCalendar() 104{ 105} 106 107/** 108* Determines whether this object uses the fixed-cycle Islamic civil calendar 109* or an approximation of the religious, astronomical calendar. 110* 111* @param beCivil <code>true</code> to use the civil calendar, 112* <code>false</code> to use the astronomical calendar. 113* @draft ICU 2.4 114*/ 115void IslamicCalendar::setCivil(ECivil beCivil, UErrorCode &status) 116{ 117 if (civil != beCivil) { 118 // The fields of the calendar will become invalid, because the calendar 119 // rules are different 120 UDate m = getTimeInMillis(status); 121 civil = beCivil; 122 clear(); 123 setTimeInMillis(m, status); 124 } 125} 126 127/** 128* Returns <code>true</code> if this object is using the fixed-cycle civil 129* calendar, or <code>false</code> if using the religious, astronomical 130* calendar. 131* @draft ICU 2.4 132*/ 133UBool IslamicCalendar::isCivil() { 134 return (civil == CIVIL); 135} 136 137//------------------------------------------------------------------------- 138// Minimum / Maximum access functions 139//------------------------------------------------------------------------- 140 141// Note: Current IslamicCalendar implementation does not work 142// well with negative years. 143 144static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { 145 // Minimum Greatest Least Maximum 146 // Minimum Maximum 147 { 0, 0, 0, 0}, // ERA 148 { 1, 1, 5000000, 5000000}, // YEAR 149 { 0, 0, 11, 11}, // MONTH 150 { 1, 1, 50, 51}, // WEEK_OF_YEAR 151 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH 152 { 1, 1, 29, 30}, // DAY_OF_MONTH 153 { 1, 1, 354, 355}, // DAY_OF_YEAR 154 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK 155 { -1, -1, 5, 5}, // DAY_OF_WEEK_IN_MONTH 156 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM 157 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR 158 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY 159 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE 160 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND 161 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND 162 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET 163 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET 164 { 1, 1, 5000000, 5000000}, // YEAR_WOY 165 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL 166 { 1, 1, 5000000, 5000000}, // EXTENDED_YEAR 167 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY 168 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY 169 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH 170}; 171 172/** 173* @draft ICU 2.4 174*/ 175int32_t IslamicCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const { 176 return LIMITS[field][limitType]; 177} 178 179//------------------------------------------------------------------------- 180// Assorted calculation utilities 181// 182 183/** 184* Determine whether a year is a leap year in the Islamic civil calendar 185*/ 186UBool IslamicCalendar::civilLeapYear(int32_t year) 187{ 188 return (14 + 11 * year) % 30 < 11; 189} 190 191/** 192* Return the day # on which the given year starts. Days are counted 193* from the Hijri epoch, origin 0. 194*/ 195int32_t IslamicCalendar::yearStart(int32_t year) { 196 if (civil == CIVIL) { 197 return (year-1)*354 + ClockMath::floorDivide((3+11*year),30); 198 } else { 199 return trueMonthStart(12*(year-1)); 200 } 201} 202 203/** 204* Return the day # on which the given month starts. Days are counted 205* from the Hijri epoch, origin 0. 206* 207* @param year The hijri year 208* @param year The hijri month, 0-based 209*/ 210int32_t IslamicCalendar::monthStart(int32_t year, int32_t month) const { 211 if (civil == CIVIL) { 212 return (int32_t)uprv_ceil(29.5*month) 213 + (year-1)*354 + (int32_t)ClockMath::floorDivide((3+11*year),30); 214 } else { 215 return trueMonthStart(12*(year-1) + month); 216 } 217} 218 219/** 220* Find the day number on which a particular month of the true/lunar 221* Islamic calendar starts. 222* 223* @param month The month in question, origin 0 from the Hijri epoch 224* 225* @return The day number on which the given month starts. 226*/ 227int32_t IslamicCalendar::trueMonthStart(int32_t month) const 228{ 229 UErrorCode status = U_ZERO_ERROR; 230 int32_t start = CalendarCache::get(&gMonthCache, month, status); 231 232 if (start==0) { 233 // Make a guess at when the month started, using the average length 234 UDate origin = HIJRA_MILLIS 235 + uprv_floor(month * CalendarAstronomer::SYNODIC_MONTH) * kOneDay; 236 237 // moonAge will fail due to memory allocation error 238 double age = moonAge(origin, status); 239 if (U_FAILURE(status)) { 240 goto trueMonthStartEnd; 241 } 242 243 if (age >= 0) { 244 // The month has already started 245 do { 246 origin -= kOneDay; 247 age = moonAge(origin, status); 248 if (U_FAILURE(status)) { 249 goto trueMonthStartEnd; 250 } 251 } while (age >= 0); 252 } 253 else { 254 // Preceding month has not ended yet. 255 do { 256 origin += kOneDay; 257 age = moonAge(origin, status); 258 if (U_FAILURE(status)) { 259 goto trueMonthStartEnd; 260 } 261 } while (age < 0); 262 } 263 start = (int32_t)ClockMath::floorDivide((origin - HIJRA_MILLIS), (double)kOneDay) + 1; 264 CalendarCache::put(&gMonthCache, month, start, status); 265 } 266trueMonthStartEnd : 267 if(U_FAILURE(status)) { 268 start = 0; 269 } 270 return start; 271} 272 273/** 274* Return the "age" of the moon at the given time; this is the difference 275* in ecliptic latitude between the moon and the sun. This method simply 276* calls CalendarAstronomer.moonAge, converts to degrees, 277* and adjusts the result to be in the range [-180, 180]. 278* 279* @param time The time at which the moon's age is desired, 280* in millis since 1/1/1970. 281*/ 282double IslamicCalendar::moonAge(UDate time, UErrorCode &status) 283{ 284 double age = 0; 285 286 umtx_lock(&astroLock); 287 if(gIslamicCalendarAstro == NULL) { 288 gIslamicCalendarAstro = new CalendarAstronomer(); 289 if (gIslamicCalendarAstro == NULL) { 290 status = U_MEMORY_ALLOCATION_ERROR; 291 return age; 292 } 293 ucln_i18n_registerCleanup(UCLN_I18N_ISLAMIC_CALENDAR, calendar_islamic_cleanup); 294 } 295 gIslamicCalendarAstro->setTime(time); 296 age = gIslamicCalendarAstro->getMoonAge(); 297 umtx_unlock(&astroLock); 298 299 // Convert to degrees and normalize... 300 age = age * 180 / CalendarAstronomer::PI; 301 if (age > 180) { 302 age = age - 360; 303 } 304 305 return age; 306} 307 308//---------------------------------------------------------------------- 309// Calendar framework 310//---------------------------------------------------------------------- 311 312/** 313* Return the length (in days) of the given month. 314* 315* @param year The hijri year 316* @param year The hijri month, 0-based 317* @draft ICU 2.4 318*/ 319int32_t IslamicCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month) const { 320 321 int32_t length = 0; 322 323 if (civil == CIVIL) { 324 length = 29 + (month+1) % 2; 325 if (month == DHU_AL_HIJJAH && civilLeapYear(extendedYear)) { 326 length++; 327 } 328 } else { 329 month = 12*(extendedYear-1) + month; 330 length = trueMonthStart(month+1) - trueMonthStart(month) ; 331 } 332 return length; 333} 334 335/** 336* Return the number of days in the given Islamic year 337* @draft ICU 2.4 338*/ 339int32_t IslamicCalendar::handleGetYearLength(int32_t extendedYear) const { 340 if (civil == CIVIL) { 341 return 354 + (civilLeapYear(extendedYear) ? 1 : 0); 342 } else { 343 int32_t month = 12*(extendedYear-1); 344 return (trueMonthStart(month + 12) - trueMonthStart(month)); 345 } 346} 347 348//------------------------------------------------------------------------- 349// Functions for converting from field values to milliseconds.... 350//------------------------------------------------------------------------- 351 352// Return JD of start of given month/year 353/** 354* @draft ICU 2.4 355*/ 356int32_t IslamicCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool /* useMonth */) const { 357 return monthStart(eyear, month) + 1948439; 358} 359 360//------------------------------------------------------------------------- 361// Functions for converting from milliseconds to field values 362//------------------------------------------------------------------------- 363 364/** 365* @draft ICU 2.4 366*/ 367int32_t IslamicCalendar::handleGetExtendedYear() { 368 int32_t year; 369 if (newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR) == UCAL_EXTENDED_YEAR) { 370 year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1 371 } else { 372 year = internalGet(UCAL_YEAR, 1); // Default to year 1 373 } 374 return year; 375} 376 377/** 378* Override Calendar to compute several fields specific to the Islamic 379* calendar system. These are: 380* 381* <ul><li>ERA 382* <li>YEAR 383* <li>MONTH 384* <li>DAY_OF_MONTH 385* <li>DAY_OF_YEAR 386* <li>EXTENDED_YEAR</ul> 387* 388* The DAY_OF_WEEK and DOW_LOCAL fields are already set when this 389* method is called. The getGregorianXxx() methods return Gregorian 390* calendar equivalents for the given Julian day. 391* @draft ICU 2.4 392*/ 393void IslamicCalendar::handleComputeFields(int32_t julianDay, UErrorCode &status) { 394 int32_t year, month, dayOfMonth, dayOfYear; 395 UDate startDate; 396 int32_t days = julianDay - 1948440; 397 398 if (civil == CIVIL) { 399 // Use the civil calendar approximation, which is just arithmetic 400 year = (int)ClockMath::floorDivide( (double)(30 * days + 10646) , 10631.0 ); 401 month = (int32_t)uprv_ceil((days - 29 - yearStart(year)) / 29.5 ); 402 month = month<11?month:11; 403 startDate = monthStart(year, month); 404 } else { 405 // Guess at the number of elapsed full months since the epoch 406 int32_t months = (int32_t)uprv_floor((double)days / CalendarAstronomer::SYNODIC_MONTH); 407 408 startDate = uprv_floor(months * CalendarAstronomer::SYNODIC_MONTH); 409 410 double age = moonAge(internalGetTime(), status); 411 if (U_FAILURE(status)) { 412 status = U_MEMORY_ALLOCATION_ERROR; 413 return; 414 } 415 if ( days - startDate >= 25 && age > 0) { 416 // If we're near the end of the month, assume next month and search backwards 417 months++; 418 } 419 420 // Find out the last time that the new moon was actually visible at this longitude 421 // This returns midnight the night that the moon was visible at sunset. 422 while ((startDate = trueMonthStart(months)) > days) { 423 // If it was after the date in question, back up a month and try again 424 months--; 425 } 426 427 year = months / 12 + 1; 428 month = months % 12; 429 } 430 431 dayOfMonth = (days - monthStart(year, month)) + 1; 432 433 // Now figure out the day of the year. 434 dayOfYear = (days - monthStart(year, 0) + 1); 435 436 internalSet(UCAL_ERA, 0); 437 internalSet(UCAL_YEAR, year); 438 internalSet(UCAL_EXTENDED_YEAR, year); 439 internalSet(UCAL_MONTH, month); 440 internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); 441 internalSet(UCAL_DAY_OF_YEAR, dayOfYear); 442} 443 444UBool 445IslamicCalendar::inDaylightTime(UErrorCode& status) const 446{ 447 // copied from GregorianCalendar 448 if (U_FAILURE(status) || (&(getTimeZone()) == NULL && !getTimeZone().useDaylightTime())) 449 return FALSE; 450 451 // Force an update of the state of the Calendar. 452 ((IslamicCalendar*)this)->complete(status); // cast away const 453 454 return (UBool)(U_SUCCESS(status) ? (internalGet(UCAL_DST_OFFSET) != 0) : FALSE); 455} 456 457// default century 458const UDate IslamicCalendar::fgSystemDefaultCentury = DBL_MIN; 459const int32_t IslamicCalendar::fgSystemDefaultCenturyYear = -1; 460 461UDate IslamicCalendar::fgSystemDefaultCenturyStart = DBL_MIN; 462int32_t IslamicCalendar::fgSystemDefaultCenturyStartYear = -1; 463 464 465UBool IslamicCalendar::haveDefaultCentury() const 466{ 467 return TRUE; 468} 469 470UDate IslamicCalendar::defaultCenturyStart() const 471{ 472 return internalGetDefaultCenturyStart(); 473} 474 475int32_t IslamicCalendar::defaultCenturyStartYear() const 476{ 477 return internalGetDefaultCenturyStartYear(); 478} 479 480UDate 481IslamicCalendar::internalGetDefaultCenturyStart() const 482{ 483 // lazy-evaluate systemDefaultCenturyStart 484 UBool needsUpdate; 485 UMTX_CHECK(NULL, (fgSystemDefaultCenturyStart == fgSystemDefaultCentury), needsUpdate); 486 487 if (needsUpdate) { 488 initializeSystemDefaultCentury(); 489 } 490 491 // use defaultCenturyStart unless it's the flag value; 492 // then use systemDefaultCenturyStart 493 494 return fgSystemDefaultCenturyStart; 495} 496 497int32_t 498IslamicCalendar::internalGetDefaultCenturyStartYear() const 499{ 500 // lazy-evaluate systemDefaultCenturyStartYear 501 UBool needsUpdate; 502 UMTX_CHECK(NULL, (fgSystemDefaultCenturyStart == fgSystemDefaultCentury), needsUpdate); 503 504 if (needsUpdate) { 505 initializeSystemDefaultCentury(); 506 } 507 508 // use defaultCenturyStart unless it's the flag value; 509 // then use systemDefaultCenturyStartYear 510 511 return fgSystemDefaultCenturyStartYear; 512} 513 514void 515IslamicCalendar::initializeSystemDefaultCentury() 516{ 517 // initialize systemDefaultCentury and systemDefaultCenturyYear based 518 // on the current time. They'll be set to 80 years before 519 // the current time. 520 UErrorCode status = U_ZERO_ERROR; 521 IslamicCalendar calendar(Locale("@calendar=islamic-civil"),status); 522 if (U_SUCCESS(status)) 523 { 524 calendar.setTime(Calendar::getNow(), status); 525 calendar.add(UCAL_YEAR, -80, status); 526 UDate newStart = calendar.getTime(status); 527 int32_t newYear = calendar.get(UCAL_YEAR, status); 528 umtx_lock(NULL); 529 if (fgSystemDefaultCenturyStart == fgSystemDefaultCentury) 530 { 531 fgSystemDefaultCenturyStartYear = newYear; 532 fgSystemDefaultCenturyStart = newStart; 533 } 534 umtx_unlock(NULL); 535 } 536 // We have no recourse upon failure unless we want to propagate the failure 537 // out. 538} 539 540UOBJECT_DEFINE_RTTI_IMPLEMENTATION(IslamicCalendar) 541 542U_NAMESPACE_END 543 544#endif 545 546