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