• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
internalZoneStrings()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      */
DateFormatSymbols()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      */
DateFormatSymbols(Locale locale)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      */
getInstance()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      */
getInstance(Locale locale)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      */
getAvailableLocales()149     public static Locale[] getAvailableLocales() {
150         return ICU.getAvailableDateFormatSymbolsLocales();
151     }
152 
readObject(ObjectInputStream ois)153     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
154         ois.defaultReadObject();
155         this.localeData = LocaleData.get(locale);
156     }
157 
writeObject(ObjectOutputStream oos)158     private void writeObject(ObjectOutputStream oos) throws IOException {
159         internalZoneStrings();
160         oos.defaultWriteObject();
161     }
162 
163     @Override
clone()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
equals(Object object)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 
timeZoneStringsEqual(DateFormatSymbols lhs, DateFormatSymbols rhs)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
toString()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      */
getAmPmStrings()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      */
getEras()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      */
getLocalPatternChars()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      */
getMonths()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      */
getShortMonths()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      */
getShortWeekdays()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      */
getWeekdays()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      */
getZoneStrings()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 
clone2dStringArray(String[][] array)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
hashCode()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      */
setAmPmStrings(String[] data)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      */
setEras(String[] data)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      */
setLocalPatternChars(String data)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      */
setMonths(String[] data)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      */
setShortMonths(String[] data)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      */
setShortWeekdays(String[] data)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      */
setWeekdays(String[] data)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      */
setZoneStrings(String[][] zoneStrings)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