1 /*
2  * Copyright (c) 2003, 2021, 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} 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 {
Date()144         protected Date() {
145             super();
146         }
Date(TimeZone zone)147         protected Date(TimeZone zone) {
148             super(zone);
149         }
150 
setNormalizedDate(int normalizedYear, int month, int dayOfMonth)151         public Date setNormalizedDate(int normalizedYear, int month, int dayOfMonth) {
152             setNormalizedYear(normalizedYear);
153             setMonth(month).setDayOfMonth(dayOfMonth);
154             return this;
155         }
156 
getNormalizedYear()157         public abstract int getNormalizedYear();
158 
setNormalizedYear(int normalizedYear)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 
hit(int year)168         protected final boolean hit(int year) {
169             return year == cachedYear;
170         }
171 
hit(long fixedDate)172         protected final boolean hit(long fixedDate) {
173             return (fixedDate >= cachedFixedDateJan1 &&
174                     fixedDate < cachedFixedDateNextJan1);
175         }
getCachedYear()176         protected int getCachedYear() {
177             return cachedYear;
178         }
179 
getCachedJan1()180         protected long getCachedJan1() {
181             return cachedFixedDateJan1;
182         }
183 
setCache(int year, long jan1, int len)184         protected void setCache(int year, long jan1, int len) {
185             cachedYear = year;
186             cachedFixedDateJan1 = jan1;
187             cachedFixedDateNextJan1 = jan1 + len;
188         }
189     }
190 
validate(CalendarDate date)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 
normalize(CalendarDate date)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 
normalizeMonth(CalendarDate date)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             if (month == 13) {
281                 year++;
282                 month = 1;
283             }
284             bdate.setNormalizedYear(year);
285             bdate.setMonth((int) month);
286         } else if (month > DECEMBER) {
287             year += (int)((month - 1) / 12);
288             month = ((month - 1)) % 12 + 1;
289             bdate.setNormalizedYear(year);
290             bdate.setMonth((int) month);
291         }
292     }
293 
294     /**
295      * Returns 366 if the specified date is in a leap year, or 365
296      * otherwise This method does not perform the normalization with
297      * the specified {@code CalendarDate}. The
298      * {@code CalendarDate} must be normalized to get a correct
299      * value.
300      *
301      * @param date a {@code CalendarDate}
302      * @return a year length in days
303      * @throws ClassCastException if the specified date is not a
304      * {@link BaseCalendar.Date}
305      */
getYearLength(CalendarDate date)306     public int getYearLength(CalendarDate date) {
307         return isLeapYear(((Date)date).getNormalizedYear()) ? 366 : 365;
308     }
309 
getYearLengthInMonths(CalendarDate date)310     public int getYearLengthInMonths(CalendarDate date) {
311         return 12;
312     }
313 
314     static final int[] DAYS_IN_MONTH
315         //  12   1   2   3   4   5   6   7   8   9  10  11  12
316         = { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
317     static final int[] ACCUMULATED_DAYS_IN_MONTH
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, 90,120,151,181,212,243, 273, 304, 334};
320 
321     static final int[] ACCUMULATED_DAYS_IN_MONTH_LEAP
322         //  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
323         = {  -30,  0, 31, 59+1, 90+1,120+1,151+1,181+1,212+1,243+1, 273+1, 304+1, 334+1};
324 
getMonthLength(CalendarDate date)325     public int getMonthLength(CalendarDate date) {
326         Date gdate = (Date) date;
327         int month = gdate.getMonth();
328         if (month < JANUARY || month > DECEMBER) {
329             throw new IllegalArgumentException("Illegal month value: " + month);
330         }
331         return getMonthLength(gdate.getNormalizedYear(), month);
332     }
333 
334     // accepts 0 (December in the previous year) to 12.
getMonthLength(int year, int month)335     private int getMonthLength(int year, int month) {
336         int days = DAYS_IN_MONTH[month];
337         if (month == FEBRUARY && isLeapYear(year)) {
338             days++;
339         }
340         return days;
341     }
342 
getDayOfYear(CalendarDate date)343     public long getDayOfYear(CalendarDate date) {
344         return getDayOfYear(((Date)date).getNormalizedYear(),
345                             date.getMonth(),
346                             date.getDayOfMonth());
347     }
348 
getDayOfYear(int year, int month, int dayOfMonth)349     final long getDayOfYear(int year, int month, int dayOfMonth) {
350         return (long) dayOfMonth
351             + (isLeapYear(year) ?
352                ACCUMULATED_DAYS_IN_MONTH_LEAP[month] : ACCUMULATED_DAYS_IN_MONTH[month]);
353     }
354 
355     // protected
getFixedDate(CalendarDate date)356     public long getFixedDate(CalendarDate date) {
357         if (!date.isNormalized()) {
358             normalizeMonth(date);
359         }
360         return getFixedDate(((Date)date).getNormalizedYear(),
361                             date.getMonth(),
362                             date.getDayOfMonth(),
363                             (BaseCalendar.Date) date);
364     }
365 
366     // public for java.util.GregorianCalendar
getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache)367     public long getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache) {
368         boolean isJan1 = month == JANUARY && dayOfMonth == 1;
369 
370         // Look up the one year cache
371         if (cache != null && cache.hit(year)) {
372             if (isJan1) {
373                 return cache.getCachedJan1();
374             }
375             return cache.getCachedJan1() + getDayOfYear(year, month, dayOfMonth) - 1;
376         }
377 
378         // Look up the pre-calculated fixed date table
379         int n = year - BASE_YEAR;
380         if (n >= 0 && n < FIXED_DATES.length) {
381             long jan1 = FIXED_DATES[n];
382             if (cache != null) {
383                 cache.setCache(year, jan1, isLeapYear(year) ? 366 : 365);
384             }
385             return isJan1 ? jan1 : jan1 + getDayOfYear(year, month, dayOfMonth) - 1;
386         }
387 
388         long prevyear = (long)year - 1;
389         long days = dayOfMonth;
390 
391         if (prevyear >= 0) {
392             days += (365 * prevyear)
393                    + (prevyear / 4)
394                    - (prevyear / 100)
395                    + (prevyear / 400)
396                    + ((367 * month - 362) / 12);
397         } else {
398             days += (365 * prevyear)
399                    + CalendarUtils.floorDivide(prevyear, 4)
400                    - CalendarUtils.floorDivide(prevyear, 100)
401                    + CalendarUtils.floorDivide(prevyear, 400)
402                    + CalendarUtils.floorDivide((367 * month - 362), 12);
403         }
404 
405         if (month > FEBRUARY) {
406             days -=  isLeapYear(year) ? 1 : 2;
407         }
408 
409         // If it's January 1, update the cache.
410         if (cache != null && isJan1) {
411             cache.setCache(year, days, isLeapYear(year) ? 366 : 365);
412         }
413 
414         return days;
415     }
416 
417     /**
418      * Calculates calendar fields and store them in the specified
419      * {@code CalendarDate}.
420      */
421     // should be 'protected'
getCalendarDateFromFixedDate(CalendarDate date, long fixedDate)422     public void getCalendarDateFromFixedDate(CalendarDate date,
423                                              long fixedDate) {
424         Date gdate = (Date) date;
425         int year;
426         long jan1;
427         boolean isLeap;
428         if (gdate.hit(fixedDate)) {
429             year = gdate.getCachedYear();
430             jan1 = gdate.getCachedJan1();
431             isLeap = isLeapYear(year);
432         } else {
433             // Looking up FIXED_DATES[] here didn't improve performance
434             // much. So we calculate year and jan1. getFixedDate()
435             // will look up FIXED_DATES[] actually.
436             year = getGregorianYearFromFixedDate(fixedDate);
437             jan1 = getFixedDate(year, JANUARY, 1, null);
438             isLeap = isLeapYear(year);
439             // Update the cache data
440             gdate.setCache (year, jan1, isLeap ? 366 : 365);
441         }
442 
443         int priorDays = (int)(fixedDate - jan1);
444         long mar1 = jan1 + 31 + 28;
445         if (isLeap) {
446             ++mar1;
447         }
448         if (fixedDate >= mar1) {
449             priorDays += isLeap ? 1 : 2;
450         }
451         int month = 12 * priorDays + 373;
452         if (month > 0) {
453             month /= 367;
454         } else {
455             month = CalendarUtils.floorDivide(month, 367);
456         }
457         long month1 = jan1 + ACCUMULATED_DAYS_IN_MONTH[month];
458         if (isLeap && month >= MARCH) {
459             ++month1;
460         }
461         int dayOfMonth = (int)(fixedDate - month1) + 1;
462         int dayOfWeek = getDayOfWeekFromFixedDate(fixedDate);
463         assert dayOfWeek > 0 : "negative day of week " + dayOfWeek;
464         gdate.setNormalizedYear(year);
465         gdate.setMonth(month);
466         gdate.setDayOfMonth(dayOfMonth);
467         gdate.setDayOfWeek(dayOfWeek);
468         gdate.setLeapYear(isLeap);
469         gdate.setNormalized(true);
470     }
471 
472     /**
473      * Returns the day of week of the given Gregorian date.
474      */
getDayOfWeek(CalendarDate date)475     public int getDayOfWeek(CalendarDate date) {
476         long fixedDate = getFixedDate(date);
477         return getDayOfWeekFromFixedDate(fixedDate);
478     }
479 
getDayOfWeekFromFixedDate(long fixedDate)480     public static final int getDayOfWeekFromFixedDate(long fixedDate) {
481         // The fixed day 1 (January 1, 1 Gregorian) is Monday.
482         if (fixedDate >= 0) {
483             return (int)(fixedDate % 7) + SUNDAY;
484         }
485         return (int)CalendarUtils.mod(fixedDate, 7) + SUNDAY;
486     }
487 
getYearFromFixedDate(long fixedDate)488     public int getYearFromFixedDate(long fixedDate) {
489         return getGregorianYearFromFixedDate(fixedDate);
490     }
491 
492     /**
493      * Returns the Gregorian year number of the given fixed date.
494      */
getGregorianYearFromFixedDate(long fixedDate)495     final int getGregorianYearFromFixedDate(long fixedDate) {
496         long d0;
497         int  d1, d2, d3, d4;
498         int  n400, n100, n4, n1;
499         int  year;
500 
501         if (fixedDate > 0) {
502             d0 = fixedDate - 1;
503             n400 = (int)(d0 / 146097);
504             d1 = (int)(d0 % 146097);
505             n100 = d1 / 36524;
506             d2 = d1 % 36524;
507             n4 = d2 / 1461;
508             d3 = d2 % 1461;
509             n1 = d3 / 365;
510             d4 = (d3 % 365) + 1;
511         } else {
512             d0 = fixedDate - 1;
513             n400 = (int)CalendarUtils.floorDivide(d0, 146097L);
514             d1 = (int)CalendarUtils.mod(d0, 146097L);
515             n100 = CalendarUtils.floorDivide(d1, 36524);
516             d2 = CalendarUtils.mod(d1, 36524);
517             n4 = CalendarUtils.floorDivide(d2, 1461);
518             d3 = CalendarUtils.mod(d2, 1461);
519             n1 = CalendarUtils.floorDivide(d3, 365);
520             d4 = CalendarUtils.mod(d3, 365) + 1;
521         }
522         year = 400 * n400 + 100 * n100 + 4 * n4 + n1;
523         if (!(n100 == 4 || n1 == 4)) {
524             ++year;
525         }
526         return year;
527     }
528 
529     /**
530      * @return true if the specified year is a Gregorian leap year, or
531      * false otherwise.
532      * @see BaseCalendar#isGregorianLeapYear
533      */
isLeapYear(CalendarDate date)534     protected boolean isLeapYear(CalendarDate date) {
535         return isLeapYear(((Date)date).getNormalizedYear());
536     }
537 
isLeapYear(int normalizedYear)538     boolean isLeapYear(int normalizedYear) {
539         return CalendarUtils.isGregorianLeapYear(normalizedYear);
540     }
541 }
542