Home | History | Annotate | Download | only in text
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package java.text;
     19 
     20 import java.io.IOException;
     21 import java.io.ObjectInputStream;
     22 import java.io.ObjectOutputStream;
     23 import java.io.Serializable;
     24 import java.util.Arrays;
     25 import java.util.Locale;
     26 import java.util.TimeZone;
     27 import libcore.icu.ICU;
     28 import libcore.icu.LocaleData;
     29 import libcore.icu.TimeZoneNames;
     30 
     31 /**
     32  * Encapsulates localized date-time formatting data, such as the names of the
     33  * months, the names of the days of the week, and the time zone data.
     34  * {@code DateFormat} and {@code SimpleDateFormat} both use
     35  * {@code DateFormatSymbols} to encapsulate this information.
     36  *
     37  * <p>Typically you shouldn't use {@code DateFormatSymbols} directly. Rather, you
     38  * are encouraged to create a date/time formatter with the {@code DateFormat}
     39  * class's factory methods: {@code getTimeInstance}, {@code getDateInstance},
     40  * or {@code getDateTimeInstance}. These methods automatically create a
     41  * {@code DateFormatSymbols} for the formatter so that you don't have to. After
     42  * the formatter is created, you may modify its format pattern using the
     43  * {@code setPattern} method. For more information about creating formatters
     44  * using {@code DateFormat}'s factory methods, see {@link DateFormat}.
     45  *
     46  * <p>Direct use of {@code DateFormatSymbols} is likely to be less efficient
     47  * because the implementation cannot make assumptions about user-supplied/user-modifiable data
     48  * to the same extent that it can with its own built-in data.
     49  *
     50  * @see DateFormat
     51  * @see SimpleDateFormat
     52  */
     53 public class DateFormatSymbols implements Serializable, Cloneable {
     54 
     55     private static final long serialVersionUID = -5987973545549424702L;
     56 
     57     private String localPatternChars;
     58 
     59     String[] ampms, eras, months, shortMonths, shortWeekdays, weekdays;
     60 
     61     // This is used to implement parts of Unicode UTS #35 not historically supported.
     62     transient LocaleData localeData;
     63 
     64     // Localized display names.
     65     String[][] zoneStrings;
     66     // Has the user called setZoneStrings?
     67     transient boolean customZoneStrings;
     68 
     69     /**
     70      * Locale, necessary to lazily load time zone strings. We force the time
     71      * zone names to load upon serialization, so this will never be needed
     72      * post deserialization.
     73      */
     74     transient final Locale locale;
     75 
     76     /**
     77      * Gets zone strings, initializing them if necessary. Does not create
     78      * a defensive copy, so make sure you do so before exposing the returned
     79      * arrays to clients.
     80      */
     81     synchronized String[][] internalZoneStrings() {
     82         if (zoneStrings == null) {
     83             zoneStrings = TimeZoneNames.getZoneStrings(locale);
     84         }
     85         return zoneStrings;
     86     }
     87 
     88     /**
     89      * Constructs a new {@code DateFormatSymbols} instance containing the
     90      * symbols for the user's default locale.
     91      * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
     92      */
     93     public DateFormatSymbols() {
     94         this(Locale.getDefault());
     95     }
     96 
     97     /**
     98      * Constructs a new {@code DateFormatSymbols} instance containing the
     99      * symbols for the specified locale.
    100      *
    101      * @param locale
    102      *            the locale.
    103      */
    104     public DateFormatSymbols(Locale locale) {
    105         this.locale = locale;
    106         this.localPatternChars = SimpleDateFormat.PATTERN_CHARS;
    107 
    108         this.localeData = LocaleData.get(locale);
    109         this.ampms = localeData.amPm;
    110         this.eras = localeData.eras;
    111         this.months = localeData.longMonthNames;
    112         this.shortMonths = localeData.shortMonthNames;
    113         this.weekdays = localeData.longWeekdayNames;
    114         this.shortWeekdays = localeData.shortWeekdayNames;
    115     }
    116 
    117     /**
    118      * Returns a new {@code DateFormatSymbols} instance for the user's default locale.
    119      * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
    120      *
    121      * @return an instance of {@code DateFormatSymbols}
    122      * @since 1.6
    123      */
    124     public static final DateFormatSymbols getInstance() {
    125         return getInstance(Locale.getDefault());
    126     }
    127 
    128     /**
    129      * Returns a new {@code DateFormatSymbols} for the given locale.
    130      *
    131      * @param locale the locale
    132      * @return an instance of {@code DateFormatSymbols}
    133      * @throws NullPointerException if {@code locale == null}
    134      * @since 1.6
    135      */
    136     public static final DateFormatSymbols getInstance(Locale locale) {
    137         if (locale == null) {
    138             throw new NullPointerException("locale == null");
    139         }
    140         return new DateFormatSymbols(locale);
    141     }
    142 
    143     /**
    144      * Returns an array of locales for which custom {@code DateFormatSymbols} instances
    145      * are available.
    146      * <p>Note that Android does not support user-supplied locale service providers.
    147      * @since 1.6
    148      */
    149     public static Locale[] getAvailableLocales() {
    150         return ICU.getAvailableDateFormatSymbolsLocales();
    151     }
    152 
    153     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    154         ois.defaultReadObject();
    155         this.localeData = LocaleData.get(locale);
    156     }
    157 
    158     private void writeObject(ObjectOutputStream oos) throws IOException {
    159         internalZoneStrings();
    160         oos.defaultWriteObject();
    161     }
    162 
    163     @Override
    164     public Object clone() {
    165         try {
    166             return super.clone();
    167         } catch (CloneNotSupportedException e) {
    168             throw new AssertionError();
    169         }
    170     }
    171 
    172     /**
    173      * Compares this object with the specified object and indicates if they are
    174      * equal.
    175      *
    176      * @param object
    177      *            the object to compare with this object.
    178      * @return {@code true} if {@code object} is an instance of
    179      *         {@code DateFormatSymbols} and has the same symbols as this
    180      *         object, {@code false} otherwise.
    181      * @see #hashCode
    182      */
    183     @Override
    184     public boolean equals(Object object) {
    185         if (this == object) {
    186             return true;
    187         }
    188         if (!(object instanceof DateFormatSymbols)) {
    189             return false;
    190         }
    191         DateFormatSymbols rhs = (DateFormatSymbols) object;
    192         return localPatternChars.equals(rhs.localPatternChars) &&
    193                 Arrays.equals(ampms, rhs.ampms) &&
    194                 Arrays.equals(eras, rhs.eras) &&
    195                 Arrays.equals(months, rhs.months) &&
    196                 Arrays.equals(shortMonths, rhs.shortMonths) &&
    197                 Arrays.equals(shortWeekdays, rhs.shortWeekdays) &&
    198                 Arrays.equals(weekdays, rhs.weekdays) &&
    199                 timeZoneStringsEqual(this, rhs);
    200     }
    201 
    202     private static boolean timeZoneStringsEqual(DateFormatSymbols lhs, DateFormatSymbols rhs) {
    203         // Quick check that may keep us from having to load the zone strings.
    204         // Note that different locales may have the same strings, so the opposite check isn't valid.
    205         if (lhs.zoneStrings == null && rhs.zoneStrings == null && lhs.locale.equals(rhs.locale)) {
    206             return true;
    207         }
    208         // Make sure zone strings are loaded, then check.
    209         return Arrays.deepEquals(lhs.internalZoneStrings(), rhs.internalZoneStrings());
    210     }
    211 
    212     @Override
    213     public String toString() {
    214         // 'locale' isn't part of the externally-visible state.
    215         // 'zoneStrings' is so large, we just print a representative value.
    216         return getClass().getName() +
    217                 "[amPmStrings=" + Arrays.toString(ampms) +
    218                 ",customZoneStrings=" + customZoneStrings +
    219                 ",eras=" + Arrays.toString(eras) +
    220                 ",localPatternChars=" + localPatternChars +
    221                 ",months=" + Arrays.toString(months) +
    222                 ",shortMonths=" + Arrays.toString(shortMonths) +
    223                 ",shortWeekdays=" + Arrays.toString(shortWeekdays) +
    224                 ",weekdays=" + Arrays.toString(weekdays) +
    225                 ",zoneStrings=[" + Arrays.toString(internalZoneStrings()[0]) + "...]" +
    226                 "]";
    227     }
    228 
    229     /**
    230      * Returns the array of strings which represent AM and PM. Use the
    231      * {@link java.util.Calendar} constants {@code Calendar.AM} and
    232      * {@code Calendar.PM} as indices for the array.
    233      *
    234      * @return an array of strings.
    235      */
    236     public String[] getAmPmStrings() {
    237         return ampms.clone();
    238     }
    239 
    240     /**
    241      * Returns the array of strings which represent BC and AD. Use the
    242      * {@link java.util.Calendar} constants {@code GregorianCalendar.BC} and
    243      * {@code GregorianCalendar.AD} as indices for the array.
    244      *
    245      * @return an array of strings.
    246      */
    247     public String[] getEras() {
    248         return eras.clone();
    249     }
    250 
    251     /**
    252      * Returns the pattern characters used by {@link SimpleDateFormat} to
    253      * specify date and time fields.
    254      *
    255      * @return a string containing the pattern characters.
    256      */
    257     public String getLocalPatternChars() {
    258         return localPatternChars;
    259     }
    260 
    261     /**
    262      * Returns the array of strings containing the full names of the months. Use
    263      * the {@link java.util.Calendar} constants {@code Calendar.JANUARY} etc. as
    264      * indices for the array.
    265      *
    266      * @return an array of strings.
    267      */
    268     public String[] getMonths() {
    269         return months.clone();
    270     }
    271 
    272     /**
    273      * Returns the array of strings containing the abbreviated names of the
    274      * months. Use the {@link java.util.Calendar} constants
    275      * {@code Calendar.JANUARY} etc. as indices for the array.
    276      *
    277      * @return an array of strings.
    278      */
    279     public String[] getShortMonths() {
    280         return shortMonths.clone();
    281     }
    282 
    283     /**
    284      * Returns the array of strings containing the abbreviated names of the days
    285      * of the week. Use the {@link java.util.Calendar} constants
    286      * {@code Calendar.SUNDAY} etc. as indices for the array.
    287      *
    288      * @return an array of strings.
    289      */
    290     public String[] getShortWeekdays() {
    291         return shortWeekdays.clone();
    292     }
    293 
    294     /**
    295      * Returns the array of strings containing the full names of the days of the
    296      * week. Use the {@link java.util.Calendar} constants
    297      * {@code Calendar.SUNDAY} etc. as indices for the array.
    298      *
    299      * @return an array of strings.
    300      */
    301     public String[] getWeekdays() {
    302         return weekdays.clone();
    303     }
    304 
    305     /**
    306      * Returns the two-dimensional array of strings containing localized names for time zones.
    307      * Each row is an array of five strings:
    308      * <ul>
    309      * <li>The time zone ID, for example "America/Los_Angeles".
    310      *     This is not localized, and is used as a key into the table.
    311      * <li>The long display name, for example "Pacific Standard Time".
    312      * <li>The short display name, for example "PST".
    313      * <li>The long display name for DST, for example "Pacific Daylight Time".
    314      *     This is the non-DST long name for zones that have never had DST, for
    315      *     example "Central Standard Time" for "Canada/Saskatchewan".
    316      * <li>The short display name for DST, for example "PDT". This is the
    317      *     non-DST short name for zones that have never had DST, for example
    318      *     "CST" for "Canada/Saskatchewan".
    319      * </ul>
    320      */
    321     public String[][] getZoneStrings() {
    322         String[][] result = clone2dStringArray(internalZoneStrings());
    323         // If icu4c doesn't have a name, our array contains a null. TimeZone.getDisplayName
    324         // knows how to format GMT offsets (and, unlike icu4c, has accurate data). http://b/8128460.
    325         for (String[] zone : result) {
    326             String id = zone[0];
    327             if (zone[1] == null) {
    328                 zone[1] = TimeZone.getTimeZone(id).getDisplayName(false, TimeZone.LONG, locale);
    329             }
    330             if (zone[2] == null) {
    331                 zone[2] = TimeZone.getTimeZone(id).getDisplayName(false, TimeZone.SHORT, locale);
    332             }
    333             if (zone[3] == null) {
    334                 zone[3] = TimeZone.getTimeZone(id).getDisplayName(true, TimeZone.LONG, locale);
    335             }
    336             if (zone[4] == null) {
    337                 zone[4] = TimeZone.getTimeZone(id).getDisplayName(true, TimeZone.SHORT, locale);
    338             }
    339         }
    340         return result;
    341     }
    342 
    343     private static String[][] clone2dStringArray(String[][] array) {
    344         String[][] result = new String[array.length][];
    345         for (int i = 0; i < array.length; ++i) {
    346             result[i] = array[i].clone();
    347         }
    348         return result;
    349     }
    350 
    351     @Override
    352     public int hashCode() {
    353         String[][] zoneStrings = internalZoneStrings();
    354         int hashCode;
    355         hashCode = localPatternChars.hashCode();
    356         for (String element : ampms) {
    357             hashCode += element.hashCode();
    358         }
    359         for (String element : eras) {
    360             hashCode += element.hashCode();
    361         }
    362         for (String element : months) {
    363             hashCode += element.hashCode();
    364         }
    365         for (String element : shortMonths) {
    366             hashCode += element.hashCode();
    367         }
    368         for (String element : shortWeekdays) {
    369             hashCode += element.hashCode();
    370         }
    371         for (String element : weekdays) {
    372             hashCode += element.hashCode();
    373         }
    374         for (String[] element : zoneStrings) {
    375             for (int j = 0; j < element.length; j++) {
    376                 if (element[j] != null) {
    377                     hashCode += element[j].hashCode();
    378                 }
    379             }
    380         }
    381         return hashCode;
    382     }
    383 
    384     /**
    385      * Sets the array of strings which represent AM and PM. Use the
    386      * {@link java.util.Calendar} constants {@code Calendar.AM} and
    387      * {@code Calendar.PM} as indices for the array.
    388      *
    389      * @param data
    390      *            the array of strings for AM and PM.
    391      */
    392     public void setAmPmStrings(String[] data) {
    393         ampms = data.clone();
    394     }
    395 
    396     /**
    397      * Sets the array of Strings which represent BC and AD. Use the
    398      * {@link java.util.Calendar} constants {@code GregorianCalendar.BC} and
    399      * {@code GregorianCalendar.AD} as indices for the array.
    400      *
    401      * @param data
    402      *            the array of strings for BC and AD.
    403      */
    404     public void setEras(String[] data) {
    405         eras = data.clone();
    406     }
    407 
    408     /**
    409      * Sets the pattern characters used by {@link SimpleDateFormat} to specify
    410      * date and time fields.
    411      *
    412      * @param data
    413      *            the string containing the pattern characters.
    414      * @throws NullPointerException
    415      *            if {@code data} is null
    416      */
    417     public void setLocalPatternChars(String data) {
    418         if (data == null) {
    419             throw new NullPointerException("data == null");
    420         }
    421         localPatternChars = data;
    422     }
    423 
    424     /**
    425      * Sets the array of strings containing the full names of the months. Use
    426      * the {@link java.util.Calendar} constants {@code Calendar.JANUARY} etc. as
    427      * indices for the array.
    428      *
    429      * @param data
    430      *            the array of strings.
    431      */
    432     public void setMonths(String[] data) {
    433         months = data.clone();
    434     }
    435 
    436     /**
    437      * Sets the array of strings containing the abbreviated names of the months.
    438      * Use the {@link java.util.Calendar} constants {@code Calendar.JANUARY}
    439      * etc. as indices for the array.
    440      *
    441      * @param data
    442      *            the array of strings.
    443      */
    444     public void setShortMonths(String[] data) {
    445         shortMonths = data.clone();
    446     }
    447 
    448     /**
    449      * Sets the array of strings containing the abbreviated names of the days of
    450      * the week. Use the {@link java.util.Calendar} constants
    451      * {@code Calendar.SUNDAY} etc. as indices for the array.
    452      *
    453      * @param data
    454      *            the array of strings.
    455      */
    456     public void setShortWeekdays(String[] data) {
    457         shortWeekdays = data.clone();
    458     }
    459 
    460     /**
    461      * Sets the array of strings containing the full names of the days of the
    462      * week. Use the {@link java.util.Calendar} constants
    463      * {@code Calendar.SUNDAY} etc. as indices for the array.
    464      *
    465      * @param data
    466      *            the array of strings.
    467      */
    468     public void setWeekdays(String[] data) {
    469         weekdays = data.clone();
    470     }
    471 
    472     /**
    473      * Sets the two-dimensional array of strings containing localized names for time zones.
    474      * See {@link #getZoneStrings} for details.
    475      * @throws IllegalArgumentException if any row has fewer than 5 elements.
    476      * @throws NullPointerException if {@code zoneStrings == null}.
    477      */
    478     public void setZoneStrings(String[][] zoneStrings) {
    479         if (zoneStrings == null) {
    480             throw new NullPointerException("zoneStrings == null");
    481         }
    482         for (String[] row : zoneStrings) {
    483             if (row.length < 5) {
    484                 throw new IllegalArgumentException(Arrays.toString(row) + ".length < 5");
    485             }
    486         }
    487         this.zoneStrings = clone2dStringArray(zoneStrings);
    488         this.customZoneStrings = true;
    489     }
    490 }
    491