/*
 * 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 BaseCalendar provides basic calendar calculation functions to support the Julian, Gregorian, and Gregorian-based calendar systems.
Author:Masayoshi Okutsu
Since:1.5
/** * 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 CalendarDate. The CalendarDate must be normalized to get a correct value.
Params:
  • a – CalendarDate
Throws:
Returns:a year length in days
/** * 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 CalendarDate.
/** * 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.
/** * 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.
/** * 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; }
See Also:
  • isGregorianLeapYear
Returns:true if the specified year is a Gregorian leap year, or false otherwise.
/** * @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); } }