2 * Copyright (C) 2003-2004, International Business Machines Corporation
3 * and others. All Rights Reserved.
4 ******************************************************************************
8 * Modification History:
10 * Date Name Description
11 * 10/14/2003 srl ported from java IslamicCalendar
12 *****************************************************************************
17 #if !UCONFIG_NO_FORMATTING
21 #include "gregoimp.h" // Math
22 #include "astro.h" // CalendarAstronomer
26 static const UDate HIJRA_MILLIS
= -42521587200000.0; // 7/16/622 AD 00:00
29 #ifdef U_DEBUG_ISLAMCAL
32 static void debug_islamcal_loc(const char *f
, int32_t l
)
34 fprintf(stderr
, "%s:%d: ", f
, l
);
37 static void debug_islamcal_msg(const char *pat
, ...)
41 vfprintf(stderr
, pat
, ap
);
44 // must use double parens, i.e.: U_DEBUG_ISLAMCAL_MSG(("four is: %d",4));
45 #define U_DEBUG_ISLAMCAL_MSG(x) {debug_islamcal_loc(__FILE__,__LINE__);debug_islamcal_msg x;}
47 #define U_DEBUG_ISLAMCAL_MSG(x)
53 static UMTX astroLock
= 0; // pod bay door lock
54 static CalendarCache
*gMonthCache
= NULL
;
55 static CalendarAstronomer
*gIslamicCalendarAstro
= NULL
;
58 static UBool
calendar_islamic_cleanup(void) {
63 if (gIslamicCalendarAstro
) {
64 delete gIslamicCalendarAstro
;
65 gIslamicCalendarAstro
= NULL
;
67 umtx_destroy(&astroLock
);
74 // Implementation of the IslamicCalendar class
76 //-------------------------------------------------------------------------
78 //-------------------------------------------------------------------------
80 const char *IslamicCalendar::getType() const {
82 return "islamic-civil";
88 Calendar
* IslamicCalendar::clone() const {
89 return new IslamicCalendar(*this);
92 IslamicCalendar::IslamicCalendar(const Locale
& aLocale
, UErrorCode
& success
, ECivil beCivil
)
93 : Calendar(TimeZone::createDefault(), aLocale
, success
),
96 setTimeInMillis(getNow(), success
); // Call this again now that the vtable is set up properly.
99 IslamicCalendar::IslamicCalendar(const IslamicCalendar
& other
) : Calendar(other
), civil(other
.civil
) {
102 IslamicCalendar::~IslamicCalendar()
107 * Determines whether this object uses the fixed-cycle Islamic civil calendar
108 * or an approximation of the religious, astronomical calendar.
110 * @param beCivil <code>true</code> to use the civil calendar,
111 * <code>false</code> to use the astronomical calendar.
114 void IslamicCalendar::setCivil(ECivil beCivil
, UErrorCode
&status
)
116 if (civil
!= beCivil
) {
117 // The fields of the calendar will become invalid, because the calendar
118 // rules are different
119 UDate m
= getTimeInMillis(status
);
122 setTimeInMillis(m
, status
);
127 * Returns <code>true</code> if this object is using the fixed-cycle civil
128 * calendar, or <code>false</code> if using the religious, astronomical
132 UBool
IslamicCalendar::isCivil() {
133 return (civil
== CIVIL
);
136 //-------------------------------------------------------------------------
137 // Minimum / Maximum access functions
138 //-------------------------------------------------------------------------
140 static const int32_t LIMITS
[UCAL_FIELD_COUNT
][4] = {
141 // Minimum Greatest Least Maximum
143 { 0, 0, 0, 0 }, // ERA
144 { 1, 1, 5000000, 5000000 }, // YEAR
145 { 0, 0, 11, 11 }, // MONTH
146 { 1, 1, 51, 52 }, // WEEK_OF_YEAR
147 { 0, 0, 5, 6 }, // WEEK_OF_MONTH
148 { 1, 1, 29, 30 }, // DAY_OF_MONTH
149 { 1, 1, 354, 355 }, // DAY_OF_YEAR
150 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DAY_OF_WEEK
151 { -1, -1, 4, 5 }, // DAY_OF_WEEK_IN_MONTH
152 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // AM_PM
153 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR
154 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // HOUR_OF_DAY
155 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MINUTE
156 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // SECOND
157 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // MILLISECOND
158 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // ZONE_OFFSET
159 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DST_OFFSET
160 { 1, 1, 5000001, 5000001 }, // YEAR_WOY
161 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // DOW_LOCAL
162 { 1, 1, 5000000, 5000000 }, // EXTENDED_YEAR
163 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1}, // JULIAN_DAY
164 {/*N/A*/-1,/*N/A*/-1,/*N/A*/-1,/*N/A*/-1} // MILLISECONDS_IN_DAY
170 int32_t IslamicCalendar::handleGetLimit(UCalendarDateFields field
, ELimitType limitType
) const {
171 return LIMITS
[field
][limitType
];
174 //-------------------------------------------------------------------------
175 // Assorted calculation utilities
179 * Determine whether a year is a leap year in the Islamic civil calendar
181 UBool
IslamicCalendar::civilLeapYear(int32_t year
)
183 return (14 + 11 * year
) % 30 < 11;
187 * Return the day # on which the given year starts. Days are counted
188 * from the Hijri epoch, origin 0.
190 int32_t IslamicCalendar::yearStart(int32_t year
) {
191 if (civil
== CIVIL
) {
192 return (year
-1)*354 + Math::floorDivide((3+11*year
),30);
194 return trueMonthStart(12*(year
-1));
199 * Return the day # on which the given month starts. Days are counted
200 * from the Hijri epoch, origin 0.
202 * @param year The hijri year
203 * @param year The hijri month, 0-based
205 int32_t IslamicCalendar::monthStart(int32_t year
, int32_t month
) const {
206 if (civil
== CIVIL
) {
207 return (int32_t)uprv_ceil(29.5*month
)
208 + (year
-1)*354 + (int32_t)Math::floorDivide((3+11*year
),30);
210 return trueMonthStart(12*(year
-1) + month
);
215 * Find the day number on which a particular month of the true/lunar
216 * Islamic calendar starts.
218 * @param month The month in question, origin 0 from the Hijri epoch
220 * @return The day number on which the given month starts.
222 int32_t IslamicCalendar::trueMonthStart(int32_t month
) const
224 UErrorCode status
= U_ZERO_ERROR
;
225 int32_t start
= CalendarCache::get(&gMonthCache
, month
, status
);
228 // Make a guess at when the month started, using the average length
229 UDate origin
= HIJRA_MILLIS
230 + uprv_floor(month
* CalendarAstronomer::SYNODIC_MONTH
- 1) * kOneDay
;
232 double age
= moonAge(origin
);
234 if (moonAge(origin
) >= 0) {
235 // The month has already started
238 age
= moonAge(origin
);
242 // Preceding month has not ended yet.
245 age
= moonAge(origin
);
248 start
= (int32_t)Math::floorDivide((origin
- HIJRA_MILLIS
), (double)kOneDay
) + 1;
249 CalendarCache::put(&gMonthCache
, month
, start
, status
);
251 if(U_FAILURE(status
)) {
258 * Return the "age" of the moon at the given time; this is the difference
259 * in ecliptic latitude between the moon and the sun. This method simply
260 * calls CalendarAstronomer.moonAge, converts to degrees,
261 * and adjusts the result to be in the range [-180, 180].
263 * @param time The time at which the moon's age is desired,
264 * in millis since 1/1/1970.
266 double IslamicCalendar::moonAge(UDate time
)
270 umtx_lock(&astroLock
);
271 if(gIslamicCalendarAstro
== NULL
) {
272 gIslamicCalendarAstro
= new CalendarAstronomer();
274 gIslamicCalendarAstro
->setTime(time
);
275 age
= gIslamicCalendarAstro
->getMoonAge();
276 ucln_i18n_registerCleanup(UCLN_I18N_ISLAMIC_CALENDAR
, calendar_islamic_cleanup
);
277 umtx_unlock(&astroLock
);
279 // Convert to degrees and normalize...
280 age
= age
* 180 / CalendarAstronomer::PI
;
288 //----------------------------------------------------------------------
289 // Calendar framework
290 //----------------------------------------------------------------------
293 * Return the length (in days) of the given month.
295 * @param year The hijri year
296 * @param year The hijri month, 0-based
299 int32_t IslamicCalendar::handleGetMonthLength(int32_t extendedYear
, int32_t month
) const {
303 if (civil
== CIVIL
) {
304 length
= 29 + (month
+1) % 2;
305 if (month
== DHU_AL_HIJJAH
&& civilLeapYear(extendedYear
)) {
309 month
= 12*(extendedYear
-1) + month
;
310 length
= trueMonthStart(month
+1) - trueMonthStart(month
) ;
316 * Return the number of days in the given Islamic year
319 int32_t IslamicCalendar::handleGetYearLength(int32_t extendedYear
) const {
320 if (civil
== CIVIL
) {
321 return 354 + (civilLeapYear(extendedYear
) ? 1 : 0);
323 int32_t month
= 12*(extendedYear
-1);
324 return (trueMonthStart(month
+ 12) - trueMonthStart(month
));
328 //-------------------------------------------------------------------------
329 // Functions for converting from field values to milliseconds....
330 //-------------------------------------------------------------------------
332 // Return JD of start of given month/year
336 int32_t IslamicCalendar::handleComputeMonthStart(int32_t eyear
, int32_t month
, UBool
/* useMonth */) const {
337 return monthStart(eyear
, month
) + 1948439;
340 //-------------------------------------------------------------------------
341 // Functions for converting from milliseconds to field values
342 //-------------------------------------------------------------------------
347 int32_t IslamicCalendar::handleGetExtendedYear() {
349 if (newerField(UCAL_EXTENDED_YEAR
, UCAL_YEAR
) == UCAL_EXTENDED_YEAR
) {
350 year
= internalGet(UCAL_EXTENDED_YEAR
, 1); // Default to year 1
352 year
= internalGet(UCAL_YEAR
, 1); // Default to year 1
358 * Override Calendar to compute several fields specific to the Islamic
359 * calendar system. These are:
366 * <li>EXTENDED_YEAR</ul>
368 * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
369 * method is called. The getGregorianXxx() methods return Gregorian
370 * calendar equivalents for the given Julian day.
373 void IslamicCalendar::handleComputeFields(int32_t julianDay
, UErrorCode
&/*status*/) {
374 int32_t year
, month
, dayOfMonth
, dayOfYear
;
376 int32_t days
= julianDay
- 1948440;
378 if (civil
== CIVIL
) {
379 // Use the civil calendar approximation, which is just arithmetic
380 year
= (int)Math::floorDivide( (double)(30 * days
+ 10646) , 10631.0 );
381 month
= (int32_t)uprv_ceil((days
- 29 - yearStart(year
)) / 29.5 );
382 month
= month
<11?month
:11;
383 startDate
= monthStart(year
, month
);
385 // Guess at the number of elapsed full months since the epoch
386 int32_t months
= (int32_t)uprv_floor((double)days
/ CalendarAstronomer::SYNODIC_MONTH
);
388 startDate
= uprv_floor(months
* CalendarAstronomer::SYNODIC_MONTH
- 1);
390 if ( days
- startDate
>= 28 && moonAge(internalGetTime()) > 0) {
391 // If we're near the end of the month, assume next month and search backwards
395 // Find out the last time that the new moon was actually visible at this longitude
396 // This returns midnight the night that the moon was visible at sunset.
397 while ((startDate
= trueMonthStart(months
)) > days
) {
398 // If it was after the date in question, back up a month and try again
402 year
= months
/ 12 + 1;
406 dayOfMonth
= (days
- monthStart(year
, month
)) + 1;
408 // Now figure out the day of the year.
409 dayOfYear
= (days
- monthStart(year
, 0) + 1);
411 internalSet(UCAL_ERA
, 0);
412 internalSet(UCAL_YEAR
, year
);
413 internalSet(UCAL_EXTENDED_YEAR
, year
);
414 internalSet(UCAL_MONTH
, month
);
415 internalSet(UCAL_DAY_OF_MONTH
, dayOfMonth
);
416 internalSet(UCAL_DAY_OF_YEAR
, dayOfYear
);
420 IslamicCalendar::inDaylightTime(UErrorCode
& status
) const
422 // copied from GregorianCalendar
423 if (U_FAILURE(status
) || !getTimeZone().useDaylightTime())
426 // Force an update of the state of the Calendar.
427 ((IslamicCalendar
*)this)->complete(status
); // cast away const
429 return (UBool
)(U_SUCCESS(status
) ? (internalGet(UCAL_DST_OFFSET
) != 0) : FALSE
);
433 const UDate
IslamicCalendar::fgSystemDefaultCentury
= DBL_MIN
;
434 const int32_t IslamicCalendar::fgSystemDefaultCenturyYear
= -1;
436 UDate
IslamicCalendar::fgSystemDefaultCenturyStart
= DBL_MIN
;
437 int32_t IslamicCalendar::fgSystemDefaultCenturyStartYear
= -1;
440 UBool
IslamicCalendar::haveDefaultCentury() const
445 UDate
IslamicCalendar::defaultCenturyStart() const
447 return internalGetDefaultCenturyStart();
450 int32_t IslamicCalendar::defaultCenturyStartYear() const
452 return internalGetDefaultCenturyStartYear();
456 IslamicCalendar::internalGetDefaultCenturyStart() const
458 // lazy-evaluate systemDefaultCenturyStart
462 needsUpdate
= (fgSystemDefaultCenturyStart
== fgSystemDefaultCentury
);
466 initializeSystemDefaultCentury();
469 // use defaultCenturyStart unless it's the flag value;
470 // then use systemDefaultCenturyStart
472 return fgSystemDefaultCenturyStart
;
476 IslamicCalendar::internalGetDefaultCenturyStartYear() const
478 // lazy-evaluate systemDefaultCenturyStartYear
482 needsUpdate
= (fgSystemDefaultCenturyStart
== fgSystemDefaultCentury
);
486 initializeSystemDefaultCentury();
489 // use defaultCenturyStart unless it's the flag value;
490 // then use systemDefaultCenturyStartYear
492 return fgSystemDefaultCenturyStartYear
;
496 IslamicCalendar::initializeSystemDefaultCentury()
498 // initialize systemDefaultCentury and systemDefaultCenturyYear based
499 // on the current time. They'll be set to 80 years before
501 // No point in locking as it should be idempotent.
502 if (fgSystemDefaultCenturyStart
== fgSystemDefaultCentury
)
504 UErrorCode status
= U_ZERO_ERROR
;
505 IslamicCalendar
calendar(Locale("@calendar=islamic-civil"),status
);
506 if (U_SUCCESS(status
))
508 calendar
.setTime(Calendar::getNow(), status
);
509 calendar
.add(UCAL_YEAR
, -80, status
);
510 UDate newStart
= calendar
.getTime(status
);
511 int32_t newYear
= calendar
.get(UCAL_YEAR
, status
);
514 fgSystemDefaultCenturyStart
= newStart
;
515 fgSystemDefaultCenturyStartYear
= newYear
;
518 // We have no recourse upon failure unless we want to propagate the failure
523 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(IslamicCalendar
)