001: /*
002: *******************************************************************************
003: * Copyright (C) 1996-2006, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: */
007:
008: package com.ibm.icu.util;
009:
010: import java.util.Date;
011:
012: /**
013: * A Holiday subclass which represents holidays that occur
014: * a fixed number of days before or after Easter. Supports both the
015: * Western and Orthodox methods for calculating Easter.
016: * @draft ICU 2.8 (retainAll)
017: * @provisional This API might change or be removed in a future release.
018: */
019: public class EasterHoliday extends Holiday {
020: /**
021: * Construct a holiday that falls on Easter Sunday every year
022: *
023: * @param name The name of the holiday
024: * @draft ICU 2.8
025: * @provisional This API might change or be removed in a future release.
026: */
027: public EasterHoliday(String name) {
028: super (name, new EasterRule(0, false));
029: }
030:
031: /**
032: * Construct a holiday that falls a specified number of days before
033: * or after Easter Sunday each year.
034: *
035: * @param daysAfter The number of days before (-) or after (+) Easter
036: * @param name The name of the holiday
037: * @draft ICU 2.8
038: * @provisional This API might change or be removed in a future release.
039: */
040: public EasterHoliday(int daysAfter, String name) {
041: super (name, new EasterRule(daysAfter, false));
042: }
043:
044: /**
045: * Construct a holiday that falls a specified number of days before
046: * or after Easter Sunday each year, using either the Western
047: * or Orthodox calendar.
048: *
049: * @param daysAfter The number of days before (-) or after (+) Easter
050: * @param orthodox Use the Orthodox calendar?
051: * @param name The name of the holiday
052: * @draft ICU 2.8
053: * @provisional This API might change or be removed in a future release.
054: */
055: public EasterHoliday(int daysAfter, boolean orthodox, String name) {
056: super (name, new EasterRule(daysAfter, orthodox));
057: }
058:
059: /**
060: * Shrove Tuesday, aka Mardi Gras, 48 days before Easter
061: * @draft ICU 2.8
062: * @provisional This API might change or be removed in a future release.
063: */
064: static public final EasterHoliday SHROVE_TUESDAY = new EasterHoliday(
065: -48, "Shrove Tuesday");
066:
067: /**
068: * Ash Wednesday, start of Lent, 47 days before Easter
069: * @draft ICU 2.8
070: * @provisional This API might change or be removed in a future release.
071: */
072: static public final EasterHoliday ASH_WEDNESDAY = new EasterHoliday(
073: -47, "Ash Wednesday");
074:
075: /**
076: * Palm Sunday, 7 days before Easter
077: * @draft ICU 2.8
078: * @provisional This API might change or be removed in a future release.
079: */
080: static public final EasterHoliday PALM_SUNDAY = new EasterHoliday(
081: -7, "Palm Sunday");
082:
083: /**
084: * Maundy Thursday, 3 days before Easter
085: * @draft ICU 2.8
086: * @provisional This API might change or be removed in a future release.
087: */
088: static public final EasterHoliday MAUNDY_THURSDAY = new EasterHoliday(
089: -3, "Maundy Thursday");
090:
091: /**
092: * Good Friday, 2 days before Easter
093: * @draft ICU 2.8
094: * @provisional This API might change or be removed in a future release.
095: */
096: static public final EasterHoliday GOOD_FRIDAY = new EasterHoliday(
097: -2, "Good Friday");
098:
099: /**
100: * Easter Sunday
101: * @draft ICU 2.8
102: * @provisional This API might change or be removed in a future release.
103: */
104: static public final EasterHoliday EASTER_SUNDAY = new EasterHoliday(
105: 0, "Easter Sunday");
106:
107: /**
108: * Easter Monday, 1 day after Easter
109: * @draft ICU 2.8
110: * @provisional This API might change or be removed in a future release.
111: */
112: static public final EasterHoliday EASTER_MONDAY = new EasterHoliday(
113: 1, "Easter Monday");
114:
115: /**
116: * Ascension, 39 days after Easter
117: * @draft ICU 2.8
118: * @provisional This API might change or be removed in a future release.
119: */
120: static public final EasterHoliday ASCENSION = new EasterHoliday(39,
121: "Ascension");
122:
123: /**
124: * Pentecost (aka Whit Sunday), 49 days after Easter
125: * @draft ICU 2.8
126: * @provisional This API might change or be removed in a future release.
127: */
128: static public final EasterHoliday PENTECOST = new EasterHoliday(49,
129: "Pentecost");
130:
131: /**
132: * Whit Sunday (aka Pentecost), 49 days after Easter
133: * @draft ICU 2.8
134: * @provisional This API might change or be removed in a future release.
135: */
136: static public final EasterHoliday WHIT_SUNDAY = new EasterHoliday(
137: 49, "Whit Sunday");
138:
139: /**
140: * Whit Monday, 50 days after Easter
141: * @draft ICU 2.8
142: * @provisional This API might change or be removed in a future release.
143: */
144: static public final EasterHoliday WHIT_MONDAY = new EasterHoliday(
145: 50, "Whit Monday");
146:
147: /**
148: * Corpus Christi, 60 days after Easter
149: * @draft ICU 2.8
150: * @provisional This API might change or be removed in a future release.
151: */
152: static public final EasterHoliday CORPUS_CHRISTI = new EasterHoliday(
153: 60, "Corpus Christi");
154: }
155:
156: class EasterRule implements DateRule {
157: public EasterRule(int daysAfterEaster, boolean isOrthodox) {
158: this .daysAfterEaster = daysAfterEaster;
159: if (isOrthodox) {
160: orthodox.setGregorianChange(new Date(Long.MAX_VALUE));
161: calendar = orthodox;
162: }
163: }
164:
165: /**
166: * Return the first occurrance of this rule on or after the given date
167: */
168: public Date firstAfter(Date start) {
169: return doFirstBetween(start, null);
170: }
171:
172: /**
173: * Return the first occurrance of this rule on or after
174: * the given start date and before the given end date.
175: */
176: public Date firstBetween(Date start, Date end) {
177: return doFirstBetween(start, end);
178: }
179:
180: /**
181: * Return true if the given Date is on the same day as Easter
182: */
183: public boolean isOn(Date date) {
184: synchronized (calendar) {
185: calendar.setTime(date);
186: int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
187:
188: calendar
189: .setTime(computeInYear(calendar.getTime(), calendar));
190:
191: return calendar.get(Calendar.DAY_OF_YEAR) == dayOfYear;
192: }
193: }
194:
195: /**
196: * Return true if Easter occurs between the two dates given
197: */
198: public boolean isBetween(Date start, Date end) {
199: return firstBetween(start, end) != null; // TODO: optimize?
200: }
201:
202: private Date doFirstBetween(Date start, Date end) {
203: //System.out.println("doFirstBetween: start = " + start.toString());
204: //System.out.println("doFirstBetween: end = " + end.toString());
205:
206: synchronized (calendar) {
207: // Figure out when this holiday lands in the given year
208: Date result = computeInYear(start, calendar);
209:
210: //System.out.println(" result = " + result.toString());
211:
212: // We might have gotten a date that's in the same year as "start", but
213: // earlier in the year. If so, go to next year
214: if (result.before(start)) {
215: calendar.setTime(start);
216: calendar.get(Calendar.YEAR); // JDK 1.1.2 bug workaround
217: calendar.add(Calendar.YEAR, 1);
218:
219: //System.out.println(" Result before start, going to next year: "
220: // + calendar.getTime().toString());
221:
222: result = computeInYear(calendar.getTime(), calendar);
223: //System.out.println(" result = " + result.toString());
224: }
225:
226: if (end != null && result.after(end)) {
227: //System.out.println("Result after end, returning null");
228: return null;
229: }
230: return result;
231: }
232: }
233:
234: /**
235: * Compute the month and date on which this holiday falls in the year
236: * containing the date "date". First figure out which date Easter
237: * lands on in this year, and then add the offset for this holiday to get
238: * the right date.
239: * <p>
240: * The algorithm here is taken from the
241: * <a href="http://www.faqs.org/faqs/calendars/faq/">Calendar FAQ</a>.
242: */
243: private Date computeInYear(Date date, GregorianCalendar cal) {
244: if (cal == null)
245: cal = calendar;
246:
247: synchronized (cal) {
248: cal.setTime(date);
249:
250: int year = cal.get(Calendar.YEAR);
251: int g = year % 19; // "Golden Number" of year - 1
252: int i = 0; // # of days from 3/21 to the Paschal full moon
253: int j = 0; // Weekday (0-based) of Paschal full moon
254:
255: if (cal.getTime().after(cal.getGregorianChange())) {
256: // We're past the Gregorian switchover, so use the Gregorian rules.
257: int c = year / 100;
258: int h = (c - c / 4 - (8 * c + 13) / 25 + 19 * g + 15) % 30;
259: i = h
260: - (h / 28)
261: * (1 - (h / 28) * (29 / (h + 1))
262: * ((21 - g) / 11));
263: j = (year + year / 4 + i + 2 - c + c / 4) % 7;
264: } else {
265: // Use the old Julian rules.
266: i = (19 * g + 15) % 30;
267: j = (year + year / 4 + i) % 7;
268: }
269: int l = i - j;
270: int m = 3 + (l + 40) / 44; // 1-based month in which Easter falls
271: int d = l + 28 - 31 * (m / 4); // Date of Easter within that month
272:
273: cal.clear();
274: cal.set(Calendar.ERA, GregorianCalendar.AD);
275: cal.set(Calendar.YEAR, year);
276: cal.set(Calendar.MONTH, m - 1); // 0-based
277: cal.set(Calendar.DATE, d);
278: cal.getTime(); // JDK 1.1.2 bug workaround
279: cal.add(Calendar.DATE, daysAfterEaster);
280:
281: return cal.getTime();
282: }
283: }
284:
285: private static GregorianCalendar gregorian = new GregorianCalendar(/* new SimpleTimeZone(0, "UTC") */);
286: private static GregorianCalendar orthodox = new GregorianCalendar(/* new SimpleTimeZone(0, "UTC") */);
287:
288: private int daysAfterEaster;
289: private GregorianCalendar calendar = gregorian;
290: }
|