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);