1/*
2 *******************************************************************************
3 * Copyright (C) 1996-2010, International Business Machines Corporation and    *
4 * others. All Rights Reserved.                                                *
5 *******************************************************************************
6 */
7
8package com.ibm.icu.util;
9
10import java.util.Date;
11
12/**
13 * <b>Note:</b> The Holiday framework is a technology preview.
14 * Despite its age, is still draft API, and clients should treat it as such.
15 *
16 * Simple implementation of DateRule.
17 * @draft ICU 2.8 (retainAll)
18 * @provisional This API might change or be removed in a future release.
19 */
20public class SimpleDateRule implements DateRule
21{
22    /**
23     * Construct a rule for a fixed date within a month
24     *
25     * @param month         The month in which this rule occurs (0-based).
26     * @param dayOfMonth    The date in that month (1-based).
27     * @draft ICU 2.8
28     * @provisional This API might change or be removed in a future release.
29     */
30    public SimpleDateRule(int month, int dayOfMonth)
31    {
32        this.month      = month;
33        this.dayOfMonth = dayOfMonth;
34        this.dayOfWeek  = 0;
35    }
36
37    // temporary
38    /* package */SimpleDateRule(int month, int dayOfMonth, Calendar cal)
39    {
40        this.month      = month;
41        this.dayOfMonth = dayOfMonth;
42        this.dayOfWeek  = 0;
43        this.calendar   = cal;
44    }
45
46    /**
47     * Construct a rule for a weekday within a month, e.g. the first Monday.
48     *
49     * @param month         The month in which this rule occurs (0-based).
50     * @param dayOfMonth    A date within that month (1-based).
51     * @param dayOfWeek     The day of the week on which this rule occurs.
52     * @param after         If true, this rule selects the first dayOfWeek
53     *                      on or after dayOfMonth.  If false, the rule selects
54     *                      the first dayOfWeek on or before dayOfMonth.
55     * @draft ICU 2.8
56     * @provisional This API might change or be removed in a future release.
57     */
58    public SimpleDateRule(int month, int dayOfMonth, int dayOfWeek, boolean after)
59    {
60        this.month      = month;
61        this.dayOfMonth = dayOfMonth;
62        this.dayOfWeek  = after ? dayOfWeek : -dayOfWeek;
63    }
64
65    /**
66     * Return the first occurrance of the event represented by this rule
67     * that is on or after the given start date.
68     *
69     * @param start Only occurrances on or after this date are returned.
70     *
71     * @return      The date on which this event occurs, or null if it
72     *              does not occur on or after the start date.
73     *
74     * @see #firstBetween
75     * @draft ICU 2.8
76     * @provisional This API might change or be removed in a future release.
77     */
78    public Date firstAfter(Date start)
79    {
80        return doFirstBetween(start, null);
81    }
82
83    /**
84     * Return the first occurrance of the event represented by this rule
85     * that is on or after the given start date and before the given
86     * end date.
87     *
88     * @param start Only occurrances on or after this date are returned.
89     * @param end   Only occurrances before this date are returned.
90     *
91     * @return      The date on which this event occurs, or null if it
92     *              does not occur between the start and end dates.
93     *
94     * @see #firstAfter
95     * @draft ICU 2.8
96     * @provisional This API might change or be removed in a future release.
97     */
98    public Date firstBetween(Date start, Date end)
99    {
100        // Pin to the min/max dates for this rule
101        return doFirstBetween(start, end);
102    }
103
104    /**
105     * Checks whether this event occurs on the given date.  This does
106     * <em>not</em> take time of day into account; instead it checks
107     * whether this event and the given date are on the same day.
108     * This is useful for applications such as determining whether a given
109     * day is a holiday.
110     *
111     * @param date  The date to check.
112     * @return      true if this event occurs on the given date.
113     * @draft ICU 2.8
114     * @provisional This API might change or be removed in a future release.
115     */
116    public boolean isOn(Date date)
117    {
118        Calendar c = calendar;
119
120        synchronized(c) {
121            c.setTime(date);
122
123            int dayOfYear = c.get(Calendar.DAY_OF_YEAR);
124
125            c.setTime(computeInYear(c.get(Calendar.YEAR), c));
126
127//              System.out.println("  isOn: dayOfYear = " + dayOfYear);
128//              System.out.println("        holiday   = " + c.get(Calendar.DAY_OF_YEAR));
129
130            return c.get(Calendar.DAY_OF_YEAR) == dayOfYear;
131        }
132    }
133
134    /**
135     * Check whether this event occurs at least once between the two
136     * dates given.
137     * @draft ICU 2.8
138     * @provisional This API might change or be removed in a future release.
139     */
140    public boolean isBetween(Date start, Date end)
141    {
142        return firstBetween(start, end) != null; // TODO: optimize?
143    }
144
145    private Date doFirstBetween(Date start, Date end)
146    {
147        Calendar c = calendar;
148
149        synchronized(c) {
150            c.setTime(start);
151
152            int year = c.get(Calendar.YEAR);
153            int mon = c.get(Calendar.MONTH);
154
155            // If the rule is earlier in the year than the start date
156            // we have to go to the next year.
157            if (mon > this.month) {
158                year++;
159            }
160
161            // Figure out when the rule lands in the given year
162            Date result = computeInYear(year, c);
163
164            // If the rule is in the same month as the start date, it's possible
165            // to get a result that's before the start.  If so, go to next year.
166            if (mon == this.month && result.before(start)) {
167                result = computeInYear(year+1, c);
168            }
169
170            if (end != null && result.after(end)) {
171                return null;
172            }
173            return result;
174        }
175    }
176
177    private Date computeInYear(int year, Calendar c)
178    {
179        synchronized(c) {
180            c.clear();
181            c.set(Calendar.ERA, c.getMaximum(Calendar.ERA));
182            c.set(Calendar.YEAR, year);
183            c.set(Calendar.MONTH, month);
184            c.set(Calendar.DATE, dayOfMonth);
185
186            //System.out.println("     computeInYear: start at " + c.getTime().toString());
187
188            if (dayOfWeek != 0) {
189                c.setTime(c.getTime());        // JDK 1.1.2 workaround
190                int weekday = c.get(Calendar.DAY_OF_WEEK);
191
192                //System.out.println("                    weekday = " + weekday);
193                //System.out.println("                    dayOfYear = " + c.get(Calendar.DAY_OF_YEAR));
194
195                int delta = 0;
196                if (dayOfWeek > 0) {
197                    // We want the first occurrance of the given day of the week
198                    // on or after the specified date in the month.
199                    delta = (dayOfWeek - weekday + 7) % 7;
200                }
201                else {
202                    // We want the first occurrance of the (-dayOfWeek)
203                    // on or before the specified date in the month.
204                    delta = -((dayOfWeek + weekday + 7) % 7);
205                }
206                //System.out.println("                    adding " + delta + " days");
207                c.add(Calendar.DATE, delta);
208            }
209
210            return c.getTime();
211        }
212    }
213
214    /**
215     * @draft ICU 2.8
216     * @provisional This API might change or be removed in a future release.
217     */
218//    public void setCalendar(Calendar c) {
219//        calendar = c;
220//    }
221
222    private static GregorianCalendar gCalendar = new GregorianCalendar();
223
224    private Calendar calendar = gCalendar;
225
226    private int     month;
227    private int     dayOfMonth;
228    private int     dayOfWeek;
229}
230