17935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
27935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
37935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Copyright (C) 1996-2014, International Business Machines Corporation and    *
47935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * others. All Rights Reserved.                                                *
57935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
67935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
77935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
87935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpackage com.ibm.icu.util;
97935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Date;
117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/**
137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <b>Note:</b> The Holiday framework is a technology preview.
147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Despite its age, is still draft API, and clients should treat it as such.
157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * A Holiday subclass which represents holidays that occur
177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * a fixed number of days before or after Easter.  Supports both the
187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Western and Orthodox methods for calculating Easter.
197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @draft ICU 2.8 (retainAll)
207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @provisional This API might change or be removed in a future release.
217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpublic class EasterHoliday extends Holiday
237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert{
247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Construct a holiday that falls on Easter Sunday every year
267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param name The name of the holiday
287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public EasterHoliday(String name)
327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        super(name, new EasterRule(0, false));
347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Construct a holiday that falls a specified number of days before
387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * or after Easter Sunday each year.
397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param daysAfter The number of days before (-) or after (+) Easter
417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param name      The name of the holiday
427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public EasterHoliday(int daysAfter, String name)
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        super(name, new EasterRule(daysAfter, false));
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Construct a holiday that falls a specified number of days before
527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * or after Easter Sunday each year, using either the Western
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * or Orthodox calendar.
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param daysAfter The number of days before (-) or after (+) Easter
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param orthodox  Use the Orthodox calendar?
577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param name      The name of the holiday
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public EasterHoliday(int daysAfter, boolean orthodox, String name)
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        super(name, new EasterRule(daysAfter, orthodox));
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Shrove Tuesday, aka Mardi Gras, 48 days before Easter
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday SHROVE_TUESDAY  = new EasterHoliday(-48,    "Shrove Tuesday");
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Ash Wednesday, start of Lent, 47 days before Easter
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday ASH_WEDNESDAY   = new EasterHoliday(-47,    "Ash Wednesday");
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Palm Sunday, 7 days before Easter
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday PALM_SUNDAY     = new EasterHoliday( -7,    "Palm Sunday");
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Maundy Thursday, 3 days before Easter
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday MAUNDY_THURSDAY = new EasterHoliday( -3,    "Maundy Thursday");
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Good Friday, 2 days before Easter
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday GOOD_FRIDAY     = new EasterHoliday( -2,    "Good Friday");
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Easter Sunday
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday EASTER_SUNDAY   = new EasterHoliday(  0,    "Easter Sunday");
1077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Easter Monday, 1 day after Easter
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday EASTER_MONDAY   = new EasterHoliday(  1,    "Easter Monday");
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Ascension, 39 days after Easter
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday ASCENSION       = new EasterHoliday( 39,    "Ascension");
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Pentecost (aka Whit Sunday), 49 days after Easter
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday PENTECOST       = new EasterHoliday( 49,    "Pentecost");
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Whit Sunday (aka Pentecost), 49 days after Easter
1317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday WHIT_SUNDAY     = new EasterHoliday( 49,    "Whit Sunday");
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Whit Monday, 50 days after Easter
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday WHIT_MONDAY     = new EasterHoliday( 50,    "Whit Monday");
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Corpus Christi, 60 days after Easter
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @draft ICU 2.8
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @provisional This API might change or be removed in a future release.
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static public final EasterHoliday CORPUS_CHRISTI  = new EasterHoliday( 60,    "Corpus Christi");
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertclass EasterRule implements DateRule {
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public EasterRule(int daysAfterEaster, boolean isOrthodox) {
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.daysAfterEaster = daysAfterEaster;
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (isOrthodox) {
1557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            orthodox.setGregorianChange(new Date(Long.MAX_VALUE));
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            calendar = orthodox;
1577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Return the first occurrance of this rule on or after the given date
1627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Date firstAfter(Date start)
1647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
1657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return doFirstBetween(start, null);
1667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Return the first occurrance of this rule on or after
1707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the given start date and before the given end date.
1717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Date firstBetween(Date start, Date end)
1737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
1747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return doFirstBetween(start, end);
1757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Return true if the given Date is on the same day as Easter
1797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public boolean isOn(Date date)
1817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
1827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        synchronized(calendar) {
1837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            calendar.setTime(date);
1847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
1857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            calendar.setTime(computeInYear(calendar.getTime(), calendar));
1877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return calendar.get(Calendar.DAY_OF_YEAR) == dayOfYear;
1897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Return true if Easter occurs between the two dates given
1947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public boolean isBetween(Date start, Date end)
1967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
1977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return firstBetween(start, end) != null; // TODO: optimize?
1987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private Date doFirstBetween(Date start, Date end)
2017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
2027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        //System.out.println("doFirstBetween: start   = " + start.toString());
2037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        //System.out.println("doFirstBetween: end     = " + end.toString());
2047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        synchronized(calendar) {
2067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Figure out when this holiday lands in the given year
2077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Date result = computeInYear(start, calendar);
2087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         //System.out.println("                result  = " + result.toString());
2107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // We might have gotten a date that's in the same year as "start", but
2127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // earlier in the year.  If so, go to next year
2137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (result.before(start))
2147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            {
2157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                calendar.setTime(start);
2167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                calendar.get(Calendar.YEAR);    // JDK 1.1.2 bug workaround
2177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                calendar.add(Calendar.YEAR, 1);
2187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                //System.out.println("                Result before start, going to next year: "
2207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                //                        + calendar.getTime().toString());
2217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result = computeInYear(calendar.getTime(), calendar);
2237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                //System.out.println("                result  = " + result.toString());
2247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
226f716bda031dccdec5e47bb40e758c5901d209729Fredrik Roubert            if (end != null && !result.before(end)) {
2277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                //System.out.println("Result after end, returning null");
2287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return null;
2297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return result;
2317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Compute the month and date on which this holiday falls in the year
2367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * containing the date "date".  First figure out which date Easter
2377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * lands on in this year, and then add the offset for this holiday to get
2387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the right date.
2397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
2407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The algorithm here is taken from the
2417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <a href="http://www.faqs.org/faqs/calendars/faq/">Calendar FAQ</a>.
2427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private Date computeInYear(Date date, GregorianCalendar cal)
2447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
2457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (cal == null) cal = calendar;
2467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        synchronized(cal) {
2487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cal.setTime(date);
2497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int year = cal.get(Calendar.YEAR);
2517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int g = year % 19;  // "Golden Number" of year - 1
2527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int i = 0;          // # of days from 3/21 to the Paschal full moon
2537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int j = 0;          // Weekday (0-based) of Paschal full moon
2547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (cal.getTime().after( cal.getGregorianChange()))
2567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            {
2577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // We're past the Gregorian switchover, so use the Gregorian rules.
2587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int c = year / 100;
2597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int h = (c - c/4 - (8*c+13)/25 + 19*g + 15) % 30;
2607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                i = h - (h/28)*(1 - (h/28)*(29/(h+1))*((21-g)/11));
2617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                j = (year + year/4 + i + 2 - c + c/4) % 7;
2627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            else
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            {
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Use the old Julian rules.
2667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                i = (19*g + 15) % 30;
2677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                j = (year + year/4 + i) % 7;
2687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int l = i - j;
2707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int m = 3 + (l+40)/44;              // 1-based month in which Easter falls
2717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int d = l + 28 - 31*(m/4);          // Date of Easter within that month
2727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cal.clear();
2747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cal.set(Calendar.ERA, GregorianCalendar.AD);
2757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cal.set(Calendar.YEAR, year);
2767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cal.set(Calendar.MONTH, m-1);       // 0-based
2777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cal.set(Calendar.DATE, d);
2787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cal.getTime();                      // JDK 1.1.2 bug workaround
2797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cal.add(Calendar.DATE, daysAfterEaster);
2807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return cal.getTime();
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static GregorianCalendar gregorian = new GregorianCalendar(/* new SimpleTimeZone(0, "UTC") */);
2867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static GregorianCalendar orthodox = new GregorianCalendar(/* new SimpleTimeZone(0, "UTC") */);
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private int               daysAfterEaster;
2897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private GregorianCalendar calendar = gregorian;
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
291