xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/test/FlexibleDateFromCLDR.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 /*
2  ******************************************************************************
3  * Copyright (C) 2006-2009,2012, International Business Machines Corporation  *
4  * and others. All Rights Reserved.                                           *
5  ******************************************************************************
6  * $Source$
7  * $Revision$
8  ******************************************************************************
9  */
10 package org.unicode.cldr.test;
11 
12 import com.ibm.icu.text.DateFormat;
13 import com.ibm.icu.text.DateTimePatternGenerator;
14 import com.ibm.icu.text.DateTimePatternGenerator.PatternInfo;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.Collection;
18 import java.util.Date;
19 import java.util.Iterator;
20 import java.util.LinkedHashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.TreeMap;
24 import org.unicode.cldr.util.CLDRFile;
25 import org.unicode.cldr.util.ICUServiceBuilder;
26 import org.unicode.cldr.util.XPathParts;
27 
28 /**
29  * Temporary class while refactoring.
30  *
31  * @author markdavis
32  */
33 class FlexibleDateFromCLDR {
34     DateTimePatternGenerator gen = DateTimePatternGenerator.getEmptyInstance();
35     private transient ICUServiceBuilder icuServiceBuilder = new ICUServiceBuilder();
36 
37     static List<String> tests =
38             Arrays.asList(
39                     new String[] {
40                         "HHmmssSSSvvvv", // 'complete' time
41                         "HHmm",
42                         "HHmmvvvv",
43                         "HHmmss",
44                         "HHmmssSSSSS",
45                         "HHmmssvvvv",
46                         "MMMd",
47                         "Md",
48                         "YYYYD", // (maybe?)
49                         "yyyyww",
50                         "yyyywwEEE",
51                         "yyyyQQQQ",
52                         "yyyyMM",
53                         "yyyyMd",
54                         "yyyyMMMd",
55                         "yyyyMMMEEEd",
56                         "GyyyyMMMd",
57                         "GyyyyMMMEEEd", // 'complete' date
58                         "YYYYwEEE", // year, week of year, weekday
59                         "yyyyDD", // year, day of year
60                         "yyyyMMFE", // year, month, nth day of week in month
61                         // misc
62                         "eG",
63                         "dMMy",
64                         "GHHmm",
65                         "yyyyHHmm",
66                         "Kmm",
67                         "kmm",
68                         "MMdd",
69                         "ddHH",
70                         "yyyyMMMd",
71                         "yyyyMMddHHmmss",
72                         "GEEEEyyyyMMddHHmmss",
73                         "GuuuuQMMMMwwWddDDDFEEEEaHHmmssSSSvvvv", // bizarre case just for testing
74                     });
75 
set(CLDRFile cldrFile)76     public void set(CLDRFile cldrFile) {
77         icuServiceBuilder.setCldrFile(cldrFile);
78         gen = DateTimePatternGenerator.getEmptyInstance(); // for now
79         failureMap.clear();
80     }
81 
82     /** */
showFlexibles()83     public void showFlexibles() {
84         Map<String, String> items = gen.getSkeletons(new LinkedHashMap<String, String>());
85         System.out.println("ERRORS");
86         for (Iterator<String> it = failureMap.keySet().iterator(); it.hasNext(); ) {
87             String item = it.next();
88             String value = failureMap.get(item);
89             System.out.println("\t" + value);
90         }
91         for (int i = 0; i < DateTimePatternGenerator.TYPE_LIMIT; ++i) {
92             String format = gen.getAppendItemFormat(i);
93             if (format.indexOf('\u251C') >= 0) {
94                 System.out.println("\tMissing AppendItem format:\t" + DISPLAY_NAME_MAP[i]);
95             }
96             if (i == DateTimePatternGenerator.FRACTIONAL_SECOND) continue; // don't need this field
97             String name = gen.getAppendItemName(i);
98             if (name.matches("F[0-9]+")) {
99                 System.out.println("\tMissing Field Name:\t" + DISPLAY_NAME_MAP[i]);
100             }
101         }
102         System.out.println("SKELETON\t=> PATTERN LIST");
103         for (Iterator<String> it = items.keySet().iterator(); it.hasNext(); ) {
104             String skeleton = it.next();
105             System.out.println("\t\"" + skeleton + "\"\t=>\t\"" + items.get(skeleton) + "\"");
106         }
107         System.out.println("REDUNDANTS");
108         Collection<String> redundants = gen.getRedundants(new ArrayList<String>());
109         for (String item : redundants) {
110             System.out.println("\t" + item);
111         }
112         System.out.println("TESTS");
113         for (String item : tests) {
114             try {
115                 String pat = gen.getBestPattern(item);
116                 String sample = "<can't format>";
117                 try {
118                     DateFormat df = icuServiceBuilder.getDateFormat("gregorian", pat);
119                     sample = df.format(new Date());
120                 } catch (RuntimeException e) {
121                 }
122                 System.out.println(
123                         "\t\"" + item + "\"\t=>\t\"" + pat + "\"\t=>\t\"" + sample + "\"");
124             } catch (RuntimeException e) {
125                 System.out.println(e.getMessage()); // e.printStackTrace();
126             }
127         }
128         System.out.println("END");
129     }
130 
131     Map<String, String> failureMap = new TreeMap<>();
132 
133     /**
134      * @param path
135      * @param value
136      * @param fullPath
137      */
checkFlexibles(String path, String value, String fullPath)138     public void checkFlexibles(String path, String value, String fullPath) {
139         if (path.indexOf("numbers/symbols/decimal") >= 0) {
140             gen.setDecimal(value);
141             return;
142         }
143         if (path.indexOf("gregorian") < 0) return;
144         if (path.indexOf("/appendItem") >= 0) {
145             XPathParts parts = XPathParts.getFrozenInstance(path);
146             String key = parts.getAttributeValue(-1, "request");
147             try {
148                 gen.setAppendItemFormat(getIndex(key, APPEND_ITEM_NAME_MAP), value);
149             } catch (RuntimeException e) {
150                 failureMap.put(
151                         path, "\tWarning: can't set AppendItemFormat:\t" + key + ":\t" + value);
152             }
153             return;
154         }
155         if (path.indexOf("/fields") >= 0) {
156             XPathParts parts = XPathParts.getFrozenInstance(path);
157             String key = parts.getAttributeValue(-2, "type");
158             try {
159                 gen.setAppendItemName(getIndex(key, DISPLAY_NAME_MAP), value);
160             } catch (RuntimeException e) {
161                 failureMap.put(
162                         path, "\tWarning: can't set AppendItemName:\t" + key + ":\t" + value);
163             }
164             return;
165         }
166 
167         if (path.indexOf("pattern") < 0
168                 && path.indexOf("dateFormatItem") < 0
169                 && path.indexOf("intervalFormatItem") < 0) return;
170         // set the am/pm preference
171         if (path.indexOf("timeFormatLength[@type=\"short\"]") >= 0) {
172             fp.set(value);
173             for (Object item : fp.getItems()) {
174                 if (item instanceof DateTimePatternGenerator.VariableField) {
175                     if (item.toString().charAt(0) == 'h') {
176                         isPreferred12Hour = true;
177                     }
178                 }
179             }
180         }
181         if (path.indexOf("dateTimeFormatLength") > 0) return; // exclude {1} {0}
182         if (path.indexOf("intervalFormatItem") < 0) {
183             // add to generator
184             try {
185                 gen.addPattern(value, false, patternInfo);
186                 switch (patternInfo.status) {
187                     case PatternInfo.CONFLICT:
188                         failureMap.put(
189                                 path,
190                                 "Conflicting Patterns: \""
191                                         + value
192                                         + "\"\t&\t\""
193                                         + patternInfo.conflictingPattern
194                                         + "\"");
195                         break;
196                 }
197             } catch (RuntimeException e) {
198                 failureMap.put(path, e.getMessage());
199             }
200         }
201     }
202 
getDTPGForCalendarType( String calendarType, List<CLDRFile> parentCLDRFiles)203     public DateTimePatternGenerator getDTPGForCalendarType(
204             String calendarType, List<CLDRFile> parentCLDRFiles) {
205         DateTimePatternGenerator dtpg = DateTimePatternGenerator.getEmptyInstance();
206         switch (calendarType) {
207             default:
208                 addAvailableFormatsForFile(dtpg, calendarType, parentCLDRFiles.get(0));
209                 int hyphenIndex = calendarType.indexOf('-');
210                 if (hyphenIndex > 0) { // e.g. islamic-umalqura, ethiopic-amete-alem
211                     // we inherit from the untruncated form
212                     String baseType = calendarType.substring(0, hyphenIndex);
213                     addAvailableFormatsForFile(dtpg, baseType, parentCLDRFiles.get(0));
214                 }
215                 // then fall through to generic (sideways)
216             case "generic":
217                 addAvailableFormatsForFile(dtpg, "generic", parentCLDRFiles.get(0));
218                 // then fall through to gregorian (sideways)
219             case "gregorian":
220                 // this inherits upward from parents
221                 addAvailableFormatsWithParents(dtpg, "gregorian", parentCLDRFiles);
222                 break;
223 
224             case "dangi":
225                 addAvailableFormatsForFile(dtpg, "dangi", parentCLDRFiles.get(0));
226                 // fall through to chinese (sideways)
227             case "chinese":
228                 // this inherits upward from parents
229                 addAvailableFormatsWithParents(dtpg, "chinese", parentCLDRFiles);
230                 break;
231         }
232         return dtpg;
233     }
234 
addAvailableFormatsWithParents( DateTimePatternGenerator dtpg, String calendarType, List<CLDRFile> parentCLDRFiles)235     private void addAvailableFormatsWithParents(
236             DateTimePatternGenerator dtpg, String calendarType, List<CLDRFile> parentCLDRFiles) {
237         for (Iterator<CLDRFile> it = parentCLDRFiles.iterator(); it.hasNext(); ) {
238             CLDRFile file = it.next();
239             addAvailableFormatsForFile(dtpg, calendarType, file);
240         }
241     }
242 
243     private static String DATE_FORMAT_ITEM_ID_PREFIX = "dateFormatItem[@id=\"";
244 
addAvailableFormatsForFile( DateTimePatternGenerator dtpg, String calendarType, CLDRFile file)245     private void addAvailableFormatsForFile(
246             DateTimePatternGenerator dtpg, String calendarType, CLDRFile file) {
247         String toppath =
248                 "//ldml/dates/calendars/calendar[@type=\""
249                         + calendarType
250                         + "\"]/dateTimeFormats/availableFormats";
251         // relevant paths here might include the following (but we want to skip alt=variant):
252         // ...dateTimeFormats/availableFormats/dateFormatItem[@id="..."]
253         // ...dateTimeFormats/availableFormats/dateFormatItem[@id="..."][@draft="..."]
254         // ...dateTimeFormats/availableFormats/dateFormatItem[@id="..."][@count="..."]
255         // ...dateTimeFormats/availableFormats/dateFormatItem[@id="..."][@count="..."][@draft="..."]
256         // ...dateTimeFormats/availableFormats/dateFormatItem[@id="..."][@alt="variant"]
257         boolean isRoot = file.getLocaleID().equals("root");
258         for (Iterator<String> it = file.iterator(toppath); it.hasNext(); ) {
259             String path = it.next();
260             int startIndex = path.indexOf(DATE_FORMAT_ITEM_ID_PREFIX);
261             if (startIndex < 0 || path.indexOf("[@alt=", startIndex) >= 0) {
262                 continue;
263             }
264             startIndex += DATE_FORMAT_ITEM_ID_PREFIX.length();
265             int endIndex = path.indexOf("\"]", startIndex);
266             String skeleton = path.substring(startIndex, endIndex);
267             String pattern = file.getWinningValue(path);
268             dtpg.addPatternWithSkeleton(pattern, skeleton, !isRoot, patternInfo);
269         }
270     }
271 
stripLiterals(String pattern)272     private String stripLiterals(String pattern) {
273         int i = 0, patlen = pattern.length();
274         StringBuilder stripped = new StringBuilder(patlen);
275         boolean inLiteral = false;
276         while (i < patlen) {
277             char c = pattern.charAt(i++);
278             if (c == '\'') {
279                 inLiteral = !inLiteral;
280             } else if (!inLiteral) {
281                 stripped.append(c);
282             }
283         }
284         return stripped.toString();
285     }
286 
checkValueAgainstSkeleton(String path, String value)287     public String checkValueAgainstSkeleton(String path, String value) {
288         String failure = null;
289         String skeleton = null;
290         String strippedPattern = null;
291         if (path.contains("dateFormatItem")) {
292             XPathParts parts = XPathParts.getFrozenInstance(path);
293             skeleton = parts.findAttributeValue("dateFormatItem", "id"); // the skeleton
294             strippedPattern = gen.getSkeleton(value); // the pattern stripped of literals
295         } else if (path.contains("intervalFormatItem")) {
296             XPathParts parts = XPathParts.getFrozenInstance(path);
297             skeleton = parts.findAttributeValue("intervalFormatItem", "id"); // the skeleton
298             strippedPattern =
299                     stripLiterals(
300                             value); // can't use gen on intervalFormat pattern (throws exception)
301         }
302         if (skeleton != null && strippedPattern != null) {
303             if (skeleton.indexOf('H') >= 0
304                     || skeleton.indexOf('k') >= 0) { // if skeleton uses 24-hour time
305                 if (strippedPattern.indexOf('h') >= 0
306                         || strippedPattern.indexOf('K') >= 0) { // but pattern uses 12...
307                     failure = "Skeleton uses 24-hour cycle (H,k) but pattern uses 12-hour (h,K)";
308                 }
309             } else if (skeleton.indexOf('h') >= 0
310                     || skeleton.indexOf('K') >= 0) { // if skeleton uses 12-hour time
311                 if (strippedPattern.indexOf('H') >= 0
312                         || strippedPattern.indexOf('k') >= 0) { // but pattern uses 24...
313                     failure = "Skeleton uses 12-hour cycle (h,K) but pattern uses 24-hour (H,k)";
314                 }
315             } else if (skeleton.indexOf('G') >= 0
316                     && strippedPattern.indexOf('G') < 0
317                     && strippedPattern.indexOf('r') < 0
318                     && strippedPattern.indexOf('U') < 0) {
319                 // If skeleton has G, pattern should have G (or for cyclic calendars like
320                 // chinese/dangi, r and/or U)
321                 failure =
322                         "Skeleton includes 'G' (era) but pattern does not have 'G' (or 'r' or 'U' for chinese/dangi calendars)";
323             }
324         }
325         return failure;
326     }
327 
328     DateTimePatternGenerator.FormatParser fp = new DateTimePatternGenerator.FormatParser();
329 
330     boolean isPreferred12Hour = false;
331 
332     private static String[] DISPLAY_NAME_MAP = {
333         "era",
334         "year",
335         "quarter",
336         "month",
337         "week",
338         "week_in_month",
339         "weekday",
340         "day",
341         "day_of_year",
342         "day_of_week_in_month",
343         "dayperiod",
344         "hour",
345         "minute",
346         "second",
347         "fractional_second",
348         "zone",
349         "-"
350     };
351 
352     private static String[] APPEND_ITEM_NAME_MAP = {
353         "Era",
354         "Year",
355         "Quarter",
356         "Month",
357         "Week",
358         "Week",
359         "Day-Of-Week",
360         "Day",
361         "Day",
362         "Day-Of-Week",
363         "-",
364         "Hour",
365         "Minute",
366         "Second",
367         "-",
368         "Timezone",
369         "-"
370     };
371 
getIndex(String s, String[] strings)372     int getIndex(String s, String[] strings) {
373         for (int i = 0; i < strings.length; ++i) {
374             if (s.equals(strings[i])) return i;
375         }
376         return -1;
377     }
378 
379     PatternInfo patternInfo = new PatternInfo();
380 
getRedundants(Collection<String> output)381     public Collection<String> getRedundants(Collection<String> output) {
382         return gen.getRedundants(output);
383     }
384 
getFailurePath(Object path)385     public Object getFailurePath(Object path) {
386         return failureMap.get(path);
387     }
388 
preferred12Hour()389     public boolean preferred12Hour() {
390         return isPreferred12Hour;
391     }
392 }
393