summaryrefslogtreecommitdiff
path: root/ojluni/src/main/java/sun/util/calendar/BaseCalendar.java
blob: a7e763a0d7cbbe4fcf97029ff3a166cd21822d4e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
/*
 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.util.calendar;

import java.util.TimeZone;

/**
 * The <code>BaseCalendar</code> provides basic calendar calculation
 * functions to support the Julian, Gregorian, and Gregorian-based
 * calendar systems.
 *
 * @author Masayoshi Okutsu
 * @since 1.5
 */

public abstract class BaseCalendar extends AbstractCalendar {

    public static final int JANUARY = 1;
    public static final int FEBRUARY = 2;
    public static final int MARCH = 3;
    public static final int APRIL = 4;
    public static final int MAY = 5;
    public static final int JUNE = 6;
    public static final int JULY = 7;
    public static final int AUGUST = 8;
    public static final int SEPTEMBER = 9;
    public static final int OCTOBER = 10;
    public static final int NOVEMBER = 11;
    public static final int DECEMBER = 12;

    // day of week constants
    public static final int SUNDAY = 1;
    public static final int MONDAY = 2;
    public static final int TUESDAY = 3;
    public static final int WEDNESDAY = 4;
    public static final int THURSDAY = 5;
    public static final int FRIDAY = 6;
    public static final int SATURDAY = 7;

    // The base Gregorian year of FIXED_DATES[]
    private static final int BASE_YEAR = 1970;

    // Pre-calculated fixed dates of January 1 from BASE_YEAR
    // (Gregorian). This table covers all the years that can be
    // supported by the POSIX time_t (32-bit) after the Epoch. Note
    // that the data type is int[].
    private static final int[] FIXED_DATES = {
        719163, // 1970
        719528, // 1971
        719893, // 1972
        720259, // 1973
        720624, // 1974
        720989, // 1975
        721354, // 1976
        721720, // 1977
        722085, // 1978
        722450, // 1979
        722815, // 1980
        723181, // 1981
        723546, // 1982
        723911, // 1983
        724276, // 1984
        724642, // 1985
        725007, // 1986
        725372, // 1987
        725737, // 1988
        726103, // 1989
        726468, // 1990
        726833, // 1991
        727198, // 1992
        727564, // 1993
        727929, // 1994
        728294, // 1995
        728659, // 1996
        729025, // 1997
        729390, // 1998
        729755, // 1999
        730120, // 2000
        730486, // 2001
        730851, // 2002
        731216, // 2003
        731581, // 2004
        731947, // 2005
        732312, // 2006
        732677, // 2007
        733042, // 2008
        733408, // 2009
        733773, // 2010
        734138, // 2011
        734503, // 2012
        734869, // 2013
        735234, // 2014
        735599, // 2015
        735964, // 2016
        736330, // 2017
        736695, // 2018
        737060, // 2019
        737425, // 2020
        737791, // 2021
        738156, // 2022
        738521, // 2023
        738886, // 2024
        739252, // 2025
        739617, // 2026
        739982, // 2027
        740347, // 2028
        740713, // 2029
        741078, // 2030
        741443, // 2031
        741808, // 2032
        742174, // 2033
        742539, // 2034
        742904, // 2035
        743269, // 2036
        743635, // 2037
        744000, // 2038
        744365, // 2039
    };

    public abstract static class Date extends CalendarDate {
        protected Date() {
            super();
        }
        protected Date(TimeZone zone) {
            super(zone);
        }

        public Date setNormalizedDate(int normalizedYear, int month, int dayOfMonth) {
            setNormalizedYear(normalizedYear);
            setMonth(month).setDayOfMonth(dayOfMonth);
            return this;
        }

        public abstract int getNormalizedYear();

        public abstract void setNormalizedYear(int normalizedYear);

        // Cache for the fixed date of January 1 and year length of the
        // cachedYear. A simple benchmark showed 7% performance
        // improvement with >90% cache hit. The initial values are for Gregorian.
        int cachedYear = 2004;
        long cachedFixedDateJan1 = 731581L;
        long cachedFixedDateNextJan1 = cachedFixedDateJan1 + 366;

