1 package org.unicode.cldr.tool; 2 3 import java.util.Arrays; 4 import java.util.Collections; 5 import java.util.HashMap; 6 import java.util.LinkedHashSet; 7 import java.util.Map; 8 import java.util.Map.Entry; 9 import java.util.Set; 10 import java.util.TreeMap; 11 import java.util.TreeSet; 12 13 import org.unicode.cldr.util.Builder; 14 import org.unicode.cldr.util.CLDRConfig; 15 import org.unicode.cldr.util.CLDRFile; 16 import org.unicode.cldr.util.Containment; 17 import org.unicode.cldr.util.DateTimeCanonicalizer.DateTimePatternType; 18 import org.unicode.cldr.util.Factory; 19 import org.unicode.cldr.util.LanguageTagParser; 20 import org.unicode.cldr.util.PreferredAndAllowedHour; 21 import org.unicode.cldr.util.SupplementalDataInfo.OfficialStatus; 22 import org.unicode.cldr.util.SupplementalDataInfo.PopulationData; 23 import org.unicode.cldr.util.With; 24 25 import com.google.common.base.Joiner; 26 import com.ibm.icu.impl.Relation; 27 import com.ibm.icu.text.DateTimePatternGenerator.FormatParser; 28 import com.ibm.icu.text.DateTimePatternGenerator.VariableField; 29 import com.ibm.icu.text.UnicodeSet; 30 31 public class FindPreferredHours { 32 private static CLDRConfig INFO = ToolConfig.getToolInstance(); 33 private static final CLDRFile ENGLISH = INFO.getEnglish(); 34 private static final UnicodeSet DIGITS = new UnicodeSet("[0-9]").freeze(); 35 36 private static final Set<Character> ONLY24 = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays 37 .asList('H'))); 38 39 private final static Map<String, Set<Character>> OVERRIDE_ALLOWED = Builder 40 .with(new HashMap<String, Set<Character>>()) 41 .put("RU", ONLY24) 42 .put("IL", ONLY24) 43 .freeze(); 44 45 private final static Map<String, Character> CONFLICT_RESOLUTION = Builder.with(new HashMap<String, Character>()) 46 .put("DJ", 'h') 47 .put("KM", 'H') 48 .put("MG", 'H') 49 .put("MU", 'H') 50 .put("MZ", 'H') 51 .put("SC", 'H') 52 .put("CM", 'H') 53 .put("TD", 'h') 54 .put("DZ", 'h') 55 .put("MA", 'h') 56 .put("TN", 'h') 57 .put("BW", 'h') 58 .put("LS", 'h') 59 .put("NA", 'h') 60 .put("SZ", 'h') 61 .put("ZA", 'h') 62 .put("GH", 'h') 63 .put("MR", 'h') 64 .put("NG", 'h') 65 .put("TG", 'H') 66 .put("CA", 'h') 67 .put("US", 'h') 68 .put("CN", 'h') 69 .put("MO", 'h') 70 .put("PH", 'H') 71 .put("IN", 'h') 72 .put("LK", 'H') 73 .put("CY", 'h') 74 .put("IL", 'H') 75 .put("SY", 'h') 76 .put("MK", 'H') 77 .put("VU", 'h') 78 .put("TO", 'H') 79 .put("001", 'H') 80 .freeze(); 81 82 static final class Hours implements Comparable<Hours> { 83 final DateTimePatternType type; 84 final char variable; 85 Hours(DateTimePatternType type, String variable)86 public Hours(DateTimePatternType type, String variable) { 87 this.type = type; 88 this.variable = variable.charAt(0); 89 } 90 91 @Override compareTo(Hours arg0)92 public int compareTo(Hours arg0) { 93 // TODO Auto-generated method stub 94 int result = type.compareTo(arg0.type); 95 if (result != 0) return result; 96 return variable < arg0.variable ? -1 : variable > arg0.variable ? 1 : 0; 97 } 98 99 @Override toString()100 public String toString() { 101 // TODO Auto-generated method stub 102 return type + ":" + variable; 103 } 104 105 @Override equals(Object obj)106 public boolean equals(Object obj) { 107 return obj instanceof Hours && compareTo((Hours) obj) == 0; 108 } 109 } 110 main(String[] args)111 public static void main(String[] args) { 112 final Relation<String, Hours> lang2Hours = Relation.of(new TreeMap<String, Set<Hours>>(), TreeSet.class); 113 final Factory factory = INFO.getCldrFactory(); 114 final FormatParser formatDateParser = new FormatParser(); 115 final LikelySubtags likely2Max = new LikelySubtags(); 116 117 for (final String locale : factory.getAvailable()) { 118 if (locale.equals("root")) { 119 continue; 120 } 121 // if (locale.charAt(0) > 'b') { 122 // continue; 123 // } 124 final CLDRFile cldrFile = factory.make(locale, true); 125 for (String path : With.in(cldrFile)) { 126 // if (path.contains("/timeFormats")) { 127 // System.out.println(path); 128 // } 129 DateTimePatternType type = DateTimePatternType.fromPath(path); 130 if (type == DateTimePatternType.NA || type == DateTimePatternType.GMT) { 131 continue; 132 } 133 String value = cldrFile.getStringValue(path); 134 formatDateParser.set(value); 135 for (Object item : formatDateParser.getItems()) { 136 if (item instanceof VariableField) { 137 String itemString = item.toString(); 138 if (PreferredAndAllowedHour.HourStyle.isHourCharacter(itemString)) { 139 lang2Hours.put(locale, new Hours(type, itemString)); 140 } 141 } 142 } 143 } 144 System.out.println(locale + "\t" + lang2Hours.get(locale)); 145 // for (Entry<String, Set<String>> e : lang2Hours.keyValuesSet()) { 146 // System.out.println(e); 147 // } 148 } 149 150 // gather data per region 151 152 Map<String, Relation<Character, String>> region2Preferred2locales = new TreeMap<>(); 153 Relation<String, Character> region2Allowed = Relation.of(new TreeMap<String, Set<Character>>(), TreeSet.class); 154 final LanguageTagParser ltp = new LanguageTagParser(); 155 156 for (Entry<String, Set<Hours>> localeAndHours : lang2Hours.keyValuesSet()) { 157 String locale = localeAndHours.getKey(); 158 String maxLocale = likely2Max.maximize(locale); 159 if (maxLocale == null) { 160 System.out.println("*** Missing likely for " + locale); 161 continue; 162 } 163 String region = ltp.set(maxLocale).getRegion(); 164 if (region.isEmpty()) { 165 System.out.println("*** Missing region for " + locale + ", " + maxLocale); 166 continue; 167 } 168 if (DIGITS.containsSome(region) && !region.equals("001")) { 169 System.out.println("*** Skipping multicountry region for " + locale + ", " + maxLocale); 170 continue; 171 } 172 for (Hours hours : localeAndHours.getValue()) { 173 region2Allowed.put(region, hours.variable); 174 if (hours.type == DateTimePatternType.STOCK) { 175 Relation<Character, String> items = region2Preferred2locales.get(region); 176 if (items == null) { 177 region2Preferred2locales.put(region, 178 items = Relation.of(new TreeMap<Character, Set<String>>(), TreeSet.class)); 179 } 180 items.put(hours.variable, locale); 181 } 182 } 183 } 184 185 // now invert 186 Relation<PreferredAndAllowedHour, String> preferred2Region = Relation.of( 187 new TreeMap<PreferredAndAllowedHour, Set<String>>(), TreeSet.class); 188 StringBuilder overrides = new StringBuilder("\n"); 189 190 for (Entry<String, Relation<Character, String>> e : region2Preferred2locales.entrySet()) { 191 String region = e.getKey(); 192 Set<Character> allowed = region2Allowed.get(region); 193 Relation<Character, String> preferredSet = e.getValue(); 194 Character resolvedValue = CONFLICT_RESOLUTION.get(region); 195 if (resolvedValue != null) { 196 if (preferredSet.size() == 1) { 197 overrides.append(region + " didn't need override!!\n"); 198 } else { 199 LinkedHashSet<Entry<Character, String>> oldValues = new LinkedHashSet<>(); 200 StringBuilder oldValuesString = new StringBuilder(); 201 for (Entry<Character, String> x : preferredSet.keyValueSet()) { 202 if (!x.getKey().equals(resolvedValue)) { 203 oldValues.add(x); 204 oldValuesString.append(x.getKey() + "=" + x.getValue() + "; "); 205 } 206 } 207 for (Entry<Character, String> x : oldValues) { 208 preferredSet.remove(x.getKey(), x.getValue()); 209 } 210 overrides.append(region + " has multiple values. Overriding with CONFLICT_RESOLUTION to " 211 + resolvedValue + " and discarded values " + oldValuesString + "\n"); 212 } 213 } 214 215 Set<Character> allAllowed = new TreeSet<>(); 216 Character preferred = null; 217 218 for (Entry<Character, Set<String>> pref : preferredSet.keyValuesSet()) { 219 allAllowed.addAll(allowed); 220 if (preferred == null) { 221 preferred = pref.getKey(); 222 } else { 223 overrides.append(region + " has multiple preferred values! " + preferredSet + "\n"); 224 } 225 // else { 226 // if (!haveFirst) { 227 // System.out.print("*** Conflict in\t" + region + "\t" + ENGLISH.getName("territory", region) + 228 // "\twith\t"); 229 // System.out.println(preferred + "\t" + locales); 230 // haveFirst = true; 231 // } 232 // //System.out.println("\t" + pref.getKey() + "\t" + pref.getValue()); 233 // } 234 } 235 Set<Character> overrideAllowed = OVERRIDE_ALLOWED.get(region); 236 if (overrideAllowed != null) { 237 allAllowed = overrideAllowed; 238 overrides.append(region + " overriding allowed to " + overrideAllowed + "\n"); 239 } 240 try { 241 preferred2Region.put(new PreferredAndAllowedHour(preferred, allAllowed), region); 242 } catch (RuntimeException e1) { 243 throw e1; 244 } 245 String subcontinent = Containment.getSubcontinent(region); 246 String continent = Containment.getContinent(region); 247 String tag = Joiner.on(",").join(preferredSet.keySet()); 248 if (tag.equals("h")) { 249 tag += "*"; 250 } 251 252 System.out.println(tag 253 + "\t" + region 254 + "\t" + ENGLISH.getName("territory", region) 255 + "\t" + subcontinent 256 + "\t" + ENGLISH.getName("territory", subcontinent) 257 + "\t" + continent 258 + "\t" + ENGLISH.getName("territory", continent) 259 + "\t" + showInfo(preferredSet)); 260 } 261 262 // now present 263 264 System.out.println(" <timeData>"); 265 for (Entry<PreferredAndAllowedHour, Set<String>> e : preferred2Region.keyValuesSet()) { 266 PreferredAndAllowedHour preferredAndAllowedHour = e.getKey(); 267 Set<String> regions = e.getValue(); 268 System.out.println(" <hours " 269 + "preferred=\"" 270 + preferredAndAllowedHour.preferred 271 + "\"" 272 + " allowed=\"" 273 + Joiner.on(" ").join(preferredAndAllowedHour.allowed) 274 + "\"" 275 + " regions=\"" + Joiner.on(" ").join(regions) + "\"" 276 + "/>"); 277 } 278 System.out.println(" </timeData>"); 279 System.out.println(overrides); 280 } 281 showInfo(Relation<Character, String> preferredSet)282 private static String showInfo(Relation<Character, String> preferredSet) { 283 StringBuilder b = new StringBuilder(); 284 for (Character key : Arrays.asList('H', 'h')) { 285 if (b.length() != 0) { 286 b.append('\t'); 287 } 288 b.append(key).append('\t'); 289 Set<String> value = preferredSet.get(key); 290 if (value != null) { 291 boolean needSpace = false; 292 for (String locale : value) { 293 if (needSpace) { 294 b.append(" "); 295 } else { 296 needSpace = true; 297 } 298 b.append(locale); 299 boolean isOfficial = false; 300 isOfficial = isOfficial(locale, isOfficial); 301 if (isOfficial) { 302 b.append('°'); 303 } 304 } 305 } 306 } 307 return b.toString(); 308 } 309 isOfficial(String locale, boolean isOfficial)310 private static boolean isOfficial(String locale, boolean isOfficial) { 311 LanguageTagParser ltp = new LanguageTagParser().set(locale); 312 PopulationData data = INFO.getSupplementalDataInfo().getLanguageAndTerritoryPopulationData( 313 ltp.getLanguageScript(), ltp.getRegion()); 314 if (data == null) { 315 data = INFO.getSupplementalDataInfo().getLanguageAndTerritoryPopulationData( 316 ltp.getLanguage(), ltp.getRegion()); 317 } 318 if (data != null) { 319 OfficialStatus status = data.getOfficialStatus(); 320 if (status == OfficialStatus.official || status == OfficialStatus.de_facto_official) { 321 isOfficial = true; 322 } 323 } 324 return isOfficial; 325 } 326 } 327