diff -r 836adbf7a2cd -r 3317bb8137f4 jdk/src/java.base/share/classes/java/util/JapaneseImperialCalendar.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.base/share/classes/java/util/JapaneseImperialCalendar.java Sun Aug 17 15:54:13 2014 +0100 @@ -0,0 +1,2354 @@ +/* + * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import sun.util.locale.provider.CalendarDataUtility; +import sun.util.calendar.BaseCalendar; +import sun.util.calendar.CalendarDate; +import sun.util.calendar.CalendarSystem; +import sun.util.calendar.CalendarUtils; +import sun.util.calendar.Era; +import sun.util.calendar.Gregorian; +import sun.util.calendar.LocalGregorianCalendar; +import sun.util.calendar.ZoneInfo; + +/** + * JapaneseImperialCalendar implements a Japanese + * calendar system in which the imperial era-based year numbering is + * supported from the Meiji era. The following are the eras supported + * by this calendar system. + *

+ * ERA value   Era name    Since (in Gregorian)
+ * ------------------------------------------------------
+ *     0       N/A         N/A
+ *     1       Meiji       1868-01-01 midnight local time
+ *     2       Taisho      1912-07-30 midnight local time
+ *     3       Showa       1926-12-25 midnight local time
+ *     4       Heisei      1989-01-08 midnight local time
+ * ------------------------------------------------------
+ * 
+ * + *

ERA value 0 specifies the years before Meiji and + * the Gregorian year values are used. Unlike {@link + * GregorianCalendar}, the Julian to Gregorian transition is not + * supported because it doesn't make any sense to the Japanese + * calendar systems used before Meiji. To represent the years before + * Gregorian year 1, 0 and negative values are used. The Japanese + * Imperial rescripts and government decrees don't specify how to deal + * with time differences for applying the era transitions. This + * calendar implementation assumes local time for all transitions. + * + * @author Masayoshi Okutsu + * @since 1.6 + */ +class JapaneseImperialCalendar extends Calendar { + /* + * Implementation Notes + * + * This implementation uses + * sun.util.calendar.LocalGregorianCalendar to perform most of the + * calendar calculations. LocalGregorianCalendar is configurable + * and reads /lib/calendars.properties at the start-up. + */ + + /** + * The ERA constant designating the era before Meiji. + */ + public static final int BEFORE_MEIJI = 0; + + /** + * The ERA constant designating the Meiji era. + */ + public static final int MEIJI = 1; + + /** + * The ERA constant designating the Taisho era. + */ + public static final int TAISHO = 2; + + /** + * The ERA constant designating the Showa era. + */ + public static final int SHOWA = 3; + + /** + * The ERA constant designating the Heisei era. + */ + public static final int HEISEI = 4; + + private static final int EPOCH_OFFSET = 719163; // Fixed date of January 1, 1970 (Gregorian) + private static final int EPOCH_YEAR = 1970; + + // Useful millisecond constants. Although ONE_DAY and ONE_WEEK can fit + // into ints, they must be longs in order to prevent arithmetic overflow + // when performing (bug 4173516). + private static final int ONE_SECOND = 1000; + private static final int ONE_MINUTE = 60*ONE_SECOND; + private static final int ONE_HOUR = 60*ONE_MINUTE; + private static final long ONE_DAY = 24*ONE_HOUR; + private static final long ONE_WEEK = 7*ONE_DAY; + + // Reference to the sun.util.calendar.LocalGregorianCalendar instance (singleton). + private static final LocalGregorianCalendar jcal + = (LocalGregorianCalendar) CalendarSystem.forName("japanese"); + + // Gregorian calendar instance. This is required because era + // transition dates are given in Gregorian dates. + private static final Gregorian gcal = CalendarSystem.getGregorianCalendar(); + + // The Era instance representing "before Meiji". + private static final Era BEFORE_MEIJI_ERA = new Era("BeforeMeiji", "BM", Long.MIN_VALUE, false); + + // Imperial eras. The sun.util.calendar.LocalGregorianCalendar + // doesn't have an Era representing before Meiji, which is + // inconvenient for a Calendar. So, era[0] is a reference to + // BEFORE_MEIJI_ERA. + private static final Era[] eras; + + // Fixed date of the first date of each era. + private static final long[] sinceFixedDates; + + /* + *

+     *                                 Greatest       Least
+     * Field name             Minimum   Minimum     Maximum     Maximum
+     * ----------             -------   -------     -------     -------
+     * ERA                          0         0           1           1
+     * YEAR                -292275055         1           ?           ?
+     * MONTH                        0         0          11          11
+     * WEEK_OF_YEAR                 1         1          52*         53
+     * WEEK_OF_MONTH                0         0           4*          6
+     * DAY_OF_MONTH                 1         1          28*         31
+     * DAY_OF_YEAR                  1         1         365*        366
+     * DAY_OF_WEEK                  1         1           7           7
+     * DAY_OF_WEEK_IN_MONTH        -1        -1           4*          6
+     * AM_PM                        0         0           1           1
+     * HOUR                         0         0          11          11
+     * HOUR_OF_DAY                  0         0          23          23
+     * MINUTE                       0         0          59          59
+     * SECOND                       0         0          59          59
+     * MILLISECOND                  0         0         999         999
+     * ZONE_OFFSET             -13:00    -13:00       14:00       14:00
+     * DST_OFFSET                0:00      0:00        0:20        2:00
+     * 
+ * *: depends on eras + */ + static final int MIN_VALUES[] = { + 0, // ERA + -292275055, // YEAR + JANUARY, // MONTH + 1, // WEEK_OF_YEAR + 0, // WEEK_OF_MONTH + 1, // DAY_OF_MONTH + 1, // DAY_OF_YEAR + SUNDAY, // DAY_OF_WEEK + 1, // DAY_OF_WEEK_IN_MONTH + AM, // AM_PM + 0, // HOUR + 0, // HOUR_OF_DAY + 0, // MINUTE + 0, // SECOND + 0, // MILLISECOND + -13*ONE_HOUR, // ZONE_OFFSET (UNIX compatibility) + 0 // DST_OFFSET + }; + static final int LEAST_MAX_VALUES[] = { + 0, // ERA (initialized later) + 0, // YEAR (initialized later) + JANUARY, // MONTH (Showa 64 ended in January.) + 0, // WEEK_OF_YEAR (Showa 1 has only 6 days which could be 0 weeks.) + 4, // WEEK_OF_MONTH + 28, // DAY_OF_MONTH + 0, // DAY_OF_YEAR (initialized later) + SATURDAY, // DAY_OF_WEEK + 4, // DAY_OF_WEEK_IN + PM, // AM_PM + 11, // HOUR + 23, // HOUR_OF_DAY + 59, // MINUTE + 59, // SECOND + 999, // MILLISECOND + 14*ONE_HOUR, // ZONE_OFFSET + 20*ONE_MINUTE // DST_OFFSET (historical least maximum) + }; + static final int MAX_VALUES[] = { + 0, // ERA + 292278994, // YEAR + DECEMBER, // MONTH + 53, // WEEK_OF_YEAR + 6, // WEEK_OF_MONTH + 31, // DAY_OF_MONTH + 366, // DAY_OF_YEAR + SATURDAY, // DAY_OF_WEEK + 6, // DAY_OF_WEEK_IN + PM, // AM_PM + 11, // HOUR + 23, // HOUR_OF_DAY + 59, // MINUTE + 59, // SECOND + 999, // MILLISECOND + 14*ONE_HOUR, // ZONE_OFFSET + 2*ONE_HOUR // DST_OFFSET (double summer time) + }; + + // Proclaim serialization compatibility with JDK 1.6 + private static final long serialVersionUID = -3364572813905467929L; + + static { + Era[] es = jcal.getEras(); + int length = es.length + 1; + eras = new Era[length]; + sinceFixedDates = new long[length]; + + // eras[BEFORE_MEIJI] and sinceFixedDate[BEFORE_MEIJI] are the + // same as Gregorian. + int index = BEFORE_MEIJI; + sinceFixedDates[index] = gcal.getFixedDate(BEFORE_MEIJI_ERA.getSinceDate()); + eras[index++] = BEFORE_MEIJI_ERA; + for (Era e : es) { + CalendarDate d = e.getSinceDate(); + sinceFixedDates[index] = gcal.getFixedDate(d); + eras[index++] = e; + } + + LEAST_MAX_VALUES[ERA] = MAX_VALUES[ERA] = eras.length - 1; + + // Calculate the least maximum year and least day of Year + // values. The following code assumes that there's at most one + // era transition in a Gregorian year. + int year = Integer.MAX_VALUE; + int dayOfYear = Integer.MAX_VALUE; + CalendarDate date = gcal.newCalendarDate(TimeZone.NO_TIMEZONE); + for (int i = 1; i < eras.length; i++) { + long fd = sinceFixedDates[i]; + CalendarDate transitionDate = eras[i].getSinceDate(); + date.setDate(transitionDate.getYear(), BaseCalendar.JANUARY, 1); + long fdd = gcal.getFixedDate(date); + if (fd != fdd) { + dayOfYear = Math.min((int)(fd - fdd) + 1, dayOfYear); + } + date.setDate(transitionDate.getYear(), BaseCalendar.DECEMBER, 31); + fdd = gcal.getFixedDate(date); + if (fd != fdd) { + dayOfYear = Math.min((int)(fdd - fd) + 1, dayOfYear); + } + LocalGregorianCalendar.Date lgd = getCalendarDate(fd - 1); + int y = lgd.getYear(); + // Unless the first year starts from January 1, the actual + // max value could be one year short. For example, if it's + // Showa 63 January 8, 63 is the actual max value since + // Showa 64 January 8 doesn't exist. + if (!(lgd.getMonth() == BaseCalendar.JANUARY && lgd.getDayOfMonth() == 1)) { + y--; + } + year = Math.min(y, year); + } + LEAST_MAX_VALUES[YEAR] = year; // Max year could be smaller than this value. + LEAST_MAX_VALUES[DAY_OF_YEAR] = dayOfYear; + } + + /** + * jdate always has a sun.util.calendar.LocalGregorianCalendar.Date instance to + * avoid overhead of creating it for each calculation. + */ + private transient LocalGregorianCalendar.Date jdate; + + /** + * Temporary int[2] to get time zone offsets. zoneOffsets[0] gets + * the GMT offset value and zoneOffsets[1] gets the daylight saving + * value. + */ + private transient int[] zoneOffsets; + + /** + * Temporary storage for saving original fields[] values in + * non-lenient mode. + */ + private transient int[] originalFields; + + /** + * Constructs a JapaneseImperialCalendar 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. + */ + JapaneseImperialCalendar(TimeZone zone, Locale aLocale) { + super(zone, aLocale); + jdate = jcal.newCalendarDate(zone); + setTimeInMillis(System.currentTimeMillis()); + } + + /** + * Constructs an "empty" {@code JapaneseImperialCalendar}. + * + * @param zone the given time zone + * @param aLocale the given locale + * @param flag the flag requesting an empty instance + */ + JapaneseImperialCalendar(TimeZone zone, Locale aLocale, boolean flag) { + super(zone, aLocale); + jdate = jcal.newCalendarDate(zone); + } + + /** + * Returns {@code "japanese"} as the calendar type of this {@code + * JapaneseImperialCalendar}. + * + * @return {@code "japanese"} + */ + @Override + public String getCalendarType() { + return "japanese"; + } + + /** + * Compares this JapaneseImperialCalendar to the specified + * Object. The result is true if and + * only if the argument is a JapaneseImperialCalendar object + * that represents the same time value (millisecond offset from + * the Epoch) under the same + * Calendar parameters. + * + * @param obj the object to compare with. + * @return true if this object is equal to obj; + * false otherwise. + * @see Calendar#compareTo(Calendar) + */ + public boolean equals(Object obj) { + return obj instanceof JapaneseImperialCalendar && + super.equals(obj); + } + + /** + * Generates the hash code for this + * JapaneseImperialCalendar object. + */ + public int hashCode() { + return super.hashCode() ^ jdate.hashCode(); + } + + /** + * Adds the specified (signed) amount of time to the given calendar field, + * based on the calendar's rules. + * + *

Add rule 1. The value of field + * after the call minus the value of field before the + * call is amount, modulo any overflow that has occurred in + * field. Overflow occurs when a field value exceeds its + * range and, as a result, the next larger field is incremented or + * decremented and the field value is adjusted back into its range.

+ * + *

Add rule 2. If a smaller field is expected to be + * invariant, but it is impossible for it to be equal to its + * prior value because of changes in its minimum or maximum after + * field is changed, then its value is adjusted to be as close + * as possible to its expected value. A smaller field represents a + * smaller unit of time. HOUR is a smaller field than + * DAY_OF_MONTH. No adjustment is made to smaller fields + * that are not expected to be invariant. The calendar system + * determines what fields are expected to be invariant.

+ * + * @param field the calendar field. + * @param amount the amount of date or time to be added to the field. + * @exception IllegalArgumentException if field is + * ZONE_OFFSET, DST_OFFSET, or unknown, + * or if any calendar fields have out-of-range values in + * non-lenient mode. + */ + public void add(int field, int amount) { + // If amount == 0, do nothing even the given field is out of + // range. This is tested by JCK. + if (amount == 0) { + return; // Do nothing! + } + + if (field < 0 || field >= ZONE_OFFSET) { + throw new IllegalArgumentException(); + } + + // Sync the time and calendar fields. + complete(); + + if (field == YEAR) { + LocalGregorianCalendar.Date d = (LocalGregorianCalendar.Date) jdate.clone(); + d.addYear(amount); + pinDayOfMonth(d); + set(ERA, getEraIndex(d)); + set(YEAR, d.getYear()); + set(MONTH, d.getMonth() - 1); + set(DAY_OF_MONTH, d.getDayOfMonth()); + } else if (field == MONTH) { + LocalGregorianCalendar.Date d = (LocalGregorianCalendar.Date) jdate.clone(); + d.addMonth(amount); + pinDayOfMonth(d); + set(ERA, getEraIndex(d)); + set(YEAR, d.getYear()); + set(MONTH, d.getMonth() - 1); + set(DAY_OF_MONTH, d.getDayOfMonth()); + } else if (field == ERA) { + int era = internalGet(ERA) + amount; + if (era < 0) { + era = 0; + } else if (era > eras.length - 1) { + era = eras.length - 1; + } + set(ERA, era); + } else { + long delta = amount; + long timeOfDay = 0; + switch (field) { + // Handle the time fields here. Convert the given + // amount to milliseconds and call setTimeInMillis. + case HOUR: + case HOUR_OF_DAY: + delta *= 60 * 60 * 1000; // hours to milliseconds + break; + + case MINUTE: + delta *= 60 * 1000; // minutes to milliseconds + break; + + case SECOND: + delta *= 1000; // seconds to milliseconds + break; + + case MILLISECOND: + break; + + // Handle week, day and AM_PM fields which involves + // time zone offset change adjustment. Convert the + // given amount to the number of days. + case WEEK_OF_YEAR: + case WEEK_OF_MONTH: + case DAY_OF_WEEK_IN_MONTH: + delta *= 7; + break; + + case DAY_OF_MONTH: // synonym of DATE + case DAY_OF_YEAR: + case DAY_OF_WEEK: + break; + + case AM_PM: + // Convert the amount to the number of days (delta) + // and +12 or -12 hours (timeOfDay). + delta = amount / 2; + timeOfDay = 12 * (amount % 2); + break; + } + + // The time fields don't require time zone offset change + // adjustment. + if (field >= HOUR) { + setTimeInMillis(time + delta); + return; + } + + // The rest of the fields (week, day or AM_PM fields) + // require time zone offset (both GMT and DST) change + // adjustment. + + // Translate the current time to the fixed date and time + // of the day. + long fd = cachedFixedDate; + timeOfDay += internalGet(HOUR_OF_DAY); + timeOfDay *= 60; + timeOfDay += internalGet(MINUTE); + timeOfDay *= 60; + timeOfDay += internalGet(SECOND); + timeOfDay *= 1000; + timeOfDay += internalGet(MILLISECOND); + if (timeOfDay >= ONE_DAY) { + fd++; + timeOfDay -= ONE_DAY; + } else if (timeOfDay < 0) { + fd--; + timeOfDay += ONE_DAY; + } + + fd += delta; // fd is the expected fixed date after the calculation + int zoneOffset = internalGet(ZONE_OFFSET) + internalGet(DST_OFFSET); + setTimeInMillis((fd - EPOCH_OFFSET) * ONE_DAY + timeOfDay - zoneOffset); + zoneOffset -= internalGet(ZONE_OFFSET) + internalGet(DST_OFFSET); + // If the time zone offset has changed, then adjust the difference. + if (zoneOffset != 0) { + setTimeInMillis(time + zoneOffset); + long fd2 = cachedFixedDate; + // If the adjustment has changed the date, then take + // the previous one. + if (fd2 != fd) { + setTimeInMillis(time - zoneOffset); + } + } + } + } + + public void roll(int field, boolean up) { + roll(field, up ? +1 : -1); + } + + /** + * Adds a signed amount to the specified calendar field without changing larger fields. + * A negative roll amount means to subtract from field without changing + * larger fields. If the specified amount is 0, this method performs nothing. + * + *

This method calls {@link #complete()} before adding the + * amount so that all the calendar fields are normalized. If there + * is any calendar field having an out-of-range value in non-lenient mode, then an + * IllegalArgumentException is thrown. + * + * @param field the calendar field. + * @param amount the signed amount to add to field. + * @exception IllegalArgumentException if field is + * ZONE_OFFSET, DST_OFFSET, or unknown, + * or if any calendar fields have out-of-range values in + * non-lenient mode. + * @see #roll(int,boolean) + * @see #add(int,int) + * @see #set(int,int) + */ + public void roll(int field, int amount) { + // If amount == 0, do nothing even the given field is out of + // range. This is tested by JCK. + if (amount == 0) { + return; + } + + if (field < 0 || field >= ZONE_OFFSET) { + throw new IllegalArgumentException(); + } + + // Sync the time and calendar fields. + complete(); + + int min = getMinimum(field); + int max = getMaximum(field); + + switch (field) { + case ERA: + case AM_PM: + case MINUTE: + case SECOND: + case MILLISECOND: + // These fields are handled simply, since they have fixed + // minima and maxima. Other fields are complicated, since + // the range within they must roll varies depending on the + // date, a time zone and the era transitions. + break; + + case HOUR: + case HOUR_OF_DAY: + { + int unit = max + 1; // 12 or 24 hours + int h = internalGet(field); + int nh = (h + amount) % unit; + if (nh < 0) { + nh += unit; + } + time += ONE_HOUR * (nh - h); + + // The day might have changed, which could happen if + // the daylight saving time transition brings it to + // the next day, although it's very unlikely. But we + // have to make sure not to change the larger fields. + CalendarDate d = jcal.getCalendarDate(time, getZone()); + if (internalGet(DAY_OF_MONTH) != d.getDayOfMonth()) { + d.setEra(jdate.getEra()); + d.setDate(internalGet(YEAR), + internalGet(MONTH) + 1, + internalGet(DAY_OF_MONTH)); + if (field == HOUR) { + assert (internalGet(AM_PM) == PM); + d.addHours(+12); // restore PM + } + time = jcal.getTime(d); + } + int hourOfDay = d.getHours(); + internalSet(field, hourOfDay % unit); + if (field == HOUR) { + internalSet(HOUR_OF_DAY, hourOfDay); + } else { + internalSet(AM_PM, hourOfDay / 12); + internalSet(HOUR, hourOfDay % 12); + } + + // Time zone offset and/or daylight saving might have changed. + int zoneOffset = d.getZoneOffset(); + int saving = d.getDaylightSaving(); + internalSet(ZONE_OFFSET, zoneOffset - saving); + internalSet(DST_OFFSET, saving); + return; + } + + case YEAR: + min = getActualMinimum(field); + max = getActualMaximum(field); + break; + + case MONTH: + // Rolling the month involves both pinning the final value to [0, 11] + // and adjusting the DAY_OF_MONTH if necessary. We only adjust the + // DAY_OF_MONTH if, after updating the MONTH field, it is illegal. + // E.g., .roll(MONTH, 1) -> or . + { + if (!isTransitionYear(jdate.getNormalizedYear())) { + int year = jdate.getYear(); + if (year == getMaximum(YEAR)) { + CalendarDate jd = jcal.getCalendarDate(time, getZone()); + CalendarDate d = jcal.getCalendarDate(Long.MAX_VALUE, getZone()); + max = d.getMonth() - 1; + int n = getRolledValue(internalGet(field), amount, min, max); + if (n == max) { + // To avoid overflow, use an equivalent year. + jd.addYear(-400); + jd.setMonth(n + 1); + if (jd.getDayOfMonth() > d.getDayOfMonth()) { + jd.setDayOfMonth(d.getDayOfMonth()); + jcal.normalize(jd); + } + if (jd.getDayOfMonth() == d.getDayOfMonth() + && jd.getTimeOfDay() > d.getTimeOfDay()) { + jd.setMonth(n + 1); + jd.setDayOfMonth(d.getDayOfMonth() - 1); + jcal.normalize(jd); + // Month may have changed by the normalization. + n = jd.getMonth() - 1; + } + set(DAY_OF_MONTH, jd.getDayOfMonth()); + } + set(MONTH, n); + } else if (year == getMinimum(YEAR)) { + CalendarDate jd = jcal.getCalendarDate(time, getZone()); + CalendarDate d = jcal.getCalendarDate(Long.MIN_VALUE, getZone()); + min = d.getMonth() - 1; + int n = getRolledValue(internalGet(field), amount, min, max); + if (n == min) { + // To avoid underflow, use an equivalent year. + jd.addYear(+400); + jd.setMonth(n + 1); + if (jd.getDayOfMonth() < d.getDayOfMonth()) { + jd.setDayOfMonth(d.getDayOfMonth()); + jcal.normalize(jd); + } + if (jd.getDayOfMonth() == d.getDayOfMonth() + && jd.getTimeOfDay() < d.getTimeOfDay()) { + jd.setMonth(n + 1); + jd.setDayOfMonth(d.getDayOfMonth() + 1); + jcal.normalize(jd); + // Month may have changed by the normalization. + n = jd.getMonth() - 1; + } + set(DAY_OF_MONTH, jd.getDayOfMonth()); + } + set(MONTH, n); + } else { + int mon = (internalGet(MONTH) + amount) % 12; + if (mon < 0) { + mon += 12; + } + set(MONTH, mon); + + // Keep the day of month in the range. We + // don't want to spill over into the next + // month; e.g., we don't want jan31 + 1 mo -> + // feb31 -> mar3. + int monthLen = monthLength(mon); + if (internalGet(DAY_OF_MONTH) > monthLen) { + set(DAY_OF_MONTH, monthLen); + } + } + } else { + int eraIndex = getEraIndex(jdate); + CalendarDate transition = null; + if (jdate.getYear() == 1) { + transition = eras[eraIndex].getSinceDate(); + min = transition.getMonth() - 1; + } else { + if (eraIndex < eras.length - 1) { + transition = eras[eraIndex + 1].getSinceDate(); + if (transition.getYear() == jdate.getNormalizedYear()) { + max = transition.getMonth() - 1; + if (transition.getDayOfMonth() == 1) { + max--; + } + } + } + } + + if (min == max) { + // The year has only one month. No need to + // process further. (Showa Gan-nen (year 1) + // and the last year have only one month.) + return; + } + int n = getRolledValue(internalGet(field), amount, min, max); + set(MONTH, n); + if (n == min) { + if (!(transition.getMonth() == BaseCalendar.JANUARY + && transition.getDayOfMonth() == 1)) { + if (jdate.getDayOfMonth() < transition.getDayOfMonth()) { + set(DAY_OF_MONTH, transition.getDayOfMonth()); + } + } + } else if (n == max && (transition.getMonth() - 1 == n)) { + int dom = transition.getDayOfMonth(); + if (jdate.getDayOfMonth() >= dom) { + set(DAY_OF_MONTH, dom - 1); + } + } + } + return; + } + + case WEEK_OF_YEAR: + { + int y = jdate.getNormalizedYear(); + max = getActualMaximum(WEEK_OF_YEAR); + set(DAY_OF_WEEK, internalGet(DAY_OF_WEEK)); // update stamp[field] + int woy = internalGet(WEEK_OF_YEAR); + int value = woy + amount; + if (!isTransitionYear(jdate.getNormalizedYear())) { + int year = jdate.getYear(); + if (year == getMaximum(YEAR)) { + max = getActualMaximum(WEEK_OF_YEAR); + } else if (year == getMinimum(YEAR)) { + min = getActualMinimum(WEEK_OF_YEAR); + max = getActualMaximum(WEEK_OF_YEAR); + if (value > min && value < max) { + set(WEEK_OF_YEAR, value); + return; + } + + } + // If the new value is in between min and max + // (exclusive), then we can use the value. + if (value > min && value < max) { + set(WEEK_OF_YEAR, value); + return; + } + long fd = cachedFixedDate; + // Make sure that the min week has the current DAY_OF_WEEK + long day1 = fd - (7 * (woy - min)); + if (year != getMinimum(YEAR)) { + if (gcal.getYearFromFixedDate(day1) != y) { + min++; + } + } else { + CalendarDate d = jcal.getCalendarDate(Long.MIN_VALUE, getZone()); + if (day1 < jcal.getFixedDate(d)) { + min++; + } + } + + // Make sure the same thing for the max week + fd += 7 * (max - internalGet(WEEK_OF_YEAR)); + if (gcal.getYearFromFixedDate(fd) != y) { + max--; + } + break; + } + + // Handle transition here. + long fd = cachedFixedDate; + long day1 = fd - (7 * (woy - min)); + // Make sure that the min week has the current DAY_OF_WEEK + LocalGregorianCalendar.Date d = getCalendarDate(day1); + if (!(d.getEra() == jdate.getEra() && d.getYear() == jdate.getYear())) { + min++; + } + + // Make sure the same thing for the max week + fd += 7 * (max - woy); + jcal.getCalendarDateFromFixedDate(d, fd); + if (!(d.getEra() == jdate.getEra() && d.getYear() == jdate.getYear())) { + max--; + } + // value: the new WEEK_OF_YEAR which must be converted + // to month and day of month. + value = getRolledValue(woy, amount, min, max) - 1; + d = getCalendarDate(day1 + value * 7); + set(MONTH, d.getMonth() - 1); + set(DAY_OF_MONTH, d.getDayOfMonth()); + return; + } + + case WEEK_OF_MONTH: + { + boolean isTransitionYear = isTransitionYear(jdate.getNormalizedYear()); + // dow: relative day of week from the first day of week + int dow = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek(); + if (dow < 0) { + dow += 7; + } + + long fd = cachedFixedDate; + long month1; // fixed date of the first day (usually 1) of the month + int monthLength; // actual month length + if (isTransitionYear) { + month1 = getFixedDateMonth1(jdate, fd); + monthLength = actualMonthLength(); + } else { + month1 = fd - internalGet(DAY_OF_MONTH) + 1; + monthLength = jcal.getMonthLength(jdate); + } + + // the first day of week of the month. + long monthDay1st = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(month1 + 6, + getFirstDayOfWeek()); + // if the week has enough days to form a week, the + // week starts from the previous month. + if ((int)(monthDay1st - month1) >= getMinimalDaysInFirstWeek()) { + monthDay1st -= 7; + } + max = getActualMaximum(field); + + // value: the new WEEK_OF_MONTH value + int value = getRolledValue(internalGet(field), amount, 1, max) - 1; + + // nfd: fixed date of the rolled date + long nfd = monthDay1st + value * 7 + dow; + + // Unlike WEEK_OF_YEAR, we need to change day of week if the + // nfd is out of the month. + if (nfd < month1) { + nfd = month1; + } else if (nfd >= (month1 + monthLength)) { + nfd = month1 + monthLength - 1; + } + set(DAY_OF_MONTH, (int)(nfd - month1) + 1); + return; + } + + case DAY_OF_MONTH: + { + if (!isTransitionYear(jdate.getNormalizedYear())) { + max = jcal.getMonthLength(jdate); + break; + } + + // TODO: Need to change the spec to be usable DAY_OF_MONTH rolling... + + // Transition handling. We can't change year and era + // values here due to the Calendar roll spec! + long month1 = getFixedDateMonth1(jdate, cachedFixedDate); + + // It may not be a regular month. Convert the date and range to + // the relative values, perform the roll, and + // convert the result back to the rolled date. + int value = getRolledValue((int)(cachedFixedDate - month1), amount, + 0, actualMonthLength() - 1); + LocalGregorianCalendar.Date d = getCalendarDate(month1 + value); + assert getEraIndex(d) == internalGetEra() + && d.getYear() == internalGet(YEAR) && d.getMonth()-1 == internalGet(MONTH); + set(DAY_OF_MONTH, d.getDayOfMonth()); + return; + } + + case DAY_OF_YEAR: + { + max = getActualMaximum(field); + if (!isTransitionYear(jdate.getNormalizedYear())) { + break; + } + + // Handle transition. We can't change year and era values + // here due to the Calendar roll spec. + int value = getRolledValue(internalGet(DAY_OF_YEAR), amount, min, max); + long jan0 = cachedFixedDate - internalGet(DAY_OF_YEAR); + LocalGregorianCalendar.Date d = getCalendarDate(jan0 + value); + assert getEraIndex(d) == internalGetEra() && d.getYear() == internalGet(YEAR); + set(MONTH, d.getMonth() - 1); + set(DAY_OF_MONTH, d.getDayOfMonth()); + return; + } + + case DAY_OF_WEEK: + { + int normalizedYear = jdate.getNormalizedYear(); + if (!isTransitionYear(normalizedYear) && !isTransitionYear(normalizedYear - 1)) { + // If the week of year is in the same year, we can + // just change DAY_OF_WEEK. + int weekOfYear = internalGet(WEEK_OF_YEAR); + if (weekOfYear > 1 && weekOfYear < 52) { + set(WEEK_OF_YEAR, internalGet(WEEK_OF_YEAR)); + max = SATURDAY; + break; + } + } + + // We need to handle it in a different way around year + // boundaries and in the transition year. Note that + // changing era and year values violates the roll + // rule: not changing larger calendar fields... + amount %= 7; + if (amount == 0) { + return; + } + long fd = cachedFixedDate; + long dowFirst = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fd, getFirstDayOfWeek()); + fd += amount; + if (fd < dowFirst) { + fd += 7; + } else if (fd >= dowFirst + 7) { + fd -= 7; + } + LocalGregorianCalendar.Date d = getCalendarDate(fd); + set(ERA, getEraIndex(d)); + set(d.getYear(), d.getMonth() - 1, d.getDayOfMonth()); + return; + } + + case DAY_OF_WEEK_IN_MONTH: + { + min = 1; // after having normalized, min should be 1. + if (!isTransitionYear(jdate.getNormalizedYear())) { + int dom = internalGet(DAY_OF_MONTH); + int monthLength = jcal.getMonthLength(jdate); + int lastDays = monthLength % 7; + max = monthLength / 7; + int x = (dom - 1) % 7; + if (x < lastDays) { + max++; + } + set(DAY_OF_WEEK, internalGet(DAY_OF_WEEK)); + break; + } + + // Transition year handling. + long fd = cachedFixedDate; + long month1 = getFixedDateMonth1(jdate, fd); + int monthLength = actualMonthLength(); + int lastDays = monthLength % 7; + max = monthLength / 7; + int x = (int)(fd - month1) % 7; + if (x < lastDays) { + max++; + } + int value = getRolledValue(internalGet(field), amount, min, max) - 1; + fd = month1 + value * 7 + x; + LocalGregorianCalendar.Date d = getCalendarDate(fd); + set(DAY_OF_MONTH, d.getDayOfMonth()); + return; + } + } + + set(field, getRolledValue(internalGet(field), amount, min, max)); + } + + @Override + public String getDisplayName(int field, int style, Locale locale) { + if (!checkDisplayNameParams(field, style, SHORT, NARROW_FORMAT, locale, + ERA_MASK|YEAR_MASK|MONTH_MASK|DAY_OF_WEEK_MASK|AM_PM_MASK)) { + return null; + } + + int fieldValue = get(field); + + // "GanNen" is supported only in the LONG style. + if (field == YEAR + && (getBaseStyle(style) != LONG || fieldValue != 1 || get(ERA) == 0)) { + return null; + } + + String name = CalendarDataUtility.retrieveFieldValueName(getCalendarType(), field, + fieldValue, style, locale); + // If the ERA value is null, then + // try to get its name or abbreviation from the Era instance. + if (name == null && field == ERA && fieldValue < eras.length) { + Era era = eras[fieldValue]; + name = (style == SHORT) ? era.getAbbreviation() : era.getName(); + } + return name; + } + + @Override + public Map getDisplayNames(int field, int style, Locale locale) { + if (!checkDisplayNameParams(field, style, ALL_STYLES, NARROW_FORMAT, locale, + ERA_MASK|YEAR_MASK|MONTH_MASK|DAY_OF_WEEK_MASK|AM_PM_MASK)) { + return null; + } + Map names; + names = CalendarDataUtility.retrieveFieldValueNames(getCalendarType(), field, style, locale); + // If strings[] has fewer than eras[], get more names from eras[]. + if (names != null) { + if (field == ERA) { + int size = names.size(); + if (style == ALL_STYLES) { + Set values = new HashSet<>(); + // count unique era values + for (String key : names.keySet()) { + values.add(names.get(key)); + } + size = values.size(); + } + if (size < eras.length) { + int baseStyle = getBaseStyle(style); + for (int i = size; i < eras.length; i++) { + Era era = eras[i]; + if (baseStyle == ALL_STYLES || baseStyle == SHORT + || baseStyle == NARROW_FORMAT) { + names.put(era.getAbbreviation(), i); + } + if (baseStyle == ALL_STYLES || baseStyle == LONG) { + names.put(era.getName(), i); + } + } + } + } + } + return names; + } + + /** + * Returns the minimum value for the given calendar field of this + * Calendar instance. The minimum value is + * defined as the smallest value returned by the {@link + * Calendar#get(int) get} method for any possible time value, + * taking into consideration the current values of the + * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek}, + * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek}, + * and {@link Calendar#getTimeZone() getTimeZone} methods. + * + * @param field the calendar field. + * @return the minimum value for the given calendar field. + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + public int getMinimum(int field) { + return MIN_VALUES[field]; + } + + /** + * Returns the maximum value for the given calendar field of this + * GregorianCalendar instance. The maximum value is + * defined as the largest value returned by the {@link + * Calendar#get(int) get} method for any possible time value, + * taking into consideration the current values of the + * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek}, + * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek}, + * and {@link Calendar#getTimeZone() getTimeZone} methods. + * + * @param field the calendar field. + * @return the maximum value for the given calendar field. + * @see #getMinimum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + public int getMaximum(int field) { + switch (field) { + case YEAR: + { + // The value should depend on the time zone of this calendar. + LocalGregorianCalendar.Date d = jcal.getCalendarDate(Long.MAX_VALUE, + getZone()); + return Math.max(LEAST_MAX_VALUES[YEAR], d.getYear()); + } + } + return MAX_VALUES[field]; + } + + /** + * Returns the highest minimum value for the given calendar field + * of this GregorianCalendar instance. The highest + * minimum value is defined as the largest value returned by + * {@link #getActualMinimum(int)} for any possible time value, + * taking into consideration the current values of the + * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek}, + * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek}, + * and {@link Calendar#getTimeZone() getTimeZone} methods. + * + * @param field the calendar field. + * @return the highest minimum value for the given calendar field. + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + public int getGreatestMinimum(int field) { + return field == YEAR ? 1 : MIN_VALUES[field]; + } + + /** + * Returns the lowest maximum value for the given calendar field + * of this GregorianCalendar instance. The lowest + * maximum value is defined as the smallest value returned by + * {@link #getActualMaximum(int)} for any possible time value, + * taking into consideration the current values of the + * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek}, + * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek}, + * and {@link Calendar#getTimeZone() getTimeZone} methods. + * + * @param field the calendar field + * @return the lowest maximum value for the given calendar field. + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + public int getLeastMaximum(int field) { + switch (field) { + case YEAR: + { + return Math.min(LEAST_MAX_VALUES[YEAR], getMaximum(YEAR)); + } + } + return LEAST_MAX_VALUES[field]; + } + + /** + * Returns the minimum value that this calendar field could have, + * taking into consideration the given time value and the current + * values of the + * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek}, + * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek}, + * and {@link Calendar#getTimeZone() getTimeZone} methods. + * + * @param field the calendar field + * @return the minimum of the given field for the time value of + * this JapaneseImperialCalendar + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMaximum(int) + */ + public int getActualMinimum(int field) { + if (!isFieldSet(YEAR_MASK|MONTH_MASK|WEEK_OF_YEAR_MASK, field)) { + return getMinimum(field); + } + + int value = 0; + JapaneseImperialCalendar jc = getNormalizedCalendar(); + // Get a local date which includes time of day and time zone, + // which are missing in jc.jdate. + LocalGregorianCalendar.Date jd = jcal.getCalendarDate(jc.getTimeInMillis(), + getZone()); + int eraIndex = getEraIndex(jd); + switch (field) { + case YEAR: + { + if (eraIndex > BEFORE_MEIJI) { + value = 1; + long since = eras[eraIndex].getSince(getZone()); + CalendarDate d = jcal.getCalendarDate(since, getZone()); + // Use the same year in jd to take care of leap + // years. i.e., both jd and d must agree on leap + // or common years. + jd.setYear(d.getYear()); + jcal.normalize(jd); + assert jd.isLeapYear() == d.isLeapYear(); + if (getYearOffsetInMillis(jd) < getYearOffsetInMillis(d)) { + value++; + } + } else { + value = getMinimum(field); + CalendarDate d = jcal.getCalendarDate(Long.MIN_VALUE, getZone()); + // Use an equvalent year of d.getYear() if + // possible. Otherwise, ignore the leap year and + // common year difference. + int y = d.getYear(); + if (y > 400) { + y -= 400; + } + jd.setYear(y); + jcal.normalize(jd); + if (getYearOffsetInMillis(jd) < getYearOffsetInMillis(d)) { + value++; + } + } + } + break; + + case MONTH: + { + // In Before Meiji and Meiji, January is the first month. + if (eraIndex > MEIJI && jd.getYear() == 1) { + long since = eras[eraIndex].getSince(getZone()); + CalendarDate d = jcal.getCalendarDate(since, getZone()); + value = d.getMonth() - 1; + if (jd.getDayOfMonth() < d.getDayOfMonth()) { + value++; + } + } + } + break; + + case WEEK_OF_YEAR: + { + value = 1; + CalendarDate d = jcal.getCalendarDate(Long.MIN_VALUE, getZone()); + // shift 400 years to avoid underflow + d.addYear(+400); + jcal.normalize(d); + jd.setEra(d.getEra()); + jd.setYear(d.getYear()); + jcal.normalize(jd); + + long jan1 = jcal.getFixedDate(d); + long fd = jcal.getFixedDate(jd); + int woy = getWeekNumber(jan1, fd); + long day1 = fd - (7 * (woy - 1)); + if ((day1 < jan1) || + (day1 == jan1 && + jd.getTimeOfDay() < d.getTimeOfDay())) { + value++; + } + } + break; + } + return value; + } + + /** + * Returns the maximum value that this calendar field could have, + * taking into consideration the given time value and the current + * values of the + * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek}, + * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek}, + * and + * {@link Calendar#getTimeZone() getTimeZone} methods. + * For example, if the date of this instance is Heisei 16February 1, + * the actual maximum value of the DAY_OF_MONTH field + * is 29 because Heisei 16 is a leap year, and if the date of this + * instance is Heisei 17 February 1, it's 28. + * + * @param field the calendar field + * @return the maximum of the given field for the time value of + * this JapaneseImperialCalendar + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + */ + public int getActualMaximum(int field) { + final int fieldsForFixedMax = ERA_MASK|DAY_OF_WEEK_MASK|HOUR_MASK|AM_PM_MASK| + HOUR_OF_DAY_MASK|MINUTE_MASK|SECOND_MASK|MILLISECOND_MASK| + ZONE_OFFSET_MASK|DST_OFFSET_MASK; + if ((fieldsForFixedMax & (1<= getMinimalDaysInFirstWeek()) { + nextJan1st -= 7; + } + value = getWeekNumber(jan1, nextJan1st); + } else { + // Get the day of week of January 1 of the year + CalendarDate d = gcal.newCalendarDate(TimeZone.NO_TIMEZONE); + d.setDate(date.getNormalizedYear(), BaseCalendar.JANUARY, 1); + int dayOfWeek = gcal.getDayOfWeek(d); + // Normalize the day of week with the firstDayOfWeek value + dayOfWeek -= getFirstDayOfWeek(); + if (dayOfWeek < 0) { + dayOfWeek += 7; + } + value = 52; + int magic = dayOfWeek + getMinimalDaysInFirstWeek() - 1; + if ((magic == 6) || + (date.isLeapYear() && (magic == 5 || magic == 12))) { + value++; + } + } + break; + } + + if (jc == this) { + jc = (JapaneseImperialCalendar) jc.clone(); + } + int max = getActualMaximum(DAY_OF_YEAR); + jc.set(DAY_OF_YEAR, max); + value = jc.get(WEEK_OF_YEAR); + if (value == 1 && max > 7) { + jc.add(WEEK_OF_YEAR, -1); + value = jc.get(WEEK_OF_YEAR); + } + } + break; + + case WEEK_OF_MONTH: + { + LocalGregorianCalendar.Date jd = jcal.getCalendarDate(Long.MAX_VALUE, + getZone()); + if (!(date.getEra() == jd.getEra() && date.getYear() == jd.getYear())) { + CalendarDate d = gcal.newCalendarDate(TimeZone.NO_TIMEZONE); + d.setDate(date.getNormalizedYear(), date.getMonth(), 1); + int dayOfWeek = gcal.getDayOfWeek(d); + int monthLength = gcal.getMonthLength(d); + dayOfWeek -= getFirstDayOfWeek(); + if (dayOfWeek < 0) { + dayOfWeek += 7; + } + int nDaysFirstWeek = 7 - dayOfWeek; // # of days in the first week + value = 3; + if (nDaysFirstWeek >= getMinimalDaysInFirstWeek()) { + value++; + } + monthLength -= nDaysFirstWeek + 7 * 3; + if (monthLength > 0) { + value++; + if (monthLength > 7) { + value++; + } + } + } else { + long fd = jcal.getFixedDate(jd); + long month1 = fd - jd.getDayOfMonth() + 1; + value = getWeekNumber(month1, fd); + } + } + break; + + case DAY_OF_WEEK_IN_MONTH: + { + int ndays, dow1; + int dow = date.getDayOfWeek(); + BaseCalendar.Date d = (BaseCalendar.Date) date.clone(); + ndays = jcal.getMonthLength(d); + d.setDayOfMonth(1); + jcal.normalize(d); + dow1 = d.getDayOfWeek(); + int x = dow - dow1; + if (x < 0) { + x += 7; + } + ndays -= x; + value = (ndays + 6) / 7; + } + break; + + case YEAR: + { + CalendarDate jd = jcal.getCalendarDate(jc.getTimeInMillis(), getZone()); + CalendarDate d; + int eraIndex = getEraIndex(date); + if (eraIndex == eras.length - 1) { + d = jcal.getCalendarDate(Long.MAX_VALUE, getZone()); + value = d.getYear(); + // Use an equivalent year for the + // getYearOffsetInMillis call to avoid overflow. + if (value > 400) { + jd.setYear(value - 400); + } + } else { + d = jcal.getCalendarDate(eras[eraIndex + 1].getSince(getZone()) - 1, + getZone()); + value = d.getYear(); + // Use the same year as d.getYear() to be + // consistent with leap and common years. + jd.setYear(value); + } + jcal.normalize(jd); + if (getYearOffsetInMillis(jd) > getYearOffsetInMillis(d)) { + value--; + } + } + break; + + default: + throw new ArrayIndexOutOfBoundsException(field); + } + return value; + } + + /** + * Returns the millisecond offset from the beginning of the + * year. In the year for Long.MIN_VALUE, it's a pseudo value + * beyond the limit. The given CalendarDate object must have been + * normalized before calling this method. + */ + private long getYearOffsetInMillis(CalendarDate date) { + long t = (jcal.getDayOfYear(date) - 1) * ONE_DAY; + return t + date.getTimeOfDay() - date.getZoneOffset(); + } + + public Object clone() { + JapaneseImperialCalendar other = (JapaneseImperialCalendar) super.clone(); + + other.jdate = (LocalGregorianCalendar.Date) jdate.clone(); + other.originalFields = null; + other.zoneOffsets = null; + return other; + } + + public TimeZone getTimeZone() { + TimeZone zone = super.getTimeZone(); + // To share the zone by the CalendarDate + jdate.setZone(zone); + return zone; + } + + public void setTimeZone(TimeZone zone) { + super.setTimeZone(zone); + // To share the zone by the CalendarDate + jdate.setZone(zone); + } + + /** + * The fixed date corresponding to jdate. If the value is + * Long.MIN_VALUE, the fixed date value is unknown. + */ + transient private long cachedFixedDate = Long.MIN_VALUE; + + /** + * Converts the time value (millisecond offset from the Epoch) to calendar field values. + * The time is not + * recomputed first; to recompute the time, then the fields, call the + * complete method. + * + * @see Calendar#complete + */ + protected void computeFields() { + int mask = 0; + if (isPartiallyNormalized()) { + // Determine which calendar fields need to be computed. + mask = getSetStateFields(); + int fieldMask = ~mask & ALL_FIELDS; + if (fieldMask != 0 || cachedFixedDate == Long.MIN_VALUE) { + mask |= computeFields(fieldMask, + mask & (ZONE_OFFSET_MASK|DST_OFFSET_MASK)); + assert mask == ALL_FIELDS; + } + } else { + // Specify all fields + mask = ALL_FIELDS; + computeFields(mask, 0); + } + // After computing all the fields, set the field state to `COMPUTED'. + setFieldsComputed(mask); + } + + /** + * This computeFields implements the conversion from UTC + * (millisecond offset from the Epoch) to calendar + * field values. fieldMask specifies which fields to change the + * setting state to COMPUTED, although all fields are set to + * the correct values. This is required to fix 4685354. + * + * @param fieldMask a bit mask to specify which fields to change + * the setting state. + * @param tzMask a bit mask to specify which time zone offset + * fields to be used for time calculations + * @return a new field mask that indicates what field values have + * actually been set. + */ + private int computeFields(int fieldMask, int tzMask) { + int zoneOffset = 0; + TimeZone tz = getZone(); + if (zoneOffsets == null) { + zoneOffsets = new int[2]; + } + if (tzMask != (ZONE_OFFSET_MASK|DST_OFFSET_MASK)) { + if (tz instanceof ZoneInfo) { + zoneOffset = ((ZoneInfo)tz).getOffsets(time, zoneOffsets); + } else { + zoneOffset = tz.getOffset(time); + zoneOffsets[0] = tz.getRawOffset(); + zoneOffsets[1] = zoneOffset - zoneOffsets[0]; + } + } + if (tzMask != 0) { + if (isFieldSet(tzMask, ZONE_OFFSET)) { + zoneOffsets[0] = internalGet(ZONE_OFFSET); + } + if (isFieldSet(tzMask, DST_OFFSET)) { + zoneOffsets[1] = internalGet(DST_OFFSET); + } + zoneOffset = zoneOffsets[0] + zoneOffsets[1]; + } + + // By computing time and zoneOffset separately, we can take + // the wider range of time+zoneOffset than the previous + // implementation. + long fixedDate = zoneOffset / ONE_DAY; + int timeOfDay = zoneOffset % (int)ONE_DAY; + fixedDate += time / ONE_DAY; + timeOfDay += (int) (time % ONE_DAY); + if (timeOfDay >= ONE_DAY) { + timeOfDay -= ONE_DAY; + ++fixedDate; + } else { + while (timeOfDay < 0) { + timeOfDay += ONE_DAY; + --fixedDate; + } + } + fixedDate += EPOCH_OFFSET; + + // See if we can use jdate to avoid date calculation. + if (fixedDate != cachedFixedDate || fixedDate < 0) { + jcal.getCalendarDateFromFixedDate(jdate, fixedDate); + cachedFixedDate = fixedDate; + } + int era = getEraIndex(jdate); + int year = jdate.getYear(); + + // Always set the ERA and YEAR values. + internalSet(ERA, era); + internalSet(YEAR, year); + int mask = fieldMask | (ERA_MASK|YEAR_MASK); + + int month = jdate.getMonth() - 1; // 0-based + int dayOfMonth = jdate.getDayOfMonth(); + + // Set the basic date fields. + if ((fieldMask & (MONTH_MASK|DAY_OF_MONTH_MASK|DAY_OF_WEEK_MASK)) + != 0) { + internalSet(MONTH, month); + internalSet(DAY_OF_MONTH, dayOfMonth); + internalSet(DAY_OF_WEEK, jdate.getDayOfWeek()); + mask |= MONTH_MASK|DAY_OF_MONTH_MASK|DAY_OF_WEEK_MASK; + } + + if ((fieldMask & (HOUR_OF_DAY_MASK|AM_PM_MASK|HOUR_MASK + |MINUTE_MASK|SECOND_MASK|MILLISECOND_MASK)) != 0) { + if (timeOfDay != 0) { + int hours = timeOfDay / ONE_HOUR; + internalSet(HOUR_OF_DAY, hours); + internalSet(AM_PM, hours / 12); // Assume AM == 0 + internalSet(HOUR, hours % 12); + int r = timeOfDay % ONE_HOUR; + internalSet(MINUTE, r / ONE_MINUTE); + r %= ONE_MINUTE; + internalSet(SECOND, r / ONE_SECOND); + internalSet(MILLISECOND, r % ONE_SECOND); + } else { + internalSet(HOUR_OF_DAY, 0); + internalSet(AM_PM, AM); + internalSet(HOUR, 0); + internalSet(MINUTE, 0); + internalSet(SECOND, 0); + internalSet(MILLISECOND, 0); + } + mask |= (HOUR_OF_DAY_MASK|AM_PM_MASK|HOUR_MASK + |MINUTE_MASK|SECOND_MASK|MILLISECOND_MASK); + } + + if ((fieldMask & (ZONE_OFFSET_MASK|DST_OFFSET_MASK)) != 0) { + internalSet(ZONE_OFFSET, zoneOffsets[0]); + internalSet(DST_OFFSET, zoneOffsets[1]); + mask |= (ZONE_OFFSET_MASK|DST_OFFSET_MASK); + } + + if ((fieldMask & (DAY_OF_YEAR_MASK|WEEK_OF_YEAR_MASK + |WEEK_OF_MONTH_MASK|DAY_OF_WEEK_IN_MONTH_MASK)) != 0) { + int normalizedYear = jdate.getNormalizedYear(); + // If it's a year of an era transition, we need to handle + // irregular year boundaries. + boolean transitionYear = isTransitionYear(jdate.getNormalizedYear()); + int dayOfYear; + long fixedDateJan1; + if (transitionYear) { + fixedDateJan1 = getFixedDateJan1(jdate, fixedDate); + dayOfYear = (int)(fixedDate - fixedDateJan1) + 1; + } else if (normalizedYear == MIN_VALUES[YEAR]) { + CalendarDate dx = jcal.getCalendarDate(Long.MIN_VALUE, getZone()); + fixedDateJan1 = jcal.getFixedDate(dx); + dayOfYear = (int)(fixedDate - fixedDateJan1) + 1; + } else { + dayOfYear = (int) jcal.getDayOfYear(jdate); + fixedDateJan1 = fixedDate - dayOfYear + 1; + } + long fixedDateMonth1 = transitionYear ? + getFixedDateMonth1(jdate, fixedDate) : fixedDate - dayOfMonth + 1; + + internalSet(DAY_OF_YEAR, dayOfYear); + internalSet(DAY_OF_WEEK_IN_MONTH, (dayOfMonth - 1) / 7 + 1); + + int weekOfYear = getWeekNumber(fixedDateJan1, fixedDate); + + // The spec is to calculate WEEK_OF_YEAR in the + // ISO8601-style. This creates problems, though. + if (weekOfYear == 0) { + // If the date belongs to the last week of the + // previous year, use the week number of "12/31" of + // the "previous" year. Again, if the previous year is + // a transition year, we need to take care of it. + // Usually the previous day of the first day of a year + // is December 31, which is not always true in the + // Japanese imperial calendar system. + long fixedDec31 = fixedDateJan1 - 1; + long prevJan1; + LocalGregorianCalendar.Date d = getCalendarDate(fixedDec31); + if (!(transitionYear || isTransitionYear(d.getNormalizedYear()))) { + prevJan1 = fixedDateJan1 - 365; + if (d.isLeapYear()) { + --prevJan1; + } + } else if (transitionYear) { + if (jdate.getYear() == 1) { + // As of Heisei (since Meiji) there's no case + // that there are multiple transitions in a + // year. Historically there was such + // case. There might be such case again in the + // future. + if (era > HEISEI) { + CalendarDate pd = eras[era - 1].getSinceDate(); + if (normalizedYear == pd.getYear()) { + d.setMonth(pd.getMonth()).setDayOfMonth(pd.getDayOfMonth()); + } + } else { + d.setMonth(LocalGregorianCalendar.JANUARY).setDayOfMonth(1); + } + jcal.normalize(d); + prevJan1 = jcal.getFixedDate(d); + } else { + prevJan1 = fixedDateJan1 - 365; + if (d.isLeapYear()) { + --prevJan1; + } + } + } else { + CalendarDate cd = eras[getEraIndex(jdate)].getSinceDate(); + d.setMonth(cd.getMonth()).setDayOfMonth(cd.getDayOfMonth()); + jcal.normalize(d); + prevJan1 = jcal.getFixedDate(d); + } + weekOfYear = getWeekNumber(prevJan1, fixedDec31); + } else { + if (!transitionYear) { + // Regular years + if (weekOfYear >= 52) { + long nextJan1 = fixedDateJan1 + 365; + if (jdate.isLeapYear()) { + nextJan1++; + } + long nextJan1st = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(nextJan1 + 6, + getFirstDayOfWeek()); + int ndays = (int)(nextJan1st - nextJan1); + if (ndays >= getMinimalDaysInFirstWeek() && fixedDate >= (nextJan1st - 7)) { + // The first days forms a week in which the date is included. + weekOfYear = 1; + } + } + } else { + LocalGregorianCalendar.Date d = (LocalGregorianCalendar.Date) jdate.clone(); + long nextJan1; + if (jdate.getYear() == 1) { + d.addYear(+1); + d.setMonth(LocalGregorianCalendar.JANUARY).setDayOfMonth(1); + nextJan1 = jcal.getFixedDate(d); + } else { + int nextEraIndex = getEraIndex(d) + 1; + CalendarDate cd = eras[nextEraIndex].getSinceDate(); + d.setEra(eras[nextEraIndex]); + d.setDate(1, cd.getMonth(), cd.getDayOfMonth()); + jcal.normalize(d); + nextJan1 = jcal.getFixedDate(d); + } + long nextJan1st = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(nextJan1 + 6, + getFirstDayOfWeek()); + int ndays = (int)(nextJan1st - nextJan1); + if (ndays >= getMinimalDaysInFirstWeek() && fixedDate >= (nextJan1st - 7)) { + // The first days forms a week in which the date is included. + weekOfYear = 1; + } + } + } + internalSet(WEEK_OF_YEAR, weekOfYear); + internalSet(WEEK_OF_MONTH, getWeekNumber(fixedDateMonth1, fixedDate)); + mask |= (DAY_OF_YEAR_MASK|WEEK_OF_YEAR_MASK|WEEK_OF_MONTH_MASK|DAY_OF_WEEK_IN_MONTH_MASK); + } + return mask; + } + + /** + * Returns the number of weeks in a period between fixedDay1 and + * fixedDate. The getFirstDayOfWeek-getMinimalDaysInFirstWeek rule + * is applied to calculate the number of weeks. + * + * @param fixedDay1 the fixed date of the first day of the period + * @param fixedDate the fixed date of the last day of the period + * @return the number of weeks of the given period + */ + private int getWeekNumber(long fixedDay1, long fixedDate) { + // We can always use `jcal' since Julian and Gregorian are the + // same thing for this calculation. + long fixedDay1st = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fixedDay1 + 6, + getFirstDayOfWeek()); + int ndays = (int)(fixedDay1st - fixedDay1); + assert ndays <= 7; + if (ndays >= getMinimalDaysInFirstWeek()) { + fixedDay1st -= 7; + } + int normalizedDayOfPeriod = (int)(fixedDate - fixedDay1st); + if (normalizedDayOfPeriod >= 0) { + return normalizedDayOfPeriod / 7 + 1; + } + return CalendarUtils.floorDivide(normalizedDayOfPeriod, 7) + 1; + } + + /** + * Converts calendar field values to the time value (millisecond + * offset from the Epoch). + * + * @exception IllegalArgumentException if any calendar fields are invalid. + */ + protected void computeTime() { + // In non-lenient mode, perform brief checking of calendar + // fields which have been set externally. Through this + // checking, the field values are stored in originalFields[] + // to see if any of them are normalized later. + if (!isLenient()) { + if (originalFields == null) { + originalFields = new int[FIELD_COUNT]; + } + for (int field = 0; field < FIELD_COUNT; field++) { + int value = internalGet(field); + if (isExternallySet(field)) { + // Quick validation for any out of range values + if (value < getMinimum(field) || value > getMaximum(field)) { + throw new IllegalArgumentException(getFieldName(field)); + } + } + originalFields[field] = value; + } + } + + // Let the super class determine which calendar fields to be + // used to calculate the time. + int fieldMask = selectFields(); + + int year; + int era; + + if (isSet(ERA)) { + era = internalGet(ERA); + year = isSet(YEAR) ? internalGet(YEAR) : 1; + } else { + if (isSet(YEAR)) { + era = eras.length - 1; + year = internalGet(YEAR); + } else { + // Equivalent to 1970 (Gregorian) + era = SHOWA; + year = 45; + } + } + + // Calculate the time of day. We rely on the convention that + // an UNSET field has 0. + long timeOfDay = 0; + if (isFieldSet(fieldMask, HOUR_OF_DAY)) { + timeOfDay += (long) internalGet(HOUR_OF_DAY); + } else { + timeOfDay += internalGet(HOUR); + // The default value of AM_PM is 0 which designates AM. + if (isFieldSet(fieldMask, AM_PM)) { + timeOfDay += 12 * internalGet(AM_PM); + } + } + timeOfDay *= 60; + timeOfDay += internalGet(MINUTE); + timeOfDay *= 60; + timeOfDay += internalGet(SECOND); + timeOfDay *= 1000; + timeOfDay += internalGet(MILLISECOND); + + // Convert the time of day to the number of days and the + // millisecond offset from midnight. + long fixedDate = timeOfDay / ONE_DAY; + timeOfDay %= ONE_DAY; + while (timeOfDay < 0) { + timeOfDay += ONE_DAY; + --fixedDate; + } + + // Calculate the fixed date since January 1, 1 (Gregorian). + fixedDate += getFixedDate(era, year, fieldMask); + + // millis represents local wall-clock time in milliseconds. + long millis = (fixedDate - EPOCH_OFFSET) * ONE_DAY + timeOfDay; + + // Compute the time zone offset and DST offset. There are two potential + // ambiguities here. We'll assume a 2:00 am (wall time) switchover time + // for discussion purposes here. + // 1. The transition into DST. Here, a designated time of 2:00 am - 2:59 am + // can be in standard or in DST depending. However, 2:00 am is an invalid + // representation (the representation jumps from 1:59:59 am Std to 3:00:00 am DST). + // We assume standard time. + // 2. The transition out of DST. Here, a designated time of 1:00 am - 1:59 am + // can be in standard or DST. Both are valid representations (the rep + // jumps from 1:59:59 DST to 1:00:00 Std). + // Again, we assume standard time. + // We use the TimeZone object, unless the user has explicitly set the ZONE_OFFSET + // or DST_OFFSET fields; then we use those fields. + TimeZone zone = getZone(); + if (zoneOffsets == null) { + zoneOffsets = new int[2]; + } + int tzMask = fieldMask & (ZONE_OFFSET_MASK|DST_OFFSET_MASK); + if (tzMask != (ZONE_OFFSET_MASK|DST_OFFSET_MASK)) { + if (zone instanceof ZoneInfo) { + ((ZoneInfo)zone).getOffsetsByWall(millis, zoneOffsets); + } else { + zone.getOffsets(millis - zone.getRawOffset(), zoneOffsets); + } + } + if (tzMask != 0) { + if (isFieldSet(tzMask, ZONE_OFFSET)) { + zoneOffsets[0] = internalGet(ZONE_OFFSET); + } + if (isFieldSet(tzMask, DST_OFFSET)) { + zoneOffsets[1] = internalGet(DST_OFFSET); + } + } + + // Adjust the time zone offset values to get the UTC time. + millis -= zoneOffsets[0] + zoneOffsets[1]; + + // Set this calendar's time in milliseconds + time = millis; + + int mask = computeFields(fieldMask | getSetStateFields(), tzMask); + + if (!isLenient()) { + for (int field = 0; field < FIELD_COUNT; field++) { + if (!isExternallySet(field)) { + continue; + } + if (originalFields[field] != internalGet(field)) { + int wrongValue = internalGet(field); + // Restore the original field values + System.arraycopy(originalFields, 0, fields, 0, fields.length); + throw new IllegalArgumentException(getFieldName(field) + "=" + wrongValue + + ", expected " + originalFields[field]); + } + } + } + setFieldsNormalized(mask); + } + + /** + * Computes the fixed date under either the Gregorian or the + * Julian calendar, using the given year and the specified calendar fields. + * + * @param era era index + * @param year the normalized year number, with 0 indicating the + * year 1 BCE, -1 indicating 2 BCE, etc. + * @param fieldMask the calendar fields to be used for the date calculation + * @return the fixed date + * @see Calendar#selectFields + */ + private long getFixedDate(int era, int year, int fieldMask) { + int month = JANUARY; + int firstDayOfMonth = 1; + if (isFieldSet(fieldMask, MONTH)) { + // No need to check if MONTH has been set (no isSet(MONTH) + // call) since its unset value happens to be JANUARY (0). + month = internalGet(MONTH); + + // If the month is out of range, adjust it into range. + if (month > DECEMBER) { + year += month / 12; + month %= 12; + } else if (month < JANUARY) { + int[] rem = new int[1]; + year += CalendarUtils.floorDivide(month, 12, rem); + month = rem[0]; + } + } else { + if (year == 1 && era != 0) { + CalendarDate d = eras[era].getSinceDate(); + month = d.getMonth() - 1; + firstDayOfMonth = d.getDayOfMonth(); + } + } + + // Adjust the base date if year is the minimum value. + if (year == MIN_VALUES[YEAR]) { + CalendarDate dx = jcal.getCalendarDate(Long.MIN_VALUE, getZone()); + int m = dx.getMonth() - 1; + if (month < m) { + month = m; + } + if (month == m) { + firstDayOfMonth = dx.getDayOfMonth(); + } + } + + LocalGregorianCalendar.Date date = jcal.newCalendarDate(TimeZone.NO_TIMEZONE); + date.setEra(era > 0 ? eras[era] : null); + date.setDate(year, month + 1, firstDayOfMonth); + jcal.normalize(date); + + // Get the fixed date since Jan 1, 1 (Gregorian). We are on + // the first day of either `month' or January in 'year'. + long fixedDate = jcal.getFixedDate(date); + + if (isFieldSet(fieldMask, MONTH)) { + // Month-based calculations + if (isFieldSet(fieldMask, DAY_OF_MONTH)) { + // We are on the "first day" of the month (which may + // not be 1). Just add the offset if DAY_OF_MONTH is + // set. If the isSet call returns false, that means + // DAY_OF_MONTH has been selected just because of the + // selected combination. We don't need to add any + // since the default value is the "first day". + if (isSet(DAY_OF_MONTH)) { + // To avoid underflow with DAY_OF_MONTH-firstDayOfMonth, add + // DAY_OF_MONTH, then subtract firstDayOfMonth. + fixedDate += internalGet(DAY_OF_MONTH); + fixedDate -= firstDayOfMonth; + } + } else { + if (isFieldSet(fieldMask, WEEK_OF_MONTH)) { + long firstDayOfWeek = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fixedDate + 6, + getFirstDayOfWeek()); + // If we have enough days in the first week, then + // move to the previous week. + if ((firstDayOfWeek - fixedDate) >= getMinimalDaysInFirstWeek()) { + firstDayOfWeek -= 7; + } + if (isFieldSet(fieldMask, DAY_OF_WEEK)) { + firstDayOfWeek = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(firstDayOfWeek + 6, + internalGet(DAY_OF_WEEK)); + } + // In lenient mode, we treat days of the previous + // months as a part of the specified + // WEEK_OF_MONTH. See 4633646. + fixedDate = firstDayOfWeek + 7 * (internalGet(WEEK_OF_MONTH) - 1); + } else { + int dayOfWeek; + if (isFieldSet(fieldMask, DAY_OF_WEEK)) { + dayOfWeek = internalGet(DAY_OF_WEEK); + } else { + dayOfWeek = getFirstDayOfWeek(); + } + // We are basing this on the day-of-week-in-month. The only + // trickiness occurs if the day-of-week-in-month is + // negative. + int dowim; + if (isFieldSet(fieldMask, DAY_OF_WEEK_IN_MONTH)) { + dowim = internalGet(DAY_OF_WEEK_IN_MONTH); + } else { + dowim = 1; + } + if (dowim >= 0) { + fixedDate = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fixedDate + (7 * dowim) - 1, + dayOfWeek); + } else { + // Go to the first day of the next week of + // the specified week boundary. + int lastDate = monthLength(month, year) + (7 * (dowim + 1)); + // Then, get the day of week date on or before the last date. + fixedDate = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fixedDate + lastDate - 1, + dayOfWeek); + } + } + } + } else { + // We are on the first day of the year. + if (isFieldSet(fieldMask, DAY_OF_YEAR)) { + if (isTransitionYear(date.getNormalizedYear())) { + fixedDate = getFixedDateJan1(date, fixedDate); + } + // Add the offset, then subtract 1. (Make sure to avoid underflow.) + fixedDate += internalGet(DAY_OF_YEAR); + fixedDate--; + } else { + long firstDayOfWeek = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fixedDate + 6, + getFirstDayOfWeek()); + // If we have enough days in the first week, then move + // to the previous week. + if ((firstDayOfWeek - fixedDate) >= getMinimalDaysInFirstWeek()) { + firstDayOfWeek -= 7; + } + if (isFieldSet(fieldMask, DAY_OF_WEEK)) { + int dayOfWeek = internalGet(DAY_OF_WEEK); + if (dayOfWeek != getFirstDayOfWeek()) { + firstDayOfWeek = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(firstDayOfWeek + 6, + dayOfWeek); + } + } + fixedDate = firstDayOfWeek + 7 * ((long)internalGet(WEEK_OF_YEAR) - 1); + } + } + return fixedDate; + } + + /** + * Returns the fixed date of the first day of the year (usually + * January 1) before the specified date. + * + * @param date the date for which the first day of the year is + * calculated. The date has to be in the cut-over year. + * @param fixedDate the fixed date representation of the date + */ + private long getFixedDateJan1(LocalGregorianCalendar.Date date, long fixedDate) { + Era era = date.getEra(); + if (date.getEra() != null && date.getYear() == 1) { + for (int eraIndex = getEraIndex(date); eraIndex > 0; eraIndex--) { + CalendarDate d = eras[eraIndex].getSinceDate(); + long fd = gcal.getFixedDate(d); + // There might be multiple era transitions in a year. + if (fd > fixedDate) { + continue; + } + return fd; + } + } + CalendarDate d = gcal.newCalendarDate(TimeZone.NO_TIMEZONE); + d.setDate(date.getNormalizedYear(), Gregorian.JANUARY, 1); + return gcal.getFixedDate(d); + } + + /** + * Returns the fixed date of the first date of the month (usually + * the 1st of the month) before the specified date. + * + * @param date the date for which the first day of the month is + * calculated. The date must be in the era transition year. + * @param fixedDate the fixed date representation of the date + */ + private long getFixedDateMonth1(LocalGregorianCalendar.Date date, + long fixedDate) { + int eraIndex = getTransitionEraIndex(date); + if (eraIndex != -1) { + long transition = sinceFixedDates[eraIndex]; + // If the given date is on or after the transition date, then + // return the transition date. + if (transition <= fixedDate) { + return transition; + } + } + + // Otherwise, we can use the 1st day of the month. + return fixedDate - date.getDayOfMonth() + 1; + } + + /** + * Returns a LocalGregorianCalendar.Date produced from the specified fixed date. + * + * @param fd the fixed date + */ + private static LocalGregorianCalendar.Date getCalendarDate(long fd) { + LocalGregorianCalendar.Date d = jcal.newCalendarDate(TimeZone.NO_TIMEZONE); + jcal.getCalendarDateFromFixedDate(d, fd); + return d; + } + + /** + * Returns the length of the specified month in the specified + * Gregorian year. The year number must be normalized. + * + * @see GregorianCalendar#isLeapYear(int) + */ + private int monthLength(int month, int gregorianYear) { + return CalendarUtils.isGregorianLeapYear(gregorianYear) ? + GregorianCalendar.LEAP_MONTH_LENGTH[month] : GregorianCalendar.MONTH_LENGTH[month]; + } + + /** + * Returns the length of the specified month in the year provided + * by internalGet(YEAR). + * + * @see GregorianCalendar#isLeapYear(int) + */ + private int monthLength(int month) { + assert jdate.isNormalized(); + return jdate.isLeapYear() ? + GregorianCalendar.LEAP_MONTH_LENGTH[month] : GregorianCalendar.MONTH_LENGTH[month]; + } + + private int actualMonthLength() { + int length = jcal.getMonthLength(jdate); + int eraIndex = getTransitionEraIndex(jdate); + if (eraIndex == -1) { + long transitionFixedDate = sinceFixedDates[eraIndex]; + CalendarDate d = eras[eraIndex].getSinceDate(); + if (transitionFixedDate <= cachedFixedDate) { + length -= d.getDayOfMonth() - 1; + } else { + length = d.getDayOfMonth() - 1; + } + } + return length; + } + + /** + * Returns the index to the new era if the given date is in a + * transition month. For example, if the give date is Heisei 1 + * (1989) January 20, then the era index for Heisei is + * returned. Likewise, if the given date is Showa 64 (1989) + * January 3, then the era index for Heisei is returned. If the + * given date is not in any transition month, then -1 is returned. + */ + private static int getTransitionEraIndex(LocalGregorianCalendar.Date date) { + int eraIndex = getEraIndex(date); + CalendarDate transitionDate = eras[eraIndex].getSinceDate(); + if (transitionDate.getYear() == date.getNormalizedYear() && + transitionDate.getMonth() == date.getMonth()) { + return eraIndex; + } + if (eraIndex < eras.length - 1) { + transitionDate = eras[++eraIndex].getSinceDate(); + if (transitionDate.getYear() == date.getNormalizedYear() && + transitionDate.getMonth() == date.getMonth()) { + return eraIndex; + } + } + return -1; + } + + private boolean isTransitionYear(int normalizedYear) { + for (int i = eras.length - 1; i > 0; i--) { + int transitionYear = eras[i].getSinceDate().getYear(); + if (normalizedYear == transitionYear) { + return true; + } + if (normalizedYear > transitionYear) { + break; + } + } + return false; + } + + private static int getEraIndex(LocalGregorianCalendar.Date date) { + Era era = date.getEra(); + for (int i = eras.length - 1; i > 0; i--) { + if (eras[i] == era) { + return i; + } + } + return 0; + } + + /** + * Returns this object if it's normalized (all fields and time are + * in sync). Otherwise, a cloned object is returned after calling + * complete() in lenient mode. + */ + private JapaneseImperialCalendar getNormalizedCalendar() { + JapaneseImperialCalendar jc; + if (isFullyNormalized()) { + jc = this; + } else { + // Create a clone and normalize the calendar fields + jc = (JapaneseImperialCalendar) this.clone(); + jc.setLenient(true); + jc.complete(); + } + return jc; + } + + /** + * After adjustments such as add(MONTH), add(YEAR), we don't want the + * month to jump around. E.g., we don't want Jan 31 + 1 month to go to Mar + * 3, we want it to go to Feb 28. Adjustments which might run into this + * problem call this method to retain the proper month. + */ + private void pinDayOfMonth(LocalGregorianCalendar.Date date) { + int year = date.getYear(); + int dom = date.getDayOfMonth(); + if (year != getMinimum(YEAR)) { + date.setDayOfMonth(1); + jcal.normalize(date); + int monthLength = jcal.getMonthLength(date); + if (dom > monthLength) { + date.setDayOfMonth(monthLength); + } else { + date.setDayOfMonth(dom); + } + jcal.normalize(date); + } else { + LocalGregorianCalendar.Date d = jcal.getCalendarDate(Long.MIN_VALUE, getZone()); + LocalGregorianCalendar.Date realDate = jcal.getCalendarDate(time, getZone()); + long tod = realDate.getTimeOfDay(); + // Use an equivalent year. + realDate.addYear(+400); + realDate.setMonth(date.getMonth()); + realDate.setDayOfMonth(1); + jcal.normalize(realDate); + int monthLength = jcal.getMonthLength(realDate); + if (dom > monthLength) { + realDate.setDayOfMonth(monthLength); + } else { + if (dom < d.getDayOfMonth()) { + realDate.setDayOfMonth(d.getDayOfMonth()); + } else { + realDate.setDayOfMonth(dom); + } + } + if (realDate.getDayOfMonth() == d.getDayOfMonth() && tod < d.getTimeOfDay()) { + realDate.setDayOfMonth(Math.min(dom + 1, monthLength)); + } + // restore the year. + date.setDate(year, realDate.getMonth(), realDate.getDayOfMonth()); + // Don't normalize date here so as not to cause underflow. + } + } + + /** + * Returns the new value after 'roll'ing the specified value and amount. + */ + private static int getRolledValue(int value, int amount, int min, int max) { + assert value >= min && value <= max; + int range = max - min + 1; + amount %= range; + int n = value + amount; + if (n > max) { + n -= range; + } else if (n < min) { + n += range; + } + assert n >= min && n <= max; + return n; + } + + /** + * Returns the ERA. We need a special method for this because the + * default ERA is the current era, but a zero (unset) ERA means before Meiji. + */ + private int internalGetEra() { + return isSet(ERA) ? internalGet(ERA) : eras.length - 1; + } + + /** + * Updates internal state. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + if (jdate == null) { + jdate = jcal.newCalendarDate(getZone()); + cachedFixedDate = Long.MIN_VALUE; + } + } +}