1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/*
4 *******************************************************************************
5 * Copyright (C) 1996-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
8 */
9
10package com.ibm.icu.text;
11
12import java.io.IOException;
13import java.io.ObjectInputStream;
14import java.io.Serializable;
15import java.util.ArrayList;
16import java.util.HashMap;
17import java.util.HashSet;
18import java.util.List;
19import java.util.Locale;
20import java.util.Map;
21import java.util.MissingResourceException;
22import java.util.ResourceBundle;
23import java.util.Set;
24import java.util.TreeMap;
25
26import com.ibm.icu.impl.CacheBase;
27import com.ibm.icu.impl.CalendarUtil;
28import com.ibm.icu.impl.ICUData;
29import com.ibm.icu.impl.ICUResourceBundle;
30import com.ibm.icu.impl.SoftCache;
31import com.ibm.icu.impl.UResource;
32import com.ibm.icu.impl.Utility;
33import com.ibm.icu.text.TimeZoneNames.NameType;
34import com.ibm.icu.util.Calendar;
35import com.ibm.icu.util.ICUCloneNotSupportedException;
36import com.ibm.icu.util.ICUException;
37import com.ibm.icu.util.TimeZone;
38import com.ibm.icu.util.ULocale;
39import com.ibm.icu.util.ULocale.Category;
40import com.ibm.icu.util.UResourceBundle;
41import com.ibm.icu.util.UResourceBundleIterator;
42
43/**
44 * {@icuenhanced java.text.DateFormatSymbols}.{@icu _usage_}
45 *
46 * <p><code>DateFormatSymbols</code> is a public class for encapsulating
47 * localizable date-time formatting data, such as the names of the
48 * months, the names of the days of the week, and the time zone data.
49 * <code>DateFormat</code> and <code>SimpleDateFormat</code> both use
50 * <code>DateFormatSymbols</code> to encapsulate this information.
51 *
52 * <p>Typically you shouldn't use <code>DateFormatSymbols</code> directly.
53 * Rather, you are encouraged to create a date-time formatter with the
54 * <code>DateFormat</code> class's factory methods: <code>getTimeInstance</code>,
55 * <code>getDateInstance</code>, or <code>getDateTimeInstance</code>.
56 * These methods automatically create a <code>DateFormatSymbols</code> for
57 * the formatter so that you don't have to. After the
58 * formatter is created, you may modify its format pattern using the
59 * <code>setPattern</code> method. For more information about
60 * creating formatters using <code>DateFormat</code>'s factory methods,
61 * see {@link DateFormat}.
62 *
63 * <p>If you decide to create a date-time formatter with a specific
64 * format pattern for a specific locale, you can do so with:
65 * <blockquote>
66 * <pre>
67 * new SimpleDateFormat(aPattern, new DateFormatSymbols(aLocale)).
68 * </pre>
69 * </blockquote>
70 *
71 * <p><code>DateFormatSymbols</code> objects are clonable. When you obtain
72 * a <code>DateFormatSymbols</code> object, feel free to modify the
73 * date-time formatting data. For instance, you can replace the localized
74 * date-time format pattern characters with the ones that you feel easy
75 * to remember. Or you can change the representative cities
76 * to your favorite ones.
77 *
78 * <p>New <code>DateFormatSymbols</code> subclasses may be added to support
79 * <code>SimpleDateFormat</code> for date-time formatting for additional locales.
80 *
81 * @see          DateFormat
82 * @see          SimpleDateFormat
83 * @see          com.ibm.icu.util.SimpleTimeZone
84 * @author       Chen-Lieh Huang
85 * @stable ICU 2.0
86 */
87public class DateFormatSymbols implements Serializable, Cloneable {
88
89    // TODO make sure local pattern char string is 18 characters long,
90    // that is, that it encompasses the new 'u' char for
91    // EXTENDED_YEAR.  Two options: 1. Make sure resource data is
92    // correct; 2. Make code add in 'u' at end if len == 17.
93
94    // Constants for context
95    /**
96     * {@icu} Constant for context.
97     * @stable ICU 3.6
98     */
99    public static final int FORMAT = 0;
100
101    /**
102     * {@icu} Constant for context.
103     * @stable ICU 3.6
104     */
105    public static final int STANDALONE = 1;
106
107    /**
108     * {@icu} Constant for context. NUMERIC context
109     * is only supported for leapMonthPatterns.
110     * @internal
111     * @deprecated This API is ICU internal only.
112     */
113    @Deprecated
114    public static final int NUMERIC = 2;
115
116    /**
117     * {@icu} Constant for context.
118     * @internal
119     * @deprecated This API is ICU internal only.
120     */
121    @Deprecated
122    public static final int DT_CONTEXT_COUNT = 3;
123
124    // Constants for width
125
126    /**
127     * {@icu} Constant for width.
128     * @stable ICU 3.6
129     */
130    public static final int ABBREVIATED = 0;
131
132    /**
133     * {@icu} Constant for width.
134     * @stable ICU 3.6
135     */
136    public static final int WIDE = 1;
137
138    /**
139     * {@icu} Constant for width.
140     * @stable ICU 3.6
141     */
142    public static final int NARROW = 2;
143
144    /**
145     * {@icu} Constant for width; only supported for weekdays.
146     * @stable ICU 51
147     */
148    public static final int SHORT = 3;
149
150    /**
151     * {@icu} Constant for width.
152     * @internal
153     * @deprecated This API is ICU internal only.
154     */
155    @Deprecated
156    public static final int DT_WIDTH_COUNT = 4;
157
158    /**
159     * {@icu} Somewhat temporary constant for leap month pattern type, adequate for Chinese calendar.
160     * @internal
161     */
162    static final int DT_LEAP_MONTH_PATTERN_FORMAT_WIDE = 0;
163
164    /**
165     * {@icu} Somewhat temporary constant for leap month pattern type, adequate for Chinese calendar.
166     * @internal
167     */
168    static final int DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV = 1;
169
170    /**
171     * {@icu} Somewhat temporary constant for leap month pattern type, adequate for Chinese calendar.
172     * @internal
173     */
174    static final int DT_LEAP_MONTH_PATTERN_FORMAT_NARROW = 2;
175
176    /**
177     * {@icu} Somewhat temporary constant for leap month pattern type, adequate for Chinese calendar.
178     * @internal
179     */
180    static final int DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE = 3;
181
182    /**
183     * {@icu} Somewhat temporary constant for leap month pattern type, adequate for Chinese calendar.
184     * @internal
185     */
186    static final int DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV = 4;
187
188    /**
189     * {@icu} Somewhat temporary constant for leap month pattern type, adequate for Chinese calendar.
190     * @internal
191     */
192    static final int DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW = 5;
193
194    /**
195     * {@icu} Somewhat temporary constant for leap month pattern type, adequate for Chinese calendar.
196     * @internal
197     */
198    static final int DT_LEAP_MONTH_PATTERN_NUMERIC = 6;
199
200    /**
201     * {@icu} Somewhat temporary constant for month pattern count, adequate for Chinese calendar.
202     * @internal
203     */
204    static final int DT_MONTH_PATTERN_COUNT = 7;
205
206    /**
207     * {@icu} This default time separator is used for formatting when the locale
208     * doesn't specify any time separator, and always recognized when parsing.
209     * @internal
210     */
211    static final String DEFAULT_TIME_SEPARATOR = ":";
212
213    /**
214     * {@icu} This alternate time separator is always recognized when parsing.
215     * @internal
216     */
217    static final String ALTERNATE_TIME_SEPARATOR = ".";
218
219   /**
220     * Constructs a DateFormatSymbols object by loading format data from
221     * resources for the default <code>FORMAT</code> locale.
222     *
223     * @throws java.util.MissingResourceException if the resources for the default locale
224     *          cannot be found or cannot be loaded.
225     * @see Category#FORMAT
226     * @stable ICU 2.0
227     */
228    public DateFormatSymbols()
229    {
230        this(ULocale.getDefault(Category.FORMAT));
231    }
232
233    /**
234     * Constructs a DateFormatSymbols object by loading format data from
235     * resources for the given locale.
236     *
237     * @throws java.util.MissingResourceException if the resources for the specified
238     *          locale cannot be found or cannot be loaded.
239     * @stable ICU 2.0
240     */
241    public DateFormatSymbols(Locale locale)
242    {
243        this(ULocale.forLocale(locale));
244    }
245
246    /**
247     * {@icu} Constructs a DateFormatSymbols object by loading format data from
248     * resources for the given ulocale.
249     *
250     * @throws java.util.MissingResourceException if the resources for the specified
251     *          locale cannot be found or cannot be loaded.
252     * @stable ICU 3.2
253     */
254    public DateFormatSymbols(ULocale locale)
255    {
256        initializeData(locale, CalendarUtil.getCalendarType(locale));
257    }
258
259    /**
260     * Returns a DateFormatSymbols instance for the default locale.
261     *
262     * {@icunote} Unlike <code>java.text.DateFormatSymbols#getInstance</code>,
263     * this method simply returns <code>new com.ibm.icu.text.DateFormatSymbols()</code>.
264     * ICU does not support <code>DateFormatSymbolsProvider</code> introduced in Java 6
265     * or its equivalent implementation for now.
266     *
267     * @return A DateFormatSymbols instance.
268     * @stable ICU 3.8
269     */
270    public static DateFormatSymbols getInstance() {
271        return new DateFormatSymbols();
272    }
273
274    /**
275     * Returns a DateFormatSymbols instance for the given locale.
276     *
277     * {@icunote} Unlike <code>java.text.DateFormatSymbols#getInstance</code>,
278     * this method simply returns <code>new com.ibm.icu.text.DateFormatSymbols(locale)</code>.
279     * ICU does not support <code>DateFormatSymbolsProvider</code> introduced in Java 6
280     * or its equivalent implementation for now.
281     *
282     * @param locale the locale.
283     * @return A DateFormatSymbols instance.
284     * @stable ICU 3.8
285     */
286    public static DateFormatSymbols getInstance(Locale locale) {
287        return new DateFormatSymbols(locale);
288    }
289
290    /**
291     * {@icu} Returns a DateFormatSymbols instance for the given locale.
292     *
293     * {@icunote} Unlike <code>java.text.DateFormatSymbols#getInstance</code>,
294     * this method simply returns <code>new com.ibm.icu.text.DateFormatSymbols(locale)</code>.
295     * ICU does not support <code>DateFormatSymbolsProvider</code> introduced in Java 6
296     * or its equivalent implementation for now.
297     *
298     * @param locale the locale.
299     * @return A DateFormatSymbols instance.
300     * @stable ICU 3.8
301     */
302    public static DateFormatSymbols getInstance(ULocale locale) {
303        return new DateFormatSymbols(locale);
304    }
305
306    /**
307     * Returns an array of all locales for which the <code>getInstance</code> methods of
308     * this class can return localized instances.
309     *
310     * {@icunote} Unlike <code>java.text.DateFormatSymbols#getAvailableLocales</code>,
311     * this method simply returns the array of <code>Locale</code>s available in this
312     * class.  ICU does not support <code>DateFormatSymbolsProvider</code> introduced in
313     * Java 6 or its equivalent implementation for now.
314     *
315     * @return An array of <code>Locale</code>s for which localized
316     * <code>DateFormatSymbols</code> instances are available.
317     * @stable ICU 3.8
318     */
319    public static Locale[] getAvailableLocales() {
320        return ICUResourceBundle.getAvailableLocales();
321    }
322
323    /**
324     * {@icu} Returns an array of all locales for which the <code>getInstance</code>
325     * methods of this class can return localized instances.
326     *
327     * {@icunote} Unlike <code>java.text.DateFormatSymbols#getAvailableLocales</code>,
328     * this method simply returns the array of <code>ULocale</code>s available in this
329     * class.  ICU does not support <code>DateFormatSymbolsProvider</code> introduced in
330     * Java 6 or its equivalent implementation for now.
331     *
332     * @return An array of <code>ULocale</code>s for which localized
333     * <code>DateFormatSymbols</code> instances are available.
334     * @draft ICU 3.8 (retain)
335     * @provisional This API might change or be removed in a future release.
336     */
337    public static ULocale[] getAvailableULocales() {
338        return ICUResourceBundle.getAvailableULocales();
339    }
340
341    /**
342     * Era strings. For example: "AD" and "BC".  An array of 2 strings,
343     * indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
344     * @serial
345     */
346    String eras[] = null;
347
348    /**
349     * Era name strings. For example: "Anno Domini" and "Before Christ".  An array of 2 strings,
350     * indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
351     * @serial
352     */
353    String eraNames[] = null;
354
355    /**
356     * Narrow era names. For example: "A" and "B". An array of 2 strings,
357     * indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
358     * @serial
359     */
360    String narrowEras[] = null;
361
362    /**
363     * Month strings. For example: "January", "February", etc.  An array
364     * of 13 strings (some calendars have 13 months), indexed by
365     * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
366     * @serial
367     */
368    String months[] = null;
369
370    /**
371     * Short month strings. For example: "Jan", "Feb", etc.  An array of
372     * 13 strings (some calendars have 13 months), indexed by
373     * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
374
375     * @serial
376     */
377    String shortMonths[] = null;
378
379    /**
380     * Narrow month strings. For example: "J", "F", etc.  An array of
381     * 13 strings (some calendars have 13 months), indexed by
382     * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
383
384     * @serial
385     */
386    String narrowMonths[] = null;
387
388    /**
389     * Standalone month strings. For example: "January", "February", etc.  An array
390     * of 13 strings (some calendars have 13 months), indexed by
391     * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
392     * @serial
393     */
394    String standaloneMonths[] = null;
395
396    /**
397     * Standalone short month strings. For example: "Jan", "Feb", etc.  An array of
398     * 13 strings (some calendars have 13 months), indexed by
399     * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
400
401     * @serial
402     */
403    String standaloneShortMonths[] = null;
404
405    /**
406     * Standalone narrow month strings. For example: "J", "F", etc.  An array of
407     * 13 strings (some calendars have 13 months), indexed by
408     * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
409
410     * @serial
411     */
412    String standaloneNarrowMonths[] = null;
413
414    /**
415     * Format wide weekday strings, for example: "Sunday", "Monday", etc.
416     * An array of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
417     * <code>Calendar.MONDAY</code>, etc.
418     * The element <code>weekdays[0]</code> is ignored.
419     * @serial
420     */
421    String weekdays[] = null;
422
423    /**
424     * CLDR-style format abbreviated (not short) weekday strings,
425     * for example: "Sun", "Mon", etc.
426     * An array of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
427     * <code>Calendar.MONDAY</code>, etc.
428     * The element <code>shortWeekdays[0]</code> is ignored.
429     * @serial
430     */
431    String shortWeekdays[] = null;
432
433    /**
434     * CLDR-style format short weekday strings, for example: "Su", "Mo", etc.
435     * An array of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
436     * <code>Calendar.MONDAY</code>, etc.
437     * The element <code>shorterWeekdays[0]</code> is ignored.
438     * @serial
439     */
440   // Note, serialization restore from pre-ICU-51 will leave this null.
441    String shorterWeekdays[] = null;
442
443    /**
444     * CLDR-style format narrow weekday strings, for example: "S", "M", etc.
445     * An array of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
446     * <code>Calendar.MONDAY</code>, etc.
447     * The element <code>narrowWeekdays[0]</code> is ignored.
448     * @serial
449     */
450    String narrowWeekdays[] = null;
451
452    /**
453     * Standalone wide weekday strings. For example: "Sunday", "Monday", etc.
454     * An array of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
455     * <code>Calendar.MONDAY</code>, etc.
456     * The element <code>standaloneWeekdays[0]</code> is ignored.
457     * @serial
458     */
459    String standaloneWeekdays[] = null;
460
461    /**
462     * CLDR-style standalone abbreviated (not short) weekday strings,
463     * for example: "Sun", "Mon", etc.
464     * An array of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
465     * <code>Calendar.MONDAY</code>, etc.
466     * The element <code>standaloneShortWeekdays[0]</code> is ignored.
467     * @serial
468     */
469    String standaloneShortWeekdays[] = null;
470
471    /**
472     * CLDR-style standalone short weekday strings, for example: "Sun", "Mon", etc.
473     * An array of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
474     * <code>Calendar.MONDAY</code>, etc.
475     * The element <code>standaloneShorterWeekdays[0]</code> is ignored.
476     * @serial
477     */
478    // Note, serialization restore from pre-ICU-51 will leave this null.
479    String standaloneShorterWeekdays[] = null;
480
481    /**
482     * Standalone narrow weekday strings. For example: "S", "M", etc.  An array
483     * of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
484     * <code>Calendar.MONDAY</code>, etc.
485     * The element <code>standaloneNarrowWeekdays[0]</code> is ignored.
486     * @serial
487     */
488    String standaloneNarrowWeekdays[] = null;
489
490    /**
491     * AM and PM strings. For example: "AM" and "PM".  An array of
492     * 2 strings, indexed by <code>Calendar.AM</code> and
493     * <code>Calendar.PM</code>.
494     * @serial
495     */
496    String ampms[] = null;
497
498    /**
499     * narrow AM and PM strings. For example: "a" and "p".  An array of
500     * 2 strings, indexed by <code>Calendar.AM</code> and
501     * <code>Calendar.PM</code>.
502     * @serial
503     */
504    String ampmsNarrow[] = null;
505
506    /**
507     * Time separator string. For example: ":".
508     * @serial
509     */
510    private String timeSeparator = null;
511
512    /**
513     * Abbreviated quarter names. For example: "Q1", "Q2", "Q3", "Q4". An array
514     * of 4 strings indexed by the month divided by 3.
515     * @serial
516     */
517    String shortQuarters[] = null;
518
519    /**
520     * Full quarter names. For example: "1st Quarter", "2nd Quarter", "3rd Quarter",
521     * "4th Quarter". An array of 4 strings, indexed by the month divided by 3.
522     * @serial
523     */
524    String quarters[] = null;
525
526    /**
527     * Standalone abbreviated quarter names. For example: "Q1", "Q2", "Q3", "Q4". An array
528     * of 4 strings indexed by the month divided by 3.
529     * @serial
530     */
531    String standaloneShortQuarters[] = null;
532
533    /**
534     * Standalone full quarter names. For example: "1st Quarter", "2nd Quarter", "3rd Quarter",
535     * "4th Quarter". An array of 4 strings, indexed by the month divided by 3.
536     * @serial
537     */
538    String standaloneQuarters[] = null;
539
540    /**
541     * All leap month patterns, for example "{0}bis".
542     * An array of DT_MONTH_PATTERN_COUNT strings, indexed by the DT_LEAP_MONTH_PATTERN_XXX value.
543     * @serial
544     */
545    String leapMonthPatterns[] = null;
546
547     /**
548     * Cyclic year names, for example: "jia-zi", "yi-chou", ... "gui-hai".
549     * An array of (normally) 60 strings, corresponding to cyclic years 1-60 (in Calendar YEAR field).
550     * Currently we only have data for format/abbreviated.
551     * For the others, just get from format/abbreviated, ignore set.
552     * @serial
553     */
554    String shortYearNames[] = null;
555
556     /**
557     * Cyclic zodiac names, for example: "Rat", "Ox", "Tiger", etc.
558     * An array of (normally) 12 strings.
559     * Currently we only have data for format/abbreviated.
560     * For the others, just get from format/abbreviated, ignore set.
561     * @serial
562     */
563    String shortZodiacNames[] = null;
564
565   /**
566     * Localized names of time zones in this locale.  This is a
567     * two-dimensional array of strings of size <em>n</em> by <em>m</em>,
568     * where <em>m</em> is at least 5 and up to 7.  Each of the <em>n</em> rows is an
569     * entry containing the localized names for a single <code>TimeZone</code>.
570     * Each such row contains (with <code>i</code> ranging from
571     * 0..<em>n</em>-1):
572     * <ul>
573     * <li><code>zoneStrings[i][0]</code> - time zone ID</li>
574     * <li><code>zoneStrings[i][1]</code> - long name of zone in standard
575     * time</li>
576     * <li><code>zoneStrings[i][2]</code> - short name of zone in
577     * standard time</li>
578     * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight
579     * savings time</li>
580     * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight
581     * savings time</li>
582     * <li><code>zoneStrings[i][5]</code> - location name of zone</li>
583     * <li><code>zoneStrings[i][6]</code> - long generic name of zone</li>
584     * <li><code>zoneStrings[i][7]</code> - short generic of zone</li>
585    *  </ul>
586     * The zone ID is <em>not</em> localized; it corresponds to the ID
587     * value associated with a system time zone object.  All other entries
588     * are localized names.  If a zone does not implement daylight savings
589     * time, the daylight savings time names are ignored.
590     * <em>Note:</em>CLDR 1.5 introduced metazone and its historical mappings.
591     * This simple two-dimensional array is no longer sufficient to represent
592     * localized names and its historic changes.  Since ICU 3.8.1, localized
593     * zone names extracted from ICU locale data is stored in a ZoneStringFormat
594     * instance.  But we still need to support the old way of customizing
595     * localized zone names, so we keep this field for the purpose.
596     * @see com.ibm.icu.util.TimeZone
597     * @serial
598     */
599    private String zoneStrings[][] = null;
600
601     /**
602     * Unlocalized date-time pattern characters. For example: 'y', 'd', etc.
603     * All locales use the same unlocalized pattern characters.
604     */
605    static final String patternChars = "GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxrbB";
606
607    /**
608     * Localized date-time pattern characters. For example, a locale may
609     * wish to use 'u' rather than 'y' to represent years in its date format
610     * pattern strings.
611     * This string must be exactly 18 characters long, with the index of
612     * the characters described by <code>DateFormat.ERA_FIELD</code>,
613     * <code>DateFormat.YEAR_FIELD</code>, etc.  Thus, if the string were
614     * "Xz...", then localized patterns would use 'X' for era and 'z' for year.
615     * @serial
616     */
617    String localPatternChars = null;
618
619    /**
620     * Localized names for abbreviated (== short) day periods.
621     * An array of strings, in the order of DayPeriod constants.
622     */
623    String abbreviatedDayPeriods[] = null;
624
625    /**
626     * Localized names for wide day periods.
627     * An array of strings, in the order of DayPeriod constants.
628     */
629    String wideDayPeriods[] = null;
630
631    /**
632     * Localized names for narrow day periods.
633     * An array of strings, in the order of DayPeriod constants.
634     */
635    String narrowDayPeriods[] = null;
636
637    /**
638     * Localized names for standalone abbreviated (== short) day periods.
639     * An array of strings, in the order of DayPeriod constants.
640     */
641    String standaloneAbbreviatedDayPeriods[] = null;
642
643    /**
644     * Localized names for standalone wide day periods.
645     * An array of strings, in the order of DayPeriod constants.
646     */
647    String standaloneWideDayPeriods[] = null;
648
649    /**
650     * Localized names for standalone narrow day periods.
651     * An array of strings, in the order of DayPeriod constants.
652     */
653    String standaloneNarrowDayPeriods[] = null;
654
655    /* use serialVersionUID from JDK 1.1.4 for interoperability */
656    private static final long serialVersionUID = -5987973545549424702L;
657
658    private static final String[][] CALENDAR_CLASSES = {
659        {"GregorianCalendar", "gregorian"},
660        {"JapaneseCalendar", "japanese"},
661        {"BuddhistCalendar", "buddhist"},
662        {"TaiwanCalendar", "roc"},
663        {"PersianCalendar", "persian"},
664        {"IslamicCalendar", "islamic"},
665        {"HebrewCalendar", "hebrew"},
666        {"ChineseCalendar", "chinese"},
667        {"IndianCalendar", "indian"},
668        {"CopticCalendar", "coptic"},
669        {"EthiopicCalendar", "ethiopic"},
670    };
671
672    /**
673     * {@icu} Constants for capitalization context usage types
674     * related to date formatting.
675     * @internal
676     */
677    enum CapitalizationContextUsage {
678        OTHER,
679        MONTH_FORMAT,     /* except narrow */
680        MONTH_STANDALONE, /* except narrow */
681        MONTH_NARROW,
682        DAY_FORMAT,     /* except narrow */
683        DAY_STANDALONE, /* except narrow */
684        DAY_NARROW,
685        ERA_WIDE,
686        ERA_ABBREV,
687        ERA_NARROW,
688        ZONE_LONG,
689        ZONE_SHORT,
690        METAZONE_LONG,
691        METAZONE_SHORT
692    }
693
694    /** Map from resource key to CapitalizationContextUsage value
695     */
696    private static final Map<String, CapitalizationContextUsage> contextUsageTypeMap;
697    static {
698        contextUsageTypeMap=new HashMap<String, CapitalizationContextUsage>();
699        contextUsageTypeMap.put("month-format-except-narrow", CapitalizationContextUsage.MONTH_FORMAT);
700        contextUsageTypeMap.put("month-standalone-except-narrow", CapitalizationContextUsage.MONTH_STANDALONE);
701        contextUsageTypeMap.put("month-narrow",   CapitalizationContextUsage.MONTH_NARROW);
702        contextUsageTypeMap.put("day-format-except-narrow", CapitalizationContextUsage.DAY_FORMAT);
703        contextUsageTypeMap.put("day-standalone-except-narrow", CapitalizationContextUsage.DAY_STANDALONE);
704        contextUsageTypeMap.put("day-narrow",     CapitalizationContextUsage.DAY_NARROW);
705        contextUsageTypeMap.put("era-name",       CapitalizationContextUsage.ERA_WIDE);
706        contextUsageTypeMap.put("era-abbr",       CapitalizationContextUsage.ERA_ABBREV);
707        contextUsageTypeMap.put("era-narrow",     CapitalizationContextUsage.ERA_NARROW);
708        contextUsageTypeMap.put("zone-long",      CapitalizationContextUsage.ZONE_LONG);
709        contextUsageTypeMap.put("zone-short",     CapitalizationContextUsage.ZONE_SHORT);
710        contextUsageTypeMap.put("metazone-long",  CapitalizationContextUsage.METAZONE_LONG);
711        contextUsageTypeMap.put("metazone-short", CapitalizationContextUsage.METAZONE_SHORT);
712    }
713
714     /**
715     * Capitalization transforms. For each usage type, the first array element indicates
716     * whether to titlecase for uiListOrMenu context, the second indicates whether to
717     * titlecase for stand-alone context.
718     * @serial
719     */
720    Map<CapitalizationContextUsage,boolean[]> capitalization = null;
721
722    /**
723     * Returns era strings. For example: "AD" and "BC".
724     * @return the era strings.
725     * @stable ICU 2.0
726     */
727    public String[] getEras() {
728        return duplicate(eras);
729    }
730
731    /**
732     * Sets era strings. For example: "AD" and "BC".
733     * @param newEras the new era strings.
734     * @stable ICU 2.0
735     */
736    public void setEras(String[] newEras) {
737        eras = duplicate(newEras);
738    }
739
740    /**
741     * {@icu} Returns era name strings. For example: "Anno Domini" and "Before Christ".
742     * @return the era strings.
743     * @stable ICU 3.4
744     */
745    public String[] getEraNames() {
746        return duplicate(eraNames);
747    }
748
749    /**
750     * {@icu} Sets era name strings. For example: "Anno Domini" and "Before Christ".
751     * @param newEraNames the new era strings.
752     * @stable ICU 3.8
753     */
754    public void setEraNames(String[] newEraNames) {
755        eraNames = duplicate(newEraNames);
756    }
757
758    // Android patch (http://b/30464240) start: Add getter for narrow eras.
759    /**
760     * {@icu} Returns narrow era name strings. For example: "A" and "B".
761     * @return the era strings.
762     * @internal
763     * @deprecated This API is ICU internal only.
764     */
765    @Deprecated
766    public String[] getNarrowEras() {
767        return duplicate(narrowEras);
768    }
769    // Android patch end.
770
771    /**
772     * Returns month strings. For example: "January", "February", etc.
773     * @return the month strings.
774     * @stable ICU 2.0
775     */
776    public String[] getMonths() {
777        return duplicate(months);
778    }
779
780    /**
781     * Returns month strings. For example: "January", "February", etc.
782     * @param context    The month context, FORMAT or STANDALONE.
783     * @param width      The width or the returned month string,
784     *                   either WIDE, ABBREVIATED, or NARROW.
785     * @return the month strings.
786     * @stable ICU 3.4
787     */
788    public String[] getMonths(int context, int width) {
789        String [] returnValue = null;
790        switch (context) {
791           case FORMAT :
792              switch(width) {
793                 case WIDE :
794                    returnValue = months;
795                    break;
796                 case ABBREVIATED :
797                 case SHORT : // no month data for this, defaults to ABBREVIATED
798                    returnValue = shortMonths;
799                    break;
800                 case NARROW :
801                    returnValue = narrowMonths;
802                    break;
803              }
804              break;
805           case STANDALONE :
806              switch(width) {
807                 case WIDE :
808                    returnValue = standaloneMonths;
809                    break;
810                 case ABBREVIATED :
811                 case SHORT : // no month data for this, defaults to ABBREVIATED
812                    returnValue = standaloneShortMonths;
813                    break;
814                 case NARROW :
815                    returnValue = standaloneNarrowMonths;
816                    break;
817              }
818              break;
819        }
820        if (returnValue == null) {
821            throw new IllegalArgumentException("Bad context or width argument");
822        }
823        return duplicate(returnValue);
824    }
825
826    /**
827     * Sets month strings. For example: "January", "February", etc.
828     * @param newMonths the new month strings.
829     * @stable ICU 2.0
830     */
831    public void setMonths(String[] newMonths) {
832        months = duplicate(newMonths);
833    }
834
835    /**
836     * Sets month strings. For example: "January", "February", etc.
837     * @param newMonths the new month strings.
838     * @param context    The formatting context, FORMAT or STANDALONE.
839     * @param width      The width of the month string,
840     *                   either WIDE, ABBREVIATED, or NARROW.
841     * @stable ICU 3.8
842     */
843    public void setMonths(String[] newMonths, int context, int width) {
844        switch (context) {
845           case FORMAT :
846              switch(width) {
847                 case WIDE :
848                    months = duplicate(newMonths);
849                    break;
850                 case ABBREVIATED :
851                    shortMonths = duplicate(newMonths);
852                    break;
853                 case NARROW :
854                    narrowMonths = duplicate(newMonths);
855                    break;
856                 default : // HANDLE SHORT, etc.
857                    break;
858              }
859              break;
860           case STANDALONE :
861              switch(width) {
862                 case WIDE :
863                    standaloneMonths = duplicate(newMonths);
864                    break;
865                 case ABBREVIATED :
866                    standaloneShortMonths = duplicate(newMonths);
867                    break;
868                 case NARROW :
869                    standaloneNarrowMonths = duplicate(newMonths);
870                    break;
871                 default : // HANDLE SHORT, etc.
872                    break;
873              }
874              break;
875        }
876    }
877
878    /**
879     * Returns short month strings. For example: "Jan", "Feb", etc.
880     * @return the short month strings.
881     * @stable ICU 2.0
882     */
883    public String[] getShortMonths() {
884        return duplicate(shortMonths);
885    }
886
887    /**
888     * Sets short month strings. For example: "Jan", "Feb", etc.
889     * @param newShortMonths the new short month strings.
890     * @stable ICU 2.0
891     */
892    public void setShortMonths(String[] newShortMonths) {
893        shortMonths = duplicate(newShortMonths);
894    }
895
896    /**
897     * Returns wide weekday strings. For example: "Sunday", "Monday", etc.
898     * @return the weekday strings. Use <code>Calendar.SUNDAY</code>,
899     * <code>Calendar.MONDAY</code>, etc. to index the result array.
900     * @stable ICU 2.0
901     */
902    public String[] getWeekdays() {
903        return duplicate(weekdays);
904    }
905
906    /**
907     * Returns weekday strings. For example: "Sunday", "Monday", etc.
908     * @return the weekday strings. Use <code>Calendar.SUNDAY</code>,
909     * <code>Calendar.MONDAY</code>, etc. to index the result array.
910     * @param context    Formatting context, either FORMAT or STANDALONE.
911     * @param width      Width of strings to be returned, either
912     *                   WIDE, ABBREVIATED, SHORT, or NARROW
913     * @stable ICU 3.4
914     */
915    public String[] getWeekdays(int context, int width) {
916        String [] returnValue = null;
917        switch (context) {
918           case FORMAT :
919              switch(width) {
920                 case WIDE :
921                    returnValue = weekdays;
922                    break;
923                 case ABBREVIATED :
924                    returnValue = shortWeekdays;
925                    break;
926                 case SHORT :
927                    returnValue = (shorterWeekdays != null)? shorterWeekdays: shortWeekdays;
928                    break;
929                 case NARROW :
930                    returnValue = narrowWeekdays;
931                    break;
932              }
933              break;
934           case STANDALONE :
935              switch(width) {
936                 case WIDE :
937                    returnValue = standaloneWeekdays;
938                    break;
939                 case ABBREVIATED :
940                    returnValue = standaloneShortWeekdays;
941                    break;
942                 case SHORT :
943                    returnValue = (standaloneShorterWeekdays != null)? standaloneShorterWeekdays: standaloneShortWeekdays;
944                    break;
945                 case NARROW :
946                    returnValue = standaloneNarrowWeekdays;
947                    break;
948              }
949              break;
950        }
951        if (returnValue == null) {
952            throw new IllegalArgumentException("Bad context or width argument");
953        }
954        return duplicate(returnValue);
955    }
956
957    /**
958     * Sets weekday strings. For example: "Sunday", "Monday", etc.
959     * @param newWeekdays The new weekday strings.
960     * @param context     The formatting context, FORMAT or STANDALONE.
961     * @param width       The width of the strings,
962     *                    either WIDE, ABBREVIATED, SHORT, or NARROW.
963     * @stable ICU 3.8
964     */
965    public void setWeekdays(String[] newWeekdays, int context, int width) {
966        switch (context) {
967           case FORMAT :
968              switch(width) {
969                 case WIDE :
970                    weekdays = duplicate(newWeekdays);
971                    break;
972                 case ABBREVIATED :
973                    shortWeekdays = duplicate(newWeekdays);
974                    break;
975                 case SHORT :
976                    shorterWeekdays = duplicate(newWeekdays);
977                    break;
978                 case NARROW :
979                    narrowWeekdays = duplicate(newWeekdays);
980                    break;
981              }
982              break;
983           case STANDALONE :
984              switch(width) {
985                 case WIDE :
986                    standaloneWeekdays = duplicate(newWeekdays);
987                    break;
988                 case ABBREVIATED :
989                    standaloneShortWeekdays = duplicate(newWeekdays);
990                    break;
991                 case SHORT :
992                    standaloneShorterWeekdays = duplicate(newWeekdays);
993                    break;
994                 case NARROW :
995                    standaloneNarrowWeekdays = duplicate(newWeekdays);
996                    break;
997              }
998              break;
999        }
1000    }
1001
1002    /**
1003     * Sets wide weekday strings. For example: "Sunday", "Monday", etc.
1004     * @param newWeekdays the new weekday strings. The array should
1005     * be indexed by <code>Calendar.SUNDAY</code>,
1006     * <code>Calendar.MONDAY</code>, etc.
1007     * @stable ICU 2.0
1008     */
1009    public void setWeekdays(String[] newWeekdays) {
1010        weekdays = duplicate(newWeekdays);
1011    }
1012
1013    /**
1014     * Returns abbreviated weekday strings; for example: "Sun", "Mon", etc.
1015     * (Note: the method name is misleading; it does not get the CLDR-style
1016     * "short" weekday strings, e.g. "Su", "Mo", etc.)
1017     * @return the abbreviated weekday strings. Use <code>Calendar.SUNDAY</code>,
1018     * <code>Calendar.MONDAY</code>, etc. to index the result array.
1019     * @stable ICU 2.0
1020     */
1021    public String[] getShortWeekdays() {
1022        return duplicate(shortWeekdays);
1023    }
1024
1025    /**
1026     * Sets abbreviated weekday strings; for example: "Sun", "Mon", etc.
1027     * (Note: the method name is misleading; it does not set the CLDR-style
1028     * "short" weekday strings, e.g. "Su", "Mo", etc.)
1029     * @param newAbbrevWeekdays the new abbreviated weekday strings. The array should
1030     * be indexed by <code>Calendar.SUNDAY</code>,
1031     * <code>Calendar.MONDAY</code>, etc.
1032     * @stable ICU 2.0
1033     */
1034    public void setShortWeekdays(String[] newAbbrevWeekdays) {
1035        shortWeekdays = duplicate(newAbbrevWeekdays);
1036    }
1037    /**
1038     * {@icu} Returns quarter strings. For example: "1st Quarter", "2nd Quarter", etc.
1039     * @param context    The quarter context, FORMAT or STANDALONE.
1040     * @param width      The width or the returned quarter string,
1041     *                   either WIDE or ABBREVIATED. There are no NARROW quarters.
1042     * @return the quarter strings.
1043     * @stable ICU 3.6
1044     */
1045    public String[] getQuarters(int context, int width) {
1046        String [] returnValue = null;
1047        switch (context) {
1048           case FORMAT :
1049              switch(width) {
1050                 case WIDE :
1051                    returnValue = quarters;
1052                    break;
1053                 case ABBREVIATED :
1054                 case SHORT : // no quarter data for this, defaults to ABBREVIATED
1055                    returnValue = shortQuarters;
1056                    break;
1057                 case NARROW :
1058                     returnValue = null;
1059                     break;
1060              }
1061              break;
1062
1063           case STANDALONE :
1064              switch(width) {
1065                 case WIDE :
1066                    returnValue = standaloneQuarters;
1067                    break;
1068                 case ABBREVIATED :
1069                 case SHORT : // no quarter data for this, defaults to ABBREVIATED
1070                    returnValue = standaloneShortQuarters;
1071                    break;
1072                 case NARROW:
1073                     returnValue = null;
1074                     break;
1075              }
1076              break;
1077        }
1078        if (returnValue == null) {
1079            throw new IllegalArgumentException("Bad context or width argument");
1080        }
1081        return duplicate(returnValue);
1082    }
1083
1084    /**
1085     * {@icu} Sets quarter strings. For example: "1st Quarter", "2nd Quarter", etc.
1086     * @param newQuarters the new quarter strings.
1087     * @param context    The formatting context, FORMAT or STANDALONE.
1088     * @param width      The width of the quarter string,
1089     *                   either WIDE or ABBREVIATED. There are no NARROW quarters.
1090     * @stable ICU 3.8
1091     */
1092    public void setQuarters(String[] newQuarters, int context, int width) {
1093        switch (context) {
1094           case FORMAT :
1095              switch(width) {
1096                 case WIDE :
1097                    quarters = duplicate(newQuarters);
1098                    break;
1099                 case ABBREVIATED :
1100                    shortQuarters = duplicate(newQuarters);
1101                    break;
1102                 case NARROW :
1103                    //narrowQuarters = duplicate(newQuarters);
1104                    break;
1105                 default : // HANDLE SHORT, etc.
1106                    break;
1107              }
1108              break;
1109           case STANDALONE :
1110              switch(width) {
1111                 case WIDE :
1112                    standaloneQuarters = duplicate(newQuarters);
1113                    break;
1114                 case ABBREVIATED :
1115                    standaloneShortQuarters = duplicate(newQuarters);
1116                    break;
1117                 case NARROW :
1118                    //standaloneNarrowQuarters = duplicate(newQuarters);
1119                    break;
1120                 default : // HANDLE SHORT, etc.
1121                    break;
1122              }
1123              break;
1124        }
1125    }
1126
1127    /**
1128     * Returns cyclic year name strings if the calendar has them,
1129     * for example: "jia-zi", "yi-chou", etc.
1130     * @param context   The usage context: FORMAT, STANDALONE.
1131     * @param width     The requested name width: WIDE, ABBREVIATED, SHORT, NARROW.
1132     * @return          The year name strings, or null if they are not
1133     *                  available for this calendar.
1134     * @stable ICU 54
1135     */
1136    public String[] getYearNames(int context, int width) {
1137        // context & width ignored for now, one set of names for all uses
1138        if (shortYearNames != null) {
1139            return duplicate(shortYearNames);
1140        }
1141        return null;
1142    }
1143
1144    /**
1145     * Sets cyclic year name strings, for example: "jia-zi", "yi-chou", etc.
1146     * @param yearNames The new cyclic year name strings.
1147     * @param context   The usage context: FORMAT, STANDALONE (currently only FORMAT is supported).
1148     * @param width     The name width: WIDE, ABBREVIATED, NARROW (currently only ABBREVIATED is supported).
1149     * @stable ICU 54
1150     */
1151    public void setYearNames(String[] yearNames, int context, int width) {
1152        if (context == FORMAT && width == ABBREVIATED) {
1153            shortYearNames = duplicate(yearNames);
1154        }
1155    }
1156
1157    /**
1158     * Returns calendar zodiac name strings if the calendar has them,
1159     * for example: "Rat", "Ox", "Tiger", etc.
1160     * @param context   The usage context: FORMAT, STANDALONE.
1161     * @param width     The requested name width: WIDE, ABBREVIATED, SHORT, NARROW.
1162     * @return          The zodiac name strings, or null if they are not
1163     *                  available for this calendar.
1164     * @stable ICU 54
1165     */
1166    public String[] getZodiacNames(int context, int width) {
1167        // context & width ignored for now, one set of names for all uses
1168        if (shortZodiacNames != null) {
1169            return duplicate(shortZodiacNames);
1170        }
1171        return null;
1172    }
1173
1174    /**
1175     * Sets calendar zodiac name strings, for example: "Rat", "Ox", "Tiger", etc.
1176     * @param zodiacNames   The new zodiac name strings.
1177     * @param context   The usage context: FORMAT, STANDALONE (currently only FORMAT is supported).
1178     * @param width     The name width: WIDE, ABBREVIATED, NARROW (currently only ABBREVIATED is supported).
1179     * @stable ICU 54
1180     */
1181    public void setZodiacNames(String[] zodiacNames, int context, int width) {
1182        if (context == FORMAT && width == ABBREVIATED) {
1183            shortZodiacNames = duplicate(zodiacNames);
1184        }
1185    }
1186
1187    /**
1188     * Returns the appropriate leapMonthPattern if the calendar has them,
1189     * for example: "{0}bis"
1190     * @param context   The usage context: FORMAT, STANDALONE, NUMERIC.
1191     * @param width     The requested pattern width: WIDE, ABBREVIATED, SHORT, NARROW.
1192     * @return          The leapMonthPattern, or null if not available for
1193     *                  this calendar.
1194     * @internal
1195     * @deprecated This API is ICU internal only.
1196     */
1197    @Deprecated
1198    public String getLeapMonthPattern(int context, int width) {
1199        if (leapMonthPatterns != null) {
1200            int leapMonthPatternIndex = -1;
1201            switch (context) {
1202               case FORMAT :
1203                  switch(width) {
1204                     case WIDE :
1205                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_FORMAT_WIDE;
1206                        break;
1207                     case ABBREVIATED :
1208                     case SHORT : // no month data for this, defaults to ABBREVIATED
1209                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV;
1210                        break;
1211                     case NARROW :
1212                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_FORMAT_NARROW;
1213                        break;
1214                  }
1215                  break;
1216               case STANDALONE :
1217                  switch(width) {
1218                     case WIDE :
1219                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE;
1220                        break;
1221                     case ABBREVIATED :
1222                     case SHORT : // no month data for this, defaults to ABBREVIATED
1223                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV;
1224                        break;
1225                     case NARROW :
1226                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW;
1227                        break;
1228                  }
1229                  break;
1230               case NUMERIC :
1231                  leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_NUMERIC;
1232                  break;
1233            }
1234            if (leapMonthPatternIndex < 0) {
1235                throw new IllegalArgumentException("Bad context or width argument");
1236            }
1237            return leapMonthPatterns[leapMonthPatternIndex];
1238        }
1239        return null;
1240    }
1241
1242    /**
1243     * Sets a leapMonthPattern, for example: "{0}bis"
1244     * @param leapMonthPattern  The new leapMonthPattern.
1245     * @param context   The usage context: FORMAT, STANDALONE, NUMERIC.
1246     * @param width     The name width: WIDE, ABBREVIATED, NARROW.
1247     * @internal
1248     * @deprecated This API is ICU internal only.
1249     */
1250    @Deprecated
1251    public void setLeapMonthPattern(String leapMonthPattern, int context, int width) {
1252        if (leapMonthPatterns != null) {
1253            int leapMonthPatternIndex = -1;
1254            switch (context) {
1255               case FORMAT :
1256                  switch(width) {
1257                     case WIDE :
1258                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_FORMAT_WIDE;
1259                        break;
1260                     case ABBREVIATED :
1261                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV;
1262                        break;
1263                     case NARROW :
1264                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_FORMAT_NARROW;
1265                        break;
1266                     default : // HANDLE SHORT, etc.
1267                        break;
1268                  }
1269                  break;
1270               case STANDALONE :
1271                  switch(width) {
1272                     case WIDE :
1273                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE;
1274                        break;
1275                     case ABBREVIATED :
1276                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV;
1277                        break;
1278                     case NARROW :
1279                        leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW;
1280                        break;
1281                     default : // HANDLE SHORT, etc.
1282                        break;
1283                  }
1284                  break;
1285               case NUMERIC :
1286                  leapMonthPatternIndex = DT_LEAP_MONTH_PATTERN_NUMERIC;
1287                  break;
1288               default :
1289                  break;
1290            }
1291            if (leapMonthPatternIndex >= 0) {
1292                leapMonthPatterns[leapMonthPatternIndex] = leapMonthPattern;
1293            }
1294        }
1295    }
1296
1297    /**
1298     * Returns am/pm strings. For example: "AM" and "PM".
1299     * @return the weekday strings.
1300     * @stable ICU 2.0
1301     */
1302    public String[] getAmPmStrings() {
1303        return duplicate(ampms);
1304    }
1305
1306    /**
1307     * Sets am/pm strings. For example: "AM" and "PM".
1308     * @param newAmpms the new ampm strings.
1309     * @stable ICU 2.0
1310     */
1311    public void setAmPmStrings(String[] newAmpms) {
1312        ampms = duplicate(newAmpms);
1313    }
1314
1315    /**
1316     * Returns the time separator string. For example: ":".
1317     * @return the time separator string.
1318     * @internal
1319     * @deprecated This API is ICU internal only.
1320     */
1321    @Deprecated
1322    public String getTimeSeparatorString() {
1323        return timeSeparator;
1324    }
1325
1326    /**
1327     * Sets the time separator string. For example: ":".
1328     * @param newTimeSeparator the new time separator string.
1329     * @internal
1330     * @deprecated This API is ICU internal only.
1331     */
1332    @Deprecated
1333    public void setTimeSeparatorString(String newTimeSeparator) {
1334        timeSeparator = newTimeSeparator;
1335    }
1336
1337    /**
1338     * Returns time zone strings.
1339     * <p>
1340     * The array returned by this API is a two dimensional String array and
1341     * each row contains at least following strings:
1342     * <ul>
1343     * <li>ZoneStrings[n][0] - System time zone ID
1344     * <li>ZoneStrings[n][1] - Long standard time display name
1345     * <li>ZoneStrings[n][2] - Short standard time display name
1346     * <li>ZoneStrings[n][3] - Long daylight saving time display name
1347     * <li>ZoneStrings[n][4] - Short daylight saving time display name
1348     * </ul>
1349     * When a localized display name is not available, the corresponding
1350     * array element will be <code>null</code>.
1351     * <p>
1352     * <b>Note</b>: ICU implements the time zone display name formatting algorithm
1353     * specified by <a href="http://www.unicode.org/reports/tr35/">UTS#35 Unicode
1354     * Locale Data Markup Language(LDML)</a>. The algorithm supports historic
1355     * display name changes and various different types of names not available in
1356     * {@link java.text.DateFormatSymbols#getZoneStrings()}. For accessing the full
1357     * set of time zone string data used by ICU implementation, you should use
1358     * {@link TimeZoneNames} APIs instead.
1359     *
1360     * @return the time zone strings.
1361     * @stable ICU 2.0
1362     */
1363    public String[][] getZoneStrings() {
1364        if (zoneStrings != null) {
1365            return duplicate(zoneStrings);
1366        }
1367
1368        String[] tzIDs = TimeZone.getAvailableIDs();
1369        TimeZoneNames tznames = TimeZoneNames.getInstance(validLocale);
1370        tznames.loadAllDisplayNames();
1371        NameType types[] = {
1372            NameType.LONG_STANDARD, NameType.SHORT_STANDARD,
1373            NameType.LONG_DAYLIGHT, NameType.SHORT_DAYLIGHT
1374        };
1375        long now = System.currentTimeMillis();
1376        String[][] array = new String[tzIDs.length][5];
1377        for (int i = 0; i < tzIDs.length; i++) {
1378            String canonicalID = TimeZone.getCanonicalID(tzIDs[i]);
1379            if (canonicalID == null) {
1380                canonicalID = tzIDs[i];
1381            }
1382
1383            array[i][0] = tzIDs[i];
1384            tznames.getDisplayNames(canonicalID, types, now, array[i], 1);
1385        }
1386
1387        zoneStrings = array;
1388        return zoneStrings;
1389    }
1390
1391    /**
1392     * Sets time zone strings.
1393     * <p>
1394     * <b>Note</b>: {@link SimpleDateFormat} no longer uses the
1395     * zone strings stored in a <code>DateFormatSymbols</code>.
1396     * Therefore, the time zone strings set by this method have
1397     * no effects in an instance of <code>SimpleDateFormat</code>
1398     * for formatting time zones. If you want to customize time
1399     * zone display names formatted by <code>SimpleDateFormat</code>,
1400     * you should customize {@link TimeZoneFormat} and set the
1401     * instance by {@link SimpleDateFormat#setTimeZoneFormat(TimeZoneFormat)}
1402     * instead.
1403     *
1404     * @param newZoneStrings the new time zone strings.
1405     * @stable ICU 2.0
1406     */
1407    public void setZoneStrings(String[][] newZoneStrings) {
1408        zoneStrings = duplicate(newZoneStrings);
1409    }
1410
1411    /**
1412     * Returns localized date-time pattern characters. For example: 'u', 't', etc.
1413     *
1414     * <p>Note: ICU no longer provides localized date-time pattern characters for a locale
1415     * starting ICU 3.8.  This method returns the non-localized date-time pattern
1416     * characters unless user defined localized data is set by setLocalPatternChars.
1417     * @return the localized date-time pattern characters.
1418     * @stable ICU 2.0
1419     */
1420    public String getLocalPatternChars() {
1421        return localPatternChars;
1422    }
1423
1424    /**
1425     * Sets localized date-time pattern characters. For example: 'u', 't', etc.
1426     * @param newLocalPatternChars the new localized date-time
1427     * pattern characters.
1428     * @stable ICU 2.0
1429     */
1430    public void setLocalPatternChars(String newLocalPatternChars) {
1431        localPatternChars = newLocalPatternChars;
1432    }
1433
1434    /**
1435     * Overrides clone.
1436     * @stable ICU 2.0
1437     */
1438    @Override
1439    public Object clone()
1440    {
1441        try {
1442            DateFormatSymbols other = (DateFormatSymbols)super.clone();
1443            return other;
1444        } catch (CloneNotSupportedException e) {
1445            ///CLOVER:OFF
1446            throw new ICUCloneNotSupportedException(e);
1447            ///CLOVER:ON
1448        }
1449    }
1450
1451    /**
1452     * Override hashCode.
1453     * Generates a hash code for the DateFormatSymbols object.
1454     * @stable ICU 2.0
1455     */
1456    @Override
1457    public int hashCode() {
1458        // Is this sufficient?
1459        return requestedLocale.toString().hashCode();
1460    }
1461
1462    /**
1463     * Overrides equals.
1464     * @stable ICU 2.0
1465     */
1466    @Override
1467    public boolean equals(Object obj)
1468    {
1469        if (this == obj) return true;
1470        if (obj == null || getClass() != obj.getClass()) return false;
1471        DateFormatSymbols that = (DateFormatSymbols) obj;
1472        return (Utility.arrayEquals(eras, that.eras)
1473                && Utility.arrayEquals(eraNames, that.eraNames)
1474                && Utility.arrayEquals(months, that.months)
1475                && Utility.arrayEquals(shortMonths, that.shortMonths)
1476                && Utility.arrayEquals(narrowMonths, that.narrowMonths)
1477                && Utility.arrayEquals(standaloneMonths, that.standaloneMonths)
1478                && Utility.arrayEquals(standaloneShortMonths, that.standaloneShortMonths)
1479                && Utility.arrayEquals(standaloneNarrowMonths, that.standaloneNarrowMonths)
1480                && Utility.arrayEquals(weekdays, that.weekdays)
1481                && Utility.arrayEquals(shortWeekdays, that.shortWeekdays)
1482                && Utility.arrayEquals(shorterWeekdays, that.shorterWeekdays)
1483                && Utility.arrayEquals(narrowWeekdays, that.narrowWeekdays)
1484                && Utility.arrayEquals(standaloneWeekdays, that.standaloneWeekdays)
1485                && Utility.arrayEquals(standaloneShortWeekdays, that.standaloneShortWeekdays)
1486                && Utility.arrayEquals(standaloneShorterWeekdays, that.standaloneShorterWeekdays)
1487                && Utility.arrayEquals(standaloneNarrowWeekdays, that.standaloneNarrowWeekdays)
1488                && Utility.arrayEquals(ampms, that.ampms)
1489                && Utility.arrayEquals(ampmsNarrow, that.ampmsNarrow)
1490                && Utility.arrayEquals(abbreviatedDayPeriods, that.abbreviatedDayPeriods)
1491                && Utility.arrayEquals(wideDayPeriods, that.wideDayPeriods)
1492                && Utility.arrayEquals(narrowDayPeriods, that.narrowDayPeriods)
1493                && Utility.arrayEquals(standaloneAbbreviatedDayPeriods, that.standaloneAbbreviatedDayPeriods)
1494                && Utility.arrayEquals(standaloneWideDayPeriods, that.standaloneWideDayPeriods)
1495                && Utility.arrayEquals(standaloneNarrowDayPeriods, that.standaloneNarrowDayPeriods)
1496                && Utility.arrayEquals(timeSeparator, that.timeSeparator)
1497                && arrayOfArrayEquals(zoneStrings, that.zoneStrings)
1498                // getDiplayName maps deprecated country and language codes to the current ones
1499                // too bad there is no way to get the current codes!
1500                // I thought canolicalize() would map the codes but .. alas! it doesn't.
1501                && requestedLocale.getDisplayName().equals(that.requestedLocale.getDisplayName())
1502                && Utility.arrayEquals(localPatternChars,
1503                                       that.localPatternChars));
1504    }
1505
1506    // =======================privates===============================
1507
1508    /**
1509     * Useful constant for defining timezone offsets.
1510     */
1511    static final int millisPerHour = 60*60*1000;
1512
1513    // DateFormatSymbols cache
1514    private static CacheBase<String, DateFormatSymbols, ULocale> DFSCACHE =
1515        new SoftCache<String, DateFormatSymbols, ULocale>() {
1516            @Override
1517            protected DateFormatSymbols createInstance(String key, ULocale locale) {
1518                // Extract the type string from the key.
1519                // Otherwise we would have to create a pair object that
1520                // carries both the locale and the type.
1521                int typeStart = key.indexOf('+') + 1;
1522                int typeLimit = key.indexOf('+', typeStart);
1523                if (typeLimit < 0) {
1524                    // no numbers keyword value
1525                    typeLimit = key.length();
1526                }
1527                String type = key.substring(typeStart, typeLimit);
1528                return new DateFormatSymbols(locale, null, type);
1529            }
1530        };
1531
1532    /**
1533     * Initializes format symbols for the locale and calendar type
1534     * @param desiredLocale The locale whose symbols are desired.
1535     * @param type          The calendar type whose date format symbols are desired.
1536     * @stable ICU 3.0
1537     */
1538    //TODO: This protected seems to be marked as @stable accidentally.
1539    // We may need to deescalate this API to @internal.
1540    protected void initializeData(ULocale desiredLocale, String type)
1541    {
1542        String key = desiredLocale.getBaseName() + '+' + type;
1543        String ns = desiredLocale.getKeywordValue("numbers");
1544        if (ns != null && ns.length() > 0) {
1545            key += '+' + ns;
1546        }
1547        DateFormatSymbols dfs = DFSCACHE.getInstance(key, desiredLocale);
1548        initializeData(dfs);
1549    }
1550
1551    /**
1552     * Initializes format symbols using another instance.
1553     *
1554     * TODO Clean up initialization methods for subclasses
1555     */
1556    void initializeData(DateFormatSymbols dfs) {
1557        this.eras = dfs.eras;
1558        this.eraNames = dfs.eraNames;
1559        this.narrowEras = dfs.narrowEras;
1560        this.months = dfs.months;
1561        this.shortMonths = dfs.shortMonths;
1562        this.narrowMonths = dfs.narrowMonths;
1563        this.standaloneMonths = dfs.standaloneMonths;
1564        this.standaloneShortMonths = dfs.standaloneShortMonths;
1565        this.standaloneNarrowMonths = dfs.standaloneNarrowMonths;
1566        this.weekdays = dfs.weekdays;
1567        this.shortWeekdays = dfs.shortWeekdays;
1568        this.shorterWeekdays = dfs.shorterWeekdays;
1569        this.narrowWeekdays = dfs.narrowWeekdays;
1570        this.standaloneWeekdays = dfs.standaloneWeekdays;
1571        this.standaloneShortWeekdays = dfs.standaloneShortWeekdays;
1572        this.standaloneShorterWeekdays = dfs.standaloneShorterWeekdays;
1573        this.standaloneNarrowWeekdays = dfs.standaloneNarrowWeekdays;
1574        this.ampms = dfs.ampms;
1575        this.ampmsNarrow = dfs.ampmsNarrow;
1576        this.timeSeparator = dfs.timeSeparator;
1577        this.shortQuarters = dfs.shortQuarters;
1578        this.quarters = dfs.quarters;
1579        this.standaloneShortQuarters = dfs.standaloneShortQuarters;
1580        this.standaloneQuarters = dfs.standaloneQuarters;
1581        this.leapMonthPatterns = dfs.leapMonthPatterns;
1582        this.shortYearNames = dfs.shortYearNames;
1583        this.shortZodiacNames = dfs.shortZodiacNames;
1584        this.abbreviatedDayPeriods = dfs.abbreviatedDayPeriods;
1585        this.wideDayPeriods = dfs.wideDayPeriods;
1586        this.narrowDayPeriods = dfs.narrowDayPeriods;
1587        this.standaloneAbbreviatedDayPeriods = dfs.standaloneAbbreviatedDayPeriods;
1588        this.standaloneWideDayPeriods = dfs.standaloneWideDayPeriods;
1589        this.standaloneNarrowDayPeriods = dfs.standaloneNarrowDayPeriods;
1590
1591        this.zoneStrings = dfs.zoneStrings; // always null at initialization time for now
1592        this.localPatternChars = dfs.localPatternChars;
1593
1594        this.capitalization = dfs.capitalization;
1595
1596        this.actualLocale = dfs.actualLocale;
1597        this.validLocale = dfs.validLocale;
1598        this.requestedLocale = dfs.requestedLocale;
1599    }
1600
1601
1602    /**
1603     * Sink to enumerate the calendar data
1604     */
1605    private static final class CalendarDataSink extends UResource.Sink {
1606
1607        // Data structures to store resources from the resource bundle
1608        Map<String, String[]> arrays = new TreeMap<String, String[]>();
1609        Map<String, Map<String, String>> maps = new TreeMap<String, Map<String, String>>();
1610        List<String> aliasPathPairs = new ArrayList<String>();
1611
1612        // Current and next calendar resource table which should be loaded
1613        String currentCalendarType = null;
1614        String nextCalendarType = null;
1615
1616        // Resources to visit when enumerating fallback calendars
1617        private Set<String> resourcesToVisit;
1618
1619        // Alias' relative path populated when an alias is read
1620        private String aliasRelativePath;
1621
1622        /**
1623         * Initializes CalendarDataSink with default values
1624         */
1625        CalendarDataSink() { }
1626
1627        /**
1628         * Configure the CalendarSink to visit all the resources
1629         */
1630        void visitAllResources() {
1631            resourcesToVisit = null;
1632        }
1633
1634        /**
1635         * Actions to be done before enumerating
1636         */
1637        void preEnumerate(String calendarType) {
1638            currentCalendarType = calendarType;
1639            nextCalendarType = null;
1640            aliasPathPairs.clear();
1641        }
1642
1643        @Override
1644        public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
1645            assert currentCalendarType != null && !currentCalendarType.isEmpty();
1646
1647            // Stores the resources to visit on the next calendar.
1648            Set<String> resourcesToVisitNext = null;
1649            UResource.Table calendarData = value.getTable();
1650
1651            // Enumerate all resources for this calendar
1652            for (int i = 0; calendarData.getKeyAndValue(i, key, value); i++) {
1653                String keyString = key.toString();
1654
1655                // == Handle aliases ==
1656                AliasType aliasType = processAliasFromValue(keyString, value);
1657                if (aliasType == AliasType.GREGORIAN) {
1658                    // Ignore aliases to the gregorian calendar, all of its resources will be loaded anyways.
1659                    continue;
1660
1661                } else if (aliasType == AliasType.DIFFERENT_CALENDAR) {
1662                    // Whenever an alias to the next calendar (except gregorian) is encountered, register the
1663                    // calendar type it's pointing to
1664                    if (resourcesToVisitNext == null) {
1665                        resourcesToVisitNext = new HashSet<String>();
1666                    }
1667                    resourcesToVisitNext.add(aliasRelativePath);
1668                    continue;
1669
1670                } else if (aliasType == AliasType.SAME_CALENDAR) {
1671                    // Register same-calendar alias
1672                    if (!arrays.containsKey(keyString) && !maps.containsKey(keyString)) {
1673                        aliasPathPairs.add(aliasRelativePath);
1674                        aliasPathPairs.add(keyString);
1675                    }
1676                    continue;
1677                }
1678
1679                // Only visit the resources that were referenced by an alias on the previous calendar
1680                // (AmPmMarkersAbbr is an exception).
1681                if (resourcesToVisit != null && !resourcesToVisit.isEmpty() && !resourcesToVisit.contains(keyString)
1682                        && !keyString.equals("AmPmMarkersAbbr")) { continue; }
1683
1684                // == Handle data ==
1685                if (keyString.startsWith("AmPmMarkers")) {
1686                    if (!keyString.endsWith("%variant") && !arrays.containsKey(keyString)) {
1687                        String[] dataArray = value.getStringArray();
1688                        arrays.put(keyString, dataArray);
1689                    }
1690                } else if (keyString.equals("eras")
1691                        || keyString.equals("dayNames")
1692                        || keyString.equals("monthNames")
1693                        || keyString.equals("quarters")
1694                        || keyString.equals("dayPeriod")
1695                        || keyString.equals("monthPatterns")
1696                        || keyString.equals("cyclicNameSets")) {
1697                    processResource(keyString, key, value);
1698                }
1699            }
1700
1701            // Apply same-calendar aliases
1702            boolean modified;
1703            do {
1704                modified = false;
1705                for (int i = 0; i < aliasPathPairs.size();) {
1706                    boolean mod = false;
1707                    String alias = aliasPathPairs.get(i);
1708                    if (arrays.containsKey(alias)) {
1709                        arrays.put(aliasPathPairs.get(i + 1), arrays.get(alias));
1710                        mod = true;
1711                    } else if (maps.containsKey(alias)) {
1712                        maps.put(aliasPathPairs.get(i + 1), maps.get(alias));
1713                        mod = true;
1714                    }
1715                    if (mod) {
1716                        aliasPathPairs.remove(i + 1);
1717                        aliasPathPairs.remove(i);
1718                        modified = true;
1719                    } else {
1720                        i += 2;
1721                    }
1722                }
1723            } while (modified && !aliasPathPairs.isEmpty());
1724
1725            // Set the resources to visit on the next calendar
1726            if (resourcesToVisitNext != null) {
1727                resourcesToVisit = resourcesToVisitNext;
1728            }
1729        }
1730
1731        /**
1732         * Process the nested resource bundle tables
1733         * @param path Table's relative path to the calendar
1734         * @param key Resource bundle key
1735         * @param value Resource bundle value (has to have the table to read)
1736         */
1737        protected void processResource(String path, UResource.Key key, UResource.Value value) {
1738
1739            UResource.Table table = value.getTable();
1740            Map<String, String> stringMap = null;
1741
1742            // Iterate over all the elements of the table and add them to the map
1743            for(int i = 0; table.getKeyAndValue(i, key, value); i++) {
1744                // Ignore '%variant' keys
1745                if (key.endsWith("%variant")) { continue; }
1746
1747                String keyString = key.toString();
1748
1749                // == Handle String elements ==
1750                if (value.getType() == ICUResourceBundle.STRING) {
1751                    // We are on a leaf, store the map elements into the stringMap
1752                    if (i == 0) {
1753                        stringMap = new HashMap<String, String>();
1754                        maps.put(path, stringMap);
1755                    }
1756                    assert stringMap != null;
1757                    stringMap.put(keyString, value.getString());
1758                    continue;
1759                }
1760                assert stringMap == null;
1761
1762                String currentPath = path + "/" + keyString;
1763                // In cyclicNameSets ignore everything but years/format/abbreviated
1764                // and zodiacs/format/abbreviated
1765                if (currentPath.startsWith("cyclicNameSets")) {
1766                    if (!"cyclicNameSets/years/format/abbreviated".startsWith(currentPath)
1767                            && !"cyclicNameSets/zodiacs/format/abbreviated".startsWith(currentPath)
1768                            && !"cyclicNameSets/dayParts/format/abbreviated".startsWith(currentPath))
1769                    { continue; }
1770                }
1771
1772                // == Handle aliases ==
1773                if (arrays.containsKey(currentPath)
1774                        || maps.containsKey(currentPath)) { continue; }
1775
1776                AliasType aliasType = processAliasFromValue(currentPath, value);
1777                if (aliasType == AliasType.SAME_CALENDAR) {
1778                    aliasPathPairs.add(aliasRelativePath);
1779                    aliasPathPairs.add(currentPath);
1780                    continue;
1781                }
1782                assert aliasType == AliasType.NONE;
1783
1784                // == Handle data ==
1785                if (value.getType() == ICUResourceBundle.ARRAY) {
1786                    // We are on a leaf, store the array
1787                    String[] dataArray = value.getStringArray();
1788                    arrays.put(currentPath, dataArray);
1789                } else if (value.getType() == ICUResourceBundle.TABLE) {
1790                    // We are not on a leaf, recursively process the subtable.
1791                    processResource(currentPath, key, value);
1792                }
1793            }
1794        }
1795
1796        // Alias' path prefix
1797        private static final String CALENDAR_ALIAS_PREFIX = "/LOCALE/calendar/";
1798
1799        /**
1800         * Populates an AliasIdentifier with the alias information contained on the UResource.Value.
1801         * @param currentRelativePath Relative path of this alias' resource
1802         * @param value Value which contains the alias
1803         * @return The AliasType of the alias found on Value
1804         */
1805        private AliasType processAliasFromValue(String currentRelativePath, UResource.Value value) {
1806            if (value.getType() == ICUResourceBundle.ALIAS) {
1807                String aliasPath = value.getAliasString();
1808                if (aliasPath.startsWith(CALENDAR_ALIAS_PREFIX) &&
1809                        aliasPath.length() > CALENDAR_ALIAS_PREFIX.length()) {
1810                    int typeLimit = aliasPath.indexOf('/', CALENDAR_ALIAS_PREFIX.length());
1811                    if (typeLimit > CALENDAR_ALIAS_PREFIX.length()) {
1812                        String aliasCalendarType = aliasPath.substring(CALENDAR_ALIAS_PREFIX.length(), typeLimit);
1813                        aliasRelativePath = aliasPath.substring(typeLimit + 1);
1814
1815                        if (currentCalendarType.equals(aliasCalendarType)
1816                                && !currentRelativePath.equals(aliasRelativePath)) {
1817                            // If we have an alias to the same calendar, the path to the resource must be different
1818                            return AliasType.SAME_CALENDAR;
1819
1820                        } else if (!currentCalendarType.equals(aliasCalendarType)
1821                                && currentRelativePath.equals(aliasRelativePath)) {
1822                            // If we have an alias to a different calendar, the path to the resource must be the same
1823                            if (aliasCalendarType.equals("gregorian")) {
1824                                return AliasType.GREGORIAN;
1825                            } else if (nextCalendarType == null || nextCalendarType.equals(aliasCalendarType)) {
1826                                nextCalendarType = aliasCalendarType;
1827                                return AliasType.DIFFERENT_CALENDAR;
1828                            }
1829                        }
1830                    }
1831                }
1832                throw new ICUException("Malformed 'calendar' alias. Path: " + aliasPath);
1833            }
1834            return AliasType.NONE;
1835        }
1836
1837        /**
1838         * Enum which specifies the type of alias received, or no alias
1839         */
1840        private enum AliasType {
1841            SAME_CALENDAR,
1842            DIFFERENT_CALENDAR,
1843            GREGORIAN,
1844            NONE
1845        }
1846    }
1847
1848    /** Private, for cache.getInstance(). */
1849    private DateFormatSymbols(ULocale desiredLocale, ICUResourceBundle b, String calendarType) {
1850        initializeData(desiredLocale, b, calendarType);
1851    }
1852
1853    /**
1854     * Initializes format symbols for the locale and calendar type
1855     * @param desiredLocale The locale whose symbols are desired.
1856     * @param b Resource bundle provided externally
1857     * @param calendarType  The calendar type being used
1858     * @internal
1859     * @deprecated This API is ICU internal only.
1860     */
1861    @Deprecated
1862    // This API was accidentally marked as @stable ICU 3.0 formerly.
1863    protected void initializeData(ULocale desiredLocale, ICUResourceBundle b, String calendarType)
1864    {
1865        // Create a CalendarSink to load this data and a resource bundle
1866        CalendarDataSink calendarSink = new CalendarDataSink();
1867        if (b == null) {
1868            b = (ICUResourceBundle) UResourceBundle
1869                    .getBundleInstance(ICUData.ICU_BASE_NAME, desiredLocale);
1870        }
1871
1872        // Iterate over the resource bundle data following the fallbacks through different calendar types
1873        while (calendarType != null) {
1874
1875            // Enumerate this calendar type. If the calendar is not found fallback to gregorian.
1876            ICUResourceBundle dataForType = b.findWithFallback("calendar/" + calendarType);
1877            if (dataForType == null) {
1878                if (!"gregorian".equals(calendarType)) {
1879                    calendarType = "gregorian";
1880                    calendarSink.visitAllResources();
1881                    continue;
1882                }
1883                throw new MissingResourceException("The 'gregorian' calendar type wasn't found for the locale: "
1884                        + desiredLocale.getBaseName(), getClass().getName(), "gregorian");
1885            }
1886            calendarSink.preEnumerate(calendarType);
1887            dataForType.getAllItemsWithFallback("", calendarSink);
1888
1889            // Stop loading when gregorian was loaded
1890            if (calendarType.equals("gregorian")) {
1891                break;
1892            }
1893
1894            // Get the next calendar type to process from the sink
1895            calendarType = calendarSink.nextCalendarType;
1896
1897            // Gregorian is always the last fallback
1898            if (calendarType == null) {
1899                calendarType = "gregorian";
1900                calendarSink.visitAllResources();
1901            }
1902        }
1903
1904        Map<String, String[]> arrays = calendarSink.arrays;
1905        Map<String, Map<String, String>> maps = calendarSink.maps;
1906
1907        eras = arrays.get("eras/abbreviated");
1908        eraNames = arrays.get("eras/wide");
1909        narrowEras = arrays.get("eras/narrow");
1910
1911        months = arrays.get("monthNames/format/wide");
1912        shortMonths = arrays.get("monthNames/format/abbreviated");
1913        narrowMonths = arrays.get("monthNames/format/narrow");
1914
1915        standaloneMonths = arrays.get("monthNames/stand-alone/wide");
1916        standaloneShortMonths = arrays.get("monthNames/stand-alone/abbreviated");
1917        standaloneNarrowMonths = arrays.get("monthNames/stand-alone/narrow");
1918
1919        String[] lWeekdays = arrays.get("dayNames/format/wide");
1920        weekdays = new String[8];
1921        weekdays[0] = "";  // 1-based
1922        System.arraycopy(lWeekdays, 0, weekdays, 1, lWeekdays.length);
1923
1924        String[] aWeekdays = arrays.get("dayNames/format/abbreviated");
1925        shortWeekdays = new String[8];
1926        shortWeekdays[0] = "";  // 1-based
1927        System.arraycopy(aWeekdays, 0, shortWeekdays, 1, aWeekdays.length);
1928
1929        String[] sWeekdays = arrays.get("dayNames/format/short");
1930        shorterWeekdays = new String[8];
1931        shorterWeekdays[0] = "";  // 1-based
1932        System.arraycopy(sWeekdays, 0, shorterWeekdays, 1, sWeekdays.length);
1933
1934        String [] nWeekdays = arrays.get("dayNames/format/narrow");
1935        if (nWeekdays == null) {
1936            nWeekdays = arrays.get("dayNames/stand-alone/narrow");
1937
1938            if (nWeekdays == null) {
1939                nWeekdays = arrays.get("dayNames/format/abbreviated");
1940
1941                if (nWeekdays == null) {
1942                    throw new MissingResourceException("Resource not found",
1943                            getClass().getName(), "dayNames/format/abbreviated");
1944                }
1945            }
1946        }
1947        narrowWeekdays = new String[8];
1948        narrowWeekdays[0] = "";  // 1-based
1949        System.arraycopy(nWeekdays, 0, narrowWeekdays, 1, nWeekdays.length);
1950
1951        String [] swWeekdays = null;
1952        swWeekdays = arrays.get("dayNames/stand-alone/wide");
1953        standaloneWeekdays = new String[8];
1954        standaloneWeekdays[0] = "";  // 1-based
1955        System.arraycopy(swWeekdays, 0, standaloneWeekdays, 1, swWeekdays.length);
1956
1957        String [] saWeekdays = null;
1958        saWeekdays = arrays.get("dayNames/stand-alone/abbreviated");
1959        standaloneShortWeekdays = new String[8];
1960        standaloneShortWeekdays[0] = "";  // 1-based
1961        System.arraycopy(saWeekdays, 0, standaloneShortWeekdays, 1, saWeekdays.length);
1962
1963        String [] ssWeekdays = null;
1964        ssWeekdays = arrays.get("dayNames/stand-alone/short");
1965        standaloneShorterWeekdays = new String[8];
1966        standaloneShorterWeekdays[0] = "";  // 1-based
1967        System.arraycopy(ssWeekdays, 0, standaloneShorterWeekdays, 1, ssWeekdays.length);
1968
1969        String [] snWeekdays = null;
1970        snWeekdays = arrays.get("dayNames/stand-alone/narrow");
1971        standaloneNarrowWeekdays = new String[8];
1972        standaloneNarrowWeekdays[0] = "";  // 1-based
1973        System.arraycopy(snWeekdays, 0, standaloneNarrowWeekdays, 1, snWeekdays.length);
1974
1975        ampms = arrays.get("AmPmMarkers");
1976        ampmsNarrow = arrays.get("AmPmMarkersNarrow");
1977
1978        quarters = arrays.get("quarters/format/wide");
1979        shortQuarters = arrays.get("quarters/format/abbreviated");
1980
1981        standaloneQuarters = arrays.get("quarters/stand-alone/wide");
1982        standaloneShortQuarters = arrays.get("quarters/stand-alone/abbreviated");
1983
1984        abbreviatedDayPeriods = loadDayPeriodStrings(maps.get("dayPeriod/format/abbreviated"));
1985        wideDayPeriods = loadDayPeriodStrings(maps.get("dayPeriod/format/wide"));
1986        narrowDayPeriods = loadDayPeriodStrings(maps.get("dayPeriod/format/narrow"));
1987        standaloneAbbreviatedDayPeriods = loadDayPeriodStrings(maps.get("dayPeriod/stand-alone/abbreviated"));
1988        standaloneWideDayPeriods = loadDayPeriodStrings(maps.get("dayPeriod/stand-alone/wide"));
1989        standaloneNarrowDayPeriods = loadDayPeriodStrings(maps.get("dayPeriod/stand-alone/narrow"));
1990
1991        for (int i = 0; i < DT_MONTH_PATTERN_COUNT; i++) {
1992            String monthPatternPath = LEAP_MONTH_PATTERNS_PATHS[i];
1993            if (monthPatternPath != null) {
1994                Map<String, String> monthPatternMap = maps.get(monthPatternPath);
1995                if (monthPatternMap != null) {
1996                    String leapMonthPattern = monthPatternMap.get("leap");
1997                    if (leapMonthPattern != null) {
1998                        if (leapMonthPatterns == null) {
1999                            leapMonthPatterns = new String[DT_MONTH_PATTERN_COUNT];
2000                        }
2001                        leapMonthPatterns[i] = leapMonthPattern;
2002                    }
2003                }
2004            }
2005        }
2006
2007        shortYearNames = arrays.get("cyclicNameSets/years/format/abbreviated");
2008        shortZodiacNames = arrays.get("cyclicNameSets/zodiacs/format/abbreviated");
2009
2010        requestedLocale = desiredLocale;
2011
2012        ICUResourceBundle rb =
2013            (ICUResourceBundle)UResourceBundle.getBundleInstance(
2014                ICUData.ICU_BASE_NAME, desiredLocale);
2015
2016        localPatternChars = patternChars;
2017
2018        // TODO: obtain correct actual/valid locale later
2019        ULocale uloc = rb.getULocale();
2020        setLocale(uloc, uloc);
2021
2022        capitalization = new HashMap<CapitalizationContextUsage,boolean[]>();
2023        boolean[] noTransforms = new boolean[2];
2024        noTransforms[0] = false;
2025        noTransforms[1] = false;
2026        CapitalizationContextUsage allUsages[] = CapitalizationContextUsage.values();
2027        for (CapitalizationContextUsage usage: allUsages) {
2028            capitalization.put(usage, noTransforms);
2029        }
2030        UResourceBundle contextTransformsBundle = null;
2031        try {
2032           contextTransformsBundle = rb.getWithFallback("contextTransforms");
2033        }
2034        catch (MissingResourceException e) {
2035            contextTransformsBundle = null; // probably redundant
2036        }
2037        if (contextTransformsBundle != null) {
2038            UResourceBundleIterator ctIterator = contextTransformsBundle.getIterator();
2039            while ( ctIterator.hasNext() ) {
2040                UResourceBundle contextTransformUsage = ctIterator.next();
2041                int[] intVector = contextTransformUsage.getIntVector();
2042                if (intVector.length >= 2) {
2043                    String usageKey = contextTransformUsage.getKey();
2044                    CapitalizationContextUsage usage = contextUsageTypeMap.get(usageKey);
2045                    if (usage != null) {
2046                        boolean[] transforms = new boolean[2];
2047                        transforms[0] = (intVector[0] != 0);
2048                        transforms[1] = (intVector[1] != 0);
2049                        capitalization.put(usage, transforms);
2050                    }
2051                }
2052            }
2053        }
2054
2055        NumberingSystem ns = NumberingSystem.getInstance(desiredLocale);
2056        String nsName = ns == null ? "latn" : ns.getName();  // Latin is default.
2057        String tsPath = "NumberElements/" + nsName + "/symbols/timeSeparator";
2058        try {
2059            setTimeSeparatorString(rb.getStringWithFallback(tsPath));
2060        } catch (MissingResourceException e) {
2061            setTimeSeparatorString(DEFAULT_TIME_SEPARATOR);
2062        }
2063    }
2064
2065    /**
2066     * Resource bundle paths for each leap month pattern
2067     */
2068    private static final String[] LEAP_MONTH_PATTERNS_PATHS = new String[DT_MONTH_PATTERN_COUNT];
2069    static {
2070        LEAP_MONTH_PATTERNS_PATHS[DT_LEAP_MONTH_PATTERN_FORMAT_WIDE] = "monthPatterns/format/wide";
2071        LEAP_MONTH_PATTERNS_PATHS[DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV] = "monthPatterns/format/abbreviated";
2072        LEAP_MONTH_PATTERNS_PATHS[DT_LEAP_MONTH_PATTERN_FORMAT_NARROW] = "monthPatterns/format/narrow";
2073        LEAP_MONTH_PATTERNS_PATHS[DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE] = "monthPatterns/stand-alone/wide";
2074        LEAP_MONTH_PATTERNS_PATHS[DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV] = "monthPatterns/stand-alone/abbreviated";
2075        LEAP_MONTH_PATTERNS_PATHS[DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW] = "monthPatterns/stand-alone/narrow";
2076        LEAP_MONTH_PATTERNS_PATHS[DT_LEAP_MONTH_PATTERN_NUMERIC] = "monthPatterns/numeric/all";
2077    }
2078
2079    private static final boolean arrayOfArrayEquals(Object[][] aa1, Object[][]aa2) {
2080        if (aa1 == aa2) { // both are null
2081            return true;
2082        }
2083        if (aa1 == null || aa2 == null) { // one is null and the other is not
2084            return false;
2085        }
2086        if (aa1.length != aa2.length) {
2087            return false;
2088        }
2089        boolean equal = true;
2090        for (int i = 0; i < aa1.length; i++) {
2091            equal = Utility.arrayEquals(aa1[i], aa2[i]);
2092            if (!equal) {
2093                break;
2094            }
2095        }
2096        return equal;
2097    }
2098
2099    /**
2100     * Keys for dayPeriods
2101     */
2102    private static final String[] DAY_PERIOD_KEYS = {"midnight", "noon",
2103            "morning1", "afternoon1", "evening1", "night1",
2104            "morning2", "afternoon2", "evening2", "night2"};
2105
2106    /**
2107     * Loads localized names for day periods in the requested format.
2108     * @param resourceMap Contains the dayPeriod resource to load
2109     */
2110    private String[] loadDayPeriodStrings(Map<String, String> resourceMap) {
2111        String strings[] = new String[DAY_PERIOD_KEYS.length];
2112        if (resourceMap != null) {
2113            for (int i = 0; i < DAY_PERIOD_KEYS.length; ++i) {
2114                strings[i] = resourceMap.get(DAY_PERIOD_KEYS[i]);  // Null if string doesn't exist.
2115            }
2116        }
2117        return strings;
2118    }
2119
2120    /*
2121     * save the input locale
2122     */
2123    private ULocale requestedLocale;
2124
2125    /*
2126     * Clones an array of Strings.
2127     * @param srcArray the source array to be cloned.
2128     * @return a cloned array.
2129     */
2130    private final String[] duplicate(String[] srcArray)
2131    {
2132        return srcArray.clone();
2133    }
2134
2135    private final String[][] duplicate(String[][] srcArray)
2136    {
2137        String[][] aCopy = new String[srcArray.length][];
2138        for (int i = 0; i < srcArray.length; ++i)
2139            aCopy[i] = duplicate(srcArray[i]);
2140        return aCopy;
2141    }
2142
2143    /*
2144     * Compares the equality of the two arrays of String.
2145     * @param current this String array.
2146     * @param other that String array.
2147    private final boolean equals(String[] current, String[] other)
2148    {
2149        int count = current.length;
2150
2151        for (int i = 0; i < count; ++i)
2152            if (!current[i].equals(other[i]))
2153                return false;
2154        return true;
2155    }
2156     */
2157
2158    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2159
2160    /**
2161     * Returns the {@link DateFormatSymbols} object that should be used to format a
2162     * calendar system's dates in the given locale.
2163     *
2164     * @param cal       The calendar system whose date format symbols are desired.
2165     * @param locale    The locale whose symbols are desired.
2166     *
2167     * @see DateFormatSymbols#DateFormatSymbols(java.util.Locale)
2168     * @stable ICU 2.0
2169     */
2170    public DateFormatSymbols(Calendar cal, Locale locale) {
2171        initializeData(ULocale.forLocale(locale), cal.getType());
2172    }
2173
2174    /**
2175     * Returns the {@link DateFormatSymbols} object that should be used to format a
2176     * calendar system's dates in the given locale.
2177     * @param cal       The calendar system whose date format symbols are desired.
2178     * @param locale    The ulocale whose symbols are desired.
2179     *
2180     * @see DateFormatSymbols#DateFormatSymbols(java.util.Locale)
2181     * @stable ICU 3.2
2182     */
2183    public DateFormatSymbols(Calendar cal, ULocale locale) {
2184        initializeData(locale, cal.getType());
2185    }
2186
2187    /**
2188     * Variant of DateFormatSymbols(Calendar, Locale) that takes the Calendar class
2189     * instead of a Calendar instance.
2190     * @see #DateFormatSymbols(Calendar, Locale)
2191     * @stable ICU 2.2
2192     */
2193    public DateFormatSymbols(Class<? extends Calendar> calendarClass, Locale locale) {
2194        this(calendarClass, ULocale.forLocale(locale));
2195    }
2196
2197    /**
2198     * Variant of DateFormatSymbols(Calendar, ULocale) that takes the Calendar class
2199     * instead of a Calendar instance.
2200     * @see #DateFormatSymbols(Calendar, Locale)
2201     * @stable ICU 3.2
2202     */
2203    public DateFormatSymbols(Class<? extends Calendar> calendarClass, ULocale locale) {
2204        String fullName = calendarClass.getName();
2205        int lastDot = fullName.lastIndexOf('.');
2206        String className = fullName.substring(lastDot+1);
2207        String calType = null;
2208        for (String[] calClassInfo : CALENDAR_CLASSES) {
2209            if (calClassInfo[0].equals(className)) {
2210                calType = calClassInfo[1];
2211                break;
2212            }
2213        }
2214        if (calType == null) {
2215            calType = className.replaceAll("Calendar", "").toLowerCase(Locale.ENGLISH);
2216        }
2217
2218        initializeData(locale, calType);
2219    }
2220
2221    // Android patch (http://b/30464240) start: Add constructor taking a calendar type.
2222    /**
2223     * Variant of DateFormatSymbols(Calendar, ULocale) that takes the calendar type
2224     * instead of a Calendar instance.
2225     * @see #DateFormatSymbols(Calendar, Locale)
2226     * @internal
2227     * @deprecated This API is ICU internal only.
2228     */
2229    @Deprecated
2230    public DateFormatSymbols(ULocale locale, String calType) {
2231        initializeData(locale, calType);
2232    }
2233    // Android patch end.
2234
2235    /**
2236     * Fetches a custom calendar's DateFormatSymbols out of the given resource
2237     * bundle.  Symbols that are not overridden are inherited from the
2238     * default DateFormatSymbols for the locale.
2239     * @see DateFormatSymbols#DateFormatSymbols(java.util.Locale)
2240     * @stable ICU 2.0
2241     */
2242    public DateFormatSymbols(ResourceBundle bundle, Locale locale) {
2243        this(bundle, ULocale.forLocale(locale));
2244    }
2245
2246    /**
2247     * Fetches a custom calendar's DateFormatSymbols out of the given resource
2248     * bundle.  Symbols that are not overridden are inherited from the
2249     * default DateFormatSymbols for the locale.
2250     * @see DateFormatSymbols#DateFormatSymbols(java.util.Locale)
2251     * @stable ICU 3.2
2252     */
2253    public DateFormatSymbols(ResourceBundle bundle, ULocale locale) {
2254        initializeData(locale, (ICUResourceBundle) bundle, CalendarUtil.getCalendarType(locale));
2255    }
2256
2257    /**
2258     * Finds the ResourceBundle containing the date format information for
2259     * a specified calendar subclass in a given locale.
2260     * <p>
2261     * The resource bundle name is based on the calendar's fully-specified
2262     * class name, with ".resources" inserted at the end of the package name
2263     * (just before the class name) and "Symbols" appended to the end.
2264     * For example, the bundle corresponding to "com.ibm.icu.util.HebrewCalendar"
2265     * is "com.ibm.icu.impl.data.HebrewCalendarSymbols".
2266     * <p>
2267     * <b>Note:</b>Because of the structural changes in the ICU locale bundle,
2268     * this API no longer works as described.  This method always returns null.
2269     * @deprecated ICU 4.0
2270     */
2271    @Deprecated
2272    // This API was formerly @stable ICU 2.0
2273    static public ResourceBundle getDateFormatBundle(Class<? extends Calendar> calendarClass,
2274                                                     Locale locale)
2275        throws MissingResourceException {
2276        return null;
2277    }
2278
2279    /**
2280     * Finds the ResourceBundle containing the date format information for
2281     * a specified calendar subclass in a given locale.
2282     * <p>
2283     * The resource bundle name is based on the calendar's fully-specified
2284     * class name, with ".resources" inserted at the end of the package name
2285     * (just before the class name) and "Symbols" appended to the end.
2286     * For example, the bundle corresponding to "com.ibm.icu.util.HebrewCalendar"
2287     * is "com.ibm.icu.impl.data.HebrewCalendarSymbols".
2288     * <p>
2289     * <b>Note:</b>Because of the structural changes in the ICU locale bundle,
2290     * this API no longer works as described.  This method always returns null.
2291     * @deprecated ICU 4.0
2292     */
2293    @Deprecated
2294    // This API was formerly @stable ICU 3.2
2295    static public ResourceBundle getDateFormatBundle(Class<? extends Calendar> calendarClass,
2296                                                     ULocale locale)
2297        throws MissingResourceException {
2298        return null;
2299    }
2300
2301    /**
2302     * Variant of getDateFormatBundle(java.lang.Class, java.util.Locale) that takes
2303     * a Calendar instance instead of a Calendar class.
2304     * <p>
2305     * <b>Note:</b>Because of the structural changes in the ICU locale bundle,
2306     * this API no longer works as described.  This method always returns null.
2307     * @see #getDateFormatBundle(java.lang.Class, java.util.Locale)
2308     * @deprecated ICU 4.0
2309     */
2310    @Deprecated
2311    // This API was formerly @stable ICU 2.2
2312    public static ResourceBundle getDateFormatBundle(Calendar cal, Locale locale)
2313        throws MissingResourceException {
2314        return null;
2315    }
2316
2317    /**
2318     * Variant of getDateFormatBundle(java.lang.Class, java.util.Locale) that takes
2319     * a Calendar instance instead of a Calendar class.
2320     * <p>
2321     * <b>Note:</b>Because of the structural changes in the ICU locale bundle,
2322     * this API no longer works as described.  This method always returns null.
2323     * @see #getDateFormatBundle(java.lang.Class, java.util.Locale)
2324     * @deprecated ICU 4.0
2325     */
2326    @Deprecated
2327    // This API was formerly @stable ICU 3.2
2328    public static ResourceBundle getDateFormatBundle(Calendar cal, ULocale locale)
2329        throws MissingResourceException {
2330        return null;
2331    }
2332
2333    // -------- BEGIN ULocale boilerplate --------
2334
2335    /**
2336     * Returns the locale that was used to create this object, or null.
2337     * This may may differ from the locale requested at the time of
2338     * this object's creation.  For example, if an object is created
2339     * for locale <tt>en_US_CALIFORNIA</tt>, the actual data may be
2340     * drawn from <tt>en</tt> (the <i>actual</i> locale), and
2341     * <tt>en_US</tt> may be the most specific locale that exists (the
2342     * <i>valid</i> locale).
2343     *
2344     * <p>Note: This method will be implemented in ICU 3.0; ICU 2.8
2345     * contains a partial preview implementation.  The * <i>actual</i>
2346     * locale is returned correctly, but the <i>valid</i> locale is
2347     * not, in most cases.
2348     * @param type type of information requested, either {@link
2349     * com.ibm.icu.util.ULocale#VALID_LOCALE} or {@link
2350     * com.ibm.icu.util.ULocale#ACTUAL_LOCALE}.
2351     * @return the information specified by <i>type</i>, or null if
2352     * this object was not constructed from locale data.
2353     * @see com.ibm.icu.util.ULocale
2354     * @see com.ibm.icu.util.ULocale#VALID_LOCALE
2355     * @see com.ibm.icu.util.ULocale#ACTUAL_LOCALE
2356     * @draft ICU 2.8 (retain)
2357     * @provisional This API might change or be removed in a future release.
2358     */
2359    public final ULocale getLocale(ULocale.Type type) {
2360        return type == ULocale.ACTUAL_LOCALE ?
2361            this.actualLocale : this.validLocale;
2362    }
2363
2364    /**
2365     * Sets information about the locales that were used to create this
2366     * object.  If the object was not constructed from locale data,
2367     * both arguments should be set to null.  Otherwise, neither
2368     * should be null.  The actual locale must be at the same level or
2369     * less specific than the valid locale.  This method is intended
2370     * for use by factories or other entities that create objects of
2371     * this class.
2372     * @param valid the most specific locale containing any resource
2373     * data, or null
2374     * @param actual the locale containing data used to construct this
2375     * object, or null
2376     * @see com.ibm.icu.util.ULocale
2377     * @see com.ibm.icu.util.ULocale#VALID_LOCALE
2378     * @see com.ibm.icu.util.ULocale#ACTUAL_LOCALE
2379     */
2380    final void setLocale(ULocale valid, ULocale actual) {
2381        // Change the following to an assertion later
2382        if ((valid == null) != (actual == null)) {
2383            ///CLOVER:OFF
2384            throw new IllegalArgumentException();
2385            ///CLOVER:ON
2386        }
2387        // Another check we could do is that the actual locale is at
2388        // the same level or less specific than the valid locale.
2389        this.validLocale = valid;
2390        this.actualLocale = actual;
2391    }
2392
2393    /**
2394     * The most specific locale containing any resource data, or null.
2395     * @see com.ibm.icu.util.ULocale
2396     */
2397    private ULocale validLocale;
2398
2399    /**
2400     * The locale containing data used to construct this object, or
2401     * null.
2402     * @see com.ibm.icu.util.ULocale
2403     */
2404    private ULocale actualLocale;
2405
2406    // -------- END ULocale boilerplate --------
2407
2408    /**
2409     * 3.8 or older version did not have localized GMT format
2410     * patterns.
2411     */
2412    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
2413        stream.defaultReadObject();
2414    }
2415}
2416