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