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) 2007-2010, International Business Machines Corporation and    *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9package com.ibm.icu.util;
10import java.util.Date;
11
12import com.ibm.icu.impl.Grego;
13
14
15/**
16 * <code>AnnualTimeZoneRule</code> is a class used for representing a time zone
17 * rule which takes effect annually.  Years used in this class are
18 * all Gregorian calendar years.
19 *
20 * @stable ICU 3.8
21 */
22public class AnnualTimeZoneRule extends TimeZoneRule {
23
24    private static final long serialVersionUID = -8870666707791230688L;
25
26    /**
27     * The constant representing the maximum year used for designating a rule is permanent.
28     * @stable ICU 3.8
29     */
30    public static final int MAX_YEAR = Integer.MAX_VALUE;
31
32    private final DateTimeRule dateTimeRule;
33    private final int startYear;
34    private final int endYear;
35
36    /**
37     * Constructs a <code>AnnualTimeZoneRule</code> with the name, the GMT offset of its
38     * standard time, the amount of daylight saving offset adjustment,
39     * the annual start time rule and the start/until years.
40     *
41     * @param name          The time zone name.
42     * @param rawOffset     The GMT offset of its standard time in milliseconds.
43     * @param dstSavings    The amount of daylight saving offset adjustment in
44     *                      milliseconds.  If this ia a rule for standard time,
45     *                      the value of this argument is 0.
46     * @param dateTimeRule  The start date/time rule repeated annually.
47     * @param startYear     The first year when this rule takes effect.
48     * @param endYear       The last year when this rule takes effect.  If this
49     *                      rule is effective forever in future, specify MAX_YEAR.
50     *
51     * @stable ICU 3.8
52     */
53    public AnnualTimeZoneRule(String name, int rawOffset, int dstSavings,
54            DateTimeRule dateTimeRule, int startYear, int endYear) {
55        super(name, rawOffset, dstSavings);
56        this.dateTimeRule = dateTimeRule;
57        this.startYear = startYear;
58        this.endYear = endYear;
59    }
60
61    /**
62     * Gets the start date/time rule associated used by this rule.
63     *
64     * @return  An <code>AnnualDateTimeRule</code> which represents the start date/time
65     *          rule used by this time zone rule.
66     *
67     * @stable ICU 3.8
68     */
69    public DateTimeRule getRule() {
70        return dateTimeRule;
71    }
72
73    /**
74     * Gets the first year when this rule takes effect.
75     *
76     * @return  The start year of this rule.  The year is in Gregorian calendar
77     *          with 0 == 1 BCE, -1 == 2 BCE, etc.
78     *
79     * @stable ICU 3.8
80     */
81    public int getStartYear() {
82        return startYear;
83    }
84
85    /**
86     * Gets the end year when this rule takes effect.
87     *
88     * @return  The end year of this rule (inclusive). The year is in Gregorian calendar
89     *          with 0 == 1 BCE, -1 == 2 BCE, etc.
90     *
91     * @stable ICU 3.8
92     */
93    public int getEndYear() {
94        return endYear;
95    }
96
97    /**
98     * Gets the time when this rule takes effect in the given year.
99     *
100     * @param year              The Gregorian year, with 0 == 1 BCE, -1 == 2 BCE, etc.
101     * @param prevRawOffset     The standard time offset from UTC before this rule
102     *                          takes effect in milliseconds.
103     * @param prevDSTSavings    The amount of daylight saving offset from the
104     *                          standard time.
105     *
106     * @return  The time when this rule takes effect in the year, or
107     *          null if this rule is not applicable in the year.
108     *
109     * @stable ICU 3.8
110     */
111    public Date getStartInYear(int year, int prevRawOffset, int prevDSTSavings) {
112        if (year < startYear || year > endYear) {
113            return null;
114        }
115
116        long ruleDay;
117        int type = dateTimeRule.getDateRuleType();
118
119        if (type == DateTimeRule.DOM) {
120            ruleDay = Grego.fieldsToDay(year, dateTimeRule.getRuleMonth(), dateTimeRule.getRuleDayOfMonth());
121        } else {
122            boolean after = true;
123            if (type == DateTimeRule.DOW) {
124                int weeks = dateTimeRule.getRuleWeekInMonth();
125                if (weeks > 0) {
126                    ruleDay = Grego.fieldsToDay(year, dateTimeRule.getRuleMonth(), 1);
127                    ruleDay += 7 * (weeks - 1);
128                } else {
129                    after = false;
130                    ruleDay = Grego.fieldsToDay(year, dateTimeRule.getRuleMonth(),
131                            Grego.monthLength(year, dateTimeRule.getRuleMonth()));
132                    ruleDay += 7 * (weeks + 1);
133                }
134            } else {
135                int month = dateTimeRule.getRuleMonth();
136                int dom = dateTimeRule.getRuleDayOfMonth();
137                if (type == DateTimeRule.DOW_LEQ_DOM) {
138                    after = false;
139                    // Handle Feb <=29
140                    if (month == Calendar.FEBRUARY && dom == 29 && !Grego.isLeapYear(year)) {
141                        dom--;
142                    }
143                }
144                ruleDay = Grego.fieldsToDay(year, month, dom);
145            }
146
147            int dow = Grego.dayOfWeek(ruleDay);
148            int delta = dateTimeRule.getRuleDayOfWeek() - dow;
149            if (after) {
150                delta = delta < 0 ? delta + 7 : delta;
151            } else {
152                delta = delta > 0 ? delta - 7 : delta;
153            }
154            ruleDay += delta;
155        }
156
157        long ruleTime = ruleDay * Grego.MILLIS_PER_DAY + dateTimeRule.getRuleMillisInDay();
158        if (dateTimeRule.getTimeRuleType() != DateTimeRule.UTC_TIME) {
159            ruleTime -= prevRawOffset;
160        }
161        if (dateTimeRule.getTimeRuleType() == DateTimeRule.WALL_TIME) {
162            ruleTime -= prevDSTSavings;
163        }
164        return new Date(ruleTime);
165    }
166
167    /**
168     * {@inheritDoc}
169     * @stable ICU 3.8
170     */
171    @Override
172    public Date getFirstStart(int prevRawOffset, int prevDSTSavings) {
173        return getStartInYear(startYear, prevRawOffset, prevDSTSavings);
174    }
175
176    /**
177     * {@inheritDoc}
178     * @stable ICU 3.8
179     */
180    @Override
181    public Date getFinalStart(int prevRawOffset, int prevDSTSavings) {
182        if (endYear == MAX_YEAR) {
183            return null;
184        }
185        return getStartInYear(endYear, prevRawOffset, prevDSTSavings);
186    }
187
188    /**
189     * {@inheritDoc}
190     * @stable ICU 3.8
191     */
192    @Override
193    public Date getNextStart(long base, int prevRawOffset, int prevDSTSavings, boolean inclusive) {
194        int[] fields = Grego.timeToFields(base, null);
195        int year = fields[0];
196        if (year < startYear) {
197            return getFirstStart(prevRawOffset, prevDSTSavings);
198        }
199        Date d = getStartInYear(year, prevRawOffset, prevDSTSavings);
200        if (d != null && (d.getTime() < base || (!inclusive && (d.getTime() == base)))) {
201            d = getStartInYear(year + 1, prevRawOffset, prevDSTSavings);
202        }
203        return d;
204    }
205
206    /**
207     * {@inheritDoc}
208     * @stable ICU 3.8
209     */
210    @Override
211    public Date getPreviousStart(long base, int prevRawOffset, int prevDSTSavings, boolean inclusive) {
212        int[] fields = Grego.timeToFields(base, null);
213        int year = fields[0];
214        if (year > endYear) {
215            return getFinalStart(prevRawOffset, prevDSTSavings);
216        }
217        Date d = getStartInYear(year, prevRawOffset, prevDSTSavings);
218        if (d != null && (d.getTime() > base || (!inclusive && (d.getTime() == base)))) {
219            d = getStartInYear(year - 1, prevRawOffset, prevDSTSavings);
220        }
221        return d;
222    }
223
224    /**
225     * {@inheritDoc}
226     * @stable ICU 3.8
227     */
228    @Override
229    public boolean isEquivalentTo(TimeZoneRule other) {
230        if (!(other instanceof AnnualTimeZoneRule)) {
231            return false;
232        }
233        AnnualTimeZoneRule otherRule = (AnnualTimeZoneRule)other;
234        if (startYear == otherRule.startYear
235                && endYear == otherRule.endYear
236                && dateTimeRule.equals(otherRule.dateTimeRule)) {
237            return super.isEquivalentTo(other);
238        }
239        return false;
240    }
241
242    /**
243     * {@inheritDoc}<br><br>
244     * Note: This method in <code>AnnualTimeZoneRule</code> always returns true.
245     * @stable ICU 3.8
246     */
247    @Override
248    public boolean isTransitionRule() {
249        return true;
250    }
251
252    /**
253     * Returns a <code>String</code> representation of this <code>AnnualTimeZoneRule</code> object.
254     * This method is used for debugging purpose only.  The string representation can be changed
255     * in future version of ICU without any notice.
256     *
257     * @stable ICU 3.8
258     */
259    @Override
260    public String toString() {
261        StringBuilder buf = new StringBuilder();
262        buf.append(super.toString());
263        buf.append(", rule={" + dateTimeRule + "}");
264        buf.append(", startYear=" + startYear);
265        buf.append(", endYear=");
266        if (endYear == MAX_YEAR) {
267            buf.append("max");
268        } else {
269            buf.append(endYear);
270        }
271        return buf.toString();
272    }
273}
274