/* * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.util.locale.provider; import android.icu.text.DateFormatSymbols; import android.icu.util.ULocale; import static java.util.Calendar.*; import libcore.util.NonNull; import java.util.Calendar; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; // Android-changed: remove mention of CalendarDataProvider that's not used on Android. /** * {@code CalendarDataUtility} is a utility class for getting calendar field name values. * * @author Masayoshi Okutsu * @author Naoto Sato */ public class CalendarDataUtility { // Android-note: This class has been rewritten from scratch and is effectively forked. // The API (names of public constants, method signatures etc.) generally derives // from OpenJDK so that other OpenJDK code that refers to this class doesn't need to // be changed, but the implementation has been rewritten; logic / identifiers // that weren't used from anywhere else have been dropped altogether. // Android-removed: Dead code, unused on Android. // public static final String FIRST_DAY_OF_WEEK = "firstDayOfWeek"; // public static final String MINIMAL_DAYS_IN_FIRST_WEEK = "minimalDaysInFirstWeek"; // Android-added: Calendar name constants for use in retrievFieldValueName. private static final String ISLAMIC_CALENDAR = "islamic"; private static final String GREGORIAN_CALENDAR = "gregorian"; private static final String BUDDHIST_CALENDAR = "buddhist"; private static final String JAPANESE_CALENDAR = "japanese"; // Android-added: REST_OF_STYLES array for use in retrieveFieldValueNames. // ALL_STYLES implies SHORT_FORMAT and all of these values. private static int[] REST_OF_STYLES = { SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE, NARROW_FORMAT, NARROW_STANDALONE }; // No instantiation private CalendarDataUtility() { } // Android-changed: Modify retrieveFirstDayOfWeek() to provide the default value. // public static int retrieveFirstDayOfWeek(Locale locale) { /** * @param defaultFirstDayOfWeek default first day of week if the {@code locale} doesn't have the * "fw" extension. This default can be obtained from the locale via ICU4J * {@code android.icu.util.Calendar}. * @return */ public static int retrieveFirstDayOfWeek(Locale locale, int defaultFirstDayOfWeek) { // Look for the Unicode Extension in the locale parameter if (locale.hasExtensions()) { String fw = locale.getUnicodeLocaleType("fw"); if (fw != null) { switch (fw.toLowerCase(Locale.ROOT)) { case "mon": return MONDAY; case "tue": return TUESDAY; case "wed": return WEDNESDAY; case "thu": return THURSDAY; case "fri": return FRIDAY; case "sat": return SATURDAY; case "sun": return SUNDAY; } } } // Android-changed: Modify retrieveFirstDayOfWeek() to use the default first day of week. /* LocaleServiceProviderPool pool = LocaleServiceProviderPool.getPool(CalendarDataProvider.class); Integer value = pool.getLocalizedObject(CalendarWeekParameterGetter.INSTANCE, findRegionOverride(locale), true, FIRST_DAY_OF_WEEK); return (value != null && (value >= SUNDAY && value <= SATURDAY)) ? value : SUNDAY; */ return defaultFirstDayOfWeek; } // BEGIN Android-removed: Dead code, unused on Android. /* public static int retrieveMinimalDaysInFirstWeek(Locale locale) { LocaleServiceProviderPool pool = LocaleServiceProviderPool.getPool(CalendarDataProvider.class); Integer value = pool.getLocalizedObject(CalendarWeekParameterGetter.INSTANCE, findRegionOverride(locale), true, MINIMAL_DAYS_IN_FIRST_WEEK); return (value != null && (value >= 1 && value <= 7)) ? value : 1; } */ // END Android-removed: Dead code, unused on Android. // BEGIN Android-changed: Implement on top of ICU. /* public static String retrieveFieldValueName(String id, int field, int value, int style, Locale locale) { LocaleServiceProviderPool pool = LocaleServiceProviderPool.getPool(CalendarNameProvider.class); return pool.getLocalizedObject(CalendarFieldValueNameGetter.INSTANCE, locale, normalizeCalendarType(id), field, value, style, false); } */ public static String retrieveFieldValueName(String id, int field, int value, int style, Locale locale) { if (field == Calendar.ERA) { // For era the field value does not always equal the index into the names array. switch (normalizeCalendarType(id)) { // These calendars have only one era, but represented it by the value 1. case BUDDHIST_CALENDAR: case ISLAMIC_CALENDAR: value -= 1; break; case JAPANESE_CALENDAR: // CLDR contains full data for historical eras, java.time only supports the 4 // modern eras and numbers the modern eras starting with 1 (MEIJI). There are // 232 historical eras in CLDR/ICU so to get the real offset, we add 231. value += 231; break; default: // Other eras use 0-based values (e.g. 0=BCE, 1=CE for gregorian). break; } } if (value < 0) { return null; } String[] names = getNames(id, field, style, locale); if (value >= names.length) { return null; } return names[value]; } // END Android-changed: Implement on top of ICU. // BEGIN Android-changed: Implement on top of ICU. /* public static String retrieveJavaTimeFieldValueName(String id, int field, int value, int style, Locale locale) { LocaleServiceProviderPool pool = LocaleServiceProviderPool.getPool(CalendarNameProvider.class); String name; name = pool.getLocalizedObject(CalendarFieldValueNameGetter.INSTANCE, locale, normalizeCalendarType(id), field, value, style, true); if (name == null) { name = pool.getLocalizedObject(CalendarFieldValueNameGetter.INSTANCE, locale, normalizeCalendarType(id), field, value, style, false); } return name; } */ public static String retrieveJavaTimeFieldValueName(String id, int field, int value, int style, Locale locale) { // Don't distinguish between retrieve* and retrieveJavaTime* methods. return retrieveFieldValueName(id, field, value, style, locale); } // END Android-changed: Implement on top of ICU. // BEGIN Android-changed: Implement on top of ICU. /* public static Map retrieveFieldValueNames(String id, int field, int style, Locale locale) { LocaleServiceProviderPool pool = LocaleServiceProviderPool.getPool(CalendarNameProvider.class); return pool.getLocalizedObject(CalendarFieldValueNamesMapGetter.INSTANCE, locale, normalizeCalendarType(id), field, style, false); } */ public static Map retrieveFieldValueNames(String id, int field, int style, Locale locale) { Map names; if (style == ALL_STYLES) { names = retrieveFieldValueNamesImpl(id, field, SHORT_FORMAT, locale); for (int st : REST_OF_STYLES) { names.putAll(retrieveFieldValueNamesImpl(id, field, st, locale)); } } else { // specific style names = retrieveFieldValueNamesImpl(id, field, style, locale); } return names.isEmpty() ? null : names; } // END Android-changed: Implement on top of ICU. // BEGIN Android-changed: Implement on top of ICU. /* public static Map retrieveJavaTimeFieldValueNames(String id, int field, int style, Locale locale) { LocaleServiceProviderPool pool = LocaleServiceProviderPool.getPool(CalendarNameProvider.class); Map map; map = pool.getLocalizedObject(CalendarFieldValueNamesMapGetter.INSTANCE, locale, normalizeCalendarType(id), field, style, true); if (map == null) { map = pool.getLocalizedObject(CalendarFieldValueNamesMapGetter.INSTANCE, locale, normalizeCalendarType(id), field, style, false); } return map; } */ public static Map retrieveJavaTimeFieldValueNames(String id, int field, int style, Locale locale) { // Don't distinguish between retrieve* and retrieveJavaTime* methods. return retrieveFieldValueNames(id, field, style, locale); } // END Android-changed: Implement on top of ICU. /** * Utility to look for a region override extension. * If no region override is found, returns the original locale. */ // BEGIN Android-removed: Dead code, unused on Android. /* public static Locale findRegionOverride(Locale l) { String rg = l.getUnicodeLocaleType("rg"); Locale override = l; if (rg != null && rg.length() == 6) { // UN M.49 code should not be allowed here // cannot use regex here, as it could be a recursive call rg = rg.toUpperCase(Locale.ROOT); if (rg.charAt(0) >= 0x0041 && rg.charAt(0) <= 0x005A && rg.charAt(1) >= 0x0041 && rg.charAt(1) <= 0x005A && rg.substring(2).equals("ZZZZ")) { override = new Locale.Builder().setLocale(l) .setRegion(rg.substring(0, 2)) .build(); } } return override; } */ // END Android-removed: Dead code, unused on Android. // Android-changed: Added private modifier for normalizeCalendarType(). // static String normalizeCalendarType(String requestID) { private static String normalizeCalendarType(String requestID) { String type; // Android-changed: normalize "gregory" to "gregorian", not the other way around. // Android maps BCP-47 calendar types to LDML defined calendar types, because it uses // ICU directly while the upstream does the opposite because the upstream uses different // data sources. See android.icu.text.DateFormatSymbols.CALENDAR_CLASSES for reference. // if (requestID.equals("gregorian") || requestID.equals("iso8601")) { // type = "gregory"; // } else if (requestID.startsWith("islamic")) { // type = "islamic"; if (requestID.equals("gregory") || requestID.equals("iso8601")) { type = GREGORIAN_CALENDAR; } else if (requestID.startsWith(ISLAMIC_CALENDAR)) { type = ISLAMIC_CALENDAR; } else { type = requestID; } return type; } // BEGIN Android-added: Various private helper methods. private static Map retrieveFieldValueNamesImpl(String id, int field, int style, Locale locale) { String[] names = getNames(id, field, style, locale); int skipped = 0; int offset = 0; if (field == Calendar.ERA) { // See retrieveFieldValueName() for explanation of this code and the values used. switch (normalizeCalendarType(id)) { case BUDDHIST_CALENDAR: case ISLAMIC_CALENDAR: offset = 1; break; case JAPANESE_CALENDAR: skipped = 232; offset = -231; break; default: break; } } Map result = new LinkedHashMap<>(); for (int i = skipped; i < names.length; i++) { if (names[i].isEmpty()) { continue; } if (result.put(names[i], i + offset) != null) { // Duplicate names indicate that the names would be ambiguous. Skip this style for // ALL_STYLES. In other cases this results in null being returned in // retrieveValueNames(), which is required by Calendar.getDisplayNames(). return new LinkedHashMap<>(); } } return result; } private static String[] getNames(String id, int field, int style, Locale locale) { int context = toContext(style); int width = toWidth(style); DateFormatSymbols symbols = getDateFormatSymbols(id, locale); switch (field) { case Calendar.MONTH: return symbols.getMonths(context, width); case Calendar.ERA: switch (width) { case DateFormatSymbols.NARROW: return symbols.getNarrowEras(); case DateFormatSymbols.ABBREVIATED: return symbols.getEras(); case DateFormatSymbols.WIDE: return symbols.getEraNames(); default: throw new UnsupportedOperationException("Unknown width: " + width); } case Calendar.DAY_OF_WEEK: return symbols.getWeekdays(context, width); case Calendar.AM_PM: return symbols.getAmPmStrings(); default: throw new UnsupportedOperationException("Unknown field: " + field); } } private static DateFormatSymbols getDateFormatSymbols(@NonNull String id, Locale locale) { String calendarType = normalizeCalendarType(id); ULocale uLocale = ULocale.forLocale(locale) .setKeywordValue("calendar", calendarType); return new DateFormatSymbols(uLocale); } /** * Transform a {@link Calendar} style constant into an ICU width value. */ private static int toWidth(int style) { switch (style) { case Calendar.SHORT_FORMAT: case Calendar.SHORT_STANDALONE: return DateFormatSymbols.ABBREVIATED; case Calendar.NARROW_FORMAT: case Calendar.NARROW_STANDALONE: return DateFormatSymbols.NARROW; case Calendar.LONG_FORMAT: case Calendar.LONG_STANDALONE: return DateFormatSymbols.WIDE; default: throw new IllegalArgumentException("Invalid style: " + style); } } /** * Transform a {@link Calendar} style constant into an ICU context value. */ private static int toContext(int style) { switch (style) { case Calendar.SHORT_FORMAT: case Calendar.NARROW_FORMAT: case Calendar.LONG_FORMAT: return DateFormatSymbols.FORMAT; case Calendar.SHORT_STANDALONE: case Calendar.NARROW_STANDALONE: case Calendar.LONG_STANDALONE: return DateFormatSymbols.STANDALONE; default: throw new IllegalArgumentException("Invalid style: " + style); } } // END Android-added: Various private helper methods. // BEGIN Android-removed: Dead code, unused on Android. /* /** * Obtains a localized field value string from a CalendarDataProvider * implementation. * private static class CalendarFieldValueNameGetter implements LocaleServiceProviderPool.LocalizedObjectGetter { private static final CalendarFieldValueNameGetter INSTANCE = new CalendarFieldValueNameGetter(); @Override public String getObject(CalendarNameProvider calendarNameProvider, Locale locale, String requestID, // calendarType Object... params) { assert params.length == 4; int field = (int) params[0]; int value = (int) params[1]; int style = (int) params[2]; boolean javatime = (boolean) params[3]; // If javatime is true, resources from CLDR have precedence over JRE // native resources. if (javatime && calendarNameProvider instanceof CalendarNameProviderImpl) { String name; name = ((CalendarNameProviderImpl)calendarNameProvider) .getJavaTimeDisplayName(requestID, field, value, style, locale); return name; } return calendarNameProvider.getDisplayName(requestID, field, value, style, locale); } } /** * Obtains a localized field-value pairs from a CalendarDataProvider * implementation. * private static class CalendarFieldValueNamesMapGetter implements LocaleServiceProviderPool.LocalizedObjectGetter> { private static final CalendarFieldValueNamesMapGetter INSTANCE = new CalendarFieldValueNamesMapGetter(); @Override public Map getObject(CalendarNameProvider calendarNameProvider, Locale locale, String requestID, // calendarType Object... params) { assert params.length == 3; int field = (int) params[0]; int style = (int) params[1]; boolean javatime = (boolean) params[2]; // If javatime is true, resources from CLDR have precedence over JRE // native resources. if (javatime && calendarNameProvider instanceof CalendarNameProviderImpl) { Map map; map = ((CalendarNameProviderImpl)calendarNameProvider) .getJavaTimeDisplayNames(requestID, field, style, locale); return map; } return calendarNameProvider.getDisplayNames(requestID, field, style, locale); } } private static class CalendarWeekParameterGetter implements LocaleServiceProviderPool.LocalizedObjectGetter { private static final CalendarWeekParameterGetter INSTANCE = new CalendarWeekParameterGetter(); @Override public Integer getObject(CalendarDataProvider calendarDataProvider, Locale locale, String requestID, // resource key Object... params) { assert params.length == 0; int value; switch (requestID) { case FIRST_DAY_OF_WEEK: value = calendarDataProvider.getFirstDayOfWeek(locale); if (value == 0) { value = MONDAY; // default for the world ("001") } break; case MINIMAL_DAYS_IN_FIRST_WEEK: value = calendarDataProvider.getMinimalDaysInFirstWeek(locale); if (value == 0) { value = 1; // default for the world ("001") } break; default: throw new InternalError("invalid requestID: " + requestID); } assert value != 0; return value; } } */ // END Android-removed: Dead code, unused on Android. }