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 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 static 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 */ 175 int32_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 */ 186 UBool 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 */ 195 int32_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 */ 210 int32_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 */ 227 int32_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 } 266 trueMonthStartEnd : 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 */ 282 double 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 */ 319 int32_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 */ 339 int32_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 */ 356 int32_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 */ 367 int32_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 */ 393 void 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 444 UBool 445 IslamicCalendar::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 458 const UDate IslamicCalendar::fgSystemDefaultCentury = DBL_MIN; 459 const int32_t IslamicCalendar::fgSystemDefaultCenturyYear = -1; 460 461 UDate IslamicCalendar::fgSystemDefaultCenturyStart = DBL_MIN; 462 int32_t IslamicCalendar::fgSystemDefaultCenturyStartYear = -1; 463 464 465 UBool IslamicCalendar::haveDefaultCentury() const 466 { 467 return TRUE; 468 } 469 470 UDate IslamicCalendar::defaultCenturyStart() const 471 { 472 return internalGetDefaultCenturyStart(); 473 } 474 475 int32_t IslamicCalendar::defaultCenturyStartYear() const 476 { 477 return internalGetDefaultCenturyStartYear(); 478 } 479 480 UDate 481 IslamicCalendar::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 497 int32_t 498 IslamicCalendar::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 514 void 515 IslamicCalendar::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 540 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(IslamicCalendar) 541 542 U_NAMESPACE_END 543 544 #endif 545 546