|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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; |
|
|
|
|
|
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; |
|
|
|
|
|
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 |
|
|
|
private static final int[] FIXED_DATES = { |
|
719163, |
|
719528, |
|
719893, |
|
720259, |
|
720624, |
|
720989, |
|
721354, |
|
721720, |
|
722085, |
|
722450, |
|
722815, |
|
723181, |
|
723546, |
|
723911, |
|
724276, |
|
724642, |
|
725007, |
|
725372, |
|
725737, |
|
726103, |
|
726468, |
|
726833, |
|
727198, |
|
727564, |
|
727929, |
|
728294, |
|
728659, |
|
729025, |
|
729390, |
|
729755, |
|
730120, |
|
730486, |
|
730851, |
|
731216, |
|
731581, |
|
731947, |
|
732312, |
|
732677, |
|
733042, |
|
733408, |
|
733773, |
|
734138, |
|
734503, |
|
734869, |
|
735234, |
|
735599, |
|
735964, |
|
736330, |
|
736695, |
|
737060, |
|
737425, |
|
737791, |
|
738156, |
|
738521, |
|
738886, |
|
739252, |
|
739617, |
|
739982, |
|
740347, |
|
740713, |
|
741078, |
|
741443, |
|
741808, |
|
742174, |
|
742539, |
|
742904, |
|
743269, |
|
743635, |
|
744000, |
|
744365, |
|
}; |
|
|
|
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 |
|
|
|
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 |
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
= { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
|
static final int[] ACCUMULATED_DAYS_IN_MONTH |
|
|
|
= { -30, 0, 31, 59, 90,120,151,181,212,243, 273, 304, 334}; |
|
|
|
static final int[] ACCUMULATED_DAYS_IN_MONTH_LEAP |
|
|
|
= { -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); |
|
} |
|
|
|
|
|
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]); |
|
} |
|
|
|
|
|
public long getFixedDate(CalendarDate date) { |
|
if (!date.isNormalized()) { |
|
normalizeMonth(date); |
|
} |
|
return getFixedDate(((Date)date).getNormalizedYear(), |
|
date.getMonth(), |
|
date.getDayOfMonth(), |
|
(BaseCalendar.Date) date); |
|
} |
|
|
|
|
|
public long getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache) { |
|
boolean isJan1 = month == JANUARY && dayOfMonth == 1; |
|
|
|
|
|
if (cache != null && cache.hit(year)) { |
|
if (isJan1) { |
|
return cache.getCachedJan1(); |
|
} |
|
return cache.getCachedJan1() + getDayOfYear(year, month, dayOfMonth) - 1; |
|
} |
|
|
|
|
|
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 (cache != null && isJan1) { |
|
cache.setCache(year, days, isLeapYear(year) ? 366 : 365); |
|
} |
|
|
|
return days; |
|
} |
|
|
|
/** |
|
* Calculates calendar fields and store them in the specified |
|
* {@code CalendarDate}. |
|
*/ |
|
|
|
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() |
|
|
|
year = getGregorianYearFromFixedDate(fixedDate); |
|
jan1 = getFixedDate(year, JANUARY, 1, null); |
|
isLeap = isLeapYear(year); |
|
|
|
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); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public int getDayOfWeek(CalendarDate date) { |
|
long fixedDate = getFixedDate(date); |
|
return getDayOfWeekFromFixedDate(fixedDate); |
|
} |
|
|
|
public static final int getDayOfWeekFromFixedDate(long fixedDate) { |
|
|
|
if (fixedDate >= 0) { |
|
return (int)(fixedDate % 7) + SUNDAY; |
|
} |
|
return (int)CalendarUtils.mod(fixedDate, 7) + SUNDAY; |
|
} |
|
|
|
public int getYearFromFixedDate(long fixedDate) { |
|
return getGregorianYearFromFixedDate(fixedDate); |
|
} |
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected boolean isLeapYear(CalendarDate date) { |
|
return isLeapYear(((Date)date).getNormalizedYear()); |
|
} |
|
|
|
boolean isLeapYear(int normalizedYear) { |
|
return CalendarUtils.isGregorianLeapYear(normalizedYear); |
|
} |
|
} |