xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/DateTimeFormats.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 package org.unicode.cldr.util;
2 
3 import com.google.common.collect.ImmutableMap;
4 import com.ibm.icu.impl.Row.R3;
5 import com.ibm.icu.text.DateFormat;
6 import com.ibm.icu.text.DateIntervalFormat;
7 import com.ibm.icu.text.DateIntervalInfo;
8 import com.ibm.icu.text.DateTimePatternGenerator;
9 import com.ibm.icu.text.DateTimePatternGenerator.FormatParser;
10 import com.ibm.icu.text.DateTimePatternGenerator.PatternInfo;
11 import com.ibm.icu.text.DateTimePatternGenerator.VariableField;
12 import com.ibm.icu.text.DecimalFormat;
13 import com.ibm.icu.text.MessageFormat;
14 import com.ibm.icu.text.SimpleDateFormat;
15 import com.ibm.icu.util.Calendar;
16 import com.ibm.icu.util.DateInterval;
17 import com.ibm.icu.util.ICUUncheckedIOException;
18 import com.ibm.icu.util.Output;
19 import com.ibm.icu.util.TimeZone;
20 import com.ibm.icu.util.ULocale;
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.PrintWriter;
24 import java.util.Arrays;
25 import java.util.Date;
26 import java.util.EnumSet;
27 import java.util.LinkedHashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.Set;
32 import java.util.TreeMap;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35 import org.unicode.cldr.draft.FileUtilities;
36 import org.unicode.cldr.tool.ChartDelta;
37 import org.unicode.cldr.tool.FormattedFileWriter;
38 import org.unicode.cldr.tool.Option;
39 import org.unicode.cldr.tool.Option.Options;
40 import org.unicode.cldr.tool.ShowData;
41 import org.unicode.cldr.util.ICUServiceBuilder.Context;
42 import org.unicode.cldr.util.ICUServiceBuilder.Width;
43 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;
44 import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
45 
46 public class DateTimeFormats {
47     private static final Date SAMPLE_DATE_DEFAULT_END = new Date(2099 - 1900, 0, 13, 14, 45, 59);
48     private static final String DIR = CLDRPaths.CHART_DIRECTORY + "/verify/dates/";
49     private static SupplementalDataInfo sdi = SupplementalDataInfo.getInstance();
50     private static Map<String, PreferredAndAllowedHour> timeData = sdi.getTimeData();
51 
52     static final Options myOptions = new Options();
53 
54     enum MyOptions {
55         organization(".*", "CLDR", "organization"),
56         filter(".*", ".*", "locale filter (regex)");
57         // boilerplate
58         final Option option;
59 
MyOptions(String argumentPattern, String defaultArgument, String helpText)60         MyOptions(String argumentPattern, String defaultArgument, String helpText) {
61             option = myOptions.add(this, argumentPattern, defaultArgument, helpText);
62         }
63     }
64 
65     private static final String TIMES_24H_TITLE = "Times 24h";
66     private static final boolean DEBUG = false;
67     private static final String DEBUG_SKELETON = "y";
68     private static final ULocale DEBUG_LIST_PATTERNS = ULocale.JAPANESE; // or null;
69 
70     private static final String FIELDS_TITLE = "Fields";
71 
72     private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
73 
74     private static final String[] STOCK = {"short", "medium", "long", "full"};
75     private static final String[] CALENDAR_FIELD_TO_PATTERN_LETTER = {
76         "G", "y", "M",
77         "w", "W", "d",
78         "D", "E", "F",
79         "a", "h", "H",
80         "m",
81     };
82     private static final Date SAMPLE_DATE = new Date(2012 - 1900, 0, 13, 14, 45, 59);
83 
84     private static final String SAMPLE_DATE_STRING = CldrUtility.isoFormat(SAMPLE_DATE);
85 
86     private static final Map<String, Date> SAMPLE_DATE_END =
87             ImmutableMap.<String, Date>builder()
88                     .put("G", SAMPLE_DATE_DEFAULT_END)
89                     .put("y", new Date(2013 - 1900, 0, 13, 14, 45, 59))
90                     .put("M", new Date(2012 - 1900, 1, 13, 14, 45, 59))
91                     .put("w", SAMPLE_DATE_DEFAULT_END)
92                     .put("W", SAMPLE_DATE_DEFAULT_END)
93                     .put("d", new Date(2012 - 1900, 0, 14, 14, 45, 59))
94                     .put("D", SAMPLE_DATE_DEFAULT_END)
95                     .put("E", new Date(2012 - 1900, 0, 14, 14, 45, 59))
96                     .put("F", SAMPLE_DATE_DEFAULT_END)
97                     .put("a", new Date(2012 - 1900, 0, 13, 2, 45, 59))
98                     .put("h", new Date(2012 - 1900, 0, 13, 15, 45, 59))
99                     .put("H", new Date(2012 - 1900, 0, 13, 15, 45, 59))
100                     .put("m", SAMPLE_DATE_DEFAULT_END)
101                     .build();
102     //        // "G", "y", "M",
103     //        null, new Date(2013 - 1900, 0, 13, 14, 45, 59), new Date(2012 - 1900, 1, 13, 14, 45,
104     // 59),
105     //        // "w", "W", "d",
106     //        null, null, new Date(2012 - 1900, 0, 14, 14, 45, 59),
107     //        // "D", "E", "F",
108     //        null, new Date(2012 - 1900, 0, 14, 14, 45, 59), null,
109     //        // "a", "h", "H",
110     //        new Date(2012 - 1900, 0, 13, 2, 45, 59), new Date(2012 - 1900, 0, 13, 15, 45, 59),
111     //        new Date(2012 - 1900, 0, 13, 15, 45, 59),
112     //        // "m",
113     //        new Date(2012 - 1900, 0, 13, 14, 46, 59)
114 
115     private DateTimePatternGenerator generator;
116     private ULocale locale;
117     private ICUServiceBuilder icuServiceBuilder;
118     private ICUServiceBuilder icuServiceBuilderEnglish =
119             new ICUServiceBuilder().setCldrFile(CLDRConfig.getInstance().getEnglish());
120 
121     private DateIntervalInfo dateIntervalInfo = new DateIntervalInfo();
122     private String calendarID;
123     private CLDRFile file;
124 
125     private static String surveyUrl =
126             CLDRConfig.getInstance()
127                     .getProperty("CLDR_SURVEY_URL", "http://st.unicode.org/cldr-apps/survey");
128 
129     /**
130      * Set a CLDRFile and calendar. Must be done before calling addTable.
131      *
132      * @param file
133      * @param calendarID
134      * @return
135      */
set(CLDRFile file, String calendarID)136     public DateTimeFormats set(CLDRFile file, String calendarID) {
137         return set(file, calendarID, true);
138     }
139 
140     /**
141      * Set a CLDRFile and calendar. Must be done before calling addTable.
142      *
143      * @param file
144      * @param calendarID
145      * @return
146      */
set(CLDRFile file, String calendarID, boolean useStock)147     public DateTimeFormats set(CLDRFile file, String calendarID, boolean useStock) {
148         this.file = file;
149         locale = new ULocale(file.getLocaleID());
150         if (useStock) {
151             icuServiceBuilder = new ICUServiceBuilder().setCldrFile(file);
152         }
153         PatternInfo returnInfo = new PatternInfo();
154         generator = DateTimePatternGenerator.getEmptyInstance();
155         this.calendarID = calendarID;
156         boolean haveDefaultHourChar = false;
157 
158         for (String stock : STOCK) {
159             String path =
160                     "//ldml/dates/calendars/calendar[@type=\""
161                             + calendarID
162                             + "\"]/dateFormats/dateFormatLength[@type=\""
163                             + stock
164                             + "\"]/dateFormat[@type=\"standard\"]/pattern[@type=\"standard\"]";
165             String dateTimePattern = file.getStringValue(path);
166             if (useStock) {
167                 generator.addPattern(dateTimePattern, true, returnInfo);
168             }
169             path =
170                     "//ldml/dates/calendars/calendar[@type=\""
171                             + calendarID
172                             + "\"]/timeFormats/timeFormatLength[@type=\""
173                             + stock
174                             + "\"]/timeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]";
175             dateTimePattern = file.getStringValue(path);
176             if (useStock) {
177                 generator.addPattern(dateTimePattern, true, returnInfo);
178             }
179             if (DEBUG && DEBUG_LIST_PATTERNS.equals(locale)) {
180                 System.out.println("* Adding: " + locale + "\t" + dateTimePattern);
181             }
182             if (!haveDefaultHourChar) {
183                 // use hour style in SHORT time pattern as the default
184                 // hour style for the locale
185                 FormatParser fp = new FormatParser();
186                 fp.set(dateTimePattern);
187                 List<Object> items = fp.getItems();
188                 for (int idx = 0; idx < items.size(); idx++) {
189                     Object item = items.get(idx);
190                     if (item instanceof VariableField) {
191                         VariableField fld = (VariableField) item;
192                         if (fld.getType() == DateTimePatternGenerator.HOUR) {
193                             generator.setDefaultHourFormatChar(fld.toString().charAt(0));
194                             haveDefaultHourChar = true;
195                             break;
196                         }
197                     }
198                 }
199             }
200         }
201 
202         // appendItems result.setAppendItemFormat(getAppendFormatNumber(formatName), value);
203         for (String path :
204                 With.in(
205                         file.iterator(
206                                 "//ldml/dates/calendars/calendar[@type=\""
207                                         + calendarID
208                                         + "\"]/dateTimeFormats/appendItems/appendItem"))) {
209             XPathParts parts = XPathParts.getFrozenInstance(path);
210             String request = parts.getAttributeValue(-1, "request");
211             int requestNumber = DateTimePatternGenerator.getAppendFormatNumber(request);
212             String value = file.getStringValue(path);
213             generator.setAppendItemFormat(requestNumber, value);
214             if (DEBUG && DEBUG_LIST_PATTERNS.equals(locale)) {
215                 System.out.println("* Adding: " + locale + "\t" + request + "\t" + value);
216             }
217         }
218 
219         // field names result.setAppendItemName(i, value);
220         // ldml/dates/fields/field[@type="day"]/displayName
221         for (String path : With.in(file.iterator("//ldml/dates/fields/field"))) {
222             if (!path.contains("displayName")) {
223                 continue;
224             }
225             XPathParts parts = XPathParts.getFrozenInstance(path);
226             String type = parts.getAttributeValue(-2, "type");
227             int requestNumber = find(FIELD_NAMES, type);
228 
229             String value = file.getStringValue(path);
230             generator.setAppendItemName(requestNumber, value);
231             if (DEBUG && DEBUG_LIST_PATTERNS.equals(locale)) {
232                 System.out.println("* Adding: " + locale + "\t" + type + "\t" + value);
233             }
234         }
235 
236         for (String path :
237                 With.in(
238                         file.iterator(
239                                 "//ldml/dates/calendars/calendar[@type=\""
240                                         + calendarID
241                                         + "\"]/dateTimeFormats/availableFormats/dateFormatItem"))) {
242             XPathParts parts = XPathParts.getFrozenInstance(path);
243             String key = parts.getAttributeValue(-1, "id");
244             String value = file.getStringValue(path);
245             if (key.equals(DEBUG_SKELETON)) {
246                 int debug = 0;
247             }
248             generator.addPatternWithSkeleton(value, key, true, returnInfo);
249             if (DEBUG && DEBUG_LIST_PATTERNS.equals(locale)) {
250                 System.out.println("* Adding: " + locale + "\t" + key + "\t" + value);
251             }
252         }
253 
254         generator.setDateTimeFormat(
255                 Calendar.getDateTimePattern(
256                         Calendar.getInstance(locale), locale, DateFormat.MEDIUM));
257 
258         // ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\"yMMMEd\"]/greatestDifference[@id=\"d\"]
259         for (String path :
260                 With.in(
261                         file.iterator(
262                                 "//ldml/dates/calendars/calendar[@type=\""
263                                         + calendarID
264                                         + "\"]/dateTimeFormats/intervalFormats/intervalFormatItem"))) {
265             XPathParts parts = XPathParts.getFrozenInstance(path);
266             String skeleton = parts.getAttributeValue(-2, "id");
267             String diff = parts.getAttributeValue(-1, "id");
268             int diffNumber = find(CALENDAR_FIELD_TO_PATTERN_LETTER, diff);
269             String intervalPattern = file.getStringValue(path);
270             dateIntervalInfo.setIntervalPattern(skeleton, diffNumber, intervalPattern);
271         }
272         if (useStock) {
273             dateIntervalInfo.setFallbackIntervalPattern(
274                     file.getStringValue(
275                             "//ldml/dates/calendars/calendar[@type=\""
276                                     + calendarID
277                                     + "\"]/dateTimeFormats/intervalFormats/intervalFormatFallback"));
278         }
279         return this;
280     }
281 
282     private static final String[] FIELD_NAMES = {
283         "era",
284         "year",
285         "quarter",
286         "month",
287         "week",
288         "week_of_month",
289         "weekday",
290         "day",
291         "day_of_year",
292         "day_of_week_in_month",
293         "dayperiod",
294         "hour",
295         "minute",
296         "second",
297         "fractional_second",
298         "zone"
299     };
300 
301     static {
302         if (FIELD_NAMES.length != DateTimePatternGenerator.TYPE_LIMIT) {
303             throw new IllegalArgumentException(
304                     "Internal error "
305                             + FIELD_NAMES.length
306                             + "\t"
307                             + DateTimePatternGenerator.TYPE_LIMIT);
308         }
309     }
310 
find(T[] array, T item)311     private <T> int find(T[] array, T item) {
312         for (int i = 0; i < array.length; ++i) {
313             if (array[i].equals(item)) {
314                 return i;
315             }
316         }
317         return 0;
318     }
319 
320     private static final String[][] NAME_AND_PATTERN = {
321         {"-", "Full Month"},
322         {"year month", "yMMMM"},
323         {" to  month+1", "yMMMM/M"},
324         {" to  year+1", "yMMMM/y"},
325         {"year month day", "yMMMMd"},
326         {" to  day+1", "yMMMMd/d"},
327         {" to  month+1", "yMMMMd/M"},
328         {" to  year+1", "yMMMMd/y"},
329         {"year month day weekday", "yMMMMEEEEd"},
330         {" to  day+1", "yMMMMEEEEd/d"},
331         {" to  month+1", "yMMMMEEEEd/M"},
332         {" to  year+1", "yMMMMEEEEd/y"},
333         {"month day", "MMMMd"},
334         {" to  day+1", "MMMMd/d"},
335         {" to  month+1", "MMMMd/M"},
336         {"month day weekday", "MMMMEEEEd"},
337         {" to  day+1", "MMMMEEEEd/d"},
338         {" to  month+1", "MMMMEEEEd/M"},
339         {"-", "Abbreviated Month"},
340         {"year month<sub>a</sub>", "yMMM"},
341         {" to  month+1", "yMMM/M"},
342         {" to  year+1", "yMMM/y"},
343         {"year month<sub>a</sub> day", "yMMMd"},
344         {" to  day+1", "yMMMd/d"},
345         {" to  month+1", "yMMMd/M"},
346         {" to  year+1", "yMMMd/y"},
347         {"year month<sub>a</sub> day weekday", "yMMMEd"},
348         {" to  day+1", "yMMMEd/d"},
349         {" to  month+1", "yMMMEd/M"},
350         {" to  year+1", "yMMMEd/y"},
351         {"month<sub>a</sub> day", "MMMd"},
352         {" to  day+1", "MMMd/d"},
353         {" to  month+1", "MMMd/M"},
354         {"month<sub>a</sub> day weekday", "MMMEd"},
355         {" to  day+1", "MMMEd/d"},
356         {" to  month+1", "MMMEd/M"},
357         {"-", "Numeric Month"},
358         {"year month<sub>n</sub>", "yM"},
359         {" to  month+1", "yM/M"},
360         {" to  year+1", "yM/y"},
361         {"year month<sub>n</sub> day", "yMd"},
362         {" to  day+1", "yMd/d"},
363         {" to  month+1", "yMd/M"},
364         {" to  year+1", "yMd/y"},
365         {"year month<sub>n</sub> day weekday", "yMEd"},
366         {" to  day+1", "yMEd/d"},
367         {" to  month+1", "yMEd/M"},
368         {" to  year+1", "yMEd/y"},
369         {"month<sub>n</sub> day", "Md"},
370         {" to  day+1", "Md/d"},
371         {" to  month+1", "Md/M"},
372         {"month<sub>n</sub> day weekday", "MEd"},
373         {" to  day+1", "MEd/d"},
374         {" to  month+1", "MEd/M"},
375         {"-", "Other Dates"},
376         {"year", "y"},
377         {" to  year+1", "y/y"},
378         {"year quarter", "yQQQQ"},
379         {"year quarter<sub>a</sub>", "yQQQ"},
380         {"quarter", "QQQQ"},
381         {"quarter<sub>a</sub>", "QQQ"},
382         {"month", "MMMM"},
383         {" to  month+1", "MMMM/M"},
384         {"month<sub>a</sub>", "MMM"},
385         {" to  month+1", "MMM/M"},
386         {"month<sub>n</sub>", "M"},
387         {" to  month+1", "M/M"},
388         {"day", "d"},
389         {" to  day+1", "d/d"},
390         {"day weekday", "Ed"},
391         {" to  day+1", "Ed/d"},
392         {"weekday", "EEEE"},
393         {" to  weekday+1", "EEEE/E"},
394         {"weekday<sub>a</sub>", "E"},
395         {" to  weekday+1", "E/E"},
396         {"-", "Times"},
397         {"hour", "j"},
398         {" to  hour+1", "j/j"},
399         {"hour minute", "jm"},
400         {" to  minute+1", "jm/m"},
401         {" to  hour+1", "jm/j"},
402         {"hour minute second", "jms"},
403         {"minute second", "ms"},
404         {"minute", "m"},
405         {"second", "s"},
406         {"-", TIMES_24H_TITLE},
407         {"hour<sub>24</sub>", "H"},
408         {" to  hour+1", "H/H"},
409         {"hour<sub>24</sub> minute", "Hm"},
410         {" to  minute+1", "Hm/m"},
411         {" to  hour+1", "Hm/H"},
412         {"hour<sub>24</sub> minute second", "Hms"},
413         {"-", "Dates and Times"},
414         {"month, day, hour, minute", "Mdjm"},
415         {"month, day, hour, minute", "MMMdjm"},
416         {"month, day, hour, minute", "MMMMdjm"},
417         {"year month, day, hour, minute", "yMdjms"},
418         {"year month, day, hour, minute", "yMMMdjms"},
419         {"year month, day, hour, minute", "yMMMMdjms"},
420         {"year month, day, hour, minute, zone", "yMMMMdjmsv"},
421         {"year month, day, hour, minute, zone (long)", "yMMMMdjmsvvvv"},
422         {"-", "Relative Dates"},
423         {"3 years ago", "®year-past-long-3"},
424         {"2 years ago", "®year-past-long-2"},
425         {"Last year", "®year-1"},
426         {"This year", "®year0"},
427         {"Next year", "®year1"},
428         {"2 years from now", "®year-future-long-2"},
429         {"3 years from now", "®year-future-long-3"},
430         {"3 months ago", "®month-past-long-3"},
431         {"Last month", "®month-1"},
432         {"This month", "®month0"},
433         {"Next month", "®month1"},
434         {"3 months from now", "®month-future-long-3"},
435         {"6 weeks ago", "®week-past-long-3"},
436         {"Last week", "®week-1"},
437         {"This week", "®week0"},
438         {"Next week", "®week1"},
439         {"6 weeks from now", "®week-future-long-3"},
440         {"Last Sunday", "®sun-1"},
441         {"This Sunday", "®sun0"},
442         {"Next Sunday", "®sun1"},
443         {"Last Sunday + time", "®sun-1jm"},
444         {"This Sunday + time", "®sun0jm"},
445         {"Next Sunday + time", "®sun1jm"},
446         {"3 days ago", "®day-past-long-3"},
447         {"Yesterday", "®day-1"},
448         {"This day", "®day0"},
449         {"Tomorrow", "®day1"},
450         {"3 days from now", "®day-future-long-3"},
451         {"3 days ago + time", "®day-past-long-3jm"},
452         {"Last day + time", "®day-1jm"},
453         {"This day + time", "®day0jm"},
454         {"Next day + time", "®day1jm"},
455         {"3 days from now + time", "®day-future-long-3jm"},
456     };
457 
458     private class Diff {
459         Set<String> availablePatterns = generator.getBaseSkeletons(new LinkedHashSet<String>());
460 
461         {
462             for (Entry<String, Set<String>> pat : dateIntervalInfo.getPatterns().entrySet()) {
463                 for (String patDiff : pat.getValue()) {
464                     availablePatterns.add(pat.getKey() + "/" + patDiff);
465                 }
466             }
467         }
468 
isPresent(String skeleton)469         public boolean isPresent(String skeleton) {
470             return availablePatterns.remove(
471                     skeleton.replace('j', generator.getDefaultHourFormatChar()));
472         }
473     }
474 
475     /**
476      * Generate a table of date examples.
477      *
478      * @param comparison
479      * @param output
480      */
addTable(DateTimeFormats comparison, Appendable output)481     public void addTable(DateTimeFormats comparison, Appendable output) {
482         try {
483             output.append(
484                     "<h2>" + hackDoubleLinked("Patterns") + "</h2>\n<table class='dtf-table'>");
485             Diff diff = new Diff();
486             boolean is24h = generator.getDefaultHourFormatChar() == 'H';
487             showRow(
488                     output,
489                     RowStyle.header,
490                     FIELDS_TITLE,
491                     "Skeleton",
492                     "English Example",
493                     "Native Example",
494                     false);
495             for (String[] nameAndSkeleton : NAME_AND_PATTERN) {
496                 String name = nameAndSkeleton[0];
497                 String skeleton = nameAndSkeleton[1];
498                 if (skeleton.equals(DEBUG_SKELETON)) {
499                     int debug = 0;
500                 }
501                 if (name.equals("-")) {
502                     if (is24h && skeleton.equals(TIMES_24H_TITLE)) {
503                         continue;
504                     }
505                     showRow(output, RowStyle.separator, skeleton, null, null, null, false);
506                 } else {
507                     if (is24h && skeleton.contains("H")) {
508                         continue;
509                     }
510                     showRow(
511                             output,
512                             RowStyle.normal,
513                             name,
514                             skeleton,
515                             comparison.getExample(skeleton),
516                             getExample(skeleton),
517                             diff.isPresent(skeleton));
518                 }
519             }
520             if (!diff.availablePatterns.isEmpty()) {
521                 showRow(
522                         output,
523                         RowStyle.separator,
524                         "Additional Patterns in Locale data",
525                         null,
526                         null,
527                         null,
528                         false);
529                 for (String skeleton : diff.availablePatterns) {
530                     if (skeleton.equals(DEBUG_SKELETON)) {
531                         int debug = 0;
532                     }
533                     if (is24h && (skeleton.contains("h") || skeleton.contains("a"))) {
534                         continue;
535                     }
536                     // skip zones, day_of_year, Day of Week in Month, numeric quarter, week in
537                     // month, week in year,
538                     // frac.sec
539                     if (skeleton.contains("v")
540                             || skeleton.contains("z")
541                             || skeleton.contains("Q") && !skeleton.contains("QQ")
542                             || skeleton.equals("D")
543                             || skeleton.equals("F")
544                             || skeleton.equals("S")
545                             || skeleton.equals("W")
546                             || skeleton.equals("w")) {
547                         continue;
548                     }
549                     showRow(
550                             output,
551                             RowStyle.normal,
552                             skeleton,
553                             skeleton,
554                             comparison.getExample(skeleton),
555                             getExample(skeleton),
556                             true);
557                 }
558             }
559             output.append("</table>");
560         } catch (IOException e) {
561             throw new ICUUncheckedIOException(e);
562         }
563     }
564 
565     /**
566      * Get an example from the "enhanced" skeleton.
567      *
568      * @param skeleton
569      * @return
570      */
getExample(String skeleton)571     private String getExample(String skeleton) {
572         String example;
573         if (skeleton.contains("®")) {
574             return getRelativeExampleFromSkeleton(skeleton);
575         } else {
576             int slashPos = skeleton.indexOf('/');
577             if (slashPos >= 0) {
578                 String mainSkeleton = skeleton.substring(0, slashPos);
579                 DateIntervalFormat dateIntervalFormat =
580                         new DateIntervalFormat(
581                                 mainSkeleton,
582                                 dateIntervalInfo,
583                                 icuServiceBuilder.getDateFormat(
584                                         calendarID, generator.getBestPattern(mainSkeleton)));
585                 String diffString = skeleton.substring(slashPos + 1).replace('j', 'H');
586                 //                int diffNumber = find(CALENDAR_FIELD_TO_PATTERN_LETTER,
587                 // diffString);
588                 Date endDate = SAMPLE_DATE_END.get(diffString);
589                 try {
590                     example =
591                             dateIntervalFormat.format(
592                                     new DateInterval(SAMPLE_DATE.getTime(), endDate.getTime()));
593                 } catch (Exception e) {
594                     throw new IllegalArgumentException(skeleton + ", " + endDate, e);
595                 }
596             } else {
597                 if (skeleton.equals(DEBUG_SKELETON)) {
598                     int debug = 0;
599                 }
600                 SimpleDateFormat format = getDateFormatFromSkeleton(skeleton);
601                 format.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
602                 example = format.format(SAMPLE_DATE);
603             }
604         }
605         return TransliteratorUtilities.toHTML.transform(example);
606     }
607 
608     static final Pattern RELATIVE_DATE =
609             PatternCache.get("®([a-z]+(?:-[a-z]+)?)+(-[a-z]+)?([+-]?\\d+)([a-zA-Z]+)?");
610 
611     class RelativePattern {
612         private static final String UNIT_PREFIX =
613                 "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"duration-";
614         final String type;
615         final int offset;
616         final String time;
617         final String path;
618         final String value;
619 
RelativePattern(CLDRFile file, String skeleton)620         public RelativePattern(CLDRFile file, String skeleton) {
621             Matcher m = RELATIVE_DATE.matcher(skeleton);
622             if (m.matches()) {
623                 type = m.group(1);
624                 String length = m.group(2);
625                 offset = Integer.parseInt(m.group(3));
626                 String temp = m.group(4);
627                 time =
628                         temp == null
629                                 ? null
630                                 : temp.replace('j', generator.getDefaultHourFormatChar());
631 
632                 if (-1 <= offset && offset <= 1) {
633                     // ldml/dates/fields/field[@type="year"]/relative[@type="-1"]
634                     path =
635                             "//ldml/dates/fields/field[@type=\""
636                                     + type
637                                     + "\"]/relative[@type=\""
638                                     + offset
639                                     + "\"]";
640                     value = file.getStringValue(path);
641                 } else {
642                     // //ldml/units/unit[@type="hour"]/unitPattern[@count="other"]
643                     PluralInfo plurals = sdi.getPlurals(file.getLocaleID());
644                     String base = UNIT_PREFIX + type + "\"]/unitPattern[@count=\"";
645                     String tempPath = base + plurals.getCount(offset) + "\"]";
646                     String tempValue = file.getStringValue(tempPath);
647                     if (tempValue == null) {
648                         tempPath = base + Count.other + "\"]";
649                         tempValue = file.getStringValue(tempPath);
650                     }
651                     path = tempPath;
652                     value = tempValue;
653                 }
654             } else {
655                 throw new IllegalArgumentException(skeleton);
656             }
657         }
658     }
659 
getRelativeExampleFromSkeleton(String skeleton)660     private String getRelativeExampleFromSkeleton(String skeleton) {
661         RelativePattern rp = new RelativePattern(file, skeleton);
662         String value = rp.value;
663         if (value == null) {
664             value = "ⓜⓘⓢⓢⓘⓝⓖ";
665         } else {
666             DecimalFormat format = icuServiceBuilder.getNumberFormat(0);
667             value = value.replace("{0}", format.format(Math.abs(rp.offset)).replace("'", "''"));
668         }
669         if (rp.time == null) {
670             return value;
671         } else {
672             SimpleDateFormat format2 = getDateFormatFromSkeleton(rp.time);
673             format2.setTimeZone(GMT);
674             String formattedTime = format2.format(SAMPLE_DATE);
675             //                String length = skeleton.contains("MMMM") ? skeleton.contains("E") ?
676             // "full" : "long"
677             //                    : skeleton.contains("MMM") ? "medium" : "short";
678             String path2 = getDTSeparator("full");
679             String datetimePattern = file.getStringValue(path2).replace("'", "");
680             return MessageFormat.format(datetimePattern, formattedTime, value);
681         }
682     }
683 
getDTSeparator(String length)684     private String getDTSeparator(String length) {
685         String path =
686                 "//ldml/dates/calendars/calendar[@type=\""
687                         + calendarID
688                         + "\"]/dateTimeFormats/dateTimeFormatLength[@type=\""
689                         + length
690                         + "\"]/dateTimeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]";
691         return path;
692     }
693 
getDateFormatFromSkeleton(String skeleton)694     public SimpleDateFormat getDateFormatFromSkeleton(String skeleton) {
695         String pattern = getBestPattern(skeleton);
696         return getDateFormat(pattern);
697     }
698 
getDateFormat(String pattern)699     private SimpleDateFormat getDateFormat(String pattern) {
700         SimpleDateFormat format = icuServiceBuilder.getDateFormat(calendarID, pattern);
701         format.setTimeZone(GMT);
702         return format;
703     }
704 
getBestPattern(String skeleton)705     public String getBestPattern(String skeleton) {
706         String pattern = generator.getBestPattern(skeleton);
707         return pattern;
708     }
709 
710     enum RowStyle {
711         header,
712         separator,
713         normal
714     }
715 
716     /**
717      * Show a single row
718      *
719      * @param output
720      * @param rowStyle
721      * @param name
722      * @param skeleton
723      * @param english
724      * @param example
725      * @param isPresent
726      * @throws IOException
727      */
showRow( Appendable output, RowStyle rowStyle, String name, String skeleton, String english, String example, boolean isPresent)728     private void showRow(
729             Appendable output,
730             RowStyle rowStyle,
731             String name,
732             String skeleton,
733             String english,
734             String example,
735             boolean isPresent)
736             throws IOException {
737         output.append("<tr>");
738         switch (rowStyle) {
739             case separator:
740                 String link = name.replace(' ', '_');
741                 output.append("<th colSpan='3' class='dtf-sep'>")
742                         .append(hackDoubleLinked(link, name))
743                         .append("</th>");
744                 break;
745             case header:
746             case normal:
747                 String startCell =
748                         rowStyle == RowStyle.header ? "<th class='dtf-h'>" : "<td class='dtf-s'>";
749                 String endCell = rowStyle == RowStyle.header ? "</th>" : "</td>";
750                 if (name.equals(FIELDS_TITLE)) {
751                     output.append("<th class='dtf-th'>").append(name).append("</a></th>");
752                 } else {
753                     String indent = "";
754                     if (name.startsWith(" ")) {
755                         indent = "&nbsp;&nbsp;&nbsp;";
756                         name = name.trim();
757                     }
758                     output.append(
759                             "<th class='dtf-left'>"
760                                     + indent
761                                     + hackDoubleLinked(skeleton, name)
762                                     + "</th>");
763                 }
764                 // .append(startCell).append(skeleton).append(endCell)
765                 output.append(startCell)
766                         .append(english)
767                         .append(endCell)
768                         .append(startCell)
769                         .append(example)
770                         .append(endCell)
771                 // .append(startCell).append(isPresent ? " " : "c").append(endCell)
772                 ;
773                 if (rowStyle != RowStyle.header) {
774                     String fix = getFix(skeleton);
775                     if (fix != null) {
776                         output.append(startCell).append(fix).append(endCell);
777                     }
778                 }
779         }
780         output.append("</tr>\n");
781     }
782 
getFix(String skeleton)783     private String getFix(String skeleton) {
784         String path;
785         String value;
786         if (skeleton.contains("®")) {
787             RelativePattern rp = new RelativePattern(file, skeleton);
788             path = rp.path;
789             value = rp.value;
790         } else {
791             skeleton = skeleton.replace('j', generator.getDefaultHourFormatChar());
792             int slashPos = skeleton.indexOf('/');
793             if (slashPos >= 0) {
794                 String mainSkeleton = skeleton.substring(0, slashPos);
795                 String diff = skeleton.substring(slashPos + 1);
796                 path =
797                         "//ldml/dates/calendars/calendar[@type=\""
798                                 + calendarID
799                                 + "\"]/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\""
800                                 + mainSkeleton
801                                 + "\"]/greatestDifference[@id=\""
802                                 + diff
803                                 + "\"]";
804             } else {
805                 path = getAvailableFormatPath(skeleton);
806             }
807             value = file.getStringValue(path);
808         }
809         if (value == null) {
810             String skeleton2 =
811                     skeleton.replace("MMMM", "MMM").replace("EEEE", "E").replace("QQQQ", "QQQ");
812             if (!skeleton.equals(skeleton2)) {
813                 return getFix(skeleton2);
814             }
815             if (DEBUG) {
816                 System.out.println("No pattern for " + skeleton + ", " + path);
817             }
818             return null;
819         }
820         return getFixFromPath(path);
821     }
822 
getAvailableFormatPath(String skeleton)823     private String getAvailableFormatPath(String skeleton) {
824         String path =
825                 "//ldml/dates/calendars/calendar[@type=\""
826                         + calendarID
827                         + "\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\""
828                         + skeleton
829                         + "\"]";
830         return path;
831     }
832 
getFixFromPath(String path)833     public String getFixFromPath(String path) {
834         String result = PathHeader.getLinkedView(surveyUrl, file, path);
835         return result == null ? "" : result;
836     }
837 
838     /**
839      * Add a table of date comparisons
840      *
841      * @param english
842      * @param output
843      */
addDateTable(CLDRFile english, Appendable output)844     public void addDateTable(CLDRFile english, Appendable output) {
845         // ldml/dates/calendars/calendar[@type="gregorian"]/months/monthContext[@type="format"]/monthWidth[@type="abbreviated"]/month[@type="1"]
846         // ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="stand-alone"]/quarterWidth[@type="wide"]/quarter[@type="1"]
847         // ldml/dates/calendars/calendar[@type="gregorian"]/days/dayContext[@type="stand-alone"]/dayWidth[@type="abbreviated"]/day[@type="sun"]
848         try {
849             output.append("<h2>" + hackDoubleLinked("Weekdays") + "</h2>\n");
850             addDateSubtable(
851                     "//ldml/dates/calendars/calendar[@type=\"CALENDAR\"]/days/dayContext[@type=\"FORMAT\"]/dayWidth[@type=\"WIDTH\"]/day[@type=\"TYPE\"]",
852                     english,
853                     output,
854                     "sun",
855                     "mon",
856                     "tue",
857                     "wed",
858                     "thu",
859                     "fri",
860                     "sat");
861             output.append("<h2>" + hackDoubleLinked("Months") + "</h2>\n");
862             addDateSubtable(
863                     "//ldml/dates/calendars/calendar[@type=\"CALENDAR\"]/months/monthContext[@type=\"FORMAT\"]/monthWidth[@type=\"WIDTH\"]/month[@type=\"TYPE\"]",
864                     english,
865                     output,
866                     "1",
867                     "2",
868                     "3",
869                     "4",
870                     "5",
871                     "6",
872                     "7",
873                     "8",
874                     "9",
875                     "10",
876                     "11",
877                     "12");
878             output.append("<h2>" + hackDoubleLinked("Quarters") + "</h2>\n");
879             addDateSubtable(
880                     "//ldml/dates/calendars/calendar[@type=\"CALENDAR\"]/quarters/quarterContext[@type=\"FORMAT\"]/quarterWidth[@type=\"WIDTH\"]/quarter[@type=\"TYPE\"]",
881                     english,
882                     output,
883                     "1",
884                     "2",
885                     "3",
886                     "4");
887             //            add24HourInfo();
888         } catch (IOException e) {
889             throw new ICUUncheckedIOException(e);
890         }
891     }
892 
893     //    private void add24HourInfo() {
894     //        PreferredAndAllowedHour timeInfo = timeData.get(locale);
895     //
896     //        for (String loc : fac)
897     //    }
898 
addDateSubtable(String path, CLDRFile english, Appendable output, String... types)899     private void addDateSubtable(String path, CLDRFile english, Appendable output, String... types)
900             throws IOException {
901         path = path.replace("CALENDAR", calendarID);
902         output.append(
903                 "<table class='dtf-table'>\n"
904                         + "<tr><th class='dtf-th'>English</th><th class='dtf-th'>Wide</th><th class='dtf-th'>Abbr.</th><th class='dtf-th'>Narrow</th></tr>"
905                         + "\n");
906         for (String type : types) {
907             String path1 = path.replace("TYPE", type);
908             output.append("<tr>");
909             boolean first = true;
910             for (String width : Arrays.asList("wide", "abbreviated", "narrow")) {
911                 String path2 = path1.replace("WIDTH", width);
912                 String last = null;
913                 String lastPath = null;
914                 for (String format : Arrays.asList("format", "stand-alone")) {
915                     String path3 = path2.replace("FORMAT", format);
916                     if (first) {
917                         String value = english.getStringValue(path3);
918                         output.append("<th class='dtf-left'>")
919                                 .append(TransliteratorUtilities.toHTML.transform(value))
920                                 .append("</th>");
921                         first = false;
922                     }
923                     String value = file.getStringValue(path3);
924                     if (last == null) {
925                         last = value;
926                         lastPath = path3;
927                     } else {
928                         String lastFix = getFixFromPath(lastPath);
929                         output.append("<td class='dtf-nopad'><table class='dtf-int'><tr><td>")
930                                 .append(TransliteratorUtilities.toHTML.transform(last));
931                         if (lastFix != null) {
932                             output.append("</td><td class='dtf-fix'>").append(lastFix);
933                         }
934                         if (!value.equals(last)) {
935                             String fix = getFixFromPath(path3);
936                             output.append("</td></tr><tr><td>")
937                                     .append(TransliteratorUtilities.toHTML.transform(value));
938                             if (fix != null) {
939                                 output.append("</td><td class='dtf-fix'>").append(fix);
940                             }
941                         }
942                         output.append("</td></tr></table></td>");
943                     }
944                 }
945             }
946             output.append("</tr>\n");
947         }
948         output.append("</table>\n");
949     }
950 
951     private static final boolean RETIRE = false;
952     private static final String LOCALES = ".*"; // "da|zh|de|ta";
953 
954     /**
955      * Produce a set of static tables from the vxml data. Only a stopgap until the above is
956      * integrated into ST.
957      *
958      * @param args
959      * @throws IOException
960      */
main(String[] args)961     public static void main(String[] args) throws IOException {
962         myOptions.parse(MyOptions.organization, args, true);
963 
964         String organization = MyOptions.organization.option.getValue();
965         String filter = MyOptions.filter.option.getValue();
966 
967         Factory englishFactory = Factory.make(CLDRPaths.MAIN_DIRECTORY, filter);
968         CLDRFile englishFile = englishFactory.make("en", true);
969 
970         Factory factory = Factory.make(CLDRPaths.MAIN_DIRECTORY, LOCALES);
971         System.out.println("Total locales: " + factory.getAvailableLanguages().size());
972         DateTimeFormats english = new DateTimeFormats().set(englishFile, "gregorian");
973 
974         new File(DIR).mkdirs();
975         FileCopier.copy(ShowData.class, "verify-index.html", CLDRPaths.VERIFY_DIR, "index.html");
976         FileCopier.copy(ChartDelta.class, "index.css", CLDRPaths.VERIFY_DIR, "index.css");
977         FormattedFileWriter.copyIncludeHtmls(CLDRPaths.VERIFY_DIR);
978         PrintWriter index = openIndex(DIR, "Date/Time");
979 
980         Map<String, String> sorted = new TreeMap<>();
981         SupplementalDataInfo sdi = SupplementalDataInfo.getInstance();
982         Set<String> defaultContent = sdi.getDefaultContentLocales();
983         for (String localeID : factory.getAvailableLanguages()) {
984             Level level = StandardCodes.make().getLocaleCoverageLevel(organization, localeID);
985             if (Level.MODERN.compareTo(level) > 0) {
986                 continue;
987             }
988             if (defaultContent.contains(localeID)) {
989                 System.out.println("Skipping default content: " + localeID);
990                 continue;
991             }
992             sorted.put(englishFile.getName(localeID, true), localeID);
993         }
994 
995         writeCss(DIR);
996         PrintWriter out;
997         // http://st.unicode.org/cldr-apps/survey?_=LOCALE&x=r_datetime&calendar=gregorian
998         int oldFirst = 0;
999         for (Entry<String, String> nameAndLocale : sorted.entrySet()) {
1000             String name = nameAndLocale.getKey();
1001             String localeID = nameAndLocale.getValue();
1002             DateTimeFormats formats =
1003                     new DateTimeFormats().set(factory.make(localeID, true), "gregorian");
1004             String filename = localeID + ".html";
1005             out = FileUtilities.openUTF8Writer(DIR, filename);
1006             String redirect =
1007                     "http://st.unicode.org/cldr-apps/survey?_="
1008                             + localeID
1009                             + "&x=r_datetime&calendar=gregorian";
1010             out.println(
1011                     "<!doctype HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'><html><head>\n"
1012                             + (RETIRE
1013                                     ? "<meta http-equiv='REFRESH' content='0;url="
1014                                             + redirect
1015                                             + "'>\n"
1016                                     : "")
1017                             + "<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>\n"
1018                             + "<title>Date/Time Charts: "
1019                             + name
1020                             + "</title>\n"
1021                             + "<link rel='stylesheet' type='text/css' href='index.css'>\n"
1022                             + "</head><body><h1>Date/Time Charts: "
1023                             + name
1024                             + "</h1>"
1025                             + "<p><a href='index.html'>Index</a></p>\n"
1026                             + "<p>The following chart shows typical usage of date and time formatting with the Gregorian calendar. "
1027                             + "<i>There is important information on <a target='CLDR_ST_DOCS' href='http://cldr.unicode.org/translation/date-time-review'>Date/Time Review</a>, "
1028                             + "so please read that page before starting!</i></p>\n");
1029             formats.addTable(english, out);
1030             formats.addDateTable(englishFile, out);
1031             formats.addDayPeriods(englishFile, out);
1032             out.println(
1033                     "<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>"
1034                             + "<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>");
1035             out.println("</body></html>");
1036             out.close();
1037             int first = name.codePointAt(0);
1038             if (oldFirst != first) {
1039                 index.append("<hr>");
1040                 oldFirst = first;
1041             } else {
1042                 index.append("  ");
1043             }
1044             index.append("<a href='").append(filename).append("'>").append(name).append("</a>\n");
1045             index.flush();
1046         }
1047         index.println("</div></body></html>");
1048         index.close();
1049     }
1050 
openIndex(String directory, String title)1051     public static PrintWriter openIndex(String directory, String title) throws IOException {
1052         String dateString = CldrUtility.isoFormatDateOnly(new Date());
1053         PrintWriter index = FileUtilities.openUTF8Writer(directory, "index.html");
1054         index.println(
1055                 "<!doctype HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'><html><head>\n"
1056                         + "<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>\n"
1057                         + "<title>"
1058                         + title
1059                         + " Charts</title>\n"
1060                         + "</head><body><h1>"
1061                         + title
1062                         + " Charts</h1>"
1063                         + "<p style='float:left; text-align:left'><a href='../index.html'>Index</a></p>\n"
1064                         +
1065                         // "<p style='float:left; text-align:left'><a
1066                         // href='index.html'>Index</a></p>\n" +
1067                         "<p style='float:right; text-align:right'>"
1068                         + dateString
1069                         + "</p>\n"
1070                         + "<div style='clear:both; margin:2em'>");
1071         return index;
1072     }
1073 
writeCss(String directory)1074     public static void writeCss(String directory) throws IOException {
1075         PrintWriter out = FileUtilities.openUTF8Writer(directory, "index.css");
1076         out.println(
1077                 ".dtf-table, .dtf-int {margin-left:auto; margin-right:auto; border-collapse:collapse;}\n"
1078                         + ".dtf-table, .dtf-s, .dtf-nopad, .dtf-fix, .dtf-th, .dtf-h, .dtf-sep, .dtf-left, .dtf-int {border:1px solid gray;}\n"
1079                         + ".dtf-th {background-color:#EEE; padding:4px}\n"
1080                         + ".dtf-s, .dtf-nopad, .dtf-fix {padding:3px; text-align:center}\n"
1081                         + ".dtf-sep {background-color:#EEF; text-align:center}\n"
1082                         + ".dtf-s {text-align:center;}\n"
1083                         + ".dtf-int {width:100%; height:100%}\n"
1084                         + ".dtf-fix {width:1px}\n"
1085                         + ".dtf-left {text-align:left;}\n"
1086                         + ".dtf-nopad {padding:0px; align:top}\n"
1087                         + ".dtf-gray {background-color:#EEF}\n");
1088         out.close();
1089     }
1090 
addDayPeriods(CLDRFile englishFile, Appendable output)1091     public void addDayPeriods(CLDRFile englishFile, Appendable output) {
1092         try {
1093             output.append("<h2>" + hackDoubleLinked("Day Periods") + "</h2>\n");
1094             output.append(
1095                     "<p>Please review these and correct if needed. The Wide fields are the most important. "
1096                             + "To correct them, go to "
1097                             + getFixFromPath(
1098                                     ICUServiceBuilder.getDayPeriodPath(
1099                                             DayPeriodInfo.DayPeriod.am, Context.format, Width.wide))
1100                             + " and following. "
1101                             + "<b>Note: </b>Day Periods can be a bit tricky; "
1102                             + "for more information, see <a target='CLDR-ST-DOCS' href='http://cldr.unicode.org/translation/date-time-names#TOC-Day-Periods-AM-and-PM-'>Day Periods</a>.</p>\n");
1103             output.append(
1104                     "<table class='dtf-table'>\n"
1105                             + "<tr>"
1106                             + "<th class='dtf-th' rowSpan='3'>DayPeriodID</th>"
1107                             + "<th class='dtf-th' rowSpan='3'>Time Span(s)</th>"
1108                             + "<th class='dtf-th' colSpan='4'>Format</th>"
1109                             + "<th class='dtf-th' colSpan='4'>Standalone</th>"
1110                             + "</tr>\n"
1111                             + "<tr>"
1112                             + "<th class='dtf-th' colSpan='2'>Wide</th>"
1113                             + "<th class='dtf-th'>Abbreviated</th>"
1114                             + "<th class='dtf-th'>Narrow</th>"
1115                             + "<th class='dtf-th' colSpan='2'>Wide</th>"
1116                             + "<th class='dtf-th'>Abbreviated</th>"
1117                             + "<th class='dtf-th'>Narrow</th>"
1118                             + "</tr>\n"
1119                             + "<tr>"
1120                             + "<th class='dtf-th'>English</th>"
1121                             + "<th class='dtf-th'>Native</th>"
1122                             + "<th class='dtf-th'>Native</th>"
1123                             + "<th class='dtf-th'>Native</th>"
1124                             + "<th class='dtf-th'>English</th>"
1125                             + "<th class='dtf-th'>Native</th>"
1126                             + "<th class='dtf-th'>Native</th>"
1127                             + "<th class='dtf-th'>Native</th>"
1128                             + "</tr>\n");
1129             DayPeriodInfo dayPeriodInfo =
1130                     sdi.getDayPeriods(DayPeriodInfo.Type.format, file.getLocaleID());
1131             Set<DayPeriodInfo.DayPeriod> dayPeriods =
1132                     new LinkedHashSet<>(dayPeriodInfo.getPeriods());
1133             DayPeriodInfo dayPeriodInfo2 = sdi.getDayPeriods(DayPeriodInfo.Type.format, "en");
1134             Set<DayPeriodInfo.DayPeriod> eDayPeriods = EnumSet.copyOf(dayPeriodInfo2.getPeriods());
1135             Output<Boolean> real = new Output<>();
1136             Output<Boolean> realEnglish = new Output<>();
1137 
1138             for (DayPeriodInfo.DayPeriod period : dayPeriods) {
1139                 R3<Integer, Integer, Boolean> first = dayPeriodInfo.getFirstDayPeriodInfo(period);
1140                 int midPoint = (first.get0() + first.get1()) / 2;
1141                 output.append("<tr>");
1142                 output.append("<th class='dtf-left'>")
1143                         .append(TransliteratorUtilities.toHTML.transform(period.toString()))
1144                         .append("</th>\n");
1145                 String periods = dayPeriodInfo.toString(period);
1146                 output.append("<th class='dtf-left'>")
1147                         .append(TransliteratorUtilities.toHTML.transform(periods))
1148                         .append("</th>\n");
1149                 for (Context context : Context.values()) {
1150                     for (Width width : Width.values()) {
1151                         final String dayPeriodPath =
1152                                 ICUServiceBuilder.getDayPeriodPath(period, context, width);
1153                         if (width == Width.wide) {
1154                             String englishValue;
1155                             if (context == Context.format) {
1156                                 englishValue =
1157                                         icuServiceBuilderEnglish.formatDayPeriod(
1158                                                 midPoint, context, width);
1159                                 realEnglish.value = true;
1160                             } else {
1161                                 englishValue =
1162                                         icuServiceBuilderEnglish.getDayPeriodValue(
1163                                                 dayPeriodPath, null, realEnglish);
1164                             }
1165                             output.append(
1166                                             "<th class='dtf-left"
1167                                                     + (realEnglish.value ? "" : " dtf-gray")
1168                                                     + "'"
1169                                                     + ">")
1170                                     .append(getCleanValue(englishValue, width, "<i>unused</i>"))
1171                                     .append("</th>\n");
1172                         }
1173                         String nativeValue =
1174                                 icuServiceBuilder.getDayPeriodValue(dayPeriodPath, "�", real);
1175                         if (context == Context.format) {
1176                             nativeValue = icuServiceBuilder.formatDayPeriod(midPoint, nativeValue);
1177                         }
1178                         output.append(
1179                                         "<td class='dtf-left"
1180                                                 + (real.value ? "" : " dtf-gray")
1181                                                 + "'>")
1182                                 .append(getCleanValue(nativeValue, width, "<i>missing</i>"))
1183                                 .append("</td>\n");
1184                     }
1185                 }
1186                 output.append("</tr>\n");
1187             }
1188             output.append("</table>\n");
1189         } catch (IOException e) {
1190             throw new ICUUncheckedIOException(e);
1191         }
1192     }
1193 
getCleanValue(String evalue, Width width, String fallback)1194     private String getCleanValue(String evalue, Width width, String fallback) {
1195         String replacement = width == Width.wide ? fallback : "<i>optional</i>";
1196         String qevalue =
1197                 evalue != null ? TransliteratorUtilities.toHTML.transform(evalue) : replacement;
1198         return qevalue.replace("�", replacement);
1199     }
1200 
1201     //    static final String SHORT_PATH =
1202     // "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/timeFormats/timeFormatLength[@type=\"short\"]/timeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]";
1203     //    static final String HM_PATH =
1204     // "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"hm\"]";
1205     //
1206     //    private String format(CLDRFile file, String evalue, int timeInDay) {
1207     //        String pattern = file.getStringValue(HM_PATH);
1208     //        if (pattern == null) {
1209     //            pattern = "h:mm \uE000";
1210     //        } else {
1211     //            pattern = pattern.replace('a', '\uE000');
1212     //        }
1213     //        SimpleDateFormat df = icuServiceBuilder.getDateFormat("gregorian", pattern);
1214     //        String formatted = df.format(timeInDay);
1215     //        String result = formatted.replace("\uE000", evalue);
1216     //        return result;
1217     //    }
1218 
hackDoubleLinked(String link, String name)1219     private String hackDoubleLinked(String link, String name) {
1220         return name;
1221     }
1222 
hackDoubleLinked(String string)1223     private String hackDoubleLinked(String string) {
1224         return string;
1225     }
1226 
writeIndexMap(Map<String, String> nameToFile, PrintWriter index)1227     static void writeIndexMap(Map<String, String> nameToFile, PrintWriter index) {
1228         int oldFirst = 0;
1229         for (Entry<String, String> entry : nameToFile.entrySet()) {
1230             String name = entry.getKey();
1231             String file = entry.getValue();
1232             int first = name.codePointAt(0);
1233             if (oldFirst != first) {
1234                 index.append("<hr>");
1235                 oldFirst = first;
1236             } else {
1237                 index.append("  ");
1238             }
1239             index.append("<a href='").append(file).append("'>").append(name).append("</a>\n");
1240             index.flush();
1241         }
1242         index.println("</div></body></html>");
1243     }
1244 }
1245