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