1 /*********************************************************************
\r
2 * Copyright (C) 2000-2009, International Business Machines
\r
3 * Corporation and others. All Rights Reserved.
\r
4 *********************************************************************
\r
7 package com.ibm.icu.util;
\r
9 import java.io.IOException;
\r
10 import java.io.ObjectInputStream;
\r
11 import java.util.Date;
\r
12 import java.util.Locale;
\r
14 import com.ibm.icu.impl.CalendarAstronomer;
\r
15 import com.ibm.icu.impl.CalendarCache;
\r
16 import com.ibm.icu.text.ChineseDateFormat;
\r
17 import com.ibm.icu.text.DateFormat;
\r
20 * <code>ChineseCalendar</code> is a concrete subclass of {@link Calendar}
\r
21 * that implements a traditional Chinese calendar. The traditional Chinese
\r
22 * calendar is a lunisolar calendar: Each month starts on a new moon, and
\r
23 * the months are numbered according to solar events, specifically, to
\r
24 * guarantee that month 11 always contains the winter solstice. In order
\r
25 * to accomplish this, leap months are inserted in certain years. Leap
\r
26 * months are numbered the same as the month they follow. The decision of
\r
27 * which month is a leap month depends on the relative movements of the sun
\r
30 * <p>This class defines one addition field beyond those defined by
\r
31 * <code>Calendar</code>: The <code>IS_LEAP_MONTH</code> field takes the
\r
32 * value of 0 for normal months, or 1 for leap months.
\r
34 * <p>All astronomical computations are performed with respect to a time
\r
35 * zone of GMT+8:00 and a longitude of 120 degrees east. Although some
\r
36 * calendars implement a historically more accurate convention of using
\r
37 * Beijing's local longitude (116 degrees 25 minutes east) and time zone
\r
38 * (GMT+7:45:40) for dates before 1929, we do not implement this here.
\r
40 * <p>Years are counted in two different ways in the Chinese calendar. The
\r
41 * first method is by sequential numbering from the 61st year of the reign
\r
42 * of Huang Di, 2637 BCE, which is designated year 1 on the Chinese
\r
43 * calendar. The second method uses 60-year cycles from the same starting
\r
44 * point, which is designated year 1 of cycle 1. In this class, the
\r
45 * <code>EXTENDED_YEAR</code> field contains the sequential year count.
\r
46 * The <code>ERA</code> field contains the cycle number, and the
\r
47 * <code>YEAR</code> field contains the year of the cycle, a value between
\r
50 * <p>There is some variation in what is considered the starting point of
\r
51 * the calendar, with some sources starting in the first year of the reign
\r
52 * of Huang Di, rather than the 61st. This gives continuous year numbers
\r
53 * 60 years greater and cycle numbers one greater than what this class
\r
56 * <p>Because <code>ChineseCalendar</code> defines an additional field and
\r
57 * redefines the way the <code>ERA</code> field is used, it requires a new
\r
58 * format class, <code>ChineseDateFormat</code>. As always, use the
\r
59 * methods <code>DateFormat.getXxxInstance(Calendar cal,...)</code> to
\r
60 * obtain a formatter for this calendar.
\r
62 * <p>References:<ul>
\r
64 * <li>Dershowitz and Reingold, <i>Calendrical Calculations</i>,
\r
65 * Cambridge University Press, 1997</li>
\r
67 * <li>Helmer Aslaksen's
\r
68 * <a href="http://www.math.nus.edu.sg/aslaksen/calendar/chinese.shtml">
\r
69 * Chinese Calendar page</a></li>
\r
71 * <li>The <a href="http://www.tondering.dk/claus/calendar.html">
\r
72 * Calendar FAQ</a></li>
\r
77 * This class should not be subclassed.</p>
\r
79 * ChineseCalendar usually should be instantiated using
\r
80 * {@link com.ibm.icu.util.Calendar#getInstance(ULocale)} passing in a <code>ULocale</code>
\r
81 * with the tag <code>"@calendar=chinese"</code>.</p>
\r
83 * @see com.ibm.icu.text.ChineseDateFormat
\r
84 * @see com.ibm.icu.util.Calendar
\r
88 public class ChineseCalendar extends Calendar {
\r
89 // jdk1.4.2 serialver
\r
90 private static final long serialVersionUID = 7312110751940929420L;
\r
92 //------------------------------------------------------------------
\r
95 // Time is represented as a scalar in two ways in this class. One is
\r
96 // the usual UTC epoch millis, that is, milliseconds after January 1,
\r
97 // 1970 Gregorian, 0:00:00.000 UTC. The other is in terms of 'local
\r
98 // days.' This is the number of days after January 1, 1970 Gregorian,
\r
99 // local to Beijing, China (since all computations of the Chinese
\r
100 // calendar are done in Beijing). That is, 0 represents January 1,
\r
101 // 1970 0:00 Asia/Shanghai. Conversion of local days to and from
\r
102 // standard epoch milliseconds is accomplished by the daysToMillis()
\r
103 // and millisToDays() methods.
\r
105 // Several methods use caches to improve performance. Caches are at
\r
106 // the object, not class level, under the assumption that typical
\r
107 // usage will be to have one instance of ChineseCalendar at a time.
\r
110 * We have one instance per object, and we don't synchronize it because
\r
111 * Calendar doesn't support multithreaded execution in the first place.
\r
113 private transient CalendarAstronomer astro = new CalendarAstronomer();
\r
116 * Cache that maps Gregorian year to local days of winter solstice.
\r
117 * @see #winterSolstice
\r
119 private transient CalendarCache winterSolsticeCache = new CalendarCache();
\r
122 * Cache that maps Gregorian year to local days of Chinese new year.
\r
125 private transient CalendarCache newYearCache = new CalendarCache();
\r
128 * True if the current year is a leap year. Updated with each time to
\r
129 * fields resolution.
\r
130 * @see #computeChineseFields
\r
132 private transient boolean isLeapYear;
\r
134 //------------------------------------------------------------------
\r
136 //------------------------------------------------------------------
\r
139 * Construct a <code>ChineseCalendar</code> with the default time zone and locale.
\r
142 public ChineseCalendar() {
\r
144 setTimeInMillis(System.currentTimeMillis());
\r
148 * Construct a <code>ChineseCalendar</code> with the give date set in the default time zone
\r
149 * with the default locale.
\r
150 * @param date The date to which the new calendar is set.
\r
153 public ChineseCalendar(Date date) {
\r
159 * Constructs a <code>ChineseCalendar</code> with the given date set
\r
160 * in the default time zone with the default locale.
\r
162 * @param year The value used to set the calendar's {@link #YEAR YEAR} time field.
\r
163 * @param month The value used to set the calendar's {@link #MONTH MONTH} time field.
\r
164 * The value is 0-based. e.g., 0 for January.
\r
165 * @param isLeapMonth The value used to set the Chiense calendar's (@link #IS_LEAP_MONTH)
\r
167 * @param date The value used to set the calendar's {@link #DATE DATE} time field.
\r
170 public ChineseCalendar(int year, int month, int isLeapMonth, int date) {
\r
171 super(TimeZone.getDefault(), ULocale.getDefault());
\r
173 // We need to set the current time once to initialize the ChineseCalendar's
\r
174 // ERA field to be the current era.
\r
175 setTimeInMillis(System.currentTimeMillis());
\r
176 // Then we need to clean up time fields
\r
177 this.set(MILLISECONDS_IN_DAY, 0);
\r
179 // Then set the given field values.
\r
180 this.set(YEAR, year);
\r
181 this.set(MONTH, month);
\r
182 this.set(IS_LEAP_MONTH, isLeapMonth);
\r
183 this.set(DATE, date);
\r
187 * Constructs a <code>ChineseCalendar</code> with the given date
\r
188 * and time set for the default time zone with the default locale.
\r
190 * @param year the value used to set the {@link #YEAR YEAR} time field in the calendar.
\r
191 * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar.
\r
192 * Note that the month value is 0-based. e.g., 0 for January.
\r
193 * @param isLeapMonth the value used to set the {@link #IS_LEAP_MONTH} time field
\r
195 * @param date the value used to set the {@link #DATE DATE} time field in the calendar.
\r
196 * @param hour the value used to set the {@link #HOUR_OF_DAY HOUR_OF_DAY} time field
\r
198 * @param minute the value used to set the {@link #MINUTE MINUTE} time field
\r
200 * @param second the value used to set the {@link #SECOND SECOND} time field
\r
204 public ChineseCalendar(int year, int month, int isLeapMonth, int date, int hour,
\r
205 int minute, int second)
\r
207 super(TimeZone.getDefault(), ULocale.getDefault());
\r
209 // We need to set the current time once to initialize the ChineseCalendar's
\r
210 // ERA field to be the current era.
\r
211 setTimeInMillis(System.currentTimeMillis());
\r
212 // Then set 0 to millisecond field
\r
213 this.set(MILLISECOND, 0);
\r
215 // Then, set the given field values.
\r
216 this.set(YEAR, year);
\r
217 this.set(MONTH, month);
\r
218 this.set(IS_LEAP_MONTH, isLeapMonth);
\r
219 this.set(DATE, date);
\r
220 this.set(HOUR_OF_DAY, hour);
\r
221 this.set(MINUTE, minute);
\r
222 this.set(SECOND, second);
\r
226 * Constructs a <code>ChineseCalendar</code> based on the current time
\r
227 * in the default time zone with the given locale.
\r
228 * @param aLocale The given locale
\r
231 public ChineseCalendar(Locale aLocale) {
\r
232 this(TimeZone.getDefault(), aLocale);
\r
233 setTimeInMillis(System.currentTimeMillis());
\r
237 * Construct a <code>ChineseCalendar</code> based on the current time
\r
238 * in the given time zone with the default locale.
\r
239 * @param zone the given time zone
\r
242 public ChineseCalendar(TimeZone zone) {
\r
243 super(zone, ULocale.getDefault());
\r
244 setTimeInMillis(System.currentTimeMillis());
\r
248 * Construct a <code>ChineseCalendar</code> based on the current time
\r
249 * in the given time zone with the given locale.
\r
250 * @param zone the given time zone
\r
251 * @param aLocale the given locale
\r
254 public ChineseCalendar(TimeZone zone, Locale aLocale) {
\r
255 super(zone, aLocale);
\r
256 setTimeInMillis(System.currentTimeMillis());
\r
260 * Constructs a <code>ChineseCalendar</code> based on the current time
\r
261 * in the default time zone with the given locale.
\r
263 * @param locale the given ulocale
\r
266 public ChineseCalendar(ULocale locale) {
\r
267 this(TimeZone.getDefault(), locale);
\r
268 setTimeInMillis(System.currentTimeMillis());
\r
272 * Construct a <code>ChineseCalendar</code> based on the current time
\r
273 * with the given time zone with the given locale.
\r
274 * @param zone the given time zone
\r
275 * @param locale the given ulocale
\r
278 public ChineseCalendar(TimeZone zone, ULocale locale) {
\r
279 super(zone, locale);
\r
280 setTimeInMillis(System.currentTimeMillis());
\r
283 //------------------------------------------------------------------
\r
284 // Public constants
\r
285 //------------------------------------------------------------------
\r
288 * Field indicating whether or not the current month is a leap month.
\r
289 * Should have a value of 0 for non-leap months, and 1 for leap months.
\r
292 public static int IS_LEAP_MONTH = BASE_FIELD_COUNT;
\r
295 * Count of fields in this class.
\r
297 private static final int FIELD_COUNT = IS_LEAP_MONTH + 1;
\r
299 //------------------------------------------------------------------
\r
300 // Calendar framework
\r
301 //------------------------------------------------------------------
\r
304 * Override Calendar to allocate our additional field.
\r
307 protected int[] handleCreateFields() {
\r
308 return new int[FIELD_COUNT];
\r
312 * Array defining the limits of field values for this class. Field
\r
313 * limits which are invariant with respect to calendar system and
\r
314 * defined by Calendar are left blank.
\r
318 * ERA 5000000 / 60 = 83333.
\r
320 * MONTH There are 12 or 13 lunar months in a year. However, we always
\r
321 * number them 0..11, with an intercalated, identically numbered leap
\r
322 * month, when necessary.
\r
324 * DAY_OF_YEAR In a non-leap year there are 353, 354, or 355 days. In
\r
325 * a leap year there are 383, 384, or 385 days.
\r
327 * WEEK_OF_YEAR The least maximum occurs if there are 353 days in the
\r
328 * year, and the first 6 are the last week of the previous year. Then
\r
329 * we have 49 full weeks and 4 days in the last week: 6 + 49*7 + 4 =
\r
330 * 353. So the least maximum is 50. The maximum occurs if there are
\r
331 * 385 days in the year, and WOY 1 extends 6 days into the prior year.
\r
332 * Then there are 54 full weeks, and 6 days in the last week: 1 + 54*7
\r
333 * + 6 = 385. The 6 days of the last week will fall into WOY 1 of the
\r
334 * next year. Maximum is 55.
\r
336 * WEEK_OF_MONTH In a 29 day month, if the first 7 days make up week 1
\r
337 * that leaves 3 full weeks and 1 day at the end. The least maximum is
\r
338 * thus 5. In a 30 days month, if the previous 6 days belong WOM 1 of
\r
339 * this month, we have 4 full weeks and 1 days at the end (which
\r
340 * technically will be WOM 1 of the next month, but will be reported by
\r
341 * time->fields and hence by getActualMaximum as WOM 6 of this month).
\r
344 * DAY_OF_WEEK_IN_MONTH In a 29 or 30 day month, there are 4 full weeks
\r
345 * plus 1 or 2 days at the end, so the maximum is always 5.
\r
347 private static final int LIMITS[][] = {
\r
348 // Minimum Greatest Least Maximum
\r
350 { 1, 1, 83333, 83333 }, // ERA
\r
351 { 1, 1, 60, 60 }, // YEAR
\r
352 { 0, 0, 11, 11 }, // MONTH
\r
353 { 1, 1, 50, 55 }, // WEEK_OF_YEAR
\r
354 {/* */}, // WEEK_OF_MONTH
\r
355 { 1, 1, 29, 30 }, // DAY_OF_MONTH
\r
356 { 1, 1, 353, 385 }, // DAY_OF_YEAR
\r
357 {/* */}, // DAY_OF_WEEK
\r
358 { -1, -1, 5, 5 }, // DAY_OF_WEEK_IN_MONTH
\r
361 {/* */}, // HOUR_OF_DAY
\r
364 {/* */}, // MILLISECOND
\r
365 {/* */}, // ZONE_OFFSET
\r
366 {/* */}, // DST_OFFSET
\r
367 { -5000000, -5000000, 5000000, 5000000 }, // YEAR_WOY
\r
368 {/* */}, // DOW_LOCAL
\r
369 { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR
\r
370 {/* */}, // JULIAN_DAY
\r
371 {/* */}, // MILLISECONDS_IN_DAY
\r
372 { 0, 0, 1, 1 }, // IS_LEAP_MONTH
\r
376 * Override Calendar to return the limit value for the given field.
\r
379 protected int handleGetLimit(int field, int limitType) {
\r
380 return LIMITS[field][limitType];
\r
384 * Implement abstract Calendar method to return the extended year
\r
385 * defined by the current fields. This will use either the ERA and
\r
386 * YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR
\r
387 * field as the continuous year count, depending on which is newer.
\r
390 protected int handleGetExtendedYear() {
\r
392 if (newestStamp(ERA, YEAR, UNSET) <= getStamp(EXTENDED_YEAR)) {
\r
393 year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
\r
395 int cycle = internalGet(ERA, 1) - 1; // 0-based cycle
\r
396 year = cycle * 60 + internalGet(YEAR, 1);
\r
402 * Override Calendar method to return the number of days in the given
\r
403 * extended year and month.
\r
405 * <p>Note: This method also reads the IS_LEAP_MONTH field to determine
\r
406 * whether or not the given month is a leap month.
\r
409 protected int handleGetMonthLength(int extendedYear, int month) {
\r
410 int thisStart = handleComputeMonthStart(extendedYear, month, true) -
\r
411 EPOCH_JULIAN_DAY + 1; // Julian day -> local days
\r
412 int nextStart = newMoonNear(thisStart + SYNODIC_GAP, true);
\r
413 return nextStart - thisStart;
\r
417 * Framework method to create a calendar-specific DateFormat object
\r
418 * using the the given pattern. This method is responsible for
\r
419 * creating the calendar- specific DateFormat and DateFormatSymbols
\r
420 * objects as needed.
\r
423 protected DateFormat handleGetDateFormat(String pattern, String override, ULocale locale) {
\r
424 return new ChineseDateFormat(pattern, override, locale);
\r
428 * Field resolution table that incorporates IS_LEAP_MONTH.
\r
430 static final int[][][] CHINESE_DATE_PRECEDENCE = {
\r
433 { WEEK_OF_YEAR, DAY_OF_WEEK },
\r
434 { WEEK_OF_MONTH, DAY_OF_WEEK },
\r
435 { DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
\r
436 { WEEK_OF_YEAR, DOW_LOCAL },
\r
437 { WEEK_OF_MONTH, DOW_LOCAL },
\r
438 { DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
\r
440 { RESOLVE_REMAP | DAY_OF_MONTH, IS_LEAP_MONTH },
\r
445 { DAY_OF_WEEK_IN_MONTH },
\r
446 { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
\r
447 { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
\r
452 * Override Calendar to add IS_LEAP_MONTH to the field resolution
\r
456 protected int[][][] getFieldResolutionTable() {
\r
457 return CHINESE_DATE_PRECEDENCE;
\r
461 * Adjust this calendar to be delta months before or after a given
\r
462 * start position, pinning the day of month if necessary. The start
\r
463 * position is given as a local days number for the start of the month
\r
464 * and a day-of-month. Used by add() and roll().
\r
465 * @param newMoon the local days of the first day of the month of the
\r
466 * start position (days after January 1, 1970 0:00 Asia/Shanghai)
\r
467 * @param dom the 1-based day-of-month of the start position
\r
468 * @param delta the number of months to move forward or backward from
\r
469 * the start position
\r
471 private void offsetMonth(int newMoon, int dom, int delta) {
\r
472 // Move to the middle of the month before our target month.
\r
473 newMoon += (int) (CalendarAstronomer.SYNODIC_MONTH * (delta - 0.5));
\r
475 // Search forward to the target month's new moon
\r
476 newMoon = newMoonNear(newMoon, true);
\r
478 // Find the target dom
\r
479 int jd = newMoon + EPOCH_JULIAN_DAY - 1 + dom;
\r
481 // Pin the dom. In this calendar all months are 29 or 30 days
\r
482 // so pinning just means handling dom 30.
\r
484 set(JULIAN_DAY, jd-1);
\r
485 // TODO Fix this. We really shouldn't ever have to
\r
486 // explicitly call complete(). This is either a bug in
\r
487 // this method, in ChineseCalendar, or in
\r
488 // Calendar.getActualMaximum(). I suspect the last.
\r
490 if (getActualMaximum(DAY_OF_MONTH) >= dom) {
\r
491 set(JULIAN_DAY, jd);
\r
494 set(JULIAN_DAY, jd);
\r
499 * Override Calendar to handle leap months properly.
\r
502 public void add(int field, int amount) {
\r
506 int dom = get(DAY_OF_MONTH);
\r
507 int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day
\r
508 int moon = day - dom + 1; // New moon
\r
509 offsetMonth(moon, dom, amount);
\r
513 super.add(field, amount);
\r
519 * Override Calendar to handle leap months properly.
\r
522 public void roll(int field, int amount) {
\r
526 int dom = get(DAY_OF_MONTH);
\r
527 int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day
\r
528 int moon = day - dom + 1; // New moon (start of this month)
\r
530 // Note throughout the following: Months 12 and 1 are never
\r
531 // followed by a leap month (D&R p. 185).
\r
533 // Compute the adjusted month number m. This is zero-based
\r
534 // value from 0..11 in a non-leap year, and from 0..12 in a
\r
536 int m = get(MONTH); // 0-based month
\r
537 if (isLeapYear) { // (member variable)
\r
538 if (get(IS_LEAP_MONTH) == 1) {
\r
541 // Check for a prior leap month. (In the
\r
542 // following, month 0 is the first month of the
\r
543 // year.) Month 0 is never followed by a leap
\r
544 // month, and we know month m is not a leap month.
\r
545 // moon1 will be the start of month 0 if there is
\r
546 // no leap month between month 0 and month m;
\r
547 // otherwise it will be the start of month 1.
\r
549 (int) (CalendarAstronomer.SYNODIC_MONTH * (m - 0.5));
\r
550 moon1 = newMoonNear(moon1, true);
\r
551 if (isLeapMonthBetween(moon1, moon)) {
\r
557 // Now do the standard roll computation on m, with the
\r
558 // allowed range of 0..n-1, where n is 12 or 13.
\r
559 int n = isLeapYear ? 13 : 12; // Months in this year
\r
560 int newM = (m + amount) % n;
\r
566 offsetMonth(moon, dom, newM - m);
\r
571 super.roll(field, amount);
\r
576 //------------------------------------------------------------------
\r
577 // Support methods and constants
\r
578 //------------------------------------------------------------------
\r
581 * The start year of the Chinese calendar, the 61st year of the reign
\r
582 * of Huang Di. Some sources use the first year of his reign,
\r
583 * resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle)
\r
584 * values one greater.
\r
586 private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year
\r
589 * The offset from GMT in milliseconds at which we perform astronomical
\r
590 * computations. Some sources use a different historically accurate
\r
591 * offset of GMT+7:45:40 for years before 1929; we do not do this.
\r
593 private static final long CHINA_OFFSET = 8*ONE_HOUR;
\r
596 * Value to be added or subtracted from the local days of a new moon to
\r
597 * get close to the next or prior new moon, but not cross it. Must be
\r
598 * >= 1 and < CalendarAstronomer.SYNODIC_MONTH.
\r
600 private static final int SYNODIC_GAP = 25;
\r
603 * Convert local days to UTC epoch milliseconds.
\r
604 * @param days days after January 1, 1970 0:00 Asia/Shanghai
\r
605 * @return milliseconds after January 1, 1970 0:00 GMT
\r
607 private static final long daysToMillis(int days) {
\r
608 return (days * ONE_DAY) - CHINA_OFFSET;
\r
612 * Convert UTC epoch milliseconds to local days.
\r
613 * @param millis milliseconds after January 1, 1970 0:00 GMT
\r
614 * @return days after January 1, 1970 0:00 Asia/Shanghai
\r
616 private static final int millisToDays(long millis) {
\r
617 return (int) floorDivide(millis + CHINA_OFFSET, ONE_DAY);
\r
620 //------------------------------------------------------------------
\r
621 // Astronomical computations
\r
622 //------------------------------------------------------------------
\r
625 * Return the major solar term on or after December 15 of the given
\r
626 * Gregorian year, that is, the winter solstice of the given year.
\r
627 * Computations are relative to Asia/Shanghai time zone.
\r
628 * @param gyear a Gregorian year
\r
629 * @return days after January 1, 1970 0:00 Asia/Shanghai of the
\r
630 * winter solstice of the given year
\r
632 private int winterSolstice(int gyear) {
\r
634 long cacheValue = winterSolsticeCache.get(gyear);
\r
636 if (cacheValue == CalendarCache.EMPTY) {
\r
637 // In books December 15 is used, but it fails for some years
\r
638 // using our algorithms, e.g.: 1298 1391 1492 1553 1560. That
\r
639 // is, winterSolstice(1298) starts search at Dec 14 08:00:00
\r
640 // PST 1298 with a final result of Dec 14 10:31:59 PST 1299.
\r
641 long ms = daysToMillis(computeGregorianMonthStart(gyear, DECEMBER) +
\r
642 1 - EPOCH_JULIAN_DAY);
\r
645 // Winter solstice is 270 degrees solar longitude aka Dongzhi
\r
646 long solarLong = astro.getSunTime(CalendarAstronomer.WINTER_SOLSTICE,
\r
648 cacheValue = millisToDays(solarLong);
\r
649 winterSolsticeCache.put(gyear, cacheValue);
\r
651 return (int) cacheValue;
\r
655 * Return the closest new moon to the given date, searching either
\r
656 * forward or backward in time.
\r
657 * @param days days after January 1, 1970 0:00 Asia/Shanghai
\r
658 * @param after if true, search for a new moon on or after the given
\r
659 * date; otherwise, search for a new moon before it
\r
660 * @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest
\r
661 * new moon after or before <code>days</code>
\r
663 private int newMoonNear(int days, boolean after) {
\r
665 astro.setTime(daysToMillis(days));
\r
666 long newMoon = astro.getMoonTime(CalendarAstronomer.NEW_MOON, after);
\r
668 return millisToDays(newMoon);
\r
672 * Return the nearest integer number of synodic months between
\r
674 * @param day1 days after January 1, 1970 0:00 Asia/Shanghai
\r
675 * @param day2 days after January 1, 1970 0:00 Asia/Shanghai
\r
676 * @return the nearest integer number of months between day1 and day2
\r
678 private int synodicMonthsBetween(int day1, int day2) {
\r
679 return (int) Math.round((day2 - day1) / CalendarAstronomer.SYNODIC_MONTH);
\r
683 * Return the major solar term on or before a given date. This
\r
684 * will be an integer from 1..12, with 1 corresponding to 330 degrees,
\r
685 * 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees.
\r
686 * @param days days after January 1, 1970 0:00 Asia/Shanghai
\r
688 private int majorSolarTerm(int days) {
\r
690 astro.setTime(daysToMillis(days));
\r
692 // Compute (floor(solarLongitude / (pi/6)) + 2) % 12
\r
693 int term = ((int) Math.floor(6 * astro.getSunLongitude() / Math.PI) + 2) % 12;
\r
701 * Return true if the given month lacks a major solar term.
\r
702 * @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new
\r
705 private boolean hasNoMajorSolarTerm(int newMoon) {
\r
707 int mst = majorSolarTerm(newMoon);
\r
708 int nmn = newMoonNear(newMoon + SYNODIC_GAP, true);
\r
709 int mstt = majorSolarTerm(nmn);
\r
710 return mst == mstt;
\r
712 return majorSolarTerm(newMoon) ==
\r
713 majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, true));
\r
717 //------------------------------------------------------------------
\r
719 //------------------------------------------------------------------
\r
722 * Return true if there is a leap month on or after month newMoon1 and
\r
723 * at or before month newMoon2.
\r
724 * @param newMoon1 days after January 1, 1970 0:00 Asia/Shanghai of a
\r
726 * @param newMoon2 days after January 1, 1970 0:00 Asia/Shanghai of a
\r
729 private boolean isLeapMonthBetween(int newMoon1, int newMoon2) {
\r
731 // This is only needed to debug the timeOfAngle divergence bug.
\r
732 // Remove this later. Liu 11/9/00
\r
734 if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) {
\r
735 throw new IllegalArgumentException("isLeapMonthBetween(" + newMoon1 +
\r
737 "): Invalid parameters");
\r
740 return (newMoon2 >= newMoon1) &&
\r
741 (isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, false)) ||
\r
742 hasNoMajorSolarTerm(newMoon2));
\r
746 * Override Calendar to compute several fields specific to the Chinese
\r
747 * calendar system. These are:
\r
754 * <li>EXTENDED_YEAR</ul>
\r
756 * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
\r
757 * method is called. The getGregorianXxx() methods return Gregorian
\r
758 * calendar equivalents for the given Julian day.
\r
760 * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH.
\r
763 protected void handleComputeFields(int julianDay) {
\r
765 computeChineseFields(julianDay - EPOCH_JULIAN_DAY, // local days
\r
766 getGregorianYear(), getGregorianMonth(),
\r
767 true); // set all fields
\r
771 * Compute fields for the Chinese calendar system. This method can
\r
772 * either set all relevant fields, as required by
\r
773 * <code>handleComputeFields()</code>, or it can just set the MONTH and
\r
774 * IS_LEAP_MONTH fields, as required by
\r
775 * <code>handleComputeMonthStart()</code>.
\r
777 * <p>As a side effect, this method sets {@link #isLeapYear}.
\r
778 * @param days days after January 1, 1970 0:00 Asia/Shanghai of the
\r
779 * date to compute fields for
\r
780 * @param gyear the Gregorian year of the given date
\r
781 * @param gmonth the Gregorian month of the given date
\r
782 * @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR,
\r
783 * DAY_OF_MONTH, and DAY_OF_YEAR fields. In either case set the MONTH
\r
784 * and IS_LEAP_MONTH fields.
\r
786 private void computeChineseFields(int days, int gyear, int gmonth,
\r
787 boolean setAllFields) {
\r
789 // Find the winter solstices before and after the target date.
\r
790 // These define the boundaries of this Chinese year, specifically,
\r
791 // the position of month 11, which always contains the solstice.
\r
792 // We want solsticeBefore <= date < solsticeAfter.
\r
793 int solsticeBefore;
\r
794 int solsticeAfter = winterSolstice(gyear);
\r
795 if (days < solsticeAfter) {
\r
796 solsticeBefore = winterSolstice(gyear - 1);
\r
798 solsticeBefore = solsticeAfter;
\r
799 solsticeAfter = winterSolstice(gyear + 1);
\r
802 // Find the start of the month after month 11. This will be either
\r
803 // the prior month 12 or leap month 11 (very rare). Also find the
\r
804 // start of the following month 11.
\r
805 int firstMoon = newMoonNear(solsticeBefore + 1, true);
\r
806 int lastMoon = newMoonNear(solsticeAfter + 1, false);
\r
807 int thisMoon = newMoonNear(days + 1, false); // Start of this month
\r
808 // Note: isLeapYear is a member variable
\r
809 isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12;
\r
811 int month = synodicMonthsBetween(firstMoon, thisMoon);
\r
812 if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) {
\r
819 boolean isLeapMonth = isLeapYear &&
\r
820 hasNoMajorSolarTerm(thisMoon) &&
\r
821 !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false));
\r
823 internalSet(MONTH, month-1); // Convert from 1-based to 0-based
\r
824 internalSet(IS_LEAP_MONTH, isLeapMonth?1:0);
\r
826 if (setAllFields) {
\r
828 int year = gyear - CHINESE_EPOCH_YEAR;
\r
833 int dayOfMonth = days - thisMoon + 1;
\r
835 internalSet(EXTENDED_YEAR, year);
\r
837 // 0->0,60 1->1,1 60->1,60 61->2,1 etc.
\r
838 int[] yearOfCycle = new int[1];
\r
839 int cycle = floorDivide(year-1, 60, yearOfCycle);
\r
840 internalSet(ERA, cycle+1);
\r
841 internalSet(YEAR, yearOfCycle[0]+1);
\r
843 internalSet(DAY_OF_MONTH, dayOfMonth);
\r
845 // Days will be before the first new year we compute if this
\r
846 // date is in month 11, leap 11, 12. There is never a leap 12.
\r
847 // New year computations are cached so this should be cheap in
\r
849 int newYear = newYear(gyear);
\r
850 if (days < newYear) {
\r
851 newYear = newYear(gyear-1);
\r
853 internalSet(DAY_OF_YEAR, days - newYear + 1);
\r
857 //------------------------------------------------------------------
\r
859 //------------------------------------------------------------------
\r
862 * Return the Chinese new year of the given Gregorian year.
\r
863 * @param gyear a Gregorian year
\r
864 * @return days after January 1, 1970 0:00 Asia/Shanghai of the
\r
865 * Chinese new year of the given year (this will be a new moon)
\r
867 private int newYear(int gyear) {
\r
869 long cacheValue = newYearCache.get(gyear);
\r
871 if (cacheValue == CalendarCache.EMPTY) {
\r
873 int solsticeBefore= winterSolstice(gyear - 1);
\r
874 int solsticeAfter = winterSolstice(gyear);
\r
875 int newMoon1 = newMoonNear(solsticeBefore + 1, true);
\r
876 int newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, true);
\r
877 int newMoon11 = newMoonNear(solsticeAfter + 1, false);
\r
879 if (synodicMonthsBetween(newMoon1, newMoon11) == 12 &&
\r
880 (hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) {
\r
881 cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, true);
\r
883 cacheValue = newMoon2;
\r
886 newYearCache.put(gyear, cacheValue);
\r
888 return (int) cacheValue;
\r
892 * Return the Julian day number of day before the first day of the
\r
893 * given month in the given extended year.
\r
895 * <p>Note: This method reads the IS_LEAP_MONTH field to determine
\r
896 * whether the given month is a leap month.
\r
897 * @param eyear the extended year
\r
898 * @param month the zero-based month. The month is also determined
\r
899 * by reading the IS_LEAP_MONTH field.
\r
900 * @return the Julian day number of the day before the first
\r
901 * day of the given month and year
\r
904 protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {
\r
906 // If the month is out of range, adjust it into range, and
\r
907 // modify the extended year value accordingly.
\r
908 if (month < 0 || month > 11) {
\r
909 int[] rem = new int[1];
\r
910 eyear += floorDivide(month, 12, rem);
\r
914 int gyear = eyear + CHINESE_EPOCH_YEAR - 1; // Gregorian year
\r
915 int newYear = newYear(gyear);
\r
916 int newMoon = newMoonNear(newYear + month * 29, true);
\r
918 int julianDay = newMoon + EPOCH_JULIAN_DAY;
\r
920 // Save fields for later restoration
\r
921 int saveMonth = internalGet(MONTH);
\r
922 int saveIsLeapMonth = internalGet(IS_LEAP_MONTH);
\r
924 // Ignore IS_LEAP_MONTH field if useMonth is false
\r
925 int isLeapMonth = useMonth ? saveIsLeapMonth : 0;
\r
927 computeGregorianFields(julianDay);
\r
929 // This will modify the MONTH and IS_LEAP_MONTH fields (only)
\r
930 computeChineseFields(newMoon, getGregorianYear(),
\r
931 getGregorianMonth(), false);
\r
933 if (month != internalGet(MONTH) ||
\r
934 isLeapMonth != internalGet(IS_LEAP_MONTH)) {
\r
935 newMoon = newMoonNear(newMoon + SYNODIC_GAP, true);
\r
936 julianDay = newMoon + EPOCH_JULIAN_DAY;
\r
939 internalSet(MONTH, saveMonth);
\r
940 internalSet(IS_LEAP_MONTH, saveIsLeapMonth);
\r
942 return julianDay - 1;
\r
946 * Return the current Calendar type.
\r
947 * @return type of calendar
\r
950 public String getType() {
\r
955 * Override readObject.
\r
957 private void readObject(ObjectInputStream stream)
\r
958 throws IOException, ClassNotFoundException
\r
960 stream.defaultReadObject();
\r
962 /* set up the transient caches... */
\r
963 astro = new CalendarAstronomer();
\r
964 winterSolsticeCache = new CalendarCache();
\r
965 newYearCache = new CalendarCache();
\r
969 private static CalendarFactory factory;
\r
970 public static CalendarFactory factory() {
\r
971 if (factory == null) {
\r
972 factory = new CalendarFactory() {
\r
973 public Calendar create(TimeZone tz, ULocale loc) {
\r
974 return new ChineseCalendar(tz, loc);
\r
977 public String factoryName() {
\r