        protected final boolean hit(int year) {
            return year == cachedYear;
        }

        protected final boolean hit(long fixedDate) {
            return (fixedDate >= cachedFixedDateJan1 &&
                    fixedDate < cachedFixedDateNextJan1);
        }
        protected int getCachedYear() {
            return cachedYear;
        }

        protected long getCachedJan1() {
            return cachedFixedDateJan1;
        }

        protected void setCache(int year, long jan1, int len) {
            cachedYear = year;
            cachedFixedDateJan1 = jan1;
            cachedFixedDateNextJan1 = jan1 + len;
        }
    }

    public boolean validate(CalendarDate date) {
        Date bdate = (Date) date;
        if (bdate.isNormalized()) {
            return true;
        }
        int month = bdate.getMonth();
        if (month < JANUARY || month > DECEMBER) {
            return false;
        }
        int d = bdate.getDayOfMonth();
        if (d <= 0 || d > getMonthLength(bdate.getNormalizedYear(), month)) {
            return false;
        }
        int dow = bdate.getDayOfWeek();
        if (dow != Date.FIELD_UNDEFINED && dow != getDayOfWeek(bdate)) {
            return false;
        }

        if (!validateTime(date)) {
            return false;
        }

        bdate.setNormalized(true);
        return true;
    }

    public boolean normalize(CalendarDate date) {
        if (date.isNormalized()) {
            return true;
        }

        Date bdate = (Date) date;
        TimeZone zi = bdate.getZone();

        // If the date has a time zone, then we need to recalculate
        // the calendar fields. Let getTime() do it.
        if (zi != null) {
            getTime(date);
            return true;
        }

        int days = normalizeTime(bdate);
        normalizeMonth(bdate);
        long d = (long)bdate.getDayOfMonth() + days;
        int m = bdate.getMonth();
        int y = bdate.getNormalizedYear();
        int ml = getMonthLength(y, m);

        if (!(d > 0 && d <= ml)) {
            if (d <= 0 && d > -28) {
                ml = getMonthLength(y, --m);
                d += ml;
                bdate.setDayOfMonth((int) d);
                if (m == 0) {
                    m = DECEMBER;
                    bdate.setNormalizedYear(y - 1);
                }
                bdate.setMonth(m);
            } else if (d > ml && d < (ml + 28)) {
                d -= ml;
                ++m;
                bdate.setDayOfMonth((int)d);
                if (m > DECEMBER) {
                    bdate.setNormalizedYear(y + 1);
                    m = JANUARY;
                }
                bdate.setMonth(m);
            } else {
                long fixedDate = d + getFixedDate(y, m, 1, bdate) - 1L;
                getCalendarDateFromFixedDate(bdate, fixedDate);
            }
        } else {
            bdate.setDayOfWeek(getDayOfWeek(bdate));
        }
        date.setLeapYear(isLeapYear(bdate.getNormalizedYear()));
        date.setZoneOffset(0);
        date.setDaylightSaving(0);
        bdate.setNormalized(true);
        return true;
    }

    void normalizeMonth(CalendarDate date) {
        Date bdate = (Date) date;
        int year = bdate.getNormalizedYear();
        long month = bdate.getMonth();
        if (month <= 0) {
            long xm = 1L - month;
            year -= (int)((xm / 12) + 1);
            month = 13 - (xm % 12);
            bdate.setNormalizedYear(year);
            bdate.setMonth((int) month);
        } else if (month > DECEMBER) {
            year += (int)((month - 1) / 12);
            month = ((month - 1)) % 12 + 1;
            bdate.setNormalizedYear(year);
            bdate.setMonth((int) month);
        }
    }

    /**
     * Returns 366 if the specified date is in a leap year, or 365
     * otherwise This method does not perform the normalization with
     * the specified <code>CalendarDate</code>. The
     * <code>CalendarDate</code> must be normalized to get a correct
     * value.
     *
     * @param a <code>CalendarDate</code>
     * @return a year length in days
     * @throws ClassCastException if the specified date is not a
     * {@link BaseCalendar.Date}
     */
    public int getYearLength(CalendarDate date) {
        return isLeapYear(((Date)date).getNormalizedYear()) ? 366 : 365;
    }

