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.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 {
    145         protected Date() {
    146             super();
    147         }
    148         protected Date(TimeZone zone) {
    149             super(zone);
    150         }
    151 
    152         public Date setNormalizedDate(int normalizedYear, int month, int dayOfMonth) {
    153             setNormalizedYear(normalizedYear);
    154             setMonth(month).setDayOfMonth(dayOfMonth);
    155             return this;
    156         }
    157 
    158         public abstract int getNormalizedYear();
    159 
    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 
    169         protected final boolean hit(int year) {
    170             return year == cachedYear;
    171         }
    172 
    173         protected final boolean hit(long fixedDate) {
    174             return (fixedDate >= cachedFixedDateJan1 &&
    175                     fixedDate < cachedFixedDateNextJan1);
    176         }
    177         protected int getCachedYear() {
    178             return cachedYear;
    179         }
    180 
    181         protected long getCachedJan1() {
    182             return cachedFixedDateJan1;
    183         }
    184 
    185         protected void setCache(int year, long jan1, int len) {
    186             cachedYear = year;
    187             cachedFixedDateJan1 = jan1;
    188             cachedFixedDateNextJan1 = jan1 + len;
    189         }
    190     }
    191 
    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 
    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 
    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      */
    303     public int getYearLength(CalendarDate date) {
    304         return isLeapYear(((Date)date).getNormalizedYear()) ? 366 : 365;
    305     }
    306 
    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 
    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.
    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 
    340     public long getDayOfYear(CalendarDate date) {
    341         return getDayOfYear(((Date)date).getNormalizedYear(),
    342                             date.getMonth(),
    343                             date.getDayOfMonth());
    344     }
    345 
    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
    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
    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'
    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      */
    472     public int getDayOfWeek(CalendarDate date) {
    473         long fixedDate = getFixedDate(date);
    474         return getDayOfWeekFromFixedDate(fixedDate);
    475     }
    476 
    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 
    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      */
    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      */
    531     protected boolean isLeapYear(CalendarDate date) {
    532         return isLeapYear(((Date)date).getNormalizedYear());
    533     }
    534 
    535     boolean isLeapYear(int normalizedYear) {
    536         return CalendarUtils.isGregorianLeapYear(normalizedYear);
    537     }
    538 }
    539