| /* |
| ******************************************************************************* |
| * Copyright (C) 1996-2014, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| |
| package com.ibm.icu.util; |
| |
| import java.util.Date; |
| |
| /** |
| * <b>Note:</b> The Holiday framework is a technology preview. |
| * Despite its age, is still draft API, and clients should treat it as such. |
| * |
| * A Holiday subclass which represents holidays that occur |
| * a fixed number of days before or after Easter. Supports both the |
| * Western and Orthodox methods for calculating Easter. |
| * @draft ICU 2.8 (retainAll) |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public class EasterHoliday extends Holiday |
| { |
| /** |
| * Construct a holiday that falls on Easter Sunday every year |
| * |
| * @param name The name of the holiday |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public EasterHoliday(String name) |
| { |
| super(name, new EasterRule(0, false)); |
| } |
| |
| /** |
| * Construct a holiday that falls a specified number of days before |
| * or after Easter Sunday each year. |
| * |
| * @param daysAfter The number of days before (-) or after (+) Easter |
| * @param name The name of the holiday |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public EasterHoliday(int daysAfter, String name) |
| { |
| super(name, new EasterRule(daysAfter, false)); |
| } |
| |
| /** |
| * Construct a holiday that falls a specified number of days before |
| * or after Easter Sunday each year, using either the Western |
| * or Orthodox calendar. |
| * |
| * @param daysAfter The number of days before (-) or after (+) Easter |
| * @param orthodox Use the Orthodox calendar? |
| * @param name The name of the holiday |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public EasterHoliday(int daysAfter, boolean orthodox, String name) |
| { |
| super(name, new EasterRule(daysAfter, orthodox)); |
| } |
| |
| /** |
| * Shrove Tuesday, aka Mardi Gras, 48 days before Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday SHROVE_TUESDAY = new EasterHoliday(-48, "Shrove Tuesday"); |
| |
| /** |
| * Ash Wednesday, start of Lent, 47 days before Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday ASH_WEDNESDAY = new EasterHoliday(-47, "Ash Wednesday"); |
| |
| /** |
| * Palm Sunday, 7 days before Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday PALM_SUNDAY = new EasterHoliday( -7, "Palm Sunday"); |
| |
| /** |
| * Maundy Thursday, 3 days before Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday MAUNDY_THURSDAY = new EasterHoliday( -3, "Maundy Thursday"); |
| |
| /** |
| * Good Friday, 2 days before Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday GOOD_FRIDAY = new EasterHoliday( -2, "Good Friday"); |
| |
| /** |
| * Easter Sunday |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday EASTER_SUNDAY = new EasterHoliday( 0, "Easter Sunday"); |
| |
| /** |
| * Easter Monday, 1 day after Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday EASTER_MONDAY = new EasterHoliday( 1, "Easter Monday"); |
| |
| /** |
| * Ascension, 39 days after Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday ASCENSION = new EasterHoliday( 39, "Ascension"); |
| |
| /** |
| * Pentecost (aka Whit Sunday), 49 days after Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday PENTECOST = new EasterHoliday( 49, "Pentecost"); |
| |
| /** |
| * Whit Sunday (aka Pentecost), 49 days after Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday WHIT_SUNDAY = new EasterHoliday( 49, "Whit Sunday"); |
| |
| /** |
| * Whit Monday, 50 days after Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday WHIT_MONDAY = new EasterHoliday( 50, "Whit Monday"); |
| |
| /** |
| * Corpus Christi, 60 days after Easter |
| * @draft ICU 2.8 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| static public final EasterHoliday CORPUS_CHRISTI = new EasterHoliday( 60, "Corpus Christi"); |
| } |
| |
| class EasterRule implements DateRule { |
| public EasterRule(int daysAfterEaster, boolean isOrthodox) { |
| this.daysAfterEaster = daysAfterEaster; |
| if (isOrthodox) { |
| orthodox.setGregorianChange(new Date(Long.MAX_VALUE)); |
| calendar = orthodox; |
| } |
| } |
| |
| /** |
| * Return the first occurrance of this rule on or after the given date |
| */ |
| public Date firstAfter(Date start) |
| { |
| return doFirstBetween(start, null); |
| } |
| |
| /** |
| * Return the first occurrance of this rule on or after |
| * the given start date and before the given end date. |
| */ |
| public Date firstBetween(Date start, Date end) |
| { |
| return doFirstBetween(start, end); |
| } |
| |
| /** |
| * Return true if the given Date is on the same day as Easter |
| */ |
| public boolean isOn(Date date) |
| { |
| synchronized(calendar) { |
| calendar.setTime(date); |
| int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR); |
| |
| calendar.setTime(computeInYear(calendar.getTime(), calendar)); |
| |
| return calendar.get(Calendar.DAY_OF_YEAR) == dayOfYear; |
| } |
| } |
| |
| /** |
| * Return true if Easter occurs between the two dates given |
| */ |
| public boolean isBetween(Date start, Date end) |
| { |
| return firstBetween(start, end) != null; // TODO: optimize? |
| } |
| |
| private Date doFirstBetween(Date start, Date end) |
| { |
| //System.out.println("doFirstBetween: start = " + start.toString()); |
| //System.out.println("doFirstBetween: end = " + end.toString()); |
| |
| synchronized(calendar) { |
| // Figure out when this holiday lands in the given year |
| Date result = computeInYear(start, calendar); |
| |
| //System.out.println(" result = " + result.toString()); |
| |
| // We might have gotten a date that's in the same year as "start", but |
| // earlier in the year. If so, go to next year |
| if (result.before(start)) |
| { |
| calendar.setTime(start); |
| calendar.get(Calendar.YEAR); // JDK 1.1.2 bug workaround |
| calendar.add(Calendar.YEAR, 1); |
| |
| //System.out.println(" Result before start, going to next year: " |
| // + calendar.getTime().toString()); |
| |
| result = computeInYear(calendar.getTime(), calendar); |
| //System.out.println(" result = " + result.toString()); |
| } |
| |
| if (end != null && !result.before(end)) { |
| //System.out.println("Result after end, returning null"); |
| return null; |
| } |
| return result; |
| } |
| } |
| |
| /** |
| * Compute the month and date on which this holiday falls in the year |
| * containing the date "date". First figure out which date Easter |
| * lands on in this year, and then add the offset for this holiday to get |
| * the right date. |
| * <p> |
| * The algorithm here is taken from the |
| * <a href="http://www.faqs.org/faqs/calendars/faq/">Calendar FAQ</a>. |
| */ |
| private Date computeInYear(Date date, GregorianCalendar cal) |
| { |
| if (cal == null) cal = calendar; |
| |
| synchronized(cal) { |
| cal.setTime(date); |
| |
| int year = cal.get(Calendar.YEAR); |
| int g = year % 19; // "Golden Number" of year - 1 |
| int i = 0; // # of days from 3/21 to the Paschal full moon |
| int j = 0; // Weekday (0-based) of Paschal full moon |
| |
| if (cal.getTime().after( cal.getGregorianChange())) |
| { |
| // We're past the Gregorian switchover, so use the Gregorian rules. |
| int c = year / 100; |
| int h = (c - c/4 - (8*c+13)/25 + 19*g + 15) % 30; |
| i = h - (h/28)*(1 - (h/28)*(29/(h+1))*((21-g)/11)); |
| j = (year + year/4 + i + 2 - c + c/4) % 7; |
| } |
| else |
| { |
| // Use the old Julian rules. |
| i = (19*g + 15) % 30; |
| j = (year + year/4 + i) % 7; |
| } |
| int l = i - j; |
| int m = 3 + (l+40)/44; // 1-based month in which Easter falls |
| int d = l + 28 - 31*(m/4); // Date of Easter within that month |
| |
| cal.clear(); |
| cal.set(Calendar.ERA, GregorianCalendar.AD); |
| cal.set(Calendar.YEAR, year); |
| cal.set(Calendar.MONTH, m-1); // 0-based |
| cal.set(Calendar.DATE, d); |
| cal.getTime(); // JDK 1.1.2 bug workaround |
| cal.add(Calendar.DATE, daysAfterEaster); |
| |
| return cal.getTime(); |
| } |
| } |
| |
| private static GregorianCalendar gregorian = new GregorianCalendar(/* new SimpleTimeZone(0, "UTC") */); |
| private static GregorianCalendar orthodox = new GregorianCalendar(/* new SimpleTimeZone(0, "UTC") */); |
| |
| private int daysAfterEaster; |
| private GregorianCalendar calendar = gregorian; |
| } |