Home | History | Annotate | Download | only in calendar
      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 }
    538