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 // Android-changed: remove mention of CalendarDataProvider that's not used on Android. 39 /** 40 * {@code CalendarDataUtility} is a utility class for getting calendar field name values. 41 * 42 * @author Masayoshi Okutsu 43 * @author Naoto Sato 44 */ 45 public class CalendarDataUtility { 46 47 // Android-note: This class has been rewritten from scratch, keeping its API the same. 48 // Since Android gets its calendar related data from ICU, the implementation of this class is 49 // effectively independent of the upstream class, with the only similarity being is API in 50 // order to keep the necessary modifications outside of this class to a minimum. 51 52 // Android-added: calendar name constants for use in retrievFieldValueName. 53 private static final String ISLAMIC_CALENDAR = "islamic"; 54 private static final String GREGORIAN_CALENDAR = "gregorian"; 55 private static final String BUDDHIST_CALENDAR = "buddhist"; 56 private static final String JAPANESE_CALENDAR = "japanese"; 57 58 // Android-added: REST_OF_STYLES array for use in retrieveFieldValueNames. 59 // ALL_STYLES implies SHORT_FORMAT and all of these values. 60 private static int[] REST_OF_STYLES = { 61 SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE, 62 NARROW_FORMAT, NARROW_STANDALONE 63 }; 64 65 // Android-removed: unused FIRST_DAY_OF_WEEK and MINIMAL_DAYS_IN_FIRST_WEEK constants. 66 67 // No instantiation 68 private CalendarDataUtility() { 69 } 70 71 // Android-removed: retrieveFirstDayOfWeek and retrieveMinimalDaysInFirstWeek. 72 // Android-note: use libcore.icu.LocaleData or android.icu.util.Calendar.WeekData instead. 73 74 public static String retrieveFieldValueName(String id, int field, int value, int style, 75 Locale locale) { 76 // Android-changed: delegate to ICU. 77 if (field == Calendar.ERA) { 78 // For era the field value does not always equal the index into the names array. 79 switch (normalizeCalendarType(id)) { 80 // These calendars have only one era, but represented it by the value 1. 81 case BUDDHIST_CALENDAR: 82 case ISLAMIC_CALENDAR: 83 value -= 1; 84 break; 85 case JAPANESE_CALENDAR: 86 // CLDR contains full data for historical eras, java.time only supports the 4 87 // modern eras and numbers the modern eras starting with 1 (MEIJI). There are 88 // 232 historical eras in CLDR/ICU so to get the real offset, we add 231. 89 value += 231; 90 break; 91 default: 92 // Other eras use 0-based values (e.g. 0=BCE, 1=CE for gregorian). 93 break; 94 } 95 } 96 if (value < 0) { 97 return null; 98 } 99 String[] names = getNames(id, field, style, locale); 100 if (value >= names.length) { 101 return null; 102 } 103 return names[value]; 104 } 105 106 public static String retrieveJavaTimeFieldValueName(String id, int field, int value, int style, 107 Locale locale) { 108 // Android-changed: don't distinguish between retrieve* and retrieveJavaTime* methods. 109 return retrieveFieldValueName(id, field, value, style, locale); 110 } 111 112 public static Map<String, Integer> retrieveFieldValueNames(String id, int field, int style, 113 Locale locale) { 114 // Android-changed: delegate to ICU. 115 Map<String, Integer> names; 116 if (style == ALL_STYLES) { 117 names = retrieveFieldValueNamesImpl(id, field, SHORT_FORMAT, locale); 118 for (int st : REST_OF_STYLES) { 119 names.putAll(retrieveFieldValueNamesImpl(id, field, st, locale)); 120 } 121 } else { 122 // specific style 123 names = retrieveFieldValueNamesImpl(id, field, style, locale); 124 } 125 return names.isEmpty() ? null : names; 126 } 127 128 public static Map<String, Integer> retrieveJavaTimeFieldValueNames(String id, int field, 129 int style, Locale locale) { 130 // Android-changed: don't distinguish between retrieve* and retrieveJavaTime* methods. 131 return retrieveFieldValueNames(id, field, style, locale); 132 } 133 134 private static String normalizeCalendarType(String requestID) { 135 String type; 136 // Android-changed: normalize "gregory" to "gregorian", not the other way around. 137 // See android.icu.text.DateFormatSymbols.CALENDAR_CLASSES for reference. 138 if (requestID.equals("gregory") || requestID.equals("iso8601")) { 139 type = GREGORIAN_CALENDAR; 140 } else if (requestID.startsWith(ISLAMIC_CALENDAR)) { 141 type = ISLAMIC_CALENDAR; 142 } else { 143 type = requestID; 144 } 145 return type; 146 } 147 148 // BEGIN Android-added: various private helper methods. 149 private static Map<String, Integer> retrieveFieldValueNamesImpl(String id, int field, int style, 150 Locale locale) { 151 String[] names = getNames(id, field, style, locale); 152 int skipped = 0; 153 int offset = 0; 154 if (field == Calendar.ERA) { 155 // See retrieveFieldValueName() for explanation of this code and the values used. 156 switch (normalizeCalendarType(id)) { 157 case BUDDHIST_CALENDAR: 158 case ISLAMIC_CALENDAR: 159 offset = 1; 160 break; 161 case JAPANESE_CALENDAR: 162 skipped = 232; 163 offset = -231; 164 break; 165 default: 166 break; 167 } 168 } 169 Map<String, Integer> result = new LinkedHashMap<>(); 170 for (int i = skipped; i < names.length; i++) { 171 if (names[i].isEmpty()) { 172 continue; 173 } 174 175 if (result.put(names[i], i + offset) != null) { 176 // Duplicate names indicate that the names would be ambiguous. Skip this style for 177 // ALL_STYLES. In other cases this results in null being returned in 178 // retrieveValueNames(), which is required by Calendar.getDisplayNames(). 179 return new LinkedHashMap<>(); 180 } 181 } 182 return result; 183 } 184 185 private static String[] getNames(String id, int field, int style, Locale locale) { 186 int context = toContext(style); 187 int width = toWidth(style); 188 DateFormatSymbols symbols = getDateFormatSymbols(id, locale); 189 switch (field) { 190 case Calendar.MONTH: 191 return symbols.getMonths(context, width); 192 case Calendar.ERA: 193 switch (width) { 194 case DateFormatSymbols.NARROW: 195 return symbols.getNarrowEras(); 196 case DateFormatSymbols.ABBREVIATED: 197 return symbols.getEras(); 198 case DateFormatSymbols.WIDE: 199 return symbols.getEraNames(); 200 default: 201 throw new UnsupportedOperationException("Unknown width: " + width); 202 } 203 case Calendar.DAY_OF_WEEK: 204 return symbols.getWeekdays(context, width); 205 case Calendar.AM_PM: 206 return symbols.getAmPmStrings(); 207 default: 208 throw new UnsupportedOperationException("Unknown field: " + field); 209 } 210 } 211 212 private static DateFormatSymbols getDateFormatSymbols(String id, Locale locale) { 213 String calendarType = normalizeCalendarType(id); 214 return new DateFormatSymbols(ULocale.forLocale(locale), calendarType); 215 } 216 217 /** 218 * Transform a {@link Calendar} style constant into an ICU width value. 219 */ 220 private static int toWidth(int style) { 221 switch (style) { 222 case Calendar.SHORT_FORMAT: 223 case Calendar.SHORT_STANDALONE: 224 return DateFormatSymbols.ABBREVIATED; 225 case Calendar.NARROW_FORMAT: 226 case Calendar.NARROW_STANDALONE: 227 return DateFormatSymbols.NARROW; 228 case Calendar.LONG_FORMAT: 229 case Calendar.LONG_STANDALONE: 230 return DateFormatSymbols.WIDE; 231 default: 232 throw new IllegalArgumentException("Invalid style: " + style); 233 } 234 } 235 236 /** 237 * Transform a {@link Calendar} style constant into an ICU context value. 238 */ 239 private static int toContext(int style) { 240 switch (style) { 241 case Calendar.SHORT_FORMAT: 242 case Calendar.NARROW_FORMAT: 243 case Calendar.LONG_FORMAT: 244 return DateFormatSymbols.FORMAT; 245 case Calendar.SHORT_STANDALONE: 246 case Calendar.NARROW_STANDALONE: 247 case Calendar.LONG_STANDALONE: 248 return DateFormatSymbols.STANDALONE; 249 default: 250 throw new IllegalArgumentException("Invalid style: " + style); 251 } 252 } 253 // END Android-added: various private helper methods. 254 255 // Android-removed: CalendarFieldValueNameGetter, CalendarFieldValueNamesMapGetter 256 // Android-removed: CalendarWeekParameterGetter 257 } 258