]>
Commit | Line | Data |
---|---|---|
374ca955 A |
1 | /* |
2 | * Copyright (C) 2003-2004, International Business Machines Corporation | |
3 | * and others. All Rights Reserved. | |
4 | ****************************************************************************** | |
5 | * | |
6 | * File ISLAMCAL.H | |
7 | * | |
8 | * Modification History: | |
9 | * | |
10 | * Date Name Description | |
11 | * 10/14/2003 srl ported from java IslamicCalendar | |
12 | ***************************************************************************** | |
13 | */ | |
14 | ||
15 | #include "islamcal.h" | |
16 | ||
17 | #if !UCONFIG_NO_FORMATTING | |
18 | ||
19 | #include "mutex.h" | |
20 | #include <float.h> | |
21 | #include "gregoimp.h" // Math | |
22 | #include "astro.h" // CalendarAstronomer | |
23 | #include "uhash.h" | |
24 | #include "ucln_in.h" | |
25 | ||
26 | static const UDate HIJRA_MILLIS = -42521587200000.0; // 7/16/622 AD 00:00 | |
27 | ||
28 | // Debugging | |
29 | #ifdef U_DEBUG_ISLAMCAL | |
30 | # include <stdio.h> | |
31 | # include <stdarg.h> | |
32 | static void debug_islamcal_loc(const char *f, int32_t l) | |
33 | { | |
34 | fprintf(stderr, "%s:%d: ", f, l); | |
35 | } | |
36 | ||
37 | static void debug_islamcal_msg(const char *pat, ...) | |
38 | { | |
39 | va_list ap; | |
40 | va_start(ap, pat); | |
41 | vfprintf(stderr, pat, ap); | |
42 | fflush(stderr); | |
43 | } | |
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;} | |
46 | #else | |
47 | #define U_DEBUG_ISLAMCAL_MSG(x) | |
48 | #endif | |
49 | ||
50 | ||
51 | // --- The cache -- | |
52 | // cache of months | |
53 | static UMTX astroLock = 0; // pod bay door lock | |
54 | static CalendarCache *gMonthCache = NULL; | |
55 | static CalendarAstronomer *gIslamicCalendarAstro = NULL; | |
56 | ||
57 | U_CDECL_BEGIN | |
58 | static UBool calendar_islamic_cleanup(void) { | |
59 | if (gMonthCache) { | |
60 | delete gMonthCache; | |
61 | gMonthCache = NULL; | |
62 | } | |
63 | if (gIslamicCalendarAstro) { | |
64 | delete gIslamicCalendarAstro; | |
65 | gIslamicCalendarAstro = NULL; | |
66 | } | |
67 | umtx_destroy(&astroLock); | |
68 | return TRUE; | |
69 | } | |
70 | U_CDECL_END | |
71 | ||
72 | U_NAMESPACE_BEGIN | |
73 | ||
74 | // Implementation of the IslamicCalendar class | |
75 | ||
76 | //------------------------------------------------------------------------- | |
77 | // Constructors... | |
78 | //------------------------------------------------------------------------- | |
79 | ||
80 | const char *IslamicCalendar::getType() const { | |
81 | if(civil==CIVIL) { | |
82 | return "islamic-civil"; | |
83 | } else { | |
84 | return "islamic"; | |
85 | } | |
86 | } | |
87 | ||
88 | Calendar* IslamicCalendar::clone() const { | |
89 | return new IslamicCalendar(*this); | |
90 | } | |
91 | ||
92 | IslamicCalendar::IslamicCalendar(const Locale& aLocale, UErrorCode& success, ECivil beCivil) | |
93 | : Calendar(TimeZone::createDefault(), aLocale, success), | |
94 | civil(beCivil) | |
95 | { | |
96 | setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly. | |
97 | } | |
98 | ||
99 | IslamicCalendar::IslamicCalendar(const IslamicCalendar& other) : Calendar(other), civil(other.civil) { | |
100 | } | |
101 | ||
102 | IslamicCalendar::~IslamicCalendar() | |
103 | { | |
104 | } | |
105 | ||
106 | /** | |
107 | * Determines whether this object uses the fixed-cycle Islamic civil calendar | |
108 | * or an approximation of the religious, astronomical calendar. | |
109 | * | |
110 | * @param beCivil <code>true</code> to use the civil calendar, | |
111 | * <code>false</code> to use the astronomical calendar. | |
112 | * @draft ICU 2.4 | |
113 | */ | |
114 | void IslamicCalendar::setCivil(ECivil beCivil, UErrorCode &status) | |
115 | { | |
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); | |
120 | civil = beCivil; | |
121 | clear(); | |
122 | setTimeInMillis(m, status); | |
123 | } | |
124 | } | |
125 | ||
126 | /** | |
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 | |
129 | * calendar. | |
130 | * @draft ICU 2.4 | |
131 | */ | |
132 | UBool IslamicCalendar::isCivil() { | |
133 | return (civil == CIVIL); | |
134 | } | |
135 | ||
136 | //------------------------------------------------------------------------- | |
137 | // Minimum / Maximum access functions | |
138 | //------------------------------------------------------------------------- | |
139 | ||
140 | static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = { | |
141 | // Minimum Greatest Least Maximum | |
142 | // Minimum 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 | |
165 | }; | |
166 | ||
167 | /** | |
168 | * @draft ICU 2.4 | |
169 | */ | |
170 | int32_t IslamicCalendar::handleGetLimit(UCalendarDateFields field, ELimitType limitType) const { | |
171 | return LIMITS[field][limitType]; | |
172 | } | |
173 | ||
174 | //------------------------------------------------------------------------- | |
175 | // Assorted calculation utilities | |
176 | // | |
177 | ||
178 | /** | |
179 | * Determine whether a year is a leap year in the Islamic civil calendar | |
180 | */ | |
181 | UBool IslamicCalendar::civilLeapYear(int32_t year) | |
182 | { | |
183 | return (14 + 11 * year) % 30 < 11; | |
184 | } | |
185 | ||
186 | /** | |
187 | * Return the day # on which the given year starts. Days are counted | |
188 | * from the Hijri epoch, origin 0. | |
189 | */ | |
190 | int32_t IslamicCalendar::yearStart(int32_t year) { | |
191 | if (civil == CIVIL) { | |
192 | return (year-1)*354 + Math::floorDivide((3+11*year),30); | |
193 | } else { | |
194 | return trueMonthStart(12*(year-1)); | |
195 | } | |
196 | } | |
197 | ||
198 | /** | |
199 | * Return the day # on which the given month starts. Days are counted | |
200 | * from the Hijri epoch, origin 0. | |
201 | * | |
202 | * @param year The hijri year | |
203 | * @param year The hijri month, 0-based | |
204 | */ | |
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); | |
209 | } else { | |
210 | return trueMonthStart(12*(year-1) + month); | |
211 | } | |
212 | } | |
213 | ||
214 | /** | |
215 | * Find the day number on which a particular month of the true/lunar | |
216 | * Islamic calendar starts. | |
217 | * | |
218 | * @param month The month in question, origin 0 from the Hijri epoch | |
219 | * | |
220 | * @return The day number on which the given month starts. | |
221 | */ | |
222 | int32_t IslamicCalendar::trueMonthStart(int32_t month) const | |
223 | { | |
224 | UErrorCode status = U_ZERO_ERROR; | |
225 | int32_t start = CalendarCache::get(&gMonthCache, month, status); | |
226 | ||
227 | if (start==0) { | |
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; | |
231 | ||
232 | double age = moonAge(origin); | |
233 | ||
234 | if (moonAge(origin) >= 0) { | |
235 | // The month has already started | |
236 | do { | |
237 | origin -= kOneDay; | |
238 | age = moonAge(origin); | |
239 | } while (age >= 0); | |
240 | } | |
241 | else { | |
242 | // Preceding month has not ended yet. | |
243 | do { | |
244 | origin += kOneDay; | |
245 | age = moonAge(origin); | |
246 | } while (age < 0); | |
247 | } | |
248 | start = (int32_t)Math::floorDivide((origin - HIJRA_MILLIS), (double)kOneDay) + 1; | |
249 | CalendarCache::put(&gMonthCache, month, start, status); | |
250 | } | |
251 | if(U_FAILURE(status)) { | |
252 | start = 0; | |
253 | } | |
254 | return start; | |
255 | } | |
256 | ||
257 | /** | |
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]. | |
262 | * | |
263 | * @param time The time at which the moon's age is desired, | |
264 | * in millis since 1/1/1970. | |
265 | */ | |
266 | double IslamicCalendar::moonAge(UDate time) | |
267 | { | |
268 | double age = 0; | |
269 | ||
270 | umtx_lock(&astroLock); | |
271 | if(gIslamicCalendarAstro == NULL) { | |
272 | gIslamicCalendarAstro = new CalendarAstronomer(); | |
273 | } | |
274 | gIslamicCalendarAstro->setTime(time); | |
275 | age = gIslamicCalendarAstro->getMoonAge(); | |
276 | ucln_i18n_registerCleanup(UCLN_I18N_ISLAMIC_CALENDAR, calendar_islamic_cleanup); | |
277 | umtx_unlock(&astroLock); | |
278 | ||
279 | // Convert to degrees and normalize... | |
280 | age = age * 180 / CalendarAstronomer::PI; | |
281 | if (age > 180) { | |
282 | age = age - 360; | |
283 | } | |
284 | ||
285 | return age; | |
286 | } | |
287 | ||
288 | //---------------------------------------------------------------------- | |
289 | // Calendar framework | |
290 | //---------------------------------------------------------------------- | |
291 | ||
292 | /** | |
293 | * Return the length (in days) of the given month. | |
294 | * | |
295 | * @param year The hijri year | |
296 | * @param year The hijri month, 0-based | |
297 | * @draft ICU 2.4 | |
298 | */ | |
299 | int32_t IslamicCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month) const { | |
300 | ||
301 | int32_t length = 0; | |
302 | ||
303 | if (civil == CIVIL) { | |
304 | length = 29 + (month+1) % 2; | |
305 | if (month == DHU_AL_HIJJAH && civilLeapYear(extendedYear)) { | |
306 | length++; | |
307 | } | |
308 | } else { | |
309 | month = 12*(extendedYear-1) + month; | |
310 | length = trueMonthStart(month+1) - trueMonthStart(month) ; | |
311 | } | |
312 | return length; | |
313 | } | |
314 | ||
315 | /** | |
316 | * Return the number of days in the given Islamic year | |
317 | * @draft ICU 2.4 | |
318 | */ | |
319 | int32_t IslamicCalendar::handleGetYearLength(int32_t extendedYear) const { | |
320 | if (civil == CIVIL) { | |
321 | return 354 + (civilLeapYear(extendedYear) ? 1 : 0); | |
322 | } else { | |
323 | int32_t month = 12*(extendedYear-1); | |
324 | return (trueMonthStart(month + 12) - trueMonthStart(month)); | |
325 | } | |
326 | } | |
327 | ||
328 | //------------------------------------------------------------------------- | |
329 | // Functions for converting from field values to milliseconds.... | |
330 | //------------------------------------------------------------------------- | |
331 | ||
332 | // Return JD of start of given month/year | |
333 | /** | |
334 | * @draft ICU 2.4 | |
335 | */ | |
336 | int32_t IslamicCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool /* useMonth */) const { | |
337 | return monthStart(eyear, month) + 1948439; | |
338 | } | |
339 | ||
340 | //------------------------------------------------------------------------- | |
341 | // Functions for converting from milliseconds to field values | |
342 | //------------------------------------------------------------------------- | |
343 | ||
344 | /** | |
345 | * @draft ICU 2.4 | |
346 | */ | |
347 | int32_t IslamicCalendar::handleGetExtendedYear() { | |
348 | int32_t year; | |
349 | if (newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR) == UCAL_EXTENDED_YEAR) { | |
350 | year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1 | |
351 | } else { | |
352 | year = internalGet(UCAL_YEAR, 1); // Default to year 1 | |
353 | } | |
354 | return year; | |
355 | } | |
356 | ||
357 | /** | |
358 | * Override Calendar to compute several fields specific to the Islamic | |
359 | * calendar system. These are: | |
360 | * | |
361 | * <ul><li>ERA | |
362 | * <li>YEAR | |
363 | * <li>MONTH | |
364 | * <li>DAY_OF_MONTH | |
365 | * <li>DAY_OF_YEAR | |
366 | * <li>EXTENDED_YEAR</ul> | |
367 | * | |
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. | |
371 | * @draft ICU 2.4 | |
372 | */ | |
373 | void IslamicCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/) { | |
374 | int32_t year, month, dayOfMonth, dayOfYear; | |
375 | UDate startDate; | |
376 | int32_t days = julianDay - 1948440; | |
377 | ||
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); | |
384 | } else { | |
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); | |
387 | ||
388 | startDate = uprv_floor(months * CalendarAstronomer::SYNODIC_MONTH - 1); | |
389 | ||
390 | if ( days - startDate >= 28 && moonAge(internalGetTime()) > 0) { | |
391 | // If we're near the end of the month, assume next month and search backwards | |
392 | months++; | |
393 | } | |
394 | ||
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 | |
399 | months--; | |
400 | } | |
401 | ||
402 | year = months / 12 + 1; | |
403 | month = months % 12; | |
404 | } | |
405 | ||
406 | dayOfMonth = (days - monthStart(year, month)) + 1; | |
407 | ||
408 | // Now figure out the day of the year. | |
409 | dayOfYear = (days - monthStart(year, 0) + 1); | |
410 | ||
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); | |
417 | } | |
418 | ||
419 | UBool | |
420 | IslamicCalendar::inDaylightTime(UErrorCode& status) const | |
421 | { | |
422 | // copied from GregorianCalendar | |
423 | if (U_FAILURE(status) || !getTimeZone().useDaylightTime()) | |
424 | return FALSE; | |
425 | ||
426 | // Force an update of the state of the Calendar. | |
427 | ((IslamicCalendar*)this)->complete(status); // cast away const | |
428 | ||
429 | return (UBool)(U_SUCCESS(status) ? (internalGet(UCAL_DST_OFFSET) != 0) : FALSE); | |
430 | } | |
431 | ||
432 | // default century | |
433 | const UDate IslamicCalendar::fgSystemDefaultCentury = DBL_MIN; | |
434 | const int32_t IslamicCalendar::fgSystemDefaultCenturyYear = -1; | |
435 | ||
436 | UDate IslamicCalendar::fgSystemDefaultCenturyStart = DBL_MIN; | |
437 | int32_t IslamicCalendar::fgSystemDefaultCenturyStartYear = -1; | |
438 | ||
439 | ||
440 | UBool IslamicCalendar::haveDefaultCentury() const | |
441 | { | |
442 | return TRUE; | |
443 | } | |
444 | ||
445 | UDate IslamicCalendar::defaultCenturyStart() const | |
446 | { | |
447 | return internalGetDefaultCenturyStart(); | |
448 | } | |
449 | ||
450 | int32_t IslamicCalendar::defaultCenturyStartYear() const | |
451 | { | |
452 | return internalGetDefaultCenturyStartYear(); | |
453 | } | |
454 | ||
455 | UDate | |
456 | IslamicCalendar::internalGetDefaultCenturyStart() const | |
457 | { | |
458 | // lazy-evaluate systemDefaultCenturyStart | |
459 | UBool needsUpdate; | |
460 | { | |
461 | Mutex m; | |
462 | needsUpdate = (fgSystemDefaultCenturyStart == fgSystemDefaultCentury); | |
463 | } | |
464 | ||
465 | if (needsUpdate) { | |
466 | initializeSystemDefaultCentury(); | |
467 | } | |
468 | ||
469 | // use defaultCenturyStart unless it's the flag value; | |
470 | // then use systemDefaultCenturyStart | |
471 | ||
472 | return fgSystemDefaultCenturyStart; | |
473 | } | |
474 | ||
475 | int32_t | |
476 | IslamicCalendar::internalGetDefaultCenturyStartYear() const | |
477 | { | |
478 | // lazy-evaluate systemDefaultCenturyStartYear | |
479 | UBool needsUpdate; | |
480 | { | |
481 | Mutex m; | |
482 | needsUpdate = (fgSystemDefaultCenturyStart == fgSystemDefaultCentury); | |
483 | } | |
484 | ||
485 | if (needsUpdate) { | |
486 | initializeSystemDefaultCentury(); | |
487 | } | |
488 | ||
489 | // use defaultCenturyStart unless it's the flag value; | |
490 | // then use systemDefaultCenturyStartYear | |
491 | ||
492 | return fgSystemDefaultCenturyStartYear; | |
493 | } | |
494 | ||
495 | void | |
496 | IslamicCalendar::initializeSystemDefaultCentury() | |
497 | { | |
498 | // initialize systemDefaultCentury and systemDefaultCenturyYear based | |
499 | // on the current time. They'll be set to 80 years before | |
500 | // the current time. | |
501 | // No point in locking as it should be idempotent. | |
502 | if (fgSystemDefaultCenturyStart == fgSystemDefaultCentury) | |
503 | { | |
504 | UErrorCode status = U_ZERO_ERROR; | |
505 | IslamicCalendar calendar(Locale("@calendar=islamic-civil"),status); | |
506 | if (U_SUCCESS(status)) | |
507 | { | |
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); | |
512 | { | |
513 | Mutex m; | |
514 | fgSystemDefaultCenturyStart = newStart; | |
515 | fgSystemDefaultCenturyStartYear = newYear; | |
516 | } | |
517 | } | |
518 | // We have no recourse upon failure unless we want to propagate the failure | |
519 | // out. | |
520 | } | |
521 | } | |
522 | ||
523 | UOBJECT_DEFINE_RTTI_IMPLEMENTATION(IslamicCalendar) | |
524 | ||
525 | U_NAMESPACE_END | |
526 | ||
527 | #endif | |
528 |