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