< prev index next >

src/java.base/share/classes/sun/util/cldr/CLDRTimeZoneNameProviderImpl.java

Print this page
rev 57986 : 8234347: "Turkey" meta time zone does not generate composed localized names
8236548: Localized time zone name inconsistency between English and other locales
Reviewed-by:
   1 /*
   2  * Copyright (c) 2018, 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.cldr;
  27 
  28 import static sun.util.locale.provider.LocaleProviderAdapter.Type;
  29 
  30 import java.text.MessageFormat;
  31 import java.util.Arrays;
  32 import java.util.Locale;
  33 import java.util.Objects;
  34 import java.util.ResourceBundle;
  35 import java.util.Set;
  36 import java.util.TimeZone;
  37 import java.util.stream.Collectors;
  38 import sun.util.calendar.ZoneInfoFile;
  39 import sun.util.locale.provider.LocaleProviderAdapter;
  40 import sun.util.locale.provider.LocaleResources;
  41 import sun.util.locale.provider.TimeZoneNameProviderImpl;
  42 import sun.util.locale.provider.TimeZoneNameUtility;
  43 
  44 /**
  45  * Concrete implementation of the
  46  * {@link java.util.spi.TimeZoneNameProvider TimeZoneNameProvider} class
  47  * for the CLDR LocaleProviderAdapter.
  48  *
  49  * @author Naoto Sato
  50  */
  51 public class CLDRTimeZoneNameProviderImpl extends TimeZoneNameProviderImpl {
  52 
  53     private static final String NO_INHERITANCE_MARKER = "\u2205\u2205\u2205";
  54     private static class AVAILABLE_IDS {
  55         static final String[] INSTANCE =
  56             Arrays.stream(ZoneInfoFile.getZoneIds())
  57                 .sorted()
  58                 .toArray(String[]::new);
  59     }
  60 
  61     // display name array indexes
  62     private static final int INDEX_TZID         = 0;
  63     private static final int INDEX_STD_LONG     = 1;
  64     private static final int INDEX_STD_SHORT    = 2;
  65     private static final int INDEX_DST_LONG     = 3;
  66     private static final int INDEX_DST_SHORT    = 4;
  67     private static final int INDEX_GEN_LONG     = 5;
  68     private static final int INDEX_GEN_SHORT    = 6;
  69 
  70     public CLDRTimeZoneNameProviderImpl(Type type, Set<String> langtags) {
  71         super(type, langtags);
  72     }
  73 
  74     @Override
  75     protected String[] getDisplayNameArray(String id, Locale locale) {
  76         // Use English for the ROOT locale
  77         locale = locale.equals(Locale.ROOT) ? Locale.ENGLISH : locale;
  78         String[] namesSuper = super.getDisplayNameArray(id, locale);
  79 
  80         if (namesSuper == null) {
  81             // try canonical id instead
  82             namesSuper = super.getDisplayNameArray(
  83                 TimeZoneNameUtility.canonicalTZID(id).orElse(id),
  84                 locale);
  85         }
  86 
  87         if (namesSuper != null) {
  88             // CLDR's resource bundle has an translated entry for this id.
  89             // Fix up names if needed, either missing or no-inheritance
  90             namesSuper[INDEX_TZID] = id;
  91 
  92             for(int i = INDEX_STD_LONG; i < namesSuper.length; i++) { // index 0 is the 'id' itself
  93                 switch (namesSuper[i]) {
  94                 case "":
  95                     // Fill in empty elements
  96                     deriveFallbackName(namesSuper, i, locale,
  97                                        !exists(namesSuper, INDEX_DST_LONG));
  98                     break;
  99                 case NO_INHERITANCE_MARKER:
 100                     // CLDR's "no inheritance marker"
 101                     namesSuper[i] = toGMTFormat(id, i == INDEX_DST_LONG || i == INDEX_DST_SHORT,
 102                                                 i % 2 != 0, locale);
 103                     break;
 104                 default:
 105                     break;
 106                 }
 107             }
 108             return namesSuper;
 109         } else {
 110             // Derive the names for this id. Validate the id first.
 111             if (Arrays.binarySearch(AVAILABLE_IDS.INSTANCE, id) >= 0) {
 112                 String[] names = new String[INDEX_GEN_SHORT + 1];
 113                 names[INDEX_TZID] = id;
 114                 deriveFallbackNames(names, locale);
 115                 return names;
 116             }
 117         }
 118 
 119         return null;
 120     }
 121 
 122     @Override
 123     protected String[][] getZoneStrings(Locale locale) {
 124         // Use English for the ROOT locale
 125         locale = locale.equals(Locale.ROOT) ? Locale.ENGLISH : locale;
 126         String[][] ret = super.getZoneStrings(locale);
 127 
 128         // Fill in for the empty names.
 129         // English names are prefilled for performance.
 130         if (!locale.equals(Locale.ENGLISH) &&
 131             !locale.equals(Locale.US)) {
 132             for (int zoneIndex = 0; zoneIndex < ret.length; zoneIndex++) {
 133                 deriveFallbackNames(ret[zoneIndex], locale);
 134             }
 135         }
 136         return ret;
 137     }
 138 
 139     // Derive fallback time zone name according to LDML's logic
 140     private void deriveFallbackNames(String[] names, Locale locale) {
 141         boolean noDST = !exists(names, INDEX_DST_LONG);

 142         for (int i = INDEX_STD_LONG; i <= INDEX_GEN_SHORT; i++) {
 143             deriveFallbackName(names, i, locale, noDST);
 144         }
 145     }
 146 
 147     private void deriveFallbackName(String[] names, int index, Locale locale, boolean noDST) {
 148         String id = names[INDEX_TZID];
 149 
 150         if (exists(names, index)) {
 151             if (names[index].equals(NO_INHERITANCE_MARKER)) {
 152                 // CLDR's "no inheritance marker"
 153                 names[index] = toGMTFormat(id,
 154                                     index == INDEX_DST_LONG || index == INDEX_DST_SHORT,
 155                                     index % 2 != 0, locale);
 156             }
 157             return;
 158         }
 159 














 160         // Check if COMPAT can substitute the name
 161         if (LocaleProviderAdapter.getAdapterPreference().contains(Type.JRE)) {
 162             String[] compatNames = (String[])LocaleProviderAdapter.forJRE()
 163                 .getLocaleResources(mapChineseLocale(locale))
 164                 .getTimeZoneNames(id);
 165             if (compatNames != null) {
 166                 for (int i = INDEX_STD_LONG; i <= INDEX_GEN_SHORT; i++) {
 167                     // Assumes COMPAT has no empty slots
 168                     if (i == index || !exists(names, i)) {
 169                         names[i] = compatNames[i];
 170                     }
 171                 }
 172                 return;
 173             }
 174         }
 175 
 176         // Type Fallback
 177         if (noDST && typeFallback(names, index)) {
 178             return;
 179         }
 180 
 181         // Region Fallback
 182         if (regionFormatFallback(names, index, locale)) {
 183             return;
 184         }
 185 
 186         // last resort
 187         if (!id.toUpperCase(Locale.ROOT).startsWith("UT")) {
 188             names[index] = toGMTFormat(id,
 189                                        index == INDEX_DST_LONG || index == INDEX_DST_SHORT,
 190                                        index % 2 != 0,
 191                                        locale);
 192             // aliases of "GMT" timezone.
 193             if ((exists(names, INDEX_STD_LONG)) && (id.startsWith("Etc/")
 194                     || id.startsWith("GMT") || id.startsWith("Greenwich"))) {
 195                 switch (id) {
 196                 case "Etc/GMT":
 197                 case "Etc/GMT-0":
 198                 case "Etc/GMT+0":
 199                 case "Etc/GMT0":
 200                 case "GMT+0":
 201                 case "GMT-0":
 202                 case "GMT0":
 203                 case "Greenwich":
 204                     names[INDEX_DST_LONG] = names[INDEX_GEN_LONG] = names[INDEX_STD_LONG];
 205                     break;
 206                 }
 207             }
 208         }
 209     }
 210 
 211     private boolean exists(String[] names, int index) {
 212         return Objects.nonNull(names)
 213                 && Objects.nonNull(names[index])
 214                 && !names[index].isEmpty();
 215     }
 216 
 217     private boolean typeFallback(String[] names, int index) {
 218         // check generic
 219         int genIndex = INDEX_GEN_SHORT - index % 2;
 220         if (!exists(names, index) && exists(names, genIndex)) {
 221             names[index] = names[genIndex];
 222         } else {
 223             // check standard
 224             int stdIndex = INDEX_STD_SHORT - index % 2;
 225             if (!exists(names, index) && exists(names, stdIndex)) {
 226                 names[index] = names[stdIndex];
 227             }
 228         }
 229 
 230         return exists(names, index);
 231     }
 232 
 233     private boolean regionFormatFallback(String[] names, int index, Locale l) {
 234         String id = names[INDEX_TZID];
 235         LocaleResources lr = LocaleProviderAdapter.forType(Type.CLDR).getLocaleResources(l);
 236         ResourceBundle fd = lr.getJavaTimeFormatData();
 237 

 238         String rgn = (String) lr.getTimeZoneNames("timezone.excity." + id);
 239         if (rgn == null && !id.startsWith("Etc") && !id.startsWith("SystemV")) {
 240             int slash = id.lastIndexOf('/');
 241             if (slash > 0) {
 242                 rgn = id.substring(slash + 1).replaceAll("_", " ");
 243             }
 244         }
 245 
 246         if (rgn != null) {
 247             String fmt = "";
 248             switch (index) {
 249             case INDEX_STD_LONG:
 250                 fmt = fd.getString("timezone.regionFormat.standard");
 251                 break;
 252             case INDEX_DST_LONG:
 253                 fmt = fd.getString("timezone.regionFormat.daylight");
 254                 break;
 255             case INDEX_GEN_LONG:
 256                 fmt = fd.getString("timezone.regionFormat");
 257                 break;
 258             }
 259             if (!fmt.isEmpty()) {
 260                 names[index] = MessageFormat.format(fmt, rgn);
 261             }
 262         }
 263 
 264         return exists(names, index);
 265     }
 266 
 267     private String toGMTFormat(String id, boolean daylight, boolean isShort, Locale l) {
 268         TimeZone tz = ZoneInfoFile.getZoneInfo(id);
 269         int offset = (tz.getRawOffset() + (daylight ? tz.getDSTSavings() : 0)) / 60000;
 270         LocaleResources lr = LocaleProviderAdapter.forType(Type.CLDR).getLocaleResources(l);
 271         ResourceBundle fd = lr.getJavaTimeFormatData();
 272 
 273         if (offset == 0) {
 274             return fd.getString("timezone.gmtZeroFormat");
 275         } else {
 276             String gmtFormat = fd.getString("timezone.gmtFormat");
 277             String hourFormat = fd.getString("timezone.hourFormat");
 278 
 279             if (offset > 0) {
 280                 hourFormat = hourFormat.substring(0, hourFormat.indexOf(";"));
 281             } else {
 282                 hourFormat = hourFormat.substring(hourFormat.indexOf(";") + 1);
 283                 offset = -offset;
 284             }
 285             hourFormat = hourFormat
 286                 .replaceFirst("H+", (isShort ? "\\%1\\$d" : "\\%1\\$02d"))
 287                 .replaceFirst("m+", "\\%2\\$02d");
 288             return MessageFormat.format(gmtFormat,
 289                     String.format(l, hourFormat, offset / 60, offset % 60));
 290         }
 291     }
 292 
 293     // Mapping CLDR's Simplified/Traditional Chinese resources
 294     // to COMPAT's zh-CN/TW
 295     private Locale mapChineseLocale(Locale locale) {
 296         if (locale.getLanguage() == "zh") {
 297             switch (locale.getScript()) {
 298                 case "Hans":
 299                     return Locale.CHINA;
 300                 case "Hant":
 301                     return Locale.TAIWAN;
 302                 case "":
 303                     // no script, guess from country code.
 304                     switch (locale.getCountry()) {
 305                         case "":
 306                         case "CN":
   1 /*
   2  * Copyright (c) 2018, 2020, 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.cldr;
  27 
  28 import static sun.util.locale.provider.LocaleProviderAdapter.Type;
  29 
  30 import java.text.MessageFormat;
  31 import java.util.Arrays;
  32 import java.util.Locale;
  33 import java.util.Objects;
  34 import java.util.ResourceBundle;
  35 import java.util.Set;
  36 import java.util.TimeZone;

  37 import sun.util.calendar.ZoneInfoFile;
  38 import sun.util.locale.provider.LocaleProviderAdapter;
  39 import sun.util.locale.provider.LocaleResources;
  40 import sun.util.locale.provider.TimeZoneNameProviderImpl;
  41 import sun.util.locale.provider.TimeZoneNameUtility;
  42 
  43 /**
  44  * Concrete implementation of the
  45  * {@link java.util.spi.TimeZoneNameProvider TimeZoneNameProvider} class
  46  * for the CLDR LocaleProviderAdapter.
  47  *
  48  * @author Naoto Sato
  49  */
  50 public class CLDRTimeZoneNameProviderImpl extends TimeZoneNameProviderImpl {
  51 
  52     private static final String NO_INHERITANCE_MARKER = "\u2205\u2205\u2205";
  53     private static class AVAILABLE_IDS {
  54         static final String[] INSTANCE =
  55             Arrays.stream(ZoneInfoFile.getZoneIds())
  56                 .sorted()
  57                 .toArray(String[]::new);
  58     }
  59 
  60     // display name array indexes
  61     private static final int INDEX_TZID         = 0;
  62     private static final int INDEX_STD_LONG     = 1;
  63     private static final int INDEX_STD_SHORT    = 2;
  64     private static final int INDEX_DST_LONG     = 3;
  65     private static final int INDEX_DST_SHORT    = 4;
  66     private static final int INDEX_GEN_LONG     = 5;
  67     private static final int INDEX_GEN_SHORT    = 6;
  68 
  69     public CLDRTimeZoneNameProviderImpl(Type type, Set<String> langtags) {
  70         super(type, langtags);
  71     }
  72 
  73     @Override
  74     protected String[] getDisplayNameArray(String id, Locale locale) {


  75         String[] namesSuper = super.getDisplayNameArray(id, locale);
  76 
  77         if (namesSuper == null) {
  78             // try canonical id instead
  79             namesSuper = super.getDisplayNameArray(
  80                 TimeZoneNameUtility.canonicalTZID(id).orElse(id),
  81                 locale);
  82         }
  83 
  84         if (namesSuper != null) {
  85             // CLDR's resource bundle has an translated entry for this id.
  86             // Fix up names if needed, either missing or no-inheritance
  87             namesSuper[INDEX_TZID] = id;
  88 
  89             for(int i = INDEX_STD_LONG; i < namesSuper.length; i++) { // index 0 is the 'id' itself
  90                 switch (namesSuper[i]) {
  91                 case "":
  92                     // Fill in empty elements
  93                     deriveFallbackName(namesSuper, i, locale,
  94                                        !TimeZone.getTimeZone(id).useDaylightTime());
  95                     break;
  96                 case NO_INHERITANCE_MARKER:
  97                     // CLDR's "no inheritance marker"
  98                     namesSuper[i] = toGMTFormat(id, i == INDEX_DST_LONG || i == INDEX_DST_SHORT,
  99                                                 locale);
 100                     break;
 101                 default:
 102                     break;
 103                 }
 104             }
 105             return namesSuper;
 106         } else {
 107             // Derive the names for this id. Validate the id first.
 108             if (Arrays.binarySearch(AVAILABLE_IDS.INSTANCE, id) >= 0) {
 109                 String[] names = new String[INDEX_GEN_SHORT + 1];
 110                 names[INDEX_TZID] = id;
 111                 deriveFallbackNames(names, locale);
 112                 return names;
 113             }
 114         }
 115 
 116         return null;
 117     }
 118 
 119     @Override
 120     protected String[][] getZoneStrings(Locale locale) {


 121         String[][] ret = super.getZoneStrings(locale);
 122 
 123         // Fill in for the empty names.



 124         for (int zoneIndex = 0; zoneIndex < ret.length; zoneIndex++) {
 125             deriveFallbackNames(ret[zoneIndex], locale);
 126         }

 127         return ret;
 128     }
 129 
 130     // Derive fallback time zone name according to LDML's logic
 131     private void deriveFallbackNames(String[] names, Locale locale) {
 132         boolean noDST = !TimeZone.getTimeZone(names[0]).useDaylightTime();
 133 
 134         for (int i = INDEX_STD_LONG; i <= INDEX_GEN_SHORT; i++) {
 135             deriveFallbackName(names, i, locale, noDST);
 136         }
 137     }
 138 
 139     private void deriveFallbackName(String[] names, int index, Locale locale, boolean noDST) {
 140         String id = names[INDEX_TZID];
 141 
 142         if (exists(names, index)) {
 143             if (names[index].equals(NO_INHERITANCE_MARKER)) {
 144                 // CLDR's "no inheritance marker"
 145                 names[index] = toGMTFormat(id,
 146                                     index == INDEX_DST_LONG || index == INDEX_DST_SHORT,
 147                                     locale);
 148             }
 149             return;
 150         }
 151 
 152         // Check parent locale first
 153         if (!exists(names, index)) {
 154             CLDRLocaleProviderAdapter clpa = (CLDRLocaleProviderAdapter)LocaleProviderAdapter.forType(Type.CLDR);
 155             var cands = clpa.getCandidateLocales("", locale);
 156             if (cands.size() > 1) {
 157                 var parentLoc = cands.get(1); // immediate parent locale
 158                 String[] parentNames = super.getDisplayNameArray(id, parentLoc);
 159                 if (parentNames != null && !parentNames[index].isEmpty()) {
 160                     names[index] = parentNames[index];
 161                     return;
 162                 }
 163             }
 164         }
 165 
 166         // Check if COMPAT can substitute the name
 167         if (LocaleProviderAdapter.getAdapterPreference().contains(Type.JRE)) {
 168             String[] compatNames = (String[])LocaleProviderAdapter.forJRE()
 169                 .getLocaleResources(mapChineseLocale(locale))
 170                 .getTimeZoneNames(id);
 171             if (compatNames != null) {
 172                 for (int i = INDEX_STD_LONG; i <= INDEX_GEN_SHORT; i++) {
 173                     // Assumes COMPAT has no empty slots
 174                     if (i == index || !exists(names, i)) {
 175                         names[i] = compatNames[i];
 176                     }
 177                 }
 178                 return;
 179             }
 180         }
 181 
 182         // Region Fallback
 183         if (regionFormatFallback(names, index, locale)) {
 184             return;
 185         }
 186 
 187         // Type Fallback
 188         if (noDST && typeFallback(names, index)) {
 189             return;
 190         }
 191 
 192         // last resort

 193         names[index] = toGMTFormat(id,
 194                                    index == INDEX_DST_LONG || index == INDEX_DST_SHORT,

 195                                    locale);
 196         // aliases of "GMT" timezone.
 197         if ((exists(names, INDEX_STD_LONG)) && (id.startsWith("Etc/")
 198                 || id.startsWith("GMT") || id.startsWith("Greenwich"))) {
 199             switch (id) {
 200             case "Etc/GMT":
 201             case "Etc/GMT-0":
 202             case "Etc/GMT+0":
 203             case "Etc/GMT0":
 204             case "GMT+0":
 205             case "GMT-0":
 206             case "GMT0":
 207             case "Greenwich":
 208                 names[INDEX_DST_LONG] = names[INDEX_GEN_LONG] = names[INDEX_STD_LONG];
 209                 break;
 210             }
 211         }
 212     }

 213 
 214     private boolean exists(String[] names, int index) {
 215         return Objects.nonNull(names)
 216                 && Objects.nonNull(names[index])
 217                 && !names[index].isEmpty();
 218     }
 219 
 220     private boolean typeFallback(String[] names, int index) {
 221         // check generic
 222         int genIndex = INDEX_GEN_SHORT - index % 2;
 223         if (!exists(names, index) && exists(names, genIndex) && !names[genIndex].startsWith("GMT")) {
 224             names[index] = names[genIndex];
 225         } else {
 226             // check standard
 227             int stdIndex = INDEX_STD_SHORT - index % 2;
 228             if (!exists(names, index) && exists(names, stdIndex) && !names[stdIndex].startsWith("GMT")) {
 229                 names[index] = names[stdIndex];
 230             }
 231         }
 232 
 233         return exists(names, index);
 234     }
 235 
 236     private boolean regionFormatFallback(String[] names, int index, Locale l) {
 237         String id = names[INDEX_TZID];
 238         LocaleResources lr = LocaleProviderAdapter.forType(Type.CLDR).getLocaleResources(l);
 239         ResourceBundle fd = lr.getJavaTimeFormatData();
 240 
 241         id = TimeZoneNameUtility.canonicalTZID(id).orElse(id);
 242         String rgn = (String) lr.getTimeZoneNames("timezone.excity." + id);
 243         if (rgn == null && !id.startsWith("Etc") && !id.startsWith("SystemV")) {
 244             int slash = id.lastIndexOf('/');
 245             if (slash > 0) {
 246                 rgn = id.substring(slash + 1).replaceAll("_", " ");
 247             }
 248         }
 249 
 250         if (rgn != null) {
 251             String fmt = "";
 252             switch (index) {
 253             case INDEX_STD_LONG:
 254                 fmt = fd.getString("timezone.regionFormat.standard");
 255                 break;
 256             case INDEX_DST_LONG:
 257                 fmt = fd.getString("timezone.regionFormat.daylight");
 258                 break;
 259             case INDEX_GEN_LONG:
 260                 fmt = fd.getString("timezone.regionFormat");
 261                 break;
 262             }
 263             if (!fmt.isEmpty()) {
 264                 names[index] = MessageFormat.format(fmt, rgn);
 265             }
 266         }
 267 
 268         return exists(names, index);
 269     }
 270 
 271     private String toGMTFormat(String id, boolean daylight, Locale l) {
 272         TimeZone tz = ZoneInfoFile.getZoneInfo(id);
 273         int offset = (tz.getRawOffset() + (daylight ? tz.getDSTSavings() : 0)) / 60000;
 274         LocaleResources lr = LocaleProviderAdapter.forType(Type.CLDR).getLocaleResources(l);
 275         ResourceBundle fd = lr.getJavaTimeFormatData();
 276 
 277         if (offset == 0) {
 278             return fd.getString("timezone.gmtZeroFormat");
 279         } else {
 280             String gmtFormat = fd.getString("timezone.gmtFormat");
 281             String hourFormat = fd.getString("timezone.hourFormat");
 282 
 283             if (offset > 0) {
 284                 hourFormat = hourFormat.substring(0, hourFormat.indexOf(";"));
 285             } else {
 286                 hourFormat = hourFormat.substring(hourFormat.indexOf(";") + 1);
 287                 offset = -offset;
 288             }
 289             hourFormat = hourFormat
 290                 .replaceFirst("H+", "\\%1\\$02d")
 291                 .replaceFirst("m+", "\\%2\\$02d");
 292             return MessageFormat.format(gmtFormat,
 293                     String.format(l, hourFormat, offset / 60, offset % 60));
 294         }
 295     }
 296 
 297     // Mapping CLDR's Simplified/Traditional Chinese resources
 298     // to COMPAT's zh-CN/TW
 299     private Locale mapChineseLocale(Locale locale) {
 300         if (locale.getLanguage() == "zh") {
 301             switch (locale.getScript()) {
 302                 case "Hans":
 303                     return Locale.CHINA;
 304                 case "Hant":
 305                     return Locale.TAIWAN;
 306                 case "":
 307                     // no script, guess from country code.
 308                     switch (locale.getCountry()) {
 309                         case "":
 310                         case "CN":
< prev index next >