Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
      4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      5  *
      6  * This code is free software; you can redistribute it and/or modify it
      7  * under the terms of the GNU General Public License version 2 only, as
      8  * published by the Free Software Foundation.  Oracle designates this
      9  * particular file as subject to the "Classpath" exception as provided
     10  * by Oracle in the LICENSE file that accompanied this code.
     11  *
     12  * This code is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     15  * version 2 for more details (a copy is included in the LICENSE file that
     16  * accompanied this code).
     17  *
     18  * You should have received a copy of the GNU General Public License version
     19  * 2 along with this work; if not, write to the Free Software Foundation,
     20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     21  *
     22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     23  * or visit www.oracle.com if you need additional information or have any
     24  * questions.
     25  */
     26 
     27 /*
     28  * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
     29  * (C) Copyright IBM Corp. 1996 - All Rights Reserved
     30  *
     31  *   The original version of this source code and documentation is copyrighted
     32  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
     33  * materials are provided under terms of a License Agreement between Taligent
     34  * and Sun. This technology is protected by multiple US and International
     35  * patents. This notice and attribution to Taligent may not be removed.
     36  *   Taligent is a registered trademark of Taligent, Inc.
     37  *
     38  */
     39 
     40 package java.text;
     41 
     42 import java.io.IOException;
     43 import java.io.ObjectInputStream;
     44 import java.io.ObjectOutputStream;
     45 import java.io.Serializable;
     46 import java.lang.ref.SoftReference;
     47 import java.util.Arrays;
     48 import java.util.Locale;
     49 import java.util.Objects;
     50 import java.util.TimeZone;
     51 import java.util.concurrent.ConcurrentHashMap;
     52 import java.util.concurrent.ConcurrentMap;
     53 
     54 import libcore.icu.ICU;
     55 import libcore.icu.LocaleData;
     56 import libcore.icu.TimeZoneNames;
     57 
     58 /**
     59  * <code>DateFormatSymbols</code> is a public class for encapsulating
     60  * localizable date-time formatting data, such as the names of the
     61  * months, the names of the days of the week, and the time zone data.
     62  * <code>SimpleDateFormat</code> uses
     63  * <code>DateFormatSymbols</code> to encapsulate this information.
     64  *
     65  * <p>
     66  * Typically you shouldn't use <code>DateFormatSymbols</code> directly.
     67  * Rather, you are encouraged to create a date-time formatter with the
     68  * <code>DateFormat</code> class's factory methods: <code>getTimeInstance</code>,
     69  * <code>getDateInstance</code>, or <code>getDateTimeInstance</code>.
     70  * These methods automatically create a <code>DateFormatSymbols</code> for
     71  * the formatter so that you don't have to. After the
     72  * formatter is created, you may modify its format pattern using the
     73  * <code>setPattern</code> method. For more information about
     74  * creating formatters using <code>DateFormat</code>'s factory methods,
     75  * see {@link DateFormat}.
     76  *
     77  * <p>
     78  * If you decide to create a date-time formatter with a specific
     79  * format pattern for a specific locale, you can do so with:
     80  * <blockquote>
     81  * <pre>
     82  * new SimpleDateFormat(aPattern, DateFormatSymbols.getInstance(aLocale)).
     83  * </pre>
     84  * </blockquote>
     85  *
     86  * <p>
     87  * <code>DateFormatSymbols</code> objects are cloneable. When you obtain
     88  * a <code>DateFormatSymbols</code> object, feel free to modify the
     89  * date-time formatting data. For instance, you can replace the localized
     90  * date-time format pattern characters with the ones that you feel easy
     91  * to remember. Or you can change the representative cities
     92  * to your favorite ones.
     93  *
     94  * <p>
     95  * New <code>DateFormatSymbols</code> subclasses may be added to support
     96  * <code>SimpleDateFormat</code> for date-time formatting for additional locales.
     97 
     98  * @see          DateFormat
     99  * @see          SimpleDateFormat
    100  * @see          java.util.SimpleTimeZone
    101  * @author       Chen-Lieh Huang
    102  */
    103 public class DateFormatSymbols implements Serializable, Cloneable {
    104 
    105     // Android-changed: Removed reference to DateFormatSymbolsProvider but suggested getInstance()
    106     // be used instead in case Android supports it in future.
    107     /**
    108      * Construct a DateFormatSymbols object by loading format data from
    109      * resources for the default {@link java.util.Locale.Category#FORMAT FORMAT}
    110      * locale. It is recommended that the {@link #getInstance(Locale) getInstance} method is used
    111      * instead.
    112      * <p>This is equivalent to calling
    113      * {@link #DateFormatSymbols(Locale)
    114      *     DateFormatSymbols(Locale.getDefault(Locale.Category.FORMAT))}.
    115      * @see #getInstance()
    116      * @see java.util.Locale#getDefault(java.util.Locale.Category)
    117      * @see java.util.Locale.Category#FORMAT
    118      * @exception  java.util.MissingResourceException
    119      *             if the resources for the default locale cannot be
    120      *             found or cannot be loaded.
    121      */
    122     public DateFormatSymbols()
    123     {
    124         initializeData(Locale.getDefault(Locale.Category.FORMAT));
    125     }
    126 
    127     // Android-changed: Removed reference to DateFormatSymbolsProvider but suggested getInstance()
    128     // be used instead in case Android supports it in future.
    129     /**
    130      * Construct a DateFormatSymbols object by loading format data from
    131      * resources for the given locale. It is recommended that the
    132      * {@link #getInstance(Locale) getInstance} method is used instead.
    133      *
    134      * @param locale the desired locale
    135      * @see #getInstance(Locale)
    136      * @exception  java.util.MissingResourceException
    137      *             if the resources for the specified locale cannot be
    138      *             found or cannot be loaded.
    139      */
    140     public DateFormatSymbols(Locale locale)
    141     {
    142         initializeData(locale);
    143     }
    144 
    145     /**
    146      * Era strings. For example: "AD" and "BC".  An array of 2 strings,
    147      * indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
    148      * @serial
    149      */
    150     String eras[] = null;
    151 
    152     /**
    153      * Month strings. For example: "January", "February", etc.  An array
    154      * of 13 strings (some calendars have 13 months), indexed by
    155      * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
    156      * @serial
    157      */
    158     String months[] = null;
    159 
    160     /**
    161      * Short month strings. For example: "Jan", "Feb", etc.  An array of
    162      * 13 strings (some calendars have 13 months), indexed by
    163      * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
    164 
    165      * @serial
    166      */
    167     String shortMonths[] = null;
    168 
    169     /**
    170      * Weekday strings. For example: "Sunday", "Monday", etc.  An array
    171      * of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
    172      * <code>Calendar.MONDAY</code>, etc.
    173      * The element <code>weekdays[0]</code> is ignored.
    174      * @serial
    175      */
    176     String weekdays[] = null;
    177 
    178     /**
    179      * Short weekday strings. For example: "Sun", "Mon", etc.  An array
    180      * of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
    181      * <code>Calendar.MONDAY</code>, etc.
    182      * The element <code>shortWeekdays[0]</code> is ignored.
    183      * @serial
    184      */
    185     String shortWeekdays[] = null;
    186 
    187     /**
    188      * AM and PM strings. For example: "AM" and "PM".  An array of
    189      * 2 strings, indexed by <code>Calendar.AM</code> and
    190      * <code>Calendar.PM</code>.
    191      * @serial
    192      */
    193     String ampms[] = null;
    194 
    195     /**
    196      * Localized names of time zones in this locale.  This is a
    197      * two-dimensional array of strings of size <em>n</em> by <em>m</em>,
    198      * where <em>m</em> is at least 5.  Each of the <em>n</em> rows is an
    199      * entry containing the localized names for a single <code>TimeZone</code>.
    200      * Each such row contains (with <code>i</code> ranging from
    201      * 0..<em>n</em>-1):
    202      * <ul>
    203      * <li><code>zoneStrings[i][0]</code> - time zone ID</li>
    204      * <li><code>zoneStrings[i][1]</code> - long name of zone in standard
    205      * time</li>
    206      * <li><code>zoneStrings[i][2]</code> - short name of zone in
    207      * standard time</li>
    208      * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight
    209      * saving time</li>
    210      * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight
    211      * saving time</li>
    212      * </ul>
    213      * The zone ID is <em>not</em> localized; it's one of the valid IDs of
    214      * the {@link java.util.TimeZone TimeZone} class that are not
    215      * <a href="../java/util/TimeZone.html#CustomID">custom IDs</a>.
    216      * All other entries are localized names.
    217      * @see java.util.TimeZone
    218      * @serial
    219      */
    220     String zoneStrings[][] = null;
    221 
    222     /**
    223      * Indicates that zoneStrings is set externally with setZoneStrings() method.
    224      */
    225     transient boolean isZoneStringsSet = false;
    226 
    227     /**
    228      * Unlocalized date-time pattern characters. For example: 'y', 'd', etc.
    229      * All locales use the same these unlocalized pattern characters.
    230      *
    231      * Pretend to support 'L' and 'c' for now. It's meant for standalone weekday and
    232      * month names, but we just use the non-standalone versions for now.
    233      */
    234     static final String  patternChars = "GyMdkHmsSEDFwWahKzZYuXLc";
    235 
    236     static final int PATTERN_ERA                  =  0; // G
    237     static final int PATTERN_YEAR                 =  1; // y
    238     static final int PATTERN_MONTH                =  2; // M
    239     static final int PATTERN_DAY_OF_MONTH         =  3; // d
    240     static final int PATTERN_HOUR_OF_DAY1         =  4; // k
    241     static final int PATTERN_HOUR_OF_DAY0         =  5; // H
    242     static final int PATTERN_MINUTE               =  6; // m
    243     static final int PATTERN_SECOND               =  7; // s
    244     static final int PATTERN_MILLISECOND          =  8; // S
    245     static final int PATTERN_DAY_OF_WEEK          =  9; // E
    246     static final int PATTERN_DAY_OF_YEAR          = 10; // D
    247     static final int PATTERN_DAY_OF_WEEK_IN_MONTH = 11; // F
    248     static final int PATTERN_WEEK_OF_YEAR         = 12; // w
    249     static final int PATTERN_WEEK_OF_MONTH        = 13; // W
    250     static final int PATTERN_AM_PM                = 14; // a
    251     static final int PATTERN_HOUR1                = 15; // h
    252     static final int PATTERN_HOUR0                = 16; // K
    253     static final int PATTERN_ZONE_NAME            = 17; // z
    254     static final int PATTERN_ZONE_VALUE           = 18; // Z
    255     static final int PATTERN_WEEK_YEAR            = 19; // Y
    256     static final int PATTERN_ISO_DAY_OF_WEEK      = 20; // u
    257     static final int PATTERN_ISO_ZONE             = 21; // X
    258     static final int PATTERN_MONTH_STANDALONE     = 22; // L
    259     static final int PATTERN_STANDALONE_DAY_OF_WEEK = 23; // c
    260 
    261     /**
    262      * Localized date-time pattern characters. For example, a locale may
    263      * wish to use 'u' rather than 'y' to represent years in its date format
    264      * pattern strings.
    265      * This string must be exactly 18 characters long, with the index of
    266      * the characters described by <code>DateFormat.ERA_FIELD</code>,
    267      * <code>DateFormat.YEAR_FIELD</code>, etc.  Thus, if the string were
    268      * "Xz...", then localized patterns would use 'X' for era and 'z' for year.
    269      * @serial
    270      */
    271     String  localPatternChars = null;
    272 
    273     /**
    274      * The locale which is used for initializing this DateFormatSymbols object.
    275      *
    276      * @since 1.6
    277      * @serial
    278      */
    279     Locale locale = null;
    280 
    281     /* use serialVersionUID from JDK 1.1.4 for interoperability */
    282     static final long serialVersionUID = -5987973545549424702L;
    283 
    284     // the internal serial version which says which version was written
    285     // - 0 (default) for version up to JDK 1.1.4
    286     // - 1 Android version that contains a whole bunch of new fields.
    287     static final int currentSerialVersion = 1;
    288 
    289     /**
    290      * The version of the serialized data on the stream.  Possible values:
    291      * <ul>
    292      * <li><b>0</b> or not present on stream: JDK 1.1.4.
    293      * <li><b>1</b> Android:
    294      * </ul>
    295      * When streaming out this class, the most recent format
    296      * and the highest allowable <code>serialVersionOnStream</code>
    297      * is written.
    298      * @serial
    299      * @since JDK1.1.4
    300      */
    301     private int serialVersionOnStream = currentSerialVersion;
    302 
    303     /**
    304      * Tiny month strings; "J", "F", "M" etc.
    305      *
    306      * @serial
    307      */
    308     private String[] tinyMonths;
    309 
    310     /**
    311      * Tiny weekday strings: "M", "F", "W" etc.
    312      *
    313      * @serial
    314      */
    315     private String[] tinyWeekdays;
    316 
    317     /**
    318      * Standalone month strings; "January", "February", "March" etc.
    319      *
    320      * @serial
    321      */
    322     private String[] standAloneMonths;
    323 
    324     /**
    325      * Short standalone month strings: "Jan", "Feb", "Mar" etc.
    326      *
    327      * @serial
    328      */
    329     private String[] shortStandAloneMonths;
    330 
    331     /**
    332      * Tiny standalone month strings: "J", "F", "M" etc.
    333      *
    334      * @serial
    335      */
    336     private String[] tinyStandAloneMonths;
    337 
    338     /**
    339      * Standalone weekday strings; "Monday", "Tuesday", "Wednesday" etc.
    340      *
    341      * @serial
    342      */
    343     private String[] standAloneWeekdays;
    344 
    345     /**
    346      * Short standalone weekday strings; "Mon", "Tue", "Wed" etc.
    347      *
    348      * @serial
    349      */
    350     private String[] shortStandAloneWeekdays;
    351 
    352     /**
    353      * Tiny standalone weekday strings; "M", "T", "W" etc.
    354      *
    355      * @serial
    356      */
    357     private String[] tinyStandAloneWeekdays;
    358 
    359     // Android-changed: Removed reference to DateFormatSymbolsProvider.
    360     /**
    361      * Returns an array of all locales for which the
    362      * <code>getInstance</code> methods of this class can return
    363      * localized instances.
    364      *
    365      * @return An array of locales for which localized
    366      *         <code>DateFormatSymbols</code> instances are available.
    367      * @since 1.6
    368      */
    369     public static Locale[] getAvailableLocales() {
    370         // Android-changed: No support for DateFormatSymbolsProvider.
    371         return ICU.getAvailableLocales();
    372     }
    373 
    374     // Android-changed: Removed reference to DateFormatSymbolsProvider.
    375     /**
    376      * Gets the <code>DateFormatSymbols</code> instance for the default
    377      * locale.
    378      * <p>This is equivalent to calling {@link #getInstance(Locale)
    379      *     getInstance(Locale.getDefault(Locale.Category.FORMAT))}.
    380      * @see java.util.Locale#getDefault(java.util.Locale.Category)
    381      * @see java.util.Locale.Category#FORMAT
    382      * @return a <code>DateFormatSymbols</code> instance.
    383      * @since 1.6
    384      */
    385     public static final DateFormatSymbols getInstance() {
    386         return getInstance(Locale.getDefault(Locale.Category.FORMAT));
    387     }
    388 
    389     // Android-changed: Removed reference to DateFormatSymbolsProvider.
    390     /**
    391      * Gets the <code>DateFormatSymbols</code> instance for the specified
    392      * locale.
    393      * @param locale the given locale.
    394      * @return a <code>DateFormatSymbols</code> instance.
    395      * @exception NullPointerException if <code>locale</code> is null
    396      * @since 1.6
    397      */
    398     public static final DateFormatSymbols getInstance(Locale locale) {
    399         // Android-changed: Removed used of DateFormatSymbolsProvider.
    400         return (DateFormatSymbols) getCachedInstance(locale).clone();
    401     }
    402 
    403     /**
    404      * Returns a DateFormatSymbols provided by a provider or found in
    405      * the cache. Note that this method returns a cached instance,
    406      * not its clone. Therefore, the instance should never be given to
    407      * an application.
    408      */
    409     static final DateFormatSymbols getInstanceRef(Locale locale) {
    410         // Android-changed: Removed used of DateFormatSymbolsProvider.
    411         return getCachedInstance(locale);
    412     }
    413 
    414     /**
    415      * Returns a cached DateFormatSymbols if it's found in the
    416      * cache. Otherwise, this method returns a newly cached instance
    417      * for the given locale.
    418      */
    419     private static DateFormatSymbols getCachedInstance(Locale locale) {
    420         SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale);
    421         DateFormatSymbols dfs = null;
    422         if (ref == null || (dfs = ref.get()) == null) {
    423             dfs = new DateFormatSymbols(locale);
    424             ref = new SoftReference<DateFormatSymbols>(dfs);
    425             SoftReference<DateFormatSymbols> x = cachedInstances.putIfAbsent(locale, ref);
    426             if (x != null) {
    427                 DateFormatSymbols y = x.get();
    428                 if (y != null) {
    429                     dfs = y;
    430                 } else {
    431                     // Replace the empty SoftReference with ref.
    432                     cachedInstances.put(locale, ref);
    433                 }
    434             }
    435         }
    436         return dfs;
    437     }
    438 
    439     /**
    440      * Gets era strings. For example: "AD" and "BC".
    441      * @return the era strings.
    442      */
    443     public String[] getEras() {
    444         return Arrays.copyOf(eras, eras.length);
    445     }
    446 
    447     /**
    448      * Sets era strings. For example: "AD" and "BC".
    449      * @param newEras the new era strings.
    450      */
    451     public void setEras(String[] newEras) {
    452         eras = Arrays.copyOf(newEras, newEras.length);
    453         cachedHashCode = 0;
    454     }
    455 
    456     /**
    457      * Gets month strings. For example: "January", "February", etc.
    458      * @return the month strings.
    459      */
    460     public String[] getMonths() {
    461         return Arrays.copyOf(months, months.length);
    462     }
    463 
    464     /**
    465      * Sets month strings. For example: "January", "February", etc.
    466      * @param newMonths the new month strings.
    467      */
    468     public void setMonths(String[] newMonths) {
    469         months = Arrays.copyOf(newMonths, newMonths.length);
    470         cachedHashCode = 0;
    471     }
    472 
    473     /**
    474      * Gets short month strings. For example: "Jan", "Feb", etc.
    475      * @return the short month strings.
    476      */
    477     public String[] getShortMonths() {
    478         return Arrays.copyOf(shortMonths, shortMonths.length);
    479     }
    480 
    481     /**
    482      * Sets short month strings. For example: "Jan", "Feb", etc.
    483      * @param newShortMonths the new short month strings.
    484      */
    485     public void setShortMonths(String[] newShortMonths) {
    486         shortMonths = Arrays.copyOf(newShortMonths, newShortMonths.length);
    487         cachedHashCode = 0;
    488     }
    489 
    490     /**
    491      * Gets weekday strings. For example: "Sunday", "Monday", etc.
    492      * @return the weekday strings. Use <code>Calendar.SUNDAY</code>,
    493      * <code>Calendar.MONDAY</code>, etc. to index the result array.
    494      */
    495     public String[] getWeekdays() {
    496         return Arrays.copyOf(weekdays, weekdays.length);
    497     }
    498 
    499     /**
    500      * Sets weekday strings. For example: "Sunday", "Monday", etc.
    501      * @param newWeekdays the new weekday strings. The array should
    502      * be indexed by <code>Calendar.SUNDAY</code>,
    503      * <code>Calendar.MONDAY</code>, etc.
    504      */
    505     public void setWeekdays(String[] newWeekdays) {
    506         weekdays = Arrays.copyOf(newWeekdays, newWeekdays.length);
    507         cachedHashCode = 0;
    508     }
    509 
    510     /**
    511      * Gets short weekday strings. For example: "Sun", "Mon", etc.
    512      * @return the short weekday strings. Use <code>Calendar.SUNDAY</code>,
    513      * <code>Calendar.MONDAY</code>, etc. to index the result array.
    514      */
    515     public String[] getShortWeekdays() {
    516         return Arrays.copyOf(shortWeekdays, shortWeekdays.length);
    517     }
    518 
    519     /**
    520      * Sets short weekday strings. For example: "Sun", "Mon", etc.
    521      * @param newShortWeekdays the new short weekday strings. The array should
    522      * be indexed by <code>Calendar.SUNDAY</code>,
    523      * <code>Calendar.MONDAY</code>, etc.
    524      */
    525     public void setShortWeekdays(String[] newShortWeekdays) {
    526         shortWeekdays = Arrays.copyOf(newShortWeekdays, newShortWeekdays.length);
    527         cachedHashCode = 0;
    528     }
    529 
    530     /**
    531      * Gets ampm strings. For example: "AM" and "PM".
    532      * @return the ampm strings.
    533      */
    534     public String[] getAmPmStrings() {
    535         return Arrays.copyOf(ampms, ampms.length);
    536     }
    537 
    538     /**
    539      * Sets ampm strings. For example: "AM" and "PM".
    540      * @param newAmpms the new ampm strings.
    541      */
    542     public void setAmPmStrings(String[] newAmpms) {
    543         ampms = Arrays.copyOf(newAmpms, newAmpms.length);
    544         cachedHashCode = 0;
    545     }
    546 
    547     // Android-changed: Removed reference to TimeZoneNameProvider.
    548     /**
    549      * Gets time zone strings.  Use of this method is discouraged; use
    550      * {@link java.util.TimeZone#getDisplayName() TimeZone.getDisplayName()}
    551      * instead.
    552      * <p>
    553      * The value returned is a
    554      * two-dimensional array of strings of size <em>n</em> by <em>m</em>,
    555      * where <em>m</em> is at least 5.  Each of the <em>n</em> rows is an
    556      * entry containing the localized names for a single <code>TimeZone</code>.
    557      * Each such row contains (with <code>i</code> ranging from
    558      * 0..<em>n</em>-1):
    559      * <ul>
    560      * <li><code>zoneStrings[i][0]</code> - time zone ID</li>
    561      * <li><code>zoneStrings[i][1]</code> - long name of zone in standard
    562      * time</li>
    563      * <li><code>zoneStrings[i][2]</code> - short name of zone in
    564      * standard time</li>
    565      * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight
    566      * saving time</li>
    567      * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight
    568      * saving time</li>
    569      * </ul>
    570      * The zone ID is <em>not</em> localized; it's one of the valid IDs of
    571      * the {@link java.util.TimeZone TimeZone} class that are not
    572      * <a href="../util/TimeZone.html#CustomID">custom IDs</a>.
    573      * All other entries are localized names.  If a zone does not implement
    574      * daylight saving time, the daylight saving time names should not be used.
    575      * <p>
    576      * If {@link #setZoneStrings(String[][]) setZoneStrings} has been called
    577      * on this <code>DateFormatSymbols</code> instance, then the strings
    578      * provided by that call are returned. Otherwise, the returned array
    579      * contains names provided by the runtime.
    580      *
    581      * @return the time zone strings.
    582      * @see #setZoneStrings(String[][])
    583      */
    584     public String[][] getZoneStrings() {
    585         return getZoneStringsImpl(true);
    586     }
    587 
    588     /**
    589      * Sets time zone strings.  The argument must be a
    590      * two-dimensional array of strings of size <em>n</em> by <em>m</em>,
    591      * where <em>m</em> is at least 5.  Each of the <em>n</em> rows is an
    592      * entry containing the localized names for a single <code>TimeZone</code>.
    593      * Each such row contains (with <code>i</code> ranging from
    594      * 0..<em>n</em>-1):
    595      * <ul>
    596      * <li><code>zoneStrings[i][0]</code> - time zone ID</li>
    597      * <li><code>zoneStrings[i][1]</code> - long name of zone in standard
    598      * time</li>
    599      * <li><code>zoneStrings[i][2]</code> - short name of zone in
    600      * standard time</li>
    601      * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight
    602      * saving time</li>
    603      * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight
    604      * saving time</li>
    605      * </ul>
    606      * The zone ID is <em>not</em> localized; it's one of the valid IDs of
    607      * the {@link java.util.TimeZone TimeZone} class that are not
    608      * <a href="../util/TimeZone.html#CustomID">custom IDs</a>.
    609      * All other entries are localized names.
    610      *
    611      * @param newZoneStrings the new time zone strings.
    612      * @exception IllegalArgumentException if the length of any row in
    613      *    <code>newZoneStrings</code> is less than 5
    614      * @exception NullPointerException if <code>newZoneStrings</code> is null
    615      * @see #getZoneStrings()
    616      */
    617     public void setZoneStrings(String[][] newZoneStrings) {
    618         String[][] aCopy = new String[newZoneStrings.length][];
    619         for (int i = 0; i < newZoneStrings.length; ++i) {
    620             int len = newZoneStrings[i].length;
    621             if (len < 5) {
    622                 throw new IllegalArgumentException();
    623             }
    624             aCopy[i] = Arrays.copyOf(newZoneStrings[i], len);
    625         }
    626         zoneStrings = aCopy;
    627         isZoneStringsSet = true;
    628         // Android-changed: don't include zone strings in hashCode to avoid populating it.
    629         // cachedHashCode = 0;
    630     }
    631 
    632     /**
    633      * Gets localized date-time pattern characters. For example: 'u', 't', etc.
    634      * @return the localized date-time pattern characters.
    635      */
    636     public String getLocalPatternChars() {
    637         return localPatternChars;
    638     }
    639 
    640     /**
    641      * Sets localized date-time pattern characters. For example: 'u', 't', etc.
    642      * @param newLocalPatternChars the new localized date-time
    643      * pattern characters.
    644      */
    645     public void setLocalPatternChars(String newLocalPatternChars) {
    646         // Call toString() to throw an NPE in case the argument is null
    647         localPatternChars = newLocalPatternChars.toString();
    648         cachedHashCode = 0;
    649     }
    650 
    651     String[] getTinyMonths() {
    652         return tinyMonths;
    653     }
    654 
    655     String[] getStandAloneMonths() {
    656         return standAloneMonths;
    657     }
    658 
    659     String[] getShortStandAloneMonths() {
    660         return shortStandAloneMonths;
    661     }
    662 
    663     String[] getTinyStandAloneMonths() {
    664         return tinyStandAloneMonths;
    665     }
    666 
    667     String[] getTinyWeekdays() {
    668         return tinyWeekdays;
    669     }
    670 
    671     String[] getStandAloneWeekdays() {
    672         return standAloneWeekdays;
    673     }
    674 
    675     String[] getShortStandAloneWeekdays() {
    676         return shortStandAloneWeekdays;
    677     }
    678 
    679     String[] getTinyStandAloneWeekdays() {
    680         return tinyStandAloneWeekdays;
    681     }
    682 
    683     /**
    684      * Overrides Cloneable
    685      */
    686     public Object clone()
    687     {
    688         try
    689         {
    690             DateFormatSymbols other = (DateFormatSymbols)super.clone();
    691             copyMembers(this, other);
    692             return other;
    693         } catch (CloneNotSupportedException e) {
    694             throw new InternalError(e);
    695         }
    696     }
    697 
    698     /**
    699      * Override hashCode.
    700      * Generates a hash code for the DateFormatSymbols object.
    701      */
    702     @Override
    703     public int hashCode() {
    704         int hashCode = cachedHashCode;
    705         if (hashCode == 0) {
    706             hashCode = 5;
    707             hashCode = 11 * hashCode + Arrays.hashCode(eras);
    708             hashCode = 11 * hashCode + Arrays.hashCode(months);
    709             hashCode = 11 * hashCode + Arrays.hashCode(shortMonths);
    710             hashCode = 11 * hashCode + Arrays.hashCode(weekdays);
    711             hashCode = 11 * hashCode + Arrays.hashCode(shortWeekdays);
    712             hashCode = 11 * hashCode + Arrays.hashCode(ampms);
    713             // Android-changed: Don't include zone strings in hashCode to avoid populating it.
    714             // hashCode = 11 * hashCode + Arrays.deepHashCode(getZoneStringsWrapper());
    715             hashCode = 11 * hashCode + Objects.hashCode(localPatternChars);
    716             cachedHashCode = hashCode;
    717         }
    718 
    719         return hashCode;
    720     }
    721 
    722     /**
    723      * Override equals
    724      */
    725     public boolean equals(Object obj)
    726     {
    727         if (this == obj) return true;
    728         if (obj == null || getClass() != obj.getClass()) return false;
    729         DateFormatSymbols that = (DateFormatSymbols) obj;
    730         if (!(Arrays.equals(eras, that.eras)
    731                 && Arrays.equals(months, that.months)
    732                 && Arrays.equals(shortMonths, that.shortMonths)
    733                 && Arrays.equals(tinyMonths, that.tinyMonths)
    734                 && Arrays.equals(weekdays, that.weekdays)
    735                 && Arrays.equals(shortWeekdays, that.shortWeekdays)
    736                 && Arrays.equals(tinyWeekdays, that.tinyWeekdays)
    737                 && Arrays.equals(standAloneMonths, that.standAloneMonths)
    738                 && Arrays.equals(shortStandAloneMonths, that.shortStandAloneMonths)
    739                 && Arrays.equals(tinyStandAloneMonths, that.tinyStandAloneMonths)
    740                 && Arrays.equals(standAloneWeekdays, that.standAloneWeekdays)
    741                 && Arrays.equals(shortStandAloneWeekdays, that.shortStandAloneWeekdays)
    742                 && Arrays.equals(tinyStandAloneWeekdays, that.tinyStandAloneWeekdays)
    743                 && Arrays.equals(ampms, that.ampms)
    744                 && ((localPatternChars != null
    745                   && localPatternChars.equals(that.localPatternChars))
    746                  || (localPatternChars == null
    747                   && that.localPatternChars == null)))) {
    748             return false;
    749         }
    750         // Android-changed: Avoid populating zoneStrings just for the comparison.
    751         if (!isZoneStringsSet && !that.isZoneStringsSet && Objects.equals(locale, that.locale)) {
    752             return true;
    753         }
    754         return Arrays.deepEquals(getZoneStringsWrapper(), that.getZoneStringsWrapper());
    755     }
    756 
    757     // =======================privates===============================
    758 
    759     /**
    760      * Useful constant for defining time zone offsets.
    761      */
    762     static final int millisPerHour = 60*60*1000;
    763 
    764     /**
    765      * Cache to hold DateFormatSymbols instances per Locale.
    766      */
    767     private static final ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> cachedInstances
    768         = new ConcurrentHashMap<Locale, SoftReference<DateFormatSymbols>>(3);
    769 
    770     private transient int lastZoneIndex = 0;
    771 
    772     /**
    773      * Cached hash code
    774      */
    775     transient volatile int cachedHashCode = 0;
    776 
    777     private void initializeData(Locale desiredLocale) {
    778         locale = desiredLocale;
    779 
    780         // Copy values of a cached instance if any.
    781         SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale);
    782         DateFormatSymbols dfs;
    783         if (ref != null && (dfs = ref.get()) != null) {
    784             copyMembers(dfs, this);
    785             return;
    786         }
    787         locale = LocaleData.mapInvalidAndNullLocales(locale);
    788         LocaleData localeData = LocaleData.get(locale);
    789 
    790         eras = localeData.eras;
    791 
    792         // Month names.
    793         months = localeData.longMonthNames;
    794         shortMonths = localeData.shortMonthNames;
    795 
    796         ampms = localeData.amPm;
    797         localPatternChars = patternChars;
    798 
    799         // Weekdays.
    800         weekdays = localeData.longWeekdayNames;
    801         shortWeekdays = localeData.shortWeekdayNames;
    802 
    803         initializeSupplementaryData(localeData);
    804     }
    805 
    806     private void initializeSupplementaryData(LocaleData localeData) {
    807         // Tiny weekdays and months.
    808         tinyMonths = localeData.tinyMonthNames;
    809         tinyWeekdays = localeData.tinyWeekdayNames;
    810 
    811         // Standalone month names.
    812         standAloneMonths = localeData.longStandAloneMonthNames;
    813         shortStandAloneMonths = localeData.shortStandAloneMonthNames;
    814         tinyStandAloneMonths = localeData.tinyStandAloneMonthNames;
    815 
    816         // Standalone weekdays.
    817         standAloneWeekdays = localeData.longStandAloneWeekdayNames;
    818         shortStandAloneWeekdays = localeData.shortStandAloneWeekdayNames;
    819         tinyStandAloneWeekdays = localeData.tinyStandAloneWeekdayNames;
    820     }
    821 
    822     /**
    823      * Package private: used by SimpleDateFormat
    824      * Gets the index for the given time zone ID to obtain the time zone
    825      * strings for formatting. The time zone ID is just for programmatic
    826      * lookup. NOT LOCALIZED!!!
    827      * @param ID the given time zone ID.
    828      * @return the index of the given time zone ID.  Returns -1 if
    829      * the given time zone ID can't be located in the DateFormatSymbols object.
    830      * @see java.util.SimpleTimeZone
    831      */
    832     final int getZoneIndex(String ID) {
    833         String[][] zoneStrings = getZoneStringsWrapper();
    834 
    835         /*
    836          * getZoneIndex has been re-written for performance reasons. instead of
    837          * traversing the zoneStrings array every time, we cache the last used zone
    838          * index
    839          */
    840         if (lastZoneIndex < zoneStrings.length && ID.equals(zoneStrings[lastZoneIndex][0])) {
    841             return lastZoneIndex;
    842         }
    843 
    844         /* slow path, search entire list */
    845         for (int index = 0; index < zoneStrings.length; index++) {
    846             if (ID.equals(zoneStrings[index][0])) {
    847                 lastZoneIndex = index;
    848                 return index;
    849             }
    850         }
    851 
    852         return -1;
    853     }
    854 
    855     /**
    856      * Wrapper method to the getZoneStrings(), which is called from inside
    857      * the java.text package and not to mutate the returned arrays, so that
    858      * it does not need to create a defensive copy.
    859      */
    860     final String[][] getZoneStringsWrapper() {
    861         if (isSubclassObject()) {
    862             return getZoneStrings();
    863         } else {
    864             return getZoneStringsImpl(false);
    865         }
    866     }
    867 
    868     private final synchronized String[][] internalZoneStrings() {
    869         if (zoneStrings == null) {
    870             zoneStrings = TimeZoneNames.getZoneStrings(locale);
    871             // If icu4c doesn't have a name, our array contains a null. TimeZone.getDisplayName
    872             // knows how to format GMT offsets (and, unlike icu4c, has accurate data). http://b/8128460.
    873             for (String[] zone : zoneStrings) {
    874                 String id = zone[0];
    875                 if (zone[1] == null) {
    876                     zone[1] =
    877                         TimeZone.getTimeZone(id).getDisplayName(false, TimeZone.LONG, locale);
    878                 }
    879                 if (zone[2] == null) {
    880                     zone[2] =
    881                         TimeZone.getTimeZone(id).getDisplayName(false, TimeZone.SHORT, locale);
    882                 }
    883                 if (zone[3] == null) {
    884                     zone[3] = TimeZone.getTimeZone(id).getDisplayName(true, TimeZone.LONG, locale);
    885                 }
    886                 if (zone[4] == null) {
    887                     zone[4] =
    888                         TimeZone.getTimeZone(id).getDisplayName(true, TimeZone.SHORT, locale);
    889                 }
    890             }
    891         }
    892         return zoneStrings;
    893     }
    894 
    895     private final String[][] getZoneStringsImpl(boolean needsCopy) {
    896         String[][] zoneStrings = internalZoneStrings();
    897 
    898         if (!needsCopy) {
    899             return zoneStrings;
    900         }
    901 
    902         int len = zoneStrings.length;
    903         String[][] aCopy = new String[len][];
    904         for (int i = 0; i < len; i++) {
    905             aCopy[i] = Arrays.copyOf(zoneStrings[i], zoneStrings[i].length);
    906         }
    907         return aCopy;
    908     }
    909 
    910     private boolean isSubclassObject() {
    911         return !getClass().getName().equals("java.text.DateFormatSymbols");
    912     }
    913 
    914     /**
    915      * Clones all the data members from the source DateFormatSymbols to
    916      * the target DateFormatSymbols. This is only for subclasses.
    917      * @param src the source DateFormatSymbols.
    918      * @param dst the target DateFormatSymbols.
    919      */
    920     private void copyMembers(DateFormatSymbols src, DateFormatSymbols dst)
    921     {
    922         dst.eras = Arrays.copyOf(src.eras, src.eras.length);
    923         dst.months = Arrays.copyOf(src.months, src.months.length);
    924         dst.shortMonths = Arrays.copyOf(src.shortMonths, src.shortMonths.length);
    925         dst.weekdays = Arrays.copyOf(src.weekdays, src.weekdays.length);
    926         dst.shortWeekdays = Arrays.copyOf(src.shortWeekdays, src.shortWeekdays.length);
    927         dst.ampms = Arrays.copyOf(src.ampms, src.ampms.length);
    928         if (src.zoneStrings != null) {
    929             dst.zoneStrings = src.getZoneStringsImpl(true);
    930         } else {
    931             dst.zoneStrings = null;
    932         }
    933         dst.localPatternChars = src.localPatternChars;
    934         dst.cachedHashCode = 0;
    935 
    936         dst.tinyMonths = src.tinyMonths;
    937         dst.tinyWeekdays = src.tinyWeekdays;
    938 
    939         dst.standAloneMonths = src.standAloneMonths;
    940         dst.shortStandAloneMonths = src.shortStandAloneMonths;
    941         dst.tinyStandAloneMonths = src.tinyStandAloneMonths;
    942 
    943         dst.standAloneWeekdays = src.standAloneWeekdays;
    944         dst.shortStandAloneWeekdays = src.shortStandAloneWeekdays;
    945         dst.tinyStandAloneWeekdays = src.tinyStandAloneWeekdays;
    946     }
    947 
    948     private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    949         stream.defaultReadObject();
    950 
    951         if (serialVersionOnStream < 1) {
    952             LocaleData localeData = LocaleData.get(locale);
    953             initializeSupplementaryData(localeData);
    954         }
    955 
    956         serialVersionOnStream = currentSerialVersion;
    957     }
    958 
    959     /**
    960      * Write out the default serializable data, after ensuring the
    961      * <code>zoneStrings</code> field is initialized in order to make
    962      * sure the backward compatibility.
    963      *
    964      * @since 1.6
    965      */
    966     private void writeObject(ObjectOutputStream stream) throws IOException {
    967         internalZoneStrings();
    968         stream.defaultWriteObject();
    969     }
    970 }
    971