]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_4_2-src/main/classes/core/src/com/ibm/icu/util/ChineseCalendar.java
go
[Dictionary.git] / jars / icu4j-4_4_2-src / main / classes / core / src / com / ibm / icu / util / ChineseCalendar.java
1 /*********************************************************************\r
2  * Copyright (C) 2000-2010, International Business Machines\r
3  * Corporation and others. All Rights Reserved.\r
4  *********************************************************************\r
5  */\r
6 \r
7 package com.ibm.icu.util;\r
8 \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
13 \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
18 \r
19 /**\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
28  * and moon.\r
29  *\r
30  * <p>All astronomical computations are performed with respect to a time\r
31  * zone of GMT+8:00 and a longitude of 120 degrees east.  Although some\r
32  * calendars implement a historically more accurate convention of using\r
33  * Beijing's local longitude (116 degrees 25 minutes east) and time zone\r
34  * (GMT+7:45:40) for dates before 1929, we do not implement this here.\r
35  *\r
36  * <p>Years are counted in two different ways in the Chinese calendar.  The\r
37  * first method is by sequential numbering from the 61st year of the reign\r
38  * of Huang Di, 2637 BCE, which is designated year 1 on the Chinese\r
39  * calendar.  The second method uses 60-year cycles from the same starting\r
40  * point, which is designated year 1 of cycle 1.  In this class, the\r
41  * <code>EXTENDED_YEAR</code> field contains the sequential year count.\r
42  * The <code>ERA</code> field contains the cycle number, and the\r
43  * <code>YEAR</code> field contains the year of the cycle, a value between\r
44  * 1 and 60.\r
45  *\r
46  * <p>There is some variation in what is considered the starting point of\r
47  * the calendar, with some sources starting in the first year of the reign\r
48  * of Huang Di, rather than the 61st.  This gives continuous year numbers\r
49  * 60 years greater and cycle numbers one greater than what this class\r
50  * implements.\r
51  *\r
52  * <p>Because <code>ChineseCalendar</code> defines an additional field and\r
53  * redefines the way the <code>ERA</code> field is used, it requires a new\r
54  * format class, <code>ChineseDateFormat</code>.  As always, use the\r
55  * methods <code>DateFormat.getXxxInstance(Calendar cal,...)</code> to\r
56  * obtain a formatter for this calendar.\r
57  *\r
58  * <p>References:<ul>\r
59  * \r
60  * <li>Dershowitz and Reingold, <i>Calendrical Calculations</i>,\r
61  * Cambridge University Press, 1997</li>\r
62  * \r
63  * <li>Helmer Aslaksen's\r
64  * <a href="http://www.math.nus.edu.sg/aslaksen/calendar/chinese.shtml">\r
65  * Chinese Calendar page</a></li>\r
66  *\r
67  * <li>The <a href="http://www.tondering.dk/claus/calendar.html">\r
68  * Calendar FAQ</a></li>\r
69  *\r
70  * </ul>\r
71  *\r
72  * <p>\r
73  * This class should not be subclassed.</p>\r
74  * <p>\r
75  * ChineseCalendar usually should be instantiated using \r
76  * {@link com.ibm.icu.util.Calendar#getInstance(ULocale)} passing in a <code>ULocale</code>\r
77  * with the tag <code>"@calendar=chinese"</code>.</p>\r
78  *\r
79  * @see com.ibm.icu.text.ChineseDateFormat\r
80  * @see com.ibm.icu.util.Calendar\r
81  * @author Alan Liu\r
82  * @stable ICU 2.8\r
83  */\r
84 public class ChineseCalendar extends Calendar {\r
85     // jdk1.4.2 serialver\r
86     private static final long serialVersionUID = 7312110751940929420L;\r
87 \r
88     //------------------------------------------------------------------\r
89     // Developer Notes\r
90     // \r
91     // Time is represented as a scalar in two ways in this class.  One is\r
92     // the usual UTC epoch millis, that is, milliseconds after January 1,\r
93     // 1970 Gregorian, 0:00:00.000 UTC.  The other is in terms of 'local\r
94     // days.'  This is the number of days after January 1, 1970 Gregorian,\r
95     // local to Beijing, China (since all computations of the Chinese\r
96     // calendar are done in Beijing).  That is, 0 represents January 1,\r
97     // 1970 0:00 Asia/Shanghai.  Conversion of local days to and from\r
98     // standard epoch milliseconds is accomplished by the daysToMillis()\r
99     // and millisToDays() methods.\r
100     // \r
101     // Several methods use caches to improve performance.  Caches are at\r
102     // the object, not class level, under the assumption that typical\r
103     // usage will be to have one instance of ChineseCalendar at a time.\r
104  \r
105     /**\r
106      * We have one instance per object, and we don't synchronize it because\r
107      * Calendar doesn't support multithreaded execution in the first place.\r
108      */\r
109     private transient CalendarAstronomer astro = new CalendarAstronomer();\r
110 \r
111     /**\r
112      * Cache that maps Gregorian year to local days of winter solstice.\r
113      * @see #winterSolstice\r
114      */\r
115     private transient CalendarCache winterSolsticeCache = new CalendarCache();\r
116 \r
117     /**\r
118      * Cache that maps Gregorian year to local days of Chinese new year.\r
119      * @see #newYear\r
120      */\r
121     private transient CalendarCache newYearCache = new CalendarCache();\r
122 \r
123     /**\r
124      * True if the current year is a leap year.  Updated with each time to\r
125      * fields resolution.\r
126      * @see #computeChineseFields\r
127      */\r
128     private transient boolean isLeapYear;\r
129 \r
130     //------------------------------------------------------------------\r
131     // Constructors\r
132     //------------------------------------------------------------------\r
133 \r
134     /**\r
135      * Construct a <code>ChineseCalendar</code> with the default time zone and locale.\r
136      * @stable ICU 2.8\r
137      */\r
138     public ChineseCalendar() {\r
139         super();\r
140         setTimeInMillis(System.currentTimeMillis());\r
141     }\r
142 \r
143     /**\r
144      * Construct a <code>ChineseCalendar</code> with the give date set in the default time zone\r
145      * with the default locale.\r
146      * @param date The date to which the new calendar is set.\r
147      * @stable ICU 4.0\r
148      */\r
149     public ChineseCalendar(Date date) {\r
150         super();\r
151         setTime(date);\r
152     }\r
153 \r
154     /**\r
155      * Constructs a <code>ChineseCalendar</code> with the given date set\r
156      * in the default time zone with the default locale.\r
157      *\r
158      * @param year      The value used to set the calendar's {@link #YEAR YEAR} time field.\r
159      * @param month     The value used to set the calendar's {@link #MONTH MONTH} time field.\r
160      *                  The value is 0-based. e.g., 0 for January.\r
161      * @param isLeapMonth The value used to set the Chinese calendar's (@link #IS_LEAP_MONTH)\r
162      *                  time field.\r
163      * @param date      The value used to set the calendar's {@link #DATE DATE} time field.\r
164      * @stable ICU 4.0\r
165      */\r
166     public ChineseCalendar(int year, int month, int isLeapMonth, int date) {\r
167         super(TimeZone.getDefault(), ULocale.getDefault());\r
168 \r
169         // We need to set the current time once to initialize the ChineseCalendar's\r
170         // ERA field to be the current era.\r
171         setTimeInMillis(System.currentTimeMillis());\r
172         // Then we need to clean up time fields\r
173         this.set(MILLISECONDS_IN_DAY, 0);\r
174 \r
175         // Then set the given field values.\r
176         this.set(YEAR, year);\r
177         this.set(MONTH, month);\r
178         this.set(IS_LEAP_MONTH, isLeapMonth);\r
179         this.set(DATE, date);\r
180     }\r
181 \r
182     /**\r
183      * Constructs a <code>ChineseCalendar</code> with the given date\r
184      * and time set for the default time zone with the default locale.\r
185      *\r
186      * @param year  the value used to set the {@link #YEAR YEAR} time field in the calendar.\r
187      * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar.\r
188      *              Note that the month value is 0-based. e.g., 0 for January.\r
189      * @param isLeapMonth the value used to set the {@link #IS_LEAP_MONTH} time field\r
190      *              in the calendar.\r
191      * @param date  the value used to set the {@link #DATE DATE} time field in the calendar.\r
192      * @param hour  the value used to set the {@link #HOUR_OF_DAY HOUR_OF_DAY} time field\r
193      *              in the calendar.\r
194      * @param minute the value used to set the {@link #MINUTE MINUTE} time field\r
195      *              in the calendar.\r
196      * @param second the value used to set the {@link #SECOND SECOND} time field\r
197      *              in the calendar.\r
198      * @stable ICU 4.0\r
199      */\r
200     public ChineseCalendar(int year, int month, int isLeapMonth, int date, int hour,\r
201                              int minute, int second)\r
202     {\r
203         super(TimeZone.getDefault(), ULocale.getDefault());\r
204 \r
205         // We need to set the current time once to initialize the ChineseCalendar's\r
206         // ERA field to be the current era.\r
207         setTimeInMillis(System.currentTimeMillis());\r
208         // Then set 0 to millisecond field\r
209         this.set(MILLISECOND, 0);\r
210 \r
211         // Then, set the given field values.\r
212         this.set(YEAR, year);\r
213         this.set(MONTH, month);\r
214         this.set(IS_LEAP_MONTH, isLeapMonth);\r
215         this.set(DATE, date);\r
216         this.set(HOUR_OF_DAY, hour);\r
217         this.set(MINUTE, minute);\r
218         this.set(SECOND, second);\r
219     }\r
220 \r
221     /**\r
222      * Constructs a <code>ChineseCalendar</code> based on the current time\r
223      * in the default time zone with the given locale.\r
224      * @param aLocale The given locale\r
225      * @stable ICU 4.0\r
226      */\r
227     public ChineseCalendar(Locale aLocale) {\r
228         this(TimeZone.getDefault(), aLocale);\r
229         setTimeInMillis(System.currentTimeMillis());\r
230     }\r
231 \r
232     /**\r
233      * Construct a <code>ChineseCalendar</code> based on the current time\r
234      * in the given time zone with the default locale.\r
235      * @param zone the given time zone\r
236      * @stable ICU 4.0\r
237      */\r
238     public ChineseCalendar(TimeZone zone) {\r
239         super(zone, ULocale.getDefault());\r
240         setTimeInMillis(System.currentTimeMillis());\r
241     }\r
242 \r
243     /**\r
244      * Construct a <code>ChineseCalendar</code> based on the current time\r
245      * in the given time zone with the given locale.\r
246      * @param zone the given time zone\r
247      * @param aLocale the given locale\r
248      * @stable ICU 2.8\r
249      */\r
250     public ChineseCalendar(TimeZone zone, Locale aLocale) {\r
251         super(zone, aLocale);\r
252         setTimeInMillis(System.currentTimeMillis());\r
253     }\r
254 \r
255     /**\r
256      * Constructs a <code>ChineseCalendar</code> based on the current time\r
257      * in the default time zone with the given locale.\r
258      *\r
259      * @param locale the given ulocale\r
260      * @stable ICU 4.0\r
261      */\r
262     public ChineseCalendar(ULocale locale) {\r
263         this(TimeZone.getDefault(), locale);\r
264         setTimeInMillis(System.currentTimeMillis());\r
265     }\r
266 \r
267     /**\r
268      * Construct a <code>ChineseCalendar</code>  based on the current time\r
269      * with the given time zone with the given locale.\r
270      * @param zone the given time zone\r
271      * @param locale the given ulocale\r
272      * @stable ICU 3.2\r
273      */\r
274     public ChineseCalendar(TimeZone zone, ULocale locale) {\r
275         super(zone, locale);\r
276         setTimeInMillis(System.currentTimeMillis());\r
277     }\r
278 \r
279     //------------------------------------------------------------------\r
280     // Public constants\r
281     //------------------------------------------------------------------\r
282 \r
283     /**\r
284      * Field indicating whether or not the current month is a leap month.\r
285      * Should have a value of 0 for non-leap months, and 1 for leap months.\r
286      * @stable ICU 2.8\r
287      */\r
288     // public static int IS_LEAP_MONTH = BASE_FIELD_COUNT;\r
289 \r
290 \r
291     //------------------------------------------------------------------\r
292     // Calendar framework\r
293     //------------------------------------------------------------------\r
294 \r
295     /**\r
296      * Array defining the limits of field values for this class.  Field\r
297      * limits which are invariant with respect to calendar system and\r
298      * defined by Calendar are left blank.\r
299      *\r
300      * Notes:\r
301      *\r
302      * ERA 5000000 / 60 = 83333.\r
303      *\r
304      * MONTH There are 12 or 13 lunar months in a year.  However, we always\r
305      * number them 0..11, with an intercalated, identically numbered leap\r
306      * month, when necessary.\r
307      *\r
308      * DAY_OF_YEAR In a non-leap year there are 353, 354, or 355 days.  In\r
309      * a leap year there are 383, 384, or 385 days.\r
310      *\r
311      * WEEK_OF_YEAR The least maximum occurs if there are 353 days in the\r
312      * year, and the first 6 are the last week of the previous year.  Then\r
313      * we have 49 full weeks and 4 days in the last week: 6 + 49*7 + 4 =\r
314      * 353.  So the least maximum is 50.  The maximum occurs if there are\r
315      * 385 days in the year, and WOY 1 extends 6 days into the prior year.\r
316      * Then there are 54 full weeks, and 6 days in the last week: 1 + 54*7\r
317      * + 6 = 385.  The 6 days of the last week will fall into WOY 1 of the\r
318      * next year.  Maximum is 55.\r
319      *\r
320      * WEEK_OF_MONTH In a 29 day month, if the first 7 days make up week 1\r
321      * that leaves 3 full weeks and 1 day at the end.  The least maximum is\r
322      * thus 5.  In a 30 days month, if the previous 6 days belong WOM 1 of\r
323      * this month, we have 4 full weeks and 1 days at the end (which\r
324      * technically will be WOM 1 of the next month, but will be reported by\r
325      * time->fields and hence by getActualMaximum as WOM 6 of this month).\r
326      * Maximum is 6.\r
327      *\r
328      * DAY_OF_WEEK_IN_MONTH In a 29 or 30 day month, there are 4 full weeks\r
329      * plus 1 or 2 days at the end, so the maximum is always 5.\r
330      */\r
331     private static final int LIMITS[][] = {\r
332         // Minimum  Greatest    Least  Maximum\r
333         //           Minimum  Maximum\r
334         {        1,        1,   83333,   83333 }, // ERA\r
335         {        1,        1,      60,      60 }, // YEAR\r
336         {        0,        0,      11,      11 }, // MONTH\r
337         {        1,        1,      50,      55 }, // WEEK_OF_YEAR\r
338         {/*                                  */}, // WEEK_OF_MONTH\r
339         {        1,        1,      29,      30 }, // DAY_OF_MONTH\r
340         {        1,        1,     353,     385 }, // DAY_OF_YEAR\r
341         {/*                                  */}, // DAY_OF_WEEK\r
342         {       -1,       -1,       5,       5 }, // DAY_OF_WEEK_IN_MONTH\r
343         {/*                                  */}, // AM_PM\r
344         {/*                                  */}, // HOUR\r
345         {/*                                  */}, // HOUR_OF_DAY\r
346         {/*                                  */}, // MINUTE\r
347         {/*                                  */}, // SECOND\r
348         {/*                                  */}, // MILLISECOND\r
349         {/*                                  */}, // ZONE_OFFSET\r
350         {/*                                  */}, // DST_OFFSET\r
351         { -5000000, -5000000, 5000000, 5000000 }, // YEAR_WOY\r
352         {/*                                  */}, // DOW_LOCAL\r
353         { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR\r
354         {/*                                  */}, // JULIAN_DAY\r
355         {/*                                  */}, // MILLISECONDS_IN_DAY\r
356         {        0,        0,       1,       1 }, // IS_LEAP_MONTH\r
357     };\r
358 \r
359     /**\r
360      * Override Calendar to return the limit value for the given field.\r
361      * @stable ICU 2.8\r
362      */\r
363     protected int handleGetLimit(int field, int limitType) {\r
364         return LIMITS[field][limitType];\r
365     }\r
366 \r
367     /**\r
368      * Implement abstract Calendar method to return the extended year\r
369      * defined by the current fields.  This will use either the ERA and\r
370      * YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR\r
371      * field as the continuous year count, depending on which is newer.\r
372      * @stable ICU 2.8\r
373      */\r
374     protected int handleGetExtendedYear() {\r
375         int year;\r
376         if (newestStamp(ERA, YEAR, UNSET) <= getStamp(EXTENDED_YEAR)) {\r
377             year = internalGet(EXTENDED_YEAR, 1); // Default to year 1\r
378         } else {\r
379             int cycle = internalGet(ERA, 1) - 1; // 0-based cycle\r
380             year = cycle * 60 + internalGet(YEAR, 1);\r
381         }\r
382         return year;\r
383     }\r
384 \r
385     /**\r
386      * Override Calendar method to return the number of days in the given\r
387      * extended year and month.\r
388      *\r
389      * <p>Note: This method also reads the IS_LEAP_MONTH field to determine\r
390      * whether or not the given month is a leap month.\r
391      * @stable ICU 2.8\r
392      */\r
393     protected int handleGetMonthLength(int extendedYear, int month) {\r
394         int thisStart = handleComputeMonthStart(extendedYear, month, true) -\r
395             EPOCH_JULIAN_DAY + 1; // Julian day -> local days\r
396         int nextStart = newMoonNear(thisStart + SYNODIC_GAP, true);\r
397         return nextStart - thisStart;\r
398     }\r
399 \r
400     /**\r
401      * Framework method to create a calendar-specific DateFormat object\r
402      * using the the given pattern.  This method is responsible for\r
403      * creating the calendar- specific DateFormat and DateFormatSymbols\r
404      * objects as needed.\r
405      * @param pattern The date formatting pattern\r
406      * @param override The override string.  A numbering system override string can take one of the following forms:\r
407      *     1). If just a numbering system name is specified, it applies to all numeric fields in the date format pattern.\r
408      *     2). To specify an alternate numbering system on a field by field basis, use the field letters from the pattern\r
409      *         followed by an = sign, followed by the numbering system name.  For example, to specify that just the year\r
410      *         be formatted using Hebrew digits, use the override "y=hebr".  Multiple overrides can be specified in a single\r
411      *         string by separating them with a semi-colon. For example, the override string "m=thai;y=deva" would format using\r
412      *         Thai digits for the month and Devanagari digits for the year.\r
413      * @param locale The locale\r
414      * @stable ICU 4.2\r
415      */\r
416     protected DateFormat handleGetDateFormat(String pattern, String override, ULocale locale) {\r
417         return new ChineseDateFormat(pattern, override, locale);\r
418     }\r
419 \r
420     /**\r
421      * Field resolution table that incorporates IS_LEAP_MONTH.\r
422      */\r
423     static final int[][][] CHINESE_DATE_PRECEDENCE = {\r
424         {\r
425             { DAY_OF_MONTH },\r
426             { WEEK_OF_YEAR, DAY_OF_WEEK },\r
427             { WEEK_OF_MONTH, DAY_OF_WEEK },\r
428             { DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },\r
429             { WEEK_OF_YEAR, DOW_LOCAL },\r
430             { WEEK_OF_MONTH, DOW_LOCAL },\r
431             { DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },\r
432             { DAY_OF_YEAR },\r
433             { RESOLVE_REMAP | DAY_OF_MONTH, IS_LEAP_MONTH },\r
434         },\r
435         {\r
436             { WEEK_OF_YEAR },\r
437             { WEEK_OF_MONTH },\r
438             { DAY_OF_WEEK_IN_MONTH },\r
439             { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },\r
440             { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },\r
441         },\r
442     };\r
443 \r
444     /**\r
445      * Override Calendar to add IS_LEAP_MONTH to the field resolution\r
446      * table.\r
447      * @stable ICU 2.8\r
448      */\r
449     protected int[][][] getFieldResolutionTable() {\r
450         return CHINESE_DATE_PRECEDENCE;\r
451     }\r
452 \r
453     /**\r
454      * Adjust this calendar to be delta months before or after a given\r
455      * start position, pinning the day of month if necessary.  The start\r
456      * position is given as a local days number for the start of the month\r
457      * and a day-of-month.  Used by add() and roll().\r
458      * @param newMoon the local days of the first day of the month of the\r
459      * start position (days after January 1, 1970 0:00 Asia/Shanghai)\r
460      * @param dom the 1-based day-of-month of the start position\r
461      * @param delta the number of months to move forward or backward from\r
462      * the start position\r
463      */\r
464     private void offsetMonth(int newMoon, int dom, int delta) {\r
465         // Move to the middle of the month before our target month.\r
466         newMoon += (int) (CalendarAstronomer.SYNODIC_MONTH * (delta - 0.5));\r
467 \r
468         // Search forward to the target month's new moon\r
469         newMoon = newMoonNear(newMoon, true);\r
470 \r
471         // Find the target dom\r
472         int jd = newMoon + EPOCH_JULIAN_DAY - 1 + dom;\r
473 \r
474         // Pin the dom.  In this calendar all months are 29 or 30 days\r
475         // so pinning just means handling dom 30.\r
476         if (dom > 29) {\r
477             set(JULIAN_DAY, jd-1);\r
478             // TODO Fix this.  We really shouldn't ever have to\r
479             // explicitly call complete().  This is either a bug in\r
480             // this method, in ChineseCalendar, or in\r
481             // Calendar.getActualMaximum().  I suspect the last.\r
482             complete();\r
483             if (getActualMaximum(DAY_OF_MONTH) >= dom) {\r
484                 set(JULIAN_DAY, jd);\r
485             }\r
486         } else {\r
487             set(JULIAN_DAY, jd);\r
488         }\r
489     }\r
490 \r
491     /**\r
492      * Override Calendar to handle leap months properly.\r
493      * @stable ICU 2.8\r
494      */\r
495     public void add(int field, int amount) {\r
496         switch (field) {\r
497         case MONTH:\r
498             if (amount != 0) {\r
499                 int dom = get(DAY_OF_MONTH);\r
500                 int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day\r
501                 int moon = day - dom + 1; // New moon \r
502                 offsetMonth(moon, dom, amount);\r
503             }\r
504             break;\r
505         default:\r
506             super.add(field, amount);\r
507             break;\r
508         }\r
509     }\r
510 \r
511     /**\r
512      * Override Calendar to handle leap months properly.\r
513      * @stable ICU 2.8\r
514      */\r
515     public void roll(int field, int amount) {\r
516         switch (field) {\r
517         case MONTH:\r
518             if (amount != 0) {\r
519                 int dom = get(DAY_OF_MONTH);\r
520                 int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day\r
521                 int moon = day - dom + 1; // New moon (start of this month)\r
522 \r
523                 // Note throughout the following:  Months 12 and 1 are never\r
524                 // followed by a leap month (D&R p. 185).\r
525 \r
526                 // Compute the adjusted month number m.  This is zero-based\r
527                 // value from 0..11 in a non-leap year, and from 0..12 in a\r
528                 // leap year.\r
529                 int m = get(MONTH); // 0-based month\r
530                 if (isLeapYear) { // (member variable)\r
531                     if (get(IS_LEAP_MONTH) == 1) {\r
532                         ++m;\r
533                     } else {\r
534                         // Check for a prior leap month.  (In the\r
535                         // following, month 0 is the first month of the\r
536                         // year.)  Month 0 is never followed by a leap\r
537                         // month, and we know month m is not a leap month.\r
538                         // moon1 will be the start of month 0 if there is\r
539                         // no leap month between month 0 and month m;\r
540                         // otherwise it will be the start of month 1.\r
541                         int moon1 = moon -\r
542                             (int) (CalendarAstronomer.SYNODIC_MONTH * (m - 0.5));\r
543                         moon1 = newMoonNear(moon1, true);\r
544                         if (isLeapMonthBetween(moon1, moon)) {\r
545                             ++m;\r
546                         }\r
547                     }\r
548                 }\r
549 \r
550                 // Now do the standard roll computation on m, with the\r
551                 // allowed range of 0..n-1, where n is 12 or 13.\r
552                 int n = isLeapYear ? 13 : 12; // Months in this year\r
553                 int newM = (m + amount) % n;\r
554                 if (newM < 0) {\r
555                     newM += n;\r
556                 }\r
557 \r
558                 if (newM != m) {\r
559                     offsetMonth(moon, dom, newM - m);\r
560                 }\r
561             }\r
562             break;\r
563         default:\r
564             super.roll(field, amount);\r
565             break;\r
566         }\r
567     }\r
568 \r
569     //------------------------------------------------------------------\r
570     // Support methods and constants\r
571     //------------------------------------------------------------------\r
572    \r
573     /**\r
574      * The start year of the Chinese calendar, the 61st year of the reign\r
575      * of Huang Di.  Some sources use the first year of his reign,\r
576      * resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle)\r
577      * values one greater.\r
578      */\r
579     private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year\r
580 \r
581     /**\r
582      * The offset from GMT in milliseconds at which we perform astronomical\r
583      * computations.  Some sources use a different historically accurate\r
584      * offset of GMT+7:45:40 for years before 1929; we do not do this.\r
585      */\r
586     private static final long CHINA_OFFSET = 8*ONE_HOUR;\r
587 \r
588     /**\r
589      * Value to be added or subtracted from the local days of a new moon to\r
590      * get close to the next or prior new moon, but not cross it.  Must be\r
591      * >= 1 and < CalendarAstronomer.SYNODIC_MONTH.\r
592      */\r
593     private static final int SYNODIC_GAP = 25;\r
594 \r
595     /**\r
596      * Convert local days to UTC epoch milliseconds.\r
597      * @param days days after January 1, 1970 0:00 Asia/Shanghai\r
598      * @return milliseconds after January 1, 1970 0:00 GMT\r
599      */\r
600     private static final long daysToMillis(int days) {\r
601         return (days * ONE_DAY) - CHINA_OFFSET;\r
602     }\r
603 \r
604     /**\r
605      * Convert UTC epoch milliseconds to local days.\r
606      * @param millis milliseconds after January 1, 1970 0:00 GMT\r
607      * @return days after January 1, 1970 0:00 Asia/Shanghai\r
608      */\r
609     private static final int millisToDays(long millis) {\r
610         return (int) floorDivide(millis + CHINA_OFFSET, ONE_DAY);\r
611     }\r
612 \r
613     //------------------------------------------------------------------\r
614     // Astronomical computations\r
615     //------------------------------------------------------------------\r
616     \r
617     /**\r
618      * Return the major solar term on or after December 15 of the given\r
619      * Gregorian year, that is, the winter solstice of the given year.\r
620      * Computations are relative to Asia/Shanghai time zone.\r
621      * @param gyear a Gregorian year\r
622      * @return days after January 1, 1970 0:00 Asia/Shanghai of the\r
623      * winter solstice of the given year\r
624      */\r
625     private int winterSolstice(int gyear) {\r
626 \r
627         long cacheValue = winterSolsticeCache.get(gyear);\r
628 \r
629         if (cacheValue == CalendarCache.EMPTY) {\r
630             // In books December 15 is used, but it fails for some years\r
631             // using our algorithms, e.g.: 1298 1391 1492 1553 1560.  That\r
632             // is, winterSolstice(1298) starts search at Dec 14 08:00:00\r
633             // PST 1298 with a final result of Dec 14 10:31:59 PST 1299.\r
634             long ms = daysToMillis(computeGregorianMonthStart(gyear, DECEMBER) +\r
635                                    1 - EPOCH_JULIAN_DAY);\r
636             astro.setTime(ms);\r
637             \r
638             // Winter solstice is 270 degrees solar longitude aka Dongzhi\r
639             long solarLong = astro.getSunTime(CalendarAstronomer.WINTER_SOLSTICE,\r
640                                               true);\r
641             cacheValue = millisToDays(solarLong);\r
642             winterSolsticeCache.put(gyear, cacheValue);\r
643         }\r
644         return (int) cacheValue;\r
645     }\r
646 \r
647     /**\r
648      * Return the closest new moon to the given date, searching either\r
649      * forward or backward in time.\r
650      * @param days days after January 1, 1970 0:00 Asia/Shanghai\r
651      * @param after if true, search for a new moon on or after the given\r
652      * date; otherwise, search for a new moon before it\r
653      * @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest\r
654      * new moon after or before <code>days</code>\r
655      */\r
656     private int newMoonNear(int days, boolean after) {\r
657         \r
658         astro.setTime(daysToMillis(days));\r
659         long newMoon = astro.getMoonTime(CalendarAstronomer.NEW_MOON, after);\r
660         \r
661         return millisToDays(newMoon);\r
662     }\r
663 \r
664     /**\r
665      * Return the nearest integer number of synodic months between\r
666      * two dates.\r
667      * @param day1 days after January 1, 1970 0:00 Asia/Shanghai\r
668      * @param day2 days after January 1, 1970 0:00 Asia/Shanghai\r
669      * @return the nearest integer number of months between day1 and day2\r
670      */\r
671     private int synodicMonthsBetween(int day1, int day2) {\r
672         return (int) Math.round((day2 - day1) / CalendarAstronomer.SYNODIC_MONTH);\r
673     }\r
674 \r
675     /**\r
676      * Return the major solar term on or before a given date.  This\r
677      * will be an integer from 1..12, with 1 corresponding to 330 degrees,\r
678      * 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees.\r
679      * @param days days after January 1, 1970 0:00 Asia/Shanghai\r
680      */\r
681     private int majorSolarTerm(int days) {\r
682         \r
683         astro.setTime(daysToMillis(days));\r
684 \r
685         // Compute (floor(solarLongitude / (pi/6)) + 2) % 12\r
686         int term = ((int) Math.floor(6 * astro.getSunLongitude() / Math.PI) + 2) % 12;\r
687         if (term < 1) {\r
688             term += 12;\r
689         }\r
690         return term;\r
691     }\r
692 \r
693     /**\r
694      * Return true if the given month lacks a major solar term.\r
695      * @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new\r
696      * moon\r
697      */\r
698     private boolean hasNoMajorSolarTerm(int newMoon) {\r
699         \r
700         int mst = majorSolarTerm(newMoon);\r
701         int nmn = newMoonNear(newMoon + SYNODIC_GAP, true);\r
702         int mstt = majorSolarTerm(nmn);\r
703         return mst == mstt;\r
704         /*\r
705         return majorSolarTerm(newMoon) ==\r
706             majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, true));\r
707         */\r
708     }\r
709 \r
710     //------------------------------------------------------------------\r
711     // Time to fields\r
712     //------------------------------------------------------------------\r
713     \r
714     /**\r
715      * Return true if there is a leap month on or after month newMoon1 and\r
716      * at or before month newMoon2.\r
717      * @param newMoon1 days after January 1, 1970 0:00 Asia/Shanghai of a\r
718      * new moon\r
719      * @param newMoon2 days after January 1, 1970 0:00 Asia/Shanghai of a\r
720      * new moon\r
721      */\r
722     private boolean isLeapMonthBetween(int newMoon1, int newMoon2) {\r
723 \r
724         // This is only needed to debug the timeOfAngle divergence bug.\r
725         // Remove this later. Liu 11/9/00\r
726         // DEBUG\r
727         if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) {\r
728             throw new IllegalArgumentException("isLeapMonthBetween(" + newMoon1 +\r
729                                                ", " + newMoon2 +\r
730                                                "): Invalid parameters");\r
731         }\r
732 \r
733         return (newMoon2 >= newMoon1) &&\r
734             (isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, false)) ||\r
735              hasNoMajorSolarTerm(newMoon2));\r
736     }\r
737 \r
738     /**\r
739      * Override Calendar to compute several fields specific to the Chinese\r
740      * calendar system.  These are:\r
741      *\r
742      * <ul><li>ERA\r
743      * <li>YEAR\r
744      * <li>MONTH\r
745      * <li>DAY_OF_MONTH\r
746      * <li>DAY_OF_YEAR\r
747      * <li>EXTENDED_YEAR</ul>\r
748      * \r
749      * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this\r
750      * method is called.  The getGregorianXxx() methods return Gregorian\r
751      * calendar equivalents for the given Julian day.\r
752      *\r
753      * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH.\r
754      * @stable ICU 2.8\r
755      */\r
756     protected void handleComputeFields(int julianDay) {\r
757 \r
758         computeChineseFields(julianDay - EPOCH_JULIAN_DAY, // local days\r
759                              getGregorianYear(), getGregorianMonth(),\r
760                              true); // set all fields\r
761     }\r
762 \r
763     /**\r
764      * Compute fields for the Chinese calendar system.  This method can\r
765      * either set all relevant fields, as required by\r
766      * <code>handleComputeFields()</code>, or it can just set the MONTH and\r
767      * IS_LEAP_MONTH fields, as required by\r
768      * <code>handleComputeMonthStart()</code>.\r
769      *\r
770      * <p>As a side effect, this method sets {@link #isLeapYear}.\r
771      * @param days days after January 1, 1970 0:00 Asia/Shanghai of the\r
772      * date to compute fields for\r
773      * @param gyear the Gregorian year of the given date\r
774      * @param gmonth the Gregorian month of the given date\r
775      * @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR,\r
776      * DAY_OF_MONTH, and DAY_OF_YEAR fields.  In either case set the MONTH\r
777      * and IS_LEAP_MONTH fields.\r
778      */\r
779     private void computeChineseFields(int days, int gyear, int gmonth,\r
780                                       boolean setAllFields) {\r
781 \r
782         // Find the winter solstices before and after the target date.\r
783         // These define the boundaries of this Chinese year, specifically,\r
784         // the position of month 11, which always contains the solstice.\r
785         // We want solsticeBefore <= date < solsticeAfter.\r
786         int solsticeBefore;\r
787         int solsticeAfter = winterSolstice(gyear);\r
788         if (days < solsticeAfter) {\r
789             solsticeBefore = winterSolstice(gyear - 1);\r
790         } else {\r
791             solsticeBefore = solsticeAfter;\r
792             solsticeAfter = winterSolstice(gyear + 1);\r
793         }\r
794 \r
795         // Find the start of the month after month 11.  This will be either\r
796         // the prior month 12 or leap month 11 (very rare).  Also find the\r
797         // start of the following month 11.\r
798         int firstMoon = newMoonNear(solsticeBefore + 1, true);\r
799         int lastMoon = newMoonNear(solsticeAfter + 1, false);\r
800         int thisMoon = newMoonNear(days + 1, false); // Start of this month\r
801         // Note: isLeapYear is a member variable\r
802         isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12;\r
803 \r
804         int month = synodicMonthsBetween(firstMoon, thisMoon);\r
805         if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) {\r
806             month--;\r
807         }\r
808         if (month < 1) {\r
809             month += 12;\r
810         }\r
811 \r
812         boolean isLeapMonth = isLeapYear &&\r
813             hasNoMajorSolarTerm(thisMoon) &&\r
814             !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false));\r
815 \r
816         internalSet(MONTH, month-1); // Convert from 1-based to 0-based\r
817         internalSet(IS_LEAP_MONTH, isLeapMonth?1:0);\r
818 \r
819         if (setAllFields) {\r
820 \r
821             int year = gyear - CHINESE_EPOCH_YEAR;\r
822             if (month < 11 ||\r
823                 gmonth >= JULY) {\r
824                 year++;\r
825             }\r
826             int dayOfMonth = days - thisMoon + 1;\r
827 \r
828             internalSet(EXTENDED_YEAR, year);\r
829 \r
830             // 0->0,60  1->1,1  60->1,60  61->2,1  etc.\r
831             int[] yearOfCycle = new int[1];\r
832             int cycle = floorDivide(year-1, 60, yearOfCycle);\r
833             internalSet(ERA, cycle+1);\r
834             internalSet(YEAR, yearOfCycle[0]+1);\r
835 \r
836             internalSet(DAY_OF_MONTH, dayOfMonth);\r
837 \r
838             // Days will be before the first new year we compute if this\r
839             // date is in month 11, leap 11, 12.  There is never a leap 12.\r
840             // New year computations are cached so this should be cheap in\r
841             // the long run.\r
842             int newYear = newYear(gyear);\r
843             if (days < newYear) {\r
844                 newYear = newYear(gyear-1);\r
845             }\r
846             internalSet(DAY_OF_YEAR, days - newYear + 1);\r
847         }\r
848     }\r
849 \r
850     //------------------------------------------------------------------\r
851     // Fields to time\r
852     //------------------------------------------------------------------\r
853     \r
854     /**\r
855      * Return the Chinese new year of the given Gregorian year.\r
856      * @param gyear a Gregorian year\r
857      * @return days after January 1, 1970 0:00 Asia/Shanghai of the\r
858      * Chinese new year of the given year (this will be a new moon)\r
859      */\r
860     private int newYear(int gyear) {\r
861 \r
862         long cacheValue = newYearCache.get(gyear);\r
863 \r
864         if (cacheValue == CalendarCache.EMPTY) {\r
865 \r
866             int solsticeBefore= winterSolstice(gyear - 1);\r
867             int solsticeAfter = winterSolstice(gyear);\r
868             int newMoon1 = newMoonNear(solsticeBefore + 1, true);\r
869             int newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, true);\r
870             int newMoon11 = newMoonNear(solsticeAfter + 1, false);\r
871             \r
872             if (synodicMonthsBetween(newMoon1, newMoon11) == 12 &&\r
873                 (hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) {\r
874                 cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, true);\r
875             } else {\r
876                 cacheValue = newMoon2;\r
877             }\r
878 \r
879             newYearCache.put(gyear, cacheValue);\r
880         }\r
881         return (int) cacheValue;\r
882     }\r
883 \r
884     /**\r
885      * Return the Julian day number of day before the first day of the\r
886      * given month in the given extended year.\r
887      * \r
888      * <p>Note: This method reads the IS_LEAP_MONTH field to determine\r
889      * whether the given month is a leap month.\r
890      * @param eyear the extended year\r
891      * @param month the zero-based month.  The month is also determined\r
892      * by reading the IS_LEAP_MONTH field.\r
893      * @return the Julian day number of the day before the first\r
894      * day of the given month and year\r
895      * @stable ICU 2.8\r
896      */\r
897     protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {\r
898 \r
899         // If the month is out of range, adjust it into range, and\r
900         // modify the extended year value accordingly.\r
901         if (month < 0 || month > 11) {\r
902             int[] rem = new int[1];\r
903             eyear += floorDivide(month, 12, rem);\r
904             month = rem[0];\r
905         }\r
906 \r
907         int gyear = eyear + CHINESE_EPOCH_YEAR - 1; // Gregorian year\r
908         int newYear = newYear(gyear);\r
909         int newMoon = newMoonNear(newYear + month * 29, true);\r
910         \r
911         int julianDay = newMoon + EPOCH_JULIAN_DAY;\r
912 \r
913         // Save fields for later restoration\r
914         int saveMonth = internalGet(MONTH);\r
915         int saveIsLeapMonth = internalGet(IS_LEAP_MONTH);\r
916 \r
917         // Ignore IS_LEAP_MONTH field if useMonth is false\r
918         int isLeapMonth = useMonth ? saveIsLeapMonth : 0;\r
919 \r
920         computeGregorianFields(julianDay);\r
921         \r
922         // This will modify the MONTH and IS_LEAP_MONTH fields (only)\r
923         computeChineseFields(newMoon, getGregorianYear(),\r
924                              getGregorianMonth(), false);        \r
925 \r
926         if (month != internalGet(MONTH) ||\r
927             isLeapMonth != internalGet(IS_LEAP_MONTH)) {\r
928             newMoon = newMoonNear(newMoon + SYNODIC_GAP, true);\r
929             julianDay = newMoon + EPOCH_JULIAN_DAY;\r
930         }\r
931 \r
932         internalSet(MONTH, saveMonth);\r
933         internalSet(IS_LEAP_MONTH, saveIsLeapMonth);\r
934 \r
935         return julianDay - 1;\r
936     }\r
937 \r
938     /**\r
939      * Return the current Calendar type.\r
940      * @return type of calendar\r
941      * @stable ICU 3.8\r
942      */\r
943     public String getType() {\r
944         return "chinese";\r
945     }\r
946 \r
947     /**\r
948      * Override readObject.\r
949      */\r
950     private void readObject(ObjectInputStream stream)\r
951         throws IOException, ClassNotFoundException\r
952     {\r
953         stream.defaultReadObject();\r
954         \r
955         /* set up the transient caches... */\r
956         astro = new CalendarAstronomer();\r
957         winterSolsticeCache = new CalendarCache();\r
958         newYearCache = new CalendarCache();\r
959     }\r
960     \r
961     /*\r
962     private static CalendarFactory factory;\r
963     public static CalendarFactory factory() {\r
964         if (factory == null) {\r
965             factory = new CalendarFactory() {\r
966                 public Calendar create(TimeZone tz, ULocale loc) {\r
967                     return new ChineseCalendar(tz, loc);\r
968                 }\r
969 \r
970                 public String factoryName() {\r
971                     return "Chinese";\r
972                 }\r
973             };\r
974         }\r
975         return factory;\r
976     }\r
977     */\r
978 }\r