    public int getYearLengthInMonths(CalendarDate date) {
        return 12;
    }

    static final int[] DAYS_IN_MONTH
        //  12   1   2   3   4   5   6   7   8   9  10  11  12
        = { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    static final int[] ACCUMULATED_DAYS_IN_MONTH
        //  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
        = {  -30,  0, 31, 59, 90,120,151,181,212,243, 273, 304, 334};

    static final int[] ACCUMULATED_DAYS_IN_MONTH_LEAP
        //  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
        = {  -30,  0, 31, 59+1, 90+1,120+1,151+1,181+1,212+1,243+1, 273+1, 304+1, 334+1};

    public int getMonthLength(CalendarDate date) {
        Date gdate = (Date) date;
        int month = gdate.getMonth();
        if (month < JANUARY || month > DECEMBER) {
            throw new IllegalArgumentException("Illegal month value: " + month);
        }
        return getMonthLength(gdate.getNormalizedYear(), month);
    }

    // accepts 0 (December in the previous year) to 12.
    private int getMonthLength(int year, int month) {
        int days = DAYS_IN_MONTH[month];
        if (month == FEBRUARY && isLeapYear(year)) {
            days++;
        }
        return days;
    }

    public long getDayOfYear(CalendarDate date) {
        return getDayOfYear(((Date)date).getNormalizedYear(),
                            date.getMonth(),
                            date.getDayOfMonth());
    }

    final long getDayOfYear(int year, int month, int dayOfMonth) {
        return (long) dayOfMonth
            + (isLeapYear(year) ?
               ACCUMULATED_DAYS_IN_MONTH_LEAP[month] : ACCUMULATED_DAYS_IN_MONTH[month]);
    }

    // protected
    public long getFixedDate(CalendarDate date) {
        if (!date.isNormalized()) {
            normalizeMonth(date);
        }
        return getFixedDate(((Date)date).getNormalizedYear(),
                            date.getMonth(),
                            date.getDayOfMonth(),
                            (BaseCalendar.Date) date);
    }

    // public for java.util.GregorianCalendar
    public long getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache) {
        boolean isJan1 = month == JANUARY && dayOfMonth == 1;

        // Look up the one year cache
        if (cache != null && cache.hit(year)) {
            if (isJan1) {
                return cache.getCachedJan1();
            }
            return cache.getCachedJan1() + getDayOfYear(year, month, dayOfMonth) - 1;
        }

        // Look up the pre-calculated fixed date table
        int n = year - BASE_YEAR;
        if (n >= 0 && n < FIXED_DATES.length) {
            long jan1 = FIXED_DATES[n];
            if (cache != null) {
                cache.setCache(year, jan1, isLeapYear(year) ? 366 : 365);
            }
            return isJan1 ? jan1 : jan1 + getDayOfYear(year, month, dayOfMonth) - 1;
        }

        long prevyear = (long)year - 1;
        long days = dayOfMonth;

        if (prevyear >= 0) {
            days += (365 * prevyear)
                   + (prevyear / 4)
                   - (prevyear / 100)
                   + (prevyear / 400)
                   + ((367 * month - 362) / 12);
        } else {
            days += (365 * prevyear)
                   + CalendarUtils.floorDivide(prevyear, 4)
                   - CalendarUtils.floorDivide(prevyear, 100)
                   + CalendarUtils.floorDivide(prevyear, 400)
                   + CalendarUtils.floorDivide((367 * month - 362), 12);
        }

        if (month > FEBRUARY) {
            days -=  isLeapYear(year) ? 1 : 2;
        }

        // If it's January 1, update the cache.
        if (cache != null && isJan1) {
            cache.setCache(year, days, isLeapYear(year) ? 366 : 365);
        }

        return days;
    }

