001: /*
002: *******************************************************************************
003: * Copyright (C) 1996-2006, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: */
007: package com.ibm.icu.util;
008:
009: import com.ibm.icu.util.TimeZone;
010: import com.ibm.icu.impl.CalendarAstronomer;
011: import com.ibm.icu.impl.CalendarCache;
012: import java.util.Date;
013: import java.util.Locale;
014:
015: /**
016: * <code>IslamicCalendar</code> is a subclass of <code>Calendar</code>
017: * that that implements the Islamic civil and religious calendars. It
018: * is used as the civil calendar in most of the Arab world and the
019: * liturgical calendar of the Islamic faith worldwide. This calendar
020: * is also known as the "Hijri" calendar, since it starts at the time
021: * of Mohammed's emigration (or "hijra") to Medinah on Thursday,
022: * July 15, 622 AD (Julian).
023: * <p>
024: * The Islamic calendar is strictly lunar, and thus an Islamic year of twelve
025: * lunar months does not correspond to the solar year used by most other
026: * calendar systems, including the Gregorian. An Islamic year is, on average,
027: * about 354 days long, so each successive Islamic year starts about 11 days
028: * earlier in the corresponding Gregorian year.
029: * <p>
030: * Each month of the calendar starts when the new moon's crescent is visible
031: * at sunset. However, in order to keep the time fields in this class
032: * synchronized with those of the other calendars and with local clock time,
033: * we treat days and months as beginning at midnight,
034: * roughly 6 hours after the corresponding sunset.
035: * <p>
036: * There are two main variants of the Islamic calendar in existence. The first
037: * is the <em>civil</em> calendar, which uses a fixed cycle of alternating 29-
038: * and 30-day months, with a leap day added to the last month of 11 out of
039: * every 30 years. This calendar is easily calculated and thus predictable in
040: * advance, so it is used as the civil calendar in a number of Arab countries.
041: * This is the default behavior of a newly-created <code>IslamicCalendar</code>
042: * object.
043: * <p>
044: * The Islamic <em>religious</em> calendar, however, is based on the <em>observation</em>
045: * of the crescent moon. It is thus affected by the position at which the
046: * observations are made, seasonal variations in the time of sunset, the
047: * eccentricities of the moon's orbit, and even the weather at the observation
048: * site. This makes it impossible to calculate in advance, and it causes the
049: * start of a month in the religious calendar to differ from the civil calendar
050: * by up to three days.
051: * <p>
052: * Using astronomical calculations for the position of the sun and moon, the
053: * moon's illumination, and other factors, it is possible to determine the start
054: * of a lunar month with a fairly high degree of certainty. However, these
055: * calculations are extremely complicated and thus slow, so most algorithms,
056: * including the one used here, are only approximations of the true astronical
057: * calculations. At present, the approximations used in this class are fairly
058: * simplistic; they will be improved in later versions of the code.
059: * <p>
060: * The {@link #setCivil setCivil} method determines
061: * which approach is used to determine the start of a month. By default, the
062: * fixed-cycle civil calendar is used. However, if <code>setCivil(false)</code>
063: * is called, an approximation of the true lunar calendar will be used.
064: * <p>
065: * This class should not be subclassed.</p>
066: * <p>
067: * IslamicCalendar usually should be instantiated using
068: * {@link com.ibm.icu.util.Calendar#getInstance(ULocale)} passing in a <code>ULocale</code>
069: * with the tag <code>"@calendar=islamic"</code> or <code>"@calendar=islamic-civil"</code>.</p>
070: *
071: * @see com.ibm.icu.util.GregorianCalendar
072: * @see com.ibm.icu.util.Calendar
073: *
074: * @author Laura Werner
075: * @author Alan Liu
076: * @stable ICU 2.8
077: */
078: public class IslamicCalendar extends Calendar {
079: // jdk1.4.2 serialver
080: private static final long serialVersionUID = -6253365474073869325L;
081:
082: private static String copyright = "Copyright \u00a9 1997-1998 IBM Corp. All Rights Reserved.";
083:
084: //-------------------------------------------------------------------------
085: // Constants...
086: //-------------------------------------------------------------------------
087:
088: /**
089: * Constant for Muharram, the 1st month of the Islamic year.
090: * @stable ICU 2.8
091: */
092: public static final int MUHARRAM = 0;
093:
094: /**
095: * Constant for Safar, the 2nd month of the Islamic year.
096: * @stable ICU 2.8
097: */
098: public static final int SAFAR = 1;
099:
100: /**
101: * Constant for Rabi' al-awwal (or Rabi' I), the 3rd month of the Islamic year.
102: * @stable ICU 2.8
103: */
104: public static final int RABI_1 = 2;
105:
106: /**
107: * Constant for Rabi' al-thani or (Rabi' II), the 4th month of the Islamic year.
108: * @stable ICU 2.8
109: */
110: public static final int RABI_2 = 3;
111:
112: /**
113: * Constant for Jumada al-awwal or (Jumada I), the 5th month of the Islamic year.
114: * @stable ICU 2.8
115: */
116: public static final int JUMADA_1 = 4;
117:
118: /**
119: * Constant for Jumada al-thani or (Jumada II), the 6th month of the Islamic year.
120: * @stable ICU 2.8
121: */
122: public static final int JUMADA_2 = 5;
123:
124: /**
125: * Constant for Rajab, the 7th month of the Islamic year.
126: * @stable ICU 2.8
127: */
128: public static final int RAJAB = 6;
129:
130: /**
131: * Constant for Sha'ban, the 8th month of the Islamic year.
132: * @stable ICU 2.8
133: */
134: public static final int SHABAN = 7;
135:
136: /**
137: * Constant for Ramadan, the 9th month of the Islamic year.
138: * @stable ICU 2.8
139: */
140: public static final int RAMADAN = 8;
141:
142: /**
143: * Constant for Shawwal, the 10th month of the Islamic year.
144: * @stable ICU 2.8
145: */
146: public static final int SHAWWAL = 9;
147:
148: /**
149: * Constant for Dhu al-Qi'dah, the 11th month of the Islamic year.
150: * @stable ICU 2.8
151: */
152: public static final int DHU_AL_QIDAH = 10;
153:
154: /**
155: * Constant for Dhu al-Hijjah, the 12th month of the Islamic year.
156: * @stable ICU 2.8
157: */
158: public static final int DHU_AL_HIJJAH = 11;
159:
160: private static final long HIJRA_MILLIS = -42521587200000L; // 7/16/622 AD 00:00
161:
162: //-------------------------------------------------------------------------
163: // Constructors...
164: //-------------------------------------------------------------------------
165:
166: /**
167: * Constructs a default <code>IslamicCalendar</code> using the current time
168: * in the default time zone with the default locale.
169: * @stable ICU 2.8
170: */
171: public IslamicCalendar() {
172: this (TimeZone.getDefault(), ULocale.getDefault());
173: }
174:
175: /**
176: * Constructs an <code>IslamicCalendar</code> based on the current time
177: * in the given time zone with the default locale.
178: * @param zone the given time zone.
179: * @stable ICU 2.8
180: */
181: public IslamicCalendar(TimeZone zone) {
182: this (zone, ULocale.getDefault());
183: }
184:
185: /**
186: * Constructs an <code>IslamicCalendar</code> based on the current time
187: * in the default time zone with the given locale.
188: *
189: * @param aLocale the given locale.
190: * @stable ICU 2.8
191: */
192: public IslamicCalendar(Locale aLocale) {
193: this (TimeZone.getDefault(), aLocale);
194: }
195:
196: /**
197: * Constructs an <code>IslamicCalendar</code> based on the current time
198: * in the default time zone with the given locale.
199: *
200: * @param locale the given ulocale.
201: * @draft ICU 3.2
202: * @provisional This API might change or be removed in a future release.
203: */
204: public IslamicCalendar(ULocale locale) {
205: this (TimeZone.getDefault(), locale);
206: }
207:
208: /**
209: * Constructs an <code>IslamicCalendar</code> based on the current time
210: * in the given time zone with the given locale.
211: *
212: * @param zone the given time zone.
213: * @param aLocale the given locale.
214: * @stable ICU 2.8
215: */
216: public IslamicCalendar(TimeZone zone, Locale aLocale) {
217: super (zone, aLocale);
218: setTimeInMillis(System.currentTimeMillis());
219: }
220:
221: /**
222: * Constructs an <code>IslamicCalendar</code> based on the current time
223: * in the given time zone with the given locale.
224: *
225: * @param zone the given time zone.
226: * @param locale the given ulocale.
227: * @draft ICU 3.2
228: * @provisional This API might change or be removed in a future release.
229: */
230: public IslamicCalendar(TimeZone zone, ULocale locale) {
231: super (zone, locale);
232: setTimeInMillis(System.currentTimeMillis());
233: }
234:
235: /**
236: * Constructs an <code>IslamicCalendar</code> with the given date set
237: * in the default time zone with the default locale.
238: *
239: * @param date The date to which the new calendar is set.
240: * @stable ICU 2.8
241: */
242: public IslamicCalendar(Date date) {
243: super (TimeZone.getDefault(), ULocale.getDefault());
244: this .setTime(date);
245: }
246:
247: /**
248: * Constructs an <code>IslamicCalendar</code> with the given date set
249: * in the default time zone with the default locale.
250: *
251: * @param year the value used to set the {@link #YEAR YEAR} time field in the calendar.
252: * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar.
253: * Note that the month value is 0-based. e.g., 0 for Muharram.
254: * @param date the value used to set the {@link #DATE DATE} time field in the calendar.
255: * @stable ICU 2.8
256: */
257: public IslamicCalendar(int year, int month, int date) {
258: super (TimeZone.getDefault(), ULocale.getDefault());
259: this .set(Calendar.YEAR, year);
260: this .set(Calendar.MONTH, month);
261: this .set(Calendar.DATE, date);
262: }
263:
264: /**
265: * Constructs an <code>IslamicCalendar</code> with the given date
266: * and time set for the default time zone with the default locale.
267: *
268: * @param year the value used to set the {@link #YEAR YEAR} time field in the calendar.
269: * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar.
270: * Note that the month value is 0-based. e.g., 0 for Muharram.
271: * @param date the value used to set the {@link #DATE DATE} time field in the calendar.
272: * @param hour the value used to set the {@link #HOUR_OF_DAY HOUR_OF_DAY} time field
273: * in the calendar.
274: * @param minute the value used to set the {@link #MINUTE MINUTE} time field
275: * in the calendar.
276: * @param second the value used to set the {@link #SECOND SECOND} time field
277: * in the calendar.
278: * @stable ICU 2.8
279: */
280: public IslamicCalendar(int year, int month, int date, int hour,
281: int minute, int second) {
282: super (TimeZone.getDefault(), ULocale.getDefault());
283: this .set(Calendar.YEAR, year);
284: this .set(Calendar.MONTH, month);
285: this .set(Calendar.DATE, date);
286: this .set(Calendar.HOUR_OF_DAY, hour);
287: this .set(Calendar.MINUTE, minute);
288: this .set(Calendar.SECOND, second);
289: }
290:
291: /**
292: * Determines whether this object uses the fixed-cycle Islamic civil calendar
293: * or an approximation of the religious, astronomical calendar.
294: *
295: * @param beCivil <code>true</code> to use the civil calendar,
296: * <code>false</code> to use the astronomical calendar.
297: * @stable ICU 2.8
298: */
299: public void setCivil(boolean beCivil) {
300: if (civil != beCivil) {
301: // The fields of the calendar will become invalid, because the calendar
302: // rules are different
303: long m = getTimeInMillis();
304: civil = beCivil;
305: clear();
306: setTimeInMillis(m);
307: }
308: }
309:
310: /**
311: * Returns <code>true</code> if this object is using the fixed-cycle civil
312: * calendar, or <code>false</code> if using the religious, astronomical
313: * calendar.
314: * @stable ICU 2.8
315: */
316: public boolean isCivil() {
317: return civil;
318: }
319:
320: //-------------------------------------------------------------------------
321: // Minimum / Maximum access functions
322: //-------------------------------------------------------------------------
323:
324: private static final int LIMITS[][] = {
325: // Minimum Greatest Least Maximum
326: // Minimum Maximum
327: { 0, 0, 0, 0 }, // ERA
328: { 1, 1, 5000000, 5000000 }, // YEAR
329: { 0, 0, 11, 11 }, // MONTH
330: { 1, 1, 51, 52 }, // WEEK_OF_YEAR
331: { 0, 0, 5, 6 }, // WEEK_OF_MONTH
332: { 1, 1, 29, 30 }, // DAY_OF_MONTH
333: { 1, 1, 354, 355 }, // DAY_OF_YEAR
334: {/* */}, // DAY_OF_WEEK
335: { -1, -1, 4, 5 }, // DAY_OF_WEEK_IN_MONTH
336: {/* */}, // AM_PM
337: {/* */}, // HOUR
338: {/* */}, // HOUR_OF_DAY
339: {/* */}, // MINUTE
340: {/* */}, // SECOND
341: {/* */}, // MILLISECOND
342: {/* */}, // ZONE_OFFSET
343: {/* */}, // DST_OFFSET
344: { -5000001, -5000001, 5000001, 5000001 }, // YEAR_WOY
345: {/* */}, // DOW_LOCAL
346: { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR
347: {/* */}, // JULIAN_DAY
348: {/* */}, // MILLISECONDS_IN_DAY
349: };
350:
351: /**
352: * @stable ICU 2.8
353: */
354: protected int handleGetLimit(int field, int limitType) {
355: return LIMITS[field][limitType];
356: }
357:
358: //-------------------------------------------------------------------------
359: // Assorted calculation utilities
360: //
361:
362: // Unused code - Alan 2003-05
363: // /**
364: // * Find the day of the week for a given day
365: // *
366: // * @param day The # of days since the start of the Islamic calendar.
367: // */
368: // // private and uncalled, perhaps not used yet?
369: // ///CLOVER:OFF
370: // private static final int absoluteDayToDayOfWeek(long day)
371: // {
372: // // Calculate the day of the week.
373: // // This relies on the fact that the epoch was a Thursday.
374: // int dayOfWeek = (int)(day + THURSDAY) % 7 + SUNDAY;
375: // if (dayOfWeek < 0) {
376: // dayOfWeek += 7;
377: // }
378: // return dayOfWeek;
379: // }
380: // ///CLOVER:ON
381:
382: /**
383: * Determine whether a year is a leap year in the Islamic civil calendar
384: */
385: private final static boolean civilLeapYear(int year) {
386: return (14 + 11 * year) % 30 < 11;
387:
388: }
389:
390: /**
391: * Return the day # on which the given year starts. Days are counted
392: * from the Hijri epoch, origin 0.
393: */
394: private long yearStart(int year) {
395: if (civil) {
396: return (year - 1) * 354
397: + (long) Math.floor((3 + 11 * year) / 30.0);
398: } else {
399: return trueMonthStart(12 * (year - 1));
400: }
401: }
402:
403: /**
404: * Return the day # on which the given month starts. Days are counted
405: * from the Hijri epoch, origin 0.
406: *
407: * @param year The hijri year
408: * @param year The hijri month, 0-based
409: */
410: private long monthStart(int year, int month) {
411: if (civil) {
412: return (long) Math.ceil(29.5 * month) + (year - 1) * 354
413: + (long) Math.floor((3 + 11 * year) / 30.0);
414: } else {
415: return trueMonthStart(12 * (year - 1) + month);
416: }
417: }
418:
419: /**
420: * Find the day number on which a particular month of the true/lunar
421: * Islamic calendar starts.
422: *
423: * @param month The month in question, origin 0 from the Hijri epoch
424: *
425: * @return The day number on which the given month starts.
426: */
427: private static final long trueMonthStart(long month) {
428: long start = cache.get(month);
429:
430: if (start == CalendarCache.EMPTY) {
431: // Make a guess at when the month started, using the average length
432: long origin = HIJRA_MILLIS
433: + (long) Math.floor(month
434: * CalendarAstronomer.SYNODIC_MONTH - 1)
435: * ONE_DAY;
436:
437: double age = moonAge(origin);
438:
439: if (moonAge(origin) >= 0) {
440: // The month has already started
441: do {
442: origin -= ONE_DAY;
443: age = moonAge(origin);
444: } while (age >= 0);
445: } else {
446: // Preceding month has not ended yet.
447: do {
448: origin += ONE_DAY;
449: age = moonAge(origin);
450: } while (age < 0);
451: }
452:
453: start = (origin - HIJRA_MILLIS) / ONE_DAY + 1;
454:
455: cache.put(month, start);
456: }
457: return start;
458: }
459:
460: /**
461: * Return the "age" of the moon at the given time; this is the difference
462: * in ecliptic latitude between the moon and the sun. This method simply
463: * calls CalendarAstronomer.moonAge, converts to degrees,
464: * and adjusts the resultto be in the range [-180, 180].
465: *
466: * @param time The time at which the moon's age is desired,
467: * in millis since 1/1/1970.
468: */
469: static final double moonAge(long time) {
470: double age = 0;
471:
472: synchronized (astro) {
473: astro.setTime(time);
474: age = astro.getMoonAge();
475: }
476: // Convert to degrees and normalize...
477: age = age * 180 / Math.PI;
478: if (age > 180) {
479: age = age - 360;
480: }
481:
482: return age;
483: }
484:
485: //-------------------------------------------------------------------------
486: // Internal data....
487: //
488:
489: // And an Astronomer object for the moon age calculations
490: private static CalendarAstronomer astro = new CalendarAstronomer();
491:
492: private static CalendarCache cache = new CalendarCache();
493:
494: /**
495: * <code>true</code> if this object uses the fixed-cycle Islamic civil calendar,
496: * and <code>false</code> if it approximates the true religious calendar using
497: * astronomical calculations for the time of the new moon.
498: *
499: * @serial
500: */
501: private boolean civil = true;
502:
503: //----------------------------------------------------------------------
504: // Calendar framework
505: //----------------------------------------------------------------------
506:
507: /**
508: * Return the length (in days) of the given month.
509: *
510: * @param extendedYear The hijri year
511: * @param month The hijri month, 0-based
512: * @stable ICU 2.8
513: */
514: protected int handleGetMonthLength(int extendedYear, int month) {
515:
516: int length = 0;
517:
518: if (civil) {
519: length = 29 + (month + 1) % 2;
520: if (month == DHU_AL_HIJJAH && civilLeapYear(extendedYear)) {
521: length++;
522: }
523: } else {
524: month = 12 * (extendedYear - 1) + month;
525: length = (int) (trueMonthStart(month + 1) - trueMonthStart(month));
526: }
527: return length;
528: }
529:
530: /**
531: * Return the number of days in the given Islamic year
532: * @stable ICU 2.8
533: */
534: protected int handleGetYearLength(int extendedYear) {
535: if (civil) {
536: return 354 + (civilLeapYear(extendedYear) ? 1 : 0);
537: } else {
538: int month = 12 * (extendedYear - 1);
539: return (int) (trueMonthStart(month + 12) - trueMonthStart(month));
540: }
541: }
542:
543: //-------------------------------------------------------------------------
544: // Functions for converting from field values to milliseconds....
545: //-------------------------------------------------------------------------
546:
547: // Return JD of start of given month/year
548: /**
549: * @stable ICU 2.8
550: */
551: protected int handleComputeMonthStart(int eyear, int month,
552: boolean useMonth) {
553: return (int) monthStart(eyear, month) + 1948439;
554: }
555:
556: //-------------------------------------------------------------------------
557: // Functions for converting from milliseconds to field values
558: //-------------------------------------------------------------------------
559:
560: /**
561: * @stable ICU 2.8
562: */
563: protected int handleGetExtendedYear() {
564: int year;
565: if (newerField(EXTENDED_YEAR, YEAR) == EXTENDED_YEAR) {
566: year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
567: } else {
568: year = internalGet(YEAR, 1); // Default to year 1
569: }
570: return year;
571: }
572:
573: /**
574: * Override Calendar to compute several fields specific to the Islamic
575: * calendar system. These are:
576: *
577: * <ul><li>ERA
578: * <li>YEAR
579: * <li>MONTH
580: * <li>DAY_OF_MONTH
581: * <li>DAY_OF_YEAR
582: * <li>EXTENDED_YEAR</ul>
583: *
584: * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
585: * method is called. The getGregorianXxx() methods return Gregorian
586: * calendar equivalents for the given Julian day.
587: * @stable ICU 2.8
588: */
589: protected void handleComputeFields(int julianDay) {
590: int year, month, dayOfMonth, dayOfYear;
591: long monthStart;
592: long days = julianDay - 1948440;
593:
594: if (civil) {
595: // Use the civil calendar approximation, which is just arithmetic
596: year = (int) Math.floor((30 * days + 10646) / 10631.0);
597: month = (int) Math
598: .ceil((days - 29 - yearStart(year)) / 29.5);
599: month = Math.min(month, 11);
600: monthStart = monthStart(year, month);
601: } else {
602: // Guess at the number of elapsed full months since the epoch
603: int months = (int) Math.floor(days
604: / CalendarAstronomer.SYNODIC_MONTH);
605:
606: monthStart = (long) Math.floor(months
607: * CalendarAstronomer.SYNODIC_MONTH - 1);
608:
609: if (days - monthStart >= 28
610: && moonAge(internalGetTimeInMillis()) > 0) {
611: // If we're near the end of the month, assume next month and search backwards
612: months++;
613: }
614:
615: // Find out the last time that the new moon was actually visible at this longitude
616: // This returns midnight the night that the moon was visible at sunset.
617: while ((monthStart = trueMonthStart(months)) > days) {
618: // If it was after the date in question, back up a month and try again
619: months--;
620: }
621:
622: year = months / 12 + 1;
623: month = months % 12;
624: }
625:
626: dayOfMonth = (int) (days - monthStart(year, month)) + 1;
627:
628: // Now figure out the day of the year.
629: dayOfYear = (int) (days - monthStart(year, 0) + 1);
630:
631: internalSet(ERA, 0);
632: internalSet(YEAR, year);
633: internalSet(EXTENDED_YEAR, year);
634: internalSet(MONTH, month);
635: internalSet(DAY_OF_MONTH, dayOfMonth);
636: internalSet(DAY_OF_YEAR, dayOfYear);
637: }
638:
639: /**
640: * Return the current Calendar type.
641: * @return type of calendar (gregorian, etc.)
642: * @internal ICU 3.0
643: * @deprecated This API is ICU internal only.
644: */
645: public String getType() {
646: return "islamic";
647: }
648:
649: /*
650: private static CalendarFactory factory;
651: public static CalendarFactory factory() {
652: if (factory == null) {
653: factory = new CalendarFactory() {
654: public Calendar create(TimeZone tz, ULocale loc) {
655: return new IslamicCalendar(tz, loc);
656: }
657:
658: public String factoryName() {
659: return "Islamic";
660: }
661: };
662: }
663: return factory;
664: }
665: */
666: }
|