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 27 static 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> 33 static void debug_islamcal_loc(const char *f, int32_t l) 34 { 35 fprintf(stderr, "%s:%d: ", f, l); 36 } 37 38 static 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 54 static UMTX astroLock = 0; // pod bay door lock 55 static U_NAMESPACE_QUALIFIER CalendarCache *gMonthCache = NULL; 56 static U_NAMESPACE_QUALIFIER CalendarAstronomer *gIslamicCalendarAstro = NULL; 57 58 U_CDECL_BEGIN 59 static 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 } 71 U_CDECL_END 72 73 U_NAMESPACE_BEGIN 74 75 // Implementation of the IslamicCalendar class 76 77 //------------------------------------------------------------------------- 78 // Constructors... 79 //------------------------------------------------------------------------- 80 81 const char *IslamicCalendar::getType() const { 82 if(civil==CIVIL) { 83 return "islamic-civil"; 84 } else { 85 return "islamic"; 86 } 87 } 88 89 Calendar* IslamicCalendar::clone() const { 90 return new IslamicCalendar(*this); 91 } 92 93 IslamicCalendar::IslamicCalendar(const Locale& aLocale, UErrorCode& success, ECivil beCivil) 94 : Calendar(TimeZone::createDefault(), aLocale, success), 95 civil(beCivil) 96 { 97 setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. 98 } 99 100 IslamicCalendar::IslamicCalendar(const IslamicCalendar& other) : Calendar(other), civil(other.civil) { 101 } 102 103 IslamicCalendar::~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 */ 115 void 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 */ 133 UBool 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. 148 static 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 */ 179 int32_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 */ 190 UBool 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 */ 199 int32_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 */ 214 int32_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 */ 231 int32_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 } 270 trueMonthStartEnd : 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 */ 286 double 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 */ 323 int32_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 */ 343 int32_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 */ 360 int32_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 */ 371 int32_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 */ 397 void 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 448 UBool 449 IslamicCalendar::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 462 const UDate IslamicCalendar::fgSystemDefaultCentury = DBL_MIN; 463 const int32_t IslamicCalendar::fgSystemDefaultCenturyYear = -1; 464 465 UDate IslamicCalendar::fgSystemDefaultCenturyStart = DBL_MIN; 466 int32_t IslamicCalendar::fgSystemDefaultCenturyStartYear = -1; 467 468 469 UBool IslamicCalendar::haveDefaultCentury() const 470 { 471 return TRUE; 472 } 473 474 UDate IslamicCalendar::defaultCenturyStart() const 475 { 476 return internalGetDefaultCenturyStart(); 477 } 478 479 int32_t IslamicCalendar::defaultCenturyStartYear() const 480 { 481 return internalGetDefaultCenturyStartYear(); 482 } 483 484 UDate 485 IslamicCalendar::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 501 int32_t 502 IslamicCalendar::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 518 void 519 IslamicCalendar::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 544 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(IslamicCalendar) 545 546 U_NAMESPACE_END 547 548 #endif 549 550