542 lines
17 KiB
Java
542 lines
17 KiB
Java
/*
|
|
* Copyright (c) 2003, 2021, 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} 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);
|
|
if (month == 13) {
|
|
year++;
|
|
month = 1;
|
|
}
|
|
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}. The
|
|
* {@code CalendarDate} must be normalized to get a correct
|
|
* value.
|
|
*
|
|
* @param date a {@code CalendarDate}
|
|
* @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}.
|
|
*/
|
|
// 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);
|
|
}
|
|
}
|