]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/util/ChineseCalendar.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / util / ChineseCalendar.java
1 /*********************************************************************\r
2  * Copyright (C) 2000-2009, 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>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
33  *\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
39  *\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
48  * 1 and 60.\r
49  *\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
54  * implements.\r
55  *\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
61  *\r
62  * <p>References:<ul>\r
63  * \r
64  * <li>Dershowitz and Reingold, <i>Calendrical Calculations</i>,\r
65  * Cambridge University Press, 1997</li>\r
66  * \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
70  *\r
71  * <li>The <a href="http://www.tondering.dk/claus/calendar.html">\r
72  * Calendar FAQ</a></li>\r
73  *\r
74  * </ul>\r
75  *\r
76  * <p>\r
77  * This class should not be subclassed.</p>\r
78  * <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
82  *\r
83  * @see com.ibm.icu.text.ChineseDateFormat\r
84  * @see com.ibm.icu.util.Calendar\r
85  * @author Alan Liu\r
86  * @stable ICU 2.8\r
87  */\r
88 public class ChineseCalendar extends Calendar {\r
89     // jdk1.4.2 serialver\r
90     private static final long serialVersionUID = 7312110751940929420L;\r
91 \r
92     //------------------------------------------------------------------\r
93     // Developer Notes\r
94     // \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
104     // \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
108  \r
109     /**\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
112      */\r
113     private transient CalendarAstronomer astro = new CalendarAstronomer();\r
114 \r
115     /**\r
116      * Cache that maps Gregorian year to local days of winter solstice.\r
117      * @see #winterSolstice\r
118      */\r
119     private transient CalendarCache winterSolsticeCache = new CalendarCache();\r
120 \r
121     /**\r
122      * Cache that maps Gregorian year to local days of Chinese new year.\r
123      * @see #newYear\r
124      */\r
125     private transient CalendarCache newYearCache = new CalendarCache();\r
126 \r
127     /**\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
131      */\r
132     private transient boolean isLeapYear;\r
133 \r
134     //------------------------------------------------------------------\r
135     // Constructors\r
136     //------------------------------------------------------------------\r
137 \r
138     /**\r
139      * Construct a <code>ChineseCalendar</code> with the default time zone and locale.\r
140      * @stable ICU 2.8\r
141      */\r
142     public ChineseCalendar() {\r
143         super();\r
144         setTimeInMillis(System.currentTimeMillis());\r
145     }\r
146 \r
147     /**\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
151      * @stable ICU 4.0\r
152      */\r
153     public ChineseCalendar(Date date) {\r
154         super();\r
155         setTime(date);\r
156     }\r
157 \r
158     /**\r
159      * Constructs a <code>ChineseCalendar</code> with the given date set\r
160      * in the default time zone with the default locale.\r
161      *\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
166      *                  time field.\r
167      * @param date      The value used to set the calendar's {@link #DATE DATE} time field.\r
168      * @stable ICU 4.0\r
169      */\r
170     public ChineseCalendar(int year, int month, int isLeapMonth, int date) {\r
171         super(TimeZone.getDefault(), ULocale.getDefault());\r
172 \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
178 \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
184     }\r
185 \r
186     /**\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
189      *\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
194      *              in the calendar.\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
197      *              in the calendar.\r
198      * @param minute the value used to set the {@link #MINUTE MINUTE} time field\r
199      *              in the calendar.\r
200      * @param second the value used to set the {@link #SECOND SECOND} time field\r
201      *              in the calendar.\r
202      * @stable ICU 4.0\r
203      */\r
204     public ChineseCalendar(int year, int month, int isLeapMonth, int date, int hour,\r
205                              int minute, int second)\r
206     {\r
207         super(TimeZone.getDefault(), ULocale.getDefault());\r
208 \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
214 \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
223     }\r
224 \r
225     /**\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
229      * @stable ICU 4.0\r
230      */\r
231     public ChineseCalendar(Locale aLocale) {\r
232         this(TimeZone.getDefault(), aLocale);\r
233         setTimeInMillis(System.currentTimeMillis());\r
234     }\r
235 \r
236     /**\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
240      * @stable ICU 4.0\r
241      */\r
242     public ChineseCalendar(TimeZone zone) {\r
243         super(zone, ULocale.getDefault());\r
244         setTimeInMillis(System.currentTimeMillis());\r
245     }\r
246 \r
247     /**\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
252      * @stable ICU 2.8\r
253      */\r
254     public ChineseCalendar(TimeZone zone, Locale aLocale) {\r
255         super(zone, aLocale);\r
256         setTimeInMillis(System.currentTimeMillis());\r
257     }\r
258 \r
259     /**\r
260      * Constructs a <code>ChineseCalendar</code> based on the current time\r
261      * in the default time zone with the given locale.\r
262      *\r
263      * @param locale the given ulocale\r
264      * @stable ICU 4.0\r
265      */\r
266     public ChineseCalendar(ULocale locale) {\r
267         this(TimeZone.getDefault(), locale);\r
268         setTimeInMillis(System.currentTimeMillis());\r
269     }\r
270 \r
271     /**\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
276      * @stable ICU 3.2\r
277      */\r
278     public ChineseCalendar(TimeZone zone, ULocale locale) {\r
279         super(zone, locale);\r
280         setTimeInMillis(System.currentTimeMillis());\r
281     }\r
282 \r
283     //------------------------------------------------------------------\r
284     // Public constants\r
285     //------------------------------------------------------------------\r
286 \r
287     /**\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
290      * @stable ICU 2.8\r
291      */\r
292     public static int IS_LEAP_MONTH = BASE_FIELD_COUNT;\r
293 \r
294     /**\r
295      * Count of fields in this class.\r
296      */\r
297     private static final int FIELD_COUNT = IS_LEAP_MONTH + 1;\r
298 \r
299     //------------------------------------------------------------------\r
300     // Calendar framework\r
301     //------------------------------------------------------------------\r
302 \r
303     /**\r
304      * Override Calendar to allocate our additional field.\r
305      * @stable ICU 2.8\r
306      */\r
307     protected int[] handleCreateFields() {\r
308         return new int[FIELD_COUNT];\r
309     }\r
310 \r
311     /**\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
315      *\r
316      * Notes:\r
317      *\r
318      * ERA 5000000 / 60 = 83333.\r
319      *\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
323      *\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
326      *\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
335      *\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
342      * Maximum is 6.\r
343      *\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
346      */\r
347     private static final int LIMITS[][] = {\r
348         // Minimum  Greatest    Least  Maximum\r
349         //           Minimum  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
359         {/*                                  */}, // AM_PM\r
360         {/*                                  */}, // HOUR\r
361         {/*                                  */}, // HOUR_OF_DAY\r
362         {/*                                  */}, // MINUTE\r
363         {/*                                  */}, // SECOND\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
373     };\r
374 \r
375     /**\r
376      * Override Calendar to return the limit value for the given field.\r
377      * @stable ICU 2.8\r
378      */\r
379     protected int handleGetLimit(int field, int limitType) {\r
380         return LIMITS[field][limitType];\r
381     }\r
382 \r
383     /**\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
388      * @stable ICU 2.8\r
389      */\r
390     protected int handleGetExtendedYear() {\r
391         int year;\r
392         if (newestStamp(ERA, YEAR, UNSET) <= getStamp(EXTENDED_YEAR)) {\r
393             year = internalGet(EXTENDED_YEAR, 1); // Default to year 1\r
394         } else {\r
395             int cycle = internalGet(ERA, 1) - 1; // 0-based cycle\r
396             year = cycle * 60 + internalGet(YEAR, 1);\r
397         }\r
398         return year;\r
399     }\r
400 \r
401     /**\r
402      * Override Calendar method to return the number of days in the given\r
403      * extended year and month.\r
404      *\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
407      * @stable ICU 2.8\r
408      */\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
414     }\r
415 \r
416     /**\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
421      * @stable ICU 2.8\r
422      */\r
423     protected DateFormat handleGetDateFormat(String pattern, String override, ULocale locale) {\r
424         return new ChineseDateFormat(pattern, override, locale);\r
425     }\r
426 \r
427     /**\r
428      * Field resolution table that incorporates IS_LEAP_MONTH.\r
429      */\r
430     static final int[][][] CHINESE_DATE_PRECEDENCE = {\r
431         {\r
432             { DAY_OF_MONTH },\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
439             { DAY_OF_YEAR },\r
440             { RESOLVE_REMAP | DAY_OF_MONTH, IS_LEAP_MONTH },\r
441         },\r
442         {\r
443             { WEEK_OF_YEAR },\r
444             { WEEK_OF_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
448         },\r
449     };\r
450 \r
451     /**\r
452      * Override Calendar to add IS_LEAP_MONTH to the field resolution\r
453      * table.\r
454      * @stable ICU 2.8\r
455      */\r
456     protected int[][][] getFieldResolutionTable() {\r
457         return CHINESE_DATE_PRECEDENCE;\r
458     }\r
459 \r
460     /**\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
470      */\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
474 \r
475         // Search forward to the target month's new moon\r
476         newMoon = newMoonNear(newMoon, true);\r
477 \r
478         // Find the target dom\r
479         int jd = newMoon + EPOCH_JULIAN_DAY - 1 + dom;\r
480 \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
483         if (dom > 29) {\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
489             complete();\r
490             if (getActualMaximum(DAY_OF_MONTH) >= dom) {\r
491                 set(JULIAN_DAY, jd);\r
492             }\r
493         } else {\r
494             set(JULIAN_DAY, jd);\r
495         }\r
496     }\r
497 \r
498     /**\r
499      * Override Calendar to handle leap months properly.\r
500      * @stable ICU 2.8\r
501      */\r
502     public void add(int field, int amount) {\r
503         switch (field) {\r
504         case MONTH:\r
505             if (amount != 0) {\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
510             }\r
511             break;\r
512         default:\r
513             super.add(field, amount);\r
514             break;\r
515         }\r
516     }\r
517 \r
518     /**\r
519      * Override Calendar to handle leap months properly.\r
520      * @stable ICU 2.8\r
521      */\r
522     public void roll(int field, int amount) {\r
523         switch (field) {\r
524         case MONTH:\r
525             if (amount != 0) {\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
529 \r
530                 // Note throughout the following:  Months 12 and 1 are never\r
531                 // followed by a leap month (D&R p. 185).\r
532 \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
535                 // leap year.\r
536                 int m = get(MONTH); // 0-based month\r
537                 if (isLeapYear) { // (member variable)\r
538                     if (get(IS_LEAP_MONTH) == 1) {\r
539                         ++m;\r
540                     } else {\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
548                         int moon1 = moon -\r
549                             (int) (CalendarAstronomer.SYNODIC_MONTH * (m - 0.5));\r
550                         moon1 = newMoonNear(moon1, true);\r
551                         if (isLeapMonthBetween(moon1, moon)) {\r
552                             ++m;\r
553                         }\r
554                     }\r
555                 }\r
556 \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
561                 if (newM < 0) {\r
562                     newM += n;\r
563                 }\r
564 \r
565                 if (newM != m) {\r
566                     offsetMonth(moon, dom, newM - m);\r
567                 }\r
568             }\r
569             break;\r
570         default:\r
571             super.roll(field, amount);\r
572             break;\r
573         }\r
574     }\r
575 \r
576     //------------------------------------------------------------------\r
577     // Support methods and constants\r
578     //------------------------------------------------------------------\r
579    \r
580     /**\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
585      */\r
586     private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year\r
587 \r
588     /**\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
592      */\r
593     private static final long CHINA_OFFSET = 8*ONE_HOUR;\r
594 \r
595     /**\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
599      */\r
600     private static final int SYNODIC_GAP = 25;\r
601 \r
602     /**\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
606      */\r
607     private static final long daysToMillis(int days) {\r
608         return (days * ONE_DAY) - CHINA_OFFSET;\r
609     }\r
610 \r
611     /**\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
615      */\r
616     private static final int millisToDays(long millis) {\r
617         return (int) floorDivide(millis + CHINA_OFFSET, ONE_DAY);\r
618     }\r
619 \r
620     //------------------------------------------------------------------\r
621     // Astronomical computations\r
622     //------------------------------------------------------------------\r
623     \r
624     /**\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
631      */\r
632     private int winterSolstice(int gyear) {\r
633 \r
634         long cacheValue = winterSolsticeCache.get(gyear);\r
635 \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
643             astro.setTime(ms);\r
644             \r
645             // Winter solstice is 270 degrees solar longitude aka Dongzhi\r
646             long solarLong = astro.getSunTime(CalendarAstronomer.WINTER_SOLSTICE,\r
647                                               true);\r
648             cacheValue = millisToDays(solarLong);\r
649             winterSolsticeCache.put(gyear, cacheValue);\r
650         }\r
651         return (int) cacheValue;\r
652     }\r
653 \r
654     /**\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
662      */\r
663     private int newMoonNear(int days, boolean after) {\r
664         \r
665         astro.setTime(daysToMillis(days));\r
666         long newMoon = astro.getMoonTime(CalendarAstronomer.NEW_MOON, after);\r
667         \r
668         return millisToDays(newMoon);\r
669     }\r
670 \r
671     /**\r
672      * Return the nearest integer number of synodic months between\r
673      * two dates.\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
677      */\r
678     private int synodicMonthsBetween(int day1, int day2) {\r
679         return (int) Math.round((day2 - day1) / CalendarAstronomer.SYNODIC_MONTH);\r
680     }\r
681 \r
682     /**\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
687      */\r
688     private int majorSolarTerm(int days) {\r
689         \r
690         astro.setTime(daysToMillis(days));\r
691 \r
692         // Compute (floor(solarLongitude / (pi/6)) + 2) % 12\r
693         int term = ((int) Math.floor(6 * astro.getSunLongitude() / Math.PI) + 2) % 12;\r
694         if (term < 1) {\r
695             term += 12;\r
696         }\r
697         return term;\r
698     }\r
699 \r
700     /**\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
703      * moon\r
704      */\r
705     private boolean hasNoMajorSolarTerm(int newMoon) {\r
706         \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
711         /*\r
712         return majorSolarTerm(newMoon) ==\r
713             majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, true));\r
714         */\r
715     }\r
716 \r
717     //------------------------------------------------------------------\r
718     // Time to fields\r
719     //------------------------------------------------------------------\r
720     \r
721     /**\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
725      * new moon\r
726      * @param newMoon2 days after January 1, 1970 0:00 Asia/Shanghai of a\r
727      * new moon\r
728      */\r
729     private boolean isLeapMonthBetween(int newMoon1, int newMoon2) {\r
730 \r
731         // This is only needed to debug the timeOfAngle divergence bug.\r
732         // Remove this later. Liu 11/9/00\r
733         // DEBUG\r
734         if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) {\r
735             throw new IllegalArgumentException("isLeapMonthBetween(" + newMoon1 +\r
736                                                ", " + newMoon2 +\r
737                                                "): Invalid parameters");\r
738         }\r
739 \r
740         return (newMoon2 >= newMoon1) &&\r
741             (isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, false)) ||\r
742              hasNoMajorSolarTerm(newMoon2));\r
743     }\r
744 \r
745     /**\r
746      * Override Calendar to compute several fields specific to the Chinese\r
747      * calendar system.  These are:\r
748      *\r
749      * <ul><li>ERA\r
750      * <li>YEAR\r
751      * <li>MONTH\r
752      * <li>DAY_OF_MONTH\r
753      * <li>DAY_OF_YEAR\r
754      * <li>EXTENDED_YEAR</ul>\r
755      * \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
759      *\r
760      * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH.\r
761      * @stable ICU 2.8\r
762      */\r
763     protected void handleComputeFields(int julianDay) {\r
764 \r
765         computeChineseFields(julianDay - EPOCH_JULIAN_DAY, // local days\r
766                              getGregorianYear(), getGregorianMonth(),\r
767                              true); // set all fields\r
768     }\r
769 \r
770     /**\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
776      *\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
785      */\r
786     private void computeChineseFields(int days, int gyear, int gmonth,\r
787                                       boolean setAllFields) {\r
788 \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
797         } else {\r
798             solsticeBefore = solsticeAfter;\r
799             solsticeAfter = winterSolstice(gyear + 1);\r
800         }\r
801 \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
810 \r
811         int month = synodicMonthsBetween(firstMoon, thisMoon);\r
812         if (isLeapYear && isLeapMonthBetween(firstMoon, thisMoon)) {\r
813             month--;\r
814         }\r
815         if (month < 1) {\r
816             month += 12;\r
817         }\r
818 \r
819         boolean isLeapMonth = isLeapYear &&\r
820             hasNoMajorSolarTerm(thisMoon) &&\r
821             !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false));\r
822 \r
823         internalSet(MONTH, month-1); // Convert from 1-based to 0-based\r
824         internalSet(IS_LEAP_MONTH, isLeapMonth?1:0);\r
825 \r
826         if (setAllFields) {\r
827 \r
828             int year = gyear - CHINESE_EPOCH_YEAR;\r
829             if (month < 11 ||\r
830                 gmonth >= JULY) {\r
831                 year++;\r
832             }\r
833             int dayOfMonth = days - thisMoon + 1;\r
834 \r
835             internalSet(EXTENDED_YEAR, year);\r
836 \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
842 \r
843             internalSet(DAY_OF_MONTH, dayOfMonth);\r
844 \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
848             // the long run.\r
849             int newYear = newYear(gyear);\r
850             if (days < newYear) {\r
851                 newYear = newYear(gyear-1);\r
852             }\r
853             internalSet(DAY_OF_YEAR, days - newYear + 1);\r
854         }\r
855     }\r
856 \r
857     //------------------------------------------------------------------\r
858     // Fields to time\r
859     //------------------------------------------------------------------\r
860     \r
861     /**\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
866      */\r
867     private int newYear(int gyear) {\r
868 \r
869         long cacheValue = newYearCache.get(gyear);\r
870 \r
871         if (cacheValue == CalendarCache.EMPTY) {\r
872 \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
878             \r
879             if (synodicMonthsBetween(newMoon1, newMoon11) == 12 &&\r
880                 (hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) {\r
881                 cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, true);\r
882             } else {\r
883                 cacheValue = newMoon2;\r
884             }\r
885 \r
886             newYearCache.put(gyear, cacheValue);\r
887         }\r
888         return (int) cacheValue;\r
889     }\r
890 \r
891     /**\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
894      * \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
902      * @stable ICU 2.8\r
903      */\r
904     protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {\r
905 \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
911             month = rem[0];\r
912         }\r
913 \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
917         \r
918         int julianDay = newMoon + EPOCH_JULIAN_DAY;\r
919 \r
920         // Save fields for later restoration\r
921         int saveMonth = internalGet(MONTH);\r
922         int saveIsLeapMonth = internalGet(IS_LEAP_MONTH);\r
923 \r
924         // Ignore IS_LEAP_MONTH field if useMonth is false\r
925         int isLeapMonth = useMonth ? saveIsLeapMonth : 0;\r
926 \r
927         computeGregorianFields(julianDay);\r
928         \r
929         // This will modify the MONTH and IS_LEAP_MONTH fields (only)\r
930         computeChineseFields(newMoon, getGregorianYear(),\r
931                              getGregorianMonth(), false);        \r
932 \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
937         }\r
938 \r
939         internalSet(MONTH, saveMonth);\r
940         internalSet(IS_LEAP_MONTH, saveIsLeapMonth);\r
941 \r
942         return julianDay - 1;\r
943     }\r
944 \r
945     /**\r
946      * Return the current Calendar type.\r
947      * @return type of calendar\r
948      * @stable ICU 3.8\r
949      */\r
950     public String getType() {\r
951         return "chinese";\r
952     }\r
953 \r
954     /**\r
955      * Override readObject.\r
956      */\r
957     private void readObject(ObjectInputStream stream)\r
958         throws IOException, ClassNotFoundException\r
959     {\r
960         stream.defaultReadObject();\r
961         \r
962         /* set up the transient caches... */\r
963         astro = new CalendarAstronomer();\r
964         winterSolsticeCache = new CalendarCache();\r
965         newYearCache = new CalendarCache();\r
966     }\r
967     \r
968     /*\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
975                 }\r
976 \r
977                 public String factoryName() {\r
978                     return "Chinese";\r
979                 }\r
980             };\r
981         }\r
982         return factory;\r
983     }\r
984     */\r
985 }\r