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