1 /*
2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.util.locale.provider;
27 
28 import android.icu.text.DateFormatSymbols;
29 import android.icu.util.ULocale;
30 
31 import static java.util.Calendar.*;
32 
33 import java.util.Calendar;
34 import java.util.LinkedHashMap;
35 import java.util.Locale;
36 import java.util.Map;
37 
38 /**
39  * {@code CalendarDataUtility} is a utility class for getting calendar field name values.
40  *
41  * @author Masayoshi Okutsu
42  * @author Naoto Sato
43  */
44 public class CalendarDataUtility {
45 
46     private static final String ISLAMIC_CALENDAR = "islamic";
47     private static final String GREGORIAN_CALENDAR = "gregorian";
48     private static final String BUDDHIST_CALENDAR = "buddhist";
49     private static final String JAPANESE_CALENDAR = "japanese";
50 
51     // No instantiation
CalendarDataUtility()52     private CalendarDataUtility() {
53     }
54 
55     // Android-changed: Removed retrieveFirstDayOfWeek and retrieveMinimalDaysInFirstWeek.
56     // use libcore.icu.LocaleData or android.icu.util.Calendar.WeekData instead
57 
retrieveFieldValueName(String id, int field, int value, int style, Locale locale)58     public static String retrieveFieldValueName(String id, int field, int value, int style,
59             Locale locale) {
60         // Android-changed: delegate to ICU.
61         if (field == Calendar.ERA) {
62             // For era the field value does not always equal the index into the names array.
63             switch (normalizeCalendarType(id)) {
64                 // These calendars have only one era, but represented it by the value 1.
65                 case BUDDHIST_CALENDAR:
66                 case ISLAMIC_CALENDAR:
67                     value -= 1;
68                     break;
69                 case JAPANESE_CALENDAR:
70                     // CLDR contains full data for historical eras, java.time only supports the 4
71                     // modern eras and numbers the modern eras starting with 1 (MEIJI). There are
72                     // 232 historical eras in CLDR/ICU so to get the real offset, we add 231.
73                     value += 231;
74                     break;
75                 default:
76                     // Other eras use 0-based values (e.g. 0=BCE, 1=CE for gregorian).
77                     break;
78             }
79         }
80         if (value < 0) {
81             return null;
82         }
83         String[] names = getNames(id, field, style, locale);
84         if (value >= names.length) {
85             return null;
86         }
87         return names[value];
88     }
89 
retrieveJavaTimeFieldValueName(String id, int field, int value, int style, Locale locale)90     public static String retrieveJavaTimeFieldValueName(String id, int field, int value, int style,
91             Locale locale) {
92         // Android-changed: don't distinguish between retrieve* and retrieveJavaTime* methods.
93         return retrieveFieldValueName(id, field, value, style, locale);
94     }
95 
96     // ALL_STYLES implies SHORT_FORMAT and all of these values.
97     private static int[] REST_OF_STYLES = {
98             SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE,
99             NARROW_FORMAT, NARROW_STANDALONE
100     };
101 
retrieveFieldValueNames(String id, int field, int style, Locale locale)102     public static Map<String, Integer> retrieveFieldValueNames(String id, int field, int style,
103             Locale locale) {
104         // Android-changed: delegate to ICU.
105         Map<String, Integer> names;
106         if (style == ALL_STYLES) {
107             names = retrieveFieldValueNamesImpl(id, field, SHORT_FORMAT, locale);
108             for (int st : REST_OF_STYLES) {
109                 names.putAll(retrieveFieldValueNamesImpl(id, field, st, locale));
110             }
111         } else {
112             // specific style
113             names = retrieveFieldValueNamesImpl(id, field, style, locale);
114         }
115         return names.isEmpty() ? null : names;
116     }
117 
retrieveFieldValueNamesImpl(String id, int field, int style, Locale locale)118     private static Map<String, Integer> retrieveFieldValueNamesImpl(String id, int field, int style,
119             Locale locale) {
120         String[] names = getNames(id, field, style, locale);
121         int skipped = 0;
122         int offset = 0;
123         if (field == Calendar.ERA) {
124             // See retrieveFieldValueName() for explanation of this code and the values used.
125             switch (normalizeCalendarType(id)) {
126                 case BUDDHIST_CALENDAR:
127                 case ISLAMIC_CALENDAR:
128                     offset = 1;
129                     break;
130                 case JAPANESE_CALENDAR:
131                     skipped = 232;
132                     offset = -231;
133                     break;
134                 default:
135                     break;
136             }
137         }
138         Map<String, Integer> result = new LinkedHashMap<>();
139         for (int i = skipped; i < names.length; i++) {
140             if (names[i].isEmpty()) {
141                 continue;
142             }
143 
144             if (result.put(names[i], i + offset) != null) {
145                 // Duplicate names indicate that the names would be ambiguous. Skip this style for
146                 // ALL_STYLES. In other cases this results in null being returned in
147                 // retrieveValueNames(), which is required by Calendar.getDisplayNames().
148                 return new LinkedHashMap<>();
149             }
150         }
151         return result;
152     }
153 
retrieveJavaTimeFieldValueNames(String id, int field, int style, Locale locale)154     public static Map<String, Integer> retrieveJavaTimeFieldValueNames(String id, int field,
155             int style, Locale locale) {
156         // Android-changed: don't distinguish between retrieve* and retrieveJavaTime* methods.
157         return retrieveFieldValueNames(id, field, style, locale);
158     }
159 
getNames(String id, int field, int style, Locale locale)160     private static String[] getNames(String id, int field, int style, Locale locale) {
161         int context = toContext(style);
162         int width = toWidth(style);
163         DateFormatSymbols symbols = getDateFormatSymbols(id, locale);
164         switch (field) {
165             case Calendar.MONTH:
166                 return symbols.getMonths(context, width);
167             case Calendar.ERA:
168                 switch (width) {
169                     case DateFormatSymbols.NARROW:
170                         return symbols.getNarrowEras();
171                     case DateFormatSymbols.ABBREVIATED:
172                         return symbols.getEras();
173                     case DateFormatSymbols.WIDE:
174                         return symbols.getEraNames();
175                     default:
176                         throw new UnsupportedOperationException("Unknown width: " + width);
177                 }
178             case Calendar.DAY_OF_WEEK:
179                 return symbols.getWeekdays(context, width);
180             case Calendar.AM_PM:
181                 return symbols.getAmPmStrings();
182             default:
183                 throw new UnsupportedOperationException("Unknown field: " + field);
184         }
185     }
186 
getDateFormatSymbols(String id, Locale locale)187     private static DateFormatSymbols getDateFormatSymbols(String id, Locale locale) {
188         String calendarType = normalizeCalendarType(id);
189         return new DateFormatSymbols(ULocale.forLocale(locale), calendarType);
190     }
191 
192     /**
193      * Transform a {@link Calendar} style constant into an ICU width value.
194      */
toWidth(int style)195     private static int toWidth(int style) {
196         switch (style) {
197             case Calendar.SHORT_FORMAT:
198             case Calendar.SHORT_STANDALONE:
199                 return DateFormatSymbols.ABBREVIATED;
200             case Calendar.NARROW_FORMAT:
201             case Calendar.NARROW_STANDALONE:
202                 return DateFormatSymbols.NARROW;
203             case Calendar.LONG_FORMAT:
204             case Calendar.LONG_STANDALONE:
205                 return DateFormatSymbols.WIDE;
206             default:
207                 throw new IllegalArgumentException("Invalid style: " + style);
208         }
209     }
210 
211     /**
212      * Transform a {@link Calendar} style constant into an ICU context value.
213      */
toContext(int style)214     private static int toContext(int style) {
215         switch (style) {
216             case Calendar.SHORT_FORMAT:
217             case Calendar.NARROW_FORMAT:
218             case Calendar.LONG_FORMAT:
219                 return DateFormatSymbols.FORMAT;
220             case Calendar.SHORT_STANDALONE:
221             case Calendar.NARROW_STANDALONE:
222             case Calendar.LONG_STANDALONE:
223                 return DateFormatSymbols.STANDALONE;
224             default:
225                 throw new IllegalArgumentException("Invalid style: " + style);
226         }
227     }
228 
normalizeCalendarType(String requestID)229     private static String normalizeCalendarType(String requestID) {
230         String type;
231         // Android-changed: normalize "gregory" to "gregorian", not the other way around.
232         // See android.icu.text.DateFormatSymbols.CALENDAR_CLASSES for reference.
233         if (requestID.equals("gregory") || requestID.equals("iso8601")) {
234             type = GREGORIAN_CALENDAR;
235         } else if (requestID.startsWith(ISLAMIC_CALENDAR)) {
236             type = ISLAMIC_CALENDAR;
237         } else {
238             type = requestID;
239         }
240         return type;
241     }
242 
243     // Android-changed: Removed CalendarFieldValueNameGetter, CalendarFieldValueNamesMapGetter
244     // and CalendarWeekParameterGetter
245 }
246