| /* |
| * Copyright (c) 2005, 2021, 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; |
| |
| /** |
| * {@code 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. |
| * <pre>{@code |
| * ERA value Era name Since (in Gregorian) |
| * ------------------------------------------------------ |
| * 0 N/A N/A |
| * 1 Meiji 1868-01-01T00:00:00 local time |
| * 2 Taisho 1912-07-30T00:00:00 local time |
| * 3 Showa 1926-12-25T00:00:00 local time |
| * 4 Heisei 1989-01-08T00:00:00 local time |
| * 5 Reiwa 2019-05-01T00:00:00 local time |
| * ------------------------------------------------------ |
| * }</pre> |
| * |
| * <p>{@code 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. |
| */ |
| |
| /** |
| * 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; |
| |
| // Android-changed: Call the New Era it's proper name Reiwa. |
| /** |
| * The ERA constant designating the Reiwa era. |
| */ |
| public static final int REIWA = 5; |
| |
| private static final int EPOCH_OFFSET = 719163; // Fixed date of January 1, 1970 (Gregorian) |
| |
| // 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; |
| |
| // 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; |
| |
| // The current era |
| private static final int currentEra; |
| |
| /* |
| * <pre> |
| * 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 |
| * </pre> |
| * *: 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 |
| @SuppressWarnings("FieldNameHidesFieldInSuperclass") |
| @java.io.Serial |
| 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; |
| // Android-removed: Zygote could initialize this class when system has outdated time. |
| // int current = index; |
| sinceFixedDates[index] = gcal.getFixedDate(BEFORE_MEIJI_ERA.getSinceDate()); |
| eras[index++] = BEFORE_MEIJI_ERA; |
| for (Era e : es) { |
| // Android-removed: Zygote could initialize this class when system has outdated time. |
| // Android hard-code the current era. Unlike upstream, Android does not add the new era |
| // in the code until the new era arrives. Thus, Android can't have newer era than the |
| // real world. currentEra is the latest Era that Android knows about. |
| // if(e.getSince(TimeZone.NO_TIMEZONE) < System.currentTimeMillis()) { |
| // current = index; |
| // } |
| CalendarDate d = e.getSinceDate(); |
| sinceFixedDates[index] = gcal.getFixedDate(d); |
| eras[index++] = e; |
| } |
| // Android-changed: Zygote could initialize this class when system has outdated time. |
| // currentEra = current; |
| currentEra = REIWA; |
| |
| 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 {@code 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 {@code JapaneseImperialCalendar} to the specified |
| * {@code Object}. The result is {@code true} if and |
| * only if the argument is a {@code JapaneseImperialCalendar} object |
| * that represents the same time value (millisecond offset from |
| * the <a href="Calendar.html#Epoch">Epoch</a>) under the same |
| * {@code Calendar} parameters. |
| * |
| * @param obj the object to compare with. |
| * @return {@code true} if this object is equal to {@code obj}; |
| * {@code false} otherwise. |
| * @see Calendar#compareTo(Calendar) |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| return obj instanceof JapaneseImperialCalendar && |
| super.equals(obj); |
| } |
| |
| /** |
| * Generates the hash code for this |
| * {@code JapaneseImperialCalendar} object. |
| */ |
| @Override |
| 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. |
| * |
| * <p><em>Add rule 1</em>. The value of {@code field} |
| * after the call minus the value of {@code field} before the |
| * call is {@code amount}, modulo any overflow that has occurred in |
| * {@code 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.</p> |
| * |
| * <p><em>Add rule 2</em>. 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 |
| * {@code 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. {@code HOUR} is a smaller field than |
| * {@code 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.</p> |
| * |
| * @param field the calendar field. |
| * @param amount the amount of date or time to be added to the field. |
| * @throws IllegalArgumentException if {@code field} is |
| * {@code ZONE_OFFSET}, {@code DST_OFFSET}, or unknown, |
| * or if any calendar fields have out-of-range values in |
| * non-lenient mode. |
| */ |
| @Override |
| 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); |
| } |
| } |
| } |
| } |
| |
| @Override |
| 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. |
| * |
| * <p>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 |
| * {@code IllegalArgumentException} is thrown. |
| * |
| * @param field the calendar field. |
| * @param amount the signed amount to add to {@code field}. |
| * @throws IllegalArgumentException if {@code field} is |
| * {@code ZONE_OFFSET}, {@code 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) |
| */ |
| @Override |
| 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., <jan31>.roll(MONTH, 1) -> <feb28> or <feb29>. |
| { |
| 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, getCalendarDate(nfd).getDayOfMonth()); |
| 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 or empty, then |
| // try to get its name or abbreviation from the Era instance. |
| if ((name == null || name.isEmpty()) && |
| field == ERA && |
| fieldValue < eras.length) { |
| Era era = eras[fieldValue]; |
| name = (style == SHORT) ? era.getAbbreviation() : era.getName(); |
| } |
| return name; |
| } |
| |
| @Override |
| public Map<String,Integer> 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<String, Integer> 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<Integer> 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 = 0; i < eras.length; i++) { |
| if (!names.values().contains(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 |
| * {@code 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 |
| * {@code 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) { |
| return switch (field) { |
| case YEAR -> { |
| // The value should depend on the time zone of this calendar. |
| LocalGregorianCalendar.Date d = jcal.getCalendarDate(Long.MAX_VALUE, getZone()); |
| yield Math.max(LEAST_MAX_VALUES[YEAR], d.getYear()); |
| } |
| default -> MAX_VALUES[field]; |
| }; |
| } |
| |
| /** |
| * Returns the highest minimum value for the given calendar field |
| * of this {@code 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 {@code 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) { |
| return switch (field) { |
| case YEAR -> Math.min(LEAST_MAX_VALUES[YEAR], getMaximum(YEAR)); |
| default -> 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 {@code 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++; |
| } |
| } |
| } |
| 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++; |
| } |
| } |
| } |
| 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++; |
| } |
| } |
| } |
| 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 {@code 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 {@code 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<<field)) != 0) { |
| return getMaximum(field); |
| } |
| |
| JapaneseImperialCalendar jc = getNormalizedCalendar(); |
| LocalGregorianCalendar.Date date = jc.jdate; |
| |
| return switch (field) { |
| case MONTH -> { |
| int month = DECEMBER; |
| if (isTransitionYear(date.getNormalizedYear())) { |
| // TODO: there may be multiple transitions in a year. |
| int eraIndex = getEraIndex(date); |
| if (date.getYear() != 1) { |
| eraIndex++; |
| assert eraIndex < eras.length; |
| } |
| long transition = sinceFixedDates[eraIndex]; |
| long fd = jc.cachedFixedDate; |
| if (fd < transition) { |
| LocalGregorianCalendar.Date ldate |
| = (LocalGregorianCalendar.Date) date.clone(); |
| jcal.getCalendarDateFromFixedDate(ldate, transition - 1); |
| month = ldate.getMonth() - 1; |
| } |
| } else { |
| LocalGregorianCalendar.Date d = jcal.getCalendarDate(Long.MAX_VALUE, getZone()); |
| if (date.getEra() == d.getEra() && date.getYear() == d.getYear()) { |
| month = d.getMonth() - 1; |
| } |
| } |
| yield month; |
| } |
| case DAY_OF_MONTH -> jcal.getMonthLength(date); |
| case DAY_OF_YEAR -> { |
| if (isTransitionYear(date.getNormalizedYear())) { |
| // Handle transition year. |
| // TODO: there may be multiple transitions in a year. |
| int eraIndex = getEraIndex(date); |
| if (date.getYear() != 1) { |
| eraIndex++; |
| assert eraIndex < eras.length; |
| } |
| long transition = sinceFixedDates[eraIndex]; |
| long fd = jc.cachedFixedDate; |
| CalendarDate d = gcal.newCalendarDate(TimeZone.NO_TIMEZONE); |
| d.setDate(date.getNormalizedYear(), BaseCalendar.JANUARY, 1); |
| if (fd < transition) { |
| yield (int) (transition - gcal.getFixedDate(d)); |
| } |
| d.addYear(1); |
| yield (int) (gcal.getFixedDate(d) - transition); |
| } |
| LocalGregorianCalendar.Date d = jcal.getCalendarDate(Long.MAX_VALUE, getZone()); |
| if (date.getEra() == d.getEra() && date.getYear() == d.getYear()) { |
| long fd = jcal.getFixedDate(d); |
| long jan1 = getFixedDateJan1(d, fd); |
| yield (int) (fd - jan1) + 1; |
| } else if (date.getYear() == getMinimum(YEAR)) { |
| CalendarDate d1 = jcal.getCalendarDate(Long.MIN_VALUE, getZone()); |
| long fd1 = jcal.getFixedDate(d1); |
| d1.addYear(1); |
| d1.setMonth(BaseCalendar.JANUARY).setDayOfMonth(1); |
| jcal.normalize(d1); |
| long fd2 = jcal.getFixedDate(d1); |
| yield (int) (fd2 - fd1); |
| } else { |
| yield jcal.getYearLength(date); |
| } |
| } |
| case WEEK_OF_YEAR -> { |
| if (!isTransitionYear(date.getNormalizedYear())) { |
| LocalGregorianCalendar.Date jd = jcal.getCalendarDate(Long.MAX_VALUE, getZone()); |
| if (date.getEra() == jd.getEra() && date.getYear() == jd.getYear()) { |
| long fd = jcal.getFixedDate(jd); |
| long jan1 = getFixedDateJan1(jd, fd); |
| yield getWeekNumber(jan1, fd); |
| } else if (date.getEra() == null && date.getYear() == getMinimum(YEAR)) { |
| 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.setDate(d.getYear() + 1, BaseCalendar.JANUARY, 1); |
| jcal.normalize(jd); |
| long jan1 = jcal.getFixedDate(d); |
| long nextJan1 = jcal.getFixedDate(jd); |
| long nextJan1st = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(nextJan1 + 6, |
| getFirstDayOfWeek()); |
| int ndays = (int) (nextJan1st - nextJan1); |
| if (ndays >= getMinimalDaysInFirstWeek()) { |
| nextJan1st -= 7; |
| } |
| yield getWeekNumber(jan1, nextJan1st); |
| } |
| // 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; |
| } |
| int magic = dayOfWeek + getMinimalDaysInFirstWeek() - 1; |
| if ((magic == 6) || |
| (date.isLeapYear() && (magic == 5 || magic == 12))) { |
| yield 53; |
| } |
| yield 52; |
| } |
| |
| if (jc == this) { |
| jc = (JapaneseImperialCalendar) jc.clone(); |
| } |
| int max = getActualMaximum(DAY_OF_YEAR); |
| jc.set(DAY_OF_YEAR, max); |
| int weekOfYear = jc.get(WEEK_OF_YEAR); |
| if (weekOfYear == 1 && max > 7) { |
| jc.add(WEEK_OF_YEAR, -1); |
| weekOfYear = jc.get(WEEK_OF_YEAR); |
| } |
| yield weekOfYear; |
| } |
| case WEEK_OF_MONTH -> { |
| LocalGregorianCalendar.Date jd = jcal.getCalendarDate(Long.MAX_VALUE, getZone()); |
| if (date.getEra() == jd.getEra() && date.getYear() == jd.getYear()) { |
| long fd = jcal.getFixedDate(jd); |
| long month1 = fd - jd.getDayOfMonth() + 1; |
| yield getWeekNumber(month1, fd); |
| } |
| CalendarDate d = gcal.newCalendarDate(TimeZone.NO_TIMEZONE); |
| d.setDate(date.getNormalizedYear(), date.getMonth(), 1); |
| int dayOfWeek = gcal.getDayOfWeek(d); |
| int monthLength = actualMonthLength(); |
| dayOfWeek -= getFirstDayOfWeek(); |
| if (dayOfWeek < 0) { |
| dayOfWeek += 7; |
| } |
| int nDaysFirstWeek = 7 - dayOfWeek; // # of days in the first week |
| int weekOfMonth = 3; |
| if (nDaysFirstWeek >= getMinimalDaysInFirstWeek()) { |
| weekOfMonth++; |
| } |
| monthLength -= nDaysFirstWeek + 7 * 3; |
| if (monthLength > 0) { |
| weekOfMonth++; |
| if (monthLength > 7) { |
| weekOfMonth++; |
| } |
| } |
| yield weekOfMonth; |
| } |
| 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; |
| yield (ndays + 6) / 7; |
| } |
| case YEAR -> { |
| CalendarDate jd = jcal.getCalendarDate(jc.getTimeInMillis(), getZone()); |
| CalendarDate d; |
| int eraIndex = getEraIndex(date); |
| int year; |
| if (eraIndex == eras.length - 1) { |
| d = jcal.getCalendarDate(Long.MAX_VALUE, getZone()); |
| year = d.getYear(); |
| // Use an equivalent year for the |
| // getYearOffsetInMillis call to avoid overflow. |
| if (year > 400) { |
| jd.setYear(year - 400); |
| } |
| } else { |
| d = jcal.getCalendarDate(eras[eraIndex + 1].getSince(getZone()) - 1, getZone()); |
| year = d.getYear(); |
| // Use the same year as d.getYear() to be |
| // consistent with leap and common years. |
| jd.setYear(year); |
| } |
| jcal.normalize(jd); |
| if (getYearOffsetInMillis(jd) > getYearOffsetInMillis(d)) { |
| year--; |
| } |
| yield year; |
| } |
| default -> throw new ArrayIndexOutOfBoundsException(field); |
| }; |
| } |
| |
| /** |
| * 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. |
| */ |
| private transient long cachedFixedDate = Long.MIN_VALUE; |
| |
| /** |
| * Converts the time value (millisecond offset from the <a |
| * href="Calendar.html#Epoch">Epoch</a>) to calendar field values. |
| * The time is <em>not</em> |
| * recomputed first; to recompute the time, then the fields, call the |
| * {@code 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)) { |
| // BEGIN Android-changed: Android doesn't have sun.util.calendar.ZoneInfo. |
| // if (tz instanceof ZoneInfo) { |
| // zoneOffset = ((ZoneInfo)tz).getOffsets(time, zoneOffsets); |
| if (tz instanceof libcore.util.ZoneInfo) { |
| zoneOffset = ((libcore.util.ZoneInfo)tz).getOffsetsByUtcTime(time, zoneOffsets); |
| // END Android-changed: Android doesn't have sun.util.calendar.ZoneInfo. |
| } 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 Reiwa (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 > REIWA) { |
| 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 <a href="Calendar.html#Epoch">Epoch</a>). |
| * |
| * @throws 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 = currentEra; |
| 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)) { |
| // Android-changed: Android doesn't have sun.util.calendar.ZoneInfo. |
| // 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) : currentEra; |
| } |
| |
| /** |
| * Updates internal state. |
| */ |
| @java.io.Serial |
| private void readObject(ObjectInputStream stream) |
| throws IOException, ClassNotFoundException { |
| stream.defaultReadObject(); |
| if (jdate == null) { |
| jdate = jcal.newCalendarDate(getZone()); |
| cachedFixedDate = Long.MIN_VALUE; |
| } |
| } |
| } |