1 /* DateFormatSymbols.java -- Format over a range of numbers
2    Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005, 2006  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package java.text;
40 
41 import gnu.java.locale.LocaleHelper;
42 
43 import java.io.IOException;
44 
45 import java.text.spi.DateFormatSymbolsProvider;
46 
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.HashMap;
50 import java.util.List;
51 import java.util.Locale;
52 import java.util.Map;
53 import java.util.MissingResourceException;
54 import java.util.Properties;
55 import java.util.ResourceBundle;
56 import java.util.ServiceLoader;
57 import java.util.TimeZone;
58 
59 import java.util.concurrent.ConcurrentMap;
60 import java.util.concurrent.ConcurrentHashMap;
61 
62 import java.util.regex.Pattern;
63 
64 import java.util.spi.TimeZoneNameProvider;
65 
66 /**
67  * This class acts as container for locale specific date/time formatting
68  * information such as the days of the week and the months of the year.
69  *
70  * @author Per Bothner (bothner@cygnus.com)
71  * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
72  * @date October 24, 1998.
73  */
74 /* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3.
75  * Status:  Believed complete and correct.
76  */
77 public class DateFormatSymbols implements java.io.Serializable, Cloneable
78 {
79   /**
80    * The set of properties for obtaining the metazone data.
81    */
82   private static transient final Properties properties;
83 
84   /**
85    * Reads in the properties.
86    */
87   static
88   {
89     properties = new Properties();
90     try
91       {
92         properties.load(DateFormatSymbols.class.getResourceAsStream("metazones.properties"));
93       }
94     catch (IOException exception)
95       {
96         System.out.println("Failed to load weeks resource: " + exception);
97       }
98   }
99 
100   private static final Pattern ZONE_SEP = Pattern.compile("\u00a9");
101 
102   private static final Pattern FIELD_SEP = Pattern.compile("\u00ae");
103 
104   /**
105    * Class for storing DateFormatSymbols data parsed from the property files.
106    */
107   private static class DFSData
108   {
109     private String[] ampms;
110     private String[] eras;
111     private String localPatternChars;
112     private String[] months;
113     private String[] shortMonths;
114     private String[] weekdays;
115     private String[] shortWeekdays;
116     private String[] dateFormats;
117     private String[] timeFormats;
118     private String[][] runtimeZoneStrings;
119 
120     /**
121      * Construct a new instance with the parsed data.
122      *
123      * @param ampms strings for "am" and "pm".
124      * @param eras strings for calendar eras.
125      * @param localPatternChars localised pattern characters.
126      * @param months strings for the months of the year.
127      * @param shortMonths short strings for the months of the year.
128      * @param weekdays strings for the days of the week.
129      * @param shortWeekdays short strings for the days of the week.
130      * @param dateFormats localised date formats.
131      * @param timeFormats localised time formats.
132      * @param runtimeZoneStrings localised time zone names.
133      */
DFSData(String[] ampms, String[] eras, String localPatternChars, String[] months, String[] shortMonths, String[] weekdays, String[] shortWeekdays, String[] dateFormats, String[] timeFormats, String[][] runtimeZoneStrings)134     public DFSData(String[] ampms, String[] eras, String localPatternChars,
135                    String[] months, String[] shortMonths, String[] weekdays,
136                    String[] shortWeekdays, String[] dateFormats,
137                    String[] timeFormats, String[][] runtimeZoneStrings)
138     {
139       this.ampms = ampms;
140       this.eras = eras;
141       this.localPatternChars = localPatternChars;
142       this.months = months;
143       this.shortMonths = shortMonths;
144       this.weekdays = weekdays;
145       this.shortWeekdays = shortWeekdays;
146       this.dateFormats = dateFormats;
147       this.timeFormats = timeFormats;
148       this.runtimeZoneStrings = runtimeZoneStrings;
149     }
150 
151     /**
152      * Accessor for the AM/PM data.
153      *
154      * @return the AM/PM strings.
155      */
getAMPMs()156     public String[] getAMPMs()
157     {
158       return ampms.clone();
159     }
160 
161     /**
162      * Accessor for the era data.
163      *
164      * @return the era strings.
165      */
getEras()166     public String[] getEras()
167     {
168       return eras.clone();
169     }
170 
171     /**
172      * Accessor for the local pattern characters.
173      *
174      * @return the local pattern characters.
175      */
getLocalPatternChars()176     public String getLocalPatternChars()
177     {
178       return localPatternChars;
179     }
180 
181     /**
182      * Accessor for the months of the year (long form).
183      *
184      * @return the months of the year (long form).
185      */
getMonths()186     public String[] getMonths()
187     {
188       return months.clone();
189     }
190 
191     /**
192      * Accessor for the months of the year (short form).
193      *
194      * @return the months of the year (short form).
195      */
getShortMonths()196     public String[] getShortMonths()
197     {
198       return shortMonths.clone();
199     }
200 
201     /**
202      * Accessor for the days of the week (long form).
203      *
204      * @return the days of the week (long form).
205      */
getWeekdays()206     public String[] getWeekdays()
207     {
208       return weekdays.clone();
209     }
210 
211     /**
212      * Accessor for the days of the week (short form).
213      *
214      * @return the days of the week (short form).
215      */
getShortWeekdays()216     public String[] getShortWeekdays()
217     {
218       return shortWeekdays.clone();
219     }
220 
221     /**
222      * Accessor for the date formats.
223      *
224      * @return the date formats.
225      */
getDateFormats()226     public String[] getDateFormats()
227     {
228       return dateFormats.clone();
229     }
230 
231     /**
232      * Accessor for the time formats.
233      *
234      * @return the time formats.
235      */
getTimeFormats()236     public String[] getTimeFormats()
237     {
238       return timeFormats.clone();
239     }
240 
241     /**
242      * Accessor for the zone strings.
243      *
244      * @return the zone strings.
245      */
getZoneStrings()246     public String[][] getZoneStrings()
247     {
248       // Perform a deep clone so subarrays aren't modifiable
249       String[][] clone = runtimeZoneStrings.clone();
250       for (int a = 0; a < clone.length; ++a)
251         clone[a] = runtimeZoneStrings[a].clone();
252       return clone;
253     }
254 
255   }
256 
257   private static final ConcurrentMap<Locale, DFSData> dataCache = new ConcurrentHashMap<Locale, DFSData>();
258 
259   String[] ampms;
260   String[] eras;
261   private String localPatternChars;
262   String[] months;
263   String[] shortMonths;
264   String[] shortWeekdays;
265   String[] weekdays;
266 
267   /**
268    * The timezone strings supplied by the runtime.
269    */
270   private String[][] runtimeZoneStrings;
271 
272   /**
273    * Custom timezone strings supplied by {@link #setZoneStrings()}.
274    */
275   private String[][] zoneStrings;
276 
277   private static final long serialVersionUID = -5987973545549424702L;
278 
279   // The order of these prefixes must be the same as in DateFormat
280   private static final String[] formatPrefixes =
281   {
282     "full", "long", "medium", "short"
283   };
284 
285   // These are each arrays with a value for SHORT, MEDIUM, LONG, FULL,
286   // and DEFAULT (constants defined in java.text.DateFormat).  While
287   // not part of the official spec, we need a way to get at locale-specific
288   // default formatting patterns.  They are declared package scope so
289   // as to be easily accessible where needed (DateFormat, SimpleDateFormat).
290   transient String[] dateFormats;
291   transient String[] timeFormats;
292 
293   /**
294    * Compiles a string array for a property using data from each of the locales in the
295    * hierarchy as necessary.
296    *
297    * @param bundles the locale hierarchy, starting with the most specific.
298    * @param name the name of the property.
299    * @param size the size the array should be when complete.
300    * @return a completed string array.
301    */
getStringArray(List<ResourceBundle> bundles, String name, int size)302   private static String[] getStringArray(List<ResourceBundle> bundles, String name, int size)
303   {
304     return getStringArray(bundles, name, size, null);
305   }
306 
307   /**
308    * Compiles a string array for a property using data from each of the locales in the
309    * hierarchy as necessary.  If non-null, the fallback array is also used for "sideways"
310    * inheritance (e.g. if there is no short name for a month, the long name is used rather
311    * than the empty string).
312    *
313    * @param bundles the locale hierarchy, starting with the most specific.
314    * @param name the name of the property.
315    * @param size the size the array should be when complete.
316    * @param fallback an array of long name fallback strings for data with both long and short names.
317    * @return a completed string array.
318    */
getStringArray(List<ResourceBundle> bundles, String name, int size, String[] fallback)319   private static String[] getStringArray(List<ResourceBundle> bundles, String name, int size,
320                                          String[] fallback)
321   {
322     String[] data = new String[size];
323     Arrays.fill(data, "");
324     // Populate array with data from each locale back to the root, starting with the most specific
325     for (int a = 0; a < bundles.size(); ++a)
326       {
327         String localeData = bundles.get(a).getString(name);
328         String[] array = FIELD_SEP.split(localeData, size);
329         for (int b = 0; b < data.length; ++b)
330           {
331             if (array.length > b && array[b] != null && data[b].isEmpty() && !array[b].isEmpty())
332               data[b] = array[b];
333           }
334       }
335     // Replace any remaining empty strings with data from the fallback array, if non-null
336     if (fallback != null && fallback.length == size)
337       {
338         for (int a = 0; a < data.length; ++a)
339           {
340             if (data[a].isEmpty() && fallback[a] != null && !fallback[a].isEmpty())
341               data[a] = fallback[a];
342           }
343       }
344     return data;
345   }
346 
getZoneStrings(List<ResourceBundle> bundles, Locale locale)347   private static String[][] getZoneStrings(List<ResourceBundle> bundles, Locale locale)
348   {
349     List<String[]> allZones = new ArrayList<String[]>();
350     try
351       {
352         Map<String,String[]> systemZones = new HashMap<String,String[]>();
353         for (ResourceBundle bundle : bundles)
354           {
355             String country = locale.getCountry();
356             String data = bundle.getString("zoneStrings");
357             String[] zones = ZONE_SEP.split(data);
358             for (int a = 0; a < zones.length; ++a)
359               {
360                 String[] strings = FIELD_SEP.split(zones[a]);
361                 String type = properties.getProperty(strings[0] + "." + country);
362                 if (type == null)
363                   type = properties.getProperty(strings[0] + ".DEFAULT");
364                 if (type != null)
365                   strings[0] = type;
366                 if (strings.length < 5)
367                   {
368                     String[] newStrings = new String[5];
369                     System.arraycopy(strings, 0, newStrings, 0, strings.length);
370                     for (int b = strings.length; b < newStrings.length; ++b)
371                       newStrings[b] = "";
372                     strings = newStrings;
373                   }
374                 String[] existing = systemZones.get(strings[0]);
375                 if (existing != null && existing.length > 1)
376                   {
377                     for (int b = 1; b < existing.length; ++b)
378                       if (!existing[b].equals(""))
379                         strings[b] = existing[b];
380                   }
381                 systemZones.put(strings[0], strings);
382               }
383           }
384         /* Final sanity check for missing values */
385         for (String[] zstrings : systemZones.values())
386           {
387             if (zstrings[1].equals("") && zstrings[2].equals(""))
388               {
389                 for (Map.Entry<Object,Object> entry : properties.entrySet())
390                   {
391                     String val = (String) entry.getValue();
392                     if (val.equals(zstrings[0]))
393                       {
394                         String key = (String) entry.getKey();
395                         String metazone = key.substring(0, key.indexOf("."));
396                         String type = properties.getProperty(metazone + "." + locale.getCountry());
397                         if (type == null)
398                           type = properties.getProperty(metazone + ".DEFAULT");
399                         if (type != null)
400                           {
401                             String[] ostrings = systemZones.get(type);
402                             zstrings[1] = ostrings[1];
403                             zstrings[2] = ostrings[2];
404                           }
405                       }
406                   }
407               }
408           }
409         allZones.addAll(systemZones.values());
410       }
411     catch (MissingResourceException e)
412       {
413         /* This means runtime support for the locale
414          * is not available, so we just include providers. */
415       }
416     for (TimeZoneNameProvider p :
417            ServiceLoader.load(TimeZoneNameProvider.class))
418       {
419         for (Locale loc : p.getAvailableLocales())
420           {
421             if (loc.equals(locale))
422               {
423                 for (String id : TimeZone.getAvailableIDs())
424                   {
425                     String[] z = new String[5];
426                     z[0] = id;
427                     z[1] = p.getDisplayName(id, false,
428                                             TimeZone.LONG,
429                                             locale);
430                     z[2] = p.getDisplayName(id, false,
431                                             TimeZone.SHORT,
432                                             locale);
433                     z[3] = p.getDisplayName(id, true,
434                                             TimeZone.LONG,
435                                             locale);
436                     z[4] = p.getDisplayName(id, true,
437                                             TimeZone.SHORT,
438                                             locale);
439                     allZones.add(z);
440                   }
441                 break;
442               }
443           }
444       }
445     return allZones.toArray(new String[allZones.size()][]);
446   }
447 
448   /**
449    * Retrieve the date or time formats for a specific key e.g.
450    * asking for "DateFormat" will return an array containing the
451    * full, long, medium and short date formats localised for
452    * the locales in the specified bundle.
453    *
454    * @param bundles the stack of bundles to check, most-specific first.
455    * @param key the type of format to retrieve.
456    * @param an array of localised strings for each format prefix.
457    */
formatsForKey(List<ResourceBundle> bundles, String key)458   private static String[] formatsForKey(List<ResourceBundle> bundles, String key)
459   {
460     String[] values = new String[formatPrefixes.length];
461 
462     for (int i = 0; i < formatPrefixes.length; i++)
463       values[i] = getString(bundles, formatPrefixes[i] + key);
464 
465     return values;
466   }
467 
468   /**
469    * Simple wrapper around extracting a {@code String} from a
470    * {@code ResourceBundle}.  Keep searching less-specific locales
471    * until a non-null non-empty value is found.
472    *
473    * @param bundles the stack of bundles to check, most-specific first.
474    * @param key the key of the value to retrieve.
475    * @return the first non-null non-empty String found or the last
476    *         retrieved if one isn't found.
477    */
getString(List<ResourceBundle> bundles, String key)478   private static String getString(List<ResourceBundle> bundles, String key)
479   {
480     String val = null;
481     for (ResourceBundle bundle : bundles)
482       {
483         val = bundle.getString(key);
484         if (val != null && !val.isEmpty())
485           return val;
486       }
487     return val;
488   }
489 
490   /**
491    * Retrieves the locale data from the property files and constructs a
492    * {@code DFSData} instance for it.
493    *
494    * @param the locale for which data should be retrieved.
495    * @return the parsed data.
496    * @throws MissingResourceException if the resources for the specified
497    *                                  locale could not be found or loaded.
498    */
retrieveData(Locale locale)499   private static DFSData retrieveData(Locale locale)
500     throws MissingResourceException
501   {
502     DFSData data = dataCache.get(locale);
503     if (data == null)
504       {
505         ClassLoader ldr = ClassLoader.getSystemClassLoader();
506         List<ResourceBundle> bundles = new ArrayList<ResourceBundle>();
507         ResourceBundle res
508           = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation", locale, ldr);
509         bundles.add(res);
510         Locale resLocale = res.getLocale();
511         while (resLocale != Locale.ROOT)
512           {
513             res = ResourceBundle.getBundle("gnu.java.locale.LocaleInformation",
514                                            LocaleHelper.getFallbackLocale(resLocale), ldr);
515             bundles.add(res);
516             resLocale = res.getLocale();
517           }
518         String[] lMonths = getStringArray(bundles, "months", 13);
519         String[] lWeekdays = getStringArray(bundles, "weekdays", 8);
520         data = new DFSData(getStringArray(bundles, "ampms", 2),
521                            getStringArray(bundles, "eras", 2),
522                            getString(bundles, "localPatternChars"),
523                            lMonths, getStringArray(bundles, "shortMonths", 13, lMonths),
524                            lWeekdays, getStringArray(bundles, "shortWeekdays", 8, lWeekdays),
525                            formatsForKey(bundles, "DateFormat"),
526                            formatsForKey(bundles, "TimeFormat"),
527                            getZoneStrings(bundles, locale));
528         DFSData cachedData = dataCache.putIfAbsent(locale, data);
529         // Use the earlier version if another thread beat us to it.
530         if (cachedData != null)
531           data = cachedData;
532       }
533     return data;
534   }
535 
536   /**
537    * This method initializes a new instance of <code>DateFormatSymbols</code>
538    * by loading the date format information for the specified locale.
539    * This constructor only obtains instances using the runtime's resources;
540    * to also include {@link java.text.spi.DateFormatSymbolsProvider} instances,
541    * call {@link #getInstance(java.util.Locale)} instead.
542    *
543    * @param locale The locale for which date formatting symbols should
544    *               be loaded.
545    * @throws MissingResourceException if the resources for the specified
546    *                                  locale could not be found or loaded.
547    * @see #getInstance(java.util.Locale)
548    */
DateFormatSymbols(Locale locale)549   public DateFormatSymbols (Locale locale)
550     throws MissingResourceException
551   {
552     DFSData data = retrieveData(locale);
553     ampms = data.getAMPMs();
554     eras = data.getEras();
555     localPatternChars = data.getLocalPatternChars();
556     months = data.getMonths();
557     shortMonths = data.getShortMonths();
558     weekdays = data.getWeekdays();
559     shortWeekdays = data.getShortWeekdays();
560     dateFormats = data.getDateFormats();
561     timeFormats = data.getTimeFormats();
562     runtimeZoneStrings = data.getZoneStrings();
563   }
564 
565   /**
566    * This method loads the format symbol information for the default
567    * locale. This constructor only obtains instances using the runtime's resources;
568    * to also include {@link java.text.spi.DateFormatSymbolsProvider} instances,
569    * call {@link #getInstance()} instead.
570    *
571    * @throws MissingResourceException if the resources for the default
572    *                                  locale could not be found or loaded.
573    * @see #getInstance()
574    */
DateFormatSymbols()575   public DateFormatSymbols()
576     throws MissingResourceException
577   {
578     this (Locale.getDefault());
579   }
580 
581   /**
582    * This method returns the list of strings used for displaying AM or PM.
583    * This is a two element <code>String</code> array indexed by
584    * <code>Calendar.AM</code> and <code>Calendar.PM</code>
585    *
586    * @return The list of AM/PM display strings.
587    */
getAmPmStrings()588   public String[] getAmPmStrings()
589   {
590     return ampms;
591   }
592 
593   /**
594     * This method returns the list of strings used for displaying eras
595     * (e.g., "BC" and "AD").  This is a two element <code>String</code>
596     * array indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
597     *
598     * @return The list of era disply strings.
599     */
getEras()600   public String[] getEras()
601   {
602     return eras;
603   }
604 
605   /**
606     * This method returns the pattern character information for this
607     * object.  This is an 18 character string that contains the characters
608     * that are used in creating the date formatting strings in
609     * <code>SimpleDateFormat</code>.   The following are the character
610     * positions in the string and which format character they correspond
611     * to (the character in parentheses is the default value in the US English
612     * locale):
613     * <p>
614     * <ul>
615     * <li>0 - era (G)</li>
616     * <li>1 - year (y)</li>
617     * <li>2 - month (M)</li>
618     * <li>3 - day of month (d)</li>
619     * <li>4 - hour out of 12, from 1-12 (h)</li>
620     * <li>5 - hour out of 24, from 0-23 (H)</li>
621     * <li>6 - minute (m)</li>
622     * <li>7 - second (s)</li>
623     * <li>8 - millisecond (S)</li>
624     * <li>9 - date of week (E)</li>
625     * <li>10 - date of year (D)</li>
626     * <li>11 - day of week in month, eg. "4th Thur in Nov" (F)</li>
627     * <li>12 - week in year (w)</li>
628     * <li>13 - week in month (W)</li>
629     * <li>14 - am/pm (a)</li>
630     * <li>15 - hour out of 24, from 1-24 (k)</li>
631     * <li>16 - hour out of 12, from 0-11 (K)</li>
632     * <li>17 - time zone (z)</li>
633     * </ul>
634     *
635     * @return The format patter characters
636     */
getLocalPatternChars()637   public String getLocalPatternChars()
638   {
639     return localPatternChars;
640   }
641 
642   /**
643    * This method returns the list of strings used for displaying month
644    * names (e.g., "January" and "February").  This is a thirteen element
645    * string array indexed by <code>Calendar.JANUARY</code> through
646    * <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
647    * elements because some calendars have thriteen months.
648    *
649    * @return The list of month display strings.
650    */
getMonths()651   public String[] getMonths ()
652   {
653     return months;
654   }
655 
656   /**
657    * This method returns the list of strings used for displaying abbreviated
658    * month names (e.g., "Jan" and "Feb").  This is a thirteen element
659    * <code>String</code> array indexed by <code>Calendar.JANUARY</code>
660    * through <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
661    * elements because some calendars have thirteen months.
662    *
663    * @return The list of abbreviated month display strings.
664    */
getShortMonths()665   public String[] getShortMonths ()
666   {
667     return shortMonths;
668   }
669 
670   /**
671    * This method returns the list of strings used for displaying abbreviated
672    * weekday names (e.g., "Sun" and "Mon").  This is an eight element
673    * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
674    * through <code>Calendar.SATURDAY</code>.  Note that the first element
675    * of this array is ignored.
676    *
677    * @return This list of abbreviated weekday display strings.
678    */
getShortWeekdays()679   public String[] getShortWeekdays ()
680   {
681     return shortWeekdays;
682   }
683 
684   /**
685    * This method returns the list of strings used for displaying weekday
686    * names (e.g., "Sunday" and "Monday").  This is an eight element
687    * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
688    * through <code>Calendar.SATURDAY</code>.  Note that the first element
689    * of this array is ignored.
690    *
691    * @return This list of weekday display strings.
692    */
getWeekdays()693   public String[] getWeekdays ()
694   {
695     return weekdays;
696   }
697 
698   /**
699    * This method returns this list of localized timezone display strings.
700    * This is a two dimensional <code>String</code> array where each row in
701    * the array contains five values:
702    * <P>
703    * <ul>
704    * <li>0 - The non-localized time zone id string.</li>
705    * <li>1 - The long name of the time zone (standard time).</li>
706    * <li>2 - The short name of the time zone (standard time).</li>
707    * <li>3 - The long name of the time zone (daylight savings time).</li>
708    * <li>4 - the short name of the time zone (daylight savings time).</li>
709    * </ul>
710    * <p>
711    * If {@link #setZoneStrings(String[][])} has been called, then the value
712    * passed to this will be returned.  Otherwise the returned array contains
713    * zone names provided by the runtime environment and any
714    * {@link java.util.spi.TimeZoneProvider} instances.
715    * </p>
716    *
717    * @return The list of time zone display strings.
718    * @see #setZoneStrings(String[][])
719    */
getZoneStrings()720   public String[][] getZoneStrings()
721   {
722     if (zoneStrings != null)
723       return zoneStrings;
724     return runtimeZoneStrings;
725   }
726 
727   /**
728    * This method sets the list of strings used to display AM/PM values to
729    * the specified list.
730    * This is a two element <code>String</code> array indexed by
731    * <code>Calendar.AM</code> and <code>Calendar.PM</code>
732    *
733    * @param value The new list of AM/PM display strings.
734    */
setAmPmStrings(String[] value)735   public void setAmPmStrings (String[] value)
736   {
737     if(value==null)
738       throw new NullPointerException();
739     ampms = value;
740   }
741 
742   /**
743    * This method sets the list of strings used to display time eras to
744    * to the specified list.
745    * This is a two element <code>String</code>
746    * array indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
747    *
748    * @param labels The new list of era display strings.
749    */
setEras(String[] labels)750   public void setEras (String[] labels)
751   {
752     if(labels==null)
753       throw new NullPointerException();
754     eras = labels;
755   }
756 
757   /**
758     * This method sets the list of characters used to specific date/time
759     * formatting strings.
760     * This is an 18 character string that contains the characters
761     * that are used in creating the date formatting strings in
762     * <code>SimpleDateFormat</code>.   The following are the character
763     * positions in the string and which format character they correspond
764     * to (the character in parentheses is the default value in the US English
765     * locale):
766     * <p>
767     * <ul>
768     * <li>0 - era (G)</li>
769     * <li>1 - year (y)</li>
770     * <li>2 - month (M)</li>
771     * <li>3 - day of month (d)</li>
772     * <li>4 - hour out of 12, from 1-12 (h)</li>
773     * <li>5 - hour out of 24, from 0-23 (H)</li>
774     * <li>6 - minute (m)</li>
775     * <li>7 - second (s)</li>
776     * <li>8 - millisecond (S)</li>
777     * <li>9 - date of week (E)</li>
778     * <li>10 - date of year (D)</li>
779     * <li>11 - day of week in month, eg. "4th Thur in Nov" (F)</li>
780     * <li>12 - week in year (w)</li>
781     * <li>13 - week in month (W)</li>
782     * <li>14 - am/pm (a)</li>
783     * <li>15 - hour out of 24, from 1-24 (k)</li>
784     * <li>16 - hour out of 12, from 0-11 (K)</li>
785     * <li>17 - time zone (z)</li>
786     * </ul>
787     *
788     * @param chars The new format pattern characters
789     */
setLocalPatternChars(String chars)790   public void setLocalPatternChars (String chars)
791   {
792     if(chars==null)
793       throw new NullPointerException();
794     localPatternChars = chars;
795   }
796 
797   /**
798     * This method sets the list of strings used to display month names.
799     * This is a thirteen element
800     * string array indexed by <code>Calendar.JANUARY</code> through
801     * <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
802     * elements because some calendars have thriteen months.
803     *
804     * @param labels The list of month display strings.
805     */
setMonths(String[] labels)806   public void setMonths (String[] labels)
807   {
808     if(labels==null)
809       throw new NullPointerException();
810     months = labels;
811   }
812 
813   /**
814    * This method sets the list of strings used to display abbreviated month
815    * names.
816    * This is a thirteen element
817    * <code>String</code> array indexed by <code>Calendar.JANUARY</code>
818    * through <code>Calendar.UNDECEMBER</code>.  Note that there are thirteen
819    * elements because some calendars have thirteen months.
820    *
821    * @param labels The new list of abbreviated month display strings.
822    */
setShortMonths(String[] labels)823   public void setShortMonths (String[] labels)
824   {
825     if(labels==null)
826       throw new NullPointerException();
827     shortMonths = labels;
828   }
829 
830   /**
831    * This method sets the list of strings used to display abbreviated
832    * weekday names.
833    * This is an eight element
834    * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
835    * through <code>Calendar.SATURDAY</code>.  Note that the first element
836    * of this array is ignored.
837    *
838    * @param labels This list of abbreviated weekday display strings.
839    */
setShortWeekdays(String[] labels)840   public void setShortWeekdays (String[] labels)
841   {
842     if(labels==null)
843       throw new NullPointerException();
844     shortWeekdays = labels;
845   }
846 
847   /**
848    * This method sets the list of strings used to display weekday names.
849    * This is an eight element
850    * <code>String</code> array indexed by <code>Calendar.SUNDAY</code>
851    * through <code>Calendar.SATURDAY</code>.  Note that the first element
852    * of this array is ignored.
853    *
854    * @param labels This list of weekday display strings.
855    */
setWeekdays(String[] labels)856   public void setWeekdays (String[] labels)
857   {
858     if(labels==null)
859       throw new NullPointerException();
860     weekdays = labels;
861   }
862 
863   /**
864    * This method sets the list of display strings for time zones.
865    * This is a two dimensional <code>String</code> array where each row in
866    * the array contains five values:
867    * <P>
868    * <ul>
869    * <li>0 - The non-localized time zone id string.</li>
870    * <li>1 - The long name of the time zone (standard time).</li>
871    * <li>2 - The short name of the time zone (standard time).</li>
872    * <li>3 - The long name of the time zone (daylight savings time).</li>
873    * <li>4 - the short name of the time zone (daylight savings time).</li>
874    * </ul>
875    *
876    * @params zones The list of time zone display strings.
877    */
setZoneStrings(String[][] zones)878   public void setZoneStrings (String[][] zones)
879   {
880     if(zones==null)
881       throw new NullPointerException();
882     zoneStrings = zones;
883   }
884 
885   /* Does a "deep" equality test - recurses into arrays. */
equals(Object x, Object y)886   private static boolean equals (Object x, Object y)
887   {
888     if (x == y)
889       return true;
890     if (x == null || y == null)
891       return false;
892     if (! (x instanceof Object[]) || ! (y instanceof Object[]))
893       return x.equals(y);
894     Object[] xa = (Object[]) x;
895     Object[] ya = (Object[]) y;
896     if (xa.length != ya.length)
897       return false;
898     for (int i = xa.length;  --i >= 0; )
899       {
900         if (! equals(xa[i], ya[i]))
901           return false;
902       }
903     return true;
904   }
905 
hashCode(Object x)906   private static int hashCode (Object x)
907   {
908     if (x == null)
909       return 0;
910     if (! (x instanceof Object[]))
911       return x.hashCode();
912     Object[] xa = (Object[]) x;
913     int hash = 0;
914     for (int i = 0;  i < xa.length;  i++)
915       hash = 37 * hashCode(xa[i]);
916     return hash;
917   }
918 
919   /**
920    * This method tests a specified object for equality against this object.
921    * This will be true if and only if the specified object:
922    * <p>
923    * <ul>
924    * <li> Is not <code>null</code>.</li>
925    * <li> Is an instance of <code>DateFormatSymbols</code>.</li>
926    * <li> Contains identical formatting symbols to this object.</li>
927    * </ul>
928    *
929    * @param obj The <code>Object</code> to test for equality against.
930    *
931    * @return <code>true</code> if the specified object is equal to this one,
932    * <code>false</code> otherwise.
933    */
equals(Object obj)934   public boolean equals (Object obj)
935   {
936     if (! (obj instanceof DateFormatSymbols))
937       return false;
938     DateFormatSymbols other = (DateFormatSymbols) obj;
939     return (equals(ampms, other.ampms)
940             && equals(eras, other.eras)
941             && equals(localPatternChars, other.localPatternChars)
942             && equals(months, other.months)
943             && equals(shortMonths, other.shortMonths)
944             && equals(shortWeekdays, other.shortWeekdays)
945             && equals(weekdays, other.weekdays)
946             && equals(zoneStrings, other.zoneStrings));
947   }
948 
949   /**
950    * Returns a new copy of this object.
951    *
952    * @return A copy of this object
953    */
clone()954   public Object clone ()
955   {
956     try
957       {
958         return super.clone ();
959       }
960     catch (CloneNotSupportedException e)
961       {
962         return null;
963       }
964   }
965 
966   /**
967    * This method returns a hash value for this object.
968    *
969    * @return A hash value for this object.
970    */
hashCode()971   public int hashCode ()
972   {
973     return (hashCode(ampms)
974             ^ hashCode(eras)
975             ^ hashCode(localPatternChars)
976             ^ hashCode(months)
977             ^ hashCode(shortMonths)
978             ^ hashCode(shortWeekdays)
979             ^ hashCode(weekdays)
980             ^ hashCode(zoneStrings));
981   }
982 
983   /**
984    * Returns a {@link DateFormatSymbols} instance for the
985    * default locale obtained from either the runtime itself
986    * or one of the installed
987    * {@link java.text.spi.DateFormatSymbolsProvider} instances.
988    * This is equivalent to calling
989    * <code>getInstance(Locale.getDefault())</code>.
990    *
991    * @return a {@link DateFormatSymbols} instance for the default
992    *         locale.
993    * @since 1.6
994    */
getInstance()995   public static final DateFormatSymbols getInstance()
996   {
997     return getInstance(Locale.getDefault());
998   }
999 
1000   /**
1001    * Returns a {@link DateFormatSymbols} instance for the
1002    * specified locale obtained from either the runtime itself
1003    * or one of the installed
1004    * {@link java.text.spi.DateFormatSymbolsProvider} instances.
1005    *
1006    * @param locale the locale for which an instance should be
1007    *               returned.
1008    * @return a {@link DateFormatSymbols} instance for the specified
1009    *         locale.
1010    * @throws NullPointerException if <code>locale</code> is
1011    *                              <code>null</code>.
1012    * @since 1.6
1013    */
getInstance(Locale locale)1014   public static final DateFormatSymbols getInstance(Locale locale)
1015   {
1016     try
1017       {
1018         DateFormatSymbols syms = new DateFormatSymbols(locale);
1019         return syms;
1020       }
1021     catch (MissingResourceException e)
1022       {
1023         /* This means runtime support for the locale
1024          * is not available, so we check providers. */
1025       }
1026     for (DateFormatSymbolsProvider p :
1027            ServiceLoader.load(DateFormatSymbolsProvider.class))
1028       {
1029         for (Locale loc : p.getAvailableLocales())
1030           {
1031             if (loc.equals(locale))
1032               {
1033                 DateFormatSymbols syms = p.getInstance(locale);
1034                 if (syms != null)
1035                   return syms;
1036                 break;
1037               }
1038           }
1039       }
1040     return getInstance(LocaleHelper.getFallbackLocale(locale));
1041   }
1042 
1043 }
1044