    /**
     * Calculates calendar fields and store them in the specified
     * <code>CalendarDate</code>.
     */
    // should be 'protected'
    public void getCalendarDateFromFixedDate(CalendarDate date,
                                             long fixedDate) {
        Date gdate = (Date) date;
        int year;
        long jan1;
        boolean isLeap;
        if (gdate.hit(fixedDate)) {
            year = gdate.getCachedYear();
            jan1 = gdate.getCachedJan1();
            isLeap = isLeapYear(year);
        } else {
            // Looking up FIXED_DATES[] here didn't improve performance
            // much. So we calculate year and jan1. getFixedDate()
            // will look up FIXED_DATES[] actually.
            year = getGregorianYearFromFixedDate(fixedDate);
            jan1 = getFixedDate(year, JANUARY, 1, null);
            isLeap = isLeapYear(year);
            // Update the cache data
            gdate.setCache (year, jan1, isLeap ? 366 : 365);
        }

        int priorDays = (int)(fixedDate - jan1);
        long mar1 = jan1 + 31 + 28;
        if (isLeap) {
            ++mar1;
        }
        if (fixedDate >= mar1) {
            priorDays += isLeap ? 1 : 2;
        }
        int month = 12 * priorDays + 373;
        if (month > 0) {
            month /= 367;
        } else {
            month = CalendarUtils.floorDivide(month, 367);
        }
        long month1 = jan1 + ACCUMULATED_DAYS_IN_MONTH[month];
        if (isLeap && month >= MARCH) {
            ++month1;
        }
        int dayOfMonth = (int)(fixedDate - month1) + 1;
        int dayOfWeek = getDayOfWeekFromFixedDate(fixedDate);
        assert dayOfWeek > 0 : "negative day of week " + dayOfWeek;
        gdate.setNormalizedYear(year);
        gdate.setMonth(month);
        gdate.setDayOfMonth(dayOfMonth);
        gdate.setDayOfWeek(dayOfWeek);
        gdate.setLeapYear(isLeap);
        gdate.setNormalized(true);
    }

    /**
     * Returns the day of week of the given Gregorian date.
     */
    public int getDayOfWeek(CalendarDate date) {
        long fixedDate = getFixedDate(date);
        return getDayOfWeekFromFixedDate(fixedDate);
    }

    public static final int getDayOfWeekFromFixedDate(long fixedDate) {
        // The fixed day 1 (January 1, 1 Gregorian) is Monday.
        if (fixedDate >= 0) {
            return (int)(fixedDate % 7) + SUNDAY;
        }
        return (int)CalendarUtils.mod(fixedDate, 7) + SUNDAY;
    }

    public int getYearFromFixedDate(long fixedDate) {
        return getGregorianYearFromFixedDate(fixedDate);
    }

    /**
     * Returns the Gregorian year number of the given fixed date.
     */
    final int getGregorianYearFromFixedDate(long fixedDate) {
        long d0;
        int  d1, d2, d3, d4;
        int  n400, n100, n4, n1;
        int  year;

        if (fixedDate > 0) {
            d0 = fixedDate - 1;
            n400 = (int)(d0 / 146097);
            d1 = (int)(d0 % 146097);
            n100 = d1 / 36524;
            d2 = d1 % 36524;
            n4 = d2 / 1461;
            d3 = d2 % 1461;
            n1 = d3 / 365;
            d4 = (d3 % 365) + 1;
        } else {
            d0 = fixedDate - 1;
            n400 = (int)CalendarUtils.floorDivide(d0, 146097L);
            d1 = (int)CalendarUtils.mod(d0, 146097L);
            n100 = CalendarUtils.floorDivide(d1, 36524);
            d2 = CalendarUtils.mod(d1, 36524);
            n4 = CalendarUtils.floorDivide(d2, 1461);
            d3 = CalendarUtils.mod(d2, 1461);
            n1 = CalendarUtils.floorDivide(d3, 365);
            d4 = CalendarUtils.mod(d3, 365) + 1;
        }
        year = 400 * n400 + 100 * n100 + 4 * n4 + n1;
        if (!(n100 == 4 || n1 == 4)) {
            ++year;
        }
        return year;
    }

    /**
     * @return true if the specified year is a Gregorian leap year, or
     * false otherwise.
     * @see BaseCalendar#isGregorianLeapYear
     */
    protected boolean isLeapYear(CalendarDate date) {
        return isLeapYear(((Date)date).getNormalizedYear());
    }

    boolean isLeapYear(int normalizedYear) {
        return CalendarUtils.isGregorianLeapYear(normalizedYear);
    }
}