Use Tree Navigation
public abstract class

BaseCalendar

extends AbstractCalendar
/*
 * Copyright (c) 2003, 2004, 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.Locale;
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 != bdate.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 final 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);
   
}
}