2 ******************************************************************************
3 * Copyright (C) 2003-2008, International Business Machines Corporation
4 * and others. All Rights Reserved.
5 ******************************************************************************
9 * Modification History:
11 * Date Name Description
12 * 10/14/2003 srl ported from java IslamicCalendar
13 *****************************************************************************
18 #if !UCONFIG_NO_FORMATTING
22 #include "gregoimp.h" // Math
23 #include "astro.h" // CalendarAstronomer
27 static const UDate HIJRA_MILLIS
= -42521587200000.0; // 7/16/622 AD 00:00
30 #ifdef U_DEBUG_ISLAMCAL
33 static void debug_islamcal_loc(const char *f
, int32_t l
)
35 fprintf(stderr
, "%s:%d: ", f
, l
);
38 static void debug_islamcal_msg(const char *pat
, ...)
42 vfprintf(stderr
, pat
, ap
);
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;}
48 #define U_DEBUG_ISLAMCAL_MSG(x)
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
;
59 static UBool
calendar_islamic_cleanup(void) {
64 if (gIslamicCalendarAstro
) {
65 delete gIslamicCalendarAstro
;
66 gIslamicCalendarAstro
= NULL
;
68 umtx_destroy(&astroLock
);
75 // Implementation of the IslamicCalendar class
77 //-------------------------------------------------------------------------
79 //-------------------------------------------------------------------------
81 const char *IslamicCalendar::getType() const {
83 return "islamic-civil";
89 Calendar
* IslamicCalendar::clone() const {
90 return new IslamicCalendar(*this);
93 IslamicCalendar::IslamicCalendar(const Locale
& aLocale
, UErrorCode
& success
, ECivil beCivil
)
94 : Calendar(TimeZone::createDefault(), aLocale
, success
),
97 setTimeInMillis(getNow(), success
); // Call this again now that the vtable is set up properly.
100 IslamicCalendar::IslamicCalendar(const IslamicCalendar
& other
) : Calendar(other
), civil(other
.civil
) {
103 IslamicCalendar::~IslamicCalendar()
108 * Determines whether this object uses the fixed-cycle Islamic civil calendar
109 * or an approximation of the religious, astronomical calendar.
111 * @param beCivil <code>true</code> to use the civil calendar,
112 * <code>false</code> to use the astronomical calendar.
115 void IslamicCalendar::setCivil(ECivil beCivil
, UErrorCode
&status
)
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
);
123 setTimeInMillis(m
, status
);
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
133 UBool
IslamicCalendar::isCivil() {
134 return (civil
== CIVIL
);
137 //-------------------------------------------------------------------------
138 // Minimum / Maximum access functions
139 //-------------------------------------------------------------------------
141 // Note: Current IslamicCalendar implementation does not work
142 // well with negative years.
144 static const int32_t LIMITS
[UCAL_FIELD_COUNT
][4] = {
145 // Minimum Greatest Least 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, 31}, // DAY_OF_MONTH (**** SB 30 ****)
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
175 int32_t IslamicCalendar::handleGetLimit(UCalendarDateFields field
, ELimitType limitType
) const {
176 return LIMITS
[field
][limitType
];
179 //-------------------------------------------------------------------------
180 // Assorted calculation utilities
184 * Determine whether a year is a leap year in the Islamic civil calendar
186 UBool
IslamicCalendar::civilLeapYear(int32_t year
)
188 return (14 + 11 * year
) % 30 < 11;
192 * Return the day # on which the given year starts. Days are counted
193 * from the Hijri epoch, origin 0.
195 int32_t IslamicCalendar::yearStart(int32_t year
) {
196 if (civil
== CIVIL
) {
197 return (year
-1)*354 + Math::floorDivide((3+11*year
),30);
199 return trueMonthStart(12*(year
-1));
204 * Return the day # on which the given month starts. Days are counted
205 * from the Hijri epoch, origin 0.
207 * @param year The hijri year
208 * @param year The hijri month, 0-based
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)Math::floorDivide((3+11*year
),30);
215 return trueMonthStart(12*(year
-1) + month
);
220 * Find the day number on which a particular month of the true/lunar
221 * Islamic calendar starts.
223 * @param month The month in question, origin 0 from the Hijri epoch
225 * @return The day number on which the given month starts.
227 int32_t IslamicCalendar::trueMonthStart(int32_t month
) const
229 UErrorCode status
= U_ZERO_ERROR
;
230 int32_t start
= CalendarCache::get(&gMonthCache
, month
, status
);
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
- 1) * kOneDay
;
237 // moonAge will fail due to memory allocation error
238 double age
= moonAge(origin
, status
);
239 if (U_FAILURE(status
)) {
240 goto trueMonthStartEnd
;
244 // The month has already started
247 age
= moonAge(origin
, status
);
248 if (U_FAILURE(status
)) {
249 goto trueMonthStartEnd
;
254 // Preceding month has not ended yet.
257 age
= moonAge(origin
, status
);
258 if (U_FAILURE(status
)) {
259 goto trueMonthStartEnd
;
263 start
= (int32_t)Math::floorDivide((origin
- HIJRA_MILLIS
), (double)kOneDay
) + 1;
264 CalendarCache::put(&gMonthCache
, month
, start
, status
);
267 if(U_FAILURE(status
)) {
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].
279 * @param time The time at which the moon's age is desired,
280 * in millis since 1/1/1970.
282 double IslamicCalendar::moonAge(UDate time
, UErrorCode
&status
)
286 umtx_lock(&astroLock
);
287 if(gIslamicCalendarAstro
== NULL
) {
288 gIslamicCalendarAstro
= new CalendarAstronomer();
289 if (gIslamicCalendarAstro
== NULL
) {
290 status
= U_MEMORY_ALLOCATION_ERROR
;
293 ucln_i18n_registerCleanup(UCLN_I18N_ISLAMIC_CALENDAR
, calendar_islamic_cleanup
);
295 gIslamicCalendarAstro
->setTime(time
);
296 age
= gIslamicCalendarAstro
->getMoonAge();
297 umtx_unlock(&astroLock
);
299 // Convert to degrees and normalize...
300 age
= age
* 180 / CalendarAstronomer::PI
;
308 //----------------------------------------------------------------------
309 // Calendar framework
310 //----------------------------------------------------------------------
313 * Return the length (in days) of the given month.
315 * @param year The hijri year
316 * @param year The hijri month, 0-based
319 int32_t IslamicCalendar::handleGetMonthLength(int32_t extendedYear
, int32_t month
) const {
323 if (civil
== CIVIL
) {
324 length
= 29 + (month
+1) % 2;
325 if (month
== DHU_AL_HIJJAH
&& civilLeapYear(extendedYear
)) {
329 month
= 12*(extendedYear
-1) + month
;
330 length
= trueMonthStart(month
+1) - trueMonthStart(month
) ;
336 * Return the number of days in the given Islamic year
339 int32_t IslamicCalendar::handleGetYearLength(int32_t extendedYear
) const {
340 if (civil
== CIVIL
) {
341 return 354 + (civilLeapYear(extendedYear
) ? 1 : 0);
343 int32_t month
= 12*(extendedYear
-1);
344 return (trueMonthStart(month
+ 12) - trueMonthStart(month
));
348 //-------------------------------------------------------------------------
349 // Functions for converting from field values to milliseconds....
350 //-------------------------------------------------------------------------
352 // Return JD of start of given month/year
356 int32_t IslamicCalendar::handleComputeMonthStart(int32_t eyear
, int32_t month
, UBool
/* useMonth */) const {
357 return monthStart(eyear
, month
) + 1948439;
360 //-------------------------------------------------------------------------
361 // Functions for converting from milliseconds to field values
362 //-------------------------------------------------------------------------
367 int32_t IslamicCalendar::handleGetExtendedYear() {
369 if (newerField(UCAL_EXTENDED_YEAR
, UCAL_YEAR
) == UCAL_EXTENDED_YEAR
) {
370 year
= internalGet(UCAL_EXTENDED_YEAR
, 1); // Default to year 1
372 year
= internalGet(UCAL_YEAR
, 1); // Default to year 1
378 * Override Calendar to compute several fields specific to the Islamic
379 * calendar system. These are:
386 * <li>EXTENDED_YEAR</ul>
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.
393 void IslamicCalendar::handleComputeFields(int32_t julianDay
, UErrorCode
&status
) {
394 int32_t year
, month
, dayOfMonth
, dayOfYear
;
396 int32_t days
= julianDay
- 1948440;
398 if (civil
== CIVIL
) {
399 // Use the civil calendar approximation, which is just arithmetic
400 year
= (int)Math::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
);
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
);
408 startDate
= uprv_floor(months
* CalendarAstronomer::SYNODIC_MONTH
- 1);
410 double age
= moonAge(internalGetTime(), status
);
411 if (U_FAILURE(status
)) {
412 status
= U_MEMORY_ALLOCATION_ERROR
;
415 if ( days
- startDate
>= 28 && age
> 0) {
416 // If we're near the end of the month, assume next month and search backwards
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
427 year
= months
/ 12 + 1;
431 dayOfMonth
= (days
- monthStart(year
, month
)) + 1;
433 // Now figure out the day of the year.
434 dayOfYear
= (days
- monthStart(year
, 0) + 1);
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
);
445 IslamicCalendar::inDaylightTime(UErrorCode
& status
) const
447 // copied from GregorianCalendar
448 if (U_FAILURE(status
) || (&(getTimeZone()) == NULL
&& !getTimeZone().useDaylightTime()))
451 // Force an update of the state of the Calendar.
452 ((IslamicCalendar
*)this)->complete(status
); // cast away const
454 return (UBool
)(U_SUCCESS(status
) ? (internalGet(UCAL_DST_OFFSET
) != 0) : FALSE
);
458 const UDate
IslamicCalendar::fgSystemDefaultCentury
= DBL_MIN
;
459 const int32_t IslamicCalendar::fgSystemDefaultCenturyYear
= -1;
461 UDate
IslamicCalendar::fgSystemDefaultCenturyStart
= DBL_MIN
;
462 int32_t IslamicCalendar::fgSystemDefaultCenturyStartYear
= -1;
465 UBool
IslamicCalendar::haveDefaultCentury() const
470 UDate
IslamicCalendar::defaultCenturyStart() const
472 return internalGetDefaultCenturyStart();
475 int32_t IslamicCalendar::defaultCenturyStartYear() const
477 return internalGetDefaultCenturyStartYear();
481 IslamicCalendar::internalGetDefaultCenturyStart() const
483 // lazy-evaluate systemDefaultCenturyStart
485 UMTX_CHECK(NULL
, (fgSystemDefaultCenturyStart
== fgSystemDefaultCentury
), needsUpdate
);
488 initializeSystemDefaultCentury();
491 // use defaultCenturyStart unless it's the flag value;
492 // then use systemDefaultCenturyStart
494 return fgSystemDefaultCenturyStart
;
498 IslamicCalendar::internalGetDefaultCenturyStartYear() const
500 // lazy-evaluate systemDefaultCenturyStartYear
502 UMTX_CHECK(NULL
, (fgSystemDefaultCenturyStart
== fgSystemDefaultCentury
), needsUpdate
);
505 initializeSystemDefaultCentury();
508 // use defaultCenturyStart unless it's the flag value;
509 // then use systemDefaultCenturyStartYear
511 return fgSystemDefaultCenturyStartYear
;
515 IslamicCalendar::initializeSystemDefaultCentury()
517 // initialize systemDefaultCentury and systemDefaultCenturyYear based
518 // on the current time. They'll be set to 80 years before
520 // No point in locking as it should be idempotent.
521 if (fgSystemDefaultCenturyStart
== fgSystemDefaultCentury
)
523 UErrorCode status
= U_ZERO_ERROR
;
524 IslamicCalendar
calendar(Locale("@calendar=islamic-civil"),status
);
525 if (U_SUCCESS(status
))
527 calendar
.setTime(Calendar::getNow(), status
);
528 calendar
.add(UCAL_YEAR
, -80, status
);
529 UDate newStart
= calendar
.getTime(status
);
530 int32_t newYear
= calendar
.get(UCAL_YEAR
, status
);
533 fgSystemDefaultCenturyStart
= newStart
;
534 fgSystemDefaultCenturyStartYear
= newYear
;
538 // We have no recourse upon failure unless we want to propagate the failure
543 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(IslamicCalendar
)