1/*
2 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.util.calendar;
27
28import java.util.TimeZone;
29
30/**
31 * The {@code BaseCalendar} provides basic calendar calculation
32 * functions to support the Julian, Gregorian, and Gregorian-based
33 * calendar systems.
34 *
35 * @author Masayoshi Okutsu
36 * @since 1.5
37 */
38
39public abstract class BaseCalendar extends AbstractCalendar {
40
41    public static final int JANUARY = 1;
42    public static final int FEBRUARY = 2;
43    public static final int MARCH = 3;
44    public static final int APRIL = 4;
45    public static final int MAY = 5;
46    public static final int JUNE = 6;
47    public static final int JULY = 7;
48    public static final int AUGUST = 8;
49    public static final int SEPTEMBER = 9;
50    public static final int OCTOBER = 10;
51    public static final int NOVEMBER = 11;
52    public static final int DECEMBER = 12;
53
54    // day of week constants
55    public static final int SUNDAY = 1;
56    public static final int MONDAY = 2;
57    public static final int TUESDAY = 3;
58    public static final int WEDNESDAY = 4;
59    public static final int THURSDAY = 5;
60    public static final int FRIDAY = 6;
61    public static final int SATURDAY = 7;
62
63    // The base Gregorian year of FIXED_DATES[]
64    private static final int BASE_YEAR = 1970;
65
66    // Pre-calculated fixed dates of January 1 from BASE_YEAR
67    // (Gregorian). This table covers all the years that can be
68    // supported by the POSIX time_t (32-bit) after the Epoch. Note
69    // that the data type is int[].
70    private static final int[] FIXED_DATES = {
71        719163, // 1970
72        719528, // 1971
73        719893, // 1972
74        720259, // 1973
75        720624, // 1974
76        720989, // 1975
77        721354, // 1976
78        721720, // 1977
79        722085, // 1978
80        722450, // 1979
81        722815, // 1980
82        723181, // 1981
83        723546, // 1982
84        723911, // 1983
85        724276, // 1984
86        724642, // 1985
87        725007, // 1986
88        725372, // 1987
89        725737, // 1988
90        726103, // 1989
91        726468, // 1990
92        726833, // 1991
93        727198, // 1992
94        727564, // 1993
95        727929, // 1994
96        728294, // 1995
97        728659, // 1996
98        729025, // 1997
99        729390, // 1998
100        729755, // 1999
101        730120, // 2000
102        730486, // 2001
103        730851, // 2002
104        731216, // 2003
105        731581, // 2004
106        731947, // 2005
107        732312, // 2006
108        732677, // 2007
109        733042, // 2008
110        733408, // 2009
111        733773, // 2010
112        734138, // 2011
113        734503, // 2012
114        734869, // 2013
115        735234, // 2014
116        735599, // 2015
117        735964, // 2016
118        736330, // 2017
119        736695, // 2018
120        737060, // 2019
121        737425, // 2020
122        737791, // 2021
123        738156, // 2022
124        738521, // 2023
125        738886, // 2024
126        739252, // 2025
127        739617, // 2026
128        739982, // 2027
129        740347, // 2028
130        740713, // 2029
131        741078, // 2030
132        741443, // 2031
133        741808, // 2032
134        742174, // 2033
135        742539, // 2034
136        742904, // 2035
137        743269, // 2036
138        743635, // 2037
139        744000, // 2038
140        744365, // 2039
141    };
142
143    public abstract static class Date extends CalendarDate {
144        protected Date() {
145            super();
146        }
147        protected Date(TimeZone zone) {
148            super(zone);
149        }
150
151        public Date setNormalizedDate(int normalizedYear, int month, int dayOfMonth) {
152            setNormalizedYear(normalizedYear);
153            setMonth(month).setDayOfMonth(dayOfMonth);
154            return this;
155        }
156
157        public abstract int getNormalizedYear();
158
159        public abstract void setNormalizedYear(int normalizedYear);
160
161        // Cache for the fixed date of January 1 and year length of the
162        // cachedYear. A simple benchmark showed 7% performance
163        // improvement with >90% cache hit. The initial values are for Gregorian.
164        int cachedYear = 2004;
165        long cachedFixedDateJan1 = 731581L;
166        long cachedFixedDateNextJan1 = cachedFixedDateJan1 + 366;
167
168        protected final boolean hit(int year) {
169            return year == cachedYear;
170        }
171
172        protected final boolean hit(long fixedDate) {
173            return (fixedDate >= cachedFixedDateJan1 &&
174                    fixedDate < cachedFixedDateNextJan1);
175        }
176        protected int getCachedYear() {
177            return cachedYear;
178        }
179
180        protected long getCachedJan1() {
181            return cachedFixedDateJan1;
182        }
183
184        protected void setCache(int year, long jan1, int len) {
185            cachedYear = year;
186            cachedFixedDateJan1 = jan1;
187            cachedFixedDateNextJan1 = jan1 + len;
188        }
189    }
190
191    public boolean validate(CalendarDate date) {
192        Date bdate = (Date) date;
193        if (bdate.isNormalized()) {
194            return true;
195        }
196        int month = bdate.getMonth();
197        if (month < JANUARY || month > DECEMBER) {
198            return false;
199        }
200        int d = bdate.getDayOfMonth();
201        if (d <= 0 || d > getMonthLength(bdate.getNormalizedYear(), month)) {
202            return false;
203        }
204        int dow = bdate.getDayOfWeek();
205        if (dow != Date.FIELD_UNDEFINED && dow != getDayOfWeek(bdate)) {
206            return false;
207        }
208
209        if (!validateTime(date)) {
210            return false;
211        }
212
213        bdate.setNormalized(true);
214        return true;
215    }
216
217    public boolean normalize(CalendarDate date) {
218        if (date.isNormalized()) {
219            return true;
220        }
221
222        Date bdate = (Date) date;
223        TimeZone zi = bdate.getZone();
224
225        // If the date has a time zone, then we need to recalculate
226        // the calendar fields. Let getTime() do it.
227        if (zi != null) {
228            getTime(date);
229            return true;
230        }
231
232        int days = normalizeTime(bdate);
233        normalizeMonth(bdate);
234        long d = (long)bdate.getDayOfMonth() + days;
235        int m = bdate.getMonth();
236        int y = bdate.getNormalizedYear();
237        int ml = getMonthLength(y, m);
238
239        if (!(d > 0 && d <= ml)) {
240            if (d <= 0 && d > -28) {
241                ml = getMonthLength(y, --m);
242                d += ml;
243                bdate.setDayOfMonth((int) d);
244                if (m == 0) {
245                    m = DECEMBER;
246                    bdate.setNormalizedYear(y - 1);
247                }
248                bdate.setMonth(m);
249            } else if (d > ml && d < (ml + 28)) {
250                d -= ml;
251                ++m;
252                bdate.setDayOfMonth((int)d);
253                if (m > DECEMBER) {
254                    bdate.setNormalizedYear(y + 1);
255                    m = JANUARY;
256                }
257                bdate.setMonth(m);
258            } else {
259                long fixedDate = d + getFixedDate(y, m, 1, bdate) - 1L;
260                getCalendarDateFromFixedDate(bdate, fixedDate);
261            }
262        } else {
263            bdate.setDayOfWeek(getDayOfWeek(bdate));
264        }
265        date.setLeapYear(isLeapYear(bdate.getNormalizedYear()));
266        date.setZoneOffset(0);
267        date.setDaylightSaving(0);
268        bdate.setNormalized(true);
269        return true;
270    }
271
272    void normalizeMonth(CalendarDate date) {
273        Date bdate = (Date) date;
274        int year = bdate.getNormalizedYear();
275        long month = bdate.getMonth();
276        if (month <= 0) {
277            long xm = 1L - month;
278            year -= (int)((xm / 12) + 1);
279            month = 13 - (xm % 12);
280            bdate.setNormalizedYear(year);
281            bdate.setMonth((int) month);
282        } else if (month > DECEMBER) {
283            year += (int)((month - 1) / 12);
284            month = ((month - 1)) % 12 + 1;
285            bdate.setNormalizedYear(year);
286            bdate.setMonth((int) month);
287        }
288    }
289
290    /**
291     * Returns 366 if the specified date is in a leap year, or 365
292     * otherwise This method does not perform the normalization with
293     * the specified {@code CalendarDate}. The
294     * {@code CalendarDate} must be normalized to get a correct
295     * value.
296     *
297     * @param date a {@code CalendarDate}
298     * @return a year length in days
299     * @throws ClassCastException if the specified date is not a
300     * {@link BaseCalendar.Date}
301     */
302    public int getYearLength(CalendarDate date) {
303        return isLeapYear(((Date)date).getNormalizedYear()) ? 366 : 365;
304    }
305
306    public int getYearLengthInMonths(CalendarDate date) {
307        return 12;
308    }
309
310    static final int[] DAYS_IN_MONTH
311        //  12   1   2   3   4   5   6   7   8   9  10  11  12
312        = { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
313    static final int[] ACCUMULATED_DAYS_IN_MONTH
314        //  12/1 1/1 2/1 3/1 4/1 5/1 6/1 7/1 8/1 9/1 10/1 11/1 12/1
315        = {  -30,  0, 31, 59, 90,120,151,181,212,243, 273, 304, 334};
316
317    static final int[] ACCUMULATED_DAYS_IN_MONTH_LEAP
318        //  12/1 1/1 2/1   3/1   4/1   5/1   6/1   7/1   8/1   9/1   10/1   11/1   12/1
319        = {  -30,  0, 31, 59+1, 90+1,120+1,151+1,181+1,212+1,243+1, 273+1, 304+1, 334+1};
320
321    public int getMonthLength(CalendarDate date) {
322        Date gdate = (Date) date;
323        int month = gdate.getMonth();
324        if (month < JANUARY || month > DECEMBER) {
325            throw new IllegalArgumentException("Illegal month value: " + month);
326        }
327        return getMonthLength(gdate.getNormalizedYear(), month);
328    }
329
330    // accepts 0 (December in the previous year) to 12.
331    private int getMonthLength(int year, int month) {
332        int days = DAYS_IN_MONTH[month];
333        if (month == FEBRUARY && isLeapYear(year)) {
334            days++;
335        }
336        return days;
337    }
338
339    public long getDayOfYear(CalendarDate date) {
340        return getDayOfYear(((Date)date).getNormalizedYear(),
341                            date.getMonth(),
342                            date.getDayOfMonth());
343    }
344
345    final long getDayOfYear(int year, int month, int dayOfMonth) {
346        return (long) dayOfMonth
347            + (isLeapYear(year) ?
348               ACCUMULATED_DAYS_IN_MONTH_LEAP[month] : ACCUMULATED_DAYS_IN_MONTH[month]);
349    }
350
351    // protected
352    public long getFixedDate(CalendarDate date) {
353        if (!date.isNormalized()) {
354            normalizeMonth(date);
355        }
356        return getFixedDate(((Date)date).getNormalizedYear(),
357                            date.getMonth(),
358                            date.getDayOfMonth(),
359                            (BaseCalendar.Date) date);
360    }
361
362    // public for java.util.GregorianCalendar
363    public long getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache) {
364        boolean isJan1 = month == JANUARY && dayOfMonth == 1;
365
366        // Look up the one year cache
367        if (cache != null && cache.hit(year)) {
368            if (isJan1) {
369                return cache.getCachedJan1();
370            }
371            return cache.getCachedJan1() + getDayOfYear(year, month, dayOfMonth) - 1;
372        }
373
374        // Look up the pre-calculated fixed date table
375        int n = year - BASE_YEAR;
376        if (n >= 0 && n < FIXED_DATES.length) {
377            long jan1 = FIXED_DATES[n];
378            if (cache != null) {
379                cache.setCache(year, jan1, isLeapYear(year) ? 366 : 365);
380            }
381            return isJan1 ? jan1 : jan1 + getDayOfYear(year, month, dayOfMonth) - 1;
382        }
383
384        long prevyear = (long)year - 1;
385        long days = dayOfMonth;
386
387        if (prevyear >= 0) {
388            days += (365 * prevyear)
389                   + (prevyear / 4)
390                   - (prevyear / 100)
391                   + (prevyear / 400)
392                   + ((367 * month - 362) / 12);
393        } else {
394            days += (365 * prevyear)
395                   + CalendarUtils.floorDivide(prevyear, 4)
396                   - CalendarUtils.floorDivide(prevyear, 100)
397                   + CalendarUtils.floorDivide(prevyear, 400)
398                   + CalendarUtils.floorDivide((367 * month - 362), 12);
399        }
400
401        if (month > FEBRUARY) {
402            days -=  isLeapYear(year) ? 1 : 2;
403        }
404
405        // If it's January 1, update the cache.
406        if (cache != null && isJan1) {
407            cache.setCache(year, days, isLeapYear(year) ? 366 : 365);
408        }
409
410        return days;
411    }
412
413    /**
414     * Calculates calendar fields and store them in the specified
415     * {@code CalendarDate}.
416     */
417    // should be 'protected'
418    public void getCalendarDateFromFixedDate(CalendarDate date,
419                                             long fixedDate) {
420        Date gdate = (Date) date;
421        int year;
422        long jan1;
423        boolean isLeap;
424        if (gdate.hit(fixedDate)) {
425            year = gdate.getCachedYear();
426            jan1 = gdate.getCachedJan1();
427            isLeap = isLeapYear(year);
428        } else {
429            // Looking up FIXED_DATES[] here didn't improve performance
430            // much. So we calculate year and jan1. getFixedDate()
431            // will look up FIXED_DATES[] actually.
432            year = getGregorianYearFromFixedDate(fixedDate);
433            jan1 = getFixedDate(year, JANUARY, 1, null);
434            isLeap = isLeapYear(year);
435            // Update the cache data
436            gdate.setCache (year, jan1, isLeap ? 366 : 365);
437        }
438
439        int priorDays = (int)(fixedDate - jan1);
440        long mar1 = jan1 + 31 + 28;
441        if (isLeap) {
442            ++mar1;
443        }
444        if (fixedDate >= mar1) {
445            priorDays += isLeap ? 1 : 2;
446        }
447        int month = 12 * priorDays + 373;
448        if (month > 0) {
449            month /= 367;
450        } else {
451            month = CalendarUtils.floorDivide(month, 367);
452        }
453        long month1 = jan1 + ACCUMULATED_DAYS_IN_MONTH[month];
454        if (isLeap && month >= MARCH) {
455            ++month1;
456        }
457        int dayOfMonth = (int)(fixedDate - month1) + 1;
458        int dayOfWeek = getDayOfWeekFromFixedDate(fixedDate);
459        assert dayOfWeek > 0 : "negative day of week " + dayOfWeek;
460        gdate.setNormalizedYear(year);
461        gdate.setMonth(month);
462        gdate.setDayOfMonth(dayOfMonth);
463        gdate.setDayOfWeek(dayOfWeek);
464        gdate.setLeapYear(isLeap);
465        gdate.setNormalized(true);
466    }
467
468    /**
469     * Returns the day of week of the given Gregorian date.
470     */
471    public int getDayOfWeek(CalendarDate date) {
472        long fixedDate = getFixedDate(date);
473        return getDayOfWeekFromFixedDate(fixedDate);
474    }
475
476    public static final int getDayOfWeekFromFixedDate(long fixedDate) {
477        // The fixed day 1 (January 1, 1 Gregorian) is Monday.
478        if (fixedDate >= 0) {
479            return (int)(fixedDate % 7) + SUNDAY;
480        }
481        return (int)CalendarUtils.mod(fixedDate, 7) + SUNDAY;
482    }
483
484    public int getYearFromFixedDate(long fixedDate) {
485        return getGregorianYearFromFixedDate(fixedDate);
486    }
487
488    /**
489     * Returns the Gregorian year number of the given fixed date.
490     */
491    final int getGregorianYearFromFixedDate(long fixedDate) {
492        long d0;
493        int  d1, d2, d3, d4;
494        int  n400, n100, n4, n1;
495        int  year;
496
497        if (fixedDate > 0) {
498            d0 = fixedDate - 1;
499            n400 = (int)(d0 / 146097);
500            d1 = (int)(d0 % 146097);
501            n100 = d1 / 36524;
502            d2 = d1 % 36524;
503            n4 = d2 / 1461;
504            d3 = d2 % 1461;
505            n1 = d3 / 365;
506            d4 = (d3 % 365) + 1;
507        } else {
508            d0 = fixedDate - 1;
509            n400 = (int)CalendarUtils.floorDivide(d0, 146097L);
510            d1 = (int)CalendarUtils.mod(d0, 146097L);
511            n100 = CalendarUtils.floorDivide(d1, 36524);
512            d2 = CalendarUtils.mod(d1, 36524);
513            n4 = CalendarUtils.floorDivide(d2, 1461);
514            d3 = CalendarUtils.mod(d2, 1461);
515            n1 = CalendarUtils.floorDivide(d3, 365);
516            d4 = CalendarUtils.mod(d3, 365) + 1;
517        }
518        year = 400 * n400 + 100 * n100 + 4 * n4 + n1;
519        if (!(n100 == 4 || n1 == 4)) {
520            ++year;
521        }
522        return year;
523    }
524
525    /**
526     * @return true if the specified year is a Gregorian leap year, or
527     * false otherwise.
528     * @see BaseCalendar#isGregorianLeapYear
529     */
530    protected boolean isLeapYear(CalendarDate date) {
531        return isLeapYear(((Date)date).getNormalizedYear());
532    }
533
534    boolean isLeapYear(int normalizedYear) {
535        return CalendarUtils.isGregorianLeapYear(normalizedYear);
536    }
537}
538