View Javadoc
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.TimeZone;
29  
30  /**
31   * The <code>BaseCalendar</code> 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  
39  public 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</code>. The
294      * <code>CalendarDate</code> must be normalized to get a correct
295      * value.
296      *
297      * @param a <code>CalendarDate</code>
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</code>.
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 }