001: /*********************************************************************
002: * Copyright (C) 2000-2006, International Business Machines Corporation and
003: * others. All Rights Reserved.
004: *********************************************************************
005: */package com.ibm.icu.util;
006:
007: import com.ibm.icu.text.*;
008: import com.ibm.icu.util.TimeZone;
009: import com.ibm.icu.impl.CalendarAstronomer;
010: import com.ibm.icu.impl.CalendarCache;
011:
012: import java.io.IOException;
013: import java.io.ObjectInputStream;
014: import java.util.Locale;
015:
016: /**
017: * <code>ChineseCalendar</code> is a concrete subclass of {@link Calendar}
018: * that implements a traditional Chinese calendar. The traditional Chinese
019: * calendar is a lunisolar calendar: Each month starts on a new moon, and
020: * the months are numbered according to solar events, specifically, to
021: * guarantee that month 11 always contains the winter solstice. In order
022: * to accomplish this, leap months are inserted in certain years. Leap
023: * months are numbered the same as the month they follow. The decision of
024: * which month is a leap month depends on the relative movements of the sun
025: * and moon.
026: *
027: * <p>This class defines one addition field beyond those defined by
028: * <code>Calendar</code>: The <code>IS_LEAP_MONTH</code> field takes the
029: * value of 0 for normal months, or 1 for leap months.
030: *
031: * <p>All astronomical computations are performed with respect to a time
032: * zone of GMT+8:00 and a longitude of 120 degrees east. Although some
033: * calendars implement a historically more accurate convention of using
034: * Beijing's local longitude (116 degrees 25 minutes east) and time zone
035: * (GMT+7:45:40) for dates before 1929, we do not implement this here.
036: *
037: * <p>Years are counted in two different ways in the Chinese calendar. The
038: * first method is by sequential numbering from the 61st year of the reign
039: * of Huang Di, 2637 BCE, which is designated year 1 on the Chinese
040: * calendar. The second method uses 60-year cycles from the same starting
041: * point, which is designated year 1 of cycle 1. In this class, the
042: * <code>EXTENDED_YEAR</code> field contains the sequential year count.
043: * The <code>ERA</code> field contains the cycle number, and the
044: * <code>YEAR</code> field contains the year of the cycle, a value between
045: * 1 and 60.
046: *
047: * <p>There is some variation in what is considered the starting point of
048: * the calendar, with some sources starting in the first year of the reign
049: * of Huang Di, rather than the 61st. This gives continuous year numbers
050: * 60 years greater and cycle numbers one greater than what this class
051: * implements.
052: *
053: * <p>Because <code>ChineseCalendar</code> defines an additional field and
054: * redefines the way the <code>ERA</code> field is used, it requires a new
055: * format class, <code>ChineseDateFormat</code>. As always, use the
056: * methods <code>DateFormat.getXxxInstance(Calendar cal,...)</code> to
057: * obtain a formatter for this calendar.
058: *
059: * <p>References:<ul>
060: *
061: * <li>Dershowitz and Reingold, <i>Calendrical Calculations</i>,
062: * Cambridge University Press, 1997</li>
063: *
064: * <li>Helmer Aslaksen's
065: * <a href="http://www.math.nus.edu.sg/aslaksen/calendar/chinese.shtml">
066: * Chinese Calendar page</a></li>
067: *
068: * <li>The <a href="http://www.tondering.dk/claus/calendar.html">
069: * Calendar FAQ</a></li>
070: *
071: * </ul>
072: *
073: * <p>
074: * This class should not be subclassed.</p>
075: * <p>
076: * ChineseCalendar usually should be instantiated using
077: * {@link com.ibm.icu.util.Calendar#getInstance(ULocale)} passing in a <code>ULocale</code>
078: * with the tag <code>"@calendar=chinese"</code>.</p>
079: *
080: * @see com.ibm.icu.text.ChineseDateFormat
081: * @see com.ibm.icu.util.Calendar
082: * @author Alan Liu
083: * @stable ICU 2.8
084: */
085: public class ChineseCalendar extends Calendar {
086: // jdk1.4.2 serialver
087: private static final long serialVersionUID = 7312110751940929420L;
088:
089: //------------------------------------------------------------------
090: // Developer Notes
091: //
092: // Time is represented as a scalar in two ways in this class. One is
093: // the usual UTC epoch millis, that is, milliseconds after January 1,
094: // 1970 Gregorian, 0:00:00.000 UTC. The other is in terms of 'local
095: // days.' This is the number of days after January 1, 1970 Gregorian,
096: // local to Beijing, China (since all computations of the Chinese
097: // calendar are done in Beijing). That is, 0 represents January 1,
098: // 1970 0:00 Asia/Shanghai. Conversion of local days to and from
099: // standard epoch milliseconds is accomplished by the daysToMillis()
100: // and millisToDays() methods.
101: //
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.
105:
106: /**
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.
109: */
110: private transient CalendarAstronomer astro = new CalendarAstronomer();
111:
112: /**
113: * Cache that maps Gregorian year to local days of winter solstice.
114: * @see #winterSolstice
115: */
116: private transient CalendarCache winterSolsticeCache = new CalendarCache();
117:
118: /**
119: * Cache that maps Gregorian year to local days of Chinese new year.
120: * @see #newYear
121: */
122: private transient CalendarCache newYearCache = new CalendarCache();
123:
124: /**
125: * True if the current year is a leap year. Updated with each time to
126: * fields resolution.
127: * @see #computeChineseFields
128: */
129: private transient boolean isLeapYear;
130:
131: //------------------------------------------------------------------
132: // Constructors
133: //------------------------------------------------------------------
134:
135: /**
136: * Construct a Chinese calendar with the default time zone and locale.
137: * @stable ICU 2.8
138: */
139: public ChineseCalendar() {
140: super ();
141: setTimeInMillis(System.currentTimeMillis());
142: }
143:
144: /**
145: * Construct a Chinese calendar with the given time zone and locale.
146: * @param zone time zone for this calendar
147: * @param locale locale for this calendar
148: * @stable ICU 2.8
149: */
150: public ChineseCalendar(TimeZone zone, Locale locale) {
151: super (zone, locale);
152: setTimeInMillis(System.currentTimeMillis());
153: }
154:
155: /**
156: * Construct a Chinese calendar with the given time zone and locale.
157: * @param zone time zone for this calendar
158: * @param locale ulocale for this calendar
159: * @draft ICU 3.2
160: * @provisional This API might change or be removed in a future release.
161: */
162: public ChineseCalendar(TimeZone zone, ULocale locale) {
163: super (zone, locale);
164: setTimeInMillis(System.currentTimeMillis());
165: }
166:
167: //------------------------------------------------------------------
168: // Public constants
169: //------------------------------------------------------------------
170:
171: /**
172: * Field indicating whether or not the current month is a leap month.
173: * Should have a value of 0 for non-leap months, and 1 for leap months.
174: * @stable ICU 2.8
175: */
176: public static int IS_LEAP_MONTH = BASE_FIELD_COUNT;
177:
178: /**
179: * Count of fields in this class.
180: */
181: private static final int FIELD_COUNT = IS_LEAP_MONTH + 1;
182:
183: //------------------------------------------------------------------
184: // Calendar framework
185: //------------------------------------------------------------------
186:
187: /**
188: * Override Calendar to allocate our additional field.
189: * @stable ICU 2.8
190: */
191: protected int[] handleCreateFields() {
192: return new int[FIELD_COUNT];
193: }
194:
195: /**
196: * Array defining the limits of field values for this class. Field
197: * limits which are invariant with respect to calendar system and
198: * defined by Calendar are left blank.
199: *
200: * Notes:
201: *
202: * ERA 5000000 / 60 = 83333.
203: *
204: * MONTH There are 12 or 13 lunar months in a year. However, we always
205: * number them 0..11, with an intercalated, identically numbered leap
206: * month, when necessary.
207: *
208: * DAY_OF_YEAR In a non-leap year there are 353, 354, or 355 days. In
209: * a leap year there are 383, 384, or 385 days.
210: *
211: * WEEK_OF_YEAR The least maximum occurs if there are 353 days in the
212: * year, and the first 6 are the last week of the previous year. Then
213: * we have 49 full weeks and 4 days in the last week: 6 + 49*7 + 4 =
214: * 353. So the least maximum is 50. The maximum occurs if there are
215: * 385 days in the year, and WOY 1 extends 6 days into the prior year.
216: * Then there are 54 full weeks, and 6 days in the last week: 1 + 54*7
217: * + 6 = 385. The 6 days of the last week will fall into WOY 1 of the
218: * next year. Maximum is 55.
219: *
220: * WEEK_OF_MONTH In a 29 day month, if the first 7 days make up week 1
221: * that leaves 3 full weeks and 1 day at the end. The least maximum is
222: * thus 5. In a 30 days month, if the previous 6 days belong WOM 1 of
223: * this month, we have 4 full weeks and 1 days at the end (which
224: * technically will be WOM 1 of the next month, but will be reported by
225: * time->fields and hence by getActualMaximum as WOM 6 of this month).
226: * Maximum is 6.
227: *
228: * DAY_OF_WEEK_IN_MONTH In a 29 or 30 day month, there are 4 full weeks
229: * plus 1 or 2 days at the end, so the maximum is always 5.
230: */
231: private static final int LIMITS[][] = {
232: // Minimum Greatest Least Maximum
233: // Minimum Maximum
234: { 1, 1, 83333, 83333 }, // ERA
235: { 1, 1, 70, 70 }, // YEAR
236: { 0, 0, 11, 11 }, // MONTH
237: { 1, 1, 50, 55 }, // WEEK_OF_YEAR
238: { 1, 1, 5, 6 }, // WEEK_OF_MONTH
239: { 1, 1, 29, 30 }, // DAY_OF_MONTH
240: { 1, 1, 353, 385 }, // DAY_OF_YEAR
241: {/* */}, // DAY_OF_WEEK
242: { -1, -1, 5, 5 }, // DAY_OF_WEEK_IN_MONTH
243: {/* */}, // AM_PM
244: {/* */}, // HOUR
245: {/* */}, // HOUR_OF_DAY
246: {/* */}, // MINUTE
247: {/* */}, // SECOND
248: {/* */}, // MILLISECOND
249: {/* */}, // ZONE_OFFSET
250: {/* */}, // DST_OFFSET
251: { -5000001, -5000001, 5000001, 5000001 }, // YEAR_WOY
252: {/* */}, // DOW_LOCAL
253: { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR
254: {/* */}, // JULIAN_DAY
255: {/* */}, // MILLISECONDS_IN_DAY
256: { 0, 0, 1, 1 }, // IS_LEAP_MONTH
257: };
258:
259: /**
260: * Override Calendar to return the limit value for the given field.
261: * @stable ICU 2.8
262: */
263: protected int handleGetLimit(int field, int limitType) {
264: return LIMITS[field][limitType];
265: }
266:
267: /**
268: * Implement abstract Calendar method to return the extended year
269: * defined by the current fields. This will use either the ERA and
270: * YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR
271: * field as the continuous year count, depending on which is newer.
272: * @stable ICU 2.8
273: */
274: protected int handleGetExtendedYear() {
275: int year;
276: if (newestStamp(ERA, YEAR, UNSET) <= getStamp(EXTENDED_YEAR)) {
277: year = internalGet(EXTENDED_YEAR, 1); // Default to year 1
278: } else {
279: int cycle = internalGet(ERA, 1) - 1; // 0-based cycle
280: year = cycle * 60 + internalGet(YEAR, 1);
281: }
282: return year;
283: }
284:
285: /**
286: * Override Calendar method to return the number of days in the given
287: * extended year and month.
288: *
289: * <p>Note: This method also reads the IS_LEAP_MONTH field to determine
290: * whether or not the given month is a leap month.
291: * @stable ICU 2.8
292: */
293: protected int handleGetMonthLength(int extendedYear, int month) {
294: int this Start = handleComputeMonthStart(extendedYear, month,
295: true)
296: - EPOCH_JULIAN_DAY + 1; // Julian day -> local days
297: int nextStart = newMoonNear(this Start + SYNODIC_GAP, true);
298: return nextStart - this Start;
299: }
300:
301: /**
302: * Framework method to create a calendar-specific DateFormat object
303: * using the the given pattern. This method is responsible for
304: * creating the calendar- specific DateFormat and DateFormatSymbols
305: * objects as needed.
306: * @stable ICU 2.8
307: */
308: protected DateFormat handleGetDateFormat(String pattern,
309: ULocale locale) {
310: return new ChineseDateFormat(pattern, locale);
311: }
312:
313: /**
314: * Field resolution table that incorporates IS_LEAP_MONTH.
315: */
316: static final int[][][] CHINESE_DATE_PRECEDENCE = {
317: { { DAY_OF_MONTH }, { WEEK_OF_YEAR, DAY_OF_WEEK },
318: { WEEK_OF_MONTH, DAY_OF_WEEK },
319: { DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
320: { WEEK_OF_YEAR, DOW_LOCAL },
321: { WEEK_OF_MONTH, DOW_LOCAL },
322: { DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
323: { DAY_OF_YEAR },
324: { RESOLVE_REMAP | DAY_OF_MONTH, IS_LEAP_MONTH }, },
325: {
326: { WEEK_OF_YEAR },
327: { WEEK_OF_MONTH },
328: { DAY_OF_WEEK_IN_MONTH },
329: { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
330: { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DOW_LOCAL }, }, };
331:
332: /**
333: * Override Calendar to add IS_LEAP_MONTH to the field resolution
334: * table.
335: * @stable ICU 2.8
336: */
337: protected int[][][] getFieldResolutionTable() {
338: return CHINESE_DATE_PRECEDENCE;
339: }
340:
341: /**
342: * Adjust this calendar to be delta months before or after a given
343: * start position, pinning the day of month if necessary. The start
344: * position is given as a local days number for the start of the month
345: * and a day-of-month. Used by add() and roll().
346: * @param newMoon the local days of the first day of the month of the
347: * start position (days after January 1, 1970 0:00 Asia/Shanghai)
348: * @param dom the 1-based day-of-month of the start position
349: * @param delta the number of months to move forward or backward from
350: * the start position
351: */
352: private void offsetMonth(int newMoon, int dom, int delta) {
353: // Move to the middle of the month before our target month.
354: newMoon += (int) (CalendarAstronomer.SYNODIC_MONTH * (delta - 0.5));
355:
356: // Search forward to the target month's new moon
357: newMoon = newMoonNear(newMoon, true);
358:
359: // Find the target dom
360: int jd = newMoon + EPOCH_JULIAN_DAY - 1 + dom;
361:
362: // Pin the dom. In this calendar all months are 29 or 30 days
363: // so pinning just means handling dom 30.
364: if (dom > 29) {
365: set(JULIAN_DAY, jd - 1);
366: // TODO Fix this. We really shouldn't ever have to
367: // explicitly call complete(). This is either a bug in
368: // this method, in ChineseCalendar, or in
369: // Calendar.getActualMaximum(). I suspect the last.
370: complete();
371: if (getActualMaximum(DAY_OF_MONTH) >= dom) {
372: set(JULIAN_DAY, jd);
373: }
374: } else {
375: set(JULIAN_DAY, jd);
376: }
377: }
378:
379: /**
380: * Override Calendar to handle leap months properly.
381: * @stable ICU 2.8
382: */
383: public void add(int field, int amount) {
384: switch (field) {
385: case MONTH:
386: if (amount != 0) {
387: int dom = get(DAY_OF_MONTH);
388: int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day
389: int moon = day - dom + 1; // New moon
390: offsetMonth(moon, dom, amount);
391: }
392: break;
393: default:
394: super .add(field, amount);
395: break;
396: }
397: }
398:
399: /**
400: * Override Calendar to handle leap months properly.
401: * @stable ICU 2.8
402: */
403: public void roll(int field, int amount) {
404: switch (field) {
405: case MONTH:
406: if (amount != 0) {
407: int dom = get(DAY_OF_MONTH);
408: int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day
409: int moon = day - dom + 1; // New moon (start of this month)
410:
411: // Note throughout the following: Months 12 and 1 are never
412: // followed by a leap month (D&R p. 185).
413:
414: // Compute the adjusted month number m. This is zero-based
415: // value from 0..11 in a non-leap year, and from 0..12 in a
416: // leap year.
417: int m = get(MONTH); // 0-based month
418: if (isLeapYear) { // (member variable)
419: if (get(IS_LEAP_MONTH) == 1) {
420: ++m;
421: } else {
422: // Check for a prior leap month. (In the
423: // following, month 0 is the first month of the
424: // year.) Month 0 is never followed by a leap
425: // month, and we know month m is not a leap month.
426: // moon1 will be the start of month 0 if there is
427: // no leap month between month 0 and month m;
428: // otherwise it will be the start of month 1.
429: int moon1 = moon
430: - (int) (CalendarAstronomer.SYNODIC_MONTH * (m - 0.5));
431: moon1 = newMoonNear(moon1, true);
432: if (isLeapMonthBetween(moon1, moon)) {
433: ++m;
434: }
435: }
436: }
437:
438: // Now do the standard roll computation on m, with the
439: // allowed range of 0..n-1, where n is 12 or 13.
440: int n = isLeapYear ? 13 : 12; // Months in this year
441: int newM = (m + amount) % n;
442: if (newM < 0) {
443: newM += n;
444: }
445:
446: if (newM != m) {
447: offsetMonth(moon, dom, newM - m);
448: }
449: }
450: break;
451: default:
452: super .roll(field, amount);
453: break;
454: }
455: }
456:
457: //------------------------------------------------------------------
458: // Support methods and constants
459: //------------------------------------------------------------------
460:
461: /**
462: * The start year of the Chinese calendar, the 61st year of the reign
463: * of Huang Di. Some sources use the first year of his reign,
464: * resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle)
465: * values one greater.
466: */
467: private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year
468:
469: /**
470: * The offset from GMT in milliseconds at which we perform astronomical
471: * computations. Some sources use a different historically accurate
472: * offset of GMT+7:45:40 for years before 1929; we do not do this.
473: */
474: private static final long CHINA_OFFSET = 8 * ONE_HOUR;
475:
476: /**
477: * Value to be added or subtracted from the local days of a new moon to
478: * get close to the next or prior new moon, but not cross it. Must be
479: * >= 1 and < CalendarAstronomer.SYNODIC_MONTH.
480: */
481: private static final int SYNODIC_GAP = 25;
482:
483: /**
484: * Convert local days to UTC epoch milliseconds.
485: * @param days days after January 1, 1970 0:00 Asia/Shanghai
486: * @return milliseconds after January 1, 1970 0:00 GMT
487: */
488: private static final long daysToMillis(int days) {
489: return (days * ONE_DAY) - CHINA_OFFSET;
490: }
491:
492: /**
493: * Convert UTC epoch milliseconds to local days.
494: * @param millis milliseconds after January 1, 1970 0:00 GMT
495: * @return days after January 1, 1970 0:00 Asia/Shanghai
496: */
497: private static final int millisToDays(long millis) {
498: return (int) floorDivide(millis + CHINA_OFFSET, ONE_DAY);
499: }
500:
501: //------------------------------------------------------------------
502: // Astronomical computations
503: //------------------------------------------------------------------
504:
505: /**
506: * Return the major solar term on or after December 15 of the given
507: * Gregorian year, that is, the winter solstice of the given year.
508: * Computations are relative to Asia/Shanghai time zone.
509: * @param gyear a Gregorian year
510: * @return days after January 1, 1970 0:00 Asia/Shanghai of the
511: * winter solstice of the given year
512: */
513: private int winterSolstice(int gyear) {
514:
515: long cacheValue = winterSolsticeCache.get(gyear);
516:
517: if (cacheValue == CalendarCache.EMPTY) {
518: // In books December 15 is used, but it fails for some years
519: // using our algorithms, e.g.: 1298 1391 1492 1553 1560. That
520: // is, winterSolstice(1298) starts search at Dec 14 08:00:00
521: // PST 1298 with a final result of Dec 14 10:31:59 PST 1299.
522: long ms = daysToMillis(computeGregorianMonthStart(gyear,
523: DECEMBER)
524: + 1 - EPOCH_JULIAN_DAY);
525: astro.setTime(ms);
526:
527: // Winter solstice is 270 degrees solar longitude aka Dongzhi
528: long solarLong = astro.getSunTime(
529: CalendarAstronomer.WINTER_SOLSTICE, true);
530: cacheValue = millisToDays(solarLong);
531: winterSolsticeCache.put(gyear, cacheValue);
532: }
533: return (int) cacheValue;
534: }
535:
536: /**
537: * Return the closest new moon to the given date, searching either
538: * forward or backward in time.
539: * @param days days after January 1, 1970 0:00 Asia/Shanghai
540: * @param after if true, search for a new moon on or after the given
541: * date; otherwise, search for a new moon before it
542: * @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest
543: * new moon after or before <code>days</code>
544: */
545: private int newMoonNear(int days, boolean after) {
546:
547: astro.setTime(daysToMillis(days));
548: long newMoon = astro.getMoonTime(CalendarAstronomer.NEW_MOON,
549: after);
550:
551: return millisToDays(newMoon);
552: }
553:
554: /**
555: * Return the nearest integer number of synodic months between
556: * two dates.
557: * @param day1 days after January 1, 1970 0:00 Asia/Shanghai
558: * @param day2 days after January 1, 1970 0:00 Asia/Shanghai
559: * @return the nearest integer number of months between day1 and day2
560: */
561: private int synodicMonthsBetween(int day1, int day2) {
562: return (int) Math.round((day2 - day1)
563: / CalendarAstronomer.SYNODIC_MONTH);
564: }
565:
566: /**
567: * Return the major solar term on or before a given date. This
568: * will be an integer from 1..12, with 1 corresponding to 330 degrees,
569: * 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees.
570: * @param days days after January 1, 1970 0:00 Asia/Shanghai
571: */
572: private int majorSolarTerm(int days) {
573:
574: astro.setTime(daysToMillis(days));
575:
576: // Compute (floor(solarLongitude / (pi/6)) + 2) % 12
577: int term = ((int) Math.floor(6 * astro.getSunLongitude()
578: / Math.PI) + 2) % 12;
579: if (term < 1) {
580: term += 12;
581: }
582: return term;
583: }
584:
585: /**
586: * Return true if the given month lacks a major solar term.
587: * @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new
588: * moon
589: */
590: private boolean hasNoMajorSolarTerm(int newMoon) {
591:
592: int mst = majorSolarTerm(newMoon);
593: int nmn = newMoonNear(newMoon + SYNODIC_GAP, true);
594: int mstt = majorSolarTerm(nmn);
595: return mst == mstt;
596: /*
597: return majorSolarTerm(newMoon) ==
598: majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, true));
599: */
600: }
601:
602: //------------------------------------------------------------------
603: // Time to fields
604: //------------------------------------------------------------------
605:
606: /**
607: * Return true if there is a leap month on or after month newMoon1 and
608: * at or before month newMoon2.
609: * @param newMoon1 days after January 1, 1970 0:00 Asia/Shanghai of a
610: * new moon
611: * @param newMoon2 days after January 1, 1970 0:00 Asia/Shanghai of a
612: * new moon
613: */
614: private boolean isLeapMonthBetween(int newMoon1, int newMoon2) {
615:
616: // This is only needed to debug the timeOfAngle divergence bug.
617: // Remove this later. Liu 11/9/00
618: // DEBUG
619: if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) {
620: throw new IllegalArgumentException("isLeapMonthBetween("
621: + newMoon1 + ", " + newMoon2
622: + "): Invalid parameters");
623: }
624:
625: return (newMoon2 >= newMoon1)
626: && (isLeapMonthBetween(newMoon1, newMoonNear(newMoon2
627: - SYNODIC_GAP, false)) || hasNoMajorSolarTerm(newMoon2));
628: }
629:
630: /**
631: * Override Calendar to compute several fields specific to the Chinese
632: * calendar system. These are:
633: *
634: * <ul><li>ERA
635: * <li>YEAR
636: * <li>MONTH
637: * <li>DAY_OF_MONTH
638: * <li>DAY_OF_YEAR
639: * <li>EXTENDED_YEAR</ul>
640: *
641: * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this
642: * method is called. The getGregorianXxx() methods return Gregorian
643: * calendar equivalents for the given Julian day.
644: *
645: * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH.
646: * @stable ICU 2.8
647: */
648: protected void handleComputeFields(int julianDay) {
649:
650: computeChineseFields(julianDay - EPOCH_JULIAN_DAY, // local days
651: getGregorianYear(), getGregorianMonth(), true); // set all fields
652: }
653:
654: /**
655: * Compute fields for the Chinese calendar system. This method can
656: * either set all relevant fields, as required by
657: * <code>handleComputeFields()</code>, or it can just set the MONTH and
658: * IS_LEAP_MONTH fields, as required by
659: * <code>handleComputeMonthStart()</code>.
660: *
661: * <p>As a side effect, this method sets {@link #isLeapYear}.
662: * @param days days after January 1, 1970 0:00 Asia/Shanghai of the
663: * date to compute fields for
664: * @param gyear the Gregorian year of the given date
665: * @param gmonth the Gregorian month of the given date
666: * @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR,
667: * DAY_OF_MONTH, and DAY_OF_YEAR fields. In either case set the MONTH
668: * and IS_LEAP_MONTH fields.
669: */
670: private void computeChineseFields(int days, int gyear, int gmonth,
671: boolean setAllFields) {
672:
673: // Find the winter solstices before and after the target date.
674: // These define the boundaries of this Chinese year, specifically,
675: // the position of month 11, which always contains the solstice.
676: // We want solsticeBefore <= date < solsticeAfter.
677: int solsticeBefore;
678: int solsticeAfter = winterSolstice(gyear);
679: if (days < solsticeAfter) {
680: solsticeBefore = winterSolstice(gyear - 1);
681: } else {
682: solsticeBefore = solsticeAfter;
683: solsticeAfter = winterSolstice(gyear + 1);
684: }
685:
686: // Find the start of the month after month 11. This will be either
687: // the prior month 12 or leap month 11 (very rare). Also find the
688: // start of the following month 11.
689: int firstMoon = newMoonNear(solsticeBefore + 1, true);
690: int lastMoon = newMoonNear(solsticeAfter + 1, false);
691: int this Moon = newMoonNear(days + 1, false); // Start of this month
692: // Note: isLeapYear is a member variable
693: isLeapYear = synodicMonthsBetween(firstMoon, lastMoon) == 12;
694:
695: int month = synodicMonthsBetween(firstMoon, this Moon);
696: if (isLeapYear && isLeapMonthBetween(firstMoon, this Moon)) {
697: month--;
698: }
699: if (month < 1) {
700: month += 12;
701: }
702:
703: boolean isLeapMonth = isLeapYear
704: && hasNoMajorSolarTerm(this Moon)
705: && !isLeapMonthBetween(firstMoon, newMoonNear(this Moon
706: - SYNODIC_GAP, false));
707:
708: internalSet(MONTH, month - 1); // Convert from 1-based to 0-based
709: internalSet(IS_LEAP_MONTH, isLeapMonth ? 1 : 0);
710:
711: if (setAllFields) {
712:
713: int year = gyear - CHINESE_EPOCH_YEAR;
714: if (month < 11 || gmonth >= JULY) {
715: year++;
716: }
717: int dayOfMonth = days - this Moon + 1;
718:
719: internalSet(EXTENDED_YEAR, year);
720:
721: // 0->0,60 1->1,1 60->1,60 61->2,1 etc.
722: int[] yearOfCycle = new int[1];
723: int cycle = floorDivide(year - 1, 60, yearOfCycle);
724: internalSet(ERA, cycle + 1);
725: internalSet(YEAR, yearOfCycle[0] + 1);
726:
727: internalSet(DAY_OF_MONTH, dayOfMonth);
728:
729: // Days will be before the first new year we compute if this
730: // date is in month 11, leap 11, 12. There is never a leap 12.
731: // New year computations are cached so this should be cheap in
732: // the long run.
733: int newYear = newYear(gyear);
734: if (days < newYear) {
735: newYear = newYear(gyear - 1);
736: }
737: internalSet(DAY_OF_YEAR, days - newYear + 1);
738: }
739: }
740:
741: //------------------------------------------------------------------
742: // Fields to time
743: //------------------------------------------------------------------
744:
745: /**
746: * Return the Chinese new year of the given Gregorian year.
747: * @param gyear a Gregorian year
748: * @return days after January 1, 1970 0:00 Asia/Shanghai of the
749: * Chinese new year of the given year (this will be a new moon)
750: */
751: private int newYear(int gyear) {
752:
753: long cacheValue = newYearCache.get(gyear);
754:
755: if (cacheValue == CalendarCache.EMPTY) {
756:
757: int solsticeBefore = winterSolstice(gyear - 1);
758: int solsticeAfter = winterSolstice(gyear);
759: int newMoon1 = newMoonNear(solsticeBefore + 1, true);
760: int newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, true);
761: int newMoon11 = newMoonNear(solsticeAfter + 1, false);
762:
763: if (synodicMonthsBetween(newMoon1, newMoon11) == 12
764: && (hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) {
765: cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, true);
766: } else {
767: cacheValue = newMoon2;
768: }
769:
770: newYearCache.put(gyear, cacheValue);
771: }
772: return (int) cacheValue;
773: }
774:
775: /**
776: * Return the Julian day number of day before the first day of the
777: * given month in the given extended year.
778: *
779: * <p>Note: This method reads the IS_LEAP_MONTH field to determine
780: * whether the given month is a leap month.
781: * @param eyear the extended year
782: * @param month the zero-based month. The month is also determined
783: * by reading the IS_LEAP_MONTH field.
784: * @return the Julian day number of the day before the first
785: * day of the given month and year
786: * @stable ICU 2.8
787: */
788: protected int handleComputeMonthStart(int eyear, int month,
789: boolean useMonth) {
790:
791: // If the month is out of range, adjust it into range, and
792: // modify the extended year value accordingly.
793: if (month < 0 || month > 11) {
794: int[] rem = new int[1];
795: eyear += floorDivide(month, 12, rem);
796: month = rem[0];
797: }
798:
799: int gyear = eyear + CHINESE_EPOCH_YEAR - 1; // Gregorian year
800: int newYear = newYear(gyear);
801: int newMoon = newMoonNear(newYear + month * 29, true);
802:
803: int julianDay = newMoon + EPOCH_JULIAN_DAY;
804:
805: // Save fields for later restoration
806: int saveMonth = internalGet(MONTH);
807: int saveIsLeapMonth = internalGet(IS_LEAP_MONTH);
808:
809: // Ignore IS_LEAP_MONTH field if useMonth is false
810: int isLeapMonth = useMonth ? saveIsLeapMonth : 0;
811:
812: computeGregorianFields(julianDay);
813:
814: // This will modify the MONTH and IS_LEAP_MONTH fields (only)
815: computeChineseFields(newMoon, getGregorianYear(),
816: getGregorianMonth(), false);
817:
818: if (month != internalGet(MONTH)
819: || isLeapMonth != internalGet(IS_LEAP_MONTH)) {
820: newMoon = newMoonNear(newMoon + SYNODIC_GAP, true);
821: julianDay = newMoon + EPOCH_JULIAN_DAY;
822: }
823:
824: internalSet(MONTH, saveMonth);
825: internalSet(IS_LEAP_MONTH, saveIsLeapMonth);
826:
827: return julianDay - 1;
828: }
829:
830: /**
831: * Return the current Calendar type.
832: * @return type of calendar (gregorian, etc.)
833: * @internal ICU 3.0
834: * @deprecated This API is ICU internal only.
835: */
836: public String getType() {
837: return "chinese";
838: }
839:
840: /**
841: * Override readObject.
842: */
843: private void readObject(ObjectInputStream stream)
844: throws IOException, ClassNotFoundException {
845: stream.defaultReadObject();
846:
847: /* set up the transient caches... */
848: astro = new CalendarAstronomer();
849: winterSolsticeCache = new CalendarCache();
850: newYearCache = new CalendarCache();
851: }
852:
853: /*
854: private static CalendarFactory factory;
855: public static CalendarFactory factory() {
856: if (factory == null) {
857: factory = new CalendarFactory() {
858: public Calendar create(TimeZone tz, ULocale loc) {
859: return new ChineseCalendar(tz, loc);
860: }
861:
862: public String factoryName() {
863: return "Chinese";
864: }
865: };
866: }
867: return factory;
868: }
869: */
870: }
|