1 /*********************************************************************
2 * Copyright (C) 2000-2011, International Business Machines
3 * Corporation and others. All Rights Reserved.
4 *********************************************************************
7 package com.ibm.icu.util;
9 import java.io.IOException;
10 import java.io.ObjectInputStream;
11 import java.util.Date;
12 import java.util.Locale;
14 import com.ibm.icu.impl.CalendarAstronomer;
15 import com.ibm.icu.impl.CalendarCache;
16 import com.ibm.icu.text.ChineseDateFormat;
17 import com.ibm.icu.text.DateFormat;
18 import com.ibm.icu.util.ULocale.Category;
21 * <code>ChineseCalendar</code> is a concrete subclass of {@link Calendar}
22 * that implements a traditional Chinese calendar. The traditional Chinese
23 * calendar is a lunisolar calendar: Each month starts on a new moon, and
24 * the months are numbered according to solar events, specifically, to
25 * guarantee that month 11 always contains the winter solstice. In order
26 * to accomplish this, leap months are inserted in certain years. Leap
27 * months are numbered the same as the month they follow. The decision of
28 * which month is a leap month depends on the relative movements of the sun
31 * <p>All astronomical computations are performed with respect to a time
32 * zone of GMT+8:00 and a longitude of 120 degrees east. Although some
33 * calendars implement a historically more accurate convention of using
34 * Beijing's local longitude (116 degrees 25 minutes east) and time zone
35 * (GMT+7:45:40) for dates before 1929, we do not implement this here.
37 * <p>Years are counted in two different ways in the Chinese calendar. The
38 * first method is by sequential numbering from the 61st year of the reign
39 * of Huang Di, 2637 BCE, which is designated year 1 on the Chinese
40 * calendar. The second method uses 60-year cycles from the same starting
41 * point, which is designated year 1 of cycle 1. In this class, the
42 * <code>EXTENDED_YEAR</code> field contains the sequential year count.
43 * The <code>ERA</code> field contains the cycle number, and the
44 * <code>YEAR</code> field contains the year of the cycle, a value between
47 * <p>There is some variation in what is considered the starting point of
48 * the calendar, with some sources starting in the first year of the reign
49 * of Huang Di, rather than the 61st. This gives continuous year numbers
50 * 60 years greater and cycle numbers one greater than what this class
53 * <p>Because <code>ChineseCalendar</code> defines an additional field and
54 * redefines the way the <code>ERA</code> field is used, it requires a new
55 * format class, <code>ChineseDateFormat</code>. As always, use the
56 * methods <code>DateFormat.getXxxInstance(Calendar cal,...)</code> to
57 * obtain a formatter for this calendar.
61 * <li>Dershowitz and Reingold, <i>Calendrical Calculations</i>,
62 * Cambridge University Press, 1997</li>
64 * <li>Helmer Aslaksen's
65 * <a href="http://www.math.nus.edu.sg/aslaksen/calendar/chinese.shtml">
66 * Chinese Calendar page</a></li>
68 * <li>The <a href="http://www.tondering.dk/claus/calendar.html">
69 * Calendar FAQ</a></li>
74 * This class should not be subclassed.</p>
76 * ChineseCalendar usually should be instantiated using
77 * {@link com.ibm.icu.util.Calendar#getInstance(ULocale)} passing in a <code>ULocale</code>
78 * with the tag <code>"@calendar=chinese"</code>.</p>
80 * @see com.ibm.icu.text.ChineseDateFormat
81 * @see com.ibm.icu.util.Calendar
85 public class ChineseCalendar extends Calendar {
87 private static final long serialVersionUID = 7312110751940929420L;
89 //------------------------------------------------------------------
92 // Time is represented as a scalar in two ways in this class. One is
93 // the usual UTC epoch millis, that is, milliseconds after January 1,
94 // 1970 Gregorian, 0:00:00.000 UTC. The other is in terms of 'local
95 // days.' This is the number of days after January 1, 1970 Gregorian,
96 // local to Beijing, China (since all computations of the Chinese
97 // calendar are done in Beijing). That is, 0 represents January 1,
98 // 1970 0:00 Asia/Shanghai. Conversion of local days to and from
99 // standard epoch milliseconds is accomplished by the daysToMillis()
100 // and millisToDays() methods.
102 // Several methods use caches to improve performance. Caches are at
103 // the object, not class level, under the assumption that typical
104 // usage will be to have one instance of ChineseCalendar at a time.
107 * We have one instance per object, and we don't synchronize it because
108 * Calendar doesn't support multithreaded execution in the first place.
110 private transient CalendarAstronomer astro = new CalendarAstronomer();
113 * Cache that maps Gregorian year to local days of winter solstice.
114 * @see #winterSolstice
116 private transient CalendarCache winterSolsticeCache = new CalendarCache();
119 * Cache that maps Gregorian year to local days of Chinese new year.
122 private transient CalendarCache newYearCache = new CalendarCache();
125 * True if the current year is a leap year. Updated with each time to
127 * @see #computeChineseFields
129 private transient boolean isLeapYear;
131 //------------------------------------------------------------------
133 //------------------------------------------------------------------
136 * Construct a <code>ChineseCalendar</code> with the default time zone and locale.
139 public ChineseCalendar() {
141 setTimeInMillis(System.currentTimeMillis());
145 * Construct a <code>ChineseCalendar</code> with the give date set in the default time zone
146 * with the default locale.
147 * @param date The date to which the new calendar is set.
150 public ChineseCalendar(Date date) {
156 * Constructs a <code>ChineseCalendar</code> with the given date set
157 * in the default time zone with the default <code>FORMAT</code> locale.
159 * @param year The value used to set the calendar's {@link #YEAR YEAR} time field.
160 * @param month The value used to set the calendar's {@link #MONTH MONTH} time field.
161 * The value is 0-based. e.g., 0 for January.
162 * @param isLeapMonth The value used to set the Chinese calendar's (@link #IS_LEAP_MONTH)
164 * @param date The value used to set the calendar's {@link #DATE DATE} time field.
165 * @see Category#FORMAT
168 public ChineseCalendar(int year, int month, int isLeapMonth, int date) {
169 super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
171 // We need to set the current time once to initialize the ChineseCalendar's
172 // ERA field to be the current era.
173 setTimeInMillis(System.currentTimeMillis());
174 // Then we need to clean up time fields
175 this.set(MILLISECONDS_IN_DAY, 0);
177 // Then set the given field values.
178 this.set(YEAR, year);
179 this.set(MONTH, month);
180 this.set(IS_LEAP_MONTH, isLeapMonth);
181 this.set(DATE, date);
185 * Constructs a <code>ChineseCalendar</code> with the given date
186 * and time set for the default time zone with the default <code>FORMAT</code> locale.
188 * @param year the value used to set the {@link #YEAR YEAR} time field in the calendar.
189 * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar.
190 * Note that the month value is 0-based. e.g., 0 for January.
191 * @param isLeapMonth the value used to set the {@link #IS_LEAP_MONTH} time field
193 * @param date the value used to set the {@link #DATE DATE} time field in the calendar.
194 * @param hour the value used to set the {@link #HOUR_OF_DAY HOUR_OF_DAY} time field
196 * @param minute the value used to set the {@link #MINUTE MINUTE} time field
198 * @param second the value used to set the {@link #SECOND SECOND} time field
200 * @see Category#FORMAT
203 public ChineseCalendar(int year, int month, int isLeapMonth, int date, int hour,
204 int minute, int second)
206 super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
208 // We need to set the current time once to initialize the ChineseCalendar's
209 // ERA field to be the current era.
210 setTimeInMillis(System.currentTimeMillis());
211 // Then set 0 to millisecond field
212 this.set(MILLISECOND, 0);
214 // Then, set the given field values.
215 this.set(YEAR, year);
216 this.set(MONTH, month);
217 this.set(IS_LEAP_MONTH, isLeapMonth);
218 this.set(DATE, date);
219 this.set(HOUR_OF_DAY, hour);
220 this.set(MINUTE, minute);
221 this.set(SECOND, second);
225 * Constructs a <code>ChineseCalendar</code> with the given date set
226 * in the default time zone with the default <code>FORMAT</code> locale.
228 * @param era The value used to set the calendar's {@link #ERA ERA} time field.
229 * @param year The value used to set the calendar's {@link #YEAR YEAR} time field.
230 * @param month The value used to set the calendar's {@link #MONTH MONTH} time field.
231 * The value is 0-based. e.g., 0 for January.
232 * @param isLeapMonth The value used to set the Chinese calendar's (@link #IS_LEAP_MONTH)
234 * @param date The value used to set the calendar's {@link #DATE DATE} time field.
235 * @see Category#FORMAT
237 * @provisional This API might change or be removed in a future release.
239 public ChineseCalendar(int era, int year, int month, int isLeapMonth, int date)
241 super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
243 // We need to set the current time once to initialize the ChineseCalendar's
244 // ERA field to be the current era.
245 setTimeInMillis(System.currentTimeMillis());
247 // Then we need to clean up time fields
248 this.set(MILLISECONDS_IN_DAY, 0);
250 // Then set the given field values.
252 this.set(YEAR, year);
253 this.set(MONTH, month);
254 this.set(IS_LEAP_MONTH, isLeapMonth);
255 this.set(DATE, date);
259 * Constructs a <code>ChineseCalendar</code> with the given date
260 * and time set for the default time zone with the default <code>FORMAT</code> locale.
262 * @param era the value used to set the calendar's {@link #ERA ERA} time field.
263 * @param year the value used to set the {@link #YEAR YEAR} time field in the calendar.
264 * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar.
265 * Note that the month value is 0-based. e.g., 0 for January.
266 * @param isLeapMonth the value used to set the {@link #IS_LEAP_MONTH} time field
268 * @param date the value used to set the {@link #DATE DATE} time field in the calendar.
269 * @param hour the value used to set the {@link #HOUR_OF_DAY HOUR_OF_DAY} time field
271 * @param minute the value used to set the {@link #MINUTE MINUTE} time field
273 * @param second the value used to set the {@link #SECOND SECOND} time field
275 * @see Category#FORMAT
277 * @provisional This API might change or be removed in a future release.
279 public ChineseCalendar(int era, int year, int month, int isLeapMonth, int date, int hour,
280 int minute, int second)
282 super(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
284 // We need to set the current time once to initialize the ChineseCalendar's
285 // ERA field to be the current era.
286 setTimeInMillis(System.currentTimeMillis());
288 // Then set 0 to millisecond field
289 this.set(MILLISECOND, 0);
291 // Then, set the given field values.
293 this.set(YEAR, year);
294 this.set(MONTH, month);
295 this.set(IS_LEAP_MONTH, isLeapMonth);
296 this.set(DATE, date);
297 this.set(HOUR_OF_DAY, hour);
298 this.set(MINUTE, minute);
299 this.set(SECOND, second);
303 * Constructs a <code>ChineseCalendar</code> based on the current time
304 * in the default time zone with the given locale.
305 * @param aLocale The given locale
308 public ChineseCalendar(Locale aLocale) {
309 this(TimeZone.getDefault(), aLocale);
310 setTimeInMillis(System.currentTimeMillis());
314 * Construct a <code>ChineseCalendar</code> based on the current time
315 * in the given time zone with the default <code>FORMAT</code> locale.
316 * @param zone the given time zone
317 * @see Category#FORMAT
320 public ChineseCalendar(TimeZone zone) {
321 super(zone, ULocale.getDefault(Category.FORMAT));
322 setTimeInMillis(System.currentTimeMillis());
326 * Construct a <code>ChineseCalendar</code> based on the current time
327 * in the given time zone with the given locale.
328 * @param zone the given time zone
329 * @param aLocale the given locale
332 public ChineseCalendar(TimeZone zone, Locale aLocale) {
333 super(zone, aLocale);
334 setTimeInMillis(System.currentTimeMillis());
338 * Constructs a <code>ChineseCalendar</code> based on the current time
339 * in the default time zone with the given locale.
341 * @param locale the given ulocale
344 public ChineseCalendar(ULocale locale) {
345 this(TimeZone.getDefault(), locale);
346 setTimeInMillis(System.currentTimeMillis());
350 * Construct a <code>ChineseCalendar</code> based on the current time
351 * with the given time zone with the given locale.
352 * @param zone the given time zone
353 * @param locale the given ulocale
356 public ChineseCalendar(TimeZone zone, ULocale locale) {
358 setTimeInMillis(System.currentTimeMillis());
361 //------------------------------------------------------------------
363 //------------------------------------------------------------------
366 * Field indicating whether or not the current month is a leap month.
367 * Should have a value of 0 for non-leap months, and 1 for leap months.
370 // public static int IS_LEAP_MONTH = BASE_FIELD_COUNT;
373 //------------------------------------------------------------------
374 // Calendar framework
375 //------------------------------------------------------------------
378 * Array defining the limits of field values for this class. Field
379 * limits which are invariant with respect to calendar system and
380 * defined by Calendar are left blank.
384 * ERA 5000000 / 60 = 83333.
386 * MONTH There are 12 or 13 lunar months in a year. However, we always
387 * number them 0..11, with an intercalated, identically numbered leap
388 * month, when necessary.
390 * DAY_OF_YEAR In a non-leap year there are 353, 354, or 355 days. In
391 * a leap year there are 383, 384, or 385 days.
393 * WEEK_OF_YEAR The least maximum occurs if there are 353 days in the
394 * year, and the first 6 are the last week of the previous year. Then
395 * we have 49 full weeks and 4 days in the last week: 6 + 49*7 + 4 =
396 * 353. So the least maximum is 50. The maximum occurs if there are
397 * 385 days in the year, and WOY 1 extends 6 days into the prior year.
398 * Then there are 54 full weeks, and 6 days in the last week: 1 + 54*7
399 * + 6 = 385. The 6 days of the last week will fall into WOY 1 of the
400 * next year. Maximum is 55.
402 * WEEK_OF_MONTH In a 29 day month, if the first 7 days make up week 1
403 * that leaves 3 full weeks and 1 day at the end. The least maximum is
404 * thus 5. In a 30 days month, if the previous 6 days belong WOM 1 of
405 * this month, we have 4 full weeks and 1 days at the end (which
406 * technically will be WOM 1 of the next month, but will be reported by
407 * time->fields and hence by getActualMaximum as WOM 6 of this month).
410 * DAY_OF_WEEK_IN_MONTH In a 29 or 30 day month, there are 4 full weeks
411 * plus 1 or 2 days at the end, so the maximum is always 5.
413 private static final int LIMITS[][] = {
414 // Minimum Greatest Least Maximum
416 { 1, 1, 83333, 83333 }, // ERA
417 { 1, 1, 60, 60 }, // YEAR
418 { 0, 0, 11, 11 }, // MONTH
419 { 1, 1, 50, 55 }, // WEEK_OF_YEAR
420 {/* */}, // WEEK_OF_MONTH
421 { 1, 1, 29, 30 }, // DAY_OF_MONTH
422 { 1, 1, 353, 385 }, // DAY_OF_YEAR
423 {/* */}, // DAY_OF_WEEK
424 { -1, -1, 5, 5 }, // DAY_OF_WEEK_IN_MONTH
427 {/* */}, // HOUR_OF_DAY
430 {/* */}, // MILLISECOND
431 {/* */}, // ZONE_OFFSET
432 {/* */}, // DST_OFFSET
433 { -5000000, -5000000, 5000000, 5000000 }, // YEAR_WOY
434 {/* */}, // DOW_LOCAL
435 { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR
436 {/* */}, // JULIAN_DAY
437 {/* */}, // MILLISECONDS_IN_DAY
438 { 0, 0, 1, 1 }, // IS_LEAP_MONTH
442 * Override Calendar to return the limit value for the given field.
445 protected int handleGetLimit(int field, int limitType) {
446 return LIMITS[field][limitType];
450 * Implement abstract Calendar method to return the extended year
451 * defined by the current fields. This will use either the ERA and
452 * YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR
453 * field as the continuous year count, depending on which is newer.
456 protected int handleGetExtendedYear() {
458 if (newestStamp(ERA, YEAR, UNSET) <= getStamp(EXTENDED_YEAR)) {
459 year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
461 int cycle = internalGet(ERA, 1) - 1; // 0-based cycle
462 year = cycle * 60 + internalGet(YEAR, 1);
468 * Override Calendar method to return the number of days in the given
469 * extended year and month.
471 * <p>Note: This method also reads the IS_LEAP_MONTH field to determine
472 * whether or not the given month is a leap month.
475 protected int handleGetMonthLength(int extendedYear, int month) {
476 int thisStart = handleComputeMonthStart(extendedYear, month, true) -
477 EPOCH_JULIAN_DAY + 1; // Julian day -> local days
478 int nextStart = newMoonNear(thisStart + SYNODIC_GAP, true);
479 return nextStart - thisStart;
483 * Framework method to create a calendar-specific DateFormat object
484 * using the the given pattern. This method is responsible for
485 * creating the calendar- specific DateFormat and DateFormatSymbols
487 * @param pattern The date formatting pattern
488 * @param override The override string. A numbering system override string can take one of the following forms:
489 * 1). If just a numbering system name is specified, it applies to all numeric fields in the date format pattern.
490 * 2). To specify an alternate numbering system on a field by field basis, use the field letters from the pattern
491 * followed by an = sign, followed by the numbering system name. For example, to specify that just the year
492 * be formatted using Hebrew digits, use the override "y=hebr". Multiple overrides can be specified in a single
493 * string by separating them with a semi-colon. For example, the override string "m=thai;y=deva" would format using
494 * Thai digits for the month and Devanagari digits for the year.
495 * @param locale The locale
498 protected DateFormat handleGetDateFormat(String pattern, String override, ULocale locale) {
499 return new ChineseDateFormat(pattern, override, locale);
503 * Field resolution table that incorporates IS_LEAP_MONTH.
505 static final int[][][] CHINESE_DATE_PRECEDENCE = {
508 { WEEK_OF_YEAR, DAY_OF_WEEK },
509 { WEEK_OF_MONTH, DAY_OF_WEEK },
510 { DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
511 { WEEK_OF_YEAR, DOW_LOCAL },
512 { WEEK_OF_MONTH, DOW_LOCAL },
513 { DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
515 { RESOLVE_REMAP | DAY_OF_MONTH, IS_LEAP_MONTH },
520 { DAY_OF_WEEK_IN_MONTH },
521 { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
522 { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
527 * Override Calendar to add IS_LEAP_MONTH to the field resolution
531 protected int[][][] getFieldResolutionTable() {
532 return CHINESE_DATE_PRECEDENCE;
536 * Adjust this calendar to be delta months before or after a given
537 * start position, pinning the day of month if necessary. The start
538 * position is given as a local days number for the start of the month
539 * and a day-of-month. Used by add() and roll().
540 * @param newMoon the local days of the first day of the month of the
541 * start position (days after January 1, 1970 0:00 Asia/Shanghai)
542 * @param dom the 1-based day-of-month of the start position
543 * @param delta the number of months to move forward or backward from
546 private void offsetMonth(int newMoon, int dom, int delta) {
547 // Move to the middle of the month before our target month.
548 newMoon += (int) (CalendarAstronomer.SYNODIC_MONTH * (delta - 0.5));
550 // Search forward to the target month's new moon
551 newMoon = newMoonNear(newMoon, true);
553 // Find the target dom
554 int jd = newMoon + EPOCH_JULIAN_DAY - 1 + dom;
556 // Pin the dom. In this calendar all months are 29 or 30 days
557 // so pinning just means handling dom 30.
559 set(JULIAN_DAY, jd-1);
560 // TODO Fix this. We really shouldn't ever have to
561 // explicitly call complete(). This is either a bug in
562 // this method, in ChineseCalendar, or in
563 // Calendar.getActualMaximum(). I suspect the last.
565 if (getActualMaximum(DAY_OF_MONTH) >= dom) {
574 * Override Calendar to handle leap months properly.
577 public void add(int field, int amount) {
581 int dom = get(DAY_OF_MONTH);
582 int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day
583 int moon = day - dom + 1; // New moon
584 offsetMonth(moon, dom, amount);
588 super.add(field, amount);
594 * Override Calendar to handle leap months properly.
597 public void roll(int field, int amount) {
601 int dom = get(DAY_OF_MONTH);
602 int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day
603 int moon = day - dom + 1; // New moon (start of this month)
605 // Note throughout the following: Months 12 and 1 are never
606 // followed by a leap month (D&R p. 185).
608 // Compute the adjusted month number m. This is zero-based
609 // value from 0..11 in a non-leap year, and from 0..12 in a
611 int m = get(MONTH); // 0-based month
612 if (isLeapYear) { // (member variable)
613 if (get(IS_LEAP_MONTH) == 1) {
616 // Check for a prior leap month. (In the
617 // following, month 0 is the first month of the
618 // year.) Month 0 is never followed by a leap
619 // month, and we know month m is not a leap month.
620 // moon1 will be the start of month 0 if there is
621 // no leap month between month 0 and month m;
622 // otherwise it will be the start of month 1.
624 (int) (CalendarAstronomer.SYNODIC_MONTH * (m - 0.5));
625 moon1 = newMoonNear(moon1, true);
626 if (isLeapMonthBetween(moon1, moon)) {
632 // Now do the standard roll computation on m, with the
633 // allowed range of 0..n-1, where n is 12 or 13.
634 int n = isLeapYear ? 13 : 12; // Months in this year
635 int newM = (m + amount) % n;
641 offsetMonth(moon, dom, newM - m);
646 super.roll(field, amount);
651 //------------------------------------------------------------------
652 // Support methods and constants
653 //------------------------------------------------------------------
656 * The start year of the Chinese calendar, the 61st year of the reign
657 * of Huang Di. Some sources use the first year of his reign,
658 * resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle)
659 * values one greater.
661 private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year
664 * The offset from GMT in milliseconds at which we perform astronomical
665 * computations. Some sources use a different historically accurate
666 * offset of GMT+7:45:40 for years before 1929; we do not do this.
668 private static final long CHINA_OFFSET = 8*ONE_HOUR;
671 * Value to be added or subtracted from the local days of a new moon to
672 * get close to the next or prior new moon, but not cross it. Must be
673 * >= 1 and < CalendarAstronomer.SYNODIC_MONTH.
675 private static final int SYNODIC_GAP = 25;
678 * Convert local days to UTC epoch milliseconds.
679 * @param days days after January 1, 1970 0:00 Asia/Shanghai
680 * @return milliseconds after January 1, 1970 0:00 GMT
682 private static final long daysToMillis(int days) {
683 return (days * ONE_DAY) - CHINA_OFFSET;
687 * Convert UTC epoch milliseconds to local days.
688 * @param millis milliseconds after January 1, 1970 0:00 GMT
689 * @return days after January 1, 1970 0:00 Asia/Shanghai
691 private static final int millisToDays(long millis) {
692 return (int) floorDivide(millis + CHINA_OFFSET, ONE_DAY);
695 //------------------------------------------------------------------
696 // Astronomical computations
697 //------------------------------------------------------------------
700 * Return the major solar term on or after December 15 of the given
701 * Gregorian year, that is, the winter solstice of the given year.
702 * Computations are relative to Asia/Shanghai time zone.
703 * @param gyear a Gregorian year
704 * @return days after January 1, 1970 0:00 Asia/Shanghai of the
705 * winter solstice of the given year
707 private int winterSolstice(int gyear) {
709 long cacheValue = winterSolsticeCache.get(gyear);
711 if (cacheValue == CalendarCache.EMPTY) {
712 // In books December 15 is used, but it fails for some years
713 // using our algorithms, e.g.: 1298 1391 1492 1553 1560. That
714 // is, winterSolstice(1298) starts search at Dec 14 08:00:00
715 // PST 1298 with a final result of Dec 14 10:31:59 PST 1299.
716 long ms = daysToMillis(computeGregorianMonthStart(gyear, DECEMBER) +
717 1 - EPOCH_JULIAN_DAY);
720 // Winter solstice is 270 degrees solar longitude aka Dongzhi
721 long solarLong = astro.getSunTime(CalendarAstronomer.WINTER_SOLSTICE,
723 cacheValue = millisToDays(solarLong);
724 winterSolsticeCache.put(gyear, cacheValue);
726 return (int) cacheValue;
730 * Return the closest new moon to the given date, searching either
731 * forward or backward in time.
732 * @param days days after January 1, 1970 0:00 Asia/Shanghai
733 * @param after if true, search for a new moon on or after the given
734 * date; otherwise, search for a new moon before it
735 * @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest
736 * new moon after or before <code>days</code>
738 private int newMoonNear(int days, boolean after) {
740 astro.setTime(daysToMillis(days));
741 long newMoon = astro.getMoonTime(CalendarAstronomer.NEW_MOON, after);
743 return millisToDays(newMoon);
747 * Return the nearest integer number of synodic months between
749 * @param day1 days after January 1, 1970 0:00 Asia/Shanghai
750 * @param day2 days after January 1, 1970 0:00 Asia/Shanghai
751 * @return the nearest integer number of months between day1 and day2
753 private int synodicMonthsBetween(int day1, int day2) {
754 return (int) Math.round((day2 - day1) / CalendarAstronomer.SYNODIC_MONTH);
758 * Return the major solar term on or before a given date. This
759 * will be an integer from 1..12, with 1 corresponding to 330 degrees,
760 * 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees.
761 * @param days days after January 1, 1970 0:00 Asia/Shanghai
763 private int majorSolarTerm(int days) {
765 astro.setTime(daysToMillis(days));
767 // Compute (floor(solarLongitude / (pi/6)) + 2) % 12
768 int term = ((int) Math.floor(6 * astro.getSunLongitude() / Math.PI) + 2) % 12;
776 * Return true if the given month lacks a major solar term.
777 * @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new
780 private boolean hasNoMajorSolarTerm(int newMoon) {
782 int mst = majorSolarTerm(newMoon);
783 int nmn = newMoonNear(newMoon + SYNODIC_GAP, true);
784 int mstt = majorSolarTerm(nmn);
787 return majorSolarTerm(newMoon) ==
788 majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, true));
792 //------------------------------------------------------------------
794 //------------------------------------------------------------------
797 * Return true if there is a leap month on or after month newMoon1 and
798 * at or before month newMoon2.
799 * @param newMoon1 days after January 1, 1970 0:00 Asia/Shanghai of a
801 * @param newMoon2 days after January 1, 1970 0:00 Asia/Shanghai of a
804 private boolean isLeapMonthBetween(int newMoon1, int newMoon2) {
806 // This is only needed to debug the timeOfAngle divergence bug.
807 // Remove this later. Liu 11/9/00
809 if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) {
810 throw new IllegalArgumentException("isLeapMonthBetween(" + newMoon1 +
812 "): Invalid parameters");
815 return (newMoon2 >= newMoon1) &&
816 (isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, false)) ||
817 hasNoMajorSolarTerm(newMoon2));
821 * Override Calendar to compute several fields specific to the Chinese
822 * calendar system. These are:
829 * <li>EXTENDED_YEAR</ul>
831 * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
832 * method is called. The getGregorianXxx() methods return Gregorian
833 * calendar equivalents for the given Julian day.
835 * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH.
838 protected void handleComputeFields(int julianDay) {
840 computeChineseFields(julianDay - EPOCH_JULIAN_DAY, // local days
841 getGregorianYear(), getGregorianMonth(),
842 true); // set all fields
846 * Compute fields for the Chinese calendar system. This method can
847 * either set all relevant fields, as required by
848 * <code>handleComputeFields()</code>, or it can just set the MONTH and
849 * IS_LEAP_MONTH fields, as required by
850 * <code>handleComputeMonthStart()</code>.
852 * <p>As a side effect, this method sets {@link #isLeapYear}.
853 * @param days days after January 1, 1970 0:00 Asia/Shanghai of the
854 * date to compute fields for
855 * @param gyear the Gregorian year of the given date
856 * @param gmonth the Gregorian month of the given date
857 * @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR,
858 * DAY_OF_MONTH, and DAY_OF_YEAR fields. In either case set the MONTH
859 * and IS_LEAP_MONTH fields.
861 private void computeChineseFields(int days, int gyear, int gmonth,
862 boolean setAllFields) {
864 // Find the winter solstices before and after the target date.
865 // These define the boundaries of this Chinese year, specifically,
866 // the position of month 11, which always contains the solstice.
867 // We want solsticeBefore <= date < solsticeAfter.
869 int solsticeAfter = winterSolstice(gyear);
870 if (days < solsticeAfter) {
871 solsticeBefore = winterSolstice(gyear - 1);
873 solsticeBefore = solsticeAfter;
874 solsticeAfter = winterSolstice(gyear + 1);
877 // Find the start of the month after month 11. This will be either
878 // the prior month 12 or leap month 11 (very rare). Also find the
879 // start of the following month 11.
880 int firstMoon = newMoonNear(solsticeBefore + 1, true);
881 int lastMoon = newMoonNear(solsticeAfter + 1, false);
882 int thisMoon = newMoonNear(days + 1, false); // Start of this month
883 // Note: isLeapYear is a member variable
884 isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12;
886 int month = synodicMonthsBetween(firstMoon, thisMoon);
887 if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) {
894 boolean isLeapMonth = isLeapYear &&
895 hasNoMajorSolarTerm(thisMoon) &&
896 !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false));
898 internalSet(MONTH, month-1); // Convert from 1-based to 0-based
899 internalSet(IS_LEAP_MONTH, isLeapMonth?1:0);
903 int year = gyear - CHINESE_EPOCH_YEAR;
908 int dayOfMonth = days - thisMoon + 1;
910 internalSet(EXTENDED_YEAR, year);
912 // 0->0,60 1->1,1 60->1,60 61->2,1 etc.
913 int[] yearOfCycle = new int[1];
914 int cycle = floorDivide(year-1, 60, yearOfCycle);
915 internalSet(ERA, cycle+1);
916 internalSet(YEAR, yearOfCycle[0]+1);
918 internalSet(DAY_OF_MONTH, dayOfMonth);
920 // Days will be before the first new year we compute if this
921 // date is in month 11, leap 11, 12. There is never a leap 12.
922 // New year computations are cached so this should be cheap in
924 int newYear = newYear(gyear);
925 if (days < newYear) {
926 newYear = newYear(gyear-1);
928 internalSet(DAY_OF_YEAR, days - newYear + 1);
932 //------------------------------------------------------------------
934 //------------------------------------------------------------------
937 * Return the Chinese new year of the given Gregorian year.
938 * @param gyear a Gregorian year
939 * @return days after January 1, 1970 0:00 Asia/Shanghai of the
940 * Chinese new year of the given year (this will be a new moon)
942 private int newYear(int gyear) {
944 long cacheValue = newYearCache.get(gyear);
946 if (cacheValue == CalendarCache.EMPTY) {
948 int solsticeBefore= winterSolstice(gyear - 1);
949 int solsticeAfter = winterSolstice(gyear);
950 int newMoon1 = newMoonNear(solsticeBefore + 1, true);
951 int newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, true);
952 int newMoon11 = newMoonNear(solsticeAfter + 1, false);
954 if (synodicMonthsBetween(newMoon1, newMoon11) == 12 &&
955 (hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) {
956 cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, true);
958 cacheValue = newMoon2;
961 newYearCache.put(gyear, cacheValue);
963 return (int) cacheValue;
967 * Return the Julian day number of day before the first day of the
968 * given month in the given extended year.
970 * <p>Note: This method reads the IS_LEAP_MONTH field to determine
971 * whether the given month is a leap month.
972 * @param eyear the extended year
973 * @param month the zero-based month. The month is also determined
974 * by reading the IS_LEAP_MONTH field.
975 * @return the Julian day number of the day before the first
976 * day of the given month and year
979 protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {
981 // If the month is out of range, adjust it into range, and
982 // modify the extended year value accordingly.
983 if (month < 0 || month > 11) {
984 int[] rem = new int[1];
985 eyear += floorDivide(month, 12, rem);
989 int gyear = eyear + CHINESE_EPOCH_YEAR - 1; // Gregorian year
990 int newYear = newYear(gyear);
991 int newMoon = newMoonNear(newYear + month * 29, true);
993 int julianDay = newMoon + EPOCH_JULIAN_DAY;
995 // Save fields for later restoration
996 int saveMonth = internalGet(MONTH);
997 int saveIsLeapMonth = internalGet(IS_LEAP_MONTH);
999 // Ignore IS_LEAP_MONTH field if useMonth is false
1000 int isLeapMonth = useMonth ? saveIsLeapMonth : 0;
1002 computeGregorianFields(julianDay);
1004 // This will modify the MONTH and IS_LEAP_MONTH fields (only)
1005 computeChineseFields(newMoon, getGregorianYear(),
1006 getGregorianMonth(), false);
1008 if (month != internalGet(MONTH) ||
1009 isLeapMonth != internalGet(IS_LEAP_MONTH)) {
1010 newMoon = newMoonNear(newMoon + SYNODIC_GAP, true);
1011 julianDay = newMoon + EPOCH_JULIAN_DAY;
1014 internalSet(MONTH, saveMonth);
1015 internalSet(IS_LEAP_MONTH, saveIsLeapMonth);
1017 return julianDay - 1;
1021 * Return the current Calendar type.
1022 * @return type of calendar
1025 public String getType() {
1030 * Override readObject.
1032 private void readObject(ObjectInputStream stream)
1033 throws IOException, ClassNotFoundException
1035 stream.defaultReadObject();
1037 /* set up the transient caches... */
1038 astro = new CalendarAstronomer();
1039 winterSolsticeCache = new CalendarCache();
1040 newYearCache = new CalendarCache();
1044 private static CalendarFactory factory;
1045 public static CalendarFactory factory() {
1046 if (factory == null) {
1047 factory = new CalendarFactory() {
1048 public Calendar create(TimeZone tz, ULocale loc) {
1049 return new ChineseCalendar(tz, loc);
1052 public String factoryName() {