Path: blob/master/src/java.base/share/classes/sun/util/calendar/BaseCalendar.java
41159 views
/*1* Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.util.calendar;2627import java.util.TimeZone;2829/**30* The {@code BaseCalendar} provides basic calendar calculation31* functions to support the Julian, Gregorian, and Gregorian-based32* calendar systems.33*34* @author Masayoshi Okutsu35* @since 1.536*/3738public abstract class BaseCalendar extends AbstractCalendar {3940public static final int JANUARY = 1;41public static final int FEBRUARY = 2;42public static final int MARCH = 3;43public static final int APRIL = 4;44public static final int MAY = 5;45public static final int JUNE = 6;46public static final int JULY = 7;47public static final int AUGUST = 8;48public static final int SEPTEMBER = 9;49public static final int OCTOBER = 10;50public static final int NOVEMBER = 11;51public static final int DECEMBER = 12;5253// day of week constants54public static final int SUNDAY = 1;55public static final int MONDAY = 2;56public static final int TUESDAY = 3;57public static final int WEDNESDAY = 4;58public static final int THURSDAY = 5;59public static final int FRIDAY = 6;60public static final int SATURDAY = 7;6162// The base Gregorian year of FIXED_DATES[]63private static final int BASE_YEAR = 1970;6465// Pre-calculated fixed dates of January 1 from BASE_YEAR66// (Gregorian). This table covers all the years that can be67// supported by the POSIX time_t (32-bit) after the Epoch. Note68// that the data type is int[].69private static final int[] FIXED_DATES = {70719163, // 197071719528, // 197172719893, // 197273720259, // 197374720624, // 197475720989, // 197576721354, // 197677721720, // 197778722085, // 197879722450, // 197980722815, // 198081723181, // 198182723546, // 198283723911, // 198384724276, // 198485724642, // 198586725007, // 198687725372, // 198788725737, // 198889726103, // 198990726468, // 199091726833, // 199192727198, // 199293727564, // 199394727929, // 199495728294, // 199596728659, // 199697729025, // 199798729390, // 199899729755, // 1999100730120, // 2000101730486, // 2001102730851, // 2002103731216, // 2003104731581, // 2004105731947, // 2005106732312, // 2006107732677, // 2007108733042, // 2008109733408, // 2009110733773, // 2010111734138, // 2011112734503, // 2012113734869, // 2013114735234, // 2014115735599, // 2015116735964, // 2016117736330, // 2017118736695, // 2018119737060, // 2019120737425, // 2020121737791, // 2021122738156, // 2022123738521, // 2023124738886, // 2024125739252, // 2025126739617, // 2026127739982, // 2027128740347, // 2028129740713, // 2029130741078, // 2030131741443, // 2031132741808, // 2032133742174, // 2033134742539, // 2034135742904, // 2035136743269, // 2036137743635, // 2037138744000, // 2038139744365, // 2039140};141142public abstract static class Date extends CalendarDate {143protected Date() {144super();145}146protected Date(TimeZone zone) {147super(zone);148}149150public Date setNormalizedDate(int normalizedYear, int month, int dayOfMonth) {151setNormalizedYear(normalizedYear);152setMonth(month).setDayOfMonth(dayOfMonth);153return this;154}155156public abstract int getNormalizedYear();157158public abstract void setNormalizedYear(int normalizedYear);159160// Cache for the fixed date of January 1 and year length of the161// cachedYear. A simple benchmark showed 7% performance162// improvement with >90% cache hit. The initial values are for Gregorian.163int cachedYear = 2004;164long cachedFixedDateJan1 = 731581L;165long cachedFixedDateNextJan1 = cachedFixedDateJan1 + 366;166167protected final boolean hit(int year) {168return year == cachedYear;169}170171protected final boolean hit(long fixedDate) {172return (fixedDate >= cachedFixedDateJan1 &&173fixedDate < cachedFixedDateNextJan1);174}175protected int getCachedYear() {176return cachedYear;177}178179protected long getCachedJan1() {180return cachedFixedDateJan1;181}182183protected void setCache(int year, long jan1, int len) {184cachedYear = year;185cachedFixedDateJan1 = jan1;186cachedFixedDateNextJan1 = jan1 + len;187}188}189190public boolean validate(CalendarDate date) {191Date bdate = (Date) date;192if (bdate.isNormalized()) {193return true;194}195int month = bdate.getMonth();196if (month < JANUARY || month > DECEMBER) {197return false;198}199int d = bdate.getDayOfMonth();200if (d <= 0 || d > getMonthLength(bdate.getNormalizedYear(), month)) {201return false;202}203int dow = bdate.getDayOfWeek();204if (dow != Date.FIELD_UNDEFINED && dow != getDayOfWeek(bdate)) {205return false;206}207208if (!validateTime(date)) {209return false;210}211212bdate.setNormalized(true);213return true;214}215216public boolean normalize(CalendarDate date) {217if (date.isNormalized()) {218return true;219}220221Date bdate = (Date) date;222TimeZone zi = bdate.getZone();223224// If the date has a time zone, then we need to recalculate225// the calendar fields. Let getTime() do it.226if (zi != null) {227getTime(date);228return true;229}230231int days = normalizeTime(bdate);232normalizeMonth(bdate);233long d = (long)bdate.getDayOfMonth() + days;234int m = bdate.getMonth();235int y = bdate.getNormalizedYear();236int ml = getMonthLength(y, m);237238if (!(d > 0 && d <= ml)) {239if (d <= 0 && d > -28) {240ml = getMonthLength(y, --m);241d += ml;242bdate.setDayOfMonth((int) d);243if (m == 0) {244m = DECEMBER;245bdate.setNormalizedYear(y - 1);246}247bdate.setMonth(m);248} else if (d > ml && d < (ml + 28)) {249d -= ml;250++m;251bdate.setDayOfMonth((int)d);252if (m > DECEMBER) {253bdate.setNormalizedYear(y + 1);254m = JANUARY;255}256bdate.setMonth(m);257} else {258long fixedDate = d + getFixedDate(y, m, 1, bdate) - 1L;259getCalendarDateFromFixedDate(bdate, fixedDate);260}261} else {262bdate.setDayOfWeek(getDayOfWeek(bdate));263}264date.setLeapYear(isLeapYear(bdate.getNormalizedYear()));265date.setZoneOffset(0);266date.setDaylightSaving(0);267bdate.setNormalized(true);268return true;269}270271void normalizeMonth(CalendarDate date) {272Date bdate = (Date) date;273int year = bdate.getNormalizedYear();274long month = bdate.getMonth();275if (month <= 0) {276long xm = 1L - month;277year -= (int)((xm / 12) + 1);278month = 13 - (xm % 12);279bdate.setNormalizedYear(year);280bdate.setMonth((int) month);281} else if (month > DECEMBER) {282year += (int)((month - 1) / 12);283month = ((month - 1)) % 12 + 1;284bdate.setNormalizedYear(year);285bdate.setMonth((int) month);286}287}288289/**290* Returns 366 if the specified date is in a leap year, or 365291* otherwise This method does not perform the normalization with292* the specified {@code CalendarDate}. The293* {@code CalendarDate} must be normalized to get a correct294* value.295*296* @param date a {@code CalendarDate}297* @return a year length in days298* @throws ClassCastException if the specified date is not a299* {@link BaseCalendar.Date}300*/301public int getYearLength(CalendarDate date) {302return isLeapYear(((Date)date).getNormalizedYear()) ? 366 : 365;303}304305public int getYearLengthInMonths(CalendarDate date) {306return 12;307}308309static final int[] DAYS_IN_MONTH310// 12 1 2 3 4 5 6 7 8 9 10 11 12311= { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};312static final int[] ACCUMULATED_DAYS_IN_MONTH313// 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/1314= { -30, 0, 31, 59, 90,120,151,181,212,243, 273, 304, 334};315316static final int[] ACCUMULATED_DAYS_IN_MONTH_LEAP317// 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/1318= { -30, 0, 31, 59+1, 90+1,120+1,151+1,181+1,212+1,243+1, 273+1, 304+1, 334+1};319320public int getMonthLength(CalendarDate date) {321Date gdate = (Date) date;322int month = gdate.getMonth();323if (month < JANUARY || month > DECEMBER) {324throw new IllegalArgumentException("Illegal month value: " + month);325}326return getMonthLength(gdate.getNormalizedYear(), month);327}328329// accepts 0 (December in the previous year) to 12.330private int getMonthLength(int year, int month) {331int days = DAYS_IN_MONTH[month];332if (month == FEBRUARY && isLeapYear(year)) {333days++;334}335return days;336}337338public long getDayOfYear(CalendarDate date) {339return getDayOfYear(((Date)date).getNormalizedYear(),340date.getMonth(),341date.getDayOfMonth());342}343344final long getDayOfYear(int year, int month, int dayOfMonth) {345return (long) dayOfMonth346+ (isLeapYear(year) ?347ACCUMULATED_DAYS_IN_MONTH_LEAP[month] : ACCUMULATED_DAYS_IN_MONTH[month]);348}349350// protected351public long getFixedDate(CalendarDate date) {352if (!date.isNormalized()) {353normalizeMonth(date);354}355return getFixedDate(((Date)date).getNormalizedYear(),356date.getMonth(),357date.getDayOfMonth(),358(BaseCalendar.Date) date);359}360361// public for java.util.GregorianCalendar362public long getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache) {363boolean isJan1 = month == JANUARY && dayOfMonth == 1;364365// Look up the one year cache366if (cache != null && cache.hit(year)) {367if (isJan1) {368return cache.getCachedJan1();369}370return cache.getCachedJan1() + getDayOfYear(year, month, dayOfMonth) - 1;371}372373// Look up the pre-calculated fixed date table374int n = year - BASE_YEAR;375if (n >= 0 && n < FIXED_DATES.length) {376long jan1 = FIXED_DATES[n];377if (cache != null) {378cache.setCache(year, jan1, isLeapYear(year) ? 366 : 365);379}380return isJan1 ? jan1 : jan1 + getDayOfYear(year, month, dayOfMonth) - 1;381}382383long prevyear = (long)year - 1;384long days = dayOfMonth;385386if (prevyear >= 0) {387days += (365 * prevyear)388+ (prevyear / 4)389- (prevyear / 100)390+ (prevyear / 400)391+ ((367 * month - 362) / 12);392} else {393days += (365 * prevyear)394+ CalendarUtils.floorDivide(prevyear, 4)395- CalendarUtils.floorDivide(prevyear, 100)396+ CalendarUtils.floorDivide(prevyear, 400)397+ CalendarUtils.floorDivide((367 * month - 362), 12);398}399400if (month > FEBRUARY) {401days -= isLeapYear(year) ? 1 : 2;402}403404// If it's January 1, update the cache.405if (cache != null && isJan1) {406cache.setCache(year, days, isLeapYear(year) ? 366 : 365);407}408409return days;410}411412/**413* Calculates calendar fields and store them in the specified414* {@code CalendarDate}.415*/416// should be 'protected'417public void getCalendarDateFromFixedDate(CalendarDate date,418long fixedDate) {419Date gdate = (Date) date;420int year;421long jan1;422boolean isLeap;423if (gdate.hit(fixedDate)) {424year = gdate.getCachedYear();425jan1 = gdate.getCachedJan1();426isLeap = isLeapYear(year);427} else {428// Looking up FIXED_DATES[] here didn't improve performance429// much. So we calculate year and jan1. getFixedDate()430// will look up FIXED_DATES[] actually.431year = getGregorianYearFromFixedDate(fixedDate);432jan1 = getFixedDate(year, JANUARY, 1, null);433isLeap = isLeapYear(year);434// Update the cache data435gdate.setCache (year, jan1, isLeap ? 366 : 365);436}437438int priorDays = (int)(fixedDate - jan1);439long mar1 = jan1 + 31 + 28;440if (isLeap) {441++mar1;442}443if (fixedDate >= mar1) {444priorDays += isLeap ? 1 : 2;445}446int month = 12 * priorDays + 373;447if (month > 0) {448month /= 367;449} else {450month = CalendarUtils.floorDivide(month, 367);451}452long month1 = jan1 + ACCUMULATED_DAYS_IN_MONTH[month];453if (isLeap && month >= MARCH) {454++month1;455}456int dayOfMonth = (int)(fixedDate - month1) + 1;457int dayOfWeek = getDayOfWeekFromFixedDate(fixedDate);458assert dayOfWeek > 0 : "negative day of week " + dayOfWeek;459gdate.setNormalizedYear(year);460gdate.setMonth(month);461gdate.setDayOfMonth(dayOfMonth);462gdate.setDayOfWeek(dayOfWeek);463gdate.setLeapYear(isLeap);464gdate.setNormalized(true);465}466467/**468* Returns the day of week of the given Gregorian date.469*/470public int getDayOfWeek(CalendarDate date) {471long fixedDate = getFixedDate(date);472return getDayOfWeekFromFixedDate(fixedDate);473}474475public static final int getDayOfWeekFromFixedDate(long fixedDate) {476// The fixed day 1 (January 1, 1 Gregorian) is Monday.477if (fixedDate >= 0) {478return (int)(fixedDate % 7) + SUNDAY;479}480return (int)CalendarUtils.mod(fixedDate, 7) + SUNDAY;481}482483public int getYearFromFixedDate(long fixedDate) {484return getGregorianYearFromFixedDate(fixedDate);485}486487/**488* Returns the Gregorian year number of the given fixed date.489*/490final int getGregorianYearFromFixedDate(long fixedDate) {491long d0;492int d1, d2, d3, d4;493int n400, n100, n4, n1;494int year;495496if (fixedDate > 0) {497d0 = fixedDate - 1;498n400 = (int)(d0 / 146097);499d1 = (int)(d0 % 146097);500n100 = d1 / 36524;501d2 = d1 % 36524;502n4 = d2 / 1461;503d3 = d2 % 1461;504n1 = d3 / 365;505d4 = (d3 % 365) + 1;506} else {507d0 = fixedDate - 1;508n400 = (int)CalendarUtils.floorDivide(d0, 146097L);509d1 = (int)CalendarUtils.mod(d0, 146097L);510n100 = CalendarUtils.floorDivide(d1, 36524);511d2 = CalendarUtils.mod(d1, 36524);512n4 = CalendarUtils.floorDivide(d2, 1461);513d3 = CalendarUtils.mod(d2, 1461);514n1 = CalendarUtils.floorDivide(d3, 365);515d4 = CalendarUtils.mod(d3, 365) + 1;516}517year = 400 * n400 + 100 * n100 + 4 * n4 + n1;518if (!(n100 == 4 || n1 == 4)) {519++year;520}521return year;522}523524/**525* @return true if the specified year is a Gregorian leap year, or526* false otherwise.527* @see BaseCalendar#isGregorianLeapYear528*/529protected boolean isLeapYear(CalendarDate date) {530return isLeapYear(((Date)date).getNormalizedYear());531}532533boolean isLeapYear(int normalizedYear) {534return CalendarUtils.isGregorianLeapYear(normalizedYear);535}536}537538539