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