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