1 /* 2 * Copyright (c) 2012, 2019, 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 libcore.util.NonNull; 34 35 import java.util.Calendar; 36 import java.util.LinkedHashMap; 37 import java.util.Locale; 38 import java.util.Map; 39 40 // Android-changed: remove mention of CalendarDataProvider that's not used on Android. 41 /** 42 * {@code CalendarDataUtility} is a utility class for getting calendar field name values. 43 * 44 * @author Masayoshi Okutsu 45 * @author Naoto Sato 46 */ 47 public class CalendarDataUtility { 48 // Android-note: This class has been rewritten from scratch and is effectively forked. 49 // The API (names of public constants, method signatures etc.) generally derives 50 // from OpenJDK so that other OpenJDK code that refers to this class doesn't need to 51 // be changed, but the implementation has been rewritten; logic / identifiers 52 // that weren't used from anywhere else have been dropped altogether. 53 54 // Android-removed: Dead code, unused on Android. 55 // public static final String FIRST_DAY_OF_WEEK = "firstDayOfWeek"; 56 // public static final String MINIMAL_DAYS_IN_FIRST_WEEK = "minimalDaysInFirstWeek"; 57 58 // Android-added: Calendar name constants for use in retrievFieldValueName. 59 private static final String ISLAMIC_CALENDAR = "islamic"; 60 private static final String GREGORIAN_CALENDAR = "gregorian"; 61 private static final String BUDDHIST_CALENDAR = "buddhist"; 62 private static final String JAPANESE_CALENDAR = "japanese"; 63 64 // Android-added: REST_OF_STYLES array for use in retrieveFieldValueNames. 65 // ALL_STYLES implies SHORT_FORMAT and all of these values. 66 private static int[] REST_OF_STYLES = { 67 SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE, 68 NARROW_FORMAT, NARROW_STANDALONE 69 }; 70 71 // No instantiation CalendarDataUtility()72 private CalendarDataUtility() { 73 } 74 75 // Android-changed: Modify retrieveFirstDayOfWeek() to provide the default value. 76 // public static int retrieveFirstDayOfWeek(Locale locale) { 77 /** 78 * @param defaultFirstDayOfWeek default first day of week if the {@code locale} doesn't have the 79 * "fw" extension. This default can be obtained from the locale via ICU4J 80 * {@code android.icu.util.Calendar}. 81 * @return 82 */ retrieveFirstDayOfWeek(Locale locale, int defaultFirstDayOfWeek)83 public static int retrieveFirstDayOfWeek(Locale locale, int defaultFirstDayOfWeek) { 84 // Look for the Unicode Extension in the locale parameter 85 if (locale.hasExtensions()) { 86 String fw = locale.getUnicodeLocaleType("fw"); 87 if (fw != null) { 88 switch (fw.toLowerCase(Locale.ROOT)) { 89 case "mon": 90 return MONDAY; 91 case "tue": 92 return TUESDAY; 93 case "wed": 94 return WEDNESDAY; 95 case "thu": 96 return THURSDAY; 97 case "fri": 98 return FRIDAY; 99 case "sat": 100 return SATURDAY; 101 case "sun": 102 return SUNDAY; 103 } 104 } 105 } 106 107 // Android-changed: Modify retrieveFirstDayOfWeek() to use the default first day of week. 108 /* 109 LocaleServiceProviderPool pool = 110 LocaleServiceProviderPool.getPool(CalendarDataProvider.class); 111 Integer value = pool.getLocalizedObject(CalendarWeekParameterGetter.INSTANCE, 112 findRegionOverride(locale), 113 true, FIRST_DAY_OF_WEEK); 114 return (value != null && (value >= SUNDAY && value <= SATURDAY)) ? value : SUNDAY; 115 */ 116 return defaultFirstDayOfWeek; 117 } 118 119 // BEGIN Android-removed: Dead code, unused on Android. 120 /* 121 public static int retrieveMinimalDaysInFirstWeek(Locale locale) { 122 LocaleServiceProviderPool pool = 123 LocaleServiceProviderPool.getPool(CalendarDataProvider.class); 124 Integer value = pool.getLocalizedObject(CalendarWeekParameterGetter.INSTANCE, 125 findRegionOverride(locale), 126 true, MINIMAL_DAYS_IN_FIRST_WEEK); 127 return (value != null && (value >= 1 && value <= 7)) ? value : 1; 128 } 129 */ 130 // END Android-removed: Dead code, unused on Android. 131 132 // BEGIN Android-changed: Implement on top of ICU. 133 /* 134 public static String retrieveFieldValueName(String id, int field, int value, int style, Locale locale) { 135 LocaleServiceProviderPool pool = 136 LocaleServiceProviderPool.getPool(CalendarNameProvider.class); 137 return pool.getLocalizedObject(CalendarFieldValueNameGetter.INSTANCE, locale, normalizeCalendarType(id), 138 field, value, style, false); 139 } 140 */ retrieveFieldValueName(String id, int field, int value, int style, Locale locale)141 public static String retrieveFieldValueName(String id, int field, int value, int style, 142 Locale locale) { 143 if (field == Calendar.ERA) { 144 // For era the field value does not always equal the index into the names array. 145 switch (normalizeCalendarType(id)) { 146 // These calendars have only one era, but represented it by the value 1. 147 case BUDDHIST_CALENDAR: 148 case ISLAMIC_CALENDAR: 149 value -= 1; 150 break; 151 case JAPANESE_CALENDAR: 152 // CLDR contains full data for historical eras, java.time only supports the 4 153 // modern eras and numbers the modern eras starting with 1 (MEIJI). There are 154 // 232 historical eras in CLDR/ICU so to get the real offset, we add 231. 155 value += 231; 156 break; 157 default: 158 // Other eras use 0-based values (e.g. 0=BCE, 1=CE for gregorian). 159 break; 160 } 161 } 162 if (value < 0) { 163 return null; 164 } 165 String[] names = getNames(id, field, style, locale); 166 if (value >= names.length) { 167 return null; 168 } 169 return names[value]; 170 } 171 // END Android-changed: Implement on top of ICU. 172 173 // BEGIN Android-changed: Implement on top of ICU. 174 /* 175 public static String retrieveJavaTimeFieldValueName(String id, int field, int value, int style, Locale locale) { 176 LocaleServiceProviderPool pool = 177 LocaleServiceProviderPool.getPool(CalendarNameProvider.class); 178 String name; 179 name = pool.getLocalizedObject(CalendarFieldValueNameGetter.INSTANCE, locale, normalizeCalendarType(id), 180 field, value, style, true); 181 if (name == null) { 182 name = pool.getLocalizedObject(CalendarFieldValueNameGetter.INSTANCE, locale, normalizeCalendarType(id), 183 field, value, style, false); 184 } 185 return name; 186 } 187 */ retrieveJavaTimeFieldValueName(String id, int field, int value, int style, Locale locale)188 public static String retrieveJavaTimeFieldValueName(String id, int field, int value, int style, 189 Locale locale) { 190 // Don't distinguish between retrieve* and retrieveJavaTime* methods. 191 return retrieveFieldValueName(id, field, value, style, locale); 192 } 193 // END Android-changed: Implement on top of ICU. 194 195 // BEGIN Android-changed: Implement on top of ICU. 196 /* 197 public static Map<String, Integer> retrieveFieldValueNames(String id, int field, int style, Locale locale) { 198 LocaleServiceProviderPool pool = 199 LocaleServiceProviderPool.getPool(CalendarNameProvider.class); 200 return pool.getLocalizedObject(CalendarFieldValueNamesMapGetter.INSTANCE, locale, 201 normalizeCalendarType(id), field, style, false); 202 } 203 */ retrieveFieldValueNames(String id, int field, int style, Locale locale)204 public static Map<String, Integer> retrieveFieldValueNames(String id, int field, int style, 205 Locale locale) { 206 Map<String, Integer> names; 207 if (style == ALL_STYLES) { 208 names = retrieveFieldValueNamesImpl(id, field, SHORT_FORMAT, locale); 209 for (int st : REST_OF_STYLES) { 210 names.putAll(retrieveFieldValueNamesImpl(id, field, st, locale)); 211 } 212 } else { 213 // specific style 214 names = retrieveFieldValueNamesImpl(id, field, style, locale); 215 } 216 return names.isEmpty() ? null : names; 217 } 218 // END Android-changed: Implement on top of ICU. 219 220 // BEGIN Android-changed: Implement on top of ICU. 221 /* 222 public static Map<String, Integer> retrieveJavaTimeFieldValueNames(String id, int field, int style, Locale locale) { 223 LocaleServiceProviderPool pool = 224 LocaleServiceProviderPool.getPool(CalendarNameProvider.class); 225 Map<String, Integer> map; 226 map = pool.getLocalizedObject(CalendarFieldValueNamesMapGetter.INSTANCE, locale, 227 normalizeCalendarType(id), field, style, true); 228 if (map == null) { 229 map = pool.getLocalizedObject(CalendarFieldValueNamesMapGetter.INSTANCE, locale, 230 normalizeCalendarType(id), field, style, false); 231 } 232 return map; 233 } 234 */ retrieveJavaTimeFieldValueNames(String id, int field, int style, Locale locale)235 public static Map<String, Integer> retrieveJavaTimeFieldValueNames(String id, int field, 236 int style, Locale locale) { 237 // Don't distinguish between retrieve* and retrieveJavaTime* methods. 238 return retrieveFieldValueNames(id, field, style, locale); 239 } 240 // END Android-changed: Implement on top of ICU. 241 242 /** 243 * Utility to look for a region override extension. 244 * If no region override is found, returns the original locale. 245 */ 246 // BEGIN Android-removed: Dead code, unused on Android. 247 /* 248 public static Locale findRegionOverride(Locale l) { 249 String rg = l.getUnicodeLocaleType("rg"); 250 Locale override = l; 251 252 if (rg != null && rg.length() == 6) { 253 // UN M.49 code should not be allowed here 254 // cannot use regex here, as it could be a recursive call 255 rg = rg.toUpperCase(Locale.ROOT); 256 if (rg.charAt(0) >= 0x0041 && 257 rg.charAt(0) <= 0x005A && 258 rg.charAt(1) >= 0x0041 && 259 rg.charAt(1) <= 0x005A && 260 rg.substring(2).equals("ZZZZ")) { 261 override = new Locale.Builder().setLocale(l) 262 .setRegion(rg.substring(0, 2)) 263 .build(); 264 } 265 } 266 267 return override; 268 } 269 */ 270 // END Android-removed: Dead code, unused on Android. 271 272 // Android-changed: Added private modifier for normalizeCalendarType(). 273 // static String normalizeCalendarType(String requestID) { normalizeCalendarType(String requestID)274 private static String normalizeCalendarType(String requestID) { 275 String type; 276 // Android-changed: normalize "gregory" to "gregorian", not the other way around. 277 // Android maps BCP-47 calendar types to LDML defined calendar types, because it uses 278 // ICU directly while the upstream does the opposite because the upstream uses different 279 // data sources. See android.icu.text.DateFormatSymbols.CALENDAR_CLASSES for reference. 280 // if (requestID.equals("gregorian") || requestID.equals("iso8601")) { 281 // type = "gregory"; 282 // } else if (requestID.startsWith("islamic")) { 283 // type = "islamic"; 284 if (requestID.equals("gregory") || requestID.equals("iso8601")) { 285 type = GREGORIAN_CALENDAR; 286 } else if (requestID.startsWith(ISLAMIC_CALENDAR)) { 287 type = ISLAMIC_CALENDAR; 288 } else { 289 type = requestID; 290 } 291 return type; 292 } 293 294 // BEGIN Android-added: Various private helper methods. retrieveFieldValueNamesImpl(String id, int field, int style, Locale locale)295 private static Map<String, Integer> retrieveFieldValueNamesImpl(String id, int field, int style, 296 Locale locale) { 297 String[] names = getNames(id, field, style, locale); 298 int skipped = 0; 299 int offset = 0; 300 if (field == Calendar.ERA) { 301 // See retrieveFieldValueName() for explanation of this code and the values used. 302 switch (normalizeCalendarType(id)) { 303 case BUDDHIST_CALENDAR: 304 case ISLAMIC_CALENDAR: 305 offset = 1; 306 break; 307 case JAPANESE_CALENDAR: 308 skipped = 232; 309 offset = -231; 310 break; 311 default: 312 break; 313 } 314 } 315 Map<String, Integer> result = new LinkedHashMap<>(); 316 for (int i = skipped; i < names.length; i++) { 317 if (names[i].isEmpty()) { 318 continue; 319 } 320 321 if (result.put(names[i], i + offset) != null) { 322 // Duplicate names indicate that the names would be ambiguous. Skip this style for 323 // ALL_STYLES. In other cases this results in null being returned in 324 // retrieveValueNames(), which is required by Calendar.getDisplayNames(). 325 return new LinkedHashMap<>(); 326 } 327 } 328 return result; 329 } 330 getNames(String id, int field, int style, Locale locale)331 private static String[] getNames(String id, int field, int style, Locale locale) { 332 int context = toContext(style); 333 int width = toWidth(style); 334 DateFormatSymbols symbols = getDateFormatSymbols(id, locale); 335 switch (field) { 336 case Calendar.MONTH: 337 return symbols.getMonths(context, width); 338 case Calendar.ERA: 339 switch (width) { 340 case DateFormatSymbols.NARROW: 341 return symbols.getNarrowEras(); 342 case DateFormatSymbols.ABBREVIATED: 343 return symbols.getEras(); 344 case DateFormatSymbols.WIDE: 345 return symbols.getEraNames(); 346 default: 347 throw new UnsupportedOperationException("Unknown width: " + width); 348 } 349 case Calendar.DAY_OF_WEEK: 350 return symbols.getWeekdays(context, width); 351 case Calendar.AM_PM: 352 return symbols.getAmPmStrings(); 353 default: 354 throw new UnsupportedOperationException("Unknown field: " + field); 355 } 356 } 357 getDateFormatSymbols(@onNull String id, Locale locale)358 private static DateFormatSymbols getDateFormatSymbols(@NonNull String id, Locale locale) { 359 String calendarType = normalizeCalendarType(id); 360 ULocale uLocale = ULocale.forLocale(locale) 361 .setKeywordValue("calendar", calendarType); 362 return new DateFormatSymbols(uLocale); 363 } 364 365 /** 366 * Transform a {@link Calendar} style constant into an ICU width value. 367 */ toWidth(int style)368 private static int toWidth(int style) { 369 switch (style) { 370 case Calendar.SHORT_FORMAT: 371 case Calendar.SHORT_STANDALONE: 372 return DateFormatSymbols.ABBREVIATED; 373 case Calendar.NARROW_FORMAT: 374 case Calendar.NARROW_STANDALONE: 375 return DateFormatSymbols.NARROW; 376 case Calendar.LONG_FORMAT: 377 case Calendar.LONG_STANDALONE: 378 return DateFormatSymbols.WIDE; 379 default: 380 throw new IllegalArgumentException("Invalid style: " + style); 381 } 382 } 383 384 /** 385 * Transform a {@link Calendar} style constant into an ICU context value. 386 */ toContext(int style)387 private static int toContext(int style) { 388 switch (style) { 389 case Calendar.SHORT_FORMAT: 390 case Calendar.NARROW_FORMAT: 391 case Calendar.LONG_FORMAT: 392 return DateFormatSymbols.FORMAT; 393 case Calendar.SHORT_STANDALONE: 394 case Calendar.NARROW_STANDALONE: 395 case Calendar.LONG_STANDALONE: 396 return DateFormatSymbols.STANDALONE; 397 default: 398 throw new IllegalArgumentException("Invalid style: " + style); 399 } 400 } 401 // END Android-added: Various private helper methods. 402 403 // BEGIN Android-removed: Dead code, unused on Android. 404 /* 405 /** 406 * Obtains a localized field value string from a CalendarDataProvider 407 * implementation. 408 * 409 private static class CalendarFieldValueNameGetter 410 implements LocaleServiceProviderPool.LocalizedObjectGetter<CalendarNameProvider, 411 String> { 412 private static final CalendarFieldValueNameGetter INSTANCE = 413 new CalendarFieldValueNameGetter(); 414 415 @Override 416 public String getObject(CalendarNameProvider calendarNameProvider, 417 Locale locale, 418 String requestID, // calendarType 419 Object... params) { 420 assert params.length == 4; 421 int field = (int) params[0]; 422 int value = (int) params[1]; 423 int style = (int) params[2]; 424 boolean javatime = (boolean) params[3]; 425 426 // If javatime is true, resources from CLDR have precedence over JRE 427 // native resources. 428 if (javatime && calendarNameProvider instanceof CalendarNameProviderImpl) { 429 String name; 430 name = ((CalendarNameProviderImpl)calendarNameProvider) 431 .getJavaTimeDisplayName(requestID, field, value, style, locale); 432 return name; 433 } 434 return calendarNameProvider.getDisplayName(requestID, field, value, style, locale); 435 } 436 } 437 438 /** 439 * Obtains a localized field-value pairs from a CalendarDataProvider 440 * implementation. 441 * 442 private static class CalendarFieldValueNamesMapGetter 443 implements LocaleServiceProviderPool.LocalizedObjectGetter<CalendarNameProvider, 444 Map<String, Integer>> { 445 private static final CalendarFieldValueNamesMapGetter INSTANCE = 446 new CalendarFieldValueNamesMapGetter(); 447 448 @Override 449 public Map<String, Integer> getObject(CalendarNameProvider calendarNameProvider, 450 Locale locale, 451 String requestID, // calendarType 452 Object... params) { 453 assert params.length == 3; 454 int field = (int) params[0]; 455 int style = (int) params[1]; 456 boolean javatime = (boolean) params[2]; 457 458 // If javatime is true, resources from CLDR have precedence over JRE 459 // native resources. 460 if (javatime && calendarNameProvider instanceof CalendarNameProviderImpl) { 461 Map<String, Integer> map; 462 map = ((CalendarNameProviderImpl)calendarNameProvider) 463 .getJavaTimeDisplayNames(requestID, field, style, locale); 464 return map; 465 } 466 return calendarNameProvider.getDisplayNames(requestID, field, style, locale); 467 } 468 } 469 470 private static class CalendarWeekParameterGetter 471 implements LocaleServiceProviderPool.LocalizedObjectGetter<CalendarDataProvider, 472 Integer> { 473 private static final CalendarWeekParameterGetter INSTANCE = 474 new CalendarWeekParameterGetter(); 475 476 @Override 477 public Integer getObject(CalendarDataProvider calendarDataProvider, 478 Locale locale, 479 String requestID, // resource key 480 Object... params) { 481 assert params.length == 0; 482 int value; 483 switch (requestID) { 484 case FIRST_DAY_OF_WEEK: 485 value = calendarDataProvider.getFirstDayOfWeek(locale); 486 if (value == 0) { 487 value = MONDAY; // default for the world ("001") 488 } 489 break; 490 case MINIMAL_DAYS_IN_FIRST_WEEK: 491 value = calendarDataProvider.getMinimalDaysInFirstWeek(locale); 492 if (value == 0) { 493 value = 1; // default for the world ("001") 494 } 495 break; 496 default: 497 throw new InternalError("invalid requestID: " + requestID); 498 } 499 500 assert value != 0; 501 return value; 502 } 503 } 504 */ 505 // END Android-removed: Dead code, unused on Android. 506 } 507