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 = " "; 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