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 
86         public Hours(DateTimePatternType type, String variable) {
87             this.type = type;
88             this.variable = variable.charAt(0);
89         }
90 
91         @Override
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
100         public String toString() {
101             // TODO Auto-generated method stub
102             return type + ":" + variable;
103         }
104 
105         @Override
106         public boolean equals(Object obj) {
107             return obj instanceof Hours && compareTo((Hours) obj) == 0;
108         }
109     }
110 
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 
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 
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