6669 lines
269 KiB
Java
6669 lines
269 KiB
Java
![]() |
/* GENERATED SOURCE. DO NOT MODIFY. */
|
||
|
// © 2016 and later: Unicode, Inc. and others.
|
||
|
// License & terms of use: http://www.unicode.org/copyright.html
|
||
|
/*
|
||
|
* Copyright (C) 1996-2016, International Business Machines
|
||
|
* Corporation and others. All Rights Reserved.
|
||
|
*/
|
||
|
|
||
|
package android.icu.util;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
import java.io.ObjectInputStream;
|
||
|
import java.io.ObjectOutputStream;
|
||
|
import java.io.Serializable;
|
||
|
import java.text.StringCharacterIterator;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Date;
|
||
|
import java.util.Locale;
|
||
|
import java.util.MissingResourceException;
|
||
|
|
||
|
import android.icu.impl.CalType;
|
||
|
import android.icu.impl.CalendarUtil;
|
||
|
import android.icu.impl.ICUCache;
|
||
|
import android.icu.impl.ICUData;
|
||
|
import android.icu.impl.ICUResourceBundle;
|
||
|
import android.icu.impl.SimpleCache;
|
||
|
import android.icu.impl.SimpleFormatterImpl;
|
||
|
import android.icu.impl.SoftCache;
|
||
|
import android.icu.text.DateFormat;
|
||
|
import android.icu.text.DateFormatSymbols;
|
||
|
import android.icu.text.DateTimePatternGenerator;
|
||
|
import android.icu.text.SimpleDateFormat;
|
||
|
import android.icu.util.BasicTimeZone.LocalOption;
|
||
|
import android.icu.util.ULocale.Category;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu enhancement]</strong> ICU's replacement for {@link java.util.Calendar}. Methods, fields, and other functionality specific to ICU are labeled '<strong>[icu]</strong>'.
|
||
|
*
|
||
|
* <p><code>Calendar</code> is an abstract base class for converting between
|
||
|
* a <code>Date</code> object and a set of integer fields such as
|
||
|
* <code>YEAR</code>, <code>MONTH</code>, <code>DAY</code>, <code>HOUR</code>,
|
||
|
* and so on. (A <code>Date</code> object represents a specific instant in
|
||
|
* time with millisecond precision. See
|
||
|
* {@link Date}
|
||
|
* for information about the <code>Date</code> class.)
|
||
|
*
|
||
|
* <p>Subclasses of <code>Calendar</code> interpret a <code>Date</code>
|
||
|
* according to the rules of a specific calendar system. ICU4J contains
|
||
|
* several subclasses implementing different international calendar systems.
|
||
|
*
|
||
|
* <p>
|
||
|
* Like other locale-sensitive classes, <code>Calendar</code> provides a
|
||
|
* class method, <code>getInstance</code>, for getting a generally useful
|
||
|
* object of this type. <code>Calendar</code>'s <code>getInstance</code> method
|
||
|
* returns a calendar of a type appropriate to the locale, whose
|
||
|
* time fields have been initialized with the current date and time:
|
||
|
* <blockquote>
|
||
|
* <pre>Calendar rightNow = Calendar.getInstance()</pre>
|
||
|
* </blockquote>
|
||
|
*
|
||
|
* <p>When a <code>ULocale</code> is used by <code>getInstance</code>, its
|
||
|
* '<code>calendar</code>' tag and value are retrieved if present. If a recognized
|
||
|
* value is supplied, a calendar is provided and configured as appropriate.
|
||
|
* Currently recognized tags are "buddhist", "chinese", "coptic", "ethiopic",
|
||
|
* "gregorian", "hebrew", "islamic", "islamic-civil", "japanese", and "roc". For
|
||
|
* example: <blockquote>
|
||
|
* <pre>Calendar cal = Calendar.getInstance(new ULocale("en_US@calendar=japanese"));</pre>
|
||
|
* </blockquote> will return an instance of JapaneseCalendar (using en_US conventions for
|
||
|
* minimum days in first week, start day of week, et cetera).
|
||
|
*
|
||
|
* <p>A <code>Calendar</code> object can produce all the time field values
|
||
|
* needed to implement the date-time formatting for a particular language and
|
||
|
* calendar style (for example, Japanese-Gregorian, Japanese-Traditional).
|
||
|
* <code>Calendar</code> defines the range of values returned by certain fields,
|
||
|
* as well as their meaning. For example, the first month of the year has value
|
||
|
* <code>MONTH</code> == <code>JANUARY</code> for all calendars. Other values
|
||
|
* are defined by the concrete subclass, such as <code>ERA</code> and
|
||
|
* <code>YEAR</code>. See individual field documentation and subclass
|
||
|
* documentation for details.
|
||
|
*
|
||
|
* <p>When a <code>Calendar</code> is <em>lenient</em>, it accepts a wider range
|
||
|
* of field values than it produces. For example, a lenient
|
||
|
* <code>GregorianCalendar</code> interprets <code>MONTH</code> ==
|
||
|
* <code>JANUARY</code>, <code>DAY_OF_MONTH</code> == 32 as February 1. A
|
||
|
* non-lenient <code>GregorianCalendar</code> throws an exception when given
|
||
|
* out-of-range field settings. When calendars recompute field values for
|
||
|
* return by <code>get()</code>, they normalize them. For example, a
|
||
|
* <code>GregorianCalendar</code> always produces <code>DAY_OF_MONTH</code>
|
||
|
* values between 1 and the length of the month.
|
||
|
*
|
||
|
* <p><code>Calendar</code> defines a locale-specific seven day week using two
|
||
|
* parameters: the first day of the week and the minimal days in first week
|
||
|
* (from 1 to 7). These numbers are taken from the locale resource data when a
|
||
|
* <code>Calendar</code> is constructed. They may also be specified explicitly
|
||
|
* through the API.
|
||
|
*
|
||
|
* <p>When setting or getting the <code>WEEK_OF_MONTH</code> or
|
||
|
* <code>WEEK_OF_YEAR</code> fields, <code>Calendar</code> must determine the
|
||
|
* first week of the month or year as a reference point. The first week of a
|
||
|
* month or year is defined as the earliest seven day period beginning on
|
||
|
* <code>getFirstDayOfWeek()</code> and containing at least
|
||
|
* <code>getMinimalDaysInFirstWeek()</code> days of that month or year. Weeks
|
||
|
* numbered ..., -1, 0 precede the first week; weeks numbered 2, 3,... follow
|
||
|
* it. Note that the normalized numbering returned by <code>get()</code> may be
|
||
|
* different. For example, a specific <code>Calendar</code> subclass may
|
||
|
* designate the week before week 1 of a year as week <em>n</em> of the previous
|
||
|
* year.
|
||
|
*
|
||
|
* <p> When computing a <code>Date</code> from time fields, some special
|
||
|
* circumstances may arise: there may be insufficient information to compute the
|
||
|
* <code>Date</code> (such as only year and month but no day in the month),
|
||
|
* there may be inconsistent information (such as "Tuesday, July 15, 1996" --
|
||
|
* July 15, 1996 is actually a Monday), or the input time might be ambiguous
|
||
|
* because of time zone transition.
|
||
|
*
|
||
|
* <p><strong>Insufficient information.</strong> The calendar will use default
|
||
|
* information to specify the missing fields. This may vary by calendar; for
|
||
|
* the Gregorian calendar, the default for a field is the same as that of the
|
||
|
* start of the epoch: i.e., YEAR = 1970, MONTH = JANUARY, DATE = 1, etc.
|
||
|
*
|
||
|
* <p><strong>Inconsistent information.</strong> If fields conflict, the calendar
|
||
|
* will give preference to fields set more recently. For example, when
|
||
|
* determining the day, the calendar will look for one of the following
|
||
|
* combinations of fields. The most recent combination, as determined by the
|
||
|
* most recently set single field, will be used.
|
||
|
*
|
||
|
* <blockquote>
|
||
|
* <pre>
|
||
|
* MONTH + DAY_OF_MONTH
|
||
|
* MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
|
||
|
* MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
|
||
|
* DAY_OF_YEAR
|
||
|
* DAY_OF_WEEK + WEEK_OF_YEAR</pre>
|
||
|
* </blockquote>
|
||
|
*
|
||
|
* For the time of day:
|
||
|
*
|
||
|
* <blockquote>
|
||
|
* <pre>
|
||
|
* HOUR_OF_DAY
|
||
|
* AM_PM + HOUR</pre>
|
||
|
* </blockquote>
|
||
|
*
|
||
|
* <p><strong>Ambiguous Wall Clock Time.</strong> When time offset from UTC has
|
||
|
* changed, it produces an ambiguous time slot around the transition. For example,
|
||
|
* many US locations observe daylight saving time. On the date switching to daylight
|
||
|
* saving time in US, wall clock time jumps from 12:59 AM (standard) to 2:00 AM
|
||
|
* (daylight). Therefore, wall clock time from 1:00 AM to 1:59 AM do not exist on
|
||
|
* the date. When the input wall time fall into this missing time slot, the ICU
|
||
|
* Calendar resolves the time using the UTC offset before the transition by default.
|
||
|
* In this example, 1:30 AM is interpreted as 1:30 AM standard time (non-exist),
|
||
|
* so the final result will be 2:30 AM daylight time.
|
||
|
*
|
||
|
* <p>On the date switching back to standard time, wall clock time is moved back one
|
||
|
* hour at 2:00 AM. So wall clock time from 1:00 AM to 1:59 AM occur twice. In this
|
||
|
* case, the ICU Calendar resolves the time using the UTC offset after the transition
|
||
|
* by default. For example, 1:30 AM on the date is resolved as 1:30 AM standard time.
|
||
|
*
|
||
|
* <p>Ambiguous wall clock time resolution behaviors can be customized by Calendar APIs
|
||
|
* {@link #setRepeatedWallTimeOption(int)} and {@link #setSkippedWallTimeOption(int)}.
|
||
|
* These methods are available in ICU 49 or later versions.
|
||
|
*
|
||
|
* <p><strong>Note:</strong> for some non-Gregorian calendars, different
|
||
|
* fields may be necessary for complete disambiguation. For example, a full
|
||
|
* specification of the historial Arabic astronomical calendar requires year,
|
||
|
* month, day-of-month <em>and</em> day-of-week in some cases.
|
||
|
*
|
||
|
* <p><strong>Note:</strong> There are certain possible ambiguities in
|
||
|
* interpretation of certain singular times, which are resolved in the
|
||
|
* following ways:
|
||
|
* <ol>
|
||
|
* <li> 24:00:00 "belongs" to the following day. That is,
|
||
|
* 23:59 on Dec 31, 1969 < 24:00 on Jan 1, 1970 < 24:01:00 on Jan 1, 1970
|
||
|
*
|
||
|
* <li> Although historically not precise, midnight also belongs to "am",
|
||
|
* and noon belongs to "pm", so on the same day,
|
||
|
* 12:00 am (midnight) < 12:01 am, and 12:00 pm (noon) < 12:01 pm
|
||
|
* </ol>
|
||
|
*
|
||
|
* <p>The date or time format strings are not part of the definition of a
|
||
|
* calendar, as those must be modifiable or overridable by the user at
|
||
|
* runtime. Use {@link DateFormat}
|
||
|
* to format dates.
|
||
|
*
|
||
|
* <p><strong>Field manipulation methods</strong></p>
|
||
|
*
|
||
|
* <p><code>Calendar</code> fields can be changed using three methods:
|
||
|
* <code>set()</code>, <code>add()</code>, and <code>roll()</code>.</p>
|
||
|
*
|
||
|
* <p><strong><code>set(f, value)</code></strong> changes field
|
||
|
* <code>f</code> to <code>value</code>. In addition, it sets an
|
||
|
* internal member variable to indicate that field <code>f</code> has
|
||
|
* been changed. Although field <code>f</code> is changed immediately,
|
||
|
* the calendar's milliseconds is not recomputed until the next call to
|
||
|
* <code>get()</code>, <code>getTime()</code>, or
|
||
|
* <code>getTimeInMillis()</code> is made. Thus, multiple calls to
|
||
|
* <code>set()</code> do not trigger multiple, unnecessary
|
||
|
* computations. As a result of changing a field using
|
||
|
* <code>set()</code>, other fields may also change, depending on the
|
||
|
* field, the field value, and the calendar system. In addition,
|
||
|
* <code>get(f)</code> will not necessarily return <code>value</code>
|
||
|
* after the fields have been recomputed. The specifics are determined by
|
||
|
* the concrete calendar class.</p>
|
||
|
*
|
||
|
* <p><em>Example</em>: Consider a <code>GregorianCalendar</code>
|
||
|
* originally set to August 31, 1999. Calling <code>set(Calendar.MONTH,
|
||
|
* Calendar.SEPTEMBER)</code> sets the calendar to September 31,
|
||
|
* 1999. This is a temporary internal representation that resolves to
|
||
|
* October 1, 1999 if <code>getTime()</code>is then called. However, a
|
||
|
* call to <code>set(Calendar.DAY_OF_MONTH, 30)</code> before the call to
|
||
|
* <code>getTime()</code> sets the calendar to September 30, 1999, since
|
||
|
* no recomputation occurs after <code>set()</code> itself.</p>
|
||
|
*
|
||
|
* <p><strong><code>add(f, delta)</code></strong> adds <code>delta</code>
|
||
|
* to field <code>f</code>. This is equivalent to calling <code>set(f,
|
||
|
* get(f) + delta)</code> with two adjustments:</p>
|
||
|
*
|
||
|
* <blockquote>
|
||
|
* <p><strong>Add rule 1</strong>. The value of field <code>f</code>
|
||
|
* after the call minus the value of field <code>f</code> before the
|
||
|
* call is <code>delta</code>, modulo any overflow that has occurred in
|
||
|
* field <code>f</code>. 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><strong>Add rule 2</strong>. 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
|
||
|
* <code>f</code> 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</code> is a smaller field than
|
||
|
* <code>DAY_OF_MONTH</code>. 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>
|
||
|
* </blockquote>
|
||
|
*
|
||
|
* <p>In addition, unlike <code>set()</code>, <code>add()</code> forces
|
||
|
* an immediate recomputation of the calendar's milliseconds and all
|
||
|
* fields.</p>
|
||
|
*
|
||
|
* <p><em>Example</em>: Consider a <code>GregorianCalendar</code>
|
||
|
* originally set to August 31, 1999. Calling <code>add(Calendar.MONTH,
|
||
|
* 13)</code> sets the calendar to September 30, 2000. <strong>Add rule
|
||
|
* 1</strong> sets the <code>MONTH</code> field to September, since
|
||
|
* adding 13 months to August gives September of the next year. Since
|
||
|
* <code>DAY_OF_MONTH</code> cannot be 31 in September in a
|
||
|
* <code>GregorianCalendar</code>, <strong>add rule 2</strong> sets the
|
||
|
* <code>DAY_OF_MONTH</code> to 30, the closest possible value. Although
|
||
|
* it is a smaller field, <code>DAY_OF_WEEK</code> is not adjusted by
|
||
|
* rule 2, since it is expected to change when the month changes in a
|
||
|
* <code>GregorianCalendar</code>.</p>
|
||
|
*
|
||
|
* <p><strong><code>roll(f, delta)</code></strong> adds
|
||
|
* <code>delta</code> to field <code>f</code> without changing larger
|
||
|
* fields. This is equivalent to calling <code>add(f, delta)</code> with
|
||
|
* the following adjustment:</p>
|
||
|
*
|
||
|
* <blockquote>
|
||
|
* <p><strong>Roll rule</strong>. Larger fields are unchanged after the
|
||
|
* call. A larger field represents a larger unit of
|
||
|
* time. <code>DAY_OF_MONTH</code> is a larger field than
|
||
|
* <code>HOUR</code>.</p>
|
||
|
* </blockquote>
|
||
|
*
|
||
|
* <p><em>Example</em>: Consider a <code>GregorianCalendar</code>
|
||
|
* originally set to August 31, 1999. Calling <code>roll(Calendar.MONTH,
|
||
|
* 8)</code> sets the calendar to April 30, <strong>1999</strong>. Add
|
||
|
* rule 1 sets the <code>MONTH</code> field to April. Using a
|
||
|
* <code>GregorianCalendar</code>, the <code>DAY_OF_MONTH</code> cannot
|
||
|
* be 31 in the month April. Add rule 2 sets it to the closest possible
|
||
|
* value, 30. Finally, the <strong>roll rule</strong> maintains the
|
||
|
* <code>YEAR</code> field value of 1999.</p>
|
||
|
*
|
||
|
* <p><em>Example</em>: Consider a <code>GregorianCalendar</code>
|
||
|
* originally set to Sunday June 6, 1999. Calling
|
||
|
* <code>roll(Calendar.WEEK_OF_MONTH, -1)</code> sets the calendar to
|
||
|
* Tuesday June 1, 1999, whereas calling
|
||
|
* <code>add(Calendar.WEEK_OF_MONTH, -1)</code> sets the calendar to
|
||
|
* Sunday May 30, 1999. This is because the roll rule imposes an
|
||
|
* additional constraint: The <code>MONTH</code> must not change when the
|
||
|
* <code>WEEK_OF_MONTH</code> is rolled. Taken together with add rule 1,
|
||
|
* the resultant date must be between Tuesday June 1 and Saturday June
|
||
|
* 5. According to add rule 2, the <code>DAY_OF_WEEK</code>, an invariant
|
||
|
* when changing the <code>WEEK_OF_MONTH</code>, is set to Tuesday, the
|
||
|
* closest possible value to Sunday (where Sunday is the first day of the
|
||
|
* week).</p>
|
||
|
*
|
||
|
* <p><strong>Usage model</strong>. To motivate the behavior of
|
||
|
* <code>add()</code> and <code>roll()</code>, consider a user interface
|
||
|
* component with increment and decrement buttons for the month, day, and
|
||
|
* year, and an underlying <code>GregorianCalendar</code>. If the
|
||
|
* interface reads January 31, 1999 and the user presses the month
|
||
|
* increment button, what should it read? If the underlying
|
||
|
* implementation uses <code>set()</code>, it might read March 3, 1999. A
|
||
|
* better result would be February 28, 1999. Furthermore, if the user
|
||
|
* presses the month increment button again, it should read March 31,
|
||
|
* 1999, not March 28, 1999. By saving the original date and using either
|
||
|
* <code>add()</code> or <code>roll()</code>, depending on whether larger
|
||
|
* fields should be affected, the user interface can behave as most users
|
||
|
* will intuitively expect.</p>
|
||
|
*
|
||
|
* <p><b>Note:</b> You should always use {@link #roll roll} and {@link #add add} rather
|
||
|
* than attempting to perform arithmetic operations directly on the fields
|
||
|
* of a <tt>Calendar</tt>. It is quite possible for <tt>Calendar</tt> subclasses
|
||
|
* to have fields with non-linear behavior, for example missing months
|
||
|
* or days during non-leap years. The subclasses' <tt>add</tt> and <tt>roll</tt>
|
||
|
* methods will take this into account, while simple arithmetic manipulations
|
||
|
* may give invalid results.
|
||
|
*
|
||
|
* <p><big><big><b>Calendar Architecture in ICU4J</b></big></big></p>
|
||
|
*
|
||
|
* <p>Recently the implementation of <code>Calendar</code> has changed
|
||
|
* significantly in order to better support subclassing. The original
|
||
|
* <code>Calendar</code> class was designed to support subclassing, but
|
||
|
* it had only one implemented subclass, <code>GregorianCalendar</code>.
|
||
|
* With the implementation of several new calendar subclasses, including
|
||
|
* the <code>BuddhistCalendar</code>, <code>ChineseCalendar</code>,
|
||
|
* <code>HebrewCalendar</code>, <code>IslamicCalendar</code>, and
|
||
|
* <code>JapaneseCalendar</code>, the subclassing API has been reworked
|
||
|
* thoroughly. This section details the new subclassing API and other
|
||
|
* ways in which <code>android.icu.util.Calendar</code> differs from
|
||
|
* <code>java.util.Calendar</code>.
|
||
|
* </p>
|
||
|
*
|
||
|
* <p><big><b>Changes</b></big></p>
|
||
|
*
|
||
|
* <p>Overview of changes between the classic <code>Calendar</code>
|
||
|
* architecture and the new architecture.
|
||
|
*
|
||
|
* <ul>
|
||
|
*
|
||
|
* <li>The <code>fields[]</code> array is <code>private</code> now
|
||
|
* instead of <code>protected</code>. Subclasses must access it
|
||
|
* using the methods {@link #internalSet} and
|
||
|
* {@link #internalGet}. <b>Motivation:</b> Subclasses should
|
||
|
* not directly access data members.</li>
|
||
|
*
|
||
|
* <li>The <code>time</code> long word is <code>private</code> now
|
||
|
* instead of <code>protected</code>. Subclasses may access it using
|
||
|
* the method {@link #internalGetTimeInMillis}, which does not
|
||
|
* provoke an update. <b>Motivation:</b> Subclasses should not
|
||
|
* directly access data members.</li>
|
||
|
*
|
||
|
* <li>The scope of responsibility of subclasses has been drastically
|
||
|
* reduced. As much functionality as possible is implemented in the
|
||
|
* <code>Calendar</code> base class. As a result, it is much easier
|
||
|
* to subclass <code>Calendar</code>. <b>Motivation:</b> Subclasses
|
||
|
* should not have to reimplement common code. Certain behaviors are
|
||
|
* common across calendar systems: The definition and behavior of
|
||
|
* week-related fields and time fields, the arithmetic
|
||
|
* ({@link #add(int, int) add} and {@link #roll(int, int) roll}) behavior of many
|
||
|
* fields, and the field validation system.</li>
|
||
|
*
|
||
|
* <li>The subclassing API has been completely redesigned.</li>
|
||
|
*
|
||
|
* <li>The <code>Calendar</code> base class contains some Gregorian
|
||
|
* calendar algorithmic support that subclasses can use (specifically
|
||
|
* in {@link #handleComputeFields}). Subclasses can use the
|
||
|
* methods <code>getGregorianXxx()</code> to obtain precomputed
|
||
|
* values. <b>Motivation:</b> This is required by all
|
||
|
* <code>Calendar</code> subclasses in order to implement consistent
|
||
|
* time zone behavior, and Gregorian-derived systems can use the
|
||
|
* already computed data.</li>
|
||
|
*
|
||
|
* <li>The <code>FIELD_COUNT</code> constant has been removed. Use
|
||
|
* {@link #getFieldCount}. In addition, framework API has been
|
||
|
* added to allow subclasses to define additional fields.
|
||
|
* <b>Motivation: </b>The number of fields is not constant across
|
||
|
* calendar systems.</li>
|
||
|
*
|
||
|
* <li>The range of handled dates has been narrowed from +/-
|
||
|
* ~300,000,000 years to +/- ~5,000,000 years. In practical terms
|
||
|
* this should not affect clients. However, it does mean that client
|
||
|
* code cannot be guaranteed well-behaved results with dates such as
|
||
|
* <code>Date(Long.MIN_VALUE)</code> or
|
||
|
* <code>Date(Long.MAX_VALUE)</code>. Instead, the
|
||
|
* <code>Calendar</code> protected constants should be used.
|
||
|
* <b>Motivation:</b> With
|
||
|
* the addition of the {@link #JULIAN_DAY} field, Julian day
|
||
|
* numbers must be restricted to a 32-bit <code>int</code>. This
|
||
|
* restricts the overall supported range. Furthermore, restricting
|
||
|
* the supported range simplifies the computations by removing
|
||
|
* special case code that was used to accommodate arithmetic overflow
|
||
|
* at millis near <code>Long.MIN_VALUE</code> and
|
||
|
* <code>Long.MAX_VALUE</code>.</li>
|
||
|
*
|
||
|
* <li>New fields are implemented: {@link #JULIAN_DAY} defines
|
||
|
* single-field specification of the
|
||
|
* date. {@link #MILLISECONDS_IN_DAY} defines a single-field
|
||
|
* specification of the wall time. {@link #DOW_LOCAL} and
|
||
|
* {@link #YEAR_WOY} implement localized day-of-week and
|
||
|
* week-of-year behavior.</li>
|
||
|
*
|
||
|
* <li>Subclasses can access protected millisecond constants
|
||
|
* defined in <code>Calendar</code>.</li>
|
||
|
*
|
||
|
* <li>New API has been added to support calendar-specific subclasses
|
||
|
* of <code>DateFormat</code>.</li>
|
||
|
*
|
||
|
* <li>Several subclasses have been implemented, representing
|
||
|
* various international calendar systems.</li>
|
||
|
*
|
||
|
* </ul>
|
||
|
*
|
||
|
* <p><big><b>Subclass API</b></big></p>
|
||
|
*
|
||
|
* <p>The original <code>Calendar</code> API was based on the experience
|
||
|
* of implementing a only a single subclass,
|
||
|
* <code>GregorianCalendar</code>. As a result, all of the subclassing
|
||
|
* kinks had not been worked out. The new subclassing API has been
|
||
|
* refined based on several implemented subclasses. This includes methods
|
||
|
* that must be overridden and methods for subclasses to call. Subclasses
|
||
|
* no longer have direct access to <code>fields</code> and
|
||
|
* <code>stamp</code>. Instead, they have new API to access
|
||
|
* these. Subclasses are able to allocate the <code>fields</code> array
|
||
|
* through a protected framework method; this allows subclasses to
|
||
|
* specify additional fields. </p>
|
||
|
*
|
||
|
* <p>More functionality has been moved into the base class. The base
|
||
|
* class now contains much of the computational machinery to support the
|
||
|
* Gregorian calendar. This is based on two things: (1) Many calendars
|
||
|
* are based on the Gregorian calendar (such as the Buddhist and Japanese
|
||
|
* imperial calendars). (2) <em>All</em> calendars require basic
|
||
|
* Gregorian support in order to handle timezone computations. </p>
|
||
|
*
|
||
|
* <p>Common computations have been moved into
|
||
|
* <code>Calendar</code>. Subclasses no longer compute the week related
|
||
|
* fields and the time related fields. These are commonly handled for all
|
||
|
* calendars by the base class. </p>
|
||
|
*
|
||
|
* <p><b>Subclass computation of time <tt>=></tt> fields</b>
|
||
|
*
|
||
|
* <p>The {@link #ERA}, {@link #YEAR},
|
||
|
* {@link #EXTENDED_YEAR}, {@link #MONTH},
|
||
|
* {@link #DAY_OF_MONTH}, and {@link #DAY_OF_YEAR} fields are
|
||
|
* computed by the subclass, based on the Julian day. All other fields
|
||
|
* are computed by <code>Calendar</code>.
|
||
|
*
|
||
|
* <ul>
|
||
|
*
|
||
|
* <li>Subclasses should implement {@link #handleComputeFields}
|
||
|
* to compute the {@link #ERA}, {@link #YEAR},
|
||
|
* {@link #EXTENDED_YEAR}, {@link #MONTH},
|
||
|
* {@link #DAY_OF_MONTH}, and {@link #DAY_OF_YEAR} fields,
|
||
|
* based on the value of the {@link #JULIAN_DAY} field. If there
|
||
|
* are calendar-specific fields not defined by <code>Calendar</code>,
|
||
|
* they must also be computed. These are the only fields that the
|
||
|
* subclass should compute. All other fields are computed by the base
|
||
|
* class, so time and week fields behave in a consistent way across
|
||
|
* all calendars. The default version of this method in
|
||
|
* <code>Calendar</code> implements a proleptic Gregorian
|
||
|
* calendar. Within this method, subclasses may call
|
||
|
* <code>getGregorianXxx()</code> to obtain the Gregorian calendar
|
||
|
* month, day of month, and extended year for the given date.</li>
|
||
|
*
|
||
|
* </ul>
|
||
|
*
|
||
|
* <p><b>Subclass computation of fields <tt>=></tt> time</b>
|
||
|
*
|
||
|
* <p>The interpretation of most field values is handled entirely by
|
||
|
* <code>Calendar</code>. <code>Calendar</code> determines which fields
|
||
|
* are set, which are not, which are set more recently, and so on. In
|
||
|
* addition, <code>Calendar</code> handles the computation of the time
|
||
|
* from the time fields and handles the week-related fields. The only
|
||
|
* thing the subclass must do is determine the extended year, based on
|
||
|
* the year fields, and then, given an extended year and a month, it must
|
||
|
* return a Julian day number.
|
||
|
*
|
||
|
* <ul>
|
||
|
*
|
||
|
* <li>Subclasses should implement {@link #handleGetExtendedYear}
|
||
|
* to return the extended year for this calendar system, based on the
|
||
|
* {@link #YEAR}, {@link #EXTENDED_YEAR}, and any fields that
|
||
|
* the calendar system uses that are larger than a year, such as
|
||
|
* {@link #ERA}.</li>
|
||
|
*
|
||
|
* <li>Subclasses should implement {@link #handleComputeMonthStart}
|
||
|
* to return the Julian day number
|
||
|
* associated with a month and extended year. This is the Julian day
|
||
|
* number of the day before the first day of the month. The month
|
||
|
* number is zero-based. This computation should not depend on any
|
||
|
* field values.</li>
|
||
|
*
|
||
|
* </ul>
|
||
|
*
|
||
|
* <p><b>Other methods</b>
|
||
|
*
|
||
|
* <ul>
|
||
|
*
|
||
|
* <li>Subclasses should implement {@link #handleGetMonthLength}
|
||
|
* to return the number of days in a
|
||
|
* given month of a given extended year. The month number, as always,
|
||
|
* is zero-based.</li>
|
||
|
*
|
||
|
* <li>Subclasses should implement {@link #handleGetYearLength}
|
||
|
* to return the number of days in the given
|
||
|
* extended year. This method is used by
|
||
|
* <tt>computeWeekFields</tt> to compute the
|
||
|
* {@link #WEEK_OF_YEAR} and {@link #YEAR_WOY} fields.</li>
|
||
|
*
|
||
|
* <li>Subclasses should implement {@link #handleGetLimit}
|
||
|
* to return the protected values of a field, depending on the value of
|
||
|
* <code>limitType</code>. This method only needs to handle the
|
||
|
* fields {@link #ERA}, {@link #YEAR}, {@link #MONTH},
|
||
|
* {@link #WEEK_OF_YEAR}, {@link #WEEK_OF_MONTH},
|
||
|
* {@link #DAY_OF_MONTH}, {@link #DAY_OF_YEAR},
|
||
|
* {@link #DAY_OF_WEEK_IN_MONTH}, {@link #YEAR_WOY}, and
|
||
|
* {@link #EXTENDED_YEAR}. Other fields are invariant (with
|
||
|
* respect to calendar system) and are handled by the base
|
||
|
* class.</li>
|
||
|
*
|
||
|
* <li>Optionally, subclasses may override {@link #validateField}
|
||
|
* to check any subclass-specific fields. If the
|
||
|
* field's value is out of range, the method should throw an
|
||
|
* <code>IllegalArgumentException</code>. The method may call
|
||
|
* <code>super.validateField(field)</code> to handle fields in a
|
||
|
* generic way, that is, to compare them to the range
|
||
|
* <code>getMinimum(field)</code>..<code>getMaximum(field)</code>.</li>
|
||
|
*
|
||
|
* <li>Optionally, subclasses may override
|
||
|
* {@link #handleCreateFields} to create an <code>int[]</code>
|
||
|
* array large enough to hold the calendar's fields. This is only
|
||
|
* necessary if the calendar defines additional fields beyond those
|
||
|
* defined by <code>Calendar</code>. The length of the result must be
|
||
|
* be between the base and maximum field counts.</li>
|
||
|
*
|
||
|
* <li>Optionally, subclasses may override
|
||
|
* {@link #handleGetDateFormat} to create a
|
||
|
* <code>DateFormat</code> appropriate to this calendar. This is only
|
||
|
* required if a calendar subclass redefines the use of a field (for
|
||
|
* example, changes the {@link #ERA} field from a symbolic field
|
||
|
* to a numeric one) or defines an additional field.</li>
|
||
|
*
|
||
|
* <li>Optionally, subclasses may override {@link #roll roll} and
|
||
|
* {@link #add add} to handle fields that are discontinuous. For
|
||
|
* example, in the Hebrew calendar the month "Adar I" only
|
||
|
* occurs in leap years; in other years the calendar jumps from
|
||
|
* Shevat (month #4) to Adar (month #6). The {@link
|
||
|
* HebrewCalendar#add HebrewCalendar.add} and {@link
|
||
|
* HebrewCalendar#roll HebrewCalendar.roll} methods take this into
|
||
|
* account, so that adding 1 month to Shevat gives the proper result
|
||
|
* (Adar) in a non-leap year. The protected utility method {@link
|
||
|
* #pinField pinField} is often useful when implementing these two
|
||
|
* methods. </li>
|
||
|
*
|
||
|
* </ul>
|
||
|
*
|
||
|
* <p><big><b>Normalized behavior</b></big>
|
||
|
*
|
||
|
* <p>The behavior of certain fields has been made consistent across all
|
||
|
* calendar systems and implemented in <code>Calendar</code>.
|
||
|
*
|
||
|
* <ul>
|
||
|
*
|
||
|
* <li>Time is normalized. Even though some calendar systems transition
|
||
|
* between days at sunset or at other times, all ICU4J calendars
|
||
|
* transition between days at <em>local zone midnight</em>. This
|
||
|
* allows ICU4J to centralize the time computations in
|
||
|
* <code>Calendar</code> and to maintain basic correspondences
|
||
|
* between calendar systems. Affected fields: {@link #AM_PM},
|
||
|
* {@link #HOUR}, {@link #HOUR_OF_DAY}, {@link #MINUTE},
|
||
|
* {@link #SECOND}, {@link #MILLISECOND},
|
||
|
* {@link #ZONE_OFFSET}, and {@link #DST_OFFSET}.</li>
|
||
|
*
|
||
|
* <li>DST behavior is normalized. Daylight savings time behavior is
|
||
|
* computed the same for all calendar systems, and depends on the
|
||
|
* value of several <code>GregorianCalendar</code> fields: the
|
||
|
* {@link #YEAR}, {@link #MONTH}, and
|
||
|
* {@link #DAY_OF_MONTH}. As a result, <code>Calendar</code>
|
||
|
* always computes these fields, even for non-Gregorian calendar
|
||
|
* systems. These fields are available to subclasses.</li>
|
||
|
*
|
||
|
* <li>Weeks are normalized. Although locales define the week
|
||
|
* differently, in terms of the day on which it starts, and the
|
||
|
* designation of week number one of a month or year, they all use a
|
||
|
* common mechanism. Furthermore, the day of the week has a simple
|
||
|
* and consistent definition throughout history. For example,
|
||
|
* although the Gregorian calendar introduced a discontinuity when
|
||
|
* first instituted, the day of week was not disrupted. For this
|
||
|
* reason, the fields {@link #DAY_OF_WEEK}, <code>WEEK_OF_YEAR,
|
||
|
* WEEK_OF_MONTH</code>, {@link #DAY_OF_WEEK_IN_MONTH},
|
||
|
* {@link #DOW_LOCAL}, {@link #YEAR_WOY} are all computed in
|
||
|
* a consistent way in the base class, based on the
|
||
|
* {@link #EXTENDED_YEAR}, {@link #DAY_OF_YEAR},
|
||
|
* {@link #MONTH}, and {@link #DAY_OF_MONTH}, which are
|
||
|
* computed by the subclass.</li>
|
||
|
*
|
||
|
* </ul>
|
||
|
*
|
||
|
* <p><big><b>Supported range</b></big>
|
||
|
*
|
||
|
* <p>The allowable range of <code>Calendar</code> has been
|
||
|
* narrowed. <code>GregorianCalendar</code> used to attempt to support
|
||
|
* the range of dates with millisecond values from
|
||
|
* <code>Long.MIN_VALUE</code> to <code>Long.MAX_VALUE</code>. This
|
||
|
* introduced awkward constructions (hacks) which slowed down
|
||
|
* performance. It also introduced non-uniform behavior at the
|
||
|
* boundaries. The new <code>Calendar</code> protocol specifies the
|
||
|
* maximum range of supportable dates as those having Julian day numbers
|
||
|
* of <code>-0x7F000000</code> to <code>+0x7F000000</code>. This
|
||
|
* corresponds to years from ~5,800,000 BCE to ~5,800,000 CE. Programmers
|
||
|
* should use the protected constants in <code>Calendar</code> to
|
||
|
* specify an extremely early or extremely late date.</p>
|
||
|
*
|
||
|
* <p><big><b>General notes</b></big>
|
||
|
*
|
||
|
* <ul>
|
||
|
*
|
||
|
* <li>Calendars implementations are <em>proleptic</em>. For example,
|
||
|
* even though the Gregorian calendar was not instituted until the
|
||
|
* 16th century, the <code>GregorianCalendar</code> class supports
|
||
|
* dates before the historical onset of the calendar by extending the
|
||
|
* calendar system backward in time. Similarly, the
|
||
|
* <code>HebrewCalendar</code> extends backward before the start of
|
||
|
* its epoch into zero and negative years. Subclasses do not throw
|
||
|
* exceptions because a date precedes the historical start of a
|
||
|
* calendar system. Instead, they implement
|
||
|
* {@link #handleGetLimit} to return appropriate limits on
|
||
|
* {@link #YEAR}, {@link #ERA}, etc. fields. Then, if the
|
||
|
* calendar is set to not be lenient, out-of-range field values will
|
||
|
* trigger an exception.</li>
|
||
|
*
|
||
|
* <li>Calendar system subclasses compute a <em>extended
|
||
|
* year</em>. This differs from the {@link #YEAR} field in that
|
||
|
* it ranges over all integer values, including zero and negative
|
||
|
* values, and it encapsulates the information of the
|
||
|
* {@link #YEAR} field and all larger fields. Thus, for the
|
||
|
* Gregorian calendar, the {@link #EXTENDED_YEAR} is computed as
|
||
|
* <code>ERA==AD ? YEAR : 1-YEAR</code>. Another example is the Mayan
|
||
|
* long count, which has years (<code>KUN</code>) and nested cycles
|
||
|
* of years (<code>KATUN</code> and <code>BAKTUN</code>). The Mayan
|
||
|
* {@link #EXTENDED_YEAR} is computed as <code>TUN + 20 * (KATUN
|
||
|
* + 20 * BAKTUN)</code>. The <code>Calendar</code> base class uses
|
||
|
* the {@link #EXTENDED_YEAR} field to compute the week-related
|
||
|
* fields.</li>
|
||
|
*
|
||
|
* </ul>
|
||
|
*
|
||
|
* @see Date
|
||
|
* @see GregorianCalendar
|
||
|
* @see TimeZone
|
||
|
* @see DateFormat
|
||
|
* @author Mark Davis, Deborah Goldsmith, Chen-Lieh Huang, Alan Liu, Laura Werner
|
||
|
*/
|
||
|
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
|
||
|
|
||
|
// Data flow in Calendar
|
||
|
// ---------------------
|
||
|
|
||
|
// The current time is represented in two ways by Calendar: as UTC
|
||
|
// milliseconds from the epoch start (1 January 1970 0:00 UTC), and as local
|
||
|
// fields such as MONTH, HOUR, AM_PM, etc. It is possible to compute the
|
||
|
// millis from the fields, and vice versa. The data needed to do this
|
||
|
// conversion is encapsulated by a TimeZone object owned by the Calendar.
|
||
|
// The data provided by the TimeZone object may also be overridden if the
|
||
|
// user sets the ZONE_OFFSET and/or DST_OFFSET fields directly. The class
|
||
|
// keeps track of what information was most recently set by the caller, and
|
||
|
// uses that to compute any other information as needed.
|
||
|
|
||
|
// If the user sets the fields using set(), the data flow is as follows.
|
||
|
// This is implemented by the Calendar subclass's computeTime() method.
|
||
|
// During this process, certain fields may be ignored. The disambiguation
|
||
|
// algorithm for resolving which fields to pay attention to is described
|
||
|
// above.
|
||
|
|
||
|
// local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.)
|
||
|
// |
|
||
|
// | Using Calendar-specific algorithm
|
||
|
// V
|
||
|
// local standard millis
|
||
|
// |
|
||
|
// | Using TimeZone or user-set ZONE_OFFSET / DST_OFFSET
|
||
|
// V
|
||
|
// UTC millis (in time data member)
|
||
|
|
||
|
// If the user sets the UTC millis using setTime(), the data flow is as
|
||
|
// follows. This is implemented by the Calendar subclass's computeFields()
|
||
|
// method.
|
||
|
|
||
|
// UTC millis (in time data member)
|
||
|
// |
|
||
|
// | Using TimeZone getOffset()
|
||
|
// V
|
||
|
// local standard millis
|
||
|
// |
|
||
|
// | Using Calendar-specific algorithm
|
||
|
// V
|
||
|
// local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.)
|
||
|
|
||
|
// In general, a round trip from fields, through local and UTC millis, and
|
||
|
// back out to fields is made when necessary. This is implemented by the
|
||
|
// complete() method. Resolving a partial set of fields into a UTC millis
|
||
|
// value allows all remaining fields to be generated from that value. If
|
||
|
// the Calendar is lenient, the fields are also renormalized to standard
|
||
|
// ranges when they are regenerated.
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* era, e.g., AD or BC in the Julian calendar. This is a calendar-specific
|
||
|
* value; see subclass documentation.
|
||
|
* @see GregorianCalendar#AD
|
||
|
* @see GregorianCalendar#BC
|
||
|
*/
|
||
|
public final static int ERA = 0;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* year. This is a calendar-specific value; see subclass documentation.
|
||
|
*/
|
||
|
public final static int YEAR = 1;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* month. This is a calendar-specific value. The first month of the year is
|
||
|
* <code>JANUARY</code>; the last depends on the number of months in a year.
|
||
|
* @see #JANUARY
|
||
|
* @see #FEBRUARY
|
||
|
* @see #MARCH
|
||
|
* @see #APRIL
|
||
|
* @see #MAY
|
||
|
* @see #JUNE
|
||
|
* @see #JULY
|
||
|
* @see #AUGUST
|
||
|
* @see #SEPTEMBER
|
||
|
* @see #OCTOBER
|
||
|
* @see #NOVEMBER
|
||
|
* @see #DECEMBER
|
||
|
* @see #UNDECIMBER
|
||
|
*/
|
||
|
public final static int MONTH = 2;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* week number within the current year. The first week of the year, as
|
||
|
* defined by {@link #getFirstDayOfWeek()} and
|
||
|
* {@link #getMinimalDaysInFirstWeek()}, has value 1. Subclasses define
|
||
|
* the value of {@link #WEEK_OF_YEAR} for days before the first week of
|
||
|
* the year.
|
||
|
* @see #getFirstDayOfWeek
|
||
|
* @see #getMinimalDaysInFirstWeek
|
||
|
*/
|
||
|
public final static int WEEK_OF_YEAR = 3;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* week number within the current month. The first week of the month, as
|
||
|
* defined by {@link #getFirstDayOfWeek()} and
|
||
|
* {@link #getMinimalDaysInFirstWeek()}, has value 1. Subclasses define
|
||
|
* the value of {@link #WEEK_OF_MONTH} for days before the first week of
|
||
|
* the month.
|
||
|
* @see #getFirstDayOfWeek
|
||
|
* @see #getMinimalDaysInFirstWeek
|
||
|
*/
|
||
|
public final static int WEEK_OF_MONTH = 4;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* day of the month. This is a synonym for {@link #DAY_OF_MONTH}.
|
||
|
* The first day of the month has value 1.
|
||
|
* @see #DAY_OF_MONTH
|
||
|
*/
|
||
|
public final static int DATE = 5;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* day of the month. This is a synonym for {@link #DATE}.
|
||
|
* The first day of the month has value 1.
|
||
|
* @see #DATE
|
||
|
*/
|
||
|
public final static int DAY_OF_MONTH = 5;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the day
|
||
|
* number within the current year. The first day of the year has value 1.
|
||
|
*/
|
||
|
public final static int DAY_OF_YEAR = 6;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the day
|
||
|
* of the week. This field takes values {@link #SUNDAY},
|
||
|
* {@link #MONDAY}, {@link #TUESDAY}, {@link #WEDNESDAY},
|
||
|
* {@link #THURSDAY}, {@link #FRIDAY}, and {@link #SATURDAY}.
|
||
|
* @see #SUNDAY
|
||
|
* @see #MONDAY
|
||
|
* @see #TUESDAY
|
||
|
* @see #WEDNESDAY
|
||
|
* @see #THURSDAY
|
||
|
* @see #FRIDAY
|
||
|
* @see #SATURDAY
|
||
|
*/
|
||
|
public final static int DAY_OF_WEEK = 7;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* ordinal number of the day of the week within the current month. Together
|
||
|
* with the {@link #DAY_OF_WEEK} field, this uniquely specifies a day
|
||
|
* within a month. Unlike {@link #WEEK_OF_MONTH} and
|
||
|
* {@link #WEEK_OF_YEAR}, this field's value does <em>not</em> depend on
|
||
|
* {@link #getFirstDayOfWeek()} or
|
||
|
* {@link #getMinimalDaysInFirstWeek()}. <code>DAY_OF_MONTH 1</code>
|
||
|
* through <code>7</code> always correspond to <code>DAY_OF_WEEK_IN_MONTH
|
||
|
* 1</code>; <code>8</code> through <code>15</code> correspond to
|
||
|
* <code>DAY_OF_WEEK_IN_MONTH 2</code>, and so on.
|
||
|
* <code>DAY_OF_WEEK_IN_MONTH 0</code> indicates the week before
|
||
|
* <code>DAY_OF_WEEK_IN_MONTH 1</code>. Negative values count back from the
|
||
|
* end of the month, so the last Sunday of a month is specified as
|
||
|
* <code>DAY_OF_WEEK = SUNDAY, DAY_OF_WEEK_IN_MONTH = -1</code>. Because
|
||
|
* negative values count backward they will usually be aligned differently
|
||
|
* within the month than positive values. For example, if a month has 31
|
||
|
* days, <code>DAY_OF_WEEK_IN_MONTH -1</code> will overlap
|
||
|
* <code>DAY_OF_WEEK_IN_MONTH 5</code> and the end of <code>4</code>.
|
||
|
* @see #DAY_OF_WEEK
|
||
|
* @see #WEEK_OF_MONTH
|
||
|
*/
|
||
|
public final static int DAY_OF_WEEK_IN_MONTH = 8;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating
|
||
|
* whether the <code>HOUR</code> is before or after noon.
|
||
|
* E.g., at 10:04:15.250 PM the <code>AM_PM</code> is <code>PM</code>.
|
||
|
* @see #AM
|
||
|
* @see #PM
|
||
|
* @see #HOUR
|
||
|
*/
|
||
|
public final static int AM_PM = 9;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* hour of the morning or afternoon. <code>HOUR</code> is used for the 12-hour
|
||
|
* clock.
|
||
|
* E.g., at 10:04:15.250 PM the <code>HOUR</code> is 10.
|
||
|
* @see #AM_PM
|
||
|
* @see #HOUR_OF_DAY
|
||
|
*/
|
||
|
public final static int HOUR = 10;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* hour of the day. <code>HOUR_OF_DAY</code> is used for the 24-hour clock.
|
||
|
* E.g., at 10:04:15.250 PM the <code>HOUR_OF_DAY</code> is 22.
|
||
|
* @see #HOUR
|
||
|
*/
|
||
|
public final static int HOUR_OF_DAY = 11;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* minute within the hour.
|
||
|
* E.g., at 10:04:15.250 PM the <code>MINUTE</code> is 4.
|
||
|
*/
|
||
|
public final static int MINUTE = 12;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* second within the minute.
|
||
|
* E.g., at 10:04:15.250 PM the <code>SECOND</code> is 15.
|
||
|
*/
|
||
|
public final static int SECOND = 13;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* millisecond within the second.
|
||
|
* E.g., at 10:04:15.250 PM the <code>MILLISECOND</code> is 250.
|
||
|
*/
|
||
|
public final static int MILLISECOND = 14;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* raw offset from GMT in milliseconds.
|
||
|
*/
|
||
|
public final static int ZONE_OFFSET = 15;
|
||
|
|
||
|
/**
|
||
|
* Field number for <code>get</code> and <code>set</code> indicating the
|
||
|
* daylight savings offset in milliseconds.
|
||
|
*/
|
||
|
public final static int DST_OFFSET = 16;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Field number for <code>get()</code> and <code>set()</code>
|
||
|
* indicating the extended year corresponding to the
|
||
|
* {@link #WEEK_OF_YEAR} field. This may be one greater or less
|
||
|
* than the value of {@link #EXTENDED_YEAR}.
|
||
|
*/
|
||
|
public static final int YEAR_WOY = 17;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Field number for <code>get()</code> and <code>set()</code>
|
||
|
* indicating the localized day of week. This will be a value from 1
|
||
|
* to 7 inclusive, with 1 being the localized first day of the week.
|
||
|
*/
|
||
|
public static final int DOW_LOCAL = 18;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Field number for <code>get()</code> and <code>set()</code>
|
||
|
* indicating the extended year. This is a single number designating
|
||
|
* the year of this calendar system, encompassing all supra-year
|
||
|
* fields. For example, for the Julian calendar system, year numbers
|
||
|
* are positive, with an era of BCE or CE. An extended year value for
|
||
|
* the Julian calendar system assigns positive values to CE years and
|
||
|
* negative values to BCE years, with 1 BCE being year 0.
|
||
|
*/
|
||
|
public static final int EXTENDED_YEAR = 19;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Field number for <code>get()</code> and <code>set()</code>
|
||
|
* indicating the modified Julian day number. This is different from
|
||
|
* the conventional Julian day number in two regards. First, it
|
||
|
* demarcates days at local zone midnight, rather than noon GMT.
|
||
|
* Second, it is a local number; that is, it depends on the local time
|
||
|
* zone. It can be thought of as a single number that encompasses all
|
||
|
* the date-related fields.
|
||
|
*/
|
||
|
public static final int JULIAN_DAY = 20;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Field number for <code>get()</code> and <code>set()</code>
|
||
|
* indicating the milliseconds in the day. This ranges from 0 to
|
||
|
* 23:59:59.999 (regardless of DST). This field behaves
|
||
|
* <em>exactly</em> like a composite of all time-related fields, not
|
||
|
* including the zone fields. As such, it also reflects
|
||
|
* discontinuities of those fields on DST transition days. On a day of
|
||
|
* DST onset, it will jump forward. On a day of DST cessation, it will
|
||
|
* jump backward. This reflects the fact that is must be combined with
|
||
|
* the DST_OFFSET field to obtain a unique local time value.
|
||
|
*/
|
||
|
public static final int MILLISECONDS_IN_DAY = 21;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Field indicating whether or not the current month is a leap month.
|
||
|
* Should have a value of 0 for non-leap months, and 1 for leap months.
|
||
|
*/
|
||
|
public static final int IS_LEAP_MONTH = 22;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Field indicating the month. This is a calendar-specific value.
|
||
|
* Differ from MONTH, this value is continuous and unique within a
|
||
|
* year and range from 0 to 11 or 0 to 12 depending on how many months in a
|
||
|
* year, the calendar system has leap month or not, and in leap year or not.
|
||
|
* It is the ordinal position of that month in the corresponding year of
|
||
|
* the calendar. For Chinese, Dangi, and Hebrew calendar, the range is
|
||
|
* 0 to 11 in non-leap years and 0 to 12 in leap years. For Coptic and Ethiopian
|
||
|
* calendar, the range is always 0 to 12. For other calendars supported by
|
||
|
* ICU now, the range is 0 to 11. When the number of months in a year of the
|
||
|
* identified calendar is variable, a different ORDINAL_MONTH value can
|
||
|
* be used for dates that are part of the same named month in different years.
|
||
|
* For example, in the Hebrew calendar, "1 Nisan 5781" is associated with
|
||
|
* ORDINAL_MONTH value 6 while "1 Nisan 5782" is associated with
|
||
|
* ORDINAL_MONTH value 7 because 5782 is a leap year and Nisan follows
|
||
|
* the insertion of Adar I. In Chinese calendar, "Year 4664 Month 6 Day 2"
|
||
|
* is associated with ORDINAL_MONTH value 5 while "Year 4665 Month 6 Day 2"
|
||
|
* is associated with ORDINAL_MONTH value 6 because 4665 is a leap year
|
||
|
* and there is an extra "Leap Month 5" which associated with ORDINAL_MONTH
|
||
|
* value 5 before "Month 6" of year 4664.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
public static final int ORDINAL_MONTH = 23;
|
||
|
|
||
|
// Android patch: Soft removal the BASE_FIELD_COUNT API on Android.
|
||
|
/**
|
||
|
* The number of fields defined by this class. Subclasses may define
|
||
|
* addition fields starting with this number.
|
||
|
* @removed ICU 58 The numeric value may change over time, see ICU ticket #12420.
|
||
|
*/
|
||
|
@Deprecated
|
||
|
protected static final int BASE_FIELD_COUNT;
|
||
|
static {
|
||
|
BASE_FIELD_COUNT = 24;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The maximum number of fields possible. Subclasses must not define
|
||
|
* more total fields than this number.
|
||
|
* @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420.
|
||
|
*/
|
||
|
@Deprecated
|
||
|
protected static final int MAX_FIELD_COUNT = 32;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>DAY_OF_WEEK</code> field indicating
|
||
|
* Sunday.
|
||
|
*/
|
||
|
public final static int SUNDAY = 1;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>DAY_OF_WEEK</code> field indicating
|
||
|
* Monday.
|
||
|
*/
|
||
|
public final static int MONDAY = 2;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>DAY_OF_WEEK</code> field indicating
|
||
|
* Tuesday.
|
||
|
*/
|
||
|
public final static int TUESDAY = 3;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>DAY_OF_WEEK</code> field indicating
|
||
|
* Wednesday.
|
||
|
*/
|
||
|
public final static int WEDNESDAY = 4;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>DAY_OF_WEEK</code> field indicating
|
||
|
* Thursday.
|
||
|
*/
|
||
|
public final static int THURSDAY = 5;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>DAY_OF_WEEK</code> field indicating
|
||
|
* Friday.
|
||
|
*/
|
||
|
public final static int FRIDAY = 6;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>DAY_OF_WEEK</code> field indicating
|
||
|
* Saturday.
|
||
|
*/
|
||
|
public final static int SATURDAY = 7;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* first month of the year.
|
||
|
*/
|
||
|
public final static int JANUARY = 0;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* second month of the year.
|
||
|
*/
|
||
|
public final static int FEBRUARY = 1;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* third month of the year.
|
||
|
*/
|
||
|
public final static int MARCH = 2;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* fourth month of the year.
|
||
|
*/
|
||
|
public final static int APRIL = 3;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* fifth month of the year.
|
||
|
*/
|
||
|
public final static int MAY = 4;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* sixth month of the year.
|
||
|
*/
|
||
|
public final static int JUNE = 5;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* seventh month of the year.
|
||
|
*/
|
||
|
public final static int JULY = 6;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* eighth month of the year.
|
||
|
*/
|
||
|
public final static int AUGUST = 7;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* ninth month of the year.
|
||
|
*/
|
||
|
public final static int SEPTEMBER = 8;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* tenth month of the year.
|
||
|
*/
|
||
|
public final static int OCTOBER = 9;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* eleventh month of the year.
|
||
|
*/
|
||
|
public final static int NOVEMBER = 10;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* twelfth month of the year.
|
||
|
*/
|
||
|
public final static int DECEMBER = 11;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>MONTH</code> field indicating the
|
||
|
* thirteenth month of the year. Although {@link GregorianCalendar}
|
||
|
* does not use this value, lunar calendars do.
|
||
|
*/
|
||
|
public final static int UNDECIMBER = 12;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>AM_PM</code> field indicating the
|
||
|
* period of the day from midnight to just before noon.
|
||
|
*/
|
||
|
public final static int AM = 0;
|
||
|
|
||
|
/**
|
||
|
* Value of the <code>AM_PM</code> field indicating the
|
||
|
* period of the day from noon to just before midnight.
|
||
|
*/
|
||
|
public final static int PM = 1;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Value returned by getDayOfWeekType(int dayOfWeek) to indicate a
|
||
|
* weekday.
|
||
|
* @see #WEEKEND
|
||
|
* @see #WEEKEND_ONSET
|
||
|
* @see #WEEKEND_CEASE
|
||
|
* @see #getDayOfWeekType
|
||
|
* @deprecated ICU 54 use {@link #getWeekDataForRegion(String)}, {@link #getWeekData()}, {@link #setWeekData(WeekData)}
|
||
|
* @hide original deprecated declaration
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static final int WEEKDAY = 0;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Value returned by getDayOfWeekType(int dayOfWeek) to indicate a
|
||
|
* weekend day.
|
||
|
* @see #WEEKDAY
|
||
|
* @see #WEEKEND_ONSET
|
||
|
* @see #WEEKEND_CEASE
|
||
|
* @see #getDayOfWeekType
|
||
|
* @deprecated ICU 54 use {@link #getWeekDataForRegion(String)}, {@link #getWeekData()}, {@link #setWeekData(WeekData)}
|
||
|
* @hide original deprecated declaration
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static final int WEEKEND = 1;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Value returned by getDayOfWeekType(int dayOfWeek) to indicate a
|
||
|
* day that starts as a weekday and transitions to the weekend.
|
||
|
* Call getWeekendTransition() to get the point of transition.
|
||
|
* @see #WEEKDAY
|
||
|
* @see #WEEKEND
|
||
|
* @see #WEEKEND_CEASE
|
||
|
* @see #getDayOfWeekType
|
||
|
* @deprecated ICU 54 use {@link #getWeekDataForRegion(String)}, {@link #getWeekData()}, {@link #setWeekData(WeekData)}
|
||
|
* @hide original deprecated declaration
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static final int WEEKEND_ONSET = 2;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Value returned by getDayOfWeekType(int dayOfWeek) to indicate a
|
||
|
* day that starts as the weekend and transitions to a weekday.
|
||
|
* Call getWeekendTransition() to get the point of transition.
|
||
|
* @see #WEEKDAY
|
||
|
* @see #WEEKEND
|
||
|
* @see #WEEKEND_ONSET
|
||
|
* @see #getDayOfWeekType
|
||
|
* @deprecated ICU 54 use {@link #getWeekDataForRegion(String)}, {@link #getWeekData()}, {@link #setWeekData(WeekData)}
|
||
|
* @hide original deprecated declaration
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static final int WEEKEND_CEASE = 3;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong>Option used by {@link #setRepeatedWallTimeOption(int)} and
|
||
|
* {@link #setSkippedWallTimeOption(int)} specifying an ambiguous wall time
|
||
|
* to be interpreted as the latest.
|
||
|
* @see #setRepeatedWallTimeOption(int)
|
||
|
* @see #getRepeatedWallTimeOption()
|
||
|
* @see #setSkippedWallTimeOption(int)
|
||
|
* @see #getSkippedWallTimeOption()
|
||
|
*/
|
||
|
public static final int WALLTIME_LAST = 0;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong>Option used by {@link #setRepeatedWallTimeOption(int)} and
|
||
|
* {@link #setSkippedWallTimeOption(int)} specifying an ambiguous wall time
|
||
|
* to be interpreted as the earliest.
|
||
|
* @see #setRepeatedWallTimeOption(int)
|
||
|
* @see #getRepeatedWallTimeOption()
|
||
|
* @see #setSkippedWallTimeOption(int)
|
||
|
* @see #getSkippedWallTimeOption()
|
||
|
*/
|
||
|
public static final int WALLTIME_FIRST = 1;
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong>Option used by {@link #setSkippedWallTimeOption(int)} specifying an
|
||
|
* ambiguous wall time to be interpreted as the next valid wall time.
|
||
|
* @see #setSkippedWallTimeOption(int)
|
||
|
* @see #getSkippedWallTimeOption()
|
||
|
*/
|
||
|
public static final int WALLTIME_NEXT_VALID = 2;
|
||
|
|
||
|
/**
|
||
|
* The number of milliseconds in one second.
|
||
|
*/
|
||
|
protected static final int ONE_SECOND = 1000;
|
||
|
|
||
|
/**
|
||
|
* The number of milliseconds in one minute.
|
||
|
*/
|
||
|
protected static final int ONE_MINUTE = 60*ONE_SECOND;
|
||
|
|
||
|
/**
|
||
|
* The number of milliseconds in one hour.
|
||
|
*/
|
||
|
protected static final int ONE_HOUR = 60*ONE_MINUTE;
|
||
|
|
||
|
/**
|
||
|
* The number of milliseconds in one day. Although ONE_DAY and
|
||
|
* ONE_WEEK can fit into ints, they must be longs in order to prevent
|
||
|
* arithmetic overflow when performing (bug 4173516).
|
||
|
*/
|
||
|
protected static final long ONE_DAY = 24*ONE_HOUR;
|
||
|
|
||
|
/**
|
||
|
* The number of milliseconds in one week. Although ONE_DAY and
|
||
|
* ONE_WEEK can fit into ints, they must be longs in order to prevent
|
||
|
* arithmetic overflow when performing (bug 4173516).
|
||
|
*/
|
||
|
protected static final long ONE_WEEK = 7*ONE_DAY;
|
||
|
|
||
|
/**
|
||
|
* The Julian day of the Gregorian epoch, that is, January 1, 1 on the
|
||
|
* Gregorian calendar.
|
||
|
*/
|
||
|
protected static final int JAN_1_1_JULIAN_DAY = 1721426;
|
||
|
|
||
|
/**
|
||
|
* The Julian day of the epoch, that is, January 1, 1970 on the
|
||
|
* Gregorian calendar.
|
||
|
*/
|
||
|
protected static final int EPOCH_JULIAN_DAY = 2440588;
|
||
|
|
||
|
/**
|
||
|
* The minimum supported Julian day. This value is equivalent to
|
||
|
* {@link #MIN_MILLIS} and {@link #MIN_DATE}.
|
||
|
* @see #JULIAN_DAY
|
||
|
*/
|
||
|
protected static final int MIN_JULIAN = -0x7F000000;
|
||
|
|
||
|
/**
|
||
|
* The minimum supported epoch milliseconds. This value is equivalent
|
||
|
* to {@link #MIN_JULIAN} and {@link #MIN_DATE}.
|
||
|
*/
|
||
|
protected static final long MIN_MILLIS = -184303902528000000L;
|
||
|
|
||
|
// Get around bug in jikes 1.12 for now. Later, use:
|
||
|
//protected static final long MIN_MILLIS = (MIN_JULIAN - EPOCH_JULIAN_DAY) * ONE_DAY;
|
||
|
|
||
|
/**
|
||
|
* The minimum supported <code>Date</code>. This value is equivalent
|
||
|
* to {@link #MIN_JULIAN} and {@link #MIN_MILLIS}.
|
||
|
*/
|
||
|
protected static final Date MIN_DATE = new Date(MIN_MILLIS);
|
||
|
|
||
|
/**
|
||
|
* The maximum supported Julian day. This value is equivalent to
|
||
|
* {@link #MAX_MILLIS} and {@link #MAX_DATE}.
|
||
|
* @see #JULIAN_DAY
|
||
|
*/
|
||
|
protected static final int MAX_JULIAN = +0x7F000000;
|
||
|
|
||
|
/**
|
||
|
* The maximum supported epoch milliseconds. This value is equivalent
|
||
|
* to {@link #MAX_JULIAN} and {@link #MAX_DATE}.
|
||
|
*/
|
||
|
protected static final long MAX_MILLIS = (MAX_JULIAN - EPOCH_JULIAN_DAY) * ONE_DAY;
|
||
|
|
||
|
/**
|
||
|
* The maximum supported <code>Date</code>. This value is equivalent
|
||
|
* to {@link #MAX_JULIAN} and {@link #MAX_MILLIS}.
|
||
|
*/
|
||
|
protected static final Date MAX_DATE = new Date(MAX_MILLIS);
|
||
|
|
||
|
/**
|
||
|
* The maximum supported hours for millisecond calculations
|
||
|
*/
|
||
|
private static final int MAX_HOURS = 548;
|
||
|
|
||
|
// Internal notes:
|
||
|
// Calendar contains two kinds of time representations: current "time" in
|
||
|
// milliseconds, and a set of time "fields" representing the current time.
|
||
|
// The two representations are usually in sync, but can get out of sync
|
||
|
// as follows.
|
||
|
// 1. Initially, no fields are set, and the time is invalid.
|
||
|
// 2. If the time is set, all fields are computed and in sync.
|
||
|
// 3. If a single field is set, the time is invalid.
|
||
|
// Recomputation of the time and fields happens when the object needs
|
||
|
// to return a result to the user, or use a result for a computation.
|
||
|
|
||
|
/**
|
||
|
* The field values for the currently set time for this calendar.
|
||
|
* This is an array of at least {@link #BASE_FIELD_COUNT} integers.
|
||
|
* @see #handleCreateFields
|
||
|
* @serial
|
||
|
*/
|
||
|
private transient int fields[];
|
||
|
|
||
|
/**
|
||
|
* Pseudo-time-stamps which specify when each field was set. There
|
||
|
* are two special values, UNSET and INTERNALLY_SET. Values from
|
||
|
* MINIMUM_USER_SET to Integer.MAX_VALUE are legal user set values.
|
||
|
*/
|
||
|
private transient int stamp[];
|
||
|
|
||
|
/**
|
||
|
* The currently set time for this calendar, expressed in milliseconds after
|
||
|
* January 1, 1970, 0:00:00 GMT.
|
||
|
* @serial
|
||
|
*/
|
||
|
private long time;
|
||
|
|
||
|
/**
|
||
|
* True if then the value of <code>time</code> is valid.
|
||
|
* The time is made invalid by a change to an item of <code>field[]</code>.
|
||
|
* @see #time
|
||
|
* @serial
|
||
|
*/
|
||
|
private transient boolean isTimeSet;
|
||
|
|
||
|
/**
|
||
|
* True if <code>fields[]</code> are in sync with the currently set time.
|
||
|
* If false, then the next attempt to get the value of a field will
|
||
|
* force a recomputation of all fields from the current value of
|
||
|
* <code>time</code>.
|
||
|
* @serial
|
||
|
*/
|
||
|
private transient boolean areFieldsSet;
|
||
|
|
||
|
/**
|
||
|
* True if all fields have been set. This is only false in a few
|
||
|
* situations: In a newly created, partially constructed object. After
|
||
|
* a call to clear(). In an object just read from a stream using
|
||
|
* readObject(). Once computeFields() has been called this is set to
|
||
|
* true and stays true until one of the above situations recurs.
|
||
|
* @serial
|
||
|
*/
|
||
|
private transient boolean areAllFieldsSet;
|
||
|
|
||
|
/**
|
||
|
* True if all fields have been virtually set, but have not yet been
|
||
|
* computed. This occurs only in setTimeInMillis(), or after readObject().
|
||
|
* A calendar set to this state will compute all fields from the time if it
|
||
|
* becomes necessary, but otherwise will delay such computation.
|
||
|
*/
|
||
|
private transient boolean areFieldsVirtuallySet;
|
||
|
|
||
|
/**
|
||
|
* True if this calendar allows out-of-range field values during computation
|
||
|
* of <code>time</code> from <code>fields[]</code>.
|
||
|
* @see #setLenient
|
||
|
* @serial
|
||
|
*/
|
||
|
private boolean lenient = true;
|
||
|
|
||
|
/**
|
||
|
* The {@link TimeZone} used by this calendar. {@link Calendar}
|
||
|
* uses the time zone data to translate between local and GMT time.
|
||
|
* @serial
|
||
|
*/
|
||
|
private TimeZone zone;
|
||
|
|
||
|
/**
|
||
|
* The first day of the week, with possible values {@link #SUNDAY},
|
||
|
* {@link #MONDAY}, etc. This is a locale-dependent value.
|
||
|
* @serial
|
||
|
*/
|
||
|
private int firstDayOfWeek;
|
||
|
|
||
|
/**
|
||
|
* The number of days required for the first week in a month or year,
|
||
|
* with possible values from 1 to 7. This is a locale-dependent value.
|
||
|
* @serial
|
||
|
*/
|
||
|
private int minimalDaysInFirstWeek;
|
||
|
|
||
|
/**
|
||
|
* First day of the weekend in this calendar's locale. Must be in
|
||
|
* the range SUNDAY...SATURDAY (1..7). The weekend starts at
|
||
|
* weekendOnsetMillis milliseconds after midnight on that day of
|
||
|
* the week. This value is taken from locale resource data.
|
||
|
*/
|
||
|
private int weekendOnset;
|
||
|
|
||
|
/**
|
||
|
* Milliseconds after midnight at which the weekend starts on the
|
||
|
* day of the week weekendOnset. Times that are greater than or
|
||
|
* equal to weekendOnsetMillis are considered part of the weekend.
|
||
|
* Must be in the range 0..24*60*60*1000-1. This value is taken
|
||
|
* from locale resource data.
|
||
|
*/
|
||
|
private int weekendOnsetMillis;
|
||
|
|
||
|
/**
|
||
|
* Day of the week when the weekend stops in this calendar's
|
||
|
* locale. Must be in the range SUNDAY...SATURDAY (1..7). The
|
||
|
* weekend stops at weekendCeaseMillis milliseconds after midnight
|
||
|
* on that day of the week. This value is taken from locale
|
||
|
* resource data.
|
||
|
*/
|
||
|
private int weekendCease;
|
||
|
|
||
|
/**
|
||
|
* Milliseconds after midnight at which the weekend stops on the
|
||
|
* day of the week weekendCease. Times that are greater than or
|
||
|
* equal to weekendCeaseMillis are considered not to be the
|
||
|
* weekend. Must be in the range 0..24*60*60*1000-1. This value
|
||
|
* is taken from locale resource data.
|
||
|
*/
|
||
|
private int weekendCeaseMillis;
|
||
|
|
||
|
/**
|
||
|
* Option used when the specified wall time occurs multiple times.
|
||
|
*/
|
||
|
private int repeatedWallTime = WALLTIME_LAST;
|
||
|
|
||
|
/**
|
||
|
* Option used when the specified wall time does not exist.
|
||
|
*/
|
||
|
private int skippedWallTime = WALLTIME_LAST;
|
||
|
|
||
|
/**
|
||
|
* Value of the time stamp <code>stamp[]</code> indicating that
|
||
|
* a field has not been set since the last call to <code>clear()</code>.
|
||
|
* @see #INTERNALLY_SET
|
||
|
* @see #MINIMUM_USER_STAMP
|
||
|
*/
|
||
|
protected static final int UNSET = 0;
|
||
|
|
||
|
/**
|
||
|
* Value of the time stamp <code>stamp[]</code> indicating that a field
|
||
|
* has been set via computations from the time or from other fields.
|
||
|
* @see #UNSET
|
||
|
* @see #MINIMUM_USER_STAMP
|
||
|
*/
|
||
|
protected static final int INTERNALLY_SET = 1;
|
||
|
|
||
|
/**
|
||
|
* If the time stamp <code>stamp[]</code> has a value greater than or
|
||
|
* equal to <code>MINIMUM_USER_SET</code> then it has been set by the
|
||
|
* user via a call to <code>set()</code>.
|
||
|
* @see #UNSET
|
||
|
* @see #INTERNALLY_SET
|
||
|
*/
|
||
|
protected static final int MINIMUM_USER_STAMP = 2;
|
||
|
|
||
|
/**
|
||
|
* The next available value for <code>stamp[]</code>, an internal array.
|
||
|
* @serial
|
||
|
*/
|
||
|
private transient int nextStamp = MINIMUM_USER_STAMP;
|
||
|
|
||
|
/* Max value for stamp allowable before recalculation */
|
||
|
private static int STAMP_MAX = 10000;
|
||
|
|
||
|
// the internal serial version which says which version was written
|
||
|
// - 0 (default) for version up to JDK 1.1.5
|
||
|
// - 1 for version from JDK 1.1.6, which writes a correct 'time' value
|
||
|
// as well as compatible values for other fields. This is a
|
||
|
// transitional format.
|
||
|
// - 2 (not implemented yet) a future version, in which fields[],
|
||
|
// areFieldsSet, and isTimeSet become transient, and isSet[] is
|
||
|
// removed. In JDK 1.1.6 we write a format compatible with version 2.
|
||
|
// static final int currentSerialVersion = 1;
|
||
|
|
||
|
/**
|
||
|
* The version of the serialized data on the stream. Possible values:
|
||
|
* <dl>
|
||
|
* <dt><b>0</b> or not present on stream</dt>
|
||
|
* <dd>
|
||
|
* JDK 1.1.5 or earlier.
|
||
|
* </dd>
|
||
|
* <dt><b>1</b></dt>
|
||
|
* <dd>
|
||
|
* JDK 1.1.6 or later. Writes a correct 'time' value
|
||
|
* as well as compatible values for other fields. This is a
|
||
|
* transitional format.
|
||
|
* </dd>
|
||
|
* </dl>
|
||
|
* When streaming out this class, the most recent format
|
||
|
* and the highest allowable <code>serialVersionOnStream</code>
|
||
|
* is written.
|
||
|
* @serial
|
||
|
*/
|
||
|
// private int serialVersionOnStream = currentSerialVersion;
|
||
|
|
||
|
// Proclaim serialization compatibility with JDK 1.1
|
||
|
// static final long serialVersionUID = -1807547505821590642L;
|
||
|
|
||
|
// haven't been compatible for awhile, no longer try
|
||
|
// jdk1.4.2 serialver
|
||
|
private static final long serialVersionUID = 6222646104888790989L;
|
||
|
|
||
|
/**
|
||
|
* Bitmask for internalSet() defining which fields may legally be set
|
||
|
* by subclasses. Any attempt to set a field not in this bitmask
|
||
|
* results in an exception, because such fields must be set by the base
|
||
|
* class.
|
||
|
*/
|
||
|
private transient int internalSetMask;
|
||
|
|
||
|
/**
|
||
|
* The Gregorian year, as computed by computeGregorianFields() and
|
||
|
* returned by getGregorianYear().
|
||
|
*/
|
||
|
private transient int gregorianYear;
|
||
|
|
||
|
/**
|
||
|
* The Gregorian month, as computed by computeGregorianFields() and
|
||
|
* returned by getGregorianMonth().
|
||
|
*/
|
||
|
private transient int gregorianMonth;
|
||
|
|
||
|
/**
|
||
|
* The Gregorian day of the year, as computed by
|
||
|
* computeGregorianFields() and returned by getGregorianDayOfYear().
|
||
|
*/
|
||
|
private transient int gregorianDayOfYear;
|
||
|
|
||
|
/**
|
||
|
* The Gregorian day of the month, as computed by
|
||
|
* computeGregorianFields() and returned by getGregorianDayOfMonth().
|
||
|
*/
|
||
|
private transient int gregorianDayOfMonth;
|
||
|
|
||
|
/**
|
||
|
* Constructs a Calendar with the default time zone
|
||
|
* and the default <code>FORMAT</code> locale.
|
||
|
* @see TimeZone#getDefault
|
||
|
* @see Category#FORMAT
|
||
|
*/
|
||
|
protected Calendar()
|
||
|
{
|
||
|
this(TimeZone.getDefault(), ULocale.getDefault(Category.FORMAT));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Constructs a calendar with the specified time zone and locale.
|
||
|
* @param zone the time zone to use
|
||
|
* @param aLocale the locale for the week data
|
||
|
*/
|
||
|
protected Calendar(TimeZone zone, Locale aLocale)
|
||
|
{
|
||
|
this(zone, ULocale.forLocale(aLocale));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Constructs a calendar with the specified time zone and locale.
|
||
|
* @param zone the time zone to use
|
||
|
* @param locale the ulocale for the week data
|
||
|
*/
|
||
|
protected Calendar(TimeZone zone, ULocale locale)
|
||
|
{
|
||
|
this.zone = zone;
|
||
|
|
||
|
// week data
|
||
|
setWeekData(getRegionForCalendar(locale));
|
||
|
|
||
|
// Check if the locale has a "fw" u extension and we honor it if present.
|
||
|
String fw = locale.getKeywordValue("fw");
|
||
|
if (fw != null) {
|
||
|
int fwOverride;
|
||
|
switch (fw) {
|
||
|
case "sun": fwOverride = SUNDAY; break;
|
||
|
case "mon": fwOverride = MONDAY; break;
|
||
|
case "tue": fwOverride = TUESDAY; break;
|
||
|
case "wed": fwOverride = WEDNESDAY; break;
|
||
|
case "thu": fwOverride = THURSDAY; break;
|
||
|
case "fri": fwOverride = FRIDAY; break;
|
||
|
case "sat": fwOverride = SATURDAY; break;
|
||
|
default: fwOverride = -1;
|
||
|
}
|
||
|
if (fwOverride != -1) {
|
||
|
setFirstDayOfWeek(fwOverride);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// set valid/actual locale
|
||
|
setCalendarLocale(locale);
|
||
|
|
||
|
initInternal();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set valid/actual locale to this calendar during initialization.
|
||
|
*
|
||
|
* Valid or actual locale does not make much sense for Calendar
|
||
|
* object. An instance of Calendar is initialized by week data
|
||
|
* determine by region and calendar type (either region or keyword).
|
||
|
* Language is not really used for calendar creation.
|
||
|
*/
|
||
|
private void setCalendarLocale(ULocale locale) {
|
||
|
ULocale calLocale = locale;
|
||
|
|
||
|
if (locale.getVariant().length() != 0 || locale.getKeywords() != null) {
|
||
|
// Construct a ULocale, without variant and keywords (except calendar).
|
||
|
StringBuilder buf = new StringBuilder();
|
||
|
|
||
|
buf.append(locale.getLanguage());
|
||
|
|
||
|
String script = locale.getScript();
|
||
|
if (script.length() > 0) {
|
||
|
buf.append("_").append(script);
|
||
|
}
|
||
|
|
||
|
String region = locale.getCountry();
|
||
|
if (region.length() > 0) {
|
||
|
buf.append("_").append(region);
|
||
|
}
|
||
|
|
||
|
String calType = locale.getKeywordValue("calendar");
|
||
|
if (calType != null) {
|
||
|
buf.append("@calendar=").append(calType);
|
||
|
}
|
||
|
|
||
|
calLocale = new ULocale(buf.toString());
|
||
|
}
|
||
|
|
||
|
setLocale(calLocale, calLocale);
|
||
|
}
|
||
|
|
||
|
private void recalculateStamp() {
|
||
|
int index;
|
||
|
int currentValue;
|
||
|
int j, i;
|
||
|
|
||
|
nextStamp = 1;
|
||
|
|
||
|
for (j = 0; j < stamp.length; j++) {
|
||
|
currentValue = STAMP_MAX;
|
||
|
index = -1;
|
||
|
|
||
|
for (i = 0; i < stamp.length; i++) {
|
||
|
if (stamp[i] > nextStamp && stamp[i] < currentValue) {
|
||
|
currentValue = stamp[i];
|
||
|
index = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (index >= 0) {
|
||
|
stamp[index] = ++nextStamp;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
nextStamp++;
|
||
|
}
|
||
|
|
||
|
private void initInternal()
|
||
|
{
|
||
|
// Allocate fields through the framework method. Subclasses
|
||
|
// may override this to define additional fields.
|
||
|
fields = handleCreateFields();
|
||
|
///CLOVER:OFF
|
||
|
// todo: fix, difficult to test without subclassing
|
||
|
if (fields == null || fields.length < BASE_FIELD_COUNT ||
|
||
|
fields.length > MAX_FIELD_COUNT) {
|
||
|
throw new IllegalStateException("Invalid fields[]");
|
||
|
}
|
||
|
///CLOVER:ON
|
||
|
stamp = new int[fields.length];
|
||
|
int mask = (1 << ERA) |
|
||
|
(1 << YEAR) |
|
||
|
(1 << MONTH) |
|
||
|
(1 << DAY_OF_MONTH) |
|
||
|
(1 << DAY_OF_YEAR) |
|
||
|
(1 << EXTENDED_YEAR) |
|
||
|
(1 << IS_LEAP_MONTH) |
|
||
|
(1 << ORDINAL_MONTH) ;
|
||
|
for (int i=BASE_FIELD_COUNT; i<fields.length; ++i) {
|
||
|
mask |= (1 << i);
|
||
|
}
|
||
|
internalSetMask = mask;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a calendar using the default time zone and locale.
|
||
|
* @return a Calendar.
|
||
|
*/
|
||
|
public static Calendar getInstance()
|
||
|
{
|
||
|
return getInstanceInternal(null, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a calendar using the specified time zone and default locale.
|
||
|
* @param zone the time zone to use
|
||
|
* @return a Calendar.
|
||
|
*/
|
||
|
public static Calendar getInstance(TimeZone zone)
|
||
|
{
|
||
|
return getInstanceInternal(zone, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a calendar using the default time zone and specified locale.
|
||
|
* @param aLocale the locale for the week data
|
||
|
* @return a Calendar.
|
||
|
*/
|
||
|
public static Calendar getInstance(Locale aLocale)
|
||
|
{
|
||
|
return getInstanceInternal(null, ULocale.forLocale(aLocale));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a calendar using the default time zone and specified locale.
|
||
|
* @param locale the ulocale for the week data
|
||
|
* @return a Calendar.
|
||
|
*/
|
||
|
public static Calendar getInstance(ULocale locale)
|
||
|
{
|
||
|
return getInstanceInternal(null, locale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a calendar with the specified time zone and locale.
|
||
|
* @param zone the time zone to use
|
||
|
* @param aLocale the locale for the week data
|
||
|
* @return a Calendar.
|
||
|
*/
|
||
|
public static Calendar getInstance(TimeZone zone, Locale aLocale) {
|
||
|
return getInstanceInternal(zone, ULocale.forLocale(aLocale));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a calendar with the specified time zone and locale.
|
||
|
* @param zone the time zone to use
|
||
|
* @param locale the ulocale for the week data
|
||
|
* @return a Calendar.
|
||
|
*/
|
||
|
public static Calendar getInstance(TimeZone zone, ULocale locale) {
|
||
|
return getInstanceInternal(zone, locale);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* All getInstance implementations call this private method to create a new
|
||
|
* Calendar instance.
|
||
|
*/
|
||
|
private static Calendar getInstanceInternal(TimeZone tz, ULocale locale) {
|
||
|
if (locale == null) {
|
||
|
locale = ULocale.getDefault(Category.FORMAT);
|
||
|
}
|
||
|
if (tz == null) {
|
||
|
tz = TimeZone.forULocaleOrDefault(locale);
|
||
|
}
|
||
|
|
||
|
Calendar cal = createInstance(locale);
|
||
|
cal.setTimeZone(tz);
|
||
|
cal.setTimeInMillis(System.currentTimeMillis());
|
||
|
return cal;
|
||
|
}
|
||
|
|
||
|
private static String getRegionForCalendar(ULocale loc) {
|
||
|
String region = ULocale.getRegionForSupplementalData(loc, true);
|
||
|
if (region.length() == 0) {
|
||
|
region = "001";
|
||
|
}
|
||
|
return region;
|
||
|
}
|
||
|
|
||
|
private static CalType getCalendarTypeForLocale(ULocale l) {
|
||
|
String s = CalendarUtil.getCalendarType(l);
|
||
|
if (s != null) {
|
||
|
s = s.toLowerCase(Locale.ENGLISH);
|
||
|
for (CalType type : CalType.values()) {
|
||
|
if (s.equals(type.getId())) {
|
||
|
return type;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
private static Calendar createInstance(ULocale locale) {
|
||
|
Calendar cal = null;
|
||
|
TimeZone zone = TimeZone.forULocaleOrDefault(locale);
|
||
|
CalType calType = getCalendarTypeForLocale(locale);
|
||
|
if (calType == null) {
|
||
|
// fallback to Gregorian
|
||
|
calType = CalType.GREGORIAN;
|
||
|
}
|
||
|
|
||
|
switch (calType) {
|
||
|
case GREGORIAN:
|
||
|
cal = new GregorianCalendar(zone, locale);
|
||
|
break;
|
||
|
case ISO8601:
|
||
|
// Only differs week numbering rule from Gregorian
|
||
|
cal = new GregorianCalendar(zone, locale);
|
||
|
// Based on UTS35 "First Day Overrides"
|
||
|
// https://unicode.org/reports/tr35/tr35-dates.html#first-day-overrides
|
||
|
// Only set fw to Monday for ISO8601 if there are no fw nor rg keywords.
|
||
|
// If there is a fw or rg keywords, the Calendar constructor already set it
|
||
|
// to the fw value or based on the rg value.
|
||
|
if (locale.getUnicodeLocaleType("fw") == null &&
|
||
|
locale.getUnicodeLocaleType("rg") == null) {
|
||
|
cal.setFirstDayOfWeek(MONDAY);
|
||
|
}
|
||
|
cal.setMinimalDaysInFirstWeek(4);
|
||
|
break;
|
||
|
|
||
|
case BUDDHIST:
|
||
|
cal = new BuddhistCalendar(zone, locale);
|
||
|
break;
|
||
|
case CHINESE:
|
||
|
cal = new ChineseCalendar(zone, locale);
|
||
|
break;
|
||
|
case COPTIC:
|
||
|
cal = new CopticCalendar(zone, locale);
|
||
|
break;
|
||
|
case DANGI:
|
||
|
cal = new DangiCalendar(zone, locale);
|
||
|
break;
|
||
|
case ETHIOPIC:
|
||
|
cal = new EthiopicCalendar(zone, locale);
|
||
|
break;
|
||
|
case ETHIOPIC_AMETE_ALEM:
|
||
|
cal = new EthiopicCalendar(zone, locale);
|
||
|
((EthiopicCalendar)cal).setAmeteAlemEra(true);
|
||
|
break;
|
||
|
case HEBREW:
|
||
|
cal = new HebrewCalendar(zone, locale);
|
||
|
break;
|
||
|
case INDIAN:
|
||
|
cal = new IndianCalendar(zone, locale);
|
||
|
break;
|
||
|
case ISLAMIC_CIVIL:
|
||
|
case ISLAMIC_UMALQURA :
|
||
|
case ISLAMIC_TBLA:
|
||
|
case ISLAMIC_RGSA:
|
||
|
case ISLAMIC:
|
||
|
cal = new IslamicCalendar(zone, locale);
|
||
|
break;
|
||
|
case JAPANESE:
|
||
|
cal = new JapaneseCalendar(zone, locale);
|
||
|
break;
|
||
|
case PERSIAN:
|
||
|
cal = new PersianCalendar(zone, locale);
|
||
|
break;
|
||
|
case ROC:
|
||
|
cal = new TaiwanCalendar(zone, locale);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// we must not get here, because unknown type is mapped to
|
||
|
// Gregorian at the beginning of this method.
|
||
|
throw new IllegalArgumentException("Unknown calendar type");
|
||
|
}
|
||
|
|
||
|
return cal;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the list of locales for which Calendars are installed.
|
||
|
* @return the list of locales for which Calendars are installed.
|
||
|
*/
|
||
|
public static Locale[] getAvailableLocales()
|
||
|
{
|
||
|
// TODO
|
||
|
return ICUResourceBundle.getAvailableLocales();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the list of locales for which Calendars are installed.
|
||
|
* @return the list of locales for which Calendars are installed.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
public static ULocale[] getAvailableULocales()
|
||
|
{
|
||
|
// TODO
|
||
|
return ICUResourceBundle.getAvailableULocales();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Given a key and a locale, returns an array of string values in a preferred
|
||
|
* order that would make a difference. These are all and only those values where
|
||
|
* the open (creation) of the service with the locale formed from the input locale
|
||
|
* plus input keyword and that value has different behavior than creation with the
|
||
|
* input locale alone.
|
||
|
* @param key one of the keys supported by this service. For now, only
|
||
|
* "calendar" is supported.
|
||
|
* @param locale the locale
|
||
|
* @param commonlyUsed if set to true it will return only commonly used values
|
||
|
* with the given locale in preferred order. Otherwise,
|
||
|
* it will return all the available values for the locale.
|
||
|
* @return an array of string values for the given key and the locale.
|
||
|
*/
|
||
|
public static final String[] getKeywordValuesForLocale(String key, ULocale locale,
|
||
|
boolean commonlyUsed) {
|
||
|
// Resolve region
|
||
|
String prefRegion = ULocale.getRegionForSupplementalData(locale, true);
|
||
|
|
||
|
// Read preferred calendar values from supplementalData calendarPreferences
|
||
|
ArrayList<String> values = new ArrayList<>();
|
||
|
|
||
|
UResourceBundle rb = UResourceBundle.getBundleInstance(
|
||
|
ICUData.ICU_BASE_NAME,
|
||
|
"supplementalData",
|
||
|
ICUResourceBundle.ICU_DATA_CLASS_LOADER);
|
||
|
UResourceBundle calPref = rb.get("calendarPreferenceData");
|
||
|
UResourceBundle order = null;
|
||
|
try {
|
||
|
order = calPref.get(prefRegion);
|
||
|
} catch (MissingResourceException mre) {
|
||
|
// use "001" as fallback
|
||
|
order = calPref.get("001");
|
||
|
}
|
||
|
|
||
|
String[] caltypes = order.getStringArray();
|
||
|
if (commonlyUsed) {
|
||
|
// we have all commonly used calendar for the target region
|
||
|
return caltypes;
|
||
|
}
|
||
|
|
||
|
// if not commonlyUsed, add all preferred calendars in the order
|
||
|
for (int i = 0; i < caltypes.length; i++) {
|
||
|
values.add(caltypes[i]);
|
||
|
}
|
||
|
// then, add other available calendars
|
||
|
for (CalType t : CalType.values()) {
|
||
|
if (!values.contains(t.getId())) {
|
||
|
values.add(t.getId());
|
||
|
}
|
||
|
}
|
||
|
return values.toArray(new String[values.size()]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this Calendar's current time.
|
||
|
* @return the current time.
|
||
|
*/
|
||
|
public final Date getTime() {
|
||
|
return new Date( getTimeInMillis() );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets this Calendar's current time with the given Date.
|
||
|
*
|
||
|
* <p>Note: Calling <code>setTime</code> with
|
||
|
* <code>Date(Long.MAX_VALUE)</code> or <code>Date(Long.MIN_VALUE)</code>
|
||
|
* may yield incorrect field values from {@link #get(int)}.
|
||
|
* @param date the given Date.
|
||
|
*/
|
||
|
public final void setTime(Date date) {
|
||
|
setTimeInMillis( date.getTime() );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns this Calendar's current time as a long.
|
||
|
* @return the current time as UTC milliseconds from the epoch.
|
||
|
*/
|
||
|
public long getTimeInMillis() {
|
||
|
if (!isTimeSet) updateTime();
|
||
|
return time;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets this Calendar's current time from the given long value.
|
||
|
* An IllegalIcuArgumentException is thrown when millis is outside the range permitted
|
||
|
* by a Calendar object when in strict mode.
|
||
|
* When in lenient mode the out of range values are pinned to their respective min/max.
|
||
|
* @param millis the new time in UTC milliseconds from the epoch.
|
||
|
*/
|
||
|
public void setTimeInMillis( long millis ) {
|
||
|
if (millis > MAX_MILLIS) {
|
||
|
if(isLenient()) {
|
||
|
millis = MAX_MILLIS;
|
||
|
} else {
|
||
|
throw new IllegalArgumentException("millis value greater than upper bounds for a Calendar : " + millis);
|
||
|
}
|
||
|
} else if (millis < MIN_MILLIS) {
|
||
|
if(isLenient()) {
|
||
|
millis = MIN_MILLIS;
|
||
|
} else {
|
||
|
throw new IllegalArgumentException("millis value less than lower bounds for a Calendar : " + millis);
|
||
|
}
|
||
|
}
|
||
|
time = millis;
|
||
|
areFieldsSet = areAllFieldsSet = false;
|
||
|
isTimeSet = areFieldsVirtuallySet = true;
|
||
|
|
||
|
for (int i=0; i<fields.length; ++i) {
|
||
|
fields[i] = stamp[i] = 0; // UNSET == 0
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Temporal Calendar API.
|
||
|
//-------------------------------------------------------------------------
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns true if the date is in a leap year. Recalculate the current time
|
||
|
* field values if the time value has been changed by a call to * setTime().
|
||
|
* This method is semantically const, but may alter the object in memory.
|
||
|
* A "leap year" is a year that contains more days than other years (for
|
||
|
* solar or lunar calendars) or more months than other years (for lunisolar
|
||
|
* calendars like Hebrew or Chinese), as defined in the ECMAScript Temporal
|
||
|
* proposal.
|
||
|
* @return true if the date in the fields is in a Temporal proposal
|
||
|
* defined leap year. False otherwise.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
public boolean inTemporalLeapYear() {
|
||
|
// Default to Gregorian based leap year rule.
|
||
|
return getActualMaximum(DAY_OF_YEAR) == 366;
|
||
|
}
|
||
|
|
||
|
private static String [] gTemporalMonthCodes = {
|
||
|
"M01", "M02", "M03", "M04", "M05", "M06", "M07", "M08", "M09", "M10", "M11", "M12"
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Gets The Temporal monthCode value corresponding to the month for the date.
|
||
|
* The value is a string identifier that starts with the literal grapheme
|
||
|
* "M" followed by two graphemes representing the zero-padded month number
|
||
|
* of the current month in a normal (non-leap) year and suffixed by an
|
||
|
* optional literal grapheme "L" if this is a leap month in a lunisolar
|
||
|
* calendar. The 25 possible values are "M01" .. "M13" and "M01L" .. "M12L".
|
||
|
* For the Hebrew calendar, the values are "M01" .. "M12" for non-leap year, and
|
||
|
* "M01" .. "M05", "M05L", "M06" .. "M12" for leap year.
|
||
|
* For the Chinese calendar, the values are "M01" .. "M12" for non-leap year and
|
||
|
* in leap year with another monthCode in "M01L" .. "M12L".
|
||
|
* For Coptic and Ethiopian calendar, the Temporal monthCode values for any
|
||
|
* years are "M01" to "M13".
|
||
|
*
|
||
|
* @return One of 25 possible strings in {"M01".."M13", "M01L".."M12L"}.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
public String getTemporalMonthCode() {
|
||
|
int month = get(MONTH);
|
||
|
assert(month < 12);
|
||
|
assert(internalGet(IS_LEAP_MONTH) == 0);
|
||
|
return gTemporalMonthCodes[month];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets The Temporal monthCode which is a string identifier that starts
|
||
|
* with the literal grapheme "M" followed by two graphemes representing
|
||
|
* the zero-padded month number of the current month in a normal
|
||
|
* (non-leap) year and suffixed by an optional literal grapheme "L" if this
|
||
|
* is a leap month in a lunisolar calendar. The 25 possible values are
|
||
|
* "M01" .. "M13" and "M01L" .. "M12L". For Hebrew calendar, the values are
|
||
|
* "M01" .. "M12" for non-leap years, and "M01" .. "M05", "M05L", "M06"
|
||
|
* .. "M12" for leap year.
|
||
|
* For the Chinese calendar, the values are "M01" .. "M12" for non-leap year and
|
||
|
* in leap year with another monthCode in "M01L" .. "M12L".
|
||
|
* For Coptic and Ethiopian calendar, the Temporal monthCode values for any
|
||
|
* years are "M01" to "M13".
|
||
|
* @param temporalMonth One of 25 possible strings in {"M01".. "M12", "M13", "M01L",
|
||
|
* "M12L"}.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
public void setTemporalMonthCode( String temporalMonth ) {
|
||
|
if (temporalMonth.length() == 3 && temporalMonth.charAt(0) == 'M') {
|
||
|
for (int m = 0; m < gTemporalMonthCodes.length; m++) {
|
||
|
if (temporalMonth.equals(gTemporalMonthCodes[m])) {
|
||
|
set(MONTH, m);
|
||
|
set(IS_LEAP_MONTH, 0);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
throw new IllegalArgumentException("Incorrect temporal Month code: " + temporalMonth);
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// End of Temporal Calendar API
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Returns the value for a given time field.
|
||
|
* @param field the given time field.
|
||
|
* @return the value for the given time field.
|
||
|
*/
|
||
|
public final int get(int field)
|
||
|
{
|
||
|
complete();
|
||
|
return fields[field];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value for a given time field. This is an internal method
|
||
|
* for subclasses that does <em>not</em> trigger any calculations.
|
||
|
* @param field the given time field.
|
||
|
* @return the value for the given time field.
|
||
|
*/
|
||
|
protected final int internalGet(int field)
|
||
|
{
|
||
|
return fields[field];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the value for a given time field, or return the given default
|
||
|
* value if the field is not set. This is an internal method for
|
||
|
* subclasses that does <em>not</em> trigger any calculations.
|
||
|
* @param field the given time field.
|
||
|
* @param defaultValue value to return if field is not set
|
||
|
* @return the value for the given time field of defaultValue if the
|
||
|
* field is unset
|
||
|
*/
|
||
|
protected final int internalGet(int field, int defaultValue) {
|
||
|
return (stamp[field] > UNSET) ? fields[field] : defaultValue;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Use this function instead of internalGet(MONTH). The implementation
|
||
|
* check the timestamp of MONTH and ORDINAL_MONTH and use the
|
||
|
* one set later. The subclass should override it to conver the value of ORDINAL_MONTH
|
||
|
* to MONTH correctly if ORDINAL_MONTH has higher priority.
|
||
|
* @return the value for the given time field.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
protected int internalGetMonth()
|
||
|
{
|
||
|
if (resolveFields(MONTH_PRECEDENCE) == MONTH) {
|
||
|
return internalGet(MONTH);
|
||
|
}
|
||
|
return internalGet(ORDINAL_MONTH);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Use this function instead of internalGet(MONTH, defaultValue). The implementation
|
||
|
* check the timestamp of MONTH and ORDINAL_MONTH and use the
|
||
|
* one set later. The subclass should override it to conver the value of ORDINAL_MONTH
|
||
|
* to MONTH correctly if ORDINAL_MONTH has higher priority.
|
||
|
* @param defaultValue a default value used if the MONTH and
|
||
|
* ORDINAL_MONTH are both unset.
|
||
|
* @return the value for the MONTH.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
protected int internalGetMonth(int defaultValue) {
|
||
|
if (resolveFields(MONTH_PRECEDENCE) == MONTH) {
|
||
|
return internalGet(MONTH, defaultValue);
|
||
|
}
|
||
|
return internalGet(ORDINAL_MONTH, defaultValue);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the time field with the given value.
|
||
|
* @param field the given time field.
|
||
|
* @param value the value to be set for the given time field.
|
||
|
*/
|
||
|
public final void set(int field, int value)
|
||
|
{
|
||
|
if (areFieldsVirtuallySet) {
|
||
|
computeFields();
|
||
|
}
|
||
|
fields[field] = value;
|
||
|
/* Ensure that the fNextStamp value doesn't go pass max value for 32 bit integer */
|
||
|
if (nextStamp == STAMP_MAX) {
|
||
|
recalculateStamp();
|
||
|
}
|
||
|
stamp[field] = nextStamp++;
|
||
|
isTimeSet = areFieldsSet = areFieldsVirtuallySet = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the values for the fields year, month, and date.
|
||
|
* Previous values of other fields are retained. If this is not desired,
|
||
|
* call {@link #clear()} first.
|
||
|
* @param year the value used to set the YEAR time field.
|
||
|
* @param month the value used to set the MONTH time field.
|
||
|
* Month value is 0-based. e.g., 0 for January.
|
||
|
* @param date the value used to set the DATE time field.
|
||
|
*/
|
||
|
public final void set(int year, int month, int date)
|
||
|
{
|
||
|
set(YEAR, year);
|
||
|
set(MONTH, month);
|
||
|
set(DATE, date);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the values for the fields year, month, date, hour, and minute.
|
||
|
* Previous values of other fields are retained. If this is not desired,
|
||
|
* call {@link #clear()} first.
|
||
|
* @param year the value used to set the YEAR time field.
|
||
|
* @param month the value used to set the MONTH time field.
|
||
|
* Month value is 0-based. e.g., 0 for January.
|
||
|
* @param date the value used to set the DATE time field.
|
||
|
* @param hour the value used to set the HOUR_OF_DAY time field.
|
||
|
* @param minute the value used to set the MINUTE time field.
|
||
|
*/
|
||
|
public final void set(int year, int month, int date, int hour, int minute)
|
||
|
{
|
||
|
set(YEAR, year);
|
||
|
set(MONTH, month);
|
||
|
set(DATE, date);
|
||
|
set(HOUR_OF_DAY, hour);
|
||
|
set(MINUTE, minute);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the values for the fields year, month, date, hour, minute, and second.
|
||
|
* Previous values of other fields are retained. If this is not desired,
|
||
|
* call {@link #clear} first.
|
||
|
* @param year the value used to set the YEAR time field.
|
||
|
* @param month the value used to set the MONTH time field.
|
||
|
* Month value is 0-based. e.g., 0 for January.
|
||
|
* @param date the value used to set the DATE time field.
|
||
|
* @param hour the value used to set the HOUR_OF_DAY time field.
|
||
|
* @param minute the value used to set the MINUTE time field.
|
||
|
* @param second the value used to set the SECOND time field.
|
||
|
*/
|
||
|
public final void set(int year, int month, int date, int hour, int minute,
|
||
|
int second)
|
||
|
{
|
||
|
set(YEAR, year);
|
||
|
set(MONTH, month);
|
||
|
set(DATE, date);
|
||
|
set(HOUR_OF_DAY, hour);
|
||
|
set(MINUTE, minute);
|
||
|
set(SECOND, second);
|
||
|
}
|
||
|
|
||
|
// -------------------------------------
|
||
|
// For now the full getRelatedYear implementation is here;
|
||
|
// per #10752 move the non-default implementation to subclasses
|
||
|
// (default implementation will do no year adjustment)
|
||
|
|
||
|
/**
|
||
|
* utility function for getRelatedYear
|
||
|
*/
|
||
|
private static int gregoYearFromIslamicStart(int year) {
|
||
|
// ad hoc conversion, improve under #10752
|
||
|
// rough est for now, ok for grego 1846-2138,
|
||
|
// otherwise occasionally wrong (for 3% of years)
|
||
|
int cycle, offset, shift = 0;
|
||
|
if (year >= 1397) {
|
||
|
cycle = (year - 1397) / 67;
|
||
|
offset = (year - 1397) % 67;
|
||
|
shift = 2*cycle + ((offset >= 33)? 1: 0);
|
||
|
} else {
|
||
|
cycle = (year - 1396) / 67 - 1;
|
||
|
offset = -(year - 1396) % 67;
|
||
|
shift = 2*cycle + ((offset <= 33)? 1: 0);
|
||
|
}
|
||
|
return year + 579 - shift;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public final int getRelatedYear() {
|
||
|
int year = get(EXTENDED_YEAR);
|
||
|
CalType type = CalType.GREGORIAN;
|
||
|
String typeString = getType();
|
||
|
for (CalType testType : CalType.values()) {
|
||
|
if (typeString.equals(testType.getId())) {
|
||
|
type = testType;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
switch (type) {
|
||
|
case PERSIAN:
|
||
|
year += 622; break;
|
||
|
case HEBREW:
|
||
|
year -= 3760; break;
|
||
|
case CHINESE:
|
||
|
year -= 2637; break;
|
||
|
case INDIAN:
|
||
|
year += 79; break;
|
||
|
case COPTIC:
|
||
|
year += 284; break;
|
||
|
case ETHIOPIC:
|
||
|
year += 8; break;
|
||
|
case ETHIOPIC_AMETE_ALEM:
|
||
|
year -=5492; break;
|
||
|
case DANGI:
|
||
|
year -= 2333; break;
|
||
|
case ISLAMIC_CIVIL:
|
||
|
case ISLAMIC:
|
||
|
case ISLAMIC_UMALQURA:
|
||
|
case ISLAMIC_TBLA:
|
||
|
case ISLAMIC_RGSA:
|
||
|
year = gregoYearFromIslamicStart(year); break;
|
||
|
// case GREGORIAN:
|
||
|
// case JAPANESE:
|
||
|
// case BUDDHIST:
|
||
|
// case ROC:
|
||
|
// case ISO8601:
|
||
|
default:
|
||
|
// do nothing, EXTENDED_YEAR same as Gregorian
|
||
|
break;
|
||
|
}
|
||
|
return year;
|
||
|
}
|
||
|
|
||
|
// -------------------------------------
|
||
|
// For now the full setRelatedYear implementation is here;
|
||
|
// per #10752 move the non-default implementation to subclasses
|
||
|
// (default implementation will do no year adjustment)
|
||
|
|
||
|
/**
|
||
|
* utility function for setRelatedYear
|
||
|
*/
|
||
|
private static int firstIslamicStartYearFromGrego(int year) {
|
||
|
// ad hoc conversion, improve under #10752
|
||
|
// rough est for now, ok for grego 1846-2138,
|
||
|
// otherwise occasionally wrong (for 3% of years)
|
||
|
int cycle, offset, shift = 0;
|
||
|
if (year >= 1977) {
|
||
|
cycle = (year - 1977) / 65;
|
||
|
offset = (year - 1977) % 65;
|
||
|
shift = 2*cycle + ((offset >= 32)? 1: 0);
|
||
|
} else {
|
||
|
cycle = (year - 1976) / 65 - 1;
|
||
|
offset = -(year - 1976) % 65;
|
||
|
shift = 2*cycle + ((offset <= 32)? 1: 0);
|
||
|
}
|
||
|
return year - 579 + shift;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public final void setRelatedYear(int year) {
|
||
|
CalType type = CalType.GREGORIAN;
|
||
|
String typeString = getType();
|
||
|
for (CalType testType : CalType.values()) {
|
||
|
if (typeString.equals(testType.getId())) {
|
||
|
type = testType;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
switch (type) {
|
||
|
case PERSIAN:
|
||
|
year -= 622; break;
|
||
|
case HEBREW:
|
||
|
year += 3760; break;
|
||
|
case CHINESE:
|
||
|
year += 2637; break;
|
||
|
case INDIAN:
|
||
|
year -= 79; break;
|
||
|
case COPTIC:
|
||
|
year -= 284; break;
|
||
|
case ETHIOPIC:
|
||
|
year -= 8; break;
|
||
|
case ETHIOPIC_AMETE_ALEM:
|
||
|
year +=5492; break;
|
||
|
case DANGI:
|
||
|
year += 2333; break;
|
||
|
case ISLAMIC_CIVIL:
|
||
|
case ISLAMIC:
|
||
|
case ISLAMIC_UMALQURA:
|
||
|
case ISLAMIC_TBLA:
|
||
|
case ISLAMIC_RGSA:
|
||
|
year = firstIslamicStartYearFromGrego(year); break;
|
||
|
// case GREGORIAN:
|
||
|
// case JAPANESE:
|
||
|
// case BUDDHIST:
|
||
|
// case ROC:
|
||
|
// case ISO8601:
|
||
|
default:
|
||
|
// do nothing, EXTENDED_YEAR same as Gregorian
|
||
|
break;
|
||
|
}
|
||
|
set(EXTENDED_YEAR, year);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clears the values of all the time fields.
|
||
|
*/
|
||
|
public final void clear()
|
||
|
{
|
||
|
for (int i=0; i<fields.length; ++i) {
|
||
|
fields[i] = stamp[i] = 0; // UNSET == 0
|
||
|
}
|
||
|
isTimeSet = areFieldsSet = areAllFieldsSet = areFieldsVirtuallySet = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clears the value in the given time field.
|
||
|
* @param field the time field to be cleared.
|
||
|
*/
|
||
|
public final void clear(int field)
|
||
|
{
|
||
|
if (areFieldsVirtuallySet) {
|
||
|
computeFields();
|
||
|
}
|
||
|
fields[field] = 0;
|
||
|
stamp[field] = UNSET;
|
||
|
if (field == MONTH) {
|
||
|
fields[ORDINAL_MONTH] = 0;
|
||
|
stamp[ORDINAL_MONTH] = UNSET;
|
||
|
}
|
||
|
if (field == ORDINAL_MONTH) {
|
||
|
fields[MONTH] = 0;
|
||
|
stamp[MONTH] = UNSET;
|
||
|
}
|
||
|
isTimeSet = areFieldsSet = areAllFieldsSet = areFieldsVirtuallySet = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines if the given time field has a value set.
|
||
|
* @return true if the given time field has a value set; false otherwise.
|
||
|
*/
|
||
|
public final boolean isSet(int field)
|
||
|
{
|
||
|
return areFieldsVirtuallySet || (stamp[field] != UNSET);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fills in any unset fields in the time field list.
|
||
|
*/
|
||
|
protected void complete()
|
||
|
{
|
||
|
if (!isTimeSet) updateTime();
|
||
|
if (!areFieldsSet) {
|
||
|
computeFields(); // fills in unset fields
|
||
|
areFieldsSet = true;
|
||
|
areAllFieldsSet = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compares this calendar to the specified object.
|
||
|
* The result is <code>true</code> if and only if the argument is
|
||
|
* not <code>null</code> and is a <code>Calendar</code> object that
|
||
|
* represents the same calendar as this object.
|
||
|
* @param obj the object to compare with.
|
||
|
* @return <code>true</code> if the objects are the same;
|
||
|
* <code>false</code> otherwise.
|
||
|
*/
|
||
|
@Override
|
||
|
public boolean equals(Object obj) {
|
||
|
if (obj == null) {
|
||
|
return false;
|
||
|
}
|
||
|
if (this == obj) {
|
||
|
return true;
|
||
|
}
|
||
|
if (this.getClass() != obj.getClass()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Calendar that = (Calendar) obj;
|
||
|
|
||
|
return isEquivalentTo(that) &&
|
||
|
getTimeInMillis() == that.getTime().getTime();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns true if the given Calendar object is equivalent to this
|
||
|
* one. An equivalent Calendar will behave exactly as this one
|
||
|
* does, but it may be set to a different time. By contrast, for
|
||
|
* the equals() method to return true, the other Calendar must
|
||
|
* be set to the same time.
|
||
|
*
|
||
|
* @param other the Calendar to be compared with this Calendar
|
||
|
*/
|
||
|
public boolean isEquivalentTo(Calendar other) {
|
||
|
return this.getClass() == other.getClass() &&
|
||
|
isLenient() == other.isLenient() &&
|
||
|
getFirstDayOfWeek() == other.getFirstDayOfWeek() &&
|
||
|
getMinimalDaysInFirstWeek() == other.getMinimalDaysInFirstWeek() &&
|
||
|
getTimeZone().equals(other.getTimeZone()) &&
|
||
|
getRepeatedWallTimeOption() == other.getRepeatedWallTimeOption() &&
|
||
|
getSkippedWallTimeOption() == other.getSkippedWallTimeOption();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a hash code for this calendar.
|
||
|
* @return a hash code value for this object.
|
||
|
*/
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
/* Don't include the time because (a) we don't want the hash value to
|
||
|
* move around just because a calendar is set to different times, and
|
||
|
* (b) we don't want to trigger a time computation just to get a hash.
|
||
|
* Note that it is not necessary for unequal objects to always have
|
||
|
* unequal hashes, but equal objects must have equal hashes. */
|
||
|
return (lenient ? 1 : 0)
|
||
|
| (firstDayOfWeek << 1)
|
||
|
| (minimalDaysInFirstWeek << 4)
|
||
|
| (repeatedWallTime << 7)
|
||
|
| (skippedWallTime << 9)
|
||
|
| (zone.hashCode() << 11);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the difference in milliseconds between the moment this
|
||
|
* calendar is set to and the moment the given calendar or Date object
|
||
|
* is set to.
|
||
|
*/
|
||
|
private long compare(Object that) {
|
||
|
long thatMs;
|
||
|
if (that instanceof Calendar) {
|
||
|
thatMs = ((Calendar)that).getTimeInMillis();
|
||
|
} else if (that instanceof Date) {
|
||
|
thatMs = ((Date)that).getTime();
|
||
|
} else {
|
||
|
throw new IllegalArgumentException(that + "is not a Calendar or Date");
|
||
|
}
|
||
|
return getTimeInMillis() - thatMs;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compares the time field records.
|
||
|
* Equivalent to comparing result of conversion to UTC.
|
||
|
* @param when the Calendar to be compared with this Calendar.
|
||
|
* @return true if the current time of this Calendar is before
|
||
|
* the time of Calendar when; false otherwise.
|
||
|
*/
|
||
|
public boolean before(Object when) {
|
||
|
return compare(when) < 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compares the time field records.
|
||
|
* Equivalent to comparing result of conversion to UTC.
|
||
|
* @param when the Calendar to be compared with this Calendar.
|
||
|
* @return true if the current time of this Calendar is after
|
||
|
* the time of Calendar when; false otherwise.
|
||
|
*/
|
||
|
public boolean after(Object when) {
|
||
|
return compare(when) > 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the maximum value that this field could have, given the
|
||
|
* current date. For example, with the Gregorian date February 3, 1997
|
||
|
* and the {@link #DAY_OF_MONTH DAY_OF_MONTH} field, the actual maximum
|
||
|
* is 28; for February 3, 1996 it is 29.
|
||
|
*
|
||
|
* <p>The actual maximum computation ignores smaller fields and the
|
||
|
* current value of like-sized fields. For example, the actual maximum
|
||
|
* of the DAY_OF_YEAR or MONTH depends only on the year and supra-year
|
||
|
* fields. The actual maximum of the DAY_OF_MONTH depends, in
|
||
|
* addition, on the MONTH field and any other fields at that
|
||
|
* granularity (such as IS_LEAP_MONTH). The
|
||
|
* DAY_OF_WEEK_IN_MONTH field does not depend on the current
|
||
|
* DAY_OF_WEEK; it returns the maximum for any day of week in the
|
||
|
* current month. Likewise for the WEEK_OF_MONTH and WEEK_OF_YEAR
|
||
|
* fields.
|
||
|
*
|
||
|
* @param field the field whose maximum is desired
|
||
|
* @return the maximum of the given field for the current date of this calendar
|
||
|
* @see #getMaximum
|
||
|
* @see #getLeastMaximum
|
||
|
*/
|
||
|
public int getActualMaximum(int field) {
|
||
|
int result;
|
||
|
|
||
|
switch (field) {
|
||
|
case DAY_OF_MONTH:
|
||
|
{
|
||
|
Calendar cal = (Calendar) clone();
|
||
|
cal.setLenient(true);
|
||
|
cal.prepareGetActual(field, false);
|
||
|
result = handleGetMonthLength(cal.get(EXTENDED_YEAR), cal.get(MONTH));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case DAY_OF_YEAR:
|
||
|
{
|
||
|
Calendar cal = (Calendar) clone();
|
||
|
cal.setLenient(true);
|
||
|
cal.prepareGetActual(field, false);
|
||
|
result = handleGetYearLength(cal.get(EXTENDED_YEAR));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ERA:
|
||
|
case DAY_OF_WEEK:
|
||
|
case AM_PM:
|
||
|
case HOUR:
|
||
|
case HOUR_OF_DAY:
|
||
|
case MINUTE:
|
||
|
case SECOND:
|
||
|
case MILLISECOND:
|
||
|
case ZONE_OFFSET:
|
||
|
case DST_OFFSET:
|
||
|
case DOW_LOCAL:
|
||
|
case JULIAN_DAY:
|
||
|
case MILLISECONDS_IN_DAY:
|
||
|
// These fields all have fixed minima/maxima
|
||
|
result = getMaximum(field);
|
||
|
break;
|
||
|
|
||
|
case ORDINAL_MONTH:
|
||
|
result = inTemporalLeapYear() ? getMaximum(ORDINAL_MONTH) : getLeastMaximum(ORDINAL_MONTH);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// For all other fields, do it the hard way....
|
||
|
result = getActualHelper(field, getLeastMaximum(field), getMaximum(field));
|
||
|
break;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the minimum value that this field could have, given the current date.
|
||
|
* For most fields, this is the same as {@link #getMinimum getMinimum}
|
||
|
* and {@link #getGreatestMinimum getGreatestMinimum}. However, some fields,
|
||
|
* especially those related to week number, are more complicated.
|
||
|
* <p>
|
||
|
* For example, assume {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek}
|
||
|
* returns 4 and {@link #getFirstDayOfWeek getFirstDayOfWeek} returns SUNDAY.
|
||
|
* If the first day of the month is Sunday, Monday, Tuesday, or Wednesday
|
||
|
* there will be four or more days in the first week, so it will be week number 1,
|
||
|
* and <code>getActualMinimum(WEEK_OF_MONTH)</code> will return 1. However,
|
||
|
* if the first of the month is a Thursday, Friday, or Saturday, there are
|
||
|
* <em>not</em> four days in that week, so it is week number 0, and
|
||
|
* <code>getActualMinimum(WEEK_OF_MONTH)</code> will return 0.
|
||
|
* <p>
|
||
|
* @param field the field whose actual minimum value is desired.
|
||
|
* @return the minimum of the given field for the current date of this calendar
|
||
|
*
|
||
|
* @see #getMinimum
|
||
|
* @see #getGreatestMinimum
|
||
|
*/
|
||
|
public int getActualMinimum(int field) {
|
||
|
int result;
|
||
|
|
||
|
switch (field) {
|
||
|
case DAY_OF_WEEK:
|
||
|
case AM_PM:
|
||
|
case HOUR:
|
||
|
case HOUR_OF_DAY:
|
||
|
case MINUTE:
|
||
|
case SECOND:
|
||
|
case MILLISECOND:
|
||
|
case ZONE_OFFSET:
|
||
|
case DST_OFFSET:
|
||
|
case DOW_LOCAL:
|
||
|
case JULIAN_DAY:
|
||
|
case MILLISECONDS_IN_DAY:
|
||
|
// These fields all have fixed minima/maxima
|
||
|
result = getMinimum(field);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// For all other fields, do it the hard way....
|
||
|
result = getActualHelper(field, getGreatestMinimum(field), getMinimum(field));
|
||
|
break;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prepare this calendar for computing the actual minimum or maximum.
|
||
|
* This method modifies this calendar's fields; it is called on a
|
||
|
* temporary calendar.
|
||
|
*
|
||
|
* <p>Rationale: The semantics of getActualXxx() is to return the
|
||
|
* maximum or minimum value that the given field can take, taking into
|
||
|
* account other relevant fields. In general these other fields are
|
||
|
* larger fields. For example, when computing the actual maximum
|
||
|
* DAY_OF_MONTH, the current value of DAY_OF_MONTH itself is ignored,
|
||
|
* as is the value of any field smaller.
|
||
|
*
|
||
|
* <p>The time fields all have fixed minima and maxima, so we don't
|
||
|
* need to worry about them. This also lets us set the
|
||
|
* MILLISECONDS_IN_DAY to zero to erase any effects the time fields
|
||
|
* might have when computing date fields.
|
||
|
*
|
||
|
* <p>DAY_OF_WEEK is adjusted specially for the WEEK_OF_MONTH and
|
||
|
* WEEK_OF_YEAR fields to ensure that they are computed correctly.
|
||
|
*/
|
||
|
protected void prepareGetActual(int field, boolean isMinimum) {
|
||
|
set(MILLISECONDS_IN_DAY, 0);
|
||
|
|
||
|
switch (field) {
|
||
|
case YEAR:
|
||
|
case EXTENDED_YEAR:
|
||
|
set(DAY_OF_YEAR, getGreatestMinimum(DAY_OF_YEAR));
|
||
|
break;
|
||
|
|
||
|
case YEAR_WOY:
|
||
|
set(WEEK_OF_YEAR, getGreatestMinimum(WEEK_OF_YEAR));
|
||
|
break;
|
||
|
|
||
|
case MONTH:
|
||
|
set(DAY_OF_MONTH, getGreatestMinimum(DAY_OF_MONTH));
|
||
|
break;
|
||
|
|
||
|
case DAY_OF_WEEK_IN_MONTH:
|
||
|
// For dowim, the maximum occurs for the DOW of the first of the
|
||
|
// month.
|
||
|
set(DAY_OF_MONTH, 1);
|
||
|
set(DAY_OF_WEEK, get(DAY_OF_WEEK)); // Make this user set
|
||
|
break;
|
||
|
|
||
|
case WEEK_OF_MONTH:
|
||
|
case WEEK_OF_YEAR:
|
||
|
// If we're counting weeks, set the day of the week to either the
|
||
|
// first or last localized DOW. We know the last week of a month
|
||
|
// or year will contain the first day of the week, and that the
|
||
|
// first week will contain the last DOW.
|
||
|
{
|
||
|
int dow = firstDayOfWeek;
|
||
|
if (isMinimum) {
|
||
|
dow = (dow + 6) % 7; // set to last DOW
|
||
|
if (dow < SUNDAY) {
|
||
|
dow += 7;
|
||
|
}
|
||
|
}
|
||
|
set(DAY_OF_WEEK, dow);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Do this last to give it the newest time stamp
|
||
|
set(field, getGreatestMinimum(field));
|
||
|
}
|
||
|
|
||
|
private int getActualHelper(int field, int startValue, int endValue) {
|
||
|
|
||
|
if (startValue == endValue) {
|
||
|
// if we know that the maximum value is always the same, just return it
|
||
|
return startValue;
|
||
|
}
|
||
|
|
||
|
final int delta = (endValue > startValue) ? 1 : -1;
|
||
|
|
||
|
// clone the calendar so we don't mess with the real one, and set it to
|
||
|
// accept anything for the field values
|
||
|
Calendar work = (Calendar) clone();
|
||
|
|
||
|
// need to resolve time here, otherwise, fields set for actual limit
|
||
|
// may cause conflict with fields previously set (but not yet resolved).
|
||
|
work.complete();
|
||
|
|
||
|
work.setLenient(true);
|
||
|
work.prepareGetActual(field, delta < 0);
|
||
|
|
||
|
// now try each value from the start to the end one by one until
|
||
|
// we get a value that normalizes to another value. The last value that
|
||
|
// normalizes to itself is the actual maximum for the current date
|
||
|
|
||
|
work.set(field, startValue);
|
||
|
// prepareGetActual sets the first day of week in the same week with
|
||
|
// the first day of a month. Unlike WEEK_OF_YEAR, week number for the
|
||
|
// which week contains days from both previous and current month is
|
||
|
// not unique. For example, last several days in the previous month
|
||
|
// is week 5, and the rest of week is week 1.
|
||
|
if (work.get(field) != startValue
|
||
|
&& field != WEEK_OF_MONTH && delta > 0) {
|
||
|
return startValue;
|
||
|
}
|
||
|
int result = startValue;
|
||
|
do {
|
||
|
startValue += delta;
|
||
|
work.add(field, delta);
|
||
|
if (work.get(field) != startValue) {
|
||
|
break;
|
||
|
}
|
||
|
result = startValue;
|
||
|
} while (startValue != endValue);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rolls (up/down) a single unit of time on the given field. If the
|
||
|
* field is rolled past its maximum allowable value, it will "wrap" back
|
||
|
* to its minimum and continue rolling. For
|
||
|
* example, to roll the current date up by one day, you can call:
|
||
|
* <p>
|
||
|
* <code>roll({@link #DATE}, true)</code>
|
||
|
* <p>
|
||
|
* When rolling on the {@link #YEAR} field, it will roll the year
|
||
|
* value in the range between 1 and the value returned by calling
|
||
|
* {@link #getMaximum getMaximum}({@link #YEAR}).
|
||
|
* <p>
|
||
|
* When rolling on certain fields, the values of other fields may conflict and
|
||
|
* need to be changed. For example, when rolling the <code>MONTH</code> field
|
||
|
* for the Gregorian date 1/31/96 upward, the <code>DAY_OF_MONTH</code> field
|
||
|
* must be adjusted so that the result is 2/29/96 rather than the invalid
|
||
|
* 2/31/96.
|
||
|
* <p>
|
||
|
* Rolling up always means rolling forward in time (unless
|
||
|
* the limit of the field is reached, in which case it may pin or wrap), so for the
|
||
|
* Gregorian calendar, starting with 100 BC and rolling the year up results in 99 BC.
|
||
|
* When eras have a definite beginning and end (as in the Chinese calendar, or as in
|
||
|
* most eras in the Japanese calendar) then rolling the year past either limit of the
|
||
|
* era will cause the year to wrap around. When eras only have a limit at one end,
|
||
|
* then attempting to roll the year past that limit will result in pinning the year
|
||
|
* at that limit. Note that for most calendars in which era 0 years move forward in
|
||
|
* time (such as Buddhist, Hebrew, or Islamic), it is possible for add or roll to
|
||
|
* result in negative years for era 0 (that is the only way to represent years before
|
||
|
* the calendar epoch in such calendars).
|
||
|
* <p>
|
||
|
* <b>Note:</b> Calling <tt>roll(field, true)</tt> N times is <em>not</em>
|
||
|
* necessarily equivalent to calling <tt>roll(field, N)</tt>. For example,
|
||
|
* imagine that you start with the date Gregorian date January 31, 1995. If you call
|
||
|
* <tt>roll(Calendar.MONTH, 2)</tt>, the result will be March 31, 1995.
|
||
|
* But if you call <tt>roll(Calendar.MONTH, true)</tt>, the result will be
|
||
|
* February 28, 1995. Calling it one more time will give March 28, 1995, which
|
||
|
* is usually not the desired result.
|
||
|
* <p>
|
||
|
* <b>Note:</b> You should always use <tt>roll</tt> and <tt>add</tt> rather
|
||
|
* than attempting to perform arithmetic operations directly on the fields
|
||
|
* of a <tt>Calendar</tt>. It is quite possible for <tt>Calendar</tt> subclasses
|
||
|
* to have fields with non-linear behavior, for example missing months
|
||
|
* or days during non-leap years. The subclasses' <tt>add</tt> and <tt>roll</tt>
|
||
|
* methods will take this into account, while simple arithmetic manipulations
|
||
|
* may give invalid results.
|
||
|
* <p>
|
||
|
* @param field the calendar field to roll.
|
||
|
*
|
||
|
* @param up indicates if the value of the specified time field is to be
|
||
|
* rolled up or rolled down. Use <code>true</code> if rolling up,
|
||
|
* <code>false</code> otherwise.
|
||
|
*
|
||
|
* @exception IllegalArgumentException if the field is invalid or refers
|
||
|
* to a field that cannot be handled by this method.
|
||
|
* @see #roll(int, int)
|
||
|
* @see #add
|
||
|
*/
|
||
|
public final void roll(int field, boolean up)
|
||
|
{
|
||
|
roll(field, up ? +1 : -1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rolls (up/down) a specified amount time on the given field. For
|
||
|
* example, to roll the current date up by three days, you can call
|
||
|
* <code>roll(Calendar.DATE, 3)</code>. If the
|
||
|
* field is rolled past its maximum allowable value, it will "wrap" back
|
||
|
* to its minimum and continue rolling.
|
||
|
* For example, calling <code>roll(Calendar.DATE, 10)</code>
|
||
|
* on a Gregorian calendar set to 4/25/96 will result in the date 4/5/96.
|
||
|
* <p>
|
||
|
* When rolling on certain fields, the values of other fields may conflict and
|
||
|
* need to be changed. For example, when rolling the {@link #MONTH MONTH} field
|
||
|
* for the Gregorian date 1/31/96 by +1, the {@link #DAY_OF_MONTH DAY_OF_MONTH} field
|
||
|
* must be adjusted so that the result is 2/29/96 rather than the invalid
|
||
|
* 2/31/96.
|
||
|
* <p>
|
||
|
* Rolling by a positive value always means rolling forward in time (unless
|
||
|
* the limit of the field is reached, in which case it may pin or wrap), so for the
|
||
|
* Gregorian calendar, starting with 100 BC and rolling the year by + 1 results in 99 BC.
|
||
|
* When eras have a definite beginning and end (as in the Chinese calendar, or as in
|
||
|
* most eras in the Japanese calendar) then rolling the year past either limit of the
|
||
|
* era will cause the year to wrap around. When eras only have a limit at one end,
|
||
|
* then attempting to roll the year past that limit will result in pinning the year
|
||
|
* at that limit. Note that for most calendars in which era 0 years move forward in
|
||
|
* time (such as Buddhist, Hebrew, or Islamic), it is possible for add or roll to
|
||
|
* result in negative years for era 0 (that is the only way to represent years before
|
||
|
* the calendar epoch in such calendars).
|
||
|
* <p>
|
||
|
* <strong>[icu] Note:</strong> the ICU implementation of this method is able to roll
|
||
|
* all fields except for {@link #ERA ERA}, {@link #DST_OFFSET DST_OFFSET},
|
||
|
* and {@link #ZONE_OFFSET ZONE_OFFSET}. Subclasses may, of course, add support for
|
||
|
* additional fields in their overrides of <code>roll</code>.
|
||
|
* <p>
|
||
|
* <b>Note:</b> You should always use <tt>roll</tt> and <tt>add</tt> rather
|
||
|
* than attempting to perform arithmetic operations directly on the fields
|
||
|
* of a <tt>Calendar</tt>. It is quite possible for <tt>Calendar</tt> subclasses
|
||
|
* to have fields with non-linear behavior, for example missing months
|
||
|
* or days during non-leap years. The subclasses' <tt>add</tt> and <tt>roll</tt>
|
||
|
* methods will take this into account, while simple arithmetic manipulations
|
||
|
* may give invalid results.
|
||
|
* <p>
|
||
|
* <b>Subclassing:</b><br>
|
||
|
* This implementation of <code>roll</code> assumes that the behavior of the
|
||
|
* field is continuous between its minimum and maximum, which are found by
|
||
|
* calling {@link #getActualMinimum getActualMinimum} and {@link #getActualMaximum getActualMaximum}.
|
||
|
* For most such fields, simple addition, subtraction, and modulus operations
|
||
|
* are sufficient to perform the roll. For week-related fields,
|
||
|
* the results of {@link #getFirstDayOfWeek getFirstDayOfWeek} and
|
||
|
* {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek} are also necessary.
|
||
|
* Subclasses can override these two methods if their values differ from the defaults.
|
||
|
* <p>
|
||
|
* Subclasses that have fields for which the assumption of continuity breaks
|
||
|
* down must override <code>roll</code> to handle those fields specially.
|
||
|
* For example, in the Hebrew calendar the month "Adar I"
|
||
|
* only occurs in leap years; in other years the calendar jumps from
|
||
|
* Shevat (month #4) to Adar (month #6). The
|
||
|
* {@link HebrewCalendar#roll HebrewCalendar.roll} method takes this into account,
|
||
|
* so that rolling the month of Shevat by one gives the proper result (Adar) in a
|
||
|
* non-leap year.
|
||
|
* <p>
|
||
|
* @param field the calendar field to roll.
|
||
|
* @param amount the amount by which the field should be rolled.
|
||
|
*
|
||
|
* @exception IllegalArgumentException if the field is invalid or refers
|
||
|
* to a field that cannot be handled by this method.
|
||
|
* @see #roll(int, boolean)
|
||
|
* @see #add
|
||
|
*/
|
||
|
public void roll(int field, int amount) {
|
||
|
|
||
|
if (amount == 0) {
|
||
|
return; // Nothing to do
|
||
|
}
|
||
|
|
||
|
complete();
|
||
|
|
||
|
switch (field) {
|
||
|
case DAY_OF_MONTH:
|
||
|
case AM_PM:
|
||
|
case MINUTE:
|
||
|
case SECOND:
|
||
|
case MILLISECOND:
|
||
|
case MILLISECONDS_IN_DAY:
|
||
|
case ERA:
|
||
|
// These are the standard roll instructions. These work for all
|
||
|
// simple cases, that is, cases in which the limits are fixed, such
|
||
|
// as the hour, the day of the month, and the era.
|
||
|
{
|
||
|
int min = getActualMinimum(field);
|
||
|
int max = getActualMaximum(field);
|
||
|
int gap = max - min + 1;
|
||
|
|
||
|
int value = internalGet(field) + amount;
|
||
|
value = (value - min) % gap;
|
||
|
if (value < 0) {
|
||
|
value += gap;
|
||
|
}
|
||
|
value += min;
|
||
|
|
||
|
set(field, value);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
case HOUR:
|
||
|
case HOUR_OF_DAY:
|
||
|
// Rolling the hour is difficult on the ONSET and CEASE days of
|
||
|
// daylight savings. For example, if the change occurs at
|
||
|
// 2 AM, we have the following progression:
|
||
|
// ONSET: 12 Std -> 1 Std -> 3 Dst -> 4 Dst
|
||
|
// CEASE: 12 Dst -> 1 Dst -> 1 Std -> 2 Std
|
||
|
// To get around this problem we don't use fields; we manipulate
|
||
|
// the time in millis directly.
|
||
|
{
|
||
|
// Assume min == 0 in calculations below
|
||
|
long start = getTimeInMillis();
|
||
|
int oldHour = internalGet(field);
|
||
|
int max = getMaximum(field);
|
||
|
int newHour = (oldHour + amount) % (max + 1);
|
||
|
if (newHour < 0) {
|
||
|
newHour += max + 1;
|
||
|
}
|
||
|
setTimeInMillis(start + ONE_HOUR * ((long)newHour - oldHour));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
case MONTH:
|
||
|
case ORDINAL_MONTH:
|
||
|
// Rolling the month involves both pinning the final value
|
||
|
// 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>.
|
||
|
{
|
||
|
int max = getActualMaximum(MONTH);
|
||
|
int mon = (internalGetMonth() + amount) % (max+1);
|
||
|
|
||
|
if (mon < 0) {
|
||
|
mon += (max + 1);
|
||
|
}
|
||
|
set(MONTH, mon);
|
||
|
|
||
|
// Keep the day of month in range. We don't want to spill over
|
||
|
// into the next month; e.g., we don't want jan31 + 1 mo -> feb31 ->
|
||
|
// mar3.
|
||
|
pinField(DAY_OF_MONTH);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
case YEAR:
|
||
|
case YEAR_WOY:
|
||
|
// * If era==0 and years go backwards in time, change sign of amount.
|
||
|
// * Until we have new API per #9393, we temporarily hardcode knowledge of
|
||
|
// which calendars have era 0 years that go backwards.
|
||
|
{
|
||
|
int era = get(ERA);
|
||
|
if (era == 0 && isEra0CountingBackward()) {
|
||
|
amount = -amount;
|
||
|
}
|
||
|
int newYear = internalGet(field) + amount;
|
||
|
if (era > 0 || newYear >= 1) {
|
||
|
int maxYear = getActualMaximum(field);
|
||
|
if (maxYear < 32768) {
|
||
|
// this era has real bounds, roll should wrap years
|
||
|
if (newYear < 1) {
|
||
|
newYear = maxYear - ((-newYear) % maxYear);
|
||
|
} else if (newYear > maxYear) {
|
||
|
newYear = ((newYear - 1) % maxYear) + 1;
|
||
|
}
|
||
|
// else era is unbounded, just pin low year instead of wrapping
|
||
|
} else if (newYear < 1) {
|
||
|
newYear = 1;
|
||
|
}
|
||
|
// else we are in era 0 with newYear < 1;
|
||
|
// calendars with years that go backwards must pin the year value at 0,
|
||
|
// other calendars can have years < 0 in era 0
|
||
|
} else if (era == 0 && isEra0CountingBackward()) {
|
||
|
newYear = 1;
|
||
|
}
|
||
|
set(field, newYear);
|
||
|
pinField(MONTH);
|
||
|
pinField(DAY_OF_MONTH);
|
||
|
return;
|
||
|
}
|
||
|
case EXTENDED_YEAR:
|
||
|
// Rolling the year can involve pinning the DAY_OF_MONTH.
|
||
|
set(field, internalGet(field) + amount);
|
||
|
pinField(MONTH);
|
||
|
pinField(DAY_OF_MONTH);
|
||
|
return;
|
||
|
|
||
|
case WEEK_OF_MONTH:
|
||
|
{
|
||
|
// This is tricky, because during the roll we may have to shift
|
||
|
// to a different day of the week. For example:
|
||
|
|
||
|
// s m t w r f s
|
||
|
// 1 2 3 4 5
|
||
|
// 6 7 8 9 10 11 12
|
||
|
|
||
|
// When rolling from the 6th or 7th back one week, we go to the
|
||
|
// 1st (assuming that the first partial week counts). The same
|
||
|
// thing happens at the end of the month.
|
||
|
|
||
|
// The other tricky thing is that we have to figure out whether
|
||
|
// the first partial week actually counts or not, based on the
|
||
|
// minimal first days in the week. And we have to use the
|
||
|
// correct first day of the week to delineate the week
|
||
|
// boundaries.
|
||
|
|
||
|
// Here's our algorithm. First, we find the real boundaries of
|
||
|
// the month. Then we discard the first partial week if it
|
||
|
// doesn't count in this locale. Then we fill in the ends with
|
||
|
// phantom days, so that the first partial week and the last
|
||
|
// partial week are full weeks. We then have a nice square
|
||
|
// block of weeks. We do the usual rolling within this block,
|
||
|
// as is done elsewhere in this method. If we wind up on one of
|
||
|
// the phantom days that we added, we recognize this and pin to
|
||
|
// the first or the last day of the month. Easy, eh?
|
||
|
|
||
|
// Normalize the DAY_OF_WEEK so that 0 is the first day of the week
|
||
|
// in this locale. We have dow in 0..6.
|
||
|
int dow = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek();
|
||
|
if (dow < 0) dow += 7;
|
||
|
|
||
|
// Find the day of the week (normalized for locale) for the first
|
||
|
// of the month.
|
||
|
int fdm = (dow - internalGet(DAY_OF_MONTH) + 1) % 7;
|
||
|
if (fdm < 0) fdm += 7;
|
||
|
|
||
|
// Get the first day of the first full week of the month,
|
||
|
// including phantom days, if any. Figure out if the first week
|
||
|
// counts or not; if it counts, then fill in phantom days. If
|
||
|
// not, advance to the first real full week (skip the partial week).
|
||
|
int start;
|
||
|
if ((7 - fdm) < getMinimalDaysInFirstWeek())
|
||
|
start = 8 - fdm; // Skip the first partial week
|
||
|
else
|
||
|
start = 1 - fdm; // This may be zero or negative
|
||
|
|
||
|
// Get the day of the week (normalized for locale) for the last
|
||
|
// day of the month.
|
||
|
int monthLen = getActualMaximum(DAY_OF_MONTH);
|
||
|
int ldm = (monthLen - internalGet(DAY_OF_MONTH) + dow) % 7;
|
||
|
// We know monthLen >= DAY_OF_MONTH so we skip the += 7 step here.
|
||
|
|
||
|
// Get the limit day for the blocked-off rectangular month; that
|
||
|
// is, the day which is one past the last day of the month,
|
||
|
// after the month has already been filled in with phantom days
|
||
|
// to fill out the last week. This day has a normalized DOW of 0.
|
||
|
int limit = monthLen + 7 - ldm;
|
||
|
|
||
|
// Now roll between start and (limit - 1).
|
||
|
int gap = limit - start;
|
||
|
int day_of_month = (internalGet(DAY_OF_MONTH) + amount*7 -
|
||
|
start) % gap;
|
||
|
if (day_of_month < 0) day_of_month += gap;
|
||
|
day_of_month += start;
|
||
|
|
||
|
// Finally, pin to the real start and end of the month.
|
||
|
if (day_of_month < 1) day_of_month = 1;
|
||
|
if (day_of_month > monthLen) day_of_month = monthLen;
|
||
|
|
||
|
// Set the DAY_OF_MONTH. We rely on the fact that this field
|
||
|
// takes precedence over everything else (since all other fields
|
||
|
// are also set at this point). If this fact changes (if the
|
||
|
// disambiguation algorithm changes) then we will have to unset
|
||
|
// the appropriate fields here so that DAY_OF_MONTH is attended
|
||
|
// to.
|
||
|
set(DAY_OF_MONTH, day_of_month);
|
||
|
return;
|
||
|
}
|
||
|
case WEEK_OF_YEAR:
|
||
|
{
|
||
|
// This follows the outline of WEEK_OF_MONTH, except it applies
|
||
|
// to the whole year. Please see the comment for WEEK_OF_MONTH
|
||
|
// for general notes.
|
||
|
|
||
|
// Normalize the DAY_OF_WEEK so that 0 is the first day of the week
|
||
|
// in this locale. We have dow in 0..6.
|
||
|
int dow = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek();
|
||
|
if (dow < 0) dow += 7;
|
||
|
|
||
|
// Find the day of the week (normalized for locale) for the first
|
||
|
// of the year.
|
||
|
int fdy = (dow - internalGet(DAY_OF_YEAR) + 1) % 7;
|
||
|
if (fdy < 0) fdy += 7;
|
||
|
|
||
|
// Get the first day of the first full week of the year,
|
||
|
// including phantom days, if any. Figure out if the first week
|
||
|
// counts or not; if it counts, then fill in phantom days. If
|
||
|
// not, advance to the first real full week (skip the partial week).
|
||
|
int start;
|
||
|
if ((7 - fdy) < getMinimalDaysInFirstWeek())
|
||
|
start = 8 - fdy; // Skip the first partial week
|
||
|
else
|
||
|
start = 1 - fdy; // This may be zero or negative
|
||
|
|
||
|
// Get the day of the week (normalized for locale) for the last
|
||
|
// day of the year.
|
||
|
int yearLen = getActualMaximum(DAY_OF_YEAR);
|
||
|
int ldy = (yearLen - internalGet(DAY_OF_YEAR) + dow) % 7;
|
||
|
// We know yearLen >= DAY_OF_YEAR so we skip the += 7 step here.
|
||
|
|
||
|
// Get the limit day for the blocked-off rectangular year; that
|
||
|
// is, the day which is one past the last day of the year,
|
||
|
// after the year has already been filled in with phantom days
|
||
|
// to fill out the last week. This day has a normalized DOW of 0.
|
||
|
int limit = yearLen + 7 - ldy;
|
||
|
|
||
|
// Now roll between start and (limit - 1).
|
||
|
int gap = limit - start;
|
||
|
int day_of_year = (internalGet(DAY_OF_YEAR) + amount*7 -
|
||
|
start) % gap;
|
||
|
if (day_of_year < 0) day_of_year += gap;
|
||
|
day_of_year += start;
|
||
|
|
||
|
// Finally, pin to the real start and end of the month.
|
||
|
if (day_of_year < 1) day_of_year = 1;
|
||
|
if (day_of_year > yearLen) day_of_year = yearLen;
|
||
|
|
||
|
// Make sure that the year and day of year are attended to by
|
||
|
// clearing other fields which would normally take precedence.
|
||
|
// If the disambiguation algorithm is changed, this section will
|
||
|
// have to be updated as well.
|
||
|
set(DAY_OF_YEAR, day_of_year);
|
||
|
clear(MONTH);
|
||
|
clear(ORDINAL_MONTH);
|
||
|
return;
|
||
|
}
|
||
|
case DAY_OF_YEAR:
|
||
|
{
|
||
|
// Roll the day of year using millis. Compute the millis for
|
||
|
// the start of the year, and get the length of the year.
|
||
|
long delta = amount * ONE_DAY; // Scale up from days to millis
|
||
|
long min2 = time - (internalGet(DAY_OF_YEAR) - 1) * ONE_DAY;
|
||
|
int yearLength = getActualMaximum(DAY_OF_YEAR);
|
||
|
time = (time + delta - min2) % (yearLength*ONE_DAY);
|
||
|
if (time < 0) time += yearLength*ONE_DAY;
|
||
|
setTimeInMillis(time + min2);
|
||
|
return;
|
||
|
}
|
||
|
case DAY_OF_WEEK:
|
||
|
case DOW_LOCAL:
|
||
|
{
|
||
|
// Roll the day of week using millis. Compute the millis for
|
||
|
// the start of the week, using the first day of week setting.
|
||
|
// Restrict the millis to [start, start+7days).
|
||
|
long delta = amount * ONE_DAY; // Scale up from days to millis
|
||
|
// Compute the number of days before the current day in this
|
||
|
// week. This will be a value 0..6.
|
||
|
int leadDays = internalGet(field);
|
||
|
leadDays -= (field == DAY_OF_WEEK) ? getFirstDayOfWeek() : 1;
|
||
|
if (leadDays < 0) leadDays += 7;
|
||
|
long min2 = time - leadDays * ONE_DAY;
|
||
|
time = (time + delta - min2) % ONE_WEEK;
|
||
|
if (time < 0) time += ONE_WEEK;
|
||
|
setTimeInMillis(time + min2);
|
||
|
return;
|
||
|
}
|
||
|
case DAY_OF_WEEK_IN_MONTH:
|
||
|
{
|
||
|
// Roll the day of week in the month using millis. Determine
|
||
|
// the first day of the week in the month, and then the last,
|
||
|
// and then roll within that range.
|
||
|
long delta = amount * ONE_WEEK; // Scale up from weeks to millis
|
||
|
// Find the number of same days of the week before this one
|
||
|
// in this month.
|
||
|
int preWeeks = (internalGet(DAY_OF_MONTH) - 1) / 7;
|
||
|
// Find the number of same days of the week after this one
|
||
|
// in this month.
|
||
|
int postWeeks = (getActualMaximum(DAY_OF_MONTH) -
|
||
|
internalGet(DAY_OF_MONTH)) / 7;
|
||
|
// From these compute the min and gap millis for rolling.
|
||
|
long min2 = time - preWeeks * ONE_WEEK;
|
||
|
long gap2 = ONE_WEEK * (preWeeks + postWeeks + 1); // Must add 1!
|
||
|
// Roll within this range
|
||
|
time = (time + delta - min2) % gap2;
|
||
|
if (time < 0) time += gap2;
|
||
|
setTimeInMillis(time + min2);
|
||
|
return;
|
||
|
}
|
||
|
case JULIAN_DAY:
|
||
|
set(field, internalGet(field) + amount);
|
||
|
return;
|
||
|
default:
|
||
|
// Other fields cannot be rolled by this method
|
||
|
throw new IllegalArgumentException("Calendar.roll(" + fieldName(field) +
|
||
|
") not supported");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a signed amount to a specified field, using this calendar's rules.
|
||
|
* For example, to add three days to the current date, you can call
|
||
|
* <code>add(Calendar.DATE, 3)</code>.
|
||
|
* <p>
|
||
|
* When adding to certain fields, the values of other fields may conflict and
|
||
|
* need to be changed. For example, when adding one to the {@link #MONTH MONTH} field
|
||
|
* for the Gregorian date 1/31/96, the {@link #DAY_OF_MONTH DAY_OF_MONTH} field
|
||
|
* must be adjusted so that the result is 2/29/96 rather than the invalid
|
||
|
* 2/31/96.
|
||
|
* <p>
|
||
|
* Adding a positive value always means moving forward in time, so for the Gregorian
|
||
|
* calendar, starting with 100 BC and adding +1 to year results in 99 BC (even though
|
||
|
* this actually reduces the numeric value of the field itself).
|
||
|
* <p>
|
||
|
* <strong>[icu] Note:</strong> The ICU implementation of this method is able to add to
|
||
|
* all fields except for {@link #ERA ERA}, {@link #DST_OFFSET DST_OFFSET},
|
||
|
* and {@link #ZONE_OFFSET ZONE_OFFSET}. Subclasses may, of course, add support for
|
||
|
* additional fields in their overrides of <code>add</code>.
|
||
|
* <p>
|
||
|
* <b>Note:</b> You should always use <tt>roll</tt> and <tt>add</tt> rather
|
||
|
* than attempting to perform arithmetic operations directly on the fields
|
||
|
* of a <tt>Calendar</tt>. It is quite possible for <tt>Calendar</tt> subclasses
|
||
|
* to have fields with non-linear behavior, for example missing months
|
||
|
* or days during non-leap years. The subclasses' <tt>add</tt> and <tt>roll</tt>
|
||
|
* methods will take this into account, while simple arithmetic manipulations
|
||
|
* may give invalid results.
|
||
|
* <p>
|
||
|
* <b>Subclassing:</b><br>
|
||
|
* This implementation of <code>add</code> assumes that the behavior of the
|
||
|
* field is continuous between its minimum and maximum, which are found by
|
||
|
* calling {@link #getActualMinimum getActualMinimum} and
|
||
|
* {@link #getActualMaximum getActualMaximum}.
|
||
|
* For such fields, simple arithmetic operations are sufficient to
|
||
|
* perform the add.
|
||
|
* <p>
|
||
|
* Subclasses that have fields for which this assumption of continuity breaks
|
||
|
* down must override <code>add</code> to handle those fields specially.
|
||
|
* For example, in the Hebrew calendar the month "Adar I"
|
||
|
* only occurs in leap years; in other years the calendar jumps from
|
||
|
* Shevat (month #4) to Adar (month #6). The
|
||
|
* {@link HebrewCalendar#add HebrewCalendar.add} method takes this into account,
|
||
|
* so that adding one month
|
||
|
* to a date in Shevat gives the proper result (Adar) in a non-leap year.
|
||
|
* <p>
|
||
|
* @param field the time field.
|
||
|
* @param amount the amount to add to the field.
|
||
|
*
|
||
|
* @exception IllegalArgumentException if the field is invalid or refers
|
||
|
* to a field that cannot be handled by this method.
|
||
|
* @see #roll(int, int)
|
||
|
*/
|
||
|
@SuppressWarnings("fallthrough")
|
||
|
public void add(int field, int amount) {
|
||
|
|
||
|
if (amount == 0) {
|
||
|
return; // Do nothing!
|
||
|
}
|
||
|
|
||
|
// We handle most fields in the same way. The algorithm is to add
|
||
|
// a computed amount of millis to the current millis. The only
|
||
|
// wrinkle is with DST (and/or a change to the zone's UTC offset, which
|
||
|
// we'll include with DST) -- for some fields, like the DAY_OF_MONTH,
|
||
|
// we don't want the wall time to shift due to changes in DST. If the
|
||
|
// result of the add operation is to move from DST to Standard, or
|
||
|
// vice versa, we need to adjust by an hour forward or back,
|
||
|
// respectively. For such fields we set keepWallTimeInvariant to true.
|
||
|
|
||
|
// We only adjust the DST for fields larger than an hour. For
|
||
|
// fields smaller than an hour, we cannot adjust for DST without
|
||
|
// causing problems. for instance, if you add one hour to April 5,
|
||
|
// 1998, 1:00 AM, in PST, the time becomes "2:00 AM PDT" (an
|
||
|
// illegal value), but then the adjustment sees the change and
|
||
|
// compensates by subtracting an hour. As a result the time
|
||
|
// doesn't advance at all.
|
||
|
|
||
|
// For some fields larger than a day, such as a MONTH, we pin the
|
||
|
// DAY_OF_MONTH. This allows <March 31>.add(MONTH, 1) to be
|
||
|
// <April 30>, rather than <April 31> => <May 1>.
|
||
|
|
||
|
long delta = amount; // delta in ms
|
||
|
boolean keepWallTimeInvariant = true;
|
||
|
|
||
|
switch (field) {
|
||
|
case ERA:
|
||
|
set(field, get(field) + amount);
|
||
|
pinField(ERA);
|
||
|
return;
|
||
|
|
||
|
case YEAR:
|
||
|
case YEAR_WOY:
|
||
|
// * If era=0 and years go backwards in time, change sign of amount.
|
||
|
// * Until we have new API per #9393, we temporarily hardcode knowledge of
|
||
|
// which calendars have era 0 years that go backwards.
|
||
|
// * Note that for YEAR (but not YEAR_WOY) we could instead handle
|
||
|
// this by applying the amount to the EXTENDED_YEAR field; but since
|
||
|
// we would still need to handle YEAR_WOY as below, might as well
|
||
|
// also handle YEAR the same way.
|
||
|
{
|
||
|
int era = get(ERA);
|
||
|
if (era == 0 && isEra0CountingBackward()) {
|
||
|
amount = -amount;
|
||
|
}
|
||
|
}
|
||
|
// Fall through into standard handling
|
||
|
case EXTENDED_YEAR:
|
||
|
case MONTH:
|
||
|
case ORDINAL_MONTH:
|
||
|
{
|
||
|
boolean oldLenient = isLenient();
|
||
|
setLenient(true);
|
||
|
set(field, get(field) + amount);
|
||
|
pinField(DAY_OF_MONTH);
|
||
|
if(oldLenient==false) {
|
||
|
complete();
|
||
|
setLenient(oldLenient);
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
|
||
|
case WEEK_OF_YEAR:
|
||
|
case WEEK_OF_MONTH:
|
||
|
case DAY_OF_WEEK_IN_MONTH:
|
||
|
delta *= ONE_WEEK;
|
||
|
break;
|
||
|
|
||
|
case AM_PM:
|
||
|
delta *= 12 * ONE_HOUR;
|
||
|
break;
|
||
|
|
||
|
case DAY_OF_MONTH:
|
||
|
case DAY_OF_YEAR:
|
||
|
case DAY_OF_WEEK:
|
||
|
case DOW_LOCAL:
|
||
|
case JULIAN_DAY:
|
||
|
delta *= ONE_DAY;
|
||
|
break;
|
||
|
|
||
|
case HOUR_OF_DAY:
|
||
|
case HOUR:
|
||
|
delta *= ONE_HOUR;
|
||
|
keepWallTimeInvariant = false;
|
||
|
break;
|
||
|
|
||
|
case MINUTE:
|
||
|
delta *= ONE_MINUTE;
|
||
|
keepWallTimeInvariant = false;
|
||
|
break;
|
||
|
|
||
|
case SECOND:
|
||
|
delta *= ONE_SECOND;
|
||
|
keepWallTimeInvariant = false;
|
||
|
break;
|
||
|
|
||
|
case MILLISECOND:
|
||
|
case MILLISECONDS_IN_DAY:
|
||
|
keepWallTimeInvariant = false;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new IllegalArgumentException("Calendar.add(" + fieldName(field) +
|
||
|
") not supported");
|
||
|
}
|
||
|
|
||
|
// In order to keep the wall time invariant (for fields where this is
|
||
|
// appropriate), check the combined DST & ZONE offset before and
|
||
|
// after the add() operation. If it changes, then adjust the millis
|
||
|
// to compensate.
|
||
|
int prevOffset = 0;
|
||
|
int prevWallTime = 0;
|
||
|
if (keepWallTimeInvariant) {
|
||
|
prevOffset = get(DST_OFFSET) + get(ZONE_OFFSET);
|
||
|
prevWallTime = get(MILLISECONDS_IN_DAY);
|
||
|
}
|
||
|
|
||
|
setTimeInMillis(getTimeInMillis() + delta);
|
||
|
|
||
|
if (keepWallTimeInvariant) {
|
||
|
int newWallTime = get(MILLISECONDS_IN_DAY);
|
||
|
if (newWallTime != prevWallTime) {
|
||
|
// There is at least one zone transition between the base
|
||
|
// time and the result time. As the result, wall time has
|
||
|
// changed.
|
||
|
long t = internalGetTimeInMillis();
|
||
|
int newOffset = get(DST_OFFSET) + get(ZONE_OFFSET);
|
||
|
if (newOffset != prevOffset) {
|
||
|
// When the difference of the previous UTC offset and
|
||
|
// the new UTC offset exceeds 1 full day, we do not want
|
||
|
// to roll over/back the date. For now, this only happens
|
||
|
// in Samoa (Pacific/Apia) on Dec 30, 2011. See ticket:9452.
|
||
|
long adjAmount = (prevOffset - newOffset) % ONE_DAY;
|
||
|
if (adjAmount != 0) {
|
||
|
setTimeInMillis(t + adjAmount);
|
||
|
newWallTime = get(MILLISECONDS_IN_DAY);
|
||
|
}
|
||
|
if (newWallTime != prevWallTime) {
|
||
|
// The result wall time or adjusted wall time was shifted because
|
||
|
// the target wall time does not exist on the result date.
|
||
|
switch (skippedWallTime) {
|
||
|
case WALLTIME_FIRST:
|
||
|
if (adjAmount > 0) {
|
||
|
setTimeInMillis(t);
|
||
|
}
|
||
|
break;
|
||
|
case WALLTIME_LAST:
|
||
|
if (adjAmount < 0) {
|
||
|
setTimeInMillis(t);
|
||
|
}
|
||
|
break;
|
||
|
case WALLTIME_NEXT_VALID:
|
||
|
long tmpT = adjAmount > 0 ? internalGetTimeInMillis() : t;
|
||
|
Long immediatePrevTrans = getImmediatePreviousZoneTransition(tmpT);
|
||
|
if (immediatePrevTrans != null) {
|
||
|
setTimeInMillis(immediatePrevTrans);
|
||
|
} else {
|
||
|
throw new RuntimeException("Could not locate a time zone transition before " + tmpT);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the name of this calendar in the language of the given locale.
|
||
|
*/
|
||
|
public String getDisplayName(Locale loc) {
|
||
|
return this.getClass().getName();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the name of this calendar in the language of the given locale.
|
||
|
*/
|
||
|
public String getDisplayName(ULocale loc) {
|
||
|
return this.getClass().getName();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compares the times (in millis) represented by two
|
||
|
* <code>Calendar</code> objects.
|
||
|
*
|
||
|
* @param that the <code>Calendar</code> to compare to this.
|
||
|
* @return <code>0</code> if the time represented by
|
||
|
* this <code>Calendar</code> is equal to the time represented
|
||
|
* by that <code>Calendar</code>, a value less than
|
||
|
* <code>0</code> if the time represented by this is before
|
||
|
* the time represented by that, and a value greater than
|
||
|
* <code>0</code> if the time represented by this
|
||
|
* is after the time represented by that.
|
||
|
* @throws NullPointerException if that
|
||
|
* <code>Calendar</code> is null.
|
||
|
* @throws IllegalArgumentException if the time of that
|
||
|
* <code>Calendar</code> can't be obtained because of invalid
|
||
|
* calendar values.
|
||
|
*/
|
||
|
@Override
|
||
|
public int compareTo(Calendar that) {
|
||
|
long v = getTimeInMillis() - that.getTimeInMillis();
|
||
|
return v < 0 ? -1 : (v > 0 ? 1 : 0);
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Interface for creating custom DateFormats for different types of Calendars
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a <code>DateFormat</code> appropriate to this calendar.
|
||
|
* Subclasses wishing to specialize this behavior should override
|
||
|
* {@link #handleGetDateFormat}.
|
||
|
*/
|
||
|
public DateFormat getDateTimeFormat(int dateStyle, int timeStyle, Locale loc) {
|
||
|
return formatHelper(this, ULocale.forLocale(loc), dateStyle, timeStyle);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns a <code>DateFormat</code> appropriate to this calendar.
|
||
|
* Subclasses wishing to specialize this behavior should override
|
||
|
* {@link #handleGetDateFormat}.
|
||
|
*/
|
||
|
public DateFormat getDateTimeFormat(int dateStyle, int timeStyle, ULocale loc) {
|
||
|
return formatHelper(this, loc, dateStyle, timeStyle);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a <code>DateFormat</code> appropriate to this calendar.
|
||
|
* This is a framework method for subclasses to override. This method
|
||
|
* is responsible for creating the calendar-specific DateFormat and
|
||
|
* DateFormatSymbols objects as needed.
|
||
|
* @param pattern the pattern, specific to the <code>DateFormat</code>
|
||
|
* subclass
|
||
|
* @param locale the locale for which the symbols should be drawn
|
||
|
* @return a <code>DateFormat</code> appropriate to this calendar
|
||
|
*/
|
||
|
protected DateFormat handleGetDateFormat(String pattern, Locale locale) {
|
||
|
return handleGetDateFormat(pattern, null, ULocale.forLocale(locale));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a <code>DateFormat</code> appropriate to this calendar.
|
||
|
* This is a framework method for subclasses to override. This method
|
||
|
* is responsible for creating the calendar-specific DateFormat and
|
||
|
* DateFormatSymbols objects as needed.
|
||
|
* @param pattern the pattern, specific to the <code>DateFormat</code>
|
||
|
* subclass
|
||
|
* @param override The override string. A numbering system override string can take one of the following forms:
|
||
|
* 1). If just a numbering system name is specified, it applies to all numeric fields in the date format pattern.
|
||
|
* 2). To specify an alternate numbering system on a field by field basis, use the field letters from the pattern
|
||
|
* followed by an = sign, followed by the numbering system name. For example, to specify that just the year
|
||
|
* be formatted using Hebrew digits, use the override "y=hebr". Multiple overrides can be specified in a single
|
||
|
* string by separating them with a semi-colon. For example, the override string "m=thai;y=deva" would format using
|
||
|
* Thai digits for the month and Devanagari digits for the year.
|
||
|
* @param locale the locale for which the symbols should be drawn
|
||
|
* @return a <code>DateFormat</code> appropriate to this calendar
|
||
|
*/
|
||
|
protected DateFormat handleGetDateFormat(String pattern, String override, Locale locale) {
|
||
|
return handleGetDateFormat(pattern, override, ULocale.forLocale(locale));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a <code>DateFormat</code> appropriate to this calendar.
|
||
|
* This is a framework method for subclasses to override. This method
|
||
|
* is responsible for creating the calendar-specific DateFormat and
|
||
|
* DateFormatSymbols objects as needed.
|
||
|
* @param pattern the pattern, specific to the <code>DateFormat</code>
|
||
|
* subclass
|
||
|
* @param locale the locale for which the symbols should be drawn
|
||
|
* @return a <code>DateFormat</code> appropriate to this calendar
|
||
|
*/
|
||
|
protected DateFormat handleGetDateFormat(String pattern, ULocale locale) {
|
||
|
return handleGetDateFormat(pattern, null, locale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a <code>DateFormat</code> appropriate to this calendar.
|
||
|
* This is a framework method for subclasses to override. This method
|
||
|
* is responsible for creating the calendar-specific DateFormat and
|
||
|
* DateFormatSymbols objects as needed.
|
||
|
* @param pattern the pattern, specific to the <code>DateFormat</code>
|
||
|
* subclass
|
||
|
* @param locale the locale for which the symbols should be drawn
|
||
|
* @return a <code>DateFormat</code> appropriate to this calendar
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
protected DateFormat handleGetDateFormat(String pattern, String override, ULocale locale) {
|
||
|
FormatConfiguration fmtConfig = new FormatConfiguration();
|
||
|
fmtConfig.pattern = pattern;
|
||
|
fmtConfig.override = override;
|
||
|
fmtConfig.formatData = new DateFormatSymbols(this, locale);
|
||
|
fmtConfig.loc = locale;
|
||
|
fmtConfig.cal = this;
|
||
|
|
||
|
return SimpleDateFormat.getInstance(fmtConfig);
|
||
|
}
|
||
|
|
||
|
// date format pattern cache
|
||
|
private static final ICUCache<String, PatternData> PATTERN_CACHE =
|
||
|
new SimpleCache<>();
|
||
|
// final fallback patterns (match current root)
|
||
|
private static final String[] DEFAULT_PATTERNS = {
|
||
|
"HH:mm:ss z",
|
||
|
"HH:mm:ss z",
|
||
|
"HH:mm:ss",
|
||
|
"HH:mm",
|
||
|
"y MMMM d, EEEE",
|
||
|
"y MMMM d",
|
||
|
"y MMM d",
|
||
|
"y-MM-dd",
|
||
|
"{1} {0}",
|
||
|
"{1} {0}",
|
||
|
"{1} {0}",
|
||
|
"{1} {0}",
|
||
|
"{1} {0}"
|
||
|
};
|
||
|
private static final String[] DEFAULT_ATTIME_PATTERNS = {
|
||
|
"{1} {0}",
|
||
|
"{1} {0}",
|
||
|
"{1} {0}",
|
||
|
"{1} {0}"
|
||
|
};
|
||
|
// final fallback patterns
|
||
|
private static final String[] TIME_SKELETONS = {
|
||
|
"jmmsszzzz", // Full
|
||
|
"jmmssz", // Long
|
||
|
"jmmss", // Medium
|
||
|
"jmm" // Short
|
||
|
};
|
||
|
|
||
|
static private DateFormat formatHelper(Calendar cal, ULocale loc, int dateStyle,
|
||
|
int timeStyle) {
|
||
|
if (timeStyle < DateFormat.NONE || timeStyle > DateFormat.SHORT) {
|
||
|
throw new IllegalArgumentException("Illegal time style " + timeStyle);
|
||
|
}
|
||
|
if (dateStyle < DateFormat.NONE || dateStyle > DateFormat.SHORT) {
|
||
|
throw new IllegalArgumentException("Illegal date style " + dateStyle);
|
||
|
}
|
||
|
|
||
|
PatternData patternData = PatternData.make(cal, loc);
|
||
|
String override = null;
|
||
|
|
||
|
// Resolve a pattern for the date/time style
|
||
|
String pattern = null;
|
||
|
if ((timeStyle >= 0) && (dateStyle >= 0)) {
|
||
|
pattern = SimpleFormatterImpl.formatRawPattern(
|
||
|
patternData.getDateAtTimePattern(dateStyle), 2, 2,
|
||
|
patternData.patterns[timeStyle],
|
||
|
patternData.patterns[dateStyle + 4]);
|
||
|
// Might need to merge the overrides from the date and time into a single
|
||
|
// override string TODO: Right now we are forcing the date's override into the
|
||
|
// time style.
|
||
|
if ( patternData.overrides != null ) {
|
||
|
String dateOverride = patternData.overrides[dateStyle + 4];
|
||
|
String timeOverride = patternData.overrides[timeStyle];
|
||
|
override = mergeOverrideStrings(
|
||
|
patternData.patterns[dateStyle+4],
|
||
|
patternData.patterns[timeStyle],
|
||
|
dateOverride, timeOverride);
|
||
|
}
|
||
|
} else if (timeStyle >= 0) {
|
||
|
pattern = patternData.patterns[timeStyle];
|
||
|
if ( patternData.overrides != null ) {
|
||
|
override = patternData.overrides[timeStyle];
|
||
|
}
|
||
|
} else if (dateStyle >= 0) {
|
||
|
pattern = patternData.patterns[dateStyle + 4];
|
||
|
if ( patternData.overrides != null ) {
|
||
|
override = patternData.overrides[dateStyle + 4];
|
||
|
}
|
||
|
} else {
|
||
|
throw new IllegalArgumentException("No date or time style specified");
|
||
|
}
|
||
|
DateFormat result = cal.handleGetDateFormat(pattern, override, loc);
|
||
|
result.setCalendar(cal);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
// Android patch (http://b/28832222) start.
|
||
|
// Expose method to get format string for java.time.
|
||
|
/**
|
||
|
* Get the date time format string for the specified values.
|
||
|
* This is a copy of {@link #formatHelper(Calendar, ULocale, int, int)} with the following
|
||
|
* changes:
|
||
|
* <ul>
|
||
|
* <li>Made public, but hidden</li>
|
||
|
* <li>take calendar type string instead of Calendar</li>
|
||
|
* <li>Ignore overrides</li>
|
||
|
* <li>Return format string instead of DateFormat.</li>
|
||
|
* </ul>
|
||
|
* This is not meant as public API.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
// TODO: Check if calType can be passed via keyword on loc parameter instead.
|
||
|
public static String getDateTimeFormatString(ULocale loc, String calType, int dateStyle,
|
||
|
int timeStyle) {
|
||
|
if (timeStyle < DateFormat.NONE || timeStyle > DateFormat.SHORT) {
|
||
|
throw new IllegalArgumentException("Illegal time style " + timeStyle);
|
||
|
}
|
||
|
if (dateStyle < DateFormat.NONE || dateStyle > DateFormat.SHORT) {
|
||
|
throw new IllegalArgumentException("Illegal date style " + dateStyle);
|
||
|
}
|
||
|
|
||
|
PatternData patternData = PatternData.make(loc, calType);
|
||
|
|
||
|
// Resolve a pattern for the date/time style
|
||
|
String pattern = null;
|
||
|
if ((timeStyle >= 0) && (dateStyle >= 0)) {
|
||
|
pattern = SimpleFormatterImpl.formatRawPattern(
|
||
|
patternData.getDateAtTimePattern(dateStyle), 2, 2,
|
||
|
patternData.patterns[timeStyle],
|
||
|
patternData.patterns[dateStyle + 4]);
|
||
|
} else if (timeStyle >= 0) {
|
||
|
pattern = patternData.patterns[timeStyle];
|
||
|
} else if (dateStyle >= 0) {
|
||
|
pattern = patternData.patterns[dateStyle + 4];
|
||
|
} else {
|
||
|
throw new IllegalArgumentException("No date or time style specified");
|
||
|
}
|
||
|
return pattern;
|
||
|
}
|
||
|
// Android patch (http://b/28832222) end.
|
||
|
|
||
|
static class PatternData {
|
||
|
// TODO make this even more object oriented
|
||
|
private String[] patterns;
|
||
|
private String[] overrides;
|
||
|
private String[] atTimePatterns;
|
||
|
public PatternData(String[] patterns, String[] overrides, String[] atTimePatterns) {
|
||
|
this.patterns = patterns;
|
||
|
this.overrides = overrides;
|
||
|
this.atTimePatterns = atTimePatterns;
|
||
|
}
|
||
|
private String getDateTimePattern(int dateStyle) {
|
||
|
int glueIndex = 8;
|
||
|
if (patterns.length >= 13) {
|
||
|
glueIndex += (dateStyle + 1);
|
||
|
}
|
||
|
final String dateTimePattern = patterns[glueIndex];
|
||
|
return dateTimePattern;
|
||
|
}
|
||
|
private String getDateAtTimePattern(int dateStyle) {
|
||
|
if (atTimePatterns != null && atTimePatterns.length >= 4) {
|
||
|
final String dateTimePattern = atTimePatterns[dateStyle];
|
||
|
return dateTimePattern;
|
||
|
} else {
|
||
|
return getDateTimePattern(dateStyle);
|
||
|
}
|
||
|
}
|
||
|
private static PatternData make(Calendar cal, ULocale loc) {
|
||
|
// Android patch (http://b/28832222) start.
|
||
|
return make(loc, cal.getType());
|
||
|
}
|
||
|
private static PatternData make(ULocale loc, String calType) {
|
||
|
// Android patch (http://b/28832222) end.
|
||
|
// First, try to get a pattern from PATTERN_CACHE
|
||
|
String key = loc.getBaseName() + "+" + calType;
|
||
|
PatternData patternData = null;
|
||
|
boolean hasHourCycleKeywords = loc.getKeywordValue("rg") != null
|
||
|
|| loc.getKeywordValue("hours") != null;
|
||
|
if (!hasHourCycleKeywords) {
|
||
|
// don't look in the cache if the locale specifies the rg or hc ("hours") keywords
|
||
|
patternData = PATTERN_CACHE.get(key);
|
||
|
}
|
||
|
if (patternData == null) {
|
||
|
// Cache missed. Get one from bundle
|
||
|
try {
|
||
|
patternData = getPatternData(loc, calType);
|
||
|
} catch (MissingResourceException e) {
|
||
|
patternData = new PatternData(DEFAULT_PATTERNS, null, DEFAULT_ATTIME_PATTERNS);
|
||
|
}
|
||
|
if (!hasHourCycleKeywords) {
|
||
|
PATTERN_CACHE.put(key, patternData);
|
||
|
}
|
||
|
}
|
||
|
return patternData;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retrieves the DateTime patterns and overrides from the resource bundle and generates a
|
||
|
* new PatternData object.
|
||
|
* @param locale Locale to retrieve.
|
||
|
* @param calType Calendar type to retrieve. If not found will fallback to gregorian.
|
||
|
* @return PatternData object for this locale and calendarType.
|
||
|
*/
|
||
|
private static PatternData getPatternData(ULocale locale, String calType) {
|
||
|
ICUResourceBundle rb =
|
||
|
(ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
|
||
|
ICUResourceBundle dtPatternsRb = rb.findWithFallback("calendar/" + calType + "/DateTimePatterns");
|
||
|
if (dtPatternsRb == null) {
|
||
|
dtPatternsRb = rb.getWithFallback("calendar/gregorian/DateTimePatterns");
|
||
|
}
|
||
|
|
||
|
int patternsSize = dtPatternsRb.getSize();
|
||
|
String[] dateTimePatterns = new String[patternsSize];
|
||
|
String[] dateTimePatternsOverrides = new String[patternsSize];
|
||
|
int i = 0; // index for dateTimePatterns, dateTimePatternsOverrides
|
||
|
|
||
|
boolean useDTPG = false;
|
||
|
if (locale.getKeywordValue("rg") != null || locale.getKeywordValue("hours") != null) {
|
||
|
useDTPG = true;
|
||
|
} else {
|
||
|
String baseLocID = locale.getBaseName();
|
||
|
if (!baseLocID.isEmpty() && !baseLocID.equals("und")) {
|
||
|
ULocale baseLoc = new ULocale(baseLocID);
|
||
|
// The following is different from ICU4C, where we can get the valid locale
|
||
|
// for the SimpleDateFormat object. Here we do not have a SimpleDateFormat and
|
||
|
// valid locale for the Calendar is a bit meaningless.
|
||
|
ULocale validLoc = ULocale.addLikelySubtags(dtPatternsRb.getULocale());
|
||
|
if (validLoc != baseLoc) {
|
||
|
String baseReg = baseLoc.getCountry();
|
||
|
if ((!baseReg.isEmpty() && !baseReg.equals(validLoc.getCountry()))
|
||
|
|| !baseLoc.getLanguage().equals(validLoc.getLanguage())) {
|
||
|
useDTPG = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (useDTPG) {
|
||
|
// use DTPG if the standard time formats may have the wrong time cycle,
|
||
|
// because the valid locale differs in important ways (region, language)
|
||
|
// from the base locale.
|
||
|
// We could *also* check whether they do actually have a mismatch with
|
||
|
// the time cycle preferences for the region, but that is a lot more
|
||
|
// work for little or no additional benefit, since just going ahead
|
||
|
// and always synthesizing the time format as per the following should
|
||
|
// create a locale-appropriate pattern with cycle that matches the
|
||
|
// region preferences anyway.
|
||
|
// In this case we get the first 4 entries of dateTimePatterns using
|
||
|
// DateTimePatternGenerator, not resource data.
|
||
|
DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstanceNoStdPat(locale);
|
||
|
for (; i < TIME_SKELETONS.length; i++) {
|
||
|
dateTimePatterns[i] = dtpg.getBestPattern(TIME_SKELETONS[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (; i < patternsSize; i++) { // get all or remaining dateTimePatterns entries
|
||
|
ICUResourceBundle concatenationPatternRb = (ICUResourceBundle) dtPatternsRb.get(i);
|
||
|
switch (concatenationPatternRb.getType()) {
|
||
|
case UResourceBundle.STRING:
|
||
|
dateTimePatterns[i] = concatenationPatternRb.getString();
|
||
|
break;
|
||
|
case UResourceBundle.ARRAY:
|
||
|
dateTimePatterns[i] = concatenationPatternRb.getString(0);
|
||
|
dateTimePatternsOverrides[i] = concatenationPatternRb.getString(1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dtPatternsRb = rb.findWithFallback("calendar/" + calType + "/DateTimePatterns%atTime");
|
||
|
if (dtPatternsRb == null) {
|
||
|
dtPatternsRb = rb.findWithFallback("calendar/gregorian/DateTimePatterns%atTime");
|
||
|
}
|
||
|
String[] atTimePatterns = null;
|
||
|
if (dtPatternsRb != null) {
|
||
|
patternsSize = dtPatternsRb.getSize();
|
||
|
atTimePatterns = new String[patternsSize];
|
||
|
if (patternsSize >= 4) {
|
||
|
for (i = 0; i < 4; i++) {
|
||
|
ICUResourceBundle concatenationPatternRb = (ICUResourceBundle) dtPatternsRb.get(i);
|
||
|
if (concatenationPatternRb.getType() == UResourceBundle.STRING) {
|
||
|
atTimePatterns[i] = concatenationPatternRb.getString();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return new PatternData(dateTimePatterns, dateTimePatternsOverrides, atTimePatterns);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static String getDateTimePattern(Calendar cal, ULocale uLocale, int dateStyle) {
|
||
|
PatternData patternData = PatternData.make(cal, uLocale);
|
||
|
return patternData.getDateTimePattern(dateStyle);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static String getDateAtTimePattern(Calendar cal, ULocale uLocale, int dateStyle) {
|
||
|
PatternData patternData = PatternData.make(cal, uLocale);
|
||
|
return patternData.getDateAtTimePattern(dateStyle);
|
||
|
}
|
||
|
|
||
|
private static String mergeOverrideStrings( String datePattern, String timePattern,
|
||
|
String dateOverride, String timeOverride ) {
|
||
|
|
||
|
if ( dateOverride == null && timeOverride == null ) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
if ( dateOverride == null ) {
|
||
|
return expandOverride(timePattern,timeOverride);
|
||
|
}
|
||
|
|
||
|
if ( timeOverride == null ) {
|
||
|
return expandOverride(datePattern,dateOverride);
|
||
|
}
|
||
|
|
||
|
if ( dateOverride.equals(timeOverride) ) {
|
||
|
return dateOverride;
|
||
|
}
|
||
|
|
||
|
return (expandOverride(datePattern,dateOverride)+";"+
|
||
|
expandOverride(timePattern,timeOverride));
|
||
|
|
||
|
}
|
||
|
|
||
|
private static final char QUOTE = '\'';
|
||
|
private static String expandOverride(String pattern, String override) {
|
||
|
|
||
|
if (override.indexOf('=') >= 0) {
|
||
|
return override;
|
||
|
}
|
||
|
boolean inQuotes = false;
|
||
|
char prevChar = ' ';
|
||
|
StringBuilder result = new StringBuilder();
|
||
|
|
||
|
StringCharacterIterator it = new StringCharacterIterator(pattern);
|
||
|
|
||
|
for (char c = it.first(); c!= StringCharacterIterator.DONE; c = it.next()) {
|
||
|
if ( c == QUOTE ) {
|
||
|
inQuotes = !inQuotes;
|
||
|
prevChar = c;
|
||
|
continue;
|
||
|
}
|
||
|
if ( !inQuotes && c != prevChar ) {
|
||
|
if (result.length() > 0) {
|
||
|
result.append(";");
|
||
|
}
|
||
|
result.append(c);
|
||
|
result.append("=");
|
||
|
result.append(override);
|
||
|
}
|
||
|
prevChar = c;
|
||
|
}
|
||
|
return result.toString();
|
||
|
}
|
||
|
/**
|
||
|
* An instance of FormatConfiguration represents calendar specific
|
||
|
* date format configuration and used for calling the ICU private
|
||
|
* SimpleDateFormat factory method.
|
||
|
*
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide Only a subset of ICU is exposed in Android
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public static class FormatConfiguration {
|
||
|
private String pattern;
|
||
|
private String override;
|
||
|
private DateFormatSymbols formatData;
|
||
|
private Calendar cal;
|
||
|
private ULocale loc;
|
||
|
|
||
|
// Only Calendar can instantiate
|
||
|
private FormatConfiguration() {
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the pattern string
|
||
|
* @return the format pattern string
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public String getPatternString() {
|
||
|
return pattern;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public String getOverrideString() {
|
||
|
return override;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the calendar
|
||
|
* @return the calendar
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public Calendar getCalendar() {
|
||
|
return cal;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the locale
|
||
|
* @return the locale
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public ULocale getLocale() {
|
||
|
return loc;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the format symbols
|
||
|
* @return the format symbols
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public DateFormatSymbols getDateFormatSymbols() {
|
||
|
return formatData;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Protected utility methods for use by subclasses. These are very handy
|
||
|
// for implementing add, roll, and computeFields.
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Adjust the specified field so that it is within
|
||
|
* the allowable range for the date to which this calendar is set.
|
||
|
* For example, in a Gregorian calendar pinning the {@link #DAY_OF_MONTH DAY_OF_MONTH}
|
||
|
* field for a calendar set to April 31 would cause it to be set
|
||
|
* to April 30.
|
||
|
* <p>
|
||
|
* <b>Subclassing:</b>
|
||
|
* <br>
|
||
|
* This utility method is intended for use by subclasses that need to implement
|
||
|
* their own overrides of {@link #roll roll} and {@link #add add}.
|
||
|
* <p>
|
||
|
* <b>Note:</b>
|
||
|
* <code>pinField</code> is implemented in terms of
|
||
|
* {@link #getActualMinimum getActualMinimum}
|
||
|
* and {@link #getActualMaximum getActualMaximum}. If either of those methods uses
|
||
|
* a slow, iterative algorithm for a particular field, it would be
|
||
|
* unwise to attempt to call <code>pinField</code> for that field. If you
|
||
|
* really do need to do so, you should override this method to do
|
||
|
* something more efficient for that field.
|
||
|
* <p>
|
||
|
* @param field The calendar field whose value should be pinned.
|
||
|
*
|
||
|
* @see #getActualMinimum
|
||
|
* @see #getActualMaximum
|
||
|
*/
|
||
|
protected void pinField(int field) {
|
||
|
int max = getActualMaximum(field);
|
||
|
int min = getActualMinimum(field);
|
||
|
|
||
|
if (fields[field] > max) {
|
||
|
set(field, max);
|
||
|
} else if (fields[field] < min) {
|
||
|
set(field, min);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The year in this calendar is counting from 1 backward if the era is 0.
|
||
|
* @return The year in era 0 of this calendar is counting backward from 1.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
protected boolean isEra0CountingBackward() {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the week number of a day, within a period. This may be the week number in
|
||
|
* a year or the week number in a month. Usually this will be a value >= 1, but if
|
||
|
* some initial days of the period are excluded from week 1, because
|
||
|
* {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek} is > 1, then
|
||
|
* the week number will be zero for those
|
||
|
* initial days. This method requires the day number and day of week for some
|
||
|
* known date in the period in order to determine the day of week
|
||
|
* on the desired day.
|
||
|
* <p>
|
||
|
* <b>Subclassing:</b>
|
||
|
* <br>
|
||
|
* This method is intended for use by subclasses in implementing their
|
||
|
* {@link #computeTime computeTime} and/or {@link #computeFields computeFields} methods.
|
||
|
* It is often useful in {@link #getActualMinimum getActualMinimum} and
|
||
|
* {@link #getActualMaximum getActualMaximum} as well.
|
||
|
* <p>
|
||
|
* This variant is handy for computing the week number of some other
|
||
|
* day of a period (often the first or last day of the period) when its day
|
||
|
* of the week is not known but the day number and day of week for some other
|
||
|
* day in the period (e.g. the current date) <em>is</em> known.
|
||
|
* <p>
|
||
|
* @param desiredDay The {@link #DAY_OF_YEAR DAY_OF_YEAR} or
|
||
|
* {@link #DAY_OF_MONTH DAY_OF_MONTH} whose week number is desired.
|
||
|
* Should be 1 for the first day of the period.
|
||
|
*
|
||
|
* @param dayOfPeriod The {@link #DAY_OF_YEAR DAY_OF_YEAR}
|
||
|
* or {@link #DAY_OF_MONTH DAY_OF_MONTH} for a day in the period whose
|
||
|
* {@link #DAY_OF_WEEK DAY_OF_WEEK} is specified by the
|
||
|
* <code>dayOfWeek</code> parameter.
|
||
|
* Should be 1 for first day of period.
|
||
|
*
|
||
|
* @param dayOfWeek The {@link #DAY_OF_WEEK DAY_OF_WEEK} for the day
|
||
|
* corresponding to the <code>dayOfPeriod</code> parameter.
|
||
|
* 1-based with 1=Sunday.
|
||
|
*
|
||
|
* @return The week number (one-based), or zero if the day falls before
|
||
|
* the first week because
|
||
|
* {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek}
|
||
|
* is more than one.
|
||
|
*/
|
||
|
protected int weekNumber(int desiredDay, int dayOfPeriod, int dayOfWeek)
|
||
|
{
|
||
|
// Determine the day of the week of the first day of the period
|
||
|
// in question (either a year or a month). Zero represents the
|
||
|
// first day of the week on this calendar.
|
||
|
int periodStartDayOfWeek = (dayOfWeek - getFirstDayOfWeek() - dayOfPeriod + 1) % 7;
|
||
|
if (periodStartDayOfWeek < 0) periodStartDayOfWeek += 7;
|
||
|
|
||
|
// Compute the week number. Initially, ignore the first week, which
|
||
|
// may be fractional (or may not be). We add periodStartDayOfWeek in
|
||
|
// order to fill out the first week, if it is fractional.
|
||
|
int weekNo = (desiredDay + periodStartDayOfWeek - 1)/7;
|
||
|
|
||
|
// If the first week is long enough, then count it. If
|
||
|
// the minimal days in the first week is one, or if the period start
|
||
|
// is zero, we always increment weekNo.
|
||
|
if ((7 - periodStartDayOfWeek) >= getMinimalDaysInFirstWeek()) ++weekNo;
|
||
|
|
||
|
return weekNo;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the week number of a day, within a period. This may be the week number in
|
||
|
* a year, or the week number in a month. Usually this will be a value >= 1, but if
|
||
|
* some initial days of the period are excluded from week 1, because
|
||
|
* {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek} is > 1,
|
||
|
* then the week number will be zero for those
|
||
|
* initial days. This method requires the day of week for the given date in order to
|
||
|
* determine the result.
|
||
|
* <p>
|
||
|
* <b>Subclassing:</b>
|
||
|
* <br>
|
||
|
* This method is intended for use by subclasses in implementing their
|
||
|
* {@link #computeTime computeTime} and/or {@link #computeFields computeFields} methods.
|
||
|
* It is often useful in {@link #getActualMinimum getActualMinimum} and
|
||
|
* {@link #getActualMaximum getActualMaximum} as well.
|
||
|
* <p>
|
||
|
* @param dayOfPeriod The {@link #DAY_OF_YEAR DAY_OF_YEAR} or
|
||
|
* {@link #DAY_OF_MONTH DAY_OF_MONTH} whose week number is desired.
|
||
|
* Should be 1 for the first day of the period.
|
||
|
*
|
||
|
* @param dayOfWeek The {@link #DAY_OF_WEEK DAY_OF_WEEK} for the day
|
||
|
* corresponding to the <code>dayOfPeriod</code> parameter.
|
||
|
* 1-based with 1=Sunday.
|
||
|
*
|
||
|
* @return The week number (one-based), or zero if the day falls before
|
||
|
* the first week because
|
||
|
* {@link #getMinimalDaysInFirstWeek getMinimalDaysInFirstWeek}
|
||
|
* is more than one.
|
||
|
*/
|
||
|
protected final int weekNumber(int dayOfPeriod, int dayOfWeek)
|
||
|
{
|
||
|
return weekNumber(dayOfPeriod, dayOfPeriod, dayOfWeek);
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Constants
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
private static final int FIELD_DIFF_MAX_INT = Integer.MAX_VALUE; // 2147483647
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the difference between the given time and the time this
|
||
|
* calendar object is set to. If this calendar is set
|
||
|
* <em>before</em> the given time, the returned value will be
|
||
|
* positive. If this calendar is set <em>after</em> the given
|
||
|
* time, the returned value will be negative. The
|
||
|
* <code>field</code> parameter specifies the units of the return
|
||
|
* value. For example, if <code>fieldDifference(when,
|
||
|
* Calendar.MONTH)</code> returns 3, then this calendar is set to
|
||
|
* 3 months before <code>when</code>, and possibly some additional
|
||
|
* time less than one month.
|
||
|
*
|
||
|
* <p>As a side effect of this call, this calendar is advanced
|
||
|
* toward <code>when</code> by the given amount. That is, calling
|
||
|
* this method has the side effect of calling <code>add(field,
|
||
|
* n)</code>, where <code>n</code> is the return value.
|
||
|
*
|
||
|
* <p>Usage: To use this method, call it first with the largest
|
||
|
* field of interest, then with progressively smaller fields. For
|
||
|
* example:
|
||
|
*
|
||
|
* <pre>
|
||
|
* int y = cal.fieldDifference(when, Calendar.YEAR);
|
||
|
* int m = cal.fieldDifference(when, Calendar.MONTH);
|
||
|
* int d = cal.fieldDifference(when, Calendar.DATE);</pre>
|
||
|
*
|
||
|
* computes the difference between <code>cal</code> and
|
||
|
* <code>when</code> in years, months, and days.
|
||
|
*
|
||
|
* <p>Note: <code>fieldDifference()</code> is
|
||
|
* <em>asymmetrical</em>. That is, in the following code:
|
||
|
*
|
||
|
* <pre>
|
||
|
* cal.setTime(date1);
|
||
|
* int m1 = cal.fieldDifference(date2, Calendar.MONTH);
|
||
|
* int d1 = cal.fieldDifference(date2, Calendar.DATE);
|
||
|
* cal.setTime(date2);
|
||
|
* int m2 = cal.fieldDifference(date1, Calendar.MONTH);
|
||
|
* int d2 = cal.fieldDifference(date1, Calendar.DATE);</pre>
|
||
|
*
|
||
|
* one might expect that <code>m1 == -m2 && d1 == -d2</code>.
|
||
|
* However, this is not generally the case, because of
|
||
|
* irregularities in the underlying calendar system (e.g., the
|
||
|
* Gregorian calendar has a varying number of days per month).
|
||
|
*
|
||
|
* @param when the date to compare this calendar's time to
|
||
|
* @param field the field in which to compute the result
|
||
|
* @return the difference, either positive or negative, between
|
||
|
* this calendar's time and <code>when</code>, in terms of
|
||
|
* <code>field</code>.
|
||
|
*/
|
||
|
public int fieldDifference(Date when, int field) {
|
||
|
int min = 0;
|
||
|
long startMs = getTimeInMillis();
|
||
|
long targetMs = when.getTime();
|
||
|
// Always add from the start millis. This accommodates
|
||
|
// operations like adding years from February 29, 2000 up to
|
||
|
// February 29, 2004. If 1, 1, 1, 1 is added to the year
|
||
|
// field, the DOM gets pinned to 28 and stays there, giving an
|
||
|
// incorrect DOM difference of 1. We have to add 1, reset, 2,
|
||
|
// reset, 3, reset, 4.
|
||
|
if (startMs < targetMs) {
|
||
|
int max = 1;
|
||
|
// Find a value that is too large
|
||
|
for (;;) {
|
||
|
setTimeInMillis(startMs);
|
||
|
add(field, max);
|
||
|
long ms = getTimeInMillis();
|
||
|
if (ms == targetMs) {
|
||
|
return max;
|
||
|
} else if (ms > targetMs) {
|
||
|
break;
|
||
|
} else if (max < FIELD_DIFF_MAX_INT) {
|
||
|
min = max;
|
||
|
max <<= 1;
|
||
|
if (max < 0) {
|
||
|
max = FIELD_DIFF_MAX_INT;
|
||
|
}
|
||
|
} else {
|
||
|
// Field difference too large to fit into int
|
||
|
throw new RuntimeException();
|
||
|
}
|
||
|
}
|
||
|
// Do a binary search
|
||
|
while ((max - min) > 1) {
|
||
|
int t = min + (max - min)/2; // make sure intermediate values don't exceed FIELD_DIFF_MAX_INT
|
||
|
setTimeInMillis(startMs);
|
||
|
add(field, t);
|
||
|
long ms = getTimeInMillis();
|
||
|
if (ms == targetMs) {
|
||
|
return t;
|
||
|
} else if (ms > targetMs) {
|
||
|
max = t;
|
||
|
} else {
|
||
|
min = t;
|
||
|
}
|
||
|
}
|
||
|
} else if (startMs > targetMs) {
|
||
|
//Eclipse stated the following is "dead code"
|
||
|
/*if (false) {
|
||
|
// This works, and makes the code smaller, but costs
|
||
|
// an extra object creation and an extra couple cycles
|
||
|
// of calendar computation.
|
||
|
setTimeInMillis(targetMs);
|
||
|
min = -fieldDifference(new Date(startMs), field);
|
||
|
}*/
|
||
|
int max = -1;
|
||
|
// Find a value that is too small
|
||
|
for (;;) {
|
||
|
setTimeInMillis(startMs);
|
||
|
add(field, max);
|
||
|
long ms = getTimeInMillis();
|
||
|
if (ms == targetMs) {
|
||
|
return max;
|
||
|
} else if (ms < targetMs) {
|
||
|
break;
|
||
|
} else {
|
||
|
min = max;
|
||
|
max <<= 1;
|
||
|
if (max == 0) {
|
||
|
// Field difference too large to fit into int
|
||
|
throw new RuntimeException();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Do a binary search
|
||
|
while ((min - max) > 1) {
|
||
|
int t = min + (max - min)/2; // make sure intermediate values don't exceed FIELD_DIFF_MAX_INT
|
||
|
setTimeInMillis(startMs);
|
||
|
add(field, t);
|
||
|
long ms = getTimeInMillis();
|
||
|
if (ms == targetMs) {
|
||
|
return t;
|
||
|
} else if (ms < targetMs) {
|
||
|
max = t;
|
||
|
} else {
|
||
|
min = t;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Set calendar to end point
|
||
|
setTimeInMillis(startMs);
|
||
|
add(field, min);
|
||
|
return min;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the time zone with the given time zone value.
|
||
|
* @param value the given time zone.
|
||
|
*/
|
||
|
public void setTimeZone(TimeZone value)
|
||
|
{
|
||
|
zone = value;
|
||
|
/* Recompute the fields from the time using the new zone. This also
|
||
|
* works if isTimeSet is false (after a call to set()). In that case
|
||
|
* the time will be computed from the fields using the new zone, then
|
||
|
* the fields will get recomputed from that. Consider the sequence of
|
||
|
* calls: cal.setTimeZone(EST); cal.set(HOUR, 1); cal.setTimeZone(PST).
|
||
|
* Is cal set to 1 o'clock EST or 1 o'clock PST? Answer: PST. More
|
||
|
* generally, a call to setTimeZone() affects calls to set() BEFORE AND
|
||
|
* AFTER it up to the next call to complete().
|
||
|
*/
|
||
|
areFieldsSet = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the time zone.
|
||
|
* @return the time zone object associated with this calendar.
|
||
|
*/
|
||
|
public TimeZone getTimeZone()
|
||
|
{
|
||
|
return zone;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specify whether or not date/time interpretation is to be lenient. With
|
||
|
* lenient interpretation, a date such as "February 942, 1996" will be
|
||
|
* treated as being equivalent to the 941st day after February 1, 1996.
|
||
|
* With strict interpretation, such dates will cause an exception to be
|
||
|
* thrown.
|
||
|
*
|
||
|
* @see DateFormat#setLenient
|
||
|
*/
|
||
|
public void setLenient(boolean lenient)
|
||
|
{
|
||
|
this.lenient = lenient;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Tell whether date/time interpretation is to be lenient.
|
||
|
*/
|
||
|
public boolean isLenient()
|
||
|
{
|
||
|
return lenient;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong>Sets the behavior for handling wall time repeating multiple times
|
||
|
* at negative time zone offset transitions. For example, 1:30 AM on
|
||
|
* November 6, 2011 in US Eastern time (America/New_York) occurs twice;
|
||
|
* 1:30 AM EDT, then 1:30 AM EST one hour later. When <code>WALLTIME_FIRST</code>
|
||
|
* is used, the wall time 1:30AM in this example will be interpreted as 1:30 AM EDT
|
||
|
* (first occurrence). When <code>WALLTIME_LAST</code> is used, it will be
|
||
|
* interpreted as 1:30 AM EST (last occurrence). The default value is
|
||
|
* <code>WALLTIME_LAST</code>.
|
||
|
*
|
||
|
* @param option the behavior for handling repeating wall time, either
|
||
|
* <code>WALLTIME_FIRST</code> or <code>WALLTIME_LAST</code>.
|
||
|
* @throws IllegalArgumentException when <code>option</code> is neither
|
||
|
* <code>WALLTIME_FIRST</code> nor <code>WALLTIME_LAST</code>.
|
||
|
*
|
||
|
* @see #getRepeatedWallTimeOption()
|
||
|
* @see #WALLTIME_FIRST
|
||
|
* @see #WALLTIME_LAST
|
||
|
*/
|
||
|
public void setRepeatedWallTimeOption(int option) {
|
||
|
if (option != WALLTIME_LAST && option != WALLTIME_FIRST) {
|
||
|
throw new IllegalArgumentException("Illegal repeated wall time option - " + option);
|
||
|
}
|
||
|
repeatedWallTime = option;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong>Gets the behavior for handling wall time repeating multiple times
|
||
|
* at negative time zone offset transitions.
|
||
|
*
|
||
|
* @return the behavior for handling repeating wall time, either
|
||
|
* <code>WALLTIME_FIRST</code> or <code>WALLTIME_LAST</code>.
|
||
|
*
|
||
|
* @see #setRepeatedWallTimeOption(int)
|
||
|
* @see #WALLTIME_FIRST
|
||
|
* @see #WALLTIME_LAST
|
||
|
*/
|
||
|
public int getRepeatedWallTimeOption() {
|
||
|
return repeatedWallTime;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong>Sets the behavior for handling skipped wall time at positive time zone offset
|
||
|
* transitions. For example, 2:30 AM on March 13, 2011 in US Eastern time (America/New_York)
|
||
|
* does not exist because the wall time jump from 1:59 AM EST to 3:00 AM EDT. When
|
||
|
* <code>WALLTIME_FIRST</code> is used, 2:30 AM is interpreted as 30 minutes before 3:00 AM
|
||
|
* EDT, therefore, it will be resolved as 1:30 AM EST. When <code>WALLTIME_LAST</code>
|
||
|
* is used, 2:30 AM is interpreted as 31 minutes after 1:59 AM EST, therefore, it will be
|
||
|
* resolved as 3:30 AM EDT. When <code>WALLTIME_NEXT_VALID</code> is used, 2:30 AM will
|
||
|
* be resolved as next valid wall time, that is 3:00 AM EDT. The default value is
|
||
|
* <code>WALLTIME_LAST</code>.
|
||
|
* <p>
|
||
|
* <b>Note:</b>This option is effective only when this calendar is {@link #isLenient() lenient}.
|
||
|
* When the calendar is strict, such non-existing wall time will cause an exception.
|
||
|
*
|
||
|
* @param option the behavior for handling skipped wall time at positive time zone
|
||
|
* offset transitions, one of <code>WALLTIME_FIRST</code>, <code>WALLTIME_LAST</code> and
|
||
|
* <code>WALLTIME_NEXT_VALID</code>.
|
||
|
* @throws IllegalArgumentException when <code>option</code> is not any of
|
||
|
* <code>WALLTIME_FIRST</code>, <code>WALLTIME_LAST</code> and <code>WALLTIME_NEXT_VALID</code>.
|
||
|
*
|
||
|
* @see #getSkippedWallTimeOption()
|
||
|
* @see #WALLTIME_FIRST
|
||
|
* @see #WALLTIME_LAST
|
||
|
* @see #WALLTIME_NEXT_VALID
|
||
|
*/
|
||
|
public void setSkippedWallTimeOption(int option) {
|
||
|
if (option != WALLTIME_LAST && option != WALLTIME_FIRST && option != WALLTIME_NEXT_VALID) {
|
||
|
throw new IllegalArgumentException("Illegal skipped wall time option - " + option);
|
||
|
}
|
||
|
skippedWallTime = option;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong>Gets the behavior for handling skipped wall time at positive time zone offset
|
||
|
* transitions.
|
||
|
*
|
||
|
* @return the behavior for handling skipped wall time, one of
|
||
|
* <code>WALLTIME_FIRST</code>, <code>WALLTIME_LAST</code> and <code>WALLTIME_NEXT_VALID</code>.
|
||
|
*
|
||
|
* @see #setSkippedWallTimeOption(int)
|
||
|
* @see #WALLTIME_FIRST
|
||
|
* @see #WALLTIME_LAST
|
||
|
* @see #WALLTIME_NEXT_VALID
|
||
|
*/
|
||
|
public int getSkippedWallTimeOption() {
|
||
|
return skippedWallTime;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets what the first day of the week is,
|
||
|
* where 1 = {@link #SUNDAY} and 7 = {@link #SATURDAY}.
|
||
|
* @param value the given first day of the week, where 1 = {@link #SUNDAY} and 7 = {@link #SATURDAY}.
|
||
|
*/
|
||
|
public void setFirstDayOfWeek(int value)
|
||
|
{
|
||
|
if (firstDayOfWeek != value) {
|
||
|
if (value < SUNDAY || value > SATURDAY) {
|
||
|
throw new IllegalArgumentException("Invalid day of week");
|
||
|
}
|
||
|
firstDayOfWeek = value;
|
||
|
areFieldsSet = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns what the first day of the week is,
|
||
|
* where 1 = {@link #SUNDAY} and 7 = {@link #SATURDAY}.
|
||
|
* e.g., Sunday in US, Monday in France
|
||
|
* @return the first day of the week, where 1 = {@link #SUNDAY} and 7 = {@link #SATURDAY}.
|
||
|
*/
|
||
|
public int getFirstDayOfWeek()
|
||
|
{
|
||
|
return firstDayOfWeek;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets what the minimal days required in the first week of the year are.
|
||
|
* For example, if the first week is defined as one that contains the first
|
||
|
* day of the first month of a year, call the method with value 1. If it
|
||
|
* must be a full week, use value 7.
|
||
|
* @param value the given minimal days required in the first week
|
||
|
* of the year.
|
||
|
*/
|
||
|
public void setMinimalDaysInFirstWeek(int value)
|
||
|
{
|
||
|
// Values less than 1 have the same effect as 1; values greater
|
||
|
// than 7 have the same effect as 7. However, we normalize values
|
||
|
// so operator== and so forth work.
|
||
|
if (value < 1) {
|
||
|
value = 1;
|
||
|
} else if (value > 7) {
|
||
|
value = 7;
|
||
|
}
|
||
|
if (minimalDaysInFirstWeek != value) {
|
||
|
minimalDaysInFirstWeek = value;
|
||
|
areFieldsSet = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns what the minimal days required in the first week of the year are.
|
||
|
* That is, if the first week is defined as one that contains the first day
|
||
|
* of the first month of a year, getMinimalDaysInFirstWeek returns 1. If
|
||
|
* the minimal days required must be a full week, getMinimalDaysInFirstWeek
|
||
|
* returns 7.
|
||
|
* @return the minimal days required in the first week of the year.
|
||
|
*/
|
||
|
public int getMinimalDaysInFirstWeek()
|
||
|
{
|
||
|
return minimalDaysInFirstWeek;
|
||
|
}
|
||
|
|
||
|
private static final int LIMITS[][] = {
|
||
|
// Minimum Greatest min Least max Greatest max
|
||
|
{/* */}, // ERA
|
||
|
{/* */}, // YEAR
|
||
|
{/* */}, // MONTH
|
||
|
{/* */}, // WEEK_OF_YEAR
|
||
|
{/* */}, // WEEK_OF_MONTH
|
||
|
{/* */}, // DAY_OF_MONTH
|
||
|
{/* */}, // DAY_OF_YEAR
|
||
|
{ 1, 1, 7, 7 }, // DAY_OF_WEEK
|
||
|
{/* */}, // DAY_OF_WEEK_IN_MONTH
|
||
|
{ 0, 0, 1, 1 }, // AM_PM
|
||
|
{ 0, 0, 11, 11 }, // HOUR
|
||
|
{ 0, 0, 23, 23 }, // HOUR_OF_DAY
|
||
|
{ 0, 0, 59, 59 }, // MINUTE
|
||
|
{ 0, 0, 59, 59 }, // SECOND
|
||
|
{ 0, 0, 999, 999 }, // MILLISECOND
|
||
|
{-24*ONE_HOUR, -16*ONE_HOUR, 12*ONE_HOUR, 30*ONE_HOUR }, // ZONE_OFFSET
|
||
|
{ 0, 0, 2*ONE_HOUR, 2*ONE_HOUR }, // DST_OFFSET
|
||
|
{/* */}, // YEAR_WOY
|
||
|
{ 1, 1, 7, 7 }, // DOW_LOCAL
|
||
|
{/* */}, // EXTENDED_YEAR
|
||
|
{ -0x7F000000, -0x7F000000, 0x7F000000, 0x7F000000 }, // JULIAN_DAY
|
||
|
{ 0, 0, 24*ONE_HOUR-1, 24*ONE_HOUR-1 }, // MILLISECONDS_IN_DAY
|
||
|
{ 0, 0, 1, 1 }, // IS_LEAP_MONTH
|
||
|
{ 0, 0, 12, 12 }, // ORDINAL_MONTH
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Subclass API for defining limits of different types.
|
||
|
* Subclasses must implement this method to return limits for the
|
||
|
* following fields:
|
||
|
*
|
||
|
* <pre>ERA
|
||
|
* YEAR
|
||
|
* MONTH
|
||
|
* WEEK_OF_YEAR
|
||
|
* WEEK_OF_MONTH
|
||
|
* DAY_OF_MONTH
|
||
|
* DAY_OF_YEAR
|
||
|
* DAY_OF_WEEK_IN_MONTH
|
||
|
* YEAR_WOY
|
||
|
* EXTENDED_YEAR</pre>
|
||
|
*
|
||
|
* @param field one of the above field numbers
|
||
|
* @param limitType one of <code>MINIMUM</code>, <code>GREATEST_MINIMUM</code>,
|
||
|
* <code>LEAST_MAXIMUM</code>, or <code>MAXIMUM</code>
|
||
|
*/
|
||
|
abstract protected int handleGetLimit(int field, int limitType);
|
||
|
|
||
|
/**
|
||
|
* Returns a limit for a field.
|
||
|
* @param field the field, from 0..<code>getFieldCount()-1</code>
|
||
|
* @param limitType the type specifier for the limit
|
||
|
* @see #MINIMUM
|
||
|
* @see #GREATEST_MINIMUM
|
||
|
* @see #LEAST_MAXIMUM
|
||
|
* @see #MAXIMUM
|
||
|
*/
|
||
|
protected int getLimit(int field, int limitType) {
|
||
|
switch (field) {
|
||
|
case DAY_OF_WEEK:
|
||
|
case AM_PM:
|
||
|
case HOUR:
|
||
|
case HOUR_OF_DAY:
|
||
|
case MINUTE:
|
||
|
case SECOND:
|
||
|
case MILLISECOND:
|
||
|
case ZONE_OFFSET:
|
||
|
case DST_OFFSET:
|
||
|
case DOW_LOCAL:
|
||
|
case JULIAN_DAY:
|
||
|
case MILLISECONDS_IN_DAY:
|
||
|
case IS_LEAP_MONTH:
|
||
|
return LIMITS[field][limitType];
|
||
|
|
||
|
case WEEK_OF_MONTH:
|
||
|
{
|
||
|
int limit;
|
||
|
if (limitType == MINIMUM) {
|
||
|
limit = getMinimalDaysInFirstWeek() == 1 ? 1 : 0;
|
||
|
} else if (limitType == GREATEST_MINIMUM){
|
||
|
limit = 1;
|
||
|
} else {
|
||
|
int minDaysInFirst = getMinimalDaysInFirstWeek();
|
||
|
int daysInMonth = handleGetLimit(DAY_OF_MONTH, limitType);
|
||
|
if (limitType == LEAST_MAXIMUM) {
|
||
|
limit = (daysInMonth + (7 - minDaysInFirst)) / 7;
|
||
|
} else { // limitType == MAXIMUM
|
||
|
limit = (daysInMonth + 6 + (7 - minDaysInFirst)) / 7;
|
||
|
}
|
||
|
}
|
||
|
return limit;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
return handleGetLimit(field, limitType);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Limit type for <code>getLimit()</code> and <code>handleGetLimit()</code>
|
||
|
* indicating the minimum value that a field can take (least minimum).
|
||
|
* @see #getLimit
|
||
|
* @see #handleGetLimit
|
||
|
*/
|
||
|
protected static final int MINIMUM = 0;
|
||
|
|
||
|
/**
|
||
|
* Limit type for <code>getLimit()</code> and <code>handleGetLimit()</code>
|
||
|
* indicating the greatest minimum value that a field can take.
|
||
|
* @see #getLimit
|
||
|
* @see #handleGetLimit
|
||
|
*/
|
||
|
protected static final int GREATEST_MINIMUM = 1;
|
||
|
|
||
|
/**
|
||
|
* Limit type for <code>getLimit()</code> and <code>handleGetLimit()</code>
|
||
|
* indicating the least maximum value that a field can take.
|
||
|
* @see #getLimit
|
||
|
* @see #handleGetLimit
|
||
|
*/
|
||
|
protected static final int LEAST_MAXIMUM = 2;
|
||
|
|
||
|
/**
|
||
|
* Limit type for <code>getLimit()</code> and <code>handleGetLimit()</code>
|
||
|
* indicating the maximum value that a field can take (greatest maximum).
|
||
|
* @see #getLimit
|
||
|
* @see #handleGetLimit
|
||
|
*/
|
||
|
protected static final int MAXIMUM = 3;
|
||
|
|
||
|
/**
|
||
|
* Returns the minimum value for the given time field.
|
||
|
* e.g., for Gregorian DAY_OF_MONTH, 1.
|
||
|
* @param field the given time field.
|
||
|
* @return the minimum value for the given time field.
|
||
|
*/
|
||
|
public final int getMinimum(int field) {
|
||
|
return getLimit(field, MINIMUM);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the maximum value for the given time field.
|
||
|
* e.g. for Gregorian DAY_OF_MONTH, 31.
|
||
|
* @param field the given time field.
|
||
|
* @return the maximum value for the given time field.
|
||
|
*/
|
||
|
public final int getMaximum(int field) {
|
||
|
return getLimit(field, MAXIMUM);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the highest minimum value for the given field if varies.
|
||
|
* Otherwise same as getMinimum(). For Gregorian, no difference.
|
||
|
* @param field the given time field.
|
||
|
* @return the highest minimum value for the given time field.
|
||
|
*/
|
||
|
public final int getGreatestMinimum(int field) {
|
||
|
return getLimit(field, GREATEST_MINIMUM);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the lowest maximum value for the given field if varies.
|
||
|
* Otherwise same as getMaximum(). e.g., for Gregorian DAY_OF_MONTH, 28.
|
||
|
* @param field the given time field.
|
||
|
* @return the lowest maximum value for the given time field.
|
||
|
*/
|
||
|
public final int getLeastMaximum(int field) {
|
||
|
return getLimit(field, LEAST_MAXIMUM);
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Weekend support -- determining which days of the week are the weekend
|
||
|
// in a given locale
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns whether the given day of the week is a weekday, a
|
||
|
* weekend day, or a day that transitions from one to the other, for the
|
||
|
* locale and calendar system associated with this Calendar (the locale's
|
||
|
* region is often the most determinant factor). If a transition occurs at
|
||
|
* midnight, then the days before and after the transition will have the
|
||
|
* type WEEKDAY or WEEKEND. If a transition occurs at a time
|
||
|
* other than midnight, then the day of the transition will have
|
||
|
* the type WEEKEND_ONSET or WEEKEND_CEASE. In this case, the
|
||
|
* method getWeekendTransition() will return the point of
|
||
|
* transition.
|
||
|
* @param dayOfWeek either SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
|
||
|
* THURSDAY, FRIDAY, or SATURDAY
|
||
|
* @return either WEEKDAY, WEEKEND, WEEKEND_ONSET, or
|
||
|
* WEEKEND_CEASE
|
||
|
* @exception IllegalArgumentException if dayOfWeek is not
|
||
|
* between SUNDAY and SATURDAY, inclusive
|
||
|
* @see #WEEKDAY
|
||
|
* @see #WEEKEND
|
||
|
* @see #WEEKEND_ONSET
|
||
|
* @see #WEEKEND_CEASE
|
||
|
* @see #getWeekendTransition
|
||
|
* @see #isWeekend(Date)
|
||
|
* @see #isWeekend()
|
||
|
* @deprecated ICU 54 use {@link #getWeekDataForRegion(String)}, {@link #getWeekData()}, {@link #setWeekData(WeekData)}
|
||
|
* @hide original deprecated declaration
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public int getDayOfWeekType(int dayOfWeek) {
|
||
|
if (dayOfWeek < SUNDAY || dayOfWeek > SATURDAY) {
|
||
|
throw new IllegalArgumentException("Invalid day of week");
|
||
|
}
|
||
|
if (weekendOnset == weekendCease) {
|
||
|
if (dayOfWeek != weekendOnset)
|
||
|
return WEEKDAY;
|
||
|
return (weekendOnsetMillis == 0) ? WEEKEND : WEEKEND_ONSET;
|
||
|
}
|
||
|
if (weekendOnset < weekendCease) {
|
||
|
if (dayOfWeek < weekendOnset || dayOfWeek > weekendCease) {
|
||
|
return WEEKDAY;
|
||
|
}
|
||
|
} else {
|
||
|
if (dayOfWeek > weekendCease && dayOfWeek < weekendOnset) {
|
||
|
return WEEKDAY;
|
||
|
}
|
||
|
}
|
||
|
if (dayOfWeek == weekendOnset) {
|
||
|
return (weekendOnsetMillis == 0) ? WEEKEND : WEEKEND_ONSET;
|
||
|
}
|
||
|
if (dayOfWeek == weekendCease) {
|
||
|
return (weekendCeaseMillis >= 86400000) ? WEEKEND : WEEKEND_CEASE;
|
||
|
}
|
||
|
return WEEKEND;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the time during the day at which the weekend begins or end in this
|
||
|
* calendar system. If getDayOfWeekType(dayOfWeek) == WEEKEND_ONSET return the time
|
||
|
* at which the weekend begins. If getDayOfWeekType(dayOfWeek) == WEEKEND_CEASE
|
||
|
* return the time at which the weekend ends. If getDayOfWeekType(dayOfWeek) has some
|
||
|
* other value, then throw an exception.
|
||
|
* @param dayOfWeek either SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
|
||
|
* THURSDAY, FRIDAY, or SATURDAY
|
||
|
* @return the milliseconds after midnight at which the
|
||
|
* weekend begins or ends
|
||
|
* @exception IllegalArgumentException if dayOfWeek is not
|
||
|
* WEEKEND_ONSET or WEEKEND_CEASE
|
||
|
* @see #getDayOfWeekType
|
||
|
* @see #isWeekend(Date)
|
||
|
* @see #isWeekend()
|
||
|
* @deprecated ICU 54 use {@link #getWeekDataForRegion(String)}, {@link #getWeekData()}, {@link #setWeekData(WeekData)}
|
||
|
* @hide original deprecated declaration
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public int getWeekendTransition(int dayOfWeek) {
|
||
|
if (dayOfWeek == weekendOnset) {
|
||
|
return weekendOnsetMillis;
|
||
|
} else if (dayOfWeek == weekendCease) {
|
||
|
return weekendCeaseMillis;
|
||
|
}
|
||
|
throw new IllegalArgumentException("Not weekend transition day");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns true if the given date and time is in the weekend in this calendar
|
||
|
* system. Equivalent to calling setTime() followed by isWeekend(). Note: This
|
||
|
* method changes the time this calendar is set to.
|
||
|
* @param date the date and time
|
||
|
* @return true if the given date and time is part of the
|
||
|
* weekend
|
||
|
* @see #isWeekend()
|
||
|
*/
|
||
|
public boolean isWeekend(Date date) {
|
||
|
setTime(date);
|
||
|
return isWeekend();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns true if this Calendar's current date and time is in the weekend in
|
||
|
* this calendar system.
|
||
|
* @return true if the given date and time is part of the
|
||
|
* weekend
|
||
|
* @see #isWeekend(Date)
|
||
|
*/
|
||
|
public boolean isWeekend() {
|
||
|
int dow = get(DAY_OF_WEEK);
|
||
|
int dowt = getDayOfWeekType(dow);
|
||
|
switch (dowt) {
|
||
|
case WEEKDAY:
|
||
|
return false;
|
||
|
case WEEKEND:
|
||
|
return true;
|
||
|
default: // That is, WEEKEND_ONSET or WEEKEND_CEASE
|
||
|
// Use internalGet() because the above call to get() populated
|
||
|
// all fields.
|
||
|
// [Note: There should be a better way to get millis in day.
|
||
|
// For ICU4J, submit request for a MILLIS_IN_DAY field
|
||
|
// and a DAY_NUMBER field (could be Julian day #). - aliu]
|
||
|
int millisInDay = internalGet(MILLISECOND) + 1000 * (internalGet(SECOND) +
|
||
|
60 * (internalGet(MINUTE) + 60 * internalGet(HOUR_OF_DAY)));
|
||
|
int transition = getWeekendTransition(dow);
|
||
|
return (dowt == WEEKEND_ONSET)
|
||
|
? (millisInDay >= transition)
|
||
|
: (millisInDay < transition);
|
||
|
}
|
||
|
// (We can never reach this point.)
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// End of weekend support
|
||
|
//-------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Overrides Cloneable
|
||
|
*/
|
||
|
@Override
|
||
|
public Object clone()
|
||
|
{
|
||
|
try {
|
||
|
Calendar other = (Calendar) super.clone();
|
||
|
|
||
|
other.fields = new int[fields.length];
|
||
|
other.stamp = new int[fields.length];
|
||
|
System.arraycopy(this.fields, 0, other.fields, 0, fields.length);
|
||
|
System.arraycopy(this.stamp, 0, other.stamp, 0, fields.length);
|
||
|
|
||
|
other.zone = (TimeZone) zone.clone();
|
||
|
return other;
|
||
|
}
|
||
|
catch (CloneNotSupportedException e) {
|
||
|
// this shouldn't happen, since we are Cloneable
|
||
|
throw new ICUCloneNotSupportedException(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a string representation of this calendar. This method
|
||
|
* is intended to be used only for debugging purposes, and the
|
||
|
* format of the returned string may vary between implementations.
|
||
|
* The returned string may be empty but may not be <code>null</code>.
|
||
|
*
|
||
|
* @return a string representation of this calendar.
|
||
|
*/
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
StringBuilder buffer = new StringBuilder();
|
||
|
buffer.append(getClass().getName());
|
||
|
buffer.append("[time=");
|
||
|
buffer.append(isTimeSet ? String.valueOf(time) : "?");
|
||
|
buffer.append(",areFieldsSet=");
|
||
|
buffer.append(areFieldsSet);
|
||
|
buffer.append(",areAllFieldsSet=");
|
||
|
buffer.append(areAllFieldsSet);
|
||
|
buffer.append(",lenient=");
|
||
|
buffer.append(lenient);
|
||
|
buffer.append(",zone=");
|
||
|
buffer.append(zone);
|
||
|
buffer.append(",firstDayOfWeek=");
|
||
|
buffer.append(firstDayOfWeek);
|
||
|
buffer.append(",minimalDaysInFirstWeek=");
|
||
|
buffer.append(minimalDaysInFirstWeek);
|
||
|
buffer.append(",repeatedWallTime=");
|
||
|
buffer.append(repeatedWallTime);
|
||
|
buffer.append(",skippedWallTime=");
|
||
|
buffer.append(skippedWallTime);
|
||
|
for (int i=0; i<fields.length; ++i) {
|
||
|
buffer.append(',').append(fieldName(i)).append('=');
|
||
|
buffer.append(isSet(i) ? String.valueOf(fields[i]) : "?");
|
||
|
}
|
||
|
buffer.append(']');
|
||
|
return buffer.toString();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Simple, immutable struct-like class for access to the CLDR week data.
|
||
|
*/
|
||
|
public static final class WeekData {
|
||
|
/**
|
||
|
* the first day of the week, where 1 = {@link #SUNDAY} and 7 = {@link #SATURDAY}
|
||
|
*/
|
||
|
public final int firstDayOfWeek;
|
||
|
/**
|
||
|
* the minimal number of days in the first week
|
||
|
*/
|
||
|
public final int minimalDaysInFirstWeek;
|
||
|
/**
|
||
|
* the onset day, where 1 = {@link #SUNDAY} and 7 = {@link #SATURDAY}
|
||
|
*/
|
||
|
public final int weekendOnset;
|
||
|
/**
|
||
|
* the onset time in millis during the onset day
|
||
|
*/
|
||
|
public final int weekendOnsetMillis;
|
||
|
/**
|
||
|
* the cease day, where 1 = {@link #SUNDAY} and 7 = {@link #SATURDAY}
|
||
|
*/
|
||
|
public final int weekendCease;
|
||
|
/**
|
||
|
* the cease time in millis during the cease day. Exclusive, so the max is 24:00:00.000.
|
||
|
* Note that this will format as 00:00 the next day.
|
||
|
*/
|
||
|
public final int weekendCeaseMillis;
|
||
|
|
||
|
/**
|
||
|
* Constructor
|
||
|
*
|
||
|
* @param fdow the first day of the week, where 1 = {@link #SUNDAY} and 7 = {@link #SATURDAY}
|
||
|
* @param mdifw the minimal number of days in the first week
|
||
|
* @param weekendOnset the onset day, where 1 = Sunday and 7 = Saturday
|
||
|
* @param weekendOnsetMillis the onset time in millis during the onset day
|
||
|
* @param weekendCease the cease day, where 1 = Sunday and 7 = Saturday
|
||
|
* @param weekendCeaseMillis the cease time in millis during the cease day.
|
||
|
*/
|
||
|
public WeekData(int fdow, int mdifw,
|
||
|
int weekendOnset, int weekendOnsetMillis,
|
||
|
int weekendCease, int weekendCeaseMillis) {
|
||
|
this.firstDayOfWeek = fdow;
|
||
|
this.minimalDaysInFirstWeek = mdifw;
|
||
|
this.weekendOnset = weekendOnset;
|
||
|
this.weekendOnsetMillis = weekendOnsetMillis;
|
||
|
this.weekendCease = weekendCease;
|
||
|
this.weekendCeaseMillis = weekendCeaseMillis;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* {@inheritDoc}
|
||
|
*/
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
return ((((firstDayOfWeek * 37 + minimalDaysInFirstWeek) * 37 + weekendOnset) * 37
|
||
|
+ weekendOnsetMillis) * 37 + weekendCease) * 37 + weekendCeaseMillis;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* {@inheritDoc}
|
||
|
*/
|
||
|
@Override
|
||
|
public boolean equals(Object other) {
|
||
|
if (this == other) {
|
||
|
return true;
|
||
|
}
|
||
|
if (!(other instanceof WeekData)) {
|
||
|
return false;
|
||
|
}
|
||
|
WeekData that = (WeekData) other;
|
||
|
return firstDayOfWeek == that.firstDayOfWeek
|
||
|
&& minimalDaysInFirstWeek == that.minimalDaysInFirstWeek
|
||
|
&& weekendOnset == that.weekendOnset
|
||
|
&& weekendOnsetMillis == that.weekendOnsetMillis
|
||
|
&& weekendCease == that.weekendCease
|
||
|
&& weekendCeaseMillis == that.weekendCeaseMillis;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* {@inheritDoc}
|
||
|
*/
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
return "{" + firstDayOfWeek
|
||
|
+ ", " + minimalDaysInFirstWeek
|
||
|
+ ", " + weekendOnset
|
||
|
+ ", " + weekendOnsetMillis
|
||
|
+ ", " + weekendCease
|
||
|
+ ", " + weekendCeaseMillis
|
||
|
+ "}";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Return simple, immutable struct-like class for access to the CLDR week data.
|
||
|
* @param region The input region. The results are undefined if the region code is not valid.
|
||
|
* @return the WeekData for the input region. It is never null.
|
||
|
*/
|
||
|
public static WeekData getWeekDataForRegion(String region) {
|
||
|
return WEEK_DATA_CACHE.createInstance(region, region);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Return simple, immutable struct-like class for access to the week data in this calendar.
|
||
|
* @return the WeekData for this calendar.
|
||
|
*/
|
||
|
public WeekData getWeekData() {
|
||
|
return new WeekData(firstDayOfWeek, minimalDaysInFirstWeek, weekendOnset, weekendOnsetMillis, weekendCease, weekendCeaseMillis);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Set data in this calendar based on the WeekData input.
|
||
|
* @param wdata The week data to use
|
||
|
* @return this, for chaining
|
||
|
*/
|
||
|
public Calendar setWeekData(WeekData wdata) {
|
||
|
setFirstDayOfWeek(wdata.firstDayOfWeek);
|
||
|
setMinimalDaysInFirstWeek(wdata.minimalDaysInFirstWeek);
|
||
|
|
||
|
weekendOnset = wdata.weekendOnset;
|
||
|
weekendOnsetMillis = wdata.weekendOnsetMillis;
|
||
|
weekendCease = wdata.weekendCease;
|
||
|
weekendCeaseMillis = wdata.weekendCeaseMillis;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
private static WeekData getWeekDataForRegionInternal(String region) {
|
||
|
if (region == null) {
|
||
|
region = "001";
|
||
|
}
|
||
|
|
||
|
UResourceBundle rb = UResourceBundle.getBundleInstance(
|
||
|
ICUData.ICU_BASE_NAME,
|
||
|
"supplementalData",
|
||
|
ICUResourceBundle.ICU_DATA_CLASS_LOADER);
|
||
|
UResourceBundle weekDataInfo = rb.get("weekData");
|
||
|
UResourceBundle weekDataBundle = null;
|
||
|
|
||
|
try {
|
||
|
weekDataBundle = weekDataInfo.get(region);
|
||
|
} catch (MissingResourceException mre) {
|
||
|
if (!region.equals("001")) {
|
||
|
// use "001" as fallback
|
||
|
weekDataBundle = weekDataInfo.get("001");
|
||
|
} else {
|
||
|
throw mre;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int[] wdi = weekDataBundle.getIntVector();
|
||
|
return new WeekData(wdi[0],wdi[1],wdi[2],wdi[3],wdi[4],wdi[5]);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Cache to hold week data by region
|
||
|
*/
|
||
|
private static class WeekDataCache extends SoftCache<String, WeekData, String> {
|
||
|
|
||
|
/* (non-Javadoc)
|
||
|
* @see android.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
|
||
|
*/
|
||
|
@Override
|
||
|
protected WeekData createInstance(String key, String data) {
|
||
|
return getWeekDataForRegionInternal(key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static final WeekDataCache WEEK_DATA_CACHE = new WeekDataCache();
|
||
|
|
||
|
/*
|
||
|
* Set this calendar to contain week and week data for the given region.
|
||
|
*/
|
||
|
private void setWeekData(String region) {
|
||
|
if (region == null) {
|
||
|
region = "001";
|
||
|
}
|
||
|
WeekData wdata = WEEK_DATA_CACHE.getInstance(region, region);
|
||
|
setWeekData(wdata);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Recompute the time and update the status fields isTimeSet
|
||
|
* and areFieldsSet. Callers should check isTimeSet and only
|
||
|
* call this method if isTimeSet is false.
|
||
|
*/
|
||
|
private void updateTime() {
|
||
|
computeTime();
|
||
|
// If we are lenient, we need to recompute the fields to normalize
|
||
|
// the values. Also, if we haven't set all the fields yet (i.e.,
|
||
|
// in a newly-created object), we need to fill in the fields. [LIU]
|
||
|
if (isLenient() || !areAllFieldsSet) areFieldsSet = false;
|
||
|
isTimeSet = true;
|
||
|
areFieldsVirtuallySet = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Save the state of this object to a stream (i.e., serialize it).
|
||
|
*/
|
||
|
private void writeObject(ObjectOutputStream stream)
|
||
|
throws IOException
|
||
|
{
|
||
|
// Try to compute the time correctly, for the future (stream
|
||
|
// version 2) in which we don't write out fields[] or isSet[].
|
||
|
if (!isTimeSet) {
|
||
|
try {
|
||
|
updateTime();
|
||
|
}
|
||
|
catch (IllegalArgumentException e) {}
|
||
|
}
|
||
|
|
||
|
// Write out the 1.1 FCS object.
|
||
|
stream.defaultWriteObject();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reconstitute this object from a stream (i.e., deserialize it).
|
||
|
*/
|
||
|
private void readObject(ObjectInputStream stream)
|
||
|
throws IOException, ClassNotFoundException {
|
||
|
|
||
|
stream.defaultReadObject();
|
||
|
|
||
|
initInternal();
|
||
|
|
||
|
isTimeSet = true;
|
||
|
areFieldsSet = areAllFieldsSet = false;
|
||
|
areFieldsVirtuallySet = true; // cause fields to be recalculated if requested.
|
||
|
nextStamp = MINIMUM_USER_STAMP;
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------
|
||
|
// Time -> Fields
|
||
|
//----------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Converts the current millisecond time value <code>time</code> to
|
||
|
* field values in <code>fields[]</code>. This synchronizes the time
|
||
|
* field values with a new time that is set for the calendar. The time
|
||
|
* is <em>not</em> recomputed first; to recompute the time, then the
|
||
|
* fields, call the <code>complete</code> method.
|
||
|
* @see #complete
|
||
|
*/
|
||
|
protected void computeFields() {
|
||
|
int offsets[] = new int[2];
|
||
|
getTimeZone().getOffset(time, false, offsets);
|
||
|
long localMillis = time + offsets[0] + offsets[1];
|
||
|
|
||
|
// Mark fields as set. Do this before calling handleComputeFields().
|
||
|
int mask = internalSetMask;
|
||
|
for (int i=0; i<fields.length; ++i) {
|
||
|
if ((mask & 1) == 0) {
|
||
|
stamp[i] = INTERNALLY_SET;
|
||
|
} else {
|
||
|
stamp[i] = UNSET;
|
||
|
}
|
||
|
mask >>= 1;
|
||
|
}
|
||
|
|
||
|
// We used to check for and correct extreme millis values (near
|
||
|
// Long.MIN_VALUE or Long.MAX_VALUE) here. Such values would cause
|
||
|
// overflows from positive to negative (or vice versa) and had to
|
||
|
// be manually tweaked. We no longer need to do this because we
|
||
|
// have limited the range of supported dates to those that have a
|
||
|
// Julian day that fits into an int. This allows us to implement a
|
||
|
// JULIAN_DAY field and also removes some inelegant code. - Liu
|
||
|
// 11/6/00
|
||
|
|
||
|
long days = floorDivide(localMillis, ONE_DAY);
|
||
|
|
||
|
fields[JULIAN_DAY] = (int) days + EPOCH_JULIAN_DAY;
|
||
|
|
||
|
computeGregorianAndDOWFields(fields[JULIAN_DAY]);
|
||
|
|
||
|
// Call framework method to have subclass compute its fields.
|
||
|
// These must include, at a minimum, MONTH, DAY_OF_MONTH,
|
||
|
// EXTENDED_YEAR, YEAR, DAY_OF_YEAR. This method will call internalSet(),
|
||
|
// which will update stamp[].
|
||
|
handleComputeFields(fields[JULIAN_DAY]);
|
||
|
|
||
|
// Compute week-related fields, based on the subclass-computed
|
||
|
// fields computed by handleComputeFields().
|
||
|
computeWeekFields();
|
||
|
|
||
|
// Compute time-related fields. These are independent of the date and
|
||
|
// of the subclass algorithm. They depend only on the local zone
|
||
|
// wall milliseconds in day.
|
||
|
int millisInDay = (int) (localMillis - (days * ONE_DAY));
|
||
|
fields[MILLISECONDS_IN_DAY] = millisInDay;
|
||
|
fields[MILLISECOND] = millisInDay % 1000;
|
||
|
millisInDay /= 1000;
|
||
|
fields[SECOND] = millisInDay % 60;
|
||
|
millisInDay /= 60;
|
||
|
fields[MINUTE] = millisInDay % 60;
|
||
|
millisInDay /= 60;
|
||
|
fields[HOUR_OF_DAY] = millisInDay;
|
||
|
fields[AM_PM] = millisInDay / 12; // Assume AM == 0
|
||
|
fields[HOUR] = millisInDay % 12;
|
||
|
fields[ZONE_OFFSET] = offsets[0];
|
||
|
fields[DST_OFFSET] = offsets[1];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compute the Gregorian calendar year, month, and day of month from
|
||
|
* the given Julian day. These values are not stored in fields, but in
|
||
|
* member variables gregorianXxx. Also compute the DAY_OF_WEEK and
|
||
|
* DOW_LOCAL fields.
|
||
|
*/
|
||
|
private final void computeGregorianAndDOWFields(int julianDay) {
|
||
|
computeGregorianFields(julianDay);
|
||
|
|
||
|
// Compute day of week: JD 0 = Monday
|
||
|
int dow = fields[DAY_OF_WEEK] = julianDayToDayOfWeek(julianDay);
|
||
|
|
||
|
// Calculate 1-based localized day of week
|
||
|
int dowLocal = dow - getFirstDayOfWeek() + 1;
|
||
|
if (dowLocal < 1) {
|
||
|
dowLocal += 7;
|
||
|
}
|
||
|
fields[DOW_LOCAL] = dowLocal;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compute the Gregorian calendar year, month, and day of month from the
|
||
|
* Julian day. These values are not stored in fields, but in member
|
||
|
* variables gregorianXxx. They are used for time zone computations and by
|
||
|
* subclasses that are Gregorian derivatives. Subclasses may call this
|
||
|
* method to perform a Gregorian calendar millis->fields computation.
|
||
|
* To perform a Gregorian calendar fields->millis computation, call
|
||
|
* computeGregorianMonthStart().
|
||
|
* @see #computeGregorianMonthStart
|
||
|
*/
|
||
|
protected final void computeGregorianFields(int julianDay) {
|
||
|
int year, month, dayOfMonth, dayOfYear;
|
||
|
|
||
|
// The Gregorian epoch day is zero for Monday January 1, year 1.
|
||
|
long gregorianEpochDay = julianDay - JAN_1_1_JULIAN_DAY;
|
||
|
|
||
|
// Here we convert from the day number to the multiple radix
|
||
|
// representation. We use 400-year, 100-year, and 4-year cycles.
|
||
|
// For example, the 4-year cycle has 4 years + 1 leap day; giving
|
||
|
// 1461 == 365*4 + 1 days.
|
||
|
int[] rem = new int[1];
|
||
|
int n400 = floorDivide(gregorianEpochDay, 146097, rem); // 400-year cycle length
|
||
|
int n100 = floorDivide(rem[0], 36524, rem); // 100-year cycle length
|
||
|
int n4 = floorDivide(rem[0], 1461, rem); // 4-year cycle length
|
||
|
int n1 = floorDivide(rem[0], 365, rem);
|
||
|
year = 400*n400 + 100*n100 + 4*n4 + n1;
|
||
|
dayOfYear = rem[0]; // zero-based day of year
|
||
|
if (n100 == 4 || n1 == 4) {
|
||
|
dayOfYear = 365; // Dec 31 at end of 4- or 400-yr cycle
|
||
|
} else {
|
||
|
++year;
|
||
|
}
|
||
|
|
||
|
boolean isLeap = ((year&0x3) == 0) && // equiv. to (year%4 == 0)
|
||
|
(year%100 != 0 || year%400 == 0);
|
||
|
|
||
|
int correction = 0;
|
||
|
int march1 = isLeap ? 60 : 59; // zero-based DOY for March 1
|
||
|
if (dayOfYear >= march1) correction = isLeap ? 1 : 2;
|
||
|
month = (12 * (dayOfYear + correction) + 6) / 367; // zero-based month
|
||
|
dayOfMonth = dayOfYear -
|
||
|
GREGORIAN_MONTH_COUNT[month][isLeap?3:2] + 1; // one-based DOM
|
||
|
|
||
|
gregorianYear = year;
|
||
|
gregorianMonth = month; // 0-based already
|
||
|
gregorianDayOfMonth = dayOfMonth; // 1-based already
|
||
|
gregorianDayOfYear = dayOfYear + 1; // Convert from 0-based to 1-based
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compute the fields WEEK_OF_YEAR, YEAR_WOY, WEEK_OF_MONTH,
|
||
|
* DAY_OF_WEEK_IN_MONTH, and DOW_LOCAL from EXTENDED_YEAR, YEAR,
|
||
|
* DAY_OF_WEEK, and DAY_OF_YEAR. The latter fields are computed by the
|
||
|
* subclass based on the calendar system.
|
||
|
*
|
||
|
* <p>The YEAR_WOY field is computed simplistically. It is equal to YEAR
|
||
|
* most of the time, but at the year boundary it may be adjusted to YEAR-1
|
||
|
* or YEAR+1 to reflect the overlap of a week into an adjacent year. In
|
||
|
* this case, a simple increment or decrement is performed on YEAR, even
|
||
|
* though this may yield an invalid YEAR value. For instance, if the YEAR
|
||
|
* is part of a calendar system with an N-year cycle field CYCLE, then
|
||
|
* incrementing the YEAR may involve incrementing CYCLE and setting YEAR
|
||
|
* back to 0 or 1. This is not handled by this code, and in fact cannot be
|
||
|
* simply handled without having subclasses define an entire parallel set of
|
||
|
* fields for fields larger than or equal to a year. This additional
|
||
|
* complexity is not warranted, since the intention of the YEAR_WOY field is
|
||
|
* to support ISO 8601 notation, so it will typically be used with a
|
||
|
* proleptic Gregorian calendar, which has no field larger than a year.
|
||
|
*/
|
||
|
private final void computeWeekFields() {
|
||
|
int eyear = fields[EXTENDED_YEAR];
|
||
|
int dayOfWeek = fields[DAY_OF_WEEK];
|
||
|
int dayOfYear = fields[DAY_OF_YEAR];
|
||
|
|
||
|
// WEEK_OF_YEAR start
|
||
|
// Compute the week of the year. For the Gregorian calendar, valid week
|
||
|
// numbers run from 1 to 52 or 53, depending on the year, the first day
|
||
|
// of the week, and the minimal days in the first week. For other
|
||
|
// calendars, the valid range may be different -- it depends on the year
|
||
|
// length. Days at the start of the year may fall into the last week of
|
||
|
// the previous year; days at the end of the year may fall into the
|
||
|
// first week of the next year. ASSUME that the year length is less than
|
||
|
// 7000 days.
|
||
|
int yearOfWeekOfYear = eyear;
|
||
|
int relDow = (dayOfWeek + 7 - getFirstDayOfWeek()) % 7; // 0..6
|
||
|
int relDowJan1 = (dayOfWeek - dayOfYear + 7001 - getFirstDayOfWeek()) % 7; // 0..6
|
||
|
int woy = (dayOfYear - 1 + relDowJan1) / 7; // 0..53
|
||
|
if ((7 - relDowJan1) >= getMinimalDaysInFirstWeek()) {
|
||
|
++woy;
|
||
|
}
|
||
|
|
||
|
// Adjust for weeks at the year end that overlap into the previous or
|
||
|
// next calendar year.
|
||
|
if (woy == 0) {
|
||
|
// We are the last week of the previous year.
|
||
|
// Check to see if we are in the last week; if so, we need
|
||
|
// to handle the case in which we are the first week of the
|
||
|
// next year.
|
||
|
|
||
|
int prevDoy = dayOfYear + handleGetYearLength(eyear - 1);
|
||
|
woy = weekNumber(prevDoy, dayOfWeek);
|
||
|
yearOfWeekOfYear--;
|
||
|
} else {
|
||
|
int lastDoy = handleGetYearLength(eyear);
|
||
|
// Fast check: For it to be week 1 of the next year, the DOY
|
||
|
// must be on or after L-5, where L is yearLength(), then it
|
||
|
// cannot possibly be week 1 of the next year:
|
||
|
// L-5 L
|
||
|
// doy: 359 360 361 362 363 364 365 001
|
||
|
// dow: 1 2 3 4 5 6 7
|
||
|
if (dayOfYear >= (lastDoy - 5)) {
|
||
|
int lastRelDow = (relDow + lastDoy - dayOfYear) % 7;
|
||
|
if (lastRelDow < 0) {
|
||
|
lastRelDow += 7;
|
||
|
}
|
||
|
if (((6 - lastRelDow) >= getMinimalDaysInFirstWeek()) &&
|
||
|
((dayOfYear + 7 - relDow) > lastDoy)) {
|
||
|
woy = 1;
|
||
|
yearOfWeekOfYear++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
fields[WEEK_OF_YEAR] = woy;
|
||
|
fields[YEAR_WOY] = yearOfWeekOfYear;
|
||
|
// WEEK_OF_YEAR end
|
||
|
|
||
|
int dayOfMonth = fields[DAY_OF_MONTH];
|
||
|
fields[WEEK_OF_MONTH] = weekNumber(dayOfMonth, dayOfWeek);
|
||
|
fields[DAY_OF_WEEK_IN_MONTH] = (dayOfMonth-1) / 7 + 1;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------
|
||
|
// Fields -> Time
|
||
|
//----------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Value to OR against resolve table field values for remapping.
|
||
|
* @see #resolveFields
|
||
|
*/
|
||
|
protected static final int RESOLVE_REMAP = 32;
|
||
|
// A power of 2 greater than or equal to MAX_FIELD_COUNT
|
||
|
|
||
|
// Default table for day in year
|
||
|
static final int[][][] DATE_PRECEDENCE = {
|
||
|
{
|
||
|
{ DAY_OF_MONTH },
|
||
|
{ WEEK_OF_YEAR, DAY_OF_WEEK },
|
||
|
{ WEEK_OF_MONTH, DAY_OF_WEEK },
|
||
|
{ DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
|
||
|
{ WEEK_OF_YEAR, DOW_LOCAL },
|
||
|
{ WEEK_OF_MONTH, DOW_LOCAL },
|
||
|
{ DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
|
||
|
{ DAY_OF_YEAR },
|
||
|
{ RESOLVE_REMAP | DAY_OF_MONTH, YEAR }, // if YEAR is set over YEAR_WOY use DAY_OF_MONTH
|
||
|
{ RESOLVE_REMAP | WEEK_OF_YEAR, YEAR_WOY }, // if YEAR_WOY is set, calc based on WEEK_OF_YEAR
|
||
|
},
|
||
|
{
|
||
|
{ WEEK_OF_YEAR },
|
||
|
{ WEEK_OF_MONTH },
|
||
|
{ DAY_OF_WEEK_IN_MONTH },
|
||
|
{ RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DAY_OF_WEEK },
|
||
|
{ RESOLVE_REMAP | DAY_OF_WEEK_IN_MONTH, DOW_LOCAL },
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static final int[][][] DOW_PRECEDENCE = {
|
||
|
{
|
||
|
{ DAY_OF_WEEK },
|
||
|
{ DOW_LOCAL },
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static final int[][][] MONTH_PRECEDENCE = {
|
||
|
{
|
||
|
{ MONTH },
|
||
|
{ ORDINAL_MONTH },
|
||
|
},
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Given a precedence table, return the newest field combination in
|
||
|
* the table, or -1 if none is found.
|
||
|
*
|
||
|
* <p>The precedence table is a 3-dimensional array of integers. It
|
||
|
* may be thought of as an array of groups. Each group is an array of
|
||
|
* lines. Each line is an array of field numbers. Within a line, if
|
||
|
* all fields are set, then the time stamp of the line is taken to be
|
||
|
* the stamp of the most recently set field. If any field of a line is
|
||
|
* unset, then the line fails to match. Within a group, the line with
|
||
|
* the newest time stamp is selected. The first field of the line is
|
||
|
* returned to indicate which line matched.
|
||
|
*
|
||
|
* <p>In some cases, it may be desirable to map a line to field that
|
||
|
* whose stamp is NOT examined. For example, if the best field is
|
||
|
* DAY_OF_WEEK then the DAY_OF_WEEK_IN_MONTH algorithm may be used. In
|
||
|
* order to do this, insert the value <code>REMAP_RESOLVE | F</code> at
|
||
|
* the start of the line, where <code>F</code> is the desired return
|
||
|
* field value. This field will NOT be examined; it only determines
|
||
|
* the return value if the other fields in the line are the newest.
|
||
|
*
|
||
|
* <p>If all lines of a group contain at least one unset field, then no
|
||
|
* line will match, and the group as a whole will fail to match. In
|
||
|
* that case, the next group will be processed. If all groups fail to
|
||
|
* match, then -1 is returned.
|
||
|
*/
|
||
|
protected int resolveFields(int[][][] precedenceTable) {
|
||
|
int bestField = -1;
|
||
|
int tempBestField;
|
||
|
for (int g=0; g<precedenceTable.length && bestField < 0; ++g) {
|
||
|
int[][] group = precedenceTable[g];
|
||
|
int bestStamp = UNSET;
|
||
|
linesInGroup:
|
||
|
for (int l=0; l<group.length; ++l) {
|
||
|
int[] line= group[l];
|
||
|
int lineStamp = UNSET;
|
||
|
// Skip over first entry if it is negative
|
||
|
for (int i=(line[0]>=RESOLVE_REMAP)?1:0; i<line.length; ++i) {
|
||
|
int s = stamp[line[i]];
|
||
|
// If any field is unset then don't use this line
|
||
|
if (s == UNSET) {
|
||
|
continue linesInGroup;
|
||
|
} else {
|
||
|
lineStamp = Math.max(lineStamp, s);
|
||
|
}
|
||
|
}
|
||
|
// Record new maximum stamp & field no.
|
||
|
if (lineStamp > bestStamp) {
|
||
|
tempBestField = line[0]; // First field refers to entire line
|
||
|
if (tempBestField >= RESOLVE_REMAP) {
|
||
|
tempBestField &= (RESOLVE_REMAP-1);
|
||
|
// This check is needed to resolve some issues with UCAL_YEAR precedence mapping
|
||
|
if (tempBestField != DATE || (stamp[WEEK_OF_MONTH] < stamp[tempBestField])) {
|
||
|
bestField = tempBestField;
|
||
|
}
|
||
|
} else {
|
||
|
bestField = tempBestField;
|
||
|
}
|
||
|
|
||
|
if (bestField == tempBestField) {
|
||
|
bestStamp = lineStamp;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return (bestField>=RESOLVE_REMAP)?(bestField&(RESOLVE_REMAP-1)):bestField;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the newest stamp of a given range of fields.
|
||
|
*/
|
||
|
protected int newestStamp(int first, int last, int bestStampSoFar) {
|
||
|
int bestStamp = bestStampSoFar;
|
||
|
for (int i=first; i<=last; ++i) {
|
||
|
if (stamp[i] > bestStamp) {
|
||
|
bestStamp = stamp[i];
|
||
|
}
|
||
|
}
|
||
|
return bestStamp;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the timestamp of a field.
|
||
|
*/
|
||
|
protected final int getStamp(int field) {
|
||
|
return stamp[field];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the field that is newer, either defaultField, or
|
||
|
* alternateField. If neither is newer or neither is set, return defaultField.
|
||
|
*/
|
||
|
protected int newerField(int defaultField, int alternateField) {
|
||
|
if (stamp[alternateField] > stamp[defaultField]) {
|
||
|
return alternateField;
|
||
|
}
|
||
|
return defaultField;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Ensure that each field is within its valid range by calling {@link
|
||
|
* #validateField(int)} on each field that has been set. This method
|
||
|
* should only be called if this calendar is not lenient.
|
||
|
* @see #isLenient
|
||
|
* @see #validateField(int)
|
||
|
*/
|
||
|
protected void validateFields() {
|
||
|
for (int field = 0; field < fields.length; field++) {
|
||
|
if (stamp[field] >= MINIMUM_USER_STAMP) {
|
||
|
validateField(field);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validate a single field of this calendar. Subclasses should
|
||
|
* override this method to validate any calendar-specific fields.
|
||
|
* Generic fields can be handled by
|
||
|
* <code>Calendar.validateField()</code>.
|
||
|
* @see #validateField(int, int, int)
|
||
|
*/
|
||
|
protected void validateField(int field) {
|
||
|
int y;
|
||
|
switch (field) {
|
||
|
case DAY_OF_MONTH:
|
||
|
y = handleGetExtendedYear();
|
||
|
validateField(field, 1, handleGetMonthLength(y, internalGetMonth()));
|
||
|
break;
|
||
|
case DAY_OF_YEAR:
|
||
|
y = handleGetExtendedYear();
|
||
|
validateField(field, 1, handleGetYearLength(y));
|
||
|
break;
|
||
|
case DAY_OF_WEEK_IN_MONTH:
|
||
|
if (internalGet(field) == 0) {
|
||
|
throw new IllegalArgumentException("DAY_OF_WEEK_IN_MONTH cannot be zero");
|
||
|
}
|
||
|
validateField(field, getMinimum(field), getMaximum(field));
|
||
|
break;
|
||
|
default:
|
||
|
validateField(field, getMinimum(field), getMaximum(field));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validate a single field of this calendar given its minimum and
|
||
|
* maximum allowed value. If the field is out of range, throw a
|
||
|
* descriptive <code>IllegalArgumentException</code>. Subclasses may
|
||
|
* use this method in their implementation of {@link
|
||
|
* #validateField(int)}.
|
||
|
*/
|
||
|
protected final void validateField(int field, int min, int max) {
|
||
|
int value = fields[field];
|
||
|
if (value < min || value > max) {
|
||
|
throw new IllegalArgumentException(fieldName(field) +
|
||
|
'=' + value + ", valid range=" +
|
||
|
min + ".." + max);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts the current field values in <code>fields[]</code> to the
|
||
|
* millisecond time value <code>time</code>.
|
||
|
*/
|
||
|
protected void computeTime() {
|
||
|
if (!isLenient()) {
|
||
|
validateFields();
|
||
|
}
|
||
|
|
||
|
// Compute the Julian day
|
||
|
int julianDay = computeJulianDay();
|
||
|
|
||
|
long millis = julianDayToMillis(julianDay);
|
||
|
|
||
|
long millisInDay;
|
||
|
|
||
|
// We only use MILLISECONDS_IN_DAY if it has been set by the user.
|
||
|
// This makes it possible for the caller to set the calendar to a
|
||
|
// time and call clear(MONTH) to reset the MONTH to January. This
|
||
|
// is legacy behavior. Without this, clear(MONTH) has no effect,
|
||
|
// since the internally set JULIAN_DAY is used.
|
||
|
if (stamp[MILLISECONDS_IN_DAY] >= MINIMUM_USER_STAMP &&
|
||
|
newestStamp(AM_PM, MILLISECOND, UNSET) <= stamp[MILLISECONDS_IN_DAY]) {
|
||
|
millisInDay = internalGet(MILLISECONDS_IN_DAY);
|
||
|
} else {
|
||
|
int hour = Math.abs(internalGet(HOUR_OF_DAY));
|
||
|
hour = Math.max(hour, Math.abs(internalGet(HOUR)));
|
||
|
// if hour field value is greater than 596, then the
|
||
|
// milliseconds value exceeds integer range, hence
|
||
|
// using a conservative estimate of 548, we invoke
|
||
|
// the long return version of the compute millis method if
|
||
|
// the hour value exceeds 548
|
||
|
if (hour > MAX_HOURS) {
|
||
|
millisInDay = computeMillisInDayLong();
|
||
|
} else {
|
||
|
millisInDay = computeMillisInDay();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (stamp[ZONE_OFFSET] >= MINIMUM_USER_STAMP ||
|
||
|
stamp[DST_OFFSET] >= MINIMUM_USER_STAMP) {
|
||
|
time = millis + millisInDay - (internalGet(ZONE_OFFSET) + internalGet(DST_OFFSET));
|
||
|
} else {
|
||
|
// 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 positive offset change such as transition into DST.
|
||
|
// Here, a designated time of 2:00 am - 2:59 am does not actually exist.
|
||
|
// For this case, skippedWallTime option specifies the behavior.
|
||
|
// For example, 2:30 am is interpreted as;
|
||
|
// - WALLTIME_LAST(default): 3:30 am (DST) (interpreting 2:30 am as 31 minutes after 1:59 am (STD))
|
||
|
// - WALLTIME_FIRST: 1:30 am (STD) (interpreting 2:30 am as 30 minutes before 3:00 am (DST))
|
||
|
// - WALLTIME_NEXT_VALID: 3:00 am (DST) (next valid time after 2:30 am on a wall clock)
|
||
|
// 2. The negative offset change such as 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).
|
||
|
// For this case, repeatedWallTime option specifies the behavior.
|
||
|
// For example, 1:30 am is interpreted as;
|
||
|
// - WALLTIME_LAST(default): 1:30 am (STD) - latter occurrence
|
||
|
// - WALLTIME_FIRST: 1:30 am (DST) - former occurrence
|
||
|
//
|
||
|
// In addition to above, when calendar is strict (not default), wall time falls into
|
||
|
// the skipped time range will be processed as an error case.
|
||
|
//
|
||
|
// These special cases are mostly handled in #computeZoneOffset(long), except WALLTIME_NEXT_VALID
|
||
|
// at positive offset change. The protected method computeZoneOffset(long) is exposed to Calendar
|
||
|
// subclass implementations and marked as @stable. Strictly speaking, WALLTIME_NEXT_VALID
|
||
|
// should be also handled in the same place, but we cannot change the code flow without deprecating
|
||
|
// the protected method.
|
||
|
//
|
||
|
// We use the TimeZone object, unless the user has explicitly set the ZONE_OFFSET
|
||
|
// or DST_OFFSET fields; then we use those fields.
|
||
|
|
||
|
if (!lenient || skippedWallTime == WALLTIME_NEXT_VALID) {
|
||
|
// When strict, invalidate a wall time falls into a skipped wall time range.
|
||
|
// When lenient and skipped wall time option is WALLTIME_NEXT_VALID,
|
||
|
// the result time will be adjusted to the next valid time (on wall clock).
|
||
|
int zoneOffset = computeZoneOffset(millis, millisInDay);
|
||
|
long tmpTime = millis + millisInDay - zoneOffset;
|
||
|
|
||
|
int zoneOffset1 = zone.getOffset(tmpTime);
|
||
|
|
||
|
// zoneOffset != zoneOffset1 only when the given wall time fall into
|
||
|
// a skipped wall time range caused by positive zone offset transition.
|
||
|
if (zoneOffset != zoneOffset1) {
|
||
|
if (!lenient) {
|
||
|
throw new IllegalArgumentException("The specified wall time does not exist due to time zone offset transition.");
|
||
|
}
|
||
|
|
||
|
assert skippedWallTime == WALLTIME_NEXT_VALID : skippedWallTime;
|
||
|
// Adjust time to the next valid wall clock time.
|
||
|
// At this point, tmpTime is on or after the zone offset transition causing
|
||
|
// the skipped time range.
|
||
|
Long immediatePrevTransition = getImmediatePreviousZoneTransition(tmpTime);
|
||
|
if (immediatePrevTransition == null) {
|
||
|
throw new RuntimeException("Could not locate a time zone transition before " + tmpTime);
|
||
|
}
|
||
|
time = immediatePrevTransition;
|
||
|
} else {
|
||
|
time = tmpTime;
|
||
|
}
|
||
|
} else {
|
||
|
time = millis + millisInDay - computeZoneOffset(millis, millisInDay);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Find the previous zone transition near the given time.
|
||
|
*
|
||
|
* @param base The base time, inclusive.
|
||
|
* @return The time of the previous transition, or null if not found.
|
||
|
*/
|
||
|
private Long getImmediatePreviousZoneTransition(long base) {
|
||
|
Long transitionTime = null;
|
||
|
|
||
|
if (zone instanceof BasicTimeZone) {
|
||
|
TimeZoneTransition transition = ((BasicTimeZone) zone).getPreviousTransition(base, true);
|
||
|
if (transition != null) {
|
||
|
transitionTime = transition.getTime();
|
||
|
}
|
||
|
} else {
|
||
|
// Usually, it is enough to check past one hour because such transition is most
|
||
|
// likely +1 hour shift. However, there is an example jumped +24 hour in the tz database.
|
||
|
transitionTime = getPreviousZoneTransitionTime(zone, base, 2 * 60 * 60 * 1000); // check last 2 hours
|
||
|
if (transitionTime == null) {
|
||
|
transitionTime = getPreviousZoneTransitionTime(zone, base, 30 * 60 * 60 * 1000); // try last 30 hours
|
||
|
}
|
||
|
}
|
||
|
return transitionTime;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Find the previous zone transition within the specified duration.
|
||
|
* Note: This method is only used when TimeZone is NOT a BasicTimeZone.
|
||
|
* @param tz The time zone.
|
||
|
* @param base The base time, inclusive.
|
||
|
* @param duration The range of time evaluated.
|
||
|
* @return The time of the previous zone transition, or null if not available.
|
||
|
*/
|
||
|
private static Long getPreviousZoneTransitionTime(TimeZone tz, long base, long duration) {
|
||
|
assert duration > 0;
|
||
|
|
||
|
long upper = base;
|
||
|
long lower = base - duration - 1;
|
||
|
int offsetU = tz.getOffset(upper);
|
||
|
int offsetL = tz.getOffset(lower);
|
||
|
if (offsetU == offsetL) {
|
||
|
return null;
|
||
|
}
|
||
|
return findPreviousZoneTransitionTime(tz, offsetU, upper, lower);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The time units used by {@link #findPreviousZoneTransitionTime(TimeZone, int, long, long)}
|
||
|
* for optimizing transition time binary search.
|
||
|
*/
|
||
|
private static final int[] FIND_ZONE_TRANSITION_TIME_UNITS = {
|
||
|
60*60*1000, // 1 hour
|
||
|
30*60*1000, // 30 minutes
|
||
|
60*1000, // 1 minute
|
||
|
1000, // 1 second
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Implementing binary search for zone transition detection, used by {@link #getPreviousZoneTransitionTime(TimeZone, long, long)}
|
||
|
* @param tz The time zone.
|
||
|
* @param upperOffset The zone offset at <code>upper</code>
|
||
|
* @param upper The upper bound, inclusive.
|
||
|
* @param lower The lower bound, exclusive.
|
||
|
* @return The time of the previous zone transition, or null if not available.
|
||
|
*/
|
||
|
private static Long findPreviousZoneTransitionTime(TimeZone tz, int upperOffset, long upper, long lower) {
|
||
|
boolean onUnitTime = false;
|
||
|
long mid = 0;
|
||
|
|
||
|
for (int unit : FIND_ZONE_TRANSITION_TIME_UNITS) {
|
||
|
long lunits = lower/unit;
|
||
|
long uunits = upper/unit;
|
||
|
if (uunits > lunits) {
|
||
|
mid = ((lunits + uunits + 1) >>> 1) * unit;
|
||
|
onUnitTime = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int midOffset;
|
||
|
if (!onUnitTime) {
|
||
|
mid = (upper + lower) >>> 1;
|
||
|
}
|
||
|
|
||
|
if (onUnitTime) {
|
||
|
if (mid != upper) {
|
||
|
midOffset = tz.getOffset(mid);
|
||
|
if (midOffset != upperOffset) {
|
||
|
return findPreviousZoneTransitionTime(tz, upperOffset, upper, mid);
|
||
|
}
|
||
|
upper = mid;
|
||
|
}
|
||
|
// check mid-1
|
||
|
mid--;
|
||
|
} else {
|
||
|
mid = (upper + lower) >>> 1;
|
||
|
}
|
||
|
|
||
|
if (mid == lower) {
|
||
|
return upper;
|
||
|
}
|
||
|
midOffset = tz.getOffset(mid);
|
||
|
if (midOffset != upperOffset) {
|
||
|
if (onUnitTime) {
|
||
|
return upper;
|
||
|
}
|
||
|
return findPreviousZoneTransitionTime(tz, upperOffset, upper, mid);
|
||
|
}
|
||
|
return findPreviousZoneTransitionTime(tz, upperOffset, mid, lower);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compute the milliseconds in the day from the fields. This is a
|
||
|
* value from 0 to 23:59:59.999 inclusive, unless fields are out of
|
||
|
* range, in which case it can be an arbitrary value. This value
|
||
|
* reflects local zone wall time.
|
||
|
* @deprecated ICU 60
|
||
|
*/
|
||
|
@Deprecated
|
||
|
protected int computeMillisInDay() {
|
||
|
// Do the time portion of the conversion.
|
||
|
|
||
|
int millisInDay = 0;
|
||
|
|
||
|
// Find the best set of fields specifying the time of day. There
|
||
|
// are only two possibilities here; the HOUR_OF_DAY or the
|
||
|
// AM_PM and the HOUR.
|
||
|
int hourOfDayStamp = stamp[HOUR_OF_DAY];
|
||
|
int hourStamp = Math.max(stamp[HOUR], stamp[AM_PM]);
|
||
|
int bestStamp = (hourStamp > hourOfDayStamp) ? hourStamp : hourOfDayStamp;
|
||
|
|
||
|
// Hours
|
||
|
if (bestStamp != UNSET) {
|
||
|
if (bestStamp == hourOfDayStamp) {
|
||
|
// Don't normalize here; let overflow bump into the next period.
|
||
|
// This is consistent with how we handle other fields.
|
||
|
millisInDay += internalGet(HOUR_OF_DAY);
|
||
|
} else {
|
||
|
// Don't normalize here; let overflow bump into the next period.
|
||
|
// This is consistent with how we handle other fields.
|
||
|
millisInDay += internalGet(HOUR);
|
||
|
millisInDay += 12 * internalGet(AM_PM); // Default works for unset AM_PM
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We use the fact that unset == 0; we start with millisInDay
|
||
|
// == HOUR_OF_DAY.
|
||
|
millisInDay *= 60;
|
||
|
millisInDay += internalGet(MINUTE); // now have minutes
|
||
|
millisInDay *= 60;
|
||
|
millisInDay += internalGet(SECOND); // now have seconds
|
||
|
millisInDay *= 1000;
|
||
|
millisInDay += internalGet(MILLISECOND); // now have millis
|
||
|
|
||
|
return millisInDay;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compute the milliseconds in the day from the fields. The standard
|
||
|
* value range is from 0 to 23:59:59.999 inclusive. This value
|
||
|
* reflects local zone wall time.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
protected long computeMillisInDayLong() {
|
||
|
// Do the time portion of the conversion.
|
||
|
|
||
|
long millisInDay = 0;
|
||
|
|
||
|
// Find the best set of fields specifying the time of day. There
|
||
|
// are only two possibilities here; the HOUR_OF_DAY or the
|
||
|
// AM_PM and the HOUR.
|
||
|
int hourOfDayStamp = stamp[HOUR_OF_DAY];
|
||
|
int hourStamp = Math.max(stamp[HOUR], stamp[AM_PM]);
|
||
|
int bestStamp = (hourStamp > hourOfDayStamp) ? hourStamp : hourOfDayStamp;
|
||
|
|
||
|
// Hours
|
||
|
if (bestStamp != UNSET) {
|
||
|
if (bestStamp == hourOfDayStamp) {
|
||
|
// Don't normalize here; let overflow bump into the next period.
|
||
|
// This is consistent with how we handle other fields.
|
||
|
millisInDay += internalGet(HOUR_OF_DAY);
|
||
|
} else {
|
||
|
// Don't normalize here; let overflow bump into the next period.
|
||
|
// This is consistent with how we handle other fields.
|
||
|
millisInDay += internalGet(HOUR);
|
||
|
millisInDay += 12 * internalGet(AM_PM); // Default works for unset AM_PM
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We use the fact that unset == 0; we start with millisInDay
|
||
|
// == HOUR_OF_DAY.
|
||
|
millisInDay *= 60;
|
||
|
millisInDay += internalGet(MINUTE); // now have minutes
|
||
|
millisInDay *= 60;
|
||
|
millisInDay += internalGet(SECOND); // now have seconds
|
||
|
millisInDay *= 1000;
|
||
|
millisInDay += internalGet(MILLISECOND); // now have millis
|
||
|
|
||
|
return millisInDay;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* This method can assume EXTENDED_YEAR has been set.
|
||
|
* @param millis milliseconds of the date fields (local midnight millis)
|
||
|
* @param millisInDay milliseconds of the time fields; may be out
|
||
|
* or range.
|
||
|
* @return total zone offset (raw + DST) for the given moment
|
||
|
* @deprecated ICU 60
|
||
|
*/
|
||
|
@Deprecated
|
||
|
protected int computeZoneOffset(long millis, int millisInDay) {
|
||
|
int[] offsets = new int[2];
|
||
|
long wall = millis + millisInDay;
|
||
|
if (zone instanceof BasicTimeZone) {
|
||
|
LocalOption nonExistingTimeOpt = (skippedWallTime == WALLTIME_FIRST) ? LocalOption.LATTER : LocalOption.FORMER;
|
||
|
LocalOption duplicatedTimeOpt = (repeatedWallTime == WALLTIME_FIRST) ? LocalOption.FORMER : LocalOption.LATTER;
|
||
|
((BasicTimeZone)zone).getOffsetFromLocal(wall, nonExistingTimeOpt, duplicatedTimeOpt, offsets);
|
||
|
} else {
|
||
|
// By default, TimeZone#getOffset behaves WALLTIME_LAST for both.
|
||
|
zone.getOffset(wall, true, offsets);
|
||
|
|
||
|
boolean sawRecentNegativeShift = false;
|
||
|
if (repeatedWallTime == WALLTIME_FIRST) {
|
||
|
// Check if the given wall time falls into repeated time range
|
||
|
long tgmt = wall - (offsets[0] + offsets[1]);
|
||
|
|
||
|
// Any negative zone transition within last 6 hours?
|
||
|
// Note: The maximum historic negative zone transition is -3 hours in the tz database.
|
||
|
// 6 hour window would be sufficient for this purpose.
|
||
|
int offsetBefore6 = zone.getOffset(tgmt - 6*60*60*1000);
|
||
|
int offsetDelta = (offsets[0] + offsets[1]) - offsetBefore6;
|
||
|
|
||
|
assert offsetDelta > -6*60*60*1000 : offsetDelta;
|
||
|
if (offsetDelta < 0) {
|
||
|
sawRecentNegativeShift = true;
|
||
|
// Negative shift within last 6 hours. When WALLTIME_FIRST is used and the given wall time falls
|
||
|
// into the repeated time range, use offsets before the transition.
|
||
|
// Note: If it does not fall into the repeated time range, offsets remain unchanged below.
|
||
|
zone.getOffset(wall + offsetDelta, true, offsets);
|
||
|
}
|
||
|
}
|
||
|
if (!sawRecentNegativeShift && skippedWallTime == WALLTIME_FIRST) {
|
||
|
// When skipped wall time option is WALLTIME_FIRST,
|
||
|
// recalculate offsets from the resolved time (non-wall).
|
||
|
// When the given wall time falls into skipped wall time,
|
||
|
// the offsets will be based on the zone offsets AFTER
|
||
|
// the transition (which means, earliest possible interpretation).
|
||
|
long tgmt = wall - (offsets[0] + offsets[1]);
|
||
|
zone.getOffset(tgmt, false, offsets);
|
||
|
}
|
||
|
}
|
||
|
return offsets[0] + offsets[1];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method can assume EXTENDED_YEAR has been set.
|
||
|
* @param millis milliseconds of the date fields (local midnight millis)
|
||
|
* @param millisInDay milliseconds of the time fields
|
||
|
* @return total zone offset (raw + DST) for the given moment
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
protected int computeZoneOffset(long millis, long millisInDay) {
|
||
|
int[] offsets = new int[2];
|
||
|
long wall = millis + millisInDay;
|
||
|
if (zone instanceof BasicTimeZone) {
|
||
|
LocalOption nonExistingTimeOpt = (skippedWallTime == WALLTIME_FIRST) ? LocalOption.LATTER : LocalOption.FORMER;
|
||
|
LocalOption duplicatedTimeOpt = (repeatedWallTime == WALLTIME_FIRST) ? LocalOption.FORMER : LocalOption.LATTER;
|
||
|
((BasicTimeZone)zone).getOffsetFromLocal(wall, nonExistingTimeOpt, duplicatedTimeOpt, offsets);
|
||
|
} else {
|
||
|
// By default, TimeZone#getOffset behaves WALLTIME_LAST for both.
|
||
|
zone.getOffset(wall, true, offsets);
|
||
|
|
||
|
boolean sawRecentNegativeShift = false;
|
||
|
if (repeatedWallTime == WALLTIME_FIRST) {
|
||
|
// Check if the given wall time falls into repeated time range
|
||
|
long tgmt = wall - (offsets[0] + offsets[1]);
|
||
|
|
||
|
// Any negative zone transition within last 6 hours?
|
||
|
// Note: The maximum historic negative zone transition is -3 hours in the tz database.
|
||
|
// 6 hour window would be sufficient for this purpose.
|
||
|
int offsetBefore6 = zone.getOffset(tgmt - 6*60*60*1000);
|
||
|
int offsetDelta = (offsets[0] + offsets[1]) - offsetBefore6;
|
||
|
|
||
|
assert offsetDelta > -6*60*60*1000 : offsetDelta;
|
||
|
if (offsetDelta < 0) {
|
||
|
sawRecentNegativeShift = true;
|
||
|
// Negative shift within last 6 hours. When WALLTIME_FIRST is used and the given wall time falls
|
||
|
// into the repeated time range, use offsets before the transition.
|
||
|
// Note: If it does not fall into the repeated time range, offsets remain unchanged below.
|
||
|
zone.getOffset(wall + offsetDelta, true, offsets);
|
||
|
}
|
||
|
}
|
||
|
if (!sawRecentNegativeShift && skippedWallTime == WALLTIME_FIRST) {
|
||
|
// When skipped wall time option is WALLTIME_FIRST,
|
||
|
// recalculate offsets from the resolved time (non-wall).
|
||
|
// When the given wall time falls into skipped wall time,
|
||
|
// the offsets will be based on the zone offsets AFTER
|
||
|
// the transition (which means, earliest possible interpretation).
|
||
|
long tgmt = wall - (offsets[0] + offsets[1]);
|
||
|
zone.getOffset(tgmt, false, offsets);
|
||
|
}
|
||
|
}
|
||
|
return offsets[0] + offsets[1];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compute the Julian day number as specified by this calendar's fields.
|
||
|
*/
|
||
|
protected int computeJulianDay() {
|
||
|
|
||
|
// We want to see if any of the date fields is newer than the
|
||
|
// JULIAN_DAY. If not, then we use JULIAN_DAY. If so, then we do
|
||
|
// the normal resolution. We only use JULIAN_DAY if it has been
|
||
|
// set by the user. This makes it possible for the caller to set
|
||
|
// the calendar to a time and call clear(MONTH) to reset the MONTH
|
||
|
// to January. This is legacy behavior. Without this,
|
||
|
// clear(MONTH) has no effect, since the internally set JULIAN_DAY
|
||
|
// is used.
|
||
|
if (stamp[JULIAN_DAY] >= MINIMUM_USER_STAMP) {
|
||
|
int bestStamp = newestStamp(ERA, DAY_OF_WEEK_IN_MONTH, UNSET);
|
||
|
bestStamp = newestStamp(YEAR_WOY, EXTENDED_YEAR, bestStamp);
|
||
|
bestStamp = newestStamp(ORDINAL_MONTH, ORDINAL_MONTH, bestStamp);
|
||
|
if (bestStamp <= stamp[JULIAN_DAY]) {
|
||
|
return internalGet(JULIAN_DAY);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int bestField = resolveFields(getFieldResolutionTable());
|
||
|
if (bestField < 0) {
|
||
|
bestField = DAY_OF_MONTH;
|
||
|
}
|
||
|
|
||
|
return handleComputeJulianDay(bestField);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the field resolution array for this calendar. Calendars that
|
||
|
* define additional fields or change the semantics of existing fields
|
||
|
* should override this method to adjust the field resolution semantics
|
||
|
* accordingly. Other subclasses should not override this method.
|
||
|
* @see #resolveFields
|
||
|
*/
|
||
|
protected int[][][] getFieldResolutionTable() {
|
||
|
return DATE_PRECEDENCE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the Julian day number of day before the first day of the
|
||
|
* given month in the given extended year. Subclasses should override
|
||
|
* this method to implement their calendar system.
|
||
|
* @param eyear the extended year
|
||
|
* @param month the zero-based month, or 0 if useMonth is false
|
||
|
* @param useMonth if false, compute the day before the first day of
|
||
|
* the given year, otherwise, compute the day before the first day of
|
||
|
* the given month
|
||
|
* @return the Julian day number of the day before the first
|
||
|
* day of the given month and year
|
||
|
*/
|
||
|
abstract protected int handleComputeMonthStart(int eyear, int month,
|
||
|
boolean useMonth);
|
||
|
|
||
|
/**
|
||
|
* Returns the extended year defined by the current fields. This will
|
||
|
* use the EXTENDED_YEAR field or the YEAR and supra-year fields (such
|
||
|
* as ERA) specific to the calendar system, depending on which set of
|
||
|
* fields is newer.
|
||
|
* @return the extended year
|
||
|
*/
|
||
|
abstract protected int handleGetExtendedYear();
|
||
|
|
||
|
// (The following method is not called because all existing subclasses
|
||
|
// override it. 2003-06-11 ICU 2.6 Alan)
|
||
|
///CLOVER:OFF
|
||
|
/**
|
||
|
* Returns the number of days in the given month of the given extended
|
||
|
* year of this calendar system. Subclasses should override this
|
||
|
* method if they can provide a more correct or more efficient
|
||
|
* implementation than the default implementation in Calendar.
|
||
|
*/
|
||
|
protected int handleGetMonthLength(int extendedYear, int month) {
|
||
|
return handleComputeMonthStart(extendedYear, month+1, true) -
|
||
|
handleComputeMonthStart(extendedYear, month, true);
|
||
|
}
|
||
|
///CLOVER:ON
|
||
|
|
||
|
/**
|
||
|
* Returns the number of days in the given extended year of this
|
||
|
* calendar system. Subclasses should override this method if they can
|
||
|
* provide a more correct or more efficient implementation than the
|
||
|
* default implementation in Calendar.
|
||
|
*/
|
||
|
protected int handleGetYearLength(int eyear) {
|
||
|
return handleComputeMonthStart(eyear+1, 0, false) -
|
||
|
handleComputeMonthStart(eyear, 0, false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Subclasses that use additional fields beyond those defined in
|
||
|
* <code>Calendar</code> should override this method to return an
|
||
|
* <code>int[]</code> array of the appropriate length. The length
|
||
|
* must be at least <code>BASE_FIELD_COUNT</code> and no more than
|
||
|
* <code>MAX_FIELD_COUNT</code>.
|
||
|
*/
|
||
|
protected int[] handleCreateFields() {
|
||
|
return new int[BASE_FIELD_COUNT];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Subclasses may override this.
|
||
|
* Called by handleComputeJulianDay. Returns the default month (0-based) for the year,
|
||
|
* taking year and era into account. Defaults to 0 (JANUARY) for Gregorian.
|
||
|
* @param extendedYear the extendedYear, as returned by handleGetExtendedYear
|
||
|
* @return the default month
|
||
|
* @see #MONTH
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
protected int getDefaultMonthInYear(int extendedYear) {
|
||
|
return Calendar.JANUARY;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Subclasses may override this.
|
||
|
* Called by handleComputeJulianDay. Returns the default day (1-based) for the month,
|
||
|
* taking currently-set year and era into account. Defaults to 1 for Gregorian.
|
||
|
* @param extendedYear the extendedYear, as returned by handleGetExtendedYear
|
||
|
* @param month the month, as returned by getDefaultMonthInYear
|
||
|
* @return the default day of the month
|
||
|
* @see #DAY_OF_MONTH
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
protected int getDefaultDayInMonth(int extendedYear, int month) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Subclasses may override this. This method calls
|
||
|
* handleGetMonthLength() to obtain the calendar-specific month
|
||
|
* length.
|
||
|
*/
|
||
|
protected int handleComputeJulianDay(int bestField) {
|
||
|
|
||
|
boolean useMonth = (bestField == DAY_OF_MONTH ||
|
||
|
bestField == WEEK_OF_MONTH ||
|
||
|
bestField == DAY_OF_WEEK_IN_MONTH);
|
||
|
|
||
|
int year;
|
||
|
|
||
|
if (bestField == WEEK_OF_YEAR && newerField(YEAR_WOY, YEAR) == YEAR_WOY) {
|
||
|
// Nota Bene! It is critical that YEAR_WOY be used as the year here, if it is
|
||
|
// set. Otherwise, when WOY is the best field, the year may be wrong at the
|
||
|
// extreme limits of the year. If YEAR_WOY is not set then it will fall back.
|
||
|
// TODO: Should resolveField(YEAR_PRECEDENCE) be brought to bear?
|
||
|
year = internalGet(YEAR_WOY);
|
||
|
} else {
|
||
|
year = handleGetExtendedYear();
|
||
|
}
|
||
|
|
||
|
internalSet(EXTENDED_YEAR, year);
|
||
|
|
||
|
if (year > Long.MAX_VALUE / 400) {
|
||
|
throw new IllegalArgumentException("year is too large");
|
||
|
}
|
||
|
|
||
|
int month = useMonth ? internalGetMonth(getDefaultMonthInYear(year)) : 0;
|
||
|
|
||
|
// Get the Julian day of the day BEFORE the start of this year.
|
||
|
// If useMonth is true, get the day before the start of the month.
|
||
|
int julianDay = handleComputeMonthStart(year, month, useMonth);
|
||
|
|
||
|
if (bestField == DAY_OF_MONTH) {
|
||
|
if(isSet(DAY_OF_MONTH)) {
|
||
|
return julianDay + internalGet(DAY_OF_MONTH, getDefaultDayInMonth(year, month));
|
||
|
} else {
|
||
|
return julianDay + getDefaultDayInMonth(year, month);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bestField == DAY_OF_YEAR) {
|
||
|
return julianDay + internalGet(DAY_OF_YEAR);
|
||
|
}
|
||
|
|
||
|
int firstDOW = getFirstDayOfWeek(); // Localized fdw
|
||
|
|
||
|
// At this point julianDay is the 0-based day BEFORE the first day of
|
||
|
// January 1, year 1 of the given calendar. If julianDay == 0, it
|
||
|
// specifies (Jan. 1, 1) - 1, in whatever calendar we are using (Julian
|
||
|
// or Gregorian).
|
||
|
|
||
|
// At this point we need to process the WEEK_OF_MONTH or
|
||
|
// WEEK_OF_YEAR, which are similar, or the DAY_OF_WEEK_IN_MONTH.
|
||
|
// First, perform initial shared computations. These locate the
|
||
|
// first week of the period.
|
||
|
|
||
|
// Get the 0-based localized DOW of day one of the month or year.
|
||
|
// Valid range 0..6.
|
||
|
int first = julianDayToDayOfWeek(julianDay + 1) - firstDOW;
|
||
|
if (first < 0) {
|
||
|
first += 7;
|
||
|
}
|
||
|
|
||
|
// Get zero-based localized DOW, valid range 0..6. This is the DOW
|
||
|
// we are looking for.
|
||
|
int dowLocal = 0;
|
||
|
switch (resolveFields(DOW_PRECEDENCE)) {
|
||
|
case DAY_OF_WEEK:
|
||
|
dowLocal = internalGet(DAY_OF_WEEK) - firstDOW;
|
||
|
break;
|
||
|
case DOW_LOCAL:
|
||
|
dowLocal = internalGet(DOW_LOCAL) - 1;
|
||
|
break;
|
||
|
}
|
||
|
dowLocal = dowLocal % 7;
|
||
|
if (dowLocal < 0) {
|
||
|
dowLocal += 7;
|
||
|
}
|
||
|
|
||
|
// Find the first target DOW (dowLocal) in the month or year.
|
||
|
// Actually, it may be just before the first of the month or year.
|
||
|
// It will be an integer from -5..7.
|
||
|
int date = 1 - first + dowLocal;
|
||
|
|
||
|
if (bestField == DAY_OF_WEEK_IN_MONTH) {
|
||
|
|
||
|
// Adjust the target DOW to be in the month or year.
|
||
|
if (date < 1) {
|
||
|
date += 7;
|
||
|
}
|
||
|
|
||
|
// The only trickiness occurs if the day-of-week-in-month is
|
||
|
// negative.
|
||
|
int dim = internalGet(DAY_OF_WEEK_IN_MONTH, 1);
|
||
|
if (dim >= 0) {
|
||
|
date += 7*(dim - 1);
|
||
|
|
||
|
} else {
|
||
|
// Move date to the last of this day-of-week in this month,
|
||
|
// then back up as needed. If dim==-1, we don't back up at
|
||
|
// all. If dim==-2, we back up once, etc. Don't back up
|
||
|
// past the first of the given day-of-week in this month.
|
||
|
// Note that we handle -2, -3, etc. correctly, even though
|
||
|
// values < -1 are technically disallowed.
|
||
|
int m = internalGetMonth(JANUARY);
|
||
|
int monthLength = handleGetMonthLength(year, m);
|
||
|
date += ((monthLength - date) / 7 + dim + 1) * 7;
|
||
|
}
|
||
|
} else {
|
||
|
// assert(bestField == WEEK_OF_MONTH || bestField == WEEK_OF_YEAR)
|
||
|
|
||
|
// Adjust for minimal days in first week
|
||
|
if ((7 - first) < getMinimalDaysInFirstWeek()) {
|
||
|
date += 7;
|
||
|
}
|
||
|
|
||
|
// Now adjust for the week number.
|
||
|
date += 7 * (internalGet(bestField) - 1);
|
||
|
}
|
||
|
|
||
|
return julianDay + date;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compute the Julian day of a month of the Gregorian calendar.
|
||
|
* Subclasses may call this method to perform a Gregorian calendar
|
||
|
* fields->millis computation. To perform a Gregorian calendar
|
||
|
* millis->fields computation, call computeGregorianFields().
|
||
|
* @param year extended Gregorian year
|
||
|
* @param month zero-based Gregorian month
|
||
|
* @return the Julian day number of the day before the first
|
||
|
* day of the given month in the given extended year
|
||
|
* @see #computeGregorianFields
|
||
|
*/
|
||
|
protected int computeGregorianMonthStart(int year, int month) {
|
||
|
|
||
|
// If the month is out of range, adjust it into range, and
|
||
|
// modify the extended year value accordingly.
|
||
|
if (month < 0 || month > 11) {
|
||
|
int[] rem = new int[1];
|
||
|
year += floorDivide(month, 12, rem);
|
||
|
month = rem[0];
|
||
|
}
|
||
|
|
||
|
boolean isLeap = (year%4 == 0) && ((year%100 != 0) || (year%400 == 0));
|
||
|
int y = year - 1;
|
||
|
// This computation is actually ... + (JAN_1_1_JULIAN_DAY - 3) + 2.
|
||
|
// Add 2 because Gregorian calendar starts 2 days after Julian
|
||
|
// calendar.
|
||
|
int julianDay = 365*y + floorDivide(y, 4) - floorDivide(y, 100) +
|
||
|
floorDivide(y, 400) + JAN_1_1_JULIAN_DAY - 1;
|
||
|
|
||
|
// At this point julianDay indicates the day BEFORE the first day
|
||
|
// of January 1, <eyear> of the Gregorian calendar.
|
||
|
if (month != 0) {
|
||
|
julianDay += GREGORIAN_MONTH_COUNT[month][isLeap?3:2];
|
||
|
}
|
||
|
|
||
|
return julianDay;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------
|
||
|
// Subclass API
|
||
|
// For subclasses to override
|
||
|
//----------------------------------------------------------------------
|
||
|
|
||
|
// (The following method is not called because all existing subclasses
|
||
|
// override it. 2003-06-11 ICU 2.6 Alan)
|
||
|
///CLOVER:OFF
|
||
|
/**
|
||
|
* Subclasses may override this method to compute several fields
|
||
|
* specific to each calendar system. These are:
|
||
|
*
|
||
|
* <ul><li>ERA
|
||
|
* <li>YEAR
|
||
|
* <li>MONTH
|
||
|
* <li>DAY_OF_MONTH
|
||
|
* <li>DAY_OF_YEAR
|
||
|
* <li>EXTENDED_YEAR</ul>
|
||
|
*
|
||
|
* Subclasses can refer to the DAY_OF_WEEK and DOW_LOCAL fields, which
|
||
|
* will be set when this method is called. Subclasses can also call
|
||
|
* the getGregorianXxx() methods to obtain Gregorian calendar
|
||
|
* equivalents for the given Julian day.
|
||
|
*
|
||
|
* <p>In addition, subclasses should compute any subclass-specific
|
||
|
* fields, that is, fields from BASE_FIELD_COUNT to
|
||
|
* getFieldCount() - 1.
|
||
|
*
|
||
|
* <p>The default implementation in <code>Calendar</code> implements
|
||
|
* a pure proleptic Gregorian calendar.
|
||
|
*/
|
||
|
protected void handleComputeFields(int julianDay) {
|
||
|
int gmonth = getGregorianMonth();
|
||
|
internalSet(MONTH, gmonth);
|
||
|
internalSet(ORDINAL_MONTH, gmonth);
|
||
|
internalSet(DAY_OF_MONTH, getGregorianDayOfMonth());
|
||
|
internalSet(DAY_OF_YEAR, getGregorianDayOfYear());
|
||
|
int eyear = getGregorianYear();
|
||
|
internalSet(EXTENDED_YEAR, eyear);
|
||
|
int era = GregorianCalendar.AD;
|
||
|
if (eyear < 1) {
|
||
|
era = GregorianCalendar.BC;
|
||
|
eyear = 1 - eyear;
|
||
|
}
|
||
|
internalSet(ERA, era);
|
||
|
internalSet(YEAR, eyear);
|
||
|
}
|
||
|
///CLOVER:ON
|
||
|
|
||
|
//----------------------------------------------------------------------
|
||
|
// Subclass API
|
||
|
// For subclasses to call
|
||
|
//----------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Returns the extended year on the Gregorian calendar as computed by
|
||
|
* <code>computeGregorianFields()</code>.
|
||
|
* @see #computeGregorianFields
|
||
|
*/
|
||
|
protected final int getGregorianYear() {
|
||
|
return gregorianYear;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the month (0-based) on the Gregorian calendar as computed by
|
||
|
* <code>computeGregorianFields()</code>.
|
||
|
* @see #computeGregorianFields
|
||
|
*/
|
||
|
protected final int getGregorianMonth() {
|
||
|
return gregorianMonth;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the day of year (1-based) on the Gregorian calendar as
|
||
|
* computed by <code>computeGregorianFields()</code>.
|
||
|
* @see #computeGregorianFields
|
||
|
*/
|
||
|
protected final int getGregorianDayOfYear() {
|
||
|
return gregorianDayOfYear;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the day of month (1-based) on the Gregorian calendar as
|
||
|
* computed by <code>computeGregorianFields()</code>.
|
||
|
* @see #computeGregorianFields
|
||
|
*/
|
||
|
protected final int getGregorianDayOfMonth() {
|
||
|
return gregorianDayOfMonth;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the number of fields defined by this calendar. Valid field
|
||
|
* arguments to <code>set()</code> and <code>get()</code> are
|
||
|
* <code>0..getFieldCount()-1</code>.
|
||
|
*/
|
||
|
public final int getFieldCount() {
|
||
|
return fields.length;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set a field to a value. Subclasses should use this method when
|
||
|
* computing fields. It sets the time stamp in the
|
||
|
* <code>stamp[]</code> array to <code>INTERNALLY_SET</code>. If a
|
||
|
* field that may not be set by subclasses is passed in, an
|
||
|
* <code>IllegalArgumentException</code> is thrown. This prevents
|
||
|
* subclasses from modifying fields that are intended to be
|
||
|
* calendar-system invariant.
|
||
|
*/
|
||
|
protected final void internalSet(int field, int value) {
|
||
|
if (((1 << field) & internalSetMask) == 0) {
|
||
|
throw new IllegalStateException("Subclass cannot set " +
|
||
|
fieldName(field));
|
||
|
}
|
||
|
fields[field] = value;
|
||
|
stamp[field] = INTERNALLY_SET;
|
||
|
}
|
||
|
|
||
|
private static final int[][] GREGORIAN_MONTH_COUNT = {
|
||
|
//len len2 st st2
|
||
|
{ 31, 31, 0, 0 }, // Jan
|
||
|
{ 28, 29, 31, 31 }, // Feb
|
||
|
{ 31, 31, 59, 60 }, // Mar
|
||
|
{ 30, 30, 90, 91 }, // Apr
|
||
|
{ 31, 31, 120, 121 }, // May
|
||
|
{ 30, 30, 151, 152 }, // Jun
|
||
|
{ 31, 31, 181, 182 }, // Jul
|
||
|
{ 31, 31, 212, 213 }, // Aug
|
||
|
{ 30, 30, 243, 244 }, // Sep
|
||
|
{ 31, 31, 273, 274 }, // Oct
|
||
|
{ 30, 30, 304, 305 }, // Nov
|
||
|
{ 31, 31, 334, 335 } // Dec
|
||
|
// len length of month
|
||
|
// len2 length of month in a leap year
|
||
|
// st days in year before start of month
|
||
|
// st2 days in year before month in leap year
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Determines if the given year is a leap year. Returns true if the
|
||
|
* given year is a leap year.
|
||
|
* @param year the given year.
|
||
|
* @return true if the given year is a leap year; false otherwise.
|
||
|
*/
|
||
|
protected static final boolean isGregorianLeapYear(int year) {
|
||
|
return (year%4 == 0) && ((year%100 != 0) || (year%400 == 0));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the length of a month of the Gregorian calendar.
|
||
|
* @param y the extended year
|
||
|
* @param m the 0-based month number
|
||
|
* @return the number of days in the given month
|
||
|
*/
|
||
|
protected static final int gregorianMonthLength(int y, int m) {
|
||
|
return GREGORIAN_MONTH_COUNT[m][isGregorianLeapYear(y)?1:0];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the length of a previous month of the Gregorian calendar.
|
||
|
* @param y the extended year
|
||
|
* @param m the 0-based month number
|
||
|
* @return the number of days in the month previous to the given month
|
||
|
*/
|
||
|
protected static final int gregorianPreviousMonthLength(int y, int m) {
|
||
|
return (m > 0) ? gregorianMonthLength(y, m-1) : 31;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Divide two long integers, returning the floor of the quotient.
|
||
|
* <p>
|
||
|
* Unlike the built-in division, this is mathematically well-behaved.
|
||
|
* E.g., <code>-1/4</code> => 0
|
||
|
* but <code>floorDivide(-1,4)</code> => -1.
|
||
|
* @param numerator the numerator
|
||
|
* @param denominator a divisor which must be > 0
|
||
|
* @return the floor of the quotient.
|
||
|
*/
|
||
|
protected static final long floorDivide(long numerator, long denominator) {
|
||
|
// We do this computation in order to handle
|
||
|
// a numerator of Long.MIN_VALUE correctly
|
||
|
return (numerator >= 0) ?
|
||
|
numerator / denominator :
|
||
|
((numerator + 1) / denominator) - 1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Divide two integers, returning the floor of the quotient.
|
||
|
* <p>
|
||
|
* Unlike the built-in division, this is mathematically well-behaved.
|
||
|
* E.g., <code>-1/4</code> => 0
|
||
|
* but <code>floorDivide(-1,4)</code> => -1.
|
||
|
* @param numerator the numerator
|
||
|
* @param denominator a divisor which must be > 0
|
||
|
* @return the floor of the quotient.
|
||
|
*/
|
||
|
protected static final int floorDivide(int numerator, int denominator) {
|
||
|
// We do this computation in order to handle
|
||
|
// a numerator of Integer.MIN_VALUE correctly
|
||
|
return (numerator >= 0) ?
|
||
|
numerator / denominator :
|
||
|
((numerator + 1) / denominator) - 1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Divide two integers, returning the floor of the quotient, and
|
||
|
* the modulus remainder.
|
||
|
* <p>
|
||
|
* Unlike the built-in division, this is mathematically well-behaved.
|
||
|
* E.g., <code>-1/4</code> => 0 and <code>-1%4</code> => -1,
|
||
|
* but <code>floorDivide(-1,4)</code> => -1 with <code>remainder[0]</code> => 3.
|
||
|
* @param numerator the numerator
|
||
|
* @param denominator a divisor which must be > 0
|
||
|
* @param remainder an array of at least one element in which the value
|
||
|
* <code>numerator mod denominator</code> is returned. Unlike <code>numerator
|
||
|
* % denominator</code>, this will always be non-negative.
|
||
|
* @return the floor of the quotient.
|
||
|
*/
|
||
|
protected static final int floorDivide(int numerator, int denominator, int[] remainder) {
|
||
|
if (numerator >= 0) {
|
||
|
remainder[0] = numerator % denominator;
|
||
|
return numerator / denominator;
|
||
|
}
|
||
|
int quotient = ((numerator + 1) / denominator) - 1;
|
||
|
remainder[0] = numerator - (quotient * denominator);
|
||
|
return quotient;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Divide two integers, returning the floor of the quotient, and
|
||
|
* the modulus remainder.
|
||
|
* <p>
|
||
|
* Unlike the built-in division, this is mathematically well-behaved.
|
||
|
* E.g., <code>-1/4</code> => 0 and <code>-1%4</code> => -1,
|
||
|
* but <code>floorDivide(-1,4)</code> => -1 with <code>remainder[0]</code> => 3.
|
||
|
* @param numerator the numerator
|
||
|
* @param denominator a divisor which must be > 0
|
||
|
* @param remainder an array of at least one element in which the value
|
||
|
* <code>numerator mod denominator</code> is returned. Unlike <code>numerator
|
||
|
* % denominator</code>, this will always be non-negative.
|
||
|
* @return the floor of the quotient.
|
||
|
*/
|
||
|
protected static final int floorDivide(long numerator, int denominator, int[] remainder) {
|
||
|
if (numerator >= 0) {
|
||
|
remainder[0] = (int)(numerator % denominator);
|
||
|
return (int)(numerator / denominator);
|
||
|
}
|
||
|
int quotient = (int)(((numerator + 1) / denominator) - 1);
|
||
|
remainder[0] = (int)(numerator - ((long)quotient * denominator));
|
||
|
return quotient;
|
||
|
}
|
||
|
|
||
|
private static final String[] FIELD_NAME = {
|
||
|
"ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
|
||
|
"DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
|
||
|
"DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
|
||
|
"MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
|
||
|
"DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
|
||
|
"JULIAN_DAY", "MILLISECONDS_IN_DAY", "IS_LEAP_MONTH", "ORDINAL_MONTH"
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns a string name for a field, for debugging and exceptions.
|
||
|
*/
|
||
|
protected String fieldName(int field) {
|
||
|
try {
|
||
|
return FIELD_NAME[field];
|
||
|
} catch (ArrayIndexOutOfBoundsException e) {
|
||
|
return "Field " + field;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts time as milliseconds to Julian day.
|
||
|
* @param millis the given milliseconds.
|
||
|
* @return the Julian day number.
|
||
|
*/
|
||
|
protected static final int millisToJulianDay(long millis) {
|
||
|
return (int) (EPOCH_JULIAN_DAY + floorDivide(millis, ONE_DAY));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts Julian day to time as milliseconds.
|
||
|
* @param julian the given Julian day number.
|
||
|
* @return time as milliseconds.
|
||
|
*/
|
||
|
protected static final long julianDayToMillis(int julian) {
|
||
|
return (julian - EPOCH_JULIAN_DAY) * ONE_DAY;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the day of week, from SUNDAY to SATURDAY, given a Julian day.
|
||
|
*/
|
||
|
protected static final int julianDayToDayOfWeek(int julian) {
|
||
|
// If julian is negative, then julian%7 will be negative, so we adjust
|
||
|
// accordingly. Julian day 0 is Monday.
|
||
|
int dayOfWeek = (julian + MONDAY) % 7;
|
||
|
if (dayOfWeek < SUNDAY) {
|
||
|
dayOfWeek += 7;
|
||
|
}
|
||
|
return dayOfWeek;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the current milliseconds without recomputing.
|
||
|
*/
|
||
|
protected final long internalGetTimeInMillis() {
|
||
|
return time;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the calendar type name string for this Calendar object.
|
||
|
* The returned string is the legacy ICU calendar attribute value,
|
||
|
* for example, "gregorian" or "japanese".
|
||
|
*
|
||
|
* <p>See type="old type name" for the calendar attribute of locale IDs
|
||
|
* at http://www.unicode.org/reports/tr35/#Key_Type_Definitions
|
||
|
*
|
||
|
* @return legacy calendar type name string
|
||
|
*/
|
||
|
public String getType() {
|
||
|
return "unknown";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns if two digit representation of year in this calendar type
|
||
|
* customarily implies a default century (i.e. 03 -> 2003).
|
||
|
* The default implementation returns <code>true</code>. A subclass may
|
||
|
* return <code>false</code> if such practice is not applicable (for example,
|
||
|
* Chinese calendar and Japanese calendar).
|
||
|
*
|
||
|
* @return <code>true</code> if this calendar has a default century.
|
||
|
* @deprecated This API is ICU internal only.
|
||
|
* @hide original deprecated declaration
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public boolean haveDefaultCentury() {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// -------- BEGIN ULocale boilerplate --------
|
||
|
|
||
|
/**
|
||
|
* <strong>[icu]</strong> Returns the locale that was used to create this object, or null.
|
||
|
* This may may differ from the locale requested at the time of
|
||
|
* this object's creation. For example, if an object is created
|
||
|
* for locale <tt>en_US_CALIFORNIA</tt>, the actual data may be
|
||
|
* drawn from <tt>en</tt> (the <i>actual</i> locale), and
|
||
|
* <tt>en_US</tt> may be the most specific locale that exists (the
|
||
|
* <i>valid</i> locale).
|
||
|
*
|
||
|
* <p>Note: This method will be implemented in ICU 3.0; ICU 2.8
|
||
|
* contains a partial preview implementation. The <i>actual</i>
|
||
|
* locale is returned correctly, but the <i>valid</i> locale is
|
||
|
* not, in most cases.
|
||
|
* @param type type of information requested, either {@link
|
||
|
* android.icu.util.ULocale#VALID_LOCALE} or {@link
|
||
|
* android.icu.util.ULocale#ACTUAL_LOCALE}.
|
||
|
* @return the information specified by <i>type</i>, or null if
|
||
|
* this object was not constructed from locale data.
|
||
|
* @see android.icu.util.ULocale
|
||
|
* @see android.icu.util.ULocale#VALID_LOCALE
|
||
|
* @see android.icu.util.ULocale#ACTUAL_LOCALE
|
||
|
* @hide draft / provisional / internal are hidden on Android
|
||
|
*/
|
||
|
@android.compat.annotation.UnsupportedAppUsage
|
||
|
public final ULocale getLocale(ULocale.Type type) {
|
||
|
return type == ULocale.ACTUAL_LOCALE ?
|
||
|
this.actualLocale : this.validLocale;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set information about the locales that were used to create this
|
||
|
* object. If the object was not constructed from locale data,
|
||
|
* both arguments should be set to null. Otherwise, neither
|
||
|
* should be null. The actual locale must be at the same level or
|
||
|
* less specific than the valid locale. This method is intended
|
||
|
* for use by factories or other entities that create objects of
|
||
|
* this class.
|
||
|
* @param valid the most specific locale containing any resource
|
||
|
* data, or null
|
||
|
* @param actual the locale containing data used to construct this
|
||
|
* object, or null
|
||
|
* @see android.icu.util.ULocale
|
||
|
* @see android.icu.util.ULocale#VALID_LOCALE
|
||
|
* @see android.icu.util.ULocale#ACTUAL_LOCALE
|
||
|
*/
|
||
|
final void setLocale(ULocale valid, ULocale actual) {
|
||
|
// Change the following to an assertion later
|
||
|
if ((valid == null) != (actual == null)) {
|
||
|
///CLOVER:OFF
|
||
|
throw new IllegalArgumentException();
|
||
|
///CLOVER:ON
|
||
|
}
|
||
|
// Another check we could do is that the actual locale is at
|
||
|
// the same level or less specific than the valid locale.
|
||
|
this.validLocale = valid;
|
||
|
this.actualLocale = actual;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The most specific locale containing any resource data, or null.
|
||
|
* @see android.icu.util.ULocale
|
||
|
*/
|
||
|
private ULocale validLocale;
|
||
|
|
||
|
/**
|
||
|
* The locale containing data used to construct this object, or
|
||
|
* null.
|
||
|
* @see android.icu.util.ULocale
|
||
|
*/
|
||
|
private ULocale actualLocale;
|
||
|
|
||
|
// -------- END ULocale boilerplate --------
|
||
|
}
|