1/* 2****************************************************************************** 3* Copyright (C) 2003-2011, 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 144// TODO: In some cases the current ICU Islamic calendar implementation shows 145// a month as having 31 days. Since date parsing now uses range checks based 146// on the table below, we need to change the range for last day of month to 147// include 31 as a workaround until the implementation is fixed. 148static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { 149 // Minimum Greatest Least Maximum 150 // Minimum Maximum 151 { 0, 0, 0, 0}, // ERA 152 { 1, 1, 5000000, 5000000}, // YEAR 153 { 0, 0, 11, 11}, // MONTH 154 { 1, 1, 50, 51}, // WEEK_OF_YEAR 155 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // WEEK_OF_MONTH 156 { 1, 1, 29, 31}, // DAY_OF_MONTH - 31 to workaround for cal implementation bug, should be 30 157 { 1, 1, 354, 355}, // DAY_OF_YEAR 158 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK 159 { -1, -1, 5, 5}, // DAY_OF_WEEK_IN_MONTH 160 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM 161 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR 162 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY 163 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE 164 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND 165 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND 166 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET 167 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET 168 { 1, 1, 5000000, 5000000}, // YEAR_WOY 169 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL 170 { 1, 1, 5000000, 5000000}, // EXTENDED_YEAR 171 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY 172 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECONDS_IN_DAY 173 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // IS_LEAP_MONTH 174}; 175 176/** 177* @draft ICU 2.4 178*/ 179int32_t IslamicCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const { 180 return LIMITS[field][limitType]; 181} 182 183//------------------------------------------------------------------------- 184// Assorted calculation utilities 185// 186 187/** 188* Determine whether a year is a leap year in the Islamic civil calendar 189*/ 190UBool IslamicCalendar::civilLeapYear(int32_t year) 191{ 192 return (14 + 11 * year) % 30 < 11; 193} 194 195/** 196* Return the day # on which the given year starts. Days are counted 197* from the Hijri epoch, origin 0. 198*/ 199int32_t IslamicCalendar::yearStart(int32_t year) { 200 if (civil == CIVIL) { 201 return (year-1)*354 + ClockMath::floorDivide((3+11*year),30); 202 } else { 203 return trueMonthStart(12*(year-1)); 204 } 205} 206 207/** 208* Return the day # on which the given month starts. Days are counted 209* from the Hijri epoch, origin 0. 210* 211* @param year The hijri year 212* @param year The hijri month, 0-based 213*/ 214int32_t IslamicCalendar::monthStart(int32_t year, int32_t month) const { 215 if (civil == CIVIL) { 216 return (int32_t)uprv_ceil(29.5*month) 217 + (year-1)*354 + (int32_t)ClockMath::floorDivide((3+11*year),30); 218 } else { 219 return trueMonthStart(12*(year-1) + month); 220 } 221} 222 223/** 224* Find the day number on which a particular month of the true/lunar 225* Islamic calendar starts. 226* 227* @param month The month in question, origin 0 from the Hijri epoch 228* 229* @return The day number on which the given month starts. 230*/ 231int32_t IslamicCalendar::trueMonthStart(int32_t month) const 232{ 233 UErrorCode status = U_ZERO_ERROR; 234 int32_t start = CalendarCache::get(&gMonthCache, month, status); 235 236 if (start==0) { 237 // Make a guess at when the month started, using the average length 238 UDate origin = HIJRA_MILLIS 239 + uprv_floor(month * CalendarAstronomer::SYNODIC_MONTH) * kOneDay; 240 241 // moonAge will fail due to memory allocation error 242 double age = moonAge(origin, status); 243 if (U_FAILURE(status)) { 244 goto trueMonthStartEnd; 245 } 246 247 if (age >= 0) { 248 // The month has already started 249 do { 250 origin -= kOneDay; 251 age = moonAge(origin, status); 252 if (U_FAILURE(status)) { 253 goto trueMonthStartEnd; 254 } 255 } while (age >= 0); 256 } 257 else { 258 // Preceding month has not ended yet. 259 do { 260 origin += kOneDay; 261 age = moonAge(origin, status); 262 if (U_FAILURE(status)) { 263 goto trueMonthStartEnd; 264 } 265 } while (age < 0); 266 } 267 start = (int32_t)ClockMath::floorDivide((origin - HIJRA_MILLIS), (double)kOneDay) + 1; 268 CalendarCache::put(&gMonthCache, month, start, status); 269 } 270trueMonthStartEnd : 271 if(U_FAILURE(status)) { 272 start = 0; 273 } 274 return start; 275} 276 277/** 278* Return the "age" of the moon at the given time; this is the difference 279* in ecliptic latitude between the moon and the sun. This method simply 280* calls CalendarAstronomer.moonAge, converts to degrees, 281* and adjusts the result to be in the range [-180, 180]. 282* 283* @param time The time at which the moon's age is desired, 284* in millis since 1/1/1970. 285*/ 286double IslamicCalendar::moonAge(UDate time, UErrorCode &status) 287{ 288 double age = 0; 289 290 umtx_lock(&astroLock); 291 if(gIslamicCalendarAstro == NULL) { 292 gIslamicCalendarAstro = new CalendarAstronomer(); 293 if (gIslamicCalendarAstro == NULL) { 294 status = U_MEMORY_ALLOCATION_ERROR; 295 return age; 296 } 297 ucln_i18n_registerCleanup(UCLN_I18N_ISLAMIC_CALENDAR, calendar_islamic_cleanup); 298 } 299 gIslamicCalendarAstro->setTime(time); 300 age = gIslamicCalendarAstro->getMoonAge(); 301 umtx_unlock(&astroLock); 302 303 // Convert to degrees and normalize... 304 age = age * 180 / CalendarAstronomer::PI; 305 if (age > 180) { 306 age = age - 360; 307 } 308 309 return age; 310} 311 312//---------------------------------------------------------------------- 313// Calendar framework 314//---------------------------------------------------------------------- 315 316/** 317* Return the length (in days) of the given month. 318* 319* @param year The hijri year 320* @param year The hijri month, 0-based 321* @draft ICU 2.4 322*/ 323int32_t IslamicCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month) const { 324 325 int32_t length = 0; 326 327 if (civil == CIVIL) { 328 length = 29 + (month+1) % 2; 329 if (month == DHU_AL_HIJJAH && civilLeapYear(extendedYear)) { 330 length++; 331 } 332 } else { 333 month = 12*(extendedYear-1) + month; 334 length = trueMonthStart(month+1) - trueMonthStart(month) ; 335 } 336 return length; 337} 338 339/** 340* Return the number of days in the given Islamic year 341* @draft ICU 2.4 342*/ 343int32_t IslamicCalendar::handleGetYearLength(int32_t extendedYear) const { 344 if (civil == CIVIL) { 345 return 354 + (civilLeapYear(extendedYear) ? 1 : 0); 346 } else { 347 int32_t month = 12*(extendedYear-1); 348 return (trueMonthStart(month + 12) - trueMonthStart(month)); 349 } 350} 351 352//------------------------------------------------------------------------- 353// Functions for converting from field values to milliseconds.... 354//------------------------------------------------------------------------- 355 356// Return JD of start of given month/year 357/** 358* @draft ICU 2.4 359*/ 360int32_t IslamicCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool /* useMonth */) const { 361 return monthStart(eyear, month) + 1948439; 362} 363 364//------------------------------------------------------------------------- 365// Functions for converting from milliseconds to field values 366//------------------------------------------------------------------------- 367 368/** 369* @draft ICU 2.4 370*/ 371int32_t IslamicCalendar::handleGetExtendedYear() { 372 int32_t year; 373 if (newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR) == UCAL_EXTENDED_YEAR) { 374 year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1 375 } else { 376 year = internalGet(UCAL_YEAR, 1); // Default to year 1 377 } 378 return year; 379} 380 381/** 382* Override Calendar to compute several fields specific to the Islamic 383* calendar system. These are: 384* 385* <ul><li>ERA 386* <li>YEAR 387* <li>MONTH 388* <li>DAY_OF_MONTH 389* <li>DAY_OF_YEAR 390* <li>EXTENDED_YEAR</ul> 391* 392* The DAY_OF_WEEK and DOW_LOCAL fields are already set when this 393* method is called. The getGregorianXxx() methods return Gregorian 394* calendar equivalents for the given Julian day. 395* @draft ICU 2.4 396*/ 397void IslamicCalendar::handleComputeFields(int32_t julianDay, UErrorCode &status) { 398 int32_t year, month, dayOfMonth, dayOfYear; 399 UDate startDate; 400 int32_t days = julianDay - 1948440; 401 402 if (civil == CIVIL) { 403 // Use the civil calendar approximation, which is just arithmetic 404 year = (int)ClockMath::floorDivide( (double)(30 * days + 10646) , 10631.0 ); 405 month = (int32_t)uprv_ceil((days - 29 - yearStart(year)) / 29.5 ); 406 month = month<11?month:11; 407 startDate = monthStart(year, month); 408 } else { 409 // Guess at the number of elapsed full months since the epoch 410 int32_t months = (int32_t)uprv_floor((double)days / CalendarAstronomer::SYNODIC_MONTH); 411 412 startDate = uprv_floor(months * CalendarAstronomer::SYNODIC_MONTH); 413 414 double age = moonAge(internalGetTime(), status); 415 if (U_FAILURE(status)) { 416 status = U_MEMORY_ALLOCATION_ERROR; 417 return; 418 } 419 if ( days - startDate >= 25 && age > 0) { 420 // If we're near the end of the month, assume next month and search backwards 421 months++; 422 } 423 424 // Find out the last time that the new moon was actually visible at this longitude 425 // This returns midnight the night that the moon was visible at sunset. 426 while ((startDate = trueMonthStart(months)) > days) { 427 // If it was after the date in question, back up a month and try again 428 months--; 429 } 430 431 year = months / 12 + 1; 432 month = months % 12; 433 } 434 435 dayOfMonth = (days - monthStart(year, month)) + 1; 436 437 // Now figure out the day of the year. 438 dayOfYear = (days - monthStart(year, 0) + 1); 439 440 internalSet(UCAL_ERA, 0); 441 internalSet(UCAL_YEAR, year); 442 internalSet(UCAL_EXTENDED_YEAR, year); 443 internalSet(UCAL_MONTH, month); 444 internalSet(UCAL_DAY_OF_MONTH, dayOfMonth); 445 internalSet(UCAL_DAY_OF_YEAR, dayOfYear); 446} 447 448UBool 449IslamicCalendar::inDaylightTime(UErrorCode& status) const 450{ 451 // copied from GregorianCalendar 452 if (U_FAILURE(status) || (&(getTimeZone()) == NULL && !getTimeZone().useDaylightTime())) 453 return FALSE; 454 455 // Force an update of the state of the Calendar. 456 ((IslamicCalendar*)this)->complete(status); // cast away const 457 458 return (UBool)(U_SUCCESS(status) ? (internalGet(UCAL_DST_OFFSET) != 0) : FALSE); 459} 460 461// default century 462const UDate IslamicCalendar::fgSystemDefaultCentury = DBL_MIN; 463const int32_t IslamicCalendar::fgSystemDefaultCenturyYear = -1; 464 465UDate IslamicCalendar::fgSystemDefaultCenturyStart = DBL_MIN; 466int32_t IslamicCalendar::fgSystemDefaultCenturyStartYear = -1; 467 468 469UBool IslamicCalendar::haveDefaultCentury() const 470{ 471 return TRUE; 472} 473 474UDate IslamicCalendar::defaultCenturyStart() const 475{ 476 return internalGetDefaultCenturyStart(); 477} 478 479int32_t IslamicCalendar::defaultCenturyStartYear() const 480{ 481 return internalGetDefaultCenturyStartYear(); 482} 483 484UDate 485IslamicCalendar::internalGetDefaultCenturyStart() const 486{ 487 // lazy-evaluate systemDefaultCenturyStart 488 UBool needsUpdate; 489 UMTX_CHECK(NULL, (fgSystemDefaultCenturyStart == fgSystemDefaultCentury), needsUpdate); 490 491 if (needsUpdate) { 492 initializeSystemDefaultCentury(); 493 } 494 495 // use defaultCenturyStart unless it's the flag value; 496 // then use systemDefaultCenturyStart 497 498 return fgSystemDefaultCenturyStart; 499} 500 501int32_t 502IslamicCalendar::internalGetDefaultCenturyStartYear() const 503{ 504 // lazy-evaluate systemDefaultCenturyStartYear 505 UBool needsUpdate; 506 UMTX_CHECK(NULL, (fgSystemDefaultCenturyStart == fgSystemDefaultCentury), needsUpdate); 507 508 if (needsUpdate) { 509 initializeSystemDefaultCentury(); 510 } 511 512 // use defaultCenturyStart unless it's the flag value; 513 // then use systemDefaultCenturyStartYear 514 515 return fgSystemDefaultCenturyStartYear; 516} 517 518void 519IslamicCalendar::initializeSystemDefaultCentury() 520{ 521 // initialize systemDefaultCentury and systemDefaultCenturyYear based 522 // on the current time. They'll be set to 80 years before 523 // the current time. 524 UErrorCode status = U_ZERO_ERROR; 525 IslamicCalendar calendar(Locale("@calendar=islamic-civil"),status); 526 if (U_SUCCESS(status)) 527 { 528 calendar.setTime(Calendar::getNow(), status); 529 calendar.add(UCAL_YEAR, -80, status); 530 UDate newStart = calendar.getTime(status); 531 int32_t newYear = calendar.get(UCAL_YEAR, status); 532 umtx_lock(NULL); 533 if (fgSystemDefaultCenturyStart == fgSystemDefaultCentury) 534 { 535 fgSystemDefaultCenturyStartYear = newYear; 536 fgSystemDefaultCenturyStart = newStart; 537 } 538 umtx_unlock(NULL); 539 } 540 // We have no recourse upon failure unless we want to propagate the failure 541 // out. 542} 543 544UOBJECT_DEFINE_RTTI_IMPLEMENTATION(IslamicCalendar) 545 546U_NAMESPACE_END 547 548#endif 549 550