GregorianCalendar.java revision 4eb6dd5362871b00084b2fe0cfb4f4b87f052d4c
1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.util;
19
20import java.io.IOException;
21import java.io.ObjectInputStream;
22import java.io.ObjectOutputStream;
23
24/**
25 * {@code GregorianCalendar} is a concrete subclass of {@link Calendar}
26 * and provides the standard calendar used by most of the world.
27 *
28 * <p>
29 * The standard (Gregorian) calendar has 2 eras, BC and AD.
30 *
31 * <p>
32 * This implementation handles a single discontinuity, which corresponds by
33 * default to the date the Gregorian calendar was instituted (October 15, 1582
34 * in some countries, later in others). The cutover date may be changed by the
35 * caller by calling {@code setGregorianChange()}.
36 *
37 * <p>
38 * Historically, in those countries which adopted the Gregorian calendar first,
39 * October 4, 1582 was thus followed by October 15, 1582. This calendar models
40 * this correctly. Before the Gregorian cutover, {@code GregorianCalendar}
41 * implements the Julian calendar. The only difference between the Gregorian and
42 * the Julian calendar is the leap year rule. The Julian calendar specifies leap
43 * years every four years, whereas the Gregorian calendar omits century years
44 * which are not divisible by 400.
45 *
46 * <p>
47 * {@code GregorianCalendar} implements <em>proleptic</em> Gregorian
48 * and Julian calendars. That is, dates are computed by extrapolating the
49 * current rules indefinitely far backward and forward in time. As a result,
50 * {@code GregorianCalendar} may be used for all years to generate
51 * meaningful and consistent results. However, dates obtained using
52 * {@code GregorianCalendar} are historically accurate only from March 1,
53 * 4 AD onward, when modern Julian calendar rules were adopted. Before this
54 * date, leap year rules were applied irregularly, and before 45 BC the Julian
55 * calendar did not even exist.
56 *
57 * <p>
58 * Prior to the institution of the Gregorian calendar, New Year's Day was March
59 * 25. To avoid confusion, this calendar always uses January 1. A manual
60 * adjustment may be made if desired for dates that are prior to the Gregorian
61 * changeover and which fall between January 1 and March 24.
62 *
63 * <p>
64 * Values calculated for the {@code WEEK_OF_YEAR} field range from 1 to
65 * 53. Week 1 for a year is the earliest seven day period starting on
66 * {@code getFirstDayOfWeek()} that contains at least
67 * {@code getMinimalDaysInFirstWeek()} days from that year. It thus
68 * depends on the values of {@code getMinimalDaysInFirstWeek()},
69 * {@code getFirstDayOfWeek()}, and the day of the week of January 1.
70 * Weeks between week 1 of one year and week 1 of the following year are
71 * numbered sequentially from 2 to 52 or 53 (as needed).
72 *
73 * <p>
74 * For example, January 1, 1998 was a Thursday. If
75 * {@code getFirstDayOfWeek()} is {@code MONDAY} and
76 * {@code getMinimalDaysInFirstWeek()} is 4 (these are the values
77 * reflecting ISO 8601 and many national standards), then week 1 of 1998 starts
78 * on December 29, 1997, and ends on January 4, 1998. If, however,
79 * {@code getFirstDayOfWeek()} is {@code SUNDAY}, then week 1 of
80 * 1998 starts on January 4, 1998, and ends on January 10, 1998; the first three
81 * days of 1998 then are part of week 53 of 1997.
82 *
83 * <p>
84 * Values calculated for the {@code WEEK_OF_MONTH} field range from 0 or
85 * 1 to 4 or 5. Week 1 of a month (the days with <code>WEEK_OF_MONTH =
86 * 1</code>)
87 * is the earliest set of at least {@code getMinimalDaysInFirstWeek()}
88 * contiguous days in that month, ending on the day before
89 * {@code getFirstDayOfWeek()}. Unlike week 1 of a year, week 1 of a
90 * month may be shorter than 7 days, need not start on
91 * {@code getFirstDayOfWeek()}, and will not include days of the
92 * previous month. Days of a month before week 1 have a
93 * {@code WEEK_OF_MONTH} of 0.
94 *
95 * <p>
96 * For example, if {@code getFirstDayOfWeek()} is {@code SUNDAY}
97 * and {@code getMinimalDaysInFirstWeek()} is 4, then the first week of
98 * January 1998 is Sunday, January 4 through Saturday, January 10. These days
99 * have a {@code WEEK_OF_MONTH} of 1. Thursday, January 1 through
100 * Saturday, January 3 have a {@code WEEK_OF_MONTH} of 0. If
101 * {@code getMinimalDaysInFirstWeek()} is changed to 3, then January 1
102 * through January 3 have a {@code WEEK_OF_MONTH} of 1.
103 *
104 * <p>
105 * <strong>Example:</strong> <blockquote>
106 *
107 * <pre>
108 * // get the supported ids for GMT-08:00 (Pacific Standard Time)
109 * String[] ids = TimeZone.getAvailableIDs(-8 * 60 * 60 * 1000);
110 * // if no ids were returned, something is wrong. get out.
111 * if (ids.length == 0)
112 *     System.exit(0);
113 *
114 *  // begin output
115 * System.out.println("Current Time");
116 *
117 * // create a Pacific Standard Time time zone
118 * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, ids[0]);
119 *
120 * // set up rules for daylight savings time
121 * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2 * 60 * 60 * 1000);
122 * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2 * 60 * 60 * 1000);
123 *
124 * // create a GregorianCalendar with the Pacific Daylight time zone
125 * // and the current date and time
126 * Calendar calendar = new GregorianCalendar(pdt);
127 * Date trialTime = new Date();
128 * calendar.setTime(trialTime);
129 *
130 * // print out a bunch of interesting things
131 * System.out.println("ERA: " + calendar.get(Calendar.ERA));
132 * System.out.println("YEAR: " + calendar.get(Calendar.YEAR));
133 * System.out.println("MONTH: " + calendar.get(Calendar.MONTH));
134 * System.out.println("WEEK_OF_YEAR: " + calendar.get(Calendar.WEEK_OF_YEAR));
135 * System.out.println("WEEK_OF_MONTH: " + calendar.get(Calendar.WEEK_OF_MONTH));
136 * System.out.println("DATE: " + calendar.get(Calendar.DATE));
137 * System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH));
138 * System.out.println("DAY_OF_YEAR: " + calendar.get(Calendar.DAY_OF_YEAR));
139 * System.out.println("DAY_OF_WEEK: " + calendar.get(Calendar.DAY_OF_WEEK));
140 * System.out.println("DAY_OF_WEEK_IN_MONTH: "
141 *                    + calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH));
142 * System.out.println("AM_PM: " + calendar.get(Calendar.AM_PM));
143 * System.out.println("HOUR: " + calendar.get(Calendar.HOUR));
144 * System.out.println("HOUR_OF_DAY: " + calendar.get(Calendar.HOUR_OF_DAY));
145 * System.out.println("MINUTE: " + calendar.get(Calendar.MINUTE));
146 * System.out.println("SECOND: " + calendar.get(Calendar.SECOND));
147 * System.out.println("MILLISECOND: " + calendar.get(Calendar.MILLISECOND));
148 * System.out.println("ZONE_OFFSET: "
149 *                    + (calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000)));
150 * System.out.println("DST_OFFSET: "
151 *                    + (calendar.get(Calendar.DST_OFFSET)/(60*60*1000)));
152
153 * System.out.println("Current Time, with hour reset to 3");
154 * calendar.clear(Calendar.HOUR_OF_DAY); // so doesn't override
155 * calendar.set(Calendar.HOUR, 3);
156 * System.out.println("ERA: " + calendar.get(Calendar.ERA));
157 * System.out.println("YEAR: " + calendar.get(Calendar.YEAR));
158 * System.out.println("MONTH: " + calendar.get(Calendar.MONTH));
159 * System.out.println("WEEK_OF_YEAR: " + calendar.get(Calendar.WEEK_OF_YEAR));
160 * System.out.println("WEEK_OF_MONTH: " + calendar.get(Calendar.WEEK_OF_MONTH));
161 * System.out.println("DATE: " + calendar.get(Calendar.DATE));
162 * System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH));
163 * System.out.println("DAY_OF_YEAR: " + calendar.get(Calendar.DAY_OF_YEAR));
164 * System.out.println("DAY_OF_WEEK: " + calendar.get(Calendar.DAY_OF_WEEK));
165 * System.out.println("DAY_OF_WEEK_IN_MONTH: "
166 *                    + calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH));
167 * System.out.println("AM_PM: " + calendar.get(Calendar.AM_PM));
168 * System.out.println("HOUR: " + calendar.get(Calendar.HOUR));
169 * System.out.println("HOUR_OF_DAY: " + calendar.get(Calendar.HOUR_OF_DAY));
170 * System.out.println("MINUTE: " + calendar.get(Calendar.MINUTE));
171 * System.out.println("SECOND: " + calendar.get(Calendar.SECOND));
172 * System.out.println("MILLISECOND: " + calendar.get(Calendar.MILLISECOND));
173 * System.out.println("ZONE_OFFSET: "
174 *        + (calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000))); // in hours
175 * System.out.println("DST_OFFSET: "
176 *        + (calendar.get(Calendar.DST_OFFSET)/(60*60*1000))); // in hours
177 * </pre>
178 *
179 * </blockquote>
180 *
181 * @see Calendar
182 * @see TimeZone
183 */
184public class GregorianCalendar extends Calendar {
185
186    private static final long serialVersionUID = -8125100834729963327L;
187
188    /**
189     * Value for the BC era.
190     */
191    public static final int BC = 0;
192
193    /**
194     * Value for the AD era.
195     */
196    public static final int AD = 1;
197
198    private static final long defaultGregorianCutover = -12219292800000l;
199
200    private long gregorianCutover = defaultGregorianCutover;
201
202    private transient int changeYear = 1582;
203
204    private transient int julianSkew = ((changeYear - 2000) / 400)
205            + julianError() - ((changeYear - 2000) / 100);
206
207    static byte[] DaysInMonth = new byte[] { 31, 28, 31, 30, 31, 30, 31, 31,
208            30, 31, 30, 31 };
209
210    private static int[] DaysInYear = new int[] { 0, 31, 59, 90, 120, 151, 181,
211            212, 243, 273, 304, 334 };
212
213    private static int[] maximums = new int[] { 1, 292278994, 11, 53, 6, 31,
214            366, 7, 6, 1, 11, 23, 59, 59, 999, 14 * 3600 * 1000, 7200000 };
215
216    private static int[] minimums = new int[] { 0, 1, 0, 1, 0, 1, 1, 1, 1, 0,
217            0, 0, 0, 0, 0, -13 * 3600 * 1000, 0 };
218
219    private static int[] leastMaximums = new int[] { 1, 292269054, 11, 50, 3,
220            28, 355, 7, 3, 1, 11, 23, 59, 59, 999, 50400000, 1200000 };
221
222    private int currentYearSkew = 10;
223
224    private int lastYearSkew = 0;
225
226    /**
227     * Constructs a new {@code GregorianCalendar} initialized to the current date and
228     * time with the default {@code Locale} and {@code TimeZone}.
229     */
230    public GregorianCalendar() {
231        this(TimeZone.getDefault(), Locale.getDefault());
232    }
233
234    /**
235     * Constructs a new {@code GregorianCalendar} initialized to midnight in the default
236     * {@code TimeZone} and {@code Locale} on the specified date.
237     *
238     * @param year
239     *            the year.
240     * @param month
241     *            the month.
242     * @param day
243     *            the day of the month.
244     */
245    public GregorianCalendar(int year, int month, int day) {
246        super(TimeZone.getDefault(), Locale.getDefault());
247        set(year, month, day);
248    }
249
250    /**
251     * Constructs a new {@code GregorianCalendar} initialized to the specified date and
252     * time in the default {@code TimeZone} and {@code Locale}.
253     *
254     * @param year
255     *            the year.
256     * @param month
257     *            the month.
258     * @param day
259     *            the day of the month.
260     * @param hour
261     *            the hour.
262     * @param minute
263     *            the minute.
264     */
265    public GregorianCalendar(int year, int month, int day, int hour, int minute) {
266        super(TimeZone.getDefault(), Locale.getDefault());
267        set(year, month, day, hour, minute);
268    }
269
270    /**
271     * Constructs a new {@code GregorianCalendar} initialized to the specified date and
272     * time in the default {@code TimeZone} and {@code Locale}.
273     *
274     * @param year
275     *            the year.
276     * @param month
277     *            the month.
278     * @param day
279     *            the day of the month.
280     * @param hour
281     *            the hour.
282     * @param minute
283     *            the minute.
284     * @param second
285     *            the second.
286     */
287    public GregorianCalendar(int year, int month, int day, int hour,
288            int minute, int second) {
289        super(TimeZone.getDefault(), Locale.getDefault());
290        set(year, month, day, hour, minute, second);
291    }
292
293    GregorianCalendar(long milliseconds) {
294        this(false);
295        setTimeInMillis(milliseconds);
296    }
297
298    /**
299     * Constructs a new {@code GregorianCalendar} initialized to the current date and
300     * time and using the specified {@code Locale} and the default {@code TimeZone}.
301     *
302     * @param locale
303     *            the {@code Locale}.
304     */
305    public GregorianCalendar(Locale locale) {
306        this(TimeZone.getDefault(), locale);
307    }
308
309    /**
310     * Constructs a new {@code GregorianCalendar} initialized to the current date and
311     * time and using the specified {@code TimeZone} and the default {@code Locale}.
312     *
313     * @param timezone
314     *            the {@code TimeZone}.
315     */
316    public GregorianCalendar(TimeZone timezone) {
317        this(timezone, Locale.getDefault());
318    }
319
320    /**
321     * Constructs a new {@code GregorianCalendar} initialized to the current date and
322     * time and using the specified {@code TimeZone} and {@code Locale}.
323     *
324     * @param timezone
325     *            the {@code TimeZone}.
326     * @param locale
327     *            the {@code Locale}.
328     */
329    public GregorianCalendar(TimeZone timezone, Locale locale) {
330        super(timezone, locale);
331        setTimeInMillis(System.currentTimeMillis());
332    }
333
334    GregorianCalendar(boolean ignored) {
335        super(TimeZone.getDefault());
336        setFirstDayOfWeek(SUNDAY);
337        setMinimalDaysInFirstWeek(1);
338    }
339
340    /**
341     * Adds the specified amount to a {@code Calendar} field.
342     *
343     * @param field
344     *            the {@code Calendar} field to modify.
345     * @param value
346     *            the amount to add to the field.
347     *
348     * @throws IllegalArgumentException
349     *                if the specified field is DST_OFFSET or ZONE_OFFSET.
350     */
351    @Override
352    public void add(int field, int value) {
353        if (value == 0) {
354            return;
355        }
356        if (field < 0 || field >= ZONE_OFFSET) {
357            throw new IllegalArgumentException();
358        }
359
360        if (field == ERA) {
361            complete();
362            if (fields[ERA] == AD) {
363                if (value >= 0) {
364                    return;
365                }
366                set(ERA, BC);
367            } else {
368                if (value <= 0) {
369                    return;
370                }
371                set(ERA, AD);
372            }
373            complete();
374            return;
375        }
376
377        if (field == YEAR || field == MONTH) {
378            complete();
379            if (field == MONTH) {
380                int month = fields[MONTH] + value;
381                if (month < 0) {
382                    value = (month - 11) / 12;
383                    month = 12 + (month % 12);
384                } else {
385                    value = month / 12;
386                }
387                set(MONTH, month % 12);
388            }
389            set(YEAR, fields[YEAR] + value);
390            int days = daysInMonth(isLeapYear(fields[YEAR]), fields[MONTH]);
391            if (fields[DATE] > days) {
392                set(DATE, days);
393            }
394            complete();
395            return;
396        }
397
398        long multiplier = 0;
399        getTimeInMillis(); // Update the time
400        switch (field) {
401            case MILLISECOND:
402                time += value;
403                break;
404            case SECOND:
405                time += value * 1000L;
406                break;
407            case MINUTE:
408                time += value * 60000L;
409                break;
410            case HOUR:
411            case HOUR_OF_DAY:
412                time += value * 3600000L;
413                break;
414            case AM_PM:
415                multiplier = 43200000L;
416                break;
417            case DATE:
418            case DAY_OF_YEAR:
419            case DAY_OF_WEEK:
420                multiplier = 86400000L;
421                break;
422            case WEEK_OF_YEAR:
423            case WEEK_OF_MONTH:
424            case DAY_OF_WEEK_IN_MONTH:
425                multiplier = 604800000L;
426                break;
427        }
428
429        if (multiplier == 0) {
430            areFieldsSet = false;
431            complete();
432            return;
433        }
434
435        long delta = value * multiplier;
436
437        /*
438         * Attempt to keep the hour and minute constant when we've crossed a DST
439         * boundary and the user's units are AM_PM or larger. The typical
440         * consequence is that calls to add(DATE, 1) will add 23, 24 or 25 hours
441         * depending on whether the DST goes forward, constant, or backward.
442         *
443         * We know we've crossed a DST boundary if the new time will have a
444         * different timezone offset. Adjust by adding the difference of the two
445         * offsets. We don't adjust when doing so prevents the change from
446         * crossing the boundary.
447         */
448        int zoneOffset = getTimeZone().getRawOffset();
449        int offsetBefore = getOffset(time + zoneOffset);
450        int offsetAfter = getOffset(time + zoneOffset + delta);
451        int dstDelta = offsetBefore - offsetAfter;
452        if (getOffset(time + zoneOffset + delta + dstDelta) == offsetAfter) {
453            delta += dstDelta;
454        }
455
456        time += delta;
457        areFieldsSet = false;
458        complete();
459    }
460
461    private void fullFieldsCalc(long timeVal, int zoneOffset) {
462        int millis = (int) (time % 86400000);
463        long days = timeVal / 86400000;
464
465        if (millis < 0) {
466            millis += 86400000;
467            days--;
468        }
469        // Cannot add ZONE_OFFSET to time as it might overflow
470        millis += zoneOffset;
471        while (millis < 0) {
472            millis += 86400000;
473            days--;
474        }
475        while (millis >= 86400000) {
476            millis -= 86400000;
477            days++;
478        }
479
480        int dayOfYear = computeYearAndDay(days, timeVal + zoneOffset);
481        fields[DAY_OF_YEAR] = dayOfYear;
482        if(fields[YEAR] == changeYear && gregorianCutover <= timeVal + zoneOffset){
483            dayOfYear += currentYearSkew;
484        }
485        int month = dayOfYear / 32;
486        boolean leapYear = isLeapYear(fields[YEAR]);
487        int date = dayOfYear - daysInYear(leapYear, month);
488        if (date > daysInMonth(leapYear, month)) {
489            date -= daysInMonth(leapYear, month);
490            month++;
491        }
492        fields[DAY_OF_WEEK] = mod7(days - 3) + 1;
493        int dstOffset = fields[YEAR] <= 0 ? 0 : getTimeZone().getOffset(AD,
494                fields[YEAR], month, date, fields[DAY_OF_WEEK], millis);
495        if (fields[YEAR] > 0) {
496            dstOffset -= zoneOffset;
497        }
498        fields[DST_OFFSET] = dstOffset;
499        if (dstOffset != 0) {
500            long oldDays = days;
501            millis += dstOffset;
502            if (millis < 0) {
503                millis += 86400000;
504                days--;
505            } else if (millis >= 86400000) {
506                millis -= 86400000;
507                days++;
508            }
509            if (oldDays != days) {
510                dayOfYear = computeYearAndDay(days, timeVal - zoneOffset
511                        + dstOffset);
512                fields[DAY_OF_YEAR] = dayOfYear;
513                if(fields[YEAR] == changeYear && gregorianCutover <= timeVal - zoneOffset + dstOffset){
514                    dayOfYear += currentYearSkew;
515                }
516                month = dayOfYear / 32;
517                leapYear = isLeapYear(fields[YEAR]);
518                date = dayOfYear - daysInYear(leapYear, month);
519                if (date > daysInMonth(leapYear, month)) {
520                    date -= daysInMonth(leapYear, month);
521                    month++;
522                }
523                fields[DAY_OF_WEEK] = mod7(days - 3) + 1;
524            }
525        }
526
527        fields[MILLISECOND] = (millis % 1000);
528        millis /= 1000;
529        fields[SECOND] = (millis % 60);
530        millis /= 60;
531        fields[MINUTE] = (millis % 60);
532        millis /= 60;
533        fields[HOUR_OF_DAY] = (millis % 24);
534        fields[AM_PM] = fields[HOUR_OF_DAY] > 11 ? 1 : 0;
535        fields[HOUR] = fields[HOUR_OF_DAY] % 12;
536
537        if (fields[YEAR] <= 0) {
538            fields[ERA] = BC;
539            fields[YEAR] = -fields[YEAR] + 1;
540        } else {
541            fields[ERA] = AD;
542        }
543        fields[MONTH] = month;
544        fields[DATE] = date;
545        fields[DAY_OF_WEEK_IN_MONTH] = (date - 1) / 7 + 1;
546        fields[WEEK_OF_MONTH] = (date - 1 + mod7(days - date - 2
547                - (getFirstDayOfWeek() - 1))) / 7 + 1;
548        int daysFromStart = mod7(days - 3 - (fields[DAY_OF_YEAR] - 1)
549                - (getFirstDayOfWeek() - 1));
550        int week = (fields[DAY_OF_YEAR] - 1 + daysFromStart) / 7
551                + (7 - daysFromStart >= getMinimalDaysInFirstWeek() ? 1 : 0);
552        if (week == 0) {
553            fields[WEEK_OF_YEAR] = 7 - mod7(daysFromStart
554                    - (isLeapYear(fields[YEAR] - 1) ? 2 : 1)) >= getMinimalDaysInFirstWeek() ? 53
555                    : 52;
556        } else if (fields[DAY_OF_YEAR] >= (leapYear ? 367 : 366)
557                - mod7(daysFromStart + (leapYear ? 2 : 1))) {
558            fields[WEEK_OF_YEAR] = 7 - mod7(daysFromStart + (leapYear ? 2 : 1)) >= getMinimalDaysInFirstWeek() ? 1
559                    : week;
560        } else {
561            fields[WEEK_OF_YEAR] = week;
562        }
563    }
564
565    @Override
566    protected void computeFields() {
567        TimeZone timeZone = getTimeZone();
568        int dstOffset = timeZone.inDaylightTime(new Date(time)) ? timeZone.getDSTSavings() : 0;
569        int zoneOffset = timeZone.getRawOffset();
570        fields[DST_OFFSET] = dstOffset;
571        fields[ZONE_OFFSET] = zoneOffset;
572
573        fullFieldsCalc(time, zoneOffset);
574
575        for (int i = 0; i < FIELD_COUNT; i++) {
576            isSet[i] = true;
577        }
578    }
579
580    @Override
581    protected void computeTime() {
582        if (!isLenient()) {
583            if (isSet[HOUR_OF_DAY]) {
584                if (fields[HOUR_OF_DAY] < 0 || fields[HOUR_OF_DAY] > 23) {
585                    throw new IllegalArgumentException();
586                }
587            } else if (isSet[HOUR] && (fields[HOUR] < 0 || fields[HOUR] > 11)) {
588                throw new IllegalArgumentException();
589            }
590            if (isSet[MINUTE] && (fields[MINUTE] < 0 || fields[MINUTE] > 59)) {
591                throw new IllegalArgumentException();
592            }
593            if (isSet[SECOND] && (fields[SECOND] < 0 || fields[SECOND] > 59)) {
594                throw new IllegalArgumentException();
595            }
596            if (isSet[MILLISECOND]
597                    && (fields[MILLISECOND] < 0 || fields[MILLISECOND] > 999)) {
598                throw new IllegalArgumentException();
599            }
600            if (isSet[WEEK_OF_YEAR]
601                    && (fields[WEEK_OF_YEAR] < 1 || fields[WEEK_OF_YEAR] > 53)) {
602                throw new IllegalArgumentException();
603            }
604            if (isSet[DAY_OF_WEEK]
605                    && (fields[DAY_OF_WEEK] < 1 || fields[DAY_OF_WEEK] > 7)) {
606                throw new IllegalArgumentException();
607            }
608            if (isSet[DAY_OF_WEEK_IN_MONTH]
609                    && (fields[DAY_OF_WEEK_IN_MONTH] < 1 || fields[DAY_OF_WEEK_IN_MONTH] > 6)) {
610                throw new IllegalArgumentException();
611            }
612            if (isSet[WEEK_OF_MONTH]
613                    && (fields[WEEK_OF_MONTH] < 1 || fields[WEEK_OF_MONTH] > 6)) {
614                throw new IllegalArgumentException();
615            }
616            if (isSet[AM_PM] && fields[AM_PM] != AM && fields[AM_PM] != PM) {
617                throw new IllegalArgumentException();
618            }
619            if (isSet[HOUR] && (fields[HOUR] < 0 || fields[HOUR] > 11)) {
620                throw new IllegalArgumentException();
621            }
622            if (isSet[YEAR]) {
623                if (isSet[ERA] && fields[ERA] == BC
624                        && (fields[YEAR] < 1 || fields[YEAR] > 292269054)) {
625                    throw new IllegalArgumentException();
626                } else if (fields[YEAR] < 1 || fields[YEAR] > 292278994) {
627                    throw new IllegalArgumentException();
628                }
629            }
630            if (isSet[MONTH] && (fields[MONTH] < 0 || fields[MONTH] > 11)) {
631                throw new IllegalArgumentException();
632            }
633        }
634
635        long timeVal;
636        long hour = 0;
637        if (isSet[HOUR_OF_DAY] && lastTimeFieldSet != HOUR) {
638            hour = fields[HOUR_OF_DAY];
639        } else if (isSet[HOUR]) {
640            hour = (fields[AM_PM] * 12) + fields[HOUR];
641        }
642        timeVal = hour * 3600000;
643
644        if (isSet[MINUTE]) {
645            timeVal += ((long) fields[MINUTE]) * 60000;
646        }
647        if (isSet[SECOND]) {
648            timeVal += ((long) fields[SECOND]) * 1000;
649        }
650        if (isSet[MILLISECOND]) {
651            timeVal += fields[MILLISECOND];
652        }
653
654        long days;
655        int year = isSet[YEAR] ? fields[YEAR] : 1970;
656        if (isSet[ERA]) {
657            // Always test for valid ERA, even if the Calendar is lenient
658            if (fields[ERA] != BC && fields[ERA] != AD) {
659                throw new IllegalArgumentException();
660            }
661            if (fields[ERA] == BC) {
662                year = 1 - year;
663            }
664        }
665
666        boolean weekMonthSet = isSet[WEEK_OF_MONTH]
667                || isSet[DAY_OF_WEEK_IN_MONTH];
668        boolean useMonth = (isSet[DATE] || isSet[MONTH] || weekMonthSet)
669                && lastDateFieldSet != DAY_OF_YEAR;
670        if (useMonth
671                && (lastDateFieldSet == DAY_OF_WEEK || lastDateFieldSet == WEEK_OF_YEAR)) {
672            if (isSet[WEEK_OF_YEAR] && isSet[DAY_OF_WEEK]) {
673                if (lastDateFieldSet == WEEK_OF_YEAR) {
674                    useMonth = false;
675                } else if (lastDateFieldSet == DAY_OF_WEEK) {
676                    // DAY_OF_WEEK belongs to both the Month + Week + Day and the
677                    // WeekOfYear + Day combinations. We're supposed to use the most
678                    // recent combination, as specified by the single set field. We can't
679                    // know for sure in this case, so we always prefer the week-month-day
680                    // combination if week-month is already set.
681                    useMonth = weekMonthSet;
682                }
683            } else if (isSet[DAY_OF_YEAR]) {
684                useMonth = isSet[DATE] && isSet[MONTH];
685            }
686        }
687
688        if (useMonth) {
689            int month = fields[MONTH];
690            year += month / 12;
691            month %= 12;
692            if (month < 0) {
693                year--;
694                month += 12;
695            }
696            boolean leapYear = isLeapYear(year);
697            days = daysFromBaseYear(year) + daysInYear(leapYear, month);
698            boolean useDate = isSet[DATE];
699            if (useDate
700                    && (lastDateFieldSet == DAY_OF_WEEK
701                            || lastDateFieldSet == WEEK_OF_MONTH || lastDateFieldSet == DAY_OF_WEEK_IN_MONTH)) {
702                useDate = !(isSet[DAY_OF_WEEK] && weekMonthSet);
703            }
704            if (useDate) {
705                if (!isLenient()
706                        && (fields[DATE] < 1 || fields[DATE] > daysInMonth(
707                                leapYear, month))) {
708                    throw new IllegalArgumentException();
709                }
710                days += fields[DATE] - 1;
711            } else {
712                int dayOfWeek;
713                if (isSet[DAY_OF_WEEK]) {
714                    dayOfWeek = fields[DAY_OF_WEEK] - 1;
715                } else {
716                    dayOfWeek = getFirstDayOfWeek() - 1;
717                }
718                if (isSet[WEEK_OF_MONTH]
719                        && lastDateFieldSet != DAY_OF_WEEK_IN_MONTH) {
720                    int skew = mod7(days - 3 - (getFirstDayOfWeek() - 1));
721                    days += (fields[WEEK_OF_MONTH] - 1) * 7
722                            + mod7(skew + dayOfWeek - (days - 3)) - skew;
723                } else if (isSet[DAY_OF_WEEK_IN_MONTH]) {
724                    if (fields[DAY_OF_WEEK_IN_MONTH] >= 0) {
725                        days += mod7(dayOfWeek - (days - 3))
726                                + (fields[DAY_OF_WEEK_IN_MONTH] - 1) * 7;
727                    } else {
728                        days += daysInMonth(leapYear, month)
729                                + mod7(dayOfWeek
730                                        - (days + daysInMonth(leapYear, month) - 3))
731                                + fields[DAY_OF_WEEK_IN_MONTH] * 7;
732                    }
733                } else if (isSet[DAY_OF_WEEK]) {
734                    int skew = mod7(days - 3 - (getFirstDayOfWeek() - 1));
735                    days += mod7(mod7(skew + dayOfWeek - (days - 3)) - skew);
736                }
737            }
738        } else {
739            boolean useWeekYear = isSet[WEEK_OF_YEAR]
740                    && lastDateFieldSet != DAY_OF_YEAR;
741            if (useWeekYear && isSet[DAY_OF_YEAR]) {
742                useWeekYear = isSet[DAY_OF_WEEK];
743            }
744            days = daysFromBaseYear(year);
745            if (useWeekYear) {
746                int dayOfWeek;
747                if (isSet[DAY_OF_WEEK]) {
748                    dayOfWeek = fields[DAY_OF_WEEK] - 1;
749                } else {
750                    dayOfWeek = getFirstDayOfWeek() - 1;
751                }
752                int skew = mod7(days - 3 - (getFirstDayOfWeek() - 1));
753                days += (fields[WEEK_OF_YEAR] - 1) * 7
754                        + mod7(skew + dayOfWeek - (days - 3)) - skew;
755                if (7 - skew < getMinimalDaysInFirstWeek()) {
756                    days += 7;
757                }
758            } else if (isSet[DAY_OF_YEAR]) {
759                if (!isLenient()
760                        && (fields[DAY_OF_YEAR] < 1 || fields[DAY_OF_YEAR] > (365 + (isLeapYear(year) ? 1
761                                : 0)))) {
762                    throw new IllegalArgumentException();
763                }
764                days += fields[DAY_OF_YEAR] - 1;
765            } else if (isSet[DAY_OF_WEEK]) {
766                days += mod7(fields[DAY_OF_WEEK] - 1 - (days - 3));
767            }
768        }
769        lastDateFieldSet = 0;
770
771        timeVal += days * 86400000;
772        // Use local time to compare with the gregorian change
773        if (year == changeYear
774                && timeVal >= gregorianCutover + julianError() * 86400000L) {
775            timeVal -= julianError() * 86400000L;
776        }
777
778        // It is not possible to simply subtract getOffset(timeVal) from timeVal
779        // to get UTC.
780        // The trick is needed for the moment when DST transition occurs,
781        // say 1:00 is a transition time when DST offset becomes +1 hour,
782        // then wall time in the interval 1:00 - 2:00 is invalid and is
783        // treated as UTC time.
784        long timeValWithoutDST = timeVal - getOffset(timeVal)
785                + getTimeZone().getRawOffset();
786        timeVal -= getOffset(timeValWithoutDST);
787        // Need to update wall time in fields, since it was invalid due to DST
788        // transition
789        this.time = timeVal;
790        if (timeValWithoutDST != timeVal) {
791            computeFields();
792            areFieldsSet = true;
793        }
794    }
795
796    private int computeYearAndDay(long dayCount, long localTime) {
797        int year = 1970;
798        long days = dayCount;
799        if (localTime < gregorianCutover) {
800            days -= julianSkew;
801        }
802        int approxYears;
803
804        while ((approxYears = (int) (days / 365)) != 0) {
805            year = year + approxYears;
806            days = dayCount - daysFromBaseYear(year);
807        }
808        if (days < 0) {
809            year = year - 1;
810            days = days + daysInYear(year);
811        }
812        fields[YEAR] = year;
813        return (int) days + 1;
814    }
815
816    private long daysFromBaseYear(long year) {
817        if (year >= 1970) {
818            long days = (year - 1970) * 365 + ((year - 1969) / 4);
819            if (year > changeYear) {
820                days -= ((year - 1901) / 100) - ((year - 1601) / 400);
821            } else {
822                if (year == changeYear) {
823                    days += currentYearSkew;
824                } else if (year == changeYear - 1) {
825                    days += lastYearSkew;
826                } else {
827                    days += julianSkew;
828                }
829            }
830            return days;
831        } else if (year <= changeYear) {
832            return (year - 1970) * 365 + ((year - 1972) / 4) + julianSkew;
833        }
834        return (year - 1970) * 365 + ((year - 1972) / 4)
835                - ((year - 2000) / 100) + ((year - 2000) / 400);
836    }
837
838    private int daysInMonth() {
839        return daysInMonth(isLeapYear(fields[YEAR]), fields[MONTH]);
840    }
841
842    private int daysInMonth(boolean leapYear, int month) {
843        if (leapYear && month == FEBRUARY) {
844            return DaysInMonth[month] + 1;
845        }
846
847        return DaysInMonth[month];
848    }
849
850    private int daysInYear(int year) {
851        int daysInYear = isLeapYear(year) ? 366 : 365;
852        if (year == changeYear) {
853            daysInYear -= currentYearSkew;
854        }
855        if (year == changeYear - 1) {
856            daysInYear -= lastYearSkew;
857        }
858        return daysInYear;
859    }
860
861    private int daysInYear(boolean leapYear, int month) {
862        if (leapYear && month > FEBRUARY) {
863            return DaysInYear[month] + 1;
864        }
865
866        return DaysInYear[month];
867    }
868
869    /**
870     * Returns true if {@code object} is a GregorianCalendar with the same
871     * properties.
872     */
873    @Override public boolean equals(Object object) {
874        if (!(object instanceof GregorianCalendar)) {
875            return false;
876        }
877        if (object == this) {
878            return true;
879        }
880        return super.equals(object)
881                && gregorianCutover == ((GregorianCalendar) object).gregorianCutover;
882    }
883
884    @Override public int getActualMaximum(int field) {
885        int value;
886        if ((value = maximums[field]) == leastMaximums[field]) {
887            return value;
888        }
889
890        complete();
891        long orgTime = time;
892        int result = 0;
893        switch (field) {
894            case WEEK_OF_YEAR:
895                set(DATE, 31);
896                set(MONTH, DECEMBER);
897                result = get(WEEK_OF_YEAR);
898                if (result == 1) {
899                    set(DATE, 31 - 7);
900                    result = get(WEEK_OF_YEAR);
901                }
902                areFieldsSet = false;
903                break;
904            case WEEK_OF_MONTH:
905                set(DATE, daysInMonth());
906                result = get(WEEK_OF_MONTH);
907                areFieldsSet = false;
908                break;
909            case DATE:
910                return daysInMonth();
911            case DAY_OF_YEAR:
912                return daysInYear(fields[YEAR]);
913            case DAY_OF_WEEK_IN_MONTH:
914                result = get(DAY_OF_WEEK_IN_MONTH)
915                        + ((daysInMonth() - get(DATE)) / 7);
916                break;
917            case YEAR:
918                GregorianCalendar clone = (GregorianCalendar) clone();
919                if (get(ERA) == AD) {
920                    clone.setTimeInMillis(Long.MAX_VALUE);
921                } else {
922                    clone.setTimeInMillis(Long.MIN_VALUE);
923                }
924                result = clone.get(YEAR);
925                clone.set(YEAR, get(YEAR));
926                if (clone.before(this)) {
927                    result--;
928                }
929                break;
930            case DST_OFFSET:
931                result = getMaximum(DST_OFFSET);
932                break;
933        }
934        time = orgTime;
935        return result;
936    }
937
938    /**
939     * Gets the minimum value of the specified field for the current date. For
940     * the gregorian calendar, this value is the same as
941     * {@code getMinimum()}.
942     *
943     * @param field
944     *            the field.
945     * @return the minimum value of the specified field.
946     */
947    @Override
948    public int getActualMinimum(int field) {
949        return getMinimum(field);
950    }
951
952    /**
953     * Gets the greatest minimum value of the specified field. For the gregorian
954     * calendar, this value is the same as {@code getMinimum()}.
955     *
956     * @param field
957     *            the field.
958     * @return the greatest minimum value of the specified field.
959     */
960    @Override
961    public int getGreatestMinimum(int field) {
962        return minimums[field];
963    }
964
965    /**
966     * Returns the gregorian change date of this calendar. This is the date on
967     * which the gregorian calendar came into effect.
968     *
969     * @return a {@code Date} which represents the gregorian change date.
970     */
971    public final Date getGregorianChange() {
972        return new Date(gregorianCutover);
973    }
974
975    /**
976     * Gets the smallest maximum value of the specified field. For example, 28
977     * for the day of month field.
978     *
979     * @param field
980     *            the field.
981     * @return the smallest maximum value of the specified field.
982     */
983    @Override
984    public int getLeastMaximum(int field) {
985        // return value for WEEK_OF_YEAR should make corresponding changes when
986        // the gregorian change date have been reset.
987        if (gregorianCutover != defaultGregorianCutover
988                && field == WEEK_OF_YEAR) {
989            long currentTimeInMillis = time;
990            setTimeInMillis(gregorianCutover);
991            int actual = getActualMaximum(field);
992            setTimeInMillis(currentTimeInMillis);
993            return actual;
994        }
995        return leastMaximums[field];
996    }
997
998    /**
999     * Gets the greatest maximum value of the specified field. For example, 31
1000     * for the day of month field.
1001     *
1002     * @param field
1003     *            the field.
1004     * @return the greatest maximum value of the specified field.
1005     */
1006    @Override
1007    public int getMaximum(int field) {
1008        return maximums[field];
1009    }
1010
1011    /**
1012     * Gets the smallest minimum value of the specified field.
1013     *
1014     * @param field
1015     *            the field.
1016     * @return the smallest minimum value of the specified field.
1017     */
1018    @Override
1019    public int getMinimum(int field) {
1020        return minimums[field];
1021    }
1022
1023    private int getOffset(long localTime) {
1024        TimeZone timeZone = getTimeZone();
1025
1026        long dayCount = localTime / 86400000;
1027        int millis = (int) (localTime % 86400000);
1028        if (millis < 0) {
1029            millis += 86400000;
1030            dayCount--;
1031        }
1032
1033        int year = 1970;
1034        long days = dayCount;
1035        if (localTime < gregorianCutover) {
1036            days -= julianSkew;
1037        }
1038        int approxYears;
1039
1040        while ((approxYears = (int) (days / 365)) != 0) {
1041            year = year + approxYears;
1042            days = dayCount - daysFromBaseYear(year);
1043        }
1044        if (days < 0) {
1045            year = year - 1;
1046            days = days + 365 + (isLeapYear(year) ? 1 : 0);
1047            if (year == changeYear && localTime < gregorianCutover) {
1048                days -= julianError();
1049            }
1050        }
1051        if (year <= 0) {
1052            return timeZone.getRawOffset();
1053        }
1054        int dayOfYear = (int) days + 1;
1055
1056        int month = dayOfYear / 32;
1057        boolean leapYear = isLeapYear(year);
1058        int date = dayOfYear - daysInYear(leapYear, month);
1059        if (date > daysInMonth(leapYear, month)) {
1060            date -= daysInMonth(leapYear, month);
1061            month++;
1062        }
1063        int dayOfWeek = mod7(dayCount - 3) + 1;
1064        return timeZone.getOffset(AD, year, month, date, dayOfWeek, millis);
1065    }
1066
1067    @Override public int hashCode() {
1068        return super.hashCode()
1069                + ((int) (gregorianCutover >>> 32) ^ (int) gregorianCutover);
1070    }
1071
1072    /**
1073     * Returns true if {@code year} is a leap year.
1074     */
1075    public boolean isLeapYear(int year) {
1076        if (year > changeYear) {
1077            return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
1078        }
1079
1080        return year % 4 == 0;
1081    }
1082
1083    private int julianError() {
1084        return changeYear / 100 - changeYear / 400 - 2;
1085    }
1086
1087    private int mod(int value, int mod) {
1088        int rem = value % mod;
1089        if (value < 0 && rem < 0) {
1090            return rem + mod;
1091        }
1092        return rem;
1093    }
1094
1095    private int mod7(long num1) {
1096        int rem = (int) (num1 % 7);
1097        if (num1 < 0 && rem < 0) {
1098            return rem + 7;
1099        }
1100        return rem;
1101    }
1102
1103    /**
1104     * Adds the specified amount the specified field and wraps the value of the
1105     * field when it goes beyond the maximum or minimum value for the current
1106     * date. Other fields will be adjusted as required to maintain a consistent
1107     * date.
1108     *
1109     * @param field
1110     *            the field to roll.
1111     * @param value
1112     *            the amount to add.
1113     *
1114     * @throws IllegalArgumentException
1115     *                if an invalid field is specified.
1116     */
1117    @Override
1118    public void roll(int field, int value) {
1119        if (value == 0) {
1120            return;
1121        }
1122        if (field < 0 || field >= ZONE_OFFSET) {
1123            throw new IllegalArgumentException();
1124        }
1125
1126        complete();
1127        int days, day, mod, maxWeeks, newWeek;
1128        int max = -1;
1129        switch (field) {
1130        case YEAR:
1131            max = maximums[field];
1132            break;
1133        case WEEK_OF_YEAR:
1134            days = daysInYear(fields[YEAR]);
1135            day = DAY_OF_YEAR;
1136            mod = mod7(fields[DAY_OF_WEEK] - fields[day]
1137                    - (getFirstDayOfWeek() - 1));
1138            maxWeeks = (days - 1 + mod) / 7 + 1;
1139            newWeek = mod(fields[field] - 1 + value, maxWeeks) + 1;
1140            if (newWeek == maxWeeks) {
1141                int addDays = (newWeek - fields[field]) * 7;
1142                if (fields[day] > addDays && fields[day] + addDays > days) {
1143                    set(field, 1);
1144                } else {
1145                    set(field, newWeek - 1);
1146                }
1147            } else if (newWeek == 1) {
1148                int week = (fields[day] - ((fields[day] - 1) / 7 * 7) - 1 + mod) / 7 + 1;
1149                if (week > 1) {
1150                    set(field, 1);
1151                } else {
1152                    set(field, newWeek);
1153                }
1154            } else {
1155                set(field, newWeek);
1156            }
1157            break;
1158        case WEEK_OF_MONTH:
1159            days = daysInMonth();
1160            day = DATE;
1161            mod = mod7(fields[DAY_OF_WEEK] - fields[day]
1162                    - (getFirstDayOfWeek() - 1));
1163            maxWeeks = (days - 1 + mod) / 7 + 1;
1164            newWeek = mod(fields[field] - 1 + value, maxWeeks) + 1;
1165            if (newWeek == maxWeeks) {
1166                if (fields[day] + (newWeek - fields[field]) * 7 > days) {
1167                    set(day, days);
1168                } else {
1169                    set(field, newWeek);
1170                }
1171            } else if (newWeek == 1) {
1172                int week = (fields[day] - ((fields[day] - 1) / 7 * 7) - 1 + mod) / 7 + 1;
1173                if (week > 1) {
1174                    set(day, 1);
1175                } else {
1176                    set(field, newWeek);
1177                }
1178            } else {
1179                set(field, newWeek);
1180            }
1181            break;
1182        case DATE:
1183            max = daysInMonth();
1184            break;
1185        case DAY_OF_YEAR:
1186            max = daysInYear(fields[YEAR]);
1187            break;
1188        case DAY_OF_WEEK:
1189            max = maximums[field];
1190            lastDateFieldSet = WEEK_OF_MONTH;
1191            break;
1192        case DAY_OF_WEEK_IN_MONTH:
1193            max = (fields[DATE] + ((daysInMonth() - fields[DATE]) / 7 * 7) - 1) / 7 + 1;
1194            break;
1195
1196        case ERA:
1197        case MONTH:
1198        case AM_PM:
1199        case HOUR:
1200        case HOUR_OF_DAY:
1201        case MINUTE:
1202        case SECOND:
1203        case MILLISECOND:
1204            set(field, mod(fields[field] + value, maximums[field] + 1));
1205            if (field == MONTH && fields[DATE] > daysInMonth()) {
1206                set(DATE, daysInMonth());
1207            } else if (field == AM_PM) {
1208                lastTimeFieldSet = HOUR;
1209            }
1210            break;
1211        }
1212        if (max != -1) {
1213            set(field, mod(fields[field] - 1 + value, max) + 1);
1214        }
1215        complete();
1216    }
1217
1218    /**
1219     * Increments or decrements the specified field and wraps the value of the
1220     * field when it goes beyond the maximum or minimum value for the current
1221     * date. Other fields will be adjusted as required to maintain a consistent
1222     * date. For example, March 31 will roll to April 30 when rolling the month
1223     * field.
1224     *
1225     * @param field
1226     *            the field to roll.
1227     * @param increment
1228     *            {@code true} to increment the field, {@code false} to
1229     *            decrement.
1230     * @throws IllegalArgumentException
1231     *                if an invalid field is specified.
1232     */
1233    @Override
1234    public void roll(int field, boolean increment) {
1235        roll(field, increment ? 1 : -1);
1236    }
1237
1238    /**
1239     * Sets the gregorian change date of this calendar.
1240     */
1241    public void setGregorianChange(Date date) {
1242        gregorianCutover = date.getTime();
1243        GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
1244        cal.setTime(date);
1245        changeYear = cal.get(YEAR);
1246        if (cal.get(ERA) == BC) {
1247            changeYear = 1 - changeYear;
1248        }
1249        julianSkew = ((changeYear - 2000) / 400) + julianError()
1250                - ((changeYear - 2000) / 100);
1251        int dayOfYear = cal.get(DAY_OF_YEAR);
1252        if (dayOfYear < julianSkew) {
1253            currentYearSkew = dayOfYear-1;
1254            lastYearSkew = julianSkew - dayOfYear + 1;
1255        } else {
1256            lastYearSkew = 0;
1257            currentYearSkew = julianSkew;
1258        }
1259    }
1260
1261    private void writeObject(ObjectOutputStream stream) throws IOException {
1262        stream.defaultWriteObject();
1263    }
1264
1265    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
1266        stream.defaultReadObject();
1267        setGregorianChange(new Date(gregorianCutover));
1268    }
1269}
1270