| /* GENERATED SOURCE. DO NOT MODIFY. */ |
| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /********************************************************************* |
| * Copyright (C) 2000-2014, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ********************************************************************* |
| */ |
| |
| package android.icu.util; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.util.Date; |
| import java.util.Locale; |
| |
| import android.icu.impl.CalendarAstronomer; |
| import android.icu.impl.CalendarCache; |
| import android.icu.text.DateFormat; |
| import android.icu.util.ULocale.Category; |
| |
| /** |
| * <code>ChineseCalendar</code> is a concrete subclass of {@link Calendar} |
| * that implements a traditional Chinese calendar. The traditional Chinese |
| * calendar is a lunisolar calendar: Each month starts on a new moon, and |
| * the months are numbered according to solar events, specifically, to |
| * guarantee that month 11 always contains the winter solstice. In order |
| * to accomplish this, leap months are inserted in certain years. Leap |
| * months are numbered the same as the month they follow. The decision of |
| * which month is a leap month depends on the relative movements of the sun |
| * and moon. |
| * |
| * <p>All astronomical computations are performed with respect to a time |
| * zone of GMT+8:00 and a longitude of 120 degrees east. Although some |
| * calendars implement a historically more accurate convention of using |
| * Beijing's local longitude (116 degrees 25 minutes east) and time zone |
| * (GMT+7:45:40) for dates before 1929, we do not implement this here. |
| * |
| * <p>Years are counted in two different ways in the Chinese calendar. The |
| * first method is by sequential numbering from the 61st year of the reign |
| * of Huang Di, 2637 BCE, which is designated year 1 on the Chinese |
| * calendar. The second method uses 60-year cycles from the same starting |
| * point, which is designated year 1 of cycle 1. In this class, the |
| * <code>EXTENDED_YEAR</code> field contains the sequential year count. |
| * The <code>ERA</code> field contains the cycle number, and the |
| * <code>YEAR</code> field contains the year of the cycle, a value between |
| * 1 and 60. |
| * |
| * <p>There is some variation in what is considered the starting point of |
| * the calendar, with some sources starting in the first year of the reign |
| * of Huang Di, rather than the 61st. This gives continuous year numbers |
| * 60 years greater and cycle numbers one greater than what this class |
| * implements. |
| * |
| * <p>Because <code>ChineseCalendar</code> defines an additional field and |
| * redefines the way the <code>ERA</code> field is used, it requires a new |
| * format class, <code>ChineseDateFormat</code>. As always, use the |
| * methods <code>DateFormat.getXxxInstance(Calendar cal,...)</code> to |
| * obtain a formatter for this calendar. |
| * |
| * <p>References:<ul> |
| * |
| * <li>Dershowitz and Reingold, <i>Calendrical Calculations</i>, |
| * Cambridge University Press, 1997</li> |
| * |
| * <li>The <a href="http://www.tondering.dk/claus/calendar.html"> |
| * Calendar FAQ</a></li> |
| * |
| * </ul> |
| * |
| * <p> |
| * This class should not be subclassed.</p> |
| * <p> |
| * ChineseCalendar usually should be instantiated using |
| * {@link android.icu.util.Calendar#getInstance(ULocale)} passing in a <code>ULocale</code> |
| * with the tag <code>"@calendar=chinese"</code>.</p> |
| * |
| * @see android.icu.util.Calendar |
| * @author Alan Liu |
| */ |
| public class ChineseCalendar extends Calendar { |
| // jdk1.4.2 serialver |
| private static final long serialVersionUID = 7312110751940929420L; |
| |
| //------------------------------------------------------------------ |
| // Developer Notes |
| // |
| // Time is represented as a scalar in two ways in this class. One is |
| // the usual UTC epoch millis, that is, milliseconds after January 1, |
| // 1970 Gregorian, 0:00:00.000 UTC. The other is in terms of 'local |
| // days.' This is the number of days after January 1, 1970 Gregorian, |
| // local to Beijing, China (since all computations of the Chinese |
| // calendar are done in Beijing). That is, 0 represents January 1, |
| // 1970 0:00 Asia/Shanghai. Conversion of local days to and from |
| // standard epoch milliseconds is accomplished by the daysToMillis() |
| // and millisToDays() methods. |
| // |
| // Several methods use caches to improve performance. Caches are at |
| // the object, not class level, under the assumption that typical |
| // usage will be to have one instance of ChineseCalendar at a time. |
| |
| /** |
| * The start year of this Chinese calendar instance. |
| */ |
| private int epochYear; |
| |
| /** |
| * The zone used for the astronomical calculation of this Chinese |
| * calendar instance. |
| */ |
| private TimeZone zoneAstro; |
| |
| /** |
| * Cache that maps Gregorian year to local days of winter solstice. |
| * @see #winterSolstice |
| */ |
| private transient CalendarCache winterSolsticeCache = new CalendarCache(); |
| |
| /** |
| * Cache that maps Gregorian year to local days of Chinese new year. |
| * @see #newYear |
| */ |
| private transient CalendarCache newYearCache = new CalendarCache(); |
| |
| /** |
| * True if there is a leap month between the Winter Solstice before and after the |
| * current date.This is different from leap year because in some year, such as |
| * 1813 and 2033, the leap month is after the Winter Solstice of that year. So |
| * this value could be false for a date prior to the Winter Solstice of that |
| * year but that year still has a leap month and therefor is a leap year. |
| * @see #computeChineseFields |
| */ |
| private transient boolean hasLeapMonthBetweenWinterSolstices; |
| |
| //------------------------------------------------------------------ |
| // Constructors |
| //------------------------------------------------------------------ |
| |
| /** |
| * Construct a <code>ChineseCalendar</code> with the default time zone and locale. |
| */ |
| public ChineseCalendar() { |
| this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT), CHINESE_EPOCH_YEAR, CHINA_ZONE); |
| } |
| |
| /** |
| * Construct a <code>ChineseCalendar</code> with the give date set in the default time zone |
| * with the default locale. |
| * @param date The date to which the new calendar is set. |
| */ |
| public ChineseCalendar(Date date) { |
| this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT), CHINESE_EPOCH_YEAR, CHINA_ZONE); |
| setTime(date); |
| } |
| |
| /** |
| * Constructs a <code>ChineseCalendar</code> with the given date set |
| * in the default time zone with the default <code>FORMAT</code> locale. |
| * |
| * @param year The value used to set the calendar's {@link #YEAR YEAR} time field. |
| * @param month The value used to set the calendar's {@link #MONTH MONTH} time field. |
| * The value is 0-based. e.g., 0 for January. |
| * @param isLeapMonth The value used to set the Chinese calendar's {@link #IS_LEAP_MONTH} |
| * time field. |
| * @param date The value used to set the calendar's {@link #DATE DATE} time field. |
| * @see Category#FORMAT |
| */ |
| public ChineseCalendar(int year, int month, int isLeapMonth, int date) { |
| this(year, month, isLeapMonth, date, 0, 0, 0); |
| } |
| |
| /** |
| * Constructs a <code>ChineseCalendar</code> with the given date |
| * and time set for the default time zone with the default <code>FORMAT</code> locale. |
| * |
| * @param year the value used to set the {@link #YEAR YEAR} time field in the calendar. |
| * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar. |
| * Note that the month value is 0-based. e.g., 0 for January. |
| * @param isLeapMonth the value used to set the {@link #IS_LEAP_MONTH} time field |
| * in the calendar. |
| * @param date the value used to set the {@link #DATE DATE} time field in the calendar. |
| * @param hour the value used to set the {@link #HOUR_OF_DAY HOUR_OF_DAY} time field |
| * in the calendar. |
| * @param minute the value used to set the {@link #MINUTE MINUTE} time field |
| * in the calendar. |
| * @param second the value used to set the {@link #SECOND SECOND} time field |
| * in the calendar. |
| * @see Category#FORMAT |
| */ |
| public ChineseCalendar(int year, int month, int isLeapMonth, int date, int hour, |
| int minute, int second) |
| { |
| this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT), CHINESE_EPOCH_YEAR, CHINA_ZONE); |
| |
| // The current time is set at this point, so ERA field is already |
| // set to the current era. |
| |
| // Then we need to clean up time fields |
| this.set(MILLISECOND, 0); |
| |
| // Then, set the given field values. |
| this.set(YEAR, year); |
| this.set(MONTH, month); |
| this.set(IS_LEAP_MONTH, isLeapMonth); |
| this.set(DATE, date); |
| this.set(HOUR_OF_DAY, hour); |
| this.set(MINUTE, minute); |
| this.set(SECOND, second); |
| } |
| |
| /** |
| * Constructs a <code>ChineseCalendar</code> with the given date set |
| * in the default time zone with the default <code>FORMAT</code> locale. |
| * |
| * @param era The value used to set the calendar's {@link #ERA ERA} time field. |
| * @param year The value used to set the calendar's {@link #YEAR YEAR} time field. |
| * @param month The value used to set the calendar's {@link #MONTH MONTH} time field. |
| * The value is 0-based. e.g., 0 for January. |
| * @param isLeapMonth The value used to set the Chinese calendar's {@link #IS_LEAP_MONTH} |
| * time field. |
| * @param date The value used to set the calendar's {@link #DATE DATE} time field. |
| * @see Category#FORMAT |
| */ |
| public ChineseCalendar(int era, int year, int month, int isLeapMonth, int date) |
| { |
| this(era, year, month, isLeapMonth, date, 0, 0, 0); |
| } |
| |
| /** |
| * Constructs a <code>ChineseCalendar</code> with the given date |
| * and time set for the default time zone with the default <code>FORMAT</code> locale. |
| * |
| * @param era the value used to set the calendar's {@link #ERA ERA} time field. |
| * @param year the value used to set the {@link #YEAR YEAR} time field in the calendar. |
| * @param month the value used to set the {@link #MONTH MONTH} time field in the calendar. |
| * Note that the month value is 0-based. e.g., 0 for January. |
| * @param isLeapMonth the value used to set the {@link #IS_LEAP_MONTH} time field |
| * in the calendar. |
| * @param date the value used to set the {@link #DATE DATE} time field in the calendar. |
| * @param hour the value used to set the {@link #HOUR_OF_DAY HOUR_OF_DAY} time field |
| * in the calendar. |
| * @param minute the value used to set the {@link #MINUTE MINUTE} time field |
| * in the calendar. |
| * @param second the value used to set the {@link #SECOND SECOND} time field |
| * in the calendar. |
| * @see Category#FORMAT |
| */ |
| public ChineseCalendar(int era, int year, int month, int isLeapMonth, int date, int hour, |
| int minute, int second) |
| { |
| this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT), CHINESE_EPOCH_YEAR, CHINA_ZONE); |
| |
| // Set 0 to millisecond field |
| this.set(MILLISECOND, 0); |
| |
| // Then, set the given field values. |
| this.set(ERA, era); |
| this.set(YEAR, year); |
| this.set(MONTH, month); |
| this.set(IS_LEAP_MONTH, isLeapMonth); |
| this.set(DATE, date); |
| this.set(HOUR_OF_DAY, hour); |
| this.set(MINUTE, minute); |
| this.set(SECOND, second); |
| } |
| |
| /** |
| * Constructs a <code>ChineseCalendar</code> based on the current time |
| * in the default time zone with the given locale. |
| * @param aLocale The given locale |
| */ |
| public ChineseCalendar(Locale aLocale) { |
| this(TimeZone.forLocaleOrDefault(aLocale), ULocale.forLocale(aLocale), CHINESE_EPOCH_YEAR, CHINA_ZONE); |
| } |
| |
| /** |
| * Construct a <code>ChineseCalendar</code> based on the current time |
| * in the given time zone with the default <code>FORMAT</code> locale. |
| * @param zone the given time zone |
| * @see Category#FORMAT |
| */ |
| public ChineseCalendar(TimeZone zone) { |
| this(zone, ULocale.getDefault(Category.FORMAT), CHINESE_EPOCH_YEAR, CHINA_ZONE); |
| } |
| |
| /** |
| * Construct a <code>ChineseCalendar</code> based on the current time |
| * in the given time zone with the given locale. |
| * @param zone the given time zone |
| * @param aLocale the given locale |
| */ |
| public ChineseCalendar(TimeZone zone, Locale aLocale) { |
| this(zone, ULocale.forLocale(aLocale), CHINESE_EPOCH_YEAR, CHINA_ZONE); |
| } |
| |
| /** |
| * Constructs a <code>ChineseCalendar</code> based on the current time |
| * in the default time zone with the given locale. |
| * |
| * @param locale the given ulocale |
| */ |
| public ChineseCalendar(ULocale locale) { |
| this(TimeZone.forULocaleOrDefault(locale), locale, CHINESE_EPOCH_YEAR, CHINA_ZONE); |
| } |
| |
| /** |
| * Construct a <code>ChineseCalendar</code> based on the current time |
| * with the given time zone with the given locale. |
| * @param zone the given time zone |
| * @param locale the given ulocale |
| */ |
| public ChineseCalendar(TimeZone zone, ULocale locale) { |
| this(zone, locale, CHINESE_EPOCH_YEAR, CHINA_ZONE); |
| } |
| |
| /** |
| * Construct a <code>ChineseCalenar</code> based on the current time |
| * with the given time zone, the locale, the epoch year and the time zone |
| * used for astronomical calculation. |
| * @deprecated This API is ICU internal only. |
| * @hide original deprecated declaration |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| @Deprecated |
| protected ChineseCalendar(TimeZone zone, ULocale locale, int epochYear, TimeZone zoneAstroCalc) { |
| super(zone, locale); |
| this.epochYear = epochYear; |
| this.zoneAstro = zoneAstroCalc; |
| setTimeInMillis(System.currentTimeMillis()); |
| } |
| |
| //------------------------------------------------------------------ |
| // Public constants |
| //------------------------------------------------------------------ |
| |
| /** |
| * Field indicating whether or not the current month is a leap month. |
| * Should have a value of 0 for non-leap months, and 1 for leap months. |
| * @stable ICU 2.8 |
| */ |
| // public static int IS_LEAP_MONTH = BASE_FIELD_COUNT; |
| |
| |
| //------------------------------------------------------------------ |
| // Calendar framework |
| //------------------------------------------------------------------ |
| |
| /** |
| * Array defining the limits of field values for this class. Field |
| * limits which are invariant with respect to calendar system and |
| * defined by Calendar are left blank. |
| * |
| * Notes: |
| * |
| * ERA 5000000 / 60 = 83333. |
| * |
| * MONTH There are 12 or 13 lunar months in a year. However, we always |
| * number them 0..11, with an intercalated, identically numbered leap |
| * month, when necessary. |
| * |
| * DAY_OF_YEAR In a non-leap year there are 353, 354, or 355 days. In |
| * a leap year there are 383, 384, or 385 days. |
| * |
| * WEEK_OF_YEAR The least maximum occurs if there are 353 days in the |
| * year, and the first 6 are the last week of the previous year. Then |
| * we have 49 full weeks and 4 days in the last week: 6 + 49*7 + 4 = |
| * 353. So the least maximum is 50. The maximum occurs if there are |
| * 385 days in the year, and WOY 1 extends 6 days into the prior year. |
| * Then there are 54 full weeks, and 6 days in the last week: 1 + 54*7 |
| * + 6 = 385. The 6 days of the last week will fall into WOY 1 of the |
| * next year. Maximum is 55. |
| * |
| * WEEK_OF_MONTH In a 29 day month, if the first 7 days make up week 1 |
| * that leaves 3 full weeks and 1 day at the end. The least maximum is |
| * thus 5. In a 30 days month, if the previous 6 days belong WOM 1 of |
| * this month, we have 4 full weeks and 1 days at the end (which |
| * technically will be WOM 1 of the next month, but will be reported by |
| * time->fields and hence by getActualMaximum as WOM 6 of this month). |
| * Maximum is 6. |
| * |
| * DAY_OF_WEEK_IN_MONTH In a 29 or 30 day month, there are 4 full weeks |
| * plus 1 or 2 days at the end, so the maximum is always 5. |
| */ |
| private static final int LIMITS[][] = { |
| // Minimum Greatest Least Maximum |
| // Minimum Maximum |
| { 1, 1, 83333, 83333 }, // ERA |
| { 1, 1, 60, 60 }, // YEAR |
| { 0, 0, 11, 11 }, // MONTH |
| { 1, 1, 50, 55 }, // WEEK_OF_YEAR |
| {/* */}, // WEEK_OF_MONTH |
| { 1, 1, 29, 30 }, // DAY_OF_MONTH |
| { 1, 1, 353, 385 }, // DAY_OF_YEAR |
| {/* */}, // DAY_OF_WEEK |
| { -1, -1, 5, 5 }, // DAY_OF_WEEK_IN_MONTH |
| {/* */}, // AM_PM |
| {/* */}, // HOUR |
| {/* */}, // HOUR_OF_DAY |
| {/* */}, // MINUTE |
| {/* */}, // SECOND |
| {/* */}, // MILLISECOND |
| {/* */}, // ZONE_OFFSET |
| {/* */}, // DST_OFFSET |
| { -5000000, -5000000, 5000000, 5000000 }, // YEAR_WOY |
| {/* */}, // DOW_LOCAL |
| { -5000000, -5000000, 5000000, 5000000 }, // EXTENDED_YEAR |
| {/* */}, // JULIAN_DAY |
| {/* */}, // MILLISECONDS_IN_DAY |
| { 0, 0, 1, 1 }, // IS_LEAP_MONTH |
| { 0, 0, 11, 12 }, // ORDINAL_MONTH |
| }; |
| |
| /** |
| * Override Calendar to return the limit value for the given field. |
| */ |
| protected int handleGetLimit(int field, int limitType) { |
| return LIMITS[field][limitType]; |
| } |
| |
| /** |
| * Implement abstract Calendar method to return the extended year |
| * defined by the current fields. This will use either the ERA and |
| * YEAR field as the cycle and year-of-cycle, or the EXTENDED_YEAR |
| * field as the continuous year count, depending on which is newer. |
| */ |
| protected int handleGetExtendedYear() { |
| int year; |
| if (newestStamp(ERA, YEAR, UNSET) <= getStamp(EXTENDED_YEAR)) { |
| year = internalGet(EXTENDED_YEAR, 1); // Default to year 1 |
| } else { |
| int cycle = internalGet(ERA, 1) - 1; // 0-based cycle |
| // adjust to the instance specific epoch |
| year = cycle * 60 + internalGet(YEAR, 1) - (epochYear - CHINESE_EPOCH_YEAR); |
| } |
| return year; |
| } |
| |
| /** |
| * Override Calendar method to return the number of days in the given |
| * extended year and month. |
| * |
| * <p>Note: This method also reads the IS_LEAP_MONTH field to determine |
| * whether or not the given month is a leap month. |
| */ |
| protected int handleGetMonthLength(int extendedYear, int month) { |
| int thisStart = handleComputeMonthStart(extendedYear, month, true) - |
| EPOCH_JULIAN_DAY + 1; // Julian day -> local days |
| int nextStart = newMoonNear(thisStart + SYNODIC_GAP, true); |
| return nextStart - thisStart; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| protected DateFormat handleGetDateFormat(String pattern, String override, ULocale locale) { |
| // Note: ICU 50 or later versions no longer use ChineseDateFormat. |
| // The super class's handleGetDateFormat will create an instance of |
| // SimpleDateFormat which supports Chinese calendar date formatting |
| // since ICU 49. |
| |
| //return new ChineseDateFormat(pattern, override, locale); |
| return super.handleGetDateFormat(pattern, override, locale); |
| } |
| |
| /** |
| * Field resolution table that incorporates IS_LEAP_MONTH. |
| */ |
| static final int[][][] CHINESE_DATE_PRECEDENCE = { |
| { |
| { DAY_OF_MONTH }, |
| { WEEK_OF_YEAR, DAY_OF_WEEK }, |
| { WEEK_OF_MONTH, DAY_OF_WEEK }, |
| { DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK }, |
| { WEEK_OF_YEAR, DOW_LOCAL }, |
| { WEEK_OF_MONTH, DOW_LOCAL }, |
| { DAY_OF_WEEK_IN_MONTH, DOW_LOCAL }, |
| { DAY_OF_YEAR }, |
| { RESOLVE_REMAP | DAY_OF_MONTH, IS_LEAP_MONTH }, |
| }, |
| { |
| { WEEK_OF_YEAR }, |
| { WEEK_OF_MONTH }, |
| { DAY_OF_WEEK_IN_MONTH }, |
| { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK }, |
| { RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DOW_LOCAL }, |
| }, |
| }; |
| |
| /** |
| * Override Calendar to add IS_LEAP_MONTH to the field resolution |
| * table. |
| */ |
| protected int[][][] getFieldResolutionTable() { |
| return CHINESE_DATE_PRECEDENCE; |
| } |
| |
| /** |
| * Adjust this calendar to be delta months before or after a given |
| * start position, pinning the day of month if necessary. The start |
| * position is given as a local days number for the start of the month |
| * and a day-of-month. Used by add() and roll(). |
| * @param newMoon the local days of the first day of the month of the |
| * start position (days after January 1, 1970 0:00 Asia/Shanghai) |
| * @param dom the 1-based day-of-month of the start position |
| * @param delta the number of months to move forward or backward from |
| * the start position |
| */ |
| private void offsetMonth(int newMoon, int dom, int delta) { |
| // Move to the middle of the month before our target month. |
| newMoon += (int) (CalendarAstronomer.SYNODIC_MONTH * (delta - 0.5)); |
| |
| // Search forward to the target month's new moon |
| newMoon = newMoonNear(newMoon, true); |
| |
| // Find the target dom |
| int jd = newMoon + EPOCH_JULIAN_DAY - 1 + dom; |
| |
| // Pin the dom. In this calendar all months are 29 or 30 days |
| // so pinning just means handling dom 30. |
| if (dom > 29) { |
| set(JULIAN_DAY, jd-1); |
| // TODO Fix this. We really shouldn't ever have to |
| // explicitly call complete(). This is either a bug in |
| // this method, in ChineseCalendar, or in |
| // Calendar.getActualMaximum(). I suspect the last. |
| complete(); |
| if (getActualMaximum(DAY_OF_MONTH) >= dom) { |
| set(JULIAN_DAY, jd); |
| } |
| } else { |
| set(JULIAN_DAY, jd); |
| } |
| } |
| |
| /** |
| * Override Calendar to handle leap months properly. |
| */ |
| public void add(int field, int amount) { |
| switch (field) { |
| case MONTH: |
| case ORDINAL_MONTH: |
| if (amount != 0) { |
| int dom = get(DAY_OF_MONTH); |
| int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day |
| int moon = day - dom + 1; // New moon |
| offsetMonth(moon, dom, amount); |
| } |
| break; |
| default: |
| super.add(field, amount); |
| break; |
| } |
| } |
| |
| /** |
| * Override Calendar to handle leap months properly. |
| */ |
| public void roll(int field, int amount) { |
| switch (field) { |
| case MONTH: |
| case ORDINAL_MONTH: |
| if (amount != 0) { |
| int dom = get(DAY_OF_MONTH); |
| int day = get(JULIAN_DAY) - EPOCH_JULIAN_DAY; // Get local day |
| int moon = day - dom + 1; // New moon (start of this month) |
| |
| // Note throughout the following: Months 12 and 1 are never |
| // followed by a leap month (D&R p. 185). |
| |
| // Compute the adjusted month number m. This is zero-based |
| // value from 0..11 in a non-leap year, and from 0..12 in a |
| // leap year. |
| int m = get(MONTH); // 0-based month |
| if (hasLeapMonthBetweenWinterSolstices) { // (member variable) |
| if (get(IS_LEAP_MONTH) == 1) { |
| ++m; |
| } else { |
| // Check for a prior leap month. (In the |
| // following, month 0 is the first month of the |
| // year.) Month 0 is never followed by a leap |
| // month, and we know month m is not a leap month. |
| // moon1 will be the start of month 0 if there is |
| // no leap month between month 0 and month m; |
| // otherwise it will be the start of month 1. |
| int moon1 = moon - |
| (int) (CalendarAstronomer.SYNODIC_MONTH * (m - 0.5)); |
| moon1 = newMoonNear(moon1, true); |
| if (isLeapMonthBetween(moon1, moon)) { |
| ++m; |
| } |
| } |
| } |
| |
| // Now do the standard roll computation on m, with the |
| // allowed range of 0..n-1, where n is 12 or 13. |
| int n = hasLeapMonthBetweenWinterSolstices ? 13 : 12; // Months in this year |
| int newM = (m + amount) % n; |
| if (newM < 0) { |
| newM += n; |
| } |
| |
| if (newM != m) { |
| offsetMonth(moon, dom, newM - m); |
| } |
| } |
| break; |
| default: |
| super.roll(field, amount); |
| break; |
| } |
| } |
| |
| //------------------------------------------------------------------ |
| // Support methods and constants |
| //------------------------------------------------------------------ |
| |
| /** |
| * The start year of the Chinese calendar, the 61st year of the reign |
| * of Huang Di. Some sources use the first year of his reign, |
| * resulting in EXTENDED_YEAR values 60 years greater and ERA (cycle) |
| * values one greater. |
| */ |
| private static final int CHINESE_EPOCH_YEAR = -2636; // Gregorian year |
| |
| /** |
| * The time zone used for performing astronomical computations. |
| * Some sources use a different historically accurate |
| * offset of GMT+7:45:40 for years before 1929; we do not do this. |
| */ |
| private static final TimeZone CHINA_ZONE = new SimpleTimeZone(8 * ONE_HOUR, "CHINA_ZONE").freeze(); |
| |
| /** |
| * Value to be added or subtracted from the local days of a new moon to |
| * get close to the next or prior new moon, but not cross it. Must be |
| * >= 1 and < CalendarAstronomer.SYNODIC_MONTH. |
| */ |
| private static final int SYNODIC_GAP = 25; |
| |
| /** |
| * Convert local days to UTC epoch milliseconds. |
| * This is not an accurate conversion in terms that getTimezoneOffset |
| * takes the milliseconds in GMT (not local time). In theory, more |
| * accurate algorithm can be implemented but practically we do not need |
| * to go through that complication as long as the historically timezone |
| * changes did not happen around the 'tricky' new moon (new moon around |
| * the midnight). |
| * |
| * @param days days after January 1, 1970 0:00 in the astronomical base zone |
| * @return milliseconds after January 1, 1970 0:00 GMT |
| */ |
| private final long daysToMillis(int days) { |
| long millis = days * ONE_DAY; |
| return millis - zoneAstro.getOffset(millis); |
| } |
| |
| /** |
| * Convert UTC epoch milliseconds to local days. |
| * @param millis milliseconds after January 1, 1970 0:00 GMT |
| * @return days days after January 1, 1970 0:00 in the astronomical base zone |
| */ |
| private final int millisToDays(long millis) { |
| return (int) floorDivide(millis + zoneAstro.getOffset(millis), ONE_DAY); |
| } |
| |
| //------------------------------------------------------------------ |
| // Astronomical computations |
| //------------------------------------------------------------------ |
| |
| /** |
| * Return the major solar term on or after December 15 of the given |
| * Gregorian year, that is, the winter solstice of the given year. |
| * Computations are relative to Asia/Shanghai time zone. |
| * @param gyear a Gregorian year |
| * @return days after January 1, 1970 0:00 Asia/Shanghai of the |
| * winter solstice of the given year |
| */ |
| private int winterSolstice(int gyear) { |
| |
| long cacheValue = winterSolsticeCache.get(gyear); |
| |
| if (cacheValue == CalendarCache.EMPTY) { |
| // In books December 15 is used, but it fails for some years |
| // using our algorithms, e.g.: 1298 1391 1492 1553 1560. That |
| // is, winterSolstice(1298) starts search at Dec 14 08:00:00 |
| // PST 1298 with a final result of Dec 14 10:31:59 PST 1299. |
| long ms = daysToMillis(computeGregorianMonthStart(gyear, DECEMBER) + |
| 1 - EPOCH_JULIAN_DAY); |
| |
| // Winter solstice is 270 degrees solar longitude aka Dongzhi |
| long solarLong = (new CalendarAstronomer(ms)).getSunTime(CalendarAstronomer.WINTER_SOLSTICE, |
| true); |
| cacheValue = millisToDays(solarLong); |
| winterSolsticeCache.put(gyear, cacheValue); |
| } |
| return (int) cacheValue; |
| } |
| |
| /** |
| * Return the closest new moon to the given date, searching either |
| * forward or backward in time. |
| * @param days days after January 1, 1970 0:00 Asia/Shanghai |
| * @param after if true, search for a new moon on or after the given |
| * date; otherwise, search for a new moon before it |
| * @return days after January 1, 1970 0:00 Asia/Shanghai of the nearest |
| * new moon after or before <code>days</code> |
| */ |
| private int newMoonNear(int days, boolean after) { |
| long newMoon = (new CalendarAstronomer(daysToMillis(days))).getMoonTime(CalendarAstronomer.NEW_MOON, after); |
| |
| return millisToDays(newMoon); |
| } |
| |
| /** |
| * Return the nearest integer number of synodic months between |
| * two dates. |
| * @param day1 days after January 1, 1970 0:00 Asia/Shanghai |
| * @param day2 days after January 1, 1970 0:00 Asia/Shanghai |
| * @return the nearest integer number of months between day1 and day2 |
| */ |
| private int synodicMonthsBetween(int day1, int day2) { |
| return (int) Math.round((day2 - day1) / CalendarAstronomer.SYNODIC_MONTH); |
| } |
| |
| /** |
| * Return the major solar term on or before a given date. This |
| * will be an integer from 1..12, with 1 corresponding to 330 degrees, |
| * 2 to 0 degrees, 3 to 30 degrees,..., and 12 to 300 degrees. |
| * @param days days after January 1, 1970 0:00 Asia/Shanghai |
| */ |
| private int majorSolarTerm(int days) { |
| // Compute (floor(solarLongitude / (pi/6)) + 2) % 12 |
| int term = ((int) Math.floor(6 * (new CalendarAstronomer(daysToMillis(days))).getSunLongitude() / Math.PI) + 2) % 12; |
| if (term < 1) { |
| term += 12; |
| } |
| return term; |
| } |
| |
| /** |
| * Return true if the given month lacks a major solar term. |
| * @param newMoon days after January 1, 1970 0:00 Asia/Shanghai of a new |
| * moon |
| */ |
| private boolean hasNoMajorSolarTerm(int newMoon) { |
| |
| int mst = majorSolarTerm(newMoon); |
| int nmn = newMoonNear(newMoon + SYNODIC_GAP, true); |
| int mstt = majorSolarTerm(nmn); |
| return mst == mstt; |
| /* |
| return majorSolarTerm(newMoon) == |
| majorSolarTerm(newMoonNear(newMoon + SYNODIC_GAP, true)); |
| */ |
| } |
| |
| //------------------------------------------------------------------ |
| // Time to fields |
| //------------------------------------------------------------------ |
| |
| /** |
| * Return true if there is a leap month on or after month newMoon1 and |
| * at or before month newMoon2. |
| * @param newMoon1 days after January 1, 1970 0:00 astronomical base zone of a |
| * new moon |
| * @param newMoon2 days after January 1, 1970 0:00 astronomical base zone of a |
| * new moon |
| */ |
| private boolean isLeapMonthBetween(int newMoon1, int newMoon2) { |
| |
| // This is only needed to debug the timeOfAngle divergence bug. |
| // Remove this later. Liu 11/9/00 |
| // DEBUG |
| if (synodicMonthsBetween(newMoon1, newMoon2) >= 50) { |
| throw new IllegalArgumentException("isLeapMonthBetween(" + newMoon1 + |
| ", " + newMoon2 + |
| "): Invalid parameters"); |
| } |
| |
| return (newMoon2 >= newMoon1) && |
| (isLeapMonthBetween(newMoon1, newMoonNear(newMoon2 - SYNODIC_GAP, false)) || |
| hasNoMajorSolarTerm(newMoon2)); |
| } |
| |
| /** |
| * Override Calendar to compute several fields specific to the Chinese |
| * calendar system. These are: |
| * |
| * <ul><li>ERA |
| * <li>YEAR |
| * <li>MONTH |
| * <li>DAY_OF_MONTH |
| * <li>DAY_OF_YEAR |
| * <li>EXTENDED_YEAR</ul> |
| * |
| * The DAY_OF_WEEK and DOW_LOCAL fields are already set when this |
| * method is called. The getGregorianXxx() methods return Gregorian |
| * calendar equivalents for the given Julian day. |
| * |
| * <p>Compute the ChineseCalendar-specific field IS_LEAP_MONTH. |
| */ |
| protected void handleComputeFields(int julianDay) { |
| |
| computeChineseFields(julianDay - EPOCH_JULIAN_DAY, // local days |
| getGregorianYear(), getGregorianMonth(), |
| true); // set all fields |
| } |
| |
| /** |
| * Compute fields for the Chinese calendar system. This method can |
| * either set all relevant fields, as required by |
| * <code>handleComputeFields()</code>, or it can just set the MONTH and |
| * IS_LEAP_MONTH fields, as required by |
| * <code>handleComputeMonthStart()</code>. |
| * |
| * <p>As a side effect, this method sets {@link #hasLeapMonthBetweenWinterSolstices}. |
| * @param days days after January 1, 1970 0:00 astronomical base zone of the |
| * date to compute fields for |
| * @param gyear the Gregorian year of the given date |
| * @param gmonth the Gregorian month of the given date |
| * @param setAllFields if true, set the EXTENDED_YEAR, ERA, YEAR, |
| * DAY_OF_MONTH, and DAY_OF_YEAR fields. In either case set the MONTH |
| * and IS_LEAP_MONTH fields. |
| */ |
| private void computeChineseFields(int days, int gyear, int gmonth, |
| boolean setAllFields) { |
| |
| // Find the winter solstices before and after the target date. |
| // These define the boundaries of this Chinese year, specifically, |
| // the position of month 11, which always contains the solstice. |
| // We want solsticeBefore <= date < solsticeAfter. |
| int solsticeBefore; |
| int solsticeAfter = winterSolstice(gyear); |
| if (days < solsticeAfter) { |
| solsticeBefore = winterSolstice(gyear - 1); |
| } else { |
| solsticeBefore = solsticeAfter; |
| solsticeAfter = winterSolstice(gyear + 1); |
| } |
| |
| // Find the start of the month after month 11. This will be either |
| // the prior month 12 or leap month 11 (very rare). Also find the |
| // start of the following month 11. |
| int firstMoon = newMoonNear(solsticeBefore + 1, true); |
| int lastMoon = newMoonNear(solsticeAfter + 1, false); |
| int thisMoon = newMoonNear(days + 1, false); // Start of this month |
| // Note: hasLeapMonthBetweenWinterSolstices is a member variable |
| hasLeapMonthBetweenWinterSolstices = synodicMonthsBetween(firstMoon, lastMoon) == 12; |
| |
| int month = synodicMonthsBetween(firstMoon, thisMoon); |
| int theNewYear = newYear(gyear); |
| if (days < theNewYear) { |
| theNewYear = newYear(gyear-1); |
| } |
| if (hasLeapMonthBetweenWinterSolstices && isLeapMonthBetween(firstMoon, thisMoon)) { |
| month--; |
| } |
| if (month < 1) { |
| month += 12; |
| } |
| int ordinalMonth = synodicMonthsBetween(theNewYear, thisMoon); |
| if (ordinalMonth < 0) { |
| ordinalMonth += 12; |
| } |
| |
| boolean isLeapMonth = hasLeapMonthBetweenWinterSolstices && |
| hasNoMajorSolarTerm(thisMoon) && |
| !isLeapMonthBetween(firstMoon, newMoonNear(thisMoon - SYNODIC_GAP, false)); |
| |
| internalSet(MONTH, month-1); // Convert from 1-based to 0-based |
| internalSet(ORDINAL_MONTH, ordinalMonth); |
| internalSet(IS_LEAP_MONTH, isLeapMonth?1:0); |
| |
| if (setAllFields) { |
| |
| // Extended year and cycle year is based on the epoch year |
| int extended_year = gyear - epochYear; |
| int cycle_year = gyear - CHINESE_EPOCH_YEAR; |
| if (month < 11 || |
| gmonth >= JULY) { |
| extended_year++; |
| cycle_year++; |
| } |
| int dayOfMonth = days - thisMoon + 1; |
| |
| internalSet(EXTENDED_YEAR, extended_year); |
| |
| // 0->0,60 1->1,1 60->1,60 61->2,1 etc. |
| int[] yearOfCycle = new int[1]; |
| int cycle = floorDivide(cycle_year-1, 60, yearOfCycle); |
| internalSet(ERA, cycle+1); |
| internalSet(YEAR, yearOfCycle[0]+1); |
| |
| internalSet(DAY_OF_MONTH, dayOfMonth); |
| |
| // Days will be before the first new year we compute if this |
| // date is in month 11, leap 11, 12. There is never a leap 12. |
| // New year computations are cached so this should be cheap in |
| // the long run. |
| int newYear = newYear(gyear); |
| if (days < newYear) { |
| newYear = newYear(gyear-1); |
| } |
| internalSet(DAY_OF_YEAR, days - newYear + 1); |
| } |
| } |
| |
| //------------------------------------------------------------------ |
| // Fields to time |
| //------------------------------------------------------------------ |
| |
| /** |
| * Return the Chinese new year of the given Gregorian year. |
| * @param gyear a Gregorian year |
| * @return days after January 1, 1970 0:00 astronomical base zone of the |
| * Chinese new year of the given year (this will be a new moon) |
| */ |
| private int newYear(int gyear) { |
| |
| long cacheValue = newYearCache.get(gyear); |
| |
| if (cacheValue == CalendarCache.EMPTY) { |
| |
| int solsticeBefore= winterSolstice(gyear - 1); |
| int solsticeAfter = winterSolstice(gyear); |
| int newMoon1 = newMoonNear(solsticeBefore + 1, true); |
| int newMoon2 = newMoonNear(newMoon1 + SYNODIC_GAP, true); |
| int newMoon11 = newMoonNear(solsticeAfter + 1, false); |
| |
| if (synodicMonthsBetween(newMoon1, newMoon11) == 12 && |
| (hasNoMajorSolarTerm(newMoon1) || hasNoMajorSolarTerm(newMoon2))) { |
| cacheValue = newMoonNear(newMoon2 + SYNODIC_GAP, true); |
| } else { |
| cacheValue = newMoon2; |
| } |
| |
| newYearCache.put(gyear, cacheValue); |
| } |
| return (int) cacheValue; |
| } |
| |
| /** |
| * Return the Julian day number of day before the first day of the |
| * given month in the given extended year. |
| * |
| * <p>Note: This method reads the IS_LEAP_MONTH field to determine |
| * whether the given month is a leap month. |
| * @param eyear the extended year |
| * @param month the zero-based month. The month is also determined |
| * by reading the IS_LEAP_MONTH field. |
| * @return the Julian day number of the day before the first |
| * day of the given month and year |
| */ |
| protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) { |
| |
| // If the month is out of range, adjust it into range, and |
| // modify the extended year value accordingly. |
| if (month < 0 || month > 11) { |
| int[] rem = new int[1]; |
| eyear += floorDivide(month, 12, rem); |
| month = rem[0]; |
| } |
| |
| int gyear = eyear + epochYear - 1; // Gregorian year |
| int newYear = newYear(gyear); |
| int newMoon = newMoonNear(newYear + month * 29, true); |
| |
| int julianDay = newMoon + EPOCH_JULIAN_DAY; |
| |
| // Save fields for later restoration |
| int saveMonth = internalGet(MONTH); |
| int saveOrdinalMonth = internalGet(ORDINAL_MONTH); |
| int saveIsLeapMonth = internalGet(IS_LEAP_MONTH); |
| |
| // Ignore IS_LEAP_MONTH field if useMonth is false |
| int isLeapMonth = useMonth ? saveIsLeapMonth : 0; |
| |
| computeGregorianFields(julianDay); |
| |
| // This will modify the MONTH and IS_LEAP_MONTH fields (only) |
| computeChineseFields(newMoon, getGregorianYear(), |
| getGregorianMonth(), false); |
| |
| if (month != internalGet(MONTH) || |
| isLeapMonth != internalGet(IS_LEAP_MONTH)) { |
| newMoon = newMoonNear(newMoon + SYNODIC_GAP, true); |
| julianDay = newMoon + EPOCH_JULIAN_DAY; |
| } |
| |
| internalSet(MONTH, saveMonth); |
| internalSet(ORDINAL_MONTH, saveOrdinalMonth); |
| internalSet(IS_LEAP_MONTH, saveIsLeapMonth); |
| |
| return julianDay - 1; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getType() { |
| return "chinese"; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @deprecated This API is ICU internal only. |
| * @hide original deprecated declaration |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| @Deprecated |
| public boolean haveDefaultCentury() { |
| return false; |
| } |
| |
| /** |
| * Override readObject. |
| */ |
| private void readObject(ObjectInputStream stream) |
| throws IOException, ClassNotFoundException |
| { |
| epochYear = CHINESE_EPOCH_YEAR; |
| zoneAstro = CHINA_ZONE; |
| |
| stream.defaultReadObject(); |
| |
| /* set up the transient caches... */ |
| winterSolsticeCache = new CalendarCache(); |
| newYearCache = new CalendarCache(); |
| } |
| |
| //------------------------------------------------------------------------- |
| // Temporal Calendar API. |
| //------------------------------------------------------------------------- |
| /** |
| * <strong>[icu]</strong> Returns true if the date is in a leap year. Recalculate the current time |
| * field values if the time value has been changed by a call to setTime(). |
| * This method is semantically const, but may alter the object in memory. |
| * A "leap year" is a year that contains more days than other years (for |
| * solar or lunar calendars) or more months than other years (for lunisolar |
| * calendars like Hebrew or Chinese), as defined in the ECMAScript Temporal |
| * proposal. |
| * @return true if the date in the fields is in a Temporal proposal |
| * defined leap year. False otherwise. |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| public boolean inTemporalLeapYear() { |
| return getActualMaximum(DAY_OF_YEAR) > 360; |
| } |
| |
| private static String [] gTemporalLeapMonthCodes = { |
| "M01L", "M02L", "M03L", "M04L", "M05L", "M06L", "M07L", "M08L", "M09L", "M10L", "M11L", "M12L" |
| }; |
| |
| /** |
| * Gets The Temporal monthCode value corresponding to the month for the date. |
| * The value is a string identifier that starts with the literal grapheme |
| * "M" followed by two graphemes representing the zero-padded month number |
| * of the current month in a normal (non-leap) year and suffixed by an |
| * optional literal grapheme "L" if this is a leap month in a lunisolar |
| * calendar. For the Chinese calendar, the values are "M01" .. "M12" for |
| * non-leap year and * in leap year with another monthCode in "M01L" .. "M12L". |
| * |
| * @return One of 24 possible strings in {"M01".."M12", "M01L".."M12L"}. |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| public String getTemporalMonthCode() { |
| // We need to call get, not internalGet, to force the calculation |
| // from ORDINAL_MONTH. |
| int is_leap = get(IS_LEAP_MONTH); |
| if (is_leap != 0) { |
| return gTemporalLeapMonthCodes[get(MONTH)]; |
| } |
| return super.getTemporalMonthCode(); |
| } |
| |
| /** |
| * Sets The Temporal monthCode which is a string identifier that starts |
| * with the literal grapheme "M" followed by two graphemes representing |
| * the zero-padded month number of the current month in a normal |
| * (non-leap) year and suffixed by an optional literal grapheme "L" if this |
| * is a leap month in a lunisolar calendar. |
| * For the Chinese calendar, the values are "M01" .. "M12" for non-leap year and |
| * in leap year with another monthCode in "M01L" .. "M12L". |
| * @param temporalMonth One of 25 possible strings in {"M01".. "M12", "M13", "M01L", |
| * "M12L"}. |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| public void setTemporalMonthCode( String temporalMonth ) { |
| if (temporalMonth.length() != 4 || temporalMonth.charAt(0) != 'M' || temporalMonth.charAt(3) != 'L') { |
| set(IS_LEAP_MONTH, 0); |
| super.setTemporalMonthCode(temporalMonth); |
| return; |
| } |
| for (int m = 0; m < gTemporalLeapMonthCodes.length; m++) { |
| if (temporalMonth.equals(gTemporalLeapMonthCodes[m])) { |
| set(MONTH, m); |
| set(IS_LEAP_MONTH, 1); |
| return; |
| } |
| } |
| throw new IllegalArgumentException("Incorrect temporal Month code: " + temporalMonth); |
| } |
| |
| //------------------------------------------------------------------------- |
| // End of Temporal Calendar API |
| //------------------------------------------------------------------------- |
| |
| /** |
| * {@inheritDoc} |
| * @deprecated This API is ICU internal only. |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| @Deprecated |
| protected int internalGetMonth() |
| { |
| if (resolveFields(MONTH_PRECEDENCE) == MONTH) { |
| return internalGet(MONTH); |
| } |
| Calendar temp = (Calendar) clone(); |
| temp.set(Calendar.MONTH, 0); |
| temp.set(Calendar.IS_LEAP_MONTH, 0); |
| temp.set(Calendar.DATE, 1); |
| // Calculate the MONTH and IS_LEAP_MONTH by adding number of months. |
| temp.roll(Calendar.MONTH, internalGet(Calendar.ORDINAL_MONTH)); |
| internalSet(Calendar.IS_LEAP_MONTH, temp.get(Calendar.IS_LEAP_MONTH)); |
| int month = temp.get(Calendar.MONTH); |
| internalSet(Calendar.MONTH, month); |
| return month; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @deprecated This API is ICU internal only. |
| * @hide draft / provisional / internal are hidden on Android |
| */ |
| @Deprecated |
| protected int internalGetMonth(int defaultValue) |
| { |
| if (resolveFields(MONTH_PRECEDENCE) == MONTH) { |
| return internalGet(MONTH, defaultValue); |
| } |
| return internalGetMonth(); |
| } |
| |
| /* |
| private static CalendarFactory factory; |
| public static CalendarFactory factory() { |
| if (factory == null) { |
| factory = new CalendarFactory() { |
| public Calendar create(TimeZone tz, ULocale loc) { |
| return new ChineseCalendar(tz, loc); |
| } |
| |
| public String factoryName() { |
| return "Chinese"; |
| } |
| }; |
| } |
| return factory; |
| } |
| */ |
| } |