Path: blob/master/src/java.base/share/classes/sun/util/cldr/CLDRTimeZoneNameProviderImpl.java
41159 views
/*1* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.util.cldr;2627import static sun.util.locale.provider.LocaleProviderAdapter.Type;2829import java.text.MessageFormat;30import java.util.Arrays;31import java.util.Locale;32import java.util.Objects;33import java.util.ResourceBundle;34import java.util.Set;35import java.util.TimeZone;36import sun.util.calendar.ZoneInfoFile;37import sun.util.locale.provider.LocaleProviderAdapter;38import sun.util.locale.provider.LocaleResources;39import sun.util.locale.provider.TimeZoneNameProviderImpl;40import sun.util.locale.provider.TimeZoneNameUtility;4142/**43* Concrete implementation of the44* {@link java.util.spi.TimeZoneNameProvider TimeZoneNameProvider} class45* for the CLDR LocaleProviderAdapter.46*47* @author Naoto Sato48*/49public class CLDRTimeZoneNameProviderImpl extends TimeZoneNameProviderImpl {5051private static final String NO_INHERITANCE_MARKER = "\u2205\u2205\u2205";52private static class AVAILABLE_IDS {53static final String[] INSTANCE =54Arrays.stream(ZoneInfoFile.getZoneIds())55.sorted()56.toArray(String[]::new);57}5859// display name array indexes60private static final int INDEX_TZID = 0;61private static final int INDEX_STD_LONG = 1;62private static final int INDEX_STD_SHORT = 2;63private static final int INDEX_DST_LONG = 3;64private static final int INDEX_DST_SHORT = 4;65private static final int INDEX_GEN_LONG = 5;66private static final int INDEX_GEN_SHORT = 6;6768public CLDRTimeZoneNameProviderImpl(Type type, Set<String> langtags) {69super(type, langtags);70}7172@Override73protected String[] getDisplayNameArray(String id, Locale locale) {74String[] namesSuper = super.getDisplayNameArray(id, locale);7576if (namesSuper == null) {77// try canonical id instead78namesSuper = super.getDisplayNameArray(79TimeZoneNameUtility.canonicalTZID(id).orElse(id),80locale);81}8283if (namesSuper != null) {84// CLDR's resource bundle has an translated entry for this id.85// Fix up names if needed, either missing or no-inheritance86namesSuper[INDEX_TZID] = id;8788for(int i = INDEX_STD_LONG; i < namesSuper.length; i++) { // index 0 is the 'id' itself89switch (namesSuper[i]) {90case "":91// Fill in empty elements92deriveFallbackName(namesSuper, i, locale,93!TimeZone.getTimeZone(id).useDaylightTime());94break;95case NO_INHERITANCE_MARKER:96// CLDR's "no inheritance marker"97namesSuper[i] = toGMTFormat(id, i == INDEX_DST_LONG || i == INDEX_DST_SHORT,98locale);99break;100default:101break;102}103}104return namesSuper;105} else {106// Derive the names for this id. Validate the id first.107if (Arrays.binarySearch(AVAILABLE_IDS.INSTANCE, id) >= 0) {108String[] names = new String[INDEX_GEN_SHORT + 1];109names[INDEX_TZID] = id;110deriveFallbackNames(names, locale);111return names;112}113}114115return null;116}117118@Override119protected String[][] getZoneStrings(Locale locale) {120String[][] ret = super.getZoneStrings(locale);121122// Fill in for the empty names.123for (int zoneIndex = 0; zoneIndex < ret.length; zoneIndex++) {124deriveFallbackNames(ret[zoneIndex], locale);125}126return ret;127}128129// Derive fallback time zone name according to LDML's logic130private void deriveFallbackNames(String[] names, Locale locale) {131boolean noDST = !TimeZone.getTimeZone(names[0]).useDaylightTime();132133for (int i = INDEX_STD_LONG; i <= INDEX_GEN_SHORT; i++) {134deriveFallbackName(names, i, locale, noDST);135}136}137138private void deriveFallbackName(String[] names, int index, Locale locale, boolean noDST) {139String id = names[INDEX_TZID];140141if (exists(names, index)) {142if (names[index].equals(NO_INHERITANCE_MARKER)) {143// CLDR's "no inheritance marker"144names[index] = toGMTFormat(id,145index == INDEX_DST_LONG || index == INDEX_DST_SHORT,146locale);147}148return;149}150151// Check parent locale first152if (!exists(names, index)) {153CLDRLocaleProviderAdapter clpa = (CLDRLocaleProviderAdapter)LocaleProviderAdapter.forType(Type.CLDR);154var cands = clpa.getCandidateLocales("", locale);155if (cands.size() > 1) {156var parentLoc = cands.get(1); // immediate parent locale157String[] parentNames = super.getDisplayNameArray(id, parentLoc);158if (parentNames != null && !parentNames[index].isEmpty()) {159names[index] = parentNames[index];160return;161}162}163}164165// Check if COMPAT can substitute the name166if (LocaleProviderAdapter.getAdapterPreference().contains(Type.JRE)) {167String[] compatNames = (String[])LocaleProviderAdapter.forJRE()168.getLocaleResources(mapChineseLocale(locale))169.getTimeZoneNames(id);170if (compatNames != null) {171for (int i = INDEX_STD_LONG; i <= INDEX_GEN_SHORT; i++) {172// Assumes COMPAT has no empty slots173if (i == index || !exists(names, i)) {174names[i] = compatNames[i];175}176}177return;178}179}180181// Region Fallback182if (regionFormatFallback(names, index, locale)) {183return;184}185186// Type Fallback187if (noDST && typeFallback(names, index)) {188return;189}190191// last resort192names[index] = toGMTFormat(id,193index == INDEX_DST_LONG || index == INDEX_DST_SHORT,194locale);195// aliases of "GMT" timezone.196if ((exists(names, INDEX_STD_LONG)) && (id.startsWith("Etc/")197|| id.startsWith("GMT") || id.startsWith("Greenwich"))) {198switch (id) {199case "Etc/GMT":200case "Etc/GMT-0":201case "Etc/GMT+0":202case "Etc/GMT0":203case "GMT+0":204case "GMT-0":205case "GMT0":206case "Greenwich":207names[INDEX_DST_LONG] = names[INDEX_GEN_LONG] = names[INDEX_STD_LONG];208break;209}210}211}212213private boolean exists(String[] names, int index) {214return Objects.nonNull(names)215&& Objects.nonNull(names[index])216&& !names[index].isEmpty();217}218219private boolean typeFallback(String[] names, int index) {220// check generic221int genIndex = INDEX_GEN_SHORT - index % 2;222if (!exists(names, index) && exists(names, genIndex) && !names[genIndex].startsWith("GMT")) {223names[index] = names[genIndex];224} else {225// check standard226int stdIndex = INDEX_STD_SHORT - index % 2;227if (!exists(names, index) && exists(names, stdIndex) && !names[stdIndex].startsWith("GMT")) {228names[index] = names[stdIndex];229}230}231232return exists(names, index);233}234235private boolean regionFormatFallback(String[] names, int index, Locale l) {236String id = names[INDEX_TZID];237LocaleResources lr = LocaleProviderAdapter.forType(Type.CLDR).getLocaleResources(l);238ResourceBundle fd = lr.getJavaTimeFormatData();239240id = TimeZoneNameUtility.canonicalTZID(id).orElse(id);241String rgn = (String) lr.getTimeZoneNames("timezone.excity." + id);242if (rgn == null && !id.startsWith("Etc") && !id.startsWith("SystemV")) {243int slash = id.lastIndexOf('/');244if (slash > 0) {245rgn = id.substring(slash + 1).replaceAll("_", " ");246}247}248249if (rgn != null) {250String fmt = "";251switch (index) {252case INDEX_STD_LONG:253fmt = fd.getString("timezone.regionFormat.standard");254break;255case INDEX_DST_LONG:256fmt = fd.getString("timezone.regionFormat.daylight");257break;258case INDEX_GEN_LONG:259fmt = fd.getString("timezone.regionFormat");260break;261}262if (!fmt.isEmpty()) {263names[index] = MessageFormat.format(fmt, rgn);264}265}266267return exists(names, index);268}269270private String toGMTFormat(String id, boolean daylight, Locale l) {271TimeZone tz = ZoneInfoFile.getZoneInfo(id);272int offset = (tz.getRawOffset() + (daylight ? tz.getDSTSavings() : 0)) / 60000;273LocaleResources lr = LocaleProviderAdapter.forType(Type.CLDR).getLocaleResources(l);274ResourceBundle fd = lr.getJavaTimeFormatData();275276if (offset == 0) {277return fd.getString("timezone.gmtZeroFormat");278} else {279String gmtFormat = fd.getString("timezone.gmtFormat");280String hourFormat = fd.getString("timezone.hourFormat");281282if (offset > 0) {283hourFormat = hourFormat.substring(0, hourFormat.indexOf(";"));284} else {285hourFormat = hourFormat.substring(hourFormat.indexOf(";") + 1);286offset = -offset;287}288hourFormat = hourFormat289.replaceFirst("H+", "\\%1\\$02d")290.replaceFirst("m+", "\\%2\\$02d");291return MessageFormat.format(gmtFormat,292String.format(l, hourFormat, offset / 60, offset % 60));293}294}295296// Mapping CLDR's Simplified/Traditional Chinese resources297// to COMPAT's zh-CN/TW298private Locale mapChineseLocale(Locale locale) {299if (locale.getLanguage() == "zh") {300switch (locale.getScript()) {301case "Hans":302return Locale.CHINA;303case "Hant":304return Locale.TAIWAN;305case "":306// no script, guess from country code.307switch (locale.getCountry()) {308case "":309case "CN":310case "SG":311return Locale.CHINA;312case "HK":313case "MO":314case "TW":315return Locale.TAIWAN;316}317break;318}319}320321// no need to map322return locale;323}324}325326327