src/share/classes/java/util/Calendar.java
Print this page
rev 5615 : 6336885: RFE: Locale Data Deployment Enhancements
4609153: Provide locale data for Indic locales
5104387: Support for gl_ES locale (galician language)
6337471: desktop/system locale preferences support
7056139: (cal) SPI support for locale-dependent Calendar parameters
7058206: Provide CalendarData SPI for week params and display field value names
7073852: Support multiple scripts for digits and decimal symbols per locale
7079560: [Fmt-Da] Context dependent month names support in SimpleDateFormat
7171324: getAvailableLocales() of locale sensitive services should return the actual availability of locales
7151414: (cal) Support calendar type identification
7168528: LocaleServiceProvider needs to be aware of Locale extensions
7171372: (cal) locale's default Calendar should be created if unknown calendar is specified
Summary: JEP 127: Improve Locale Data Packaging and Adopt Unicode CLDR Data (part 1 w/o Jigsaw. by Naoto Sato and Masayoshi Okutsu)
@@ -51,13 +51,15 @@
import java.security.ProtectionDomain;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.spi.CalendarDataProvider;
import sun.util.BuddhistCalendar;
+import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.calendar.ZoneInfo;
-import sun.util.resources.LocaleData;
+import sun.util.locale.provider.CalendarDataUtility;
/**
* The <code>Calendar</code> class is an abstract class that provides methods
* for converting between a specific instant in time and a set of {@link
* #fields calendar fields} such as <code>YEAR</code>, <code>MONTH</code>,
@@ -705,36 +707,94 @@
/**
* A style specifier for {@link #getDisplayNames(int, int, Locale)
* getDisplayNames} indicating names in all styles, such as
* "January" and "Jan".
*
+ * @see #SHORT_FORMAT
+ * @see #LONG_FORMAT
+ * @see #SHORT_STANDALONE
+ * @see #LONG_STANDALONE
* @see #SHORT
* @see #LONG
* @since 1.6
*/
public static final int ALL_STYLES = 0;
+ static final int STANDALONE_MASK = 0x8000;
+
/**
* A style specifier for {@link #getDisplayName(int, int, Locale)
* getDisplayName} and {@link #getDisplayNames(int, int, Locale)
- * getDisplayNames} indicating a short name, such as "Jan".
+ * getDisplayNames} equivalent to {@link #SHORT_FORMAT}.
*
+ * @see #SHORT_STANDALONE
* @see #LONG
* @since 1.6
*/
public static final int SHORT = 1;
/**
* A style specifier for {@link #getDisplayName(int, int, Locale)
* getDisplayName} and {@link #getDisplayNames(int, int, Locale)
- * getDisplayNames} indicating a long name, such as "January".
+ * getDisplayNames} equivalent to {@link #LONG_FORMAT}.
*
+ * @see #LONG_STANDALONE
* @see #SHORT
* @since 1.6
*/
public static final int LONG = 2;
+ /**
+ * A style specifier for {@link #getDisplayName(int, int, Locale)
+ * getDisplayName} and {@link #getDisplayNames(int, int, Locale)
+ * getDisplayNames} indicating a short name used for format.
+ *
+ * @see #SHORT_STANDALONE
+ * @see #LONG_FORMAT
+ * @see #LONG_STANDALONE
+ * @since 1.8
+ */
+ public static final int SHORT_FORMAT = 1;
+
+ /**
+ * A style specifier for {@link #getDisplayName(int, int, Locale)
+ * getDisplayName} and {@link #getDisplayNames(int, int, Locale)
+ * getDisplayNames} indicating a long name used for format.
+ *
+ * @see #LONG_STANDALONE
+ * @see #SHORT_FORMAT
+ * @see #SHORT_STANDALONE
+ * @since 1.8
+ */
+ public static final int LONG_FORMAT = 2;
+
+ /**
+ * A style specifier for {@link #getDisplayName(int, int, Locale)
+ * getDisplayName} and {@link #getDisplayNames(int, int, Locale)
+ * getDisplayNames} indicating a short name used independently,
+ * such as a month abbreviation as calendar headers.
+ *
+ * @see #SHORT_FORMAT
+ * @see #LONG_FORMAT
+ * @see #LONG_STANDALONE
+ * @since 1.8
+ */
+ public static final int SHORT_STANDALONE = SHORT | STANDALONE_MASK;
+
+ /**
+ * A style specifier for {@link #getDisplayName(int, int, Locale)
+ * getDisplayName} and {@link #getDisplayNames(int, int, Locale)
+ * getDisplayNames} indicating a long name used independently,
+ * such as a month name as calendar headers.
+ *
+ * @see #LONG_FORMAT
+ * @see #SHORT_FORMAT
+ * @see #SHORT_STANDALONE
+ * @since 1.8
+ */
+ public static final int LONG_STANDALONE = LONG | STANDALONE_MASK;
+
// Internal notes:
// Calendar contains two kinds of time representations: current "time" in
// milliseconds, and a set of calendar "fields" representing the current time.
// The two representations are usually in sync, but can get out of sync
// as follows.
@@ -748,10 +808,11 @@
* The calendar field values for the currently set time for this calendar.
* This is an array of <code>FIELD_COUNT</code> integers, with index values
* <code>ERA</code> through <code>DST_OFFSET</code>.
* @serial
*/
+ @SuppressWarnings("ProtectedField")
protected int fields[];
/**
* The flags which tell if a specified calendar field for the calendar is set.
* A new object has no fields set. After the first call to a method
@@ -758,10 +819,11 @@
* which generates the fields, they all remain set after that.
* This is an array of <code>FIELD_COUNT</code> booleans, with index values
* <code>ERA</code> through <code>DST_OFFSET</code>.
* @serial
*/
+ @SuppressWarnings("ProtectedField")
protected boolean isSet[];
/**
* Pseudo-time-stamps which specify when each field was set. There
* are two special values, UNSET and COMPUTED. Values from
@@ -773,27 +835,30 @@
* The currently set time for this calendar, expressed in milliseconds after
* January 1, 1970, 0:00:00 GMT.
* @see #isTimeSet
* @serial
*/
+ @SuppressWarnings("ProtectedField")
protected 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
*/
+ @SuppressWarnings("ProtectedField")
protected 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
*/
+ @SuppressWarnings("ProtectedField")
protected boolean areFieldsSet;
/**
* True if all fields have been set.
* @serial
@@ -908,10 +973,11 @@
// Proclaim serialization compatibility with JDK 1.1
static final long serialVersionUID = -1807547505821590642L;
// Mask values for calendar fields
+ @SuppressWarnings("PointlessBitwiseExpression")
final static int ERA_MASK = (1 << ERA);
final static int YEAR_MASK = (1 << YEAR);
final static int MONTH_MASK = (1 << MONTH);
final static int WEEK_OF_YEAR_MASK = (1 << WEEK_OF_YEAR);
final static int WEEK_OF_MONTH_MASK = (1 << WEEK_OF_MONTH);
@@ -1016,31 +1082,42 @@
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{
Calendar cal = null;
+ if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
- if (caltype == null) {
- // Calendar type is not specified.
- // If the specified locale is a Thai locale,
- // returns a BuddhistCalendar instance.
- if ("th".equals(aLocale.getLanguage())
- && ("TH".equals(aLocale.getCountry()))) {
+ if (caltype != null) {
+ switch (caltype) {
+ case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
- } else {
+ break;
+ case "japanese":
+ cal = new JapaneseImperialCalendar(zone, aLocale);
+ break;
+ case "gregory":
cal = new GregorianCalendar(zone, aLocale);
+ break;
}
- } else if (caltype.equals("japanese")) {
- cal = new JapaneseImperialCalendar(zone, aLocale);
- } else if (caltype.equals("buddhist")) {
+ }
+ }
+ if (cal == null) {
+ // If no known calendar type is explicitly specified,
+ // perform the traditional way to create a Calendar:
+ // create a BuddhistCalendar for th_TH locale,
+ // a JapaneseImperialCalendar for ja_JP_JP locale, or
+ // a GregorianCalendar for any other locales.
+ // NOTE: The language, country and variant strings are interned.
+ if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
+ } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
+ && aLocale.getCountry() == "JP") {
+ cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
- // Unsupported calendar type.
- // Use Gregorian calendar as a fallback.
cal = new GregorianCalendar(zone, aLocale);
}
-
+ }
return cal;
}
/**
* Returns an array of all locales for which the <code>getInstance</code>
@@ -1391,14 +1468,16 @@
*
* @param field
* the calendar field for which the string representation
* is returned
* @param style
- * the style applied to the string representation; one of
- * {@link #SHORT} or {@link #LONG}.
+ * the style applied to the string representation; one of {@link
+ * #SHORT_FORMAT} ({@link #SHORT}), {@link #SHORT_STANDALONE},
+ * {@link #LONG_FORMAT} ({@link #LONG}) or {@link #LONG_STANDALONE}.
* @param locale
* the locale for the string representation
+ * (any calendar types specified by {@code locale} are ignored)
* @return the string representation of the given
* <code>field</code> in the given <code>style</code>, or
* <code>null</code> if no string representation is
* applicable.
* @exception IllegalArgumentException
@@ -1408,15 +1487,22 @@
* @exception NullPointerException
* if <code>locale</code> is null
* @since 1.6
*/
public String getDisplayName(int field, int style, Locale locale) {
- if (!checkDisplayNameParams(field, style, ALL_STYLES, LONG, locale,
+ if (!checkDisplayNameParams(field, style, SHORT, LONG, locale,
ERA_MASK|MONTH_MASK|DAY_OF_WEEK_MASK|AM_PM_MASK)) {
return null;
}
+ // the standalone styles are supported only through CalendarDataProviders.
+ if (isStandaloneStyle(style)) {
+ return CalendarDataUtility.retrieveFieldValueName(getCalendarType(),
+ field, get(field),
+ style, locale);
+ }
+
DateFormatSymbols symbols = DateFormatSymbols.getInstance(locale);
String[] strings = getFieldStrings(field, style, symbols);
if (strings != null) {
int fieldValue = get(field);
if (fieldValue < strings.length) {
@@ -1451,12 +1537,13 @@
* and {@link DateFormatSymbols#getMonths()}.
*
* @param field
* the calendar field for which the display names are returned
* @param style
- * the style applied to the display names; one of {@link
- * #SHORT}, {@link #LONG}, or {@link #ALL_STYLES}.
+ * the style applied to the string representation; one of {@link
+ * #SHORT_FORMAT} ({@link #SHORT}), {@link #SHORT_STANDALONE},
+ * {@link #LONG_FORMAT} ({@link #LONG}) or {@link #LONG_STANDALONE}.
* @param locale
* the locale for the display names
* @return a <code>Map</code> containing all display names in
* <code>style</code> and <code>locale</code> and their
* field values, or <code>null</code> if no display names
@@ -1472,27 +1559,13 @@
public Map<String, Integer> getDisplayNames(int field, int style, Locale locale) {
if (!checkDisplayNameParams(field, style, ALL_STYLES, LONG, locale,
ERA_MASK|MONTH_MASK|DAY_OF_WEEK_MASK|AM_PM_MASK)) {
return null;
}
-
- // ALL_STYLES
- if (style == ALL_STYLES) {
- Map<String,Integer> shortNames = getDisplayNamesImpl(field, SHORT, locale);
- if (field == ERA || field == AM_PM) {
- return shortNames;
+ if (style == ALL_STYLES || isStandaloneStyle(style)) {
+ return CalendarDataUtility.retrieveFieldValueNames(getCalendarType(), field, style, locale);
}
- Map<String,Integer> longNames = getDisplayNamesImpl(field, LONG, locale);
- if (shortNames == null) {
- return longNames;
- }
- if (longNames != null) {
- shortNames.putAll(longNames);
- }
- return shortNames;
- }
-
// SHORT or LONG
return getDisplayNamesImpl(field, style, locale);
}
private Map<String,Integer> getDisplayNamesImpl(int field, int style, Locale locale) {
@@ -1511,33 +1584,35 @@
return null;
}
boolean checkDisplayNameParams(int field, int style, int minStyle, int maxStyle,
Locale locale, int fieldMask) {
+ int baseStyle = getBaseStyle(style); // Ignore the standalone mask
if (field < 0 || field >= fields.length ||
- style < minStyle || style > maxStyle) {
+ baseStyle < minStyle || baseStyle > maxStyle) {
throw new IllegalArgumentException();
}
if (locale == null) {
throw new NullPointerException();
}
return isFieldSet(fieldMask, field);
}
private String[] getFieldStrings(int field, int style, DateFormatSymbols symbols) {
+ int baseStyle = getBaseStyle(style); // ignore the standalone mask
String[] strings = null;
switch (field) {
case ERA:
strings = symbols.getEras();
break;
case MONTH:
- strings = (style == LONG) ? symbols.getMonths() : symbols.getShortMonths();
+ strings = (baseStyle == LONG) ? symbols.getMonths() : symbols.getShortMonths();
break;
case DAY_OF_WEEK:
- strings = (style == LONG) ? symbols.getWeekdays() : symbols.getShortWeekdays();
+ strings = (baseStyle == LONG) ? symbols.getWeekdays() : symbols.getShortWeekdays();
break;
case AM_PM:
strings = symbols.getAmPmStrings();
break;
@@ -1552,12 +1627,13 @@
* calendar field values. Then, the {@link #computeFields()} method is
* called to calculate all calendar field values.
*/
protected void complete()
{
- if (!isTimeSet)
+ if (!isTimeSet) {
updateTime();
+ }
if (!areFieldsSet || !areAllFieldsSet) {
computeFields(); // fills in unset fields
areAllFieldsSet = areFieldsSet = true;
}
}
@@ -1687,11 +1763,11 @@
/**
* Returns whether the specified <code>field</code> is on in the
* <code>fieldMask</code>.
*/
- static final boolean isFieldSet(int fieldMask, int field) {
+ static boolean isFieldSet(int fieldMask, int field) {
return (fieldMask & (1 << field)) != 0;
}
/**
* Returns a field mask indicating which calendar field values
@@ -1863,24 +1939,53 @@
}
return fieldMask;
}
+ int getBaseStyle(int style) {
+ return style & ~STANDALONE_MASK;
+ }
+
+ boolean isStandaloneStyle(int style) {
+ return (style & STANDALONE_MASK) != 0;
+ }
+
/**
* Returns the pseudo-time-stamp for two fields, given their
* individual pseudo-time-stamps. If either of the fields
* is unset, then the aggregate is unset. Otherwise, the
* aggregate is the later of the two stamps.
*/
- private static final int aggregateStamp(int stamp_a, int stamp_b) {
+ private static int aggregateStamp(int stamp_a, int stamp_b) {
if (stamp_a == UNSET || stamp_b == UNSET) {
return UNSET;
}
return (stamp_a > stamp_b) ? stamp_a : stamp_b;
}
/**
+ * Returns the calendar type of this {@code Calendar}. Calendar types are
+ * defined by the <em>Unicode Locale Data Markup Language (LDML)</em>
+ * specification.
+ *
+ * <p>The default implementation of this method returns the class name of
+ * this {@code Calendar} instance. Any subclasses that implement
+ * LDML-defined calendar systems should override this method to return
+ * appropriate calendar types.
+ *
+ * @return the LDML-defined calendar type or the class name of this
+ * {@code Calendar} instance
+ * @since 1.8
+ * @see <a href="Locale.html#def_extensions">Locale extensions</a>
+ * @see Locale.Builder#setLocale(Locale)
+ * @see Locale.Builder#setUnicodeLocaleKeyword(String, String)
+ */
+ public String getCalendarType() {
+ return this.getClass().getName();
+ }
+
+ /**
* Compares this <code>Calendar</code> to the specified
* <code>Object</code>. The result is <code>true</code> if and only if
* the argument is a <code>Calendar</code> object of the same calendar
* system that represents the same time value (millisecond offset from the
* <a href="#Epoch">Epoch</a>) under the same
@@ -1898,13 +2003,16 @@
*
* @param obj the object to compare with.
* @return <code>true</code> if this object is equal to <code>obj</code>;
* <code>false</code> otherwise.
*/
+ @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+ @Override
public boolean equals(Object obj) {
- if (this == obj)
+ if (this == obj) {
return true;
+ }
try {
Calendar that = (Calendar)obj;
return compareTo(getMillisOf(that)) == 0 &&
lenient == that.lenient &&
firstDayOfWeek == that.firstDayOfWeek &&
@@ -1922,10 +2030,11 @@
* Returns a hash code for this calendar.
*
* @return a hash code value for this object.
* @since 1.2
*/
+ @Override
public int hashCode() {
// 'otheritems' represents the hash code for the previous versions.
int otheritems = (lenient ? 1 : 0)
| (firstDayOfWeek << 1)
| (minimalDaysInFirstWeek << 4)
@@ -1993,10 +2102,11 @@
* @exception IllegalArgumentException if the time value of the
* specified <code>Calendar</code> object can't be obtained due to
* any invalid calendar values.
* @since 1.5
*/
+ @Override
public int compareTo(Calendar anotherCalendar) {
return compareTo(getMillisOf(anotherCalendar));
}
/**
@@ -2466,12 +2576,13 @@
Calendar work = (Calendar)this.clone();
work.setLenient(true);
// if we're counting weeks, set the day of the week to Sunday. We know the
// last week of a month or year will contain the first day of the week.
- if (field == WEEK_OF_YEAR || field == WEEK_OF_MONTH)
+ if (field == WEEK_OF_YEAR || field == WEEK_OF_MONTH) {
work.set(DAY_OF_WEEK, firstDayOfWeek);
+ }
// now try each value from getLeastMaximum() to getMaximum() 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
int result = fieldValue;
@@ -2492,10 +2603,11 @@
/**
* Creates and returns a copy of this object.
*
* @return a copy of this object.
*/
+ @Override
public Object clone()
{
try {
Calendar other = (Calendar) super.clone();
@@ -2529,11 +2641,11 @@
* @param field the calendar field
* @return the calendar field name
* @exception IndexOutOfBoundsException if <code>field</code> is negative,
* equal to or greater then <code>FIELD_COUNT</code>.
*/
- static final String getFieldName(int field) {
+ static String getFieldName(int field) {
return FIELD_NAME[field];
}
/**
* Return a string representation of this calendar. This method
@@ -2541,10 +2653,11 @@
* 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() {
// NOTE: BuddhistCalendar.toString() interprets the string
// produced by this method so that the Gregorian year number
// is substituted by its B.E. year value. It relies on
// "...,YEAR=<year>,..." or "...,YEAR=?,...".
@@ -2565,11 +2678,11 @@
return buffer.toString();
}
// =======================privates===============================
- private static final void appendValue(StringBuilder sb, String item, boolean valid, long value) {
+ private static void appendValue(StringBuilder sb, String item, boolean valid, long value) {
sb.append(item).append('=');
if (valid) {
sb.append(value);
} else {
sb.append('?');
@@ -2585,14 +2698,16 @@
private void setWeekCountData(Locale desiredLocale)
{
/* try to get the Locale data from the cache */
int[] data = cachedLocaleData.get(desiredLocale);
if (data == null) { /* cache miss */
- ResourceBundle bundle = LocaleData.getCalendarData(desiredLocale);
+ LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(CalendarDataProvider.class, desiredLocale);
+ CalendarDataProvider provider = adapter.getCalendarDataProvider();
data = new int[2];
- data[0] = Integer.parseInt(bundle.getString("firstDayOfWeek"));
- data[1] = Integer.parseInt(bundle.getString("minimalDaysInFirstWeek"));
+ data[0] = provider.getFirstDayOfWeek(desiredLocale);
+ data[1] = provider.getMinimalDaysInFirstWeek(desiredLocale);
+ assert data[0] != 0 && data[1] != 0;
cachedLocaleData.putIfAbsent(desiredLocale, data);
}
firstDayOfWeek = data[0];
minimalDaysInFirstWeek = data[1];
}
@@ -2612,11 +2727,11 @@
private int compareTo(long t) {
long thisTime = getMillisOf(this);
return (thisTime > t) ? 1 : (thisTime == t) ? 0 : -1;
}
- private static final long getMillisOf(Calendar calendar) {
+ private static long getMillisOf(Calendar calendar) {
if (calendar.isTimeSet) {
return calendar.time;
}
Calendar cal = (Calendar) calendar.clone();
cal.setLenient(true);
@@ -2625,11 +2740,11 @@
/**
* Adjusts the stamp[] values before nextStamp overflow. nextStamp
* is set to the next stamp value upon the return.
*/
- private final void adjustStamp() {
+ private void adjustStamp() {
int max = MINIMUM_USER_STAMP;
int newStamp = MINIMUM_USER_STAMP;
for (;;) {
int min = Integer.MAX_VALUE;
@@ -2750,11 +2865,13 @@
perms.add(perm);
INSTANCE = new AccessControlContext(new ProtectionDomain[] {
new ProtectionDomain(null, perms)
});
}
+ private CalendarAccessControlContext() {
}
+ }
/**
* Reconstitutes this object from a stream (i.e., deserialize it).
*/
private void readObject(ObjectInputStream stream)
@@ -2769,26 +2886,32 @@
// fields[], isSet[], isTimeSet, and areFieldsSet may not be
// streamed out anymore. We expect 'time' to be correct.
if (serialVersionOnStream >= 2)
{
isTimeSet = true;
- if (fields == null) fields = new int[FIELD_COUNT];
- if (isSet == null) isSet = new boolean[FIELD_COUNT];
+ if (fields == null) {
+ fields = new int[FIELD_COUNT];
}
+ if (isSet == null) {
+ isSet = new boolean[FIELD_COUNT];
+ }
+ }
else if (serialVersionOnStream >= 0)
{
- for (int i=0; i<FIELD_COUNT; ++i)
+ for (int i=0; i<FIELD_COUNT; ++i) {
stamp[i] = isSet[i] ? COMPUTED : UNSET;
}
+ }
serialVersionOnStream = currentSerialVersion;
// If there's a ZoneInfo object, use it for zone.
ZoneInfo zi = null;
try {
zi = AccessController.doPrivileged(
new PrivilegedExceptionAction<ZoneInfo>() {
+ @Override
public ZoneInfo run() throws Exception {
return (ZoneInfo) input.readObject();
}
},
CalendarAccessControlContext.INSTANCE);