Calendar Redesign including addition of Chinese Calendar

X-SVN-Rev: 2947
This commit is contained in:
Alan Liu 2000-11-18 01:07:18 +00:00
parent a3e0229297
commit 906c86b1bc
6 changed files with 4798 additions and 3588 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,597 @@
/*********************************************************************
* Copyright (C) 2000, International Business Machines Corporation and
* others. All Rights Reserved.
*********************************************************************
* $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/util/ChineseCalendar.java,v $
* $Date: 2000/11/18 01:07:18 $
* $Revision: 1.3 $
*/
package com.ibm.util;
import java.util.Date;
import java.util.Locale;
/**
* <code>ChineseCalendar</code> is a concrete subclass of {@link Calendar}
* that implements a traditional Chinese calendar. The traditional Chinese
* calendar is a lunisolar calendar: Each month starts on a new moon, and
* the months are numbered according to solar events, specifically, to
* guarantee that month 11 always contains the winter solstice. In order
* to accomplish this, leap months are inserted in certain years. Leap
* months are numbered the same as the month they follow. The decision of
* which month is a leap month depends on the relative movements of the sun
* and moon.
*
* <p>This class defines one addition field beyond those defined by
* <code>Calendar</code>: The <code>IS_LEAP_MONTH</code> field takes the
* value of 0 for normal months, or 1 for leap months.
*
* <p>All astronomical computations are performed with respect to a time
* zone of GMT+8:00 and a longitude of 120 degrees east. Although some
* calendars implement a historically more accurate convention of using
* Beijing's local longitude (116 degrees 25 minutes east) and time zone
* (GMT+7:45:40) for dates before 1929, we do not implement this here.
*
* <p>Years are counted in two different ways in the Chinese calendar. The
* first method is by sequential numbering from the 61st year of the reign
* of Huang Di, 2637 BCE, which is designated year 1 on the Chinese
* calendar. The second method uses 60-year cycles from the same starting
* point, which is designated year 1 of cycle 1. In this class, the
* <code>EXTENDED_YEAR</code> field contains the sequential year count.
* The <code>ERA</code> field contains the cycle number, and the
* <code>YEAR</code> field contains the year of the cycle, a value between
* 1 and 60.
*
* <p>There is some variation in what is considered the starting point of
* the calendar, with some sources starting in the first year of the reign
* of Huang Di, rather than the 61st. This gives continuous year numbers
* 60 years greater and cycle numbers one greater than what this class
* implements.
*
* <p>References:<ul>
*
* <li>Dershowitz and Reingold, <i>Calendrical Calculations</i>,
* Cambridge University Press, 1997</li>
*
* <li>Helmer Aslaksen's
* <a href="http://www.math.nus.edu.sg/aslaksen/calendar/chinese.shtml">
* Chinese Calendar page</a></li>
*
* <li>The <a href="http://www.tondering.dk/claus/calendar.html">
* Calendar FAQ</a></li>
*
* </ul>
* @see com.ibm.text.ChineseDateFormat
* @author Alan Liu
*/
public class ChineseCalendar extends Calendar {
//------------------------------------------------------------------
// Developer Notes
//
// Time is represented as a scalar in two ways in this class. One is
// the usual UTC epoch millis, that is, milliseconds after January 1,
// 1970 Gregorian, 0:00:00.000 UTC. The other is in terms of 'local
// days.' This is the number of days after January 1, 1970 Gregorian,
// local to Beijing, China (since all computations of the Chinese
// calendar are done in Beijing). That is, 0 represents January 1,
// 1970 0:00 Asia/Shanghai. Conversion of local days to and from
// standard epoch milliseconds is accomplished by the daysToMillis()
// and millisToDays() methods.
//
// Several methods use caches to improve performance. Caches are at
// the object, not class level, under the assumption that typical
// usage will be to have one instance of ChineseCalendar at a time.
/**
* We have one instance per object, and we don't synchronize it because
* Calendar doesn't support multithreaded execution in the first place.
*/
private transient CalendarAstronomer astro = new CalendarAstronomer();
/**
* Cache that maps Gregorian year to local days of winter solstice.
* @see winterSolstice
*/
private transient CalendarCache winterSolsticeCache = new CalendarCache();
/**
* Cache that maps Gregorian year to local days of Chinese new year.
* @see newYear
*/
private transient CalendarCache newYearCache = new CalendarCache();
//------------------------------------------------------------------
// Constructors
//------------------------------------------------------------------
/**
* Construct a Chinese calendar with the default time zone and locale.
*/
public ChineseCalendar() {
super();
}
/**
* Construct a Chinese calendar with the given time zone and locale.
* @param zone time zone for this calendar
* @param locale locale for this calendar
*/
public ChineseCalendar(TimeZone zone, Locale locale) {
super(zone, locale);
}
//------------------------------------------------------------------
// Public constants
//------------------------------------------------------------------
/**
* Field indicating whether or not the current month is a leap month.
* Should have a value of 0 for non-leap months, and 1 for leap months.
*/
public static int IS_LEAP_MONTH = BASE_FIELD_COUNT;
/**
* Count of fields in this class.
*/
private static final int FIELD_COUNT = IS_LEAP_MONTH + 1;
//------------------------------------------------------------------
// Calendar framework
//------------------------------------------------------------------
/**
* Override Calendar to allocate our additional field.
*/
protected int[] handleCreateFields() {
return new int[FIELD_COUNT];
}
/**
* Array defining the limits of field values for this class. Field
* limits which are invariant with respect to calendar system and
* defined by Calendar are left blank.
*
* Notes:
*
* ERA 5000000 / 60 = 83333.
*
* MONTH There are 12 or 13 lunar months in a year. However, we always
* number them 0..11, with an intercalated, identically numbered leap
* month, when necessary.
*
* DAY_OF_YEAR In a non-leap year there are 353, 354, or 355 days. In
* a leap year there are 383, 384, or 385 days.
*
* WEEK_OF_YEAR The least maximum occurs if there are 353 days in the
* year, and the first 6 are the last week of the previous year. Then
* we have 49 full weeks and 4 days in the last week: 6 + 49*7 + 4 =
* 353. So the least maximum is 50. The maximum occurs if there are
* 385 days in the year, and WOY 1 extends 6 days into the prior year.
* Then there are 54 full weeks, and 6 days in the last week: 1 + 54*7
* + 6 = 385. The 6 days of the last week will fall into WOY 1 of the
* next year. Maximum is 55.
*
* WEEK_OF_MONTH In a 29 day month, if the first 7 days make up week 1
* that leaves 3 full weeks and 1 day at the end. The least maximum is
* thus 5. In a 30 days month, if the previous 6 days belong WOM 1 of
* this month, we have 4 full weeks and 1 days at the end (which
* technically will be WOM 1 of the next month, but will be reported by
* time->fields and hence by getActualMaximum as WOM 6 of this month).
* Maximum is 6.
*
* DAY_OF_WEEK_IN_MONTH In a 29 or 30 day month, there are 4 full weeks
* plus 1 or 2 days at the end, so the maximum is always 5.
*/
private static final int LIMITS[][] = {
// Minimum Greatest Least Maximum
// Minimum Maximum
{ 1, 1, 83333, 83333 }, // ERA
{ 1, 1, 70, 70 }, // YEAR
{ 0, 0, 11, 11 }, // MONTH
{ 1, 1, 50, 55 }, // WEEK_OF_YEAR
{ 1, 1, 5, 6 }, // WEEK_OF_MONTH
{ 1, 1, 29, 30 }, // DAY_OF_MONTH
{ 1, 1, 353, 385 }, // DAY_OF_YEAR
{/* */}, // DAY_OF_WEEK
{ -1, -1, 5, 5 }, // DAY_OF_WEEK_IN_MONTH
{/* */}, // AM_PM
{/* */}, // HOUR
{/* */}, // HOUR_OF_DAY
{/* */}, // MINUTE
{/* */}, // SECOND
{/* */}, // MILLISECOND
{/* */}, // ZONE_OFFSET
{/* */}, // DST_OFFSET
{ -5000001, -5000001, 5000001, 5000001 }, // YEAR_WOY
{/* */}, // DOW_LOCAL
{ -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR
{/* */}, // JULIAN_DAY
{/* */}, // MILLISECONDS_IN_DAY
{ 0, 0, 1, 1 }, // IS_LEAP_MONTH
};
/**
* Override Calendar to return the limit value for the given field.
*/
protected int handleGetLimit(int field, int limitType) {
return LIMITS[field][limitType];
}
/**
* Implement abstract Calendar method to return the extended year
* defined by the current fields. This will use either the ERA and
* YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR
* field as the continuous year count, depending on which is newer.
*/
protected int handleGetExtendedYear() {
int year;
if (newestStamp(ERA, YEAR, UNSET) <= getStamp(EXTENDED_YEAR)) {
year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
} else {
int cycle = internalGet(ERA, 1) - 1; // 0-based cycle
year = cycle * 60 + internalGet(YEAR, 1);
}
return year;
}
/**
* Override Calendar method to return the number of days in the given
* extended year and month.
*
* <p>Note: This method also reads the IS_LEAP_MONTH field to determine
* whether or not the given month is a leap month.
*/
protected int handleGetMonthLength(int extendedYear, int month) {
int thisStart = handleComputeMonthStart(extendedYear, month) -
EPOCH_JULIAN_DAY + 1; // Julian day -> local days
int nextStart = newMoonNear(thisStart + SYNODIC_GAP, true);
return nextStart - thisStart;
}
//------------------------------------------------------------------
// Support methods and constants
//------------------------------------------------------------------
/**
* The start year of the Chinese calendar, the 61st year of the reign
* of Huang Di. Some sources use the first year of his reign,
* resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle)
* values one greater.
*/
private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year
/**
* The offset from GMT in milliseconds at which we perform astronomical
* computations. Some sources use a different historically accurate
* offset of GMT+7:45:40 for years before 1929; we do not do this.
*/
private static final long CHINA_OFFSET = 8*ONE_HOUR;
/**
* Value to be added or subtracted from the local days of a new moon to
* get close to the next or prior new moon, but not cross it. Must be
* >= 1 and < CalendarAstronomer.SYNODIC_MONTH.
*/
private static final int SYNODIC_GAP = 25;
/**
* Convert local days to UTC epoch milliseconds.
* @param days days after January 1, 1970 0:00 Asia/Shanghai
* @return milliseconds after January 1, 1970 0:00 GMT
*/
private static final long daysToMillis(int days) {
return (days * ONE_DAY) - CHINA_OFFSET;
}
/**
* Convert UTC epoch milliseconds to local days.
* @param millis milliseconds after January 1, 1970 0:00 GMT
* @return days after January 1, 1970 0:00 Asia/Shanghai
*/
private static final int millisToDays(long millis) {
return (int) floorDivide(millis + CHINA_OFFSET, ONE_DAY);
}
//------------------------------------------------------------------
// Astronomical computations
//------------------------------------------------------------------
/**
* Return the major solar term on or after December 15 of the given
* Gregorian year, that is, the winter solstice of the given year.
* Computations are relative to Asia/Shanghai time zone.
* @param gyear a Gregorian year
* @return days after January 1, 1970 0:00 Asia/Shanghai of the
* winter solstice of the given year
*/
private int winterSolstice(int gyear) {
long cacheValue = winterSolsticeCache.get(gyear);
if (cacheValue == CalendarCache.EMPTY) {
// In books December 15 is used, but it fails for some years
// using our algorithms, e.g.: 1298 1391 1492 1553 1560. That
// is, winterSolstice(1298) starts search at Dec 14 08:00:00
// PST 1298 with a final result of Dec 14 10:31:59 PST 1299.
long ms = daysToMillis(computeGregorianMonthStart(gyear, Calendar.DECEMBER) +
1 - EPOCH_JULIAN_DAY);
astro.setTime(ms);
// Winter solstice is 270 degrees solar longitude aka Dongzhi
long solarLong = astro.getSunTime(CalendarAstronomer.WINTER_SOLSTICE,
true);
cacheValue = millisToDays(solarLong);
winterSolsticeCache.put(gyear, cacheValue);
}
return (int) cacheValue;
}
/**
* Return the closest new moon to the given date, searching either
* forward or backward in time.
* @param days days after January 1, 1970 0:00 Asia/Shanghai
* @param after if true, search for a new moon on or after the given
* date; otherwise, search for a new moon before it
* @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest
* new moon after or before <code>days</code>
*/
private int newMoonNear(int days, boolean after) {
astro.setTime(daysToMillis(days));
long newMoon = astro.getMoonTime(CalendarAstronomer.NEW_MOON, after);
return millisToDays(newMoon);
}
/**
* Return the nearest integer number of synodic months between
* two dates.
* @param day1 days after January 1, 1970 0:00 Asia/Shanghai
* @param day2 days after January 1, 1970 0:00 Asia/Shanghai
* @return the nearest integer number of months between day1 and day2
*/
private int synodicMonthsBetween(int day1, int day2) {
return (int) Math.round((day2 - day1) / CalendarAstronomer.SYNODIC_MONTH);
}
/**
* Return the major solar term on or before a given date. This
* will be an integer from 1..12, with 1 corresponding to 330 degrees,
* 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees.
* @param days days after January 1, 1970 0:00 Asia/Shanghai
*/
private int majorSolarTerm(int days) {
astro.setTime(daysToMillis(days));
// Compute (floor(solarLongitude / (pi/6)) + 2) % 12
int term = ((int) Math.floor(6 * astro.getSunLongitude() / Math.PI) + 2) % 12;
if (term < 1) {
term += 12;
}
return term;
}
/**
* Return true if the given month lacks a major solar term.
* @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new
* moon
*/
private boolean hasNoMajorSolarTerm(int newMoon) {
return majorSolarTerm(newMoon) ==
majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, true));
}
//------------------------------------------------------------------
// Time to fields
//------------------------------------------------------------------
/**
* Return true if there is a leap month on or after month newMoon1 and
* at or before month newMoon2.
* @param newMoon1 days after January 1, 1970 0:00 Asia/Shanghai of a
* new moon
* @param newMoon2 days after January 1, 1970 0:00 Asia/Shanghai of a
* new moon
*/
private boolean isLeapMonthBetween(int newMoon1, int newMoon2) {
// This is only needed to debug the timeOfAngle divergence bug.
// Remove this later. Liu 11/9/00
// DEBUG
if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) {
throw new IllegalArgumentException("isLeapMonthBetween(" + newMoon1 +
", " + newMoon2 +
"): Invalid parameters");
}
return (newMoon2 >= newMoon1) &&
(isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, false)) ||
hasNoMajorSolarTerm(newMoon2));
}
/**
* Override Calendar to compute several fields specific to the Chinese
* calendar system. These are:
*
* <ul><li>ERA
* <li>YEAR
* <li>MONTH
* <li>DAY_OF_MONTH
* <li>DAY_OF_YEAR
* <li>EXTENDED_YEAR</ul>
*
* The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
* method is called. The getGregorianXxx() methods return Gregorian
* calendar equivalents for the given Julian day.
*
* <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH.
*/
protected void handleComputeFields(int julianDay) {
computeChineseFields(julianDay - EPOCH_JULIAN_DAY, // local days
getGregorianYear(), getGregorianMonth(),
true); // set all fields
}
/**
* Compute fields for the Chinese calendar system. This method can
* either set all relevant fields, as required by
* <code>handleComputeFields()</code>, or it can just set the MONTH and
* IS_LEAP_MONTH fields, as required by
* <code>handleComputeMonthStart()</code>.
* @param days days after January 1, 1970 0:00 Asia/Shanghai of the
* date to compute fields for
* @param gyear the Gregorian year of the given date
* @param gmonth the Gregorian month of the given date
* @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR,
* DAY_OF_MONTH, and DAY_OF_YEAR fields. In either case set the MONTH
* and IS_LEAP_MONTH fields.
*/
private void computeChineseFields(int days, int gyear, int gmonth,
boolean setAllFields) {
// Find the winter solstices before and after the target date.
// These define the boundaries of this Chinese year, specifically,
// the position of month 11, which always contains the solstice.
// We want solsticeBefore <= date < solsticeAfter.
int solsticeBefore;
int solsticeAfter = winterSolstice(gyear);
if (days < solsticeAfter) {
solsticeBefore = winterSolstice(gyear - 1);
} else {
solsticeBefore = solsticeAfter;
solsticeAfter = winterSolstice(gyear + 1);
}
// Find the start of the month after month 11. This will be either
// the prior month 12 or leap month 11 (very rare). Also find the
// start of the following month 11.
int firstMoon = newMoonNear(solsticeBefore + 1, true);
int lastMoon = newMoonNear(solsticeAfter + 1, false);
int thisMoon = newMoonNear(days + 1, false); // Start of this month
boolean isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12;
int month = synodicMonthsBetween(firstMoon, thisMoon);
if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) {
month--;
}
if (month < 1) {
month += 12;
}
boolean isLeapMonth = isLeapYear &&
hasNoMajorSolarTerm(thisMoon) &&
!isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false));
internalSet(MONTH, month-1); // Convert from 1-based to 0-based
internalSet(IS_LEAP_MONTH, isLeapMonth?1:0);
if (setAllFields) {
int year = gyear - CHINESE_EPOCH_YEAR;
if (month < 11 ||
gmonth >= Calendar.JULY) {
year++;
}
int dayOfMonth = days - thisMoon + 1;
internalSet(EXTENDED_YEAR, year);
// 0->0,60 1->1,1 60->1,60 61->2,1 etc.
int[] yearOfCycle = new int[1];
int cycle = floorDivide(year-1, 60, yearOfCycle);
internalSet(ERA, cycle+1);
internalSet(YEAR, yearOfCycle[0]+1);
internalSet(DAY_OF_MONTH, dayOfMonth);
// Days will be before the first new year we compute if this
// date is in month 11, leap 11, 12. There is never a leap 12.
// New year computations are cached so this should be cheap in
// the long run.
int newYear = newYear(gyear);
if (days < newYear) {
newYear = newYear(gyear-1);
}
internalSet(DAY_OF_YEAR, days - newYear + 1);
}
}
//------------------------------------------------------------------
// Fields to time
//------------------------------------------------------------------
/**
* Return the Chinese new year of the given Gregorian year.
* @param gyear a Gregorian year
* @return days after January 1, 1970 0:00 Asia/Shanghai of the
* Chinese new year of the given year (this will be a new moon)
*/
private int newYear(int gyear) {
long cacheValue = newYearCache.get(gyear);
if (cacheValue == CalendarCache.EMPTY) {
int solsticeBefore= winterSolstice(gyear - 1);
int solsticeAfter = winterSolstice(gyear);
int newMoon1 = newMoonNear(solsticeBefore + 1, true);
int newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, true);
int newMoon11 = newMoonNear(solsticeAfter + 1, false);
if (synodicMonthsBetween(newMoon1, newMoon11) == 12 &&
(hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) {
cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, true);
} else {
cacheValue = newMoon2;
}
newYearCache.put(gyear, cacheValue);
}
return (int) cacheValue;
}
/**
* Return the Julian day number of day before the first day of the
* given month in the given extended year.
*
* <p>Note: This method reads the IS_LEAP_MONTH field to determine
* whether the given month is a leap month.
* @param eyear the extended year
* @param month the zero-based month. The month is also determined
* by reading the IS_LEAP_MONTH field.
* @param return the Julian day number of the day before the first
* day of the given month and year
*/
protected int handleComputeMonthStart(int eyear, int month) {
int gyear = eyear + CHINESE_EPOCH_YEAR - 1; // Gregorian year
int newYear = newYear(gyear);
int newMoon = newMoonNear(newYear + month * 29, true);
int julianDay = newMoon + EPOCH_JULIAN_DAY;
int isLeapMonth = internalGet(IS_LEAP_MONTH);
computeGregorianFields(julianDay);
// Save fields for later restoration
int saveMonth = internalGet(MONTH);
// This will modify the MONTH and IS_LEAP_MONTH fields (only)
computeChineseFields(newMoon, getGregorianYear(),
getGregorianMonth(), false);
if (month != internalGet(MONTH) ||
isLeapMonth != internalGet(IS_LEAP_MONTH)) {
newMoon = newMoonNear(newMoon + SYNODIC_GAP, true);
julianDay = newMoon + EPOCH_JULIAN_DAY;
}
internalSet(MONTH, saveMonth);
internalSet(IS_LEAP_MONTH, isLeapMonth);
return julianDay - 1;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,597 @@
/*********************************************************************
* Copyright (C) 2000, International Business Machines Corporation and
* others. All Rights Reserved.
*********************************************************************
* $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/util/Attic/ChineseCalendar.java,v $
* $Date: 2000/11/18 01:07:18 $
* $Revision: 1.3 $
*/
package com.ibm.util;
import java.util.Date;
import java.util.Locale;
/**
* <code>ChineseCalendar</code> is a concrete subclass of {@link Calendar}
* that implements a traditional Chinese calendar. The traditional Chinese
* calendar is a lunisolar calendar: Each month starts on a new moon, and
* the months are numbered according to solar events, specifically, to
* guarantee that month 11 always contains the winter solstice. In order
* to accomplish this, leap months are inserted in certain years. Leap
* months are numbered the same as the month they follow. The decision of
* which month is a leap month depends on the relative movements of the sun
* and moon.
*
* <p>This class defines one addition field beyond those defined by
* <code>Calendar</code>: The <code>IS_LEAP_MONTH</code> field takes the
* value of 0 for normal months, or 1 for leap months.
*
* <p>All astronomical computations are performed with respect to a time
* zone of GMT+8:00 and a longitude of 120 degrees east. Although some
* calendars implement a historically more accurate convention of using
* Beijing's local longitude (116 degrees 25 minutes east) and time zone
* (GMT+7:45:40) for dates before 1929, we do not implement this here.
*
* <p>Years are counted in two different ways in the Chinese calendar. The
* first method is by sequential numbering from the 61st year of the reign
* of Huang Di, 2637 BCE, which is designated year 1 on the Chinese
* calendar. The second method uses 60-year cycles from the same starting
* point, which is designated year 1 of cycle 1. In this class, the
* <code>EXTENDED_YEAR</code> field contains the sequential year count.
* The <code>ERA</code> field contains the cycle number, and the
* <code>YEAR</code> field contains the year of the cycle, a value between
* 1 and 60.
*
* <p>There is some variation in what is considered the starting point of
* the calendar, with some sources starting in the first year of the reign
* of Huang Di, rather than the 61st. This gives continuous year numbers
* 60 years greater and cycle numbers one greater than what this class
* implements.
*
* <p>References:<ul>
*
* <li>Dershowitz and Reingold, <i>Calendrical Calculations</i>,
* Cambridge University Press, 1997</li>
*
* <li>Helmer Aslaksen's
* <a href="http://www.math.nus.edu.sg/aslaksen/calendar/chinese.shtml">
* Chinese Calendar page</a></li>
*
* <li>The <a href="http://www.tondering.dk/claus/calendar.html">
* Calendar FAQ</a></li>
*
* </ul>
* @see com.ibm.text.ChineseDateFormat
* @author Alan Liu
*/
public class ChineseCalendar extends Calendar {
//------------------------------------------------------------------
// Developer Notes
//
// Time is represented as a scalar in two ways in this class. One is
// the usual UTC epoch millis, that is, milliseconds after January 1,
// 1970 Gregorian, 0:00:00.000 UTC. The other is in terms of 'local
// days.' This is the number of days after January 1, 1970 Gregorian,
// local to Beijing, China (since all computations of the Chinese
// calendar are done in Beijing). That is, 0 represents January 1,
// 1970 0:00 Asia/Shanghai. Conversion of local days to and from
// standard epoch milliseconds is accomplished by the daysToMillis()
// and millisToDays() methods.
//
// Several methods use caches to improve performance. Caches are at
// the object, not class level, under the assumption that typical
// usage will be to have one instance of ChineseCalendar at a time.
/**
* We have one instance per object, and we don't synchronize it because
* Calendar doesn't support multithreaded execution in the first place.
*/
private transient CalendarAstronomer astro = new CalendarAstronomer();
/**
* Cache that maps Gregorian year to local days of winter solstice.
* @see winterSolstice
*/
private transient CalendarCache winterSolsticeCache = new CalendarCache();
/**
* Cache that maps Gregorian year to local days of Chinese new year.
* @see newYear
*/
private transient CalendarCache newYearCache = new CalendarCache();
//------------------------------------------------------------------
// Constructors
//------------------------------------------------------------------
/**
* Construct a Chinese calendar with the default time zone and locale.
*/
public ChineseCalendar() {
super();
}
/**
* Construct a Chinese calendar with the given time zone and locale.
* @param zone time zone for this calendar
* @param locale locale for this calendar
*/
public ChineseCalendar(TimeZone zone, Locale locale) {
super(zone, locale);
}
//------------------------------------------------------------------
// Public constants
//------------------------------------------------------------------
/**
* Field indicating whether or not the current month is a leap month.
* Should have a value of 0 for non-leap months, and 1 for leap months.
*/
public static int IS_LEAP_MONTH = BASE_FIELD_COUNT;
/**
* Count of fields in this class.
*/
private static final int FIELD_COUNT = IS_LEAP_MONTH + 1;
//------------------------------------------------------------------
// Calendar framework
//------------------------------------------------------------------
/**
* Override Calendar to allocate our additional field.
*/
protected int[] handleCreateFields() {
return new int[FIELD_COUNT];
}
/**
* Array defining the limits of field values for this class. Field
* limits which are invariant with respect to calendar system and
* defined by Calendar are left blank.
*
* Notes:
*
* ERA 5000000 / 60 = 83333.
*
* MONTH There are 12 or 13 lunar months in a year. However, we always
* number them 0..11, with an intercalated, identically numbered leap
* month, when necessary.
*
* DAY_OF_YEAR In a non-leap year there are 353, 354, or 355 days. In
* a leap year there are 383, 384, or 385 days.
*
* WEEK_OF_YEAR The least maximum occurs if there are 353 days in the
* year, and the first 6 are the last week of the previous year. Then
* we have 49 full weeks and 4 days in the last week: 6 + 49*7 + 4 =
* 353. So the least maximum is 50. The maximum occurs if there are
* 385 days in the year, and WOY 1 extends 6 days into the prior year.
* Then there are 54 full weeks, and 6 days in the last week: 1 + 54*7
* + 6 = 385. The 6 days of the last week will fall into WOY 1 of the
* next year. Maximum is 55.
*
* WEEK_OF_MONTH In a 29 day month, if the first 7 days make up week 1
* that leaves 3 full weeks and 1 day at the end. The least maximum is
* thus 5. In a 30 days month, if the previous 6 days belong WOM 1 of
* this month, we have 4 full weeks and 1 days at the end (which
* technically will be WOM 1 of the next month, but will be reported by
* time->fields and hence by getActualMaximum as WOM 6 of this month).
* Maximum is 6.
*
* DAY_OF_WEEK_IN_MONTH In a 29 or 30 day month, there are 4 full weeks
* plus 1 or 2 days at the end, so the maximum is always 5.
*/
private static final int LIMITS[][] = {
// Minimum Greatest Least Maximum
// Minimum Maximum
{ 1, 1, 83333, 83333 }, // ERA
{ 1, 1, 70, 70 }, // YEAR
{ 0, 0, 11, 11 }, // MONTH
{ 1, 1, 50, 55 }, // WEEK_OF_YEAR
{ 1, 1, 5, 6 }, // WEEK_OF_MONTH
{ 1, 1, 29, 30 }, // DAY_OF_MONTH
{ 1, 1, 353, 385 }, // DAY_OF_YEAR
{/* */}, // DAY_OF_WEEK
{ -1, -1, 5, 5 }, // DAY_OF_WEEK_IN_MONTH
{/* */}, // AM_PM
{/* */}, // HOUR
{/* */}, // HOUR_OF_DAY
{/* */}, // MINUTE
{/* */}, // SECOND
{/* */}, // MILLISECOND
{/* */}, // ZONE_OFFSET
{/* */}, // DST_OFFSET
{ -5000001, -5000001, 5000001, 5000001 }, // YEAR_WOY
{/* */}, // DOW_LOCAL
{ -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR
{/* */}, // JULIAN_DAY
{/* */}, // MILLISECONDS_IN_DAY
{ 0, 0, 1, 1 }, // IS_LEAP_MONTH
};
/**
* Override Calendar to return the limit value for the given field.
*/
protected int handleGetLimit(int field, int limitType) {
return LIMITS[field][limitType];
}
/**
* Implement abstract Calendar method to return the extended year
* defined by the current fields. This will use either the ERA and
* YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR
* field as the continuous year count, depending on which is newer.
*/
protected int handleGetExtendedYear() {
int year;
if (newestStamp(ERA, YEAR, UNSET) <= getStamp(EXTENDED_YEAR)) {
year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
} else {
int cycle = internalGet(ERA, 1) - 1; // 0-based cycle
year = cycle * 60 + internalGet(YEAR, 1);
}
return year;
}
/**
* Override Calendar method to return the number of days in the given
* extended year and month.
*
* <p>Note: This method also reads the IS_LEAP_MONTH field to determine
* whether or not the given month is a leap month.
*/
protected int handleGetMonthLength(int extendedYear, int month) {
int thisStart = handleComputeMonthStart(extendedYear, month) -
EPOCH_JULIAN_DAY + 1; // Julian day -> local days
int nextStart = newMoonNear(thisStart + SYNODIC_GAP, true);
return nextStart - thisStart;
}
//------------------------------------------------------------------
// Support methods and constants
//------------------------------------------------------------------
/**
* The start year of the Chinese calendar, the 61st year of the reign
* of Huang Di. Some sources use the first year of his reign,
* resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle)
* values one greater.
*/
private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year
/**
* The offset from GMT in milliseconds at which we perform astronomical
* computations. Some sources use a different historically accurate
* offset of GMT+7:45:40 for years before 1929; we do not do this.
*/
private static final long CHINA_OFFSET = 8*ONE_HOUR;
/**
* Value to be added or subtracted from the local days of a new moon to
* get close to the next or prior new moon, but not cross it. Must be
* >= 1 and < CalendarAstronomer.SYNODIC_MONTH.
*/
private static final int SYNODIC_GAP = 25;
/**
* Convert local days to UTC epoch milliseconds.
* @param days days after January 1, 1970 0:00 Asia/Shanghai
* @return milliseconds after January 1, 1970 0:00 GMT
*/
private static final long daysToMillis(int days) {
return (days * ONE_DAY) - CHINA_OFFSET;
}
/**
* Convert UTC epoch milliseconds to local days.
* @param millis milliseconds after January 1, 1970 0:00 GMT
* @return days after January 1, 1970 0:00 Asia/Shanghai
*/
private static final int millisToDays(long millis) {
return (int) floorDivide(millis + CHINA_OFFSET, ONE_DAY);
}
//------------------------------------------------------------------
// Astronomical computations
//------------------------------------------------------------------
/**
* Return the major solar term on or after December 15 of the given
* Gregorian year, that is, the winter solstice of the given year.
* Computations are relative to Asia/Shanghai time zone.
* @param gyear a Gregorian year
* @return days after January 1, 1970 0:00 Asia/Shanghai of the
* winter solstice of the given year
*/
private int winterSolstice(int gyear) {
long cacheValue = winterSolsticeCache.get(gyear);
if (cacheValue == CalendarCache.EMPTY) {
// In books December 15 is used, but it fails for some years
// using our algorithms, e.g.: 1298 1391 1492 1553 1560. That
// is, winterSolstice(1298) starts search at Dec 14 08:00:00
// PST 1298 with a final result of Dec 14 10:31:59 PST 1299.
long ms = daysToMillis(computeGregorianMonthStart(gyear, Calendar.DECEMBER) +
1 - EPOCH_JULIAN_DAY);
astro.setTime(ms);
// Winter solstice is 270 degrees solar longitude aka Dongzhi
long solarLong = astro.getSunTime(CalendarAstronomer.WINTER_SOLSTICE,
true);
cacheValue = millisToDays(solarLong);
winterSolsticeCache.put(gyear, cacheValue);
}
return (int) cacheValue;
}
/**
* Return the closest new moon to the given date, searching either
* forward or backward in time.
* @param days days after January 1, 1970 0:00 Asia/Shanghai
* @param after if true, search for a new moon on or after the given
* date; otherwise, search for a new moon before it
* @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest
* new moon after or before <code>days</code>
*/
private int newMoonNear(int days, boolean after) {
astro.setTime(daysToMillis(days));
long newMoon = astro.getMoonTime(CalendarAstronomer.NEW_MOON, after);
return millisToDays(newMoon);
}
/**
* Return the nearest integer number of synodic months between
* two dates.
* @param day1 days after January 1, 1970 0:00 Asia/Shanghai
* @param day2 days after January 1, 1970 0:00 Asia/Shanghai
* @return the nearest integer number of months between day1 and day2
*/
private int synodicMonthsBetween(int day1, int day2) {
return (int) Math.round((day2 - day1) / CalendarAstronomer.SYNODIC_MONTH);
}
/**
* Return the major solar term on or before a given date. This
* will be an integer from 1..12, with 1 corresponding to 330 degrees,
* 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees.
* @param days days after January 1, 1970 0:00 Asia/Shanghai
*/
private int majorSolarTerm(int days) {
astro.setTime(daysToMillis(days));
// Compute (floor(solarLongitude / (pi/6)) + 2) % 12
int term = ((int) Math.floor(6 * astro.getSunLongitude() / Math.PI) + 2) % 12;
if (term < 1) {
term += 12;
}
return term;
}
/**
* Return true if the given month lacks a major solar term.
* @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new
* moon
*/
private boolean hasNoMajorSolarTerm(int newMoon) {
return majorSolarTerm(newMoon) ==
majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, true));
}
//------------------------------------------------------------------
// Time to fields
//------------------------------------------------------------------
/**
* Return true if there is a leap month on or after month newMoon1 and
* at or before month newMoon2.
* @param newMoon1 days after January 1, 1970 0:00 Asia/Shanghai of a
* new moon
* @param newMoon2 days after January 1, 1970 0:00 Asia/Shanghai of a
* new moon
*/
private boolean isLeapMonthBetween(int newMoon1, int newMoon2) {
// This is only needed to debug the timeOfAngle divergence bug.
// Remove this later. Liu 11/9/00
// DEBUG
if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) {
throw new IllegalArgumentException("isLeapMonthBetween(" + newMoon1 +
", " + newMoon2 +
"): Invalid parameters");
}
return (newMoon2 >= newMoon1) &&
(isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, false)) ||
hasNoMajorSolarTerm(newMoon2));
}
/**
* Override Calendar to compute several fields specific to the Chinese
* calendar system. These are:
*
* <ul><li>ERA
* <li>YEAR
* <li>MONTH
* <li>DAY_OF_MONTH
* <li>DAY_OF_YEAR
* <li>EXTENDED_YEAR</ul>
*
* The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
* method is called. The getGregorianXxx() methods return Gregorian
* calendar equivalents for the given Julian day.
*
* <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH.
*/
protected void handleComputeFields(int julianDay) {
computeChineseFields(julianDay - EPOCH_JULIAN_DAY, // local days
getGregorianYear(), getGregorianMonth(),
true); // set all fields
}
/**
* Compute fields for the Chinese calendar system. This method can
* either set all relevant fields, as required by
* <code>handleComputeFields()</code>, or it can just set the MONTH and
* IS_LEAP_MONTH fields, as required by
* <code>handleComputeMonthStart()</code>.
* @param days days after January 1, 1970 0:00 Asia/Shanghai of the
* date to compute fields for
* @param gyear the Gregorian year of the given date
* @param gmonth the Gregorian month of the given date
* @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR,
* DAY_OF_MONTH, and DAY_OF_YEAR fields. In either case set the MONTH
* and IS_LEAP_MONTH fields.
*/
private void computeChineseFields(int days, int gyear, int gmonth,
boolean setAllFields) {
// Find the winter solstices before and after the target date.
// These define the boundaries of this Chinese year, specifically,
// the position of month 11, which always contains the solstice.
// We want solsticeBefore <= date < solsticeAfter.
int solsticeBefore;
int solsticeAfter = winterSolstice(gyear);
if (days < solsticeAfter) {
solsticeBefore = winterSolstice(gyear - 1);
} else {
solsticeBefore = solsticeAfter;
solsticeAfter = winterSolstice(gyear + 1);
}
// Find the start of the month after month 11. This will be either
// the prior month 12 or leap month 11 (very rare). Also find the
// start of the following month 11.
int firstMoon = newMoonNear(solsticeBefore + 1, true);
int lastMoon = newMoonNear(solsticeAfter + 1, false);
int thisMoon = newMoonNear(days + 1, false); // Start of this month
boolean isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12;
int month = synodicMonthsBetween(firstMoon, thisMoon);
if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) {
month--;
}
if (month < 1) {
month += 12;
}
boolean isLeapMonth = isLeapYear &&
hasNoMajorSolarTerm(thisMoon) &&
!isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false));
internalSet(MONTH, month-1); // Convert from 1-based to 0-based
internalSet(IS_LEAP_MONTH, isLeapMonth?1:0);
if (setAllFields) {
int year = gyear - CHINESE_EPOCH_YEAR;
if (month < 11 ||
gmonth >= Calendar.JULY) {
year++;
}
int dayOfMonth = days - thisMoon + 1;
internalSet(EXTENDED_YEAR, year);
// 0->0,60 1->1,1 60->1,60 61->2,1 etc.
int[] yearOfCycle = new int[1];
int cycle = floorDivide(year-1, 60, yearOfCycle);
internalSet(ERA, cycle+1);
internalSet(YEAR, yearOfCycle[0]+1);
internalSet(DAY_OF_MONTH, dayOfMonth);
// Days will be before the first new year we compute if this
// date is in month 11, leap 11, 12. There is never a leap 12.
// New year computations are cached so this should be cheap in
// the long run.
int newYear = newYear(gyear);
if (days < newYear) {
newYear = newYear(gyear-1);
}
internalSet(DAY_OF_YEAR, days - newYear + 1);
}
}
//------------------------------------------------------------------
// Fields to time
//------------------------------------------------------------------
/**
* Return the Chinese new year of the given Gregorian year.
* @param gyear a Gregorian year
* @return days after January 1, 1970 0:00 Asia/Shanghai of the
* Chinese new year of the given year (this will be a new moon)
*/
private int newYear(int gyear) {
long cacheValue = newYearCache.get(gyear);
if (cacheValue == CalendarCache.EMPTY) {
int solsticeBefore= winterSolstice(gyear - 1);
int solsticeAfter = winterSolstice(gyear);
int newMoon1 = newMoonNear(solsticeBefore + 1, true);
int newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, true);
int newMoon11 = newMoonNear(solsticeAfter + 1, false);
if (synodicMonthsBetween(newMoon1, newMoon11) == 12 &&
(hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) {
cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, true);
} else {
cacheValue = newMoon2;
}
newYearCache.put(gyear, cacheValue);
}
return (int) cacheValue;
}
/**
* Return the Julian day number of day before the first day of the
* given month in the given extended year.
*
* <p>Note: This method reads the IS_LEAP_MONTH field to determine
* whether the given month is a leap month.
* @param eyear the extended year
* @param month the zero-based month. The month is also determined
* by reading the IS_LEAP_MONTH field.
* @param return the Julian day number of the day before the first
* day of the given month and year
*/
protected int handleComputeMonthStart(int eyear, int month) {
int gyear = eyear + CHINESE_EPOCH_YEAR - 1; // Gregorian year
int newYear = newYear(gyear);
int newMoon = newMoonNear(newYear + month * 29, true);
int julianDay = newMoon + EPOCH_JULIAN_DAY;
int isLeapMonth = internalGet(IS_LEAP_MONTH);
computeGregorianFields(julianDay);
// Save fields for later restoration
int saveMonth = internalGet(MONTH);
// This will modify the MONTH and IS_LEAP_MONTH fields (only)
computeChineseFields(newMoon, getGregorianYear(),
getGregorianMonth(), false);
if (month != internalGet(MONTH) ||
isLeapMonth != internalGet(IS_LEAP_MONTH)) {
newMoon = newMoonNear(newMoon + SYNODIC_GAP, true);
julianDay = newMoon + EPOCH_JULIAN_DAY;
}
internalSet(MONTH, saveMonth);
internalSet(IS_LEAP_MONTH, isLeapMonth);
return julianDay - 1;
}
}

File diff suppressed because it is too large Load Diff