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