blob: cfcb3d28fecc62277c76627d5e1b6c13f8c66fff [file] [log] [blame]
package org.unicode.cldr.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Date;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.unicode.cldr.draft.FileUtilities;
import org.unicode.cldr.tool.Option;
import org.unicode.cldr.tool.Option.Options;
import org.unicode.cldr.util.ICUServiceBuilder.Context;
import org.unicode.cldr.util.ICUServiceBuilder.Width;
import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo;
import org.unicode.cldr.util.SupplementalDataInfo.PluralInfo.Count;
import com.ibm.icu.impl.Row.R3;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.DateIntervalFormat;
import com.ibm.icu.text.DateIntervalInfo;
import com.ibm.icu.text.DateTimePatternGenerator;
import com.ibm.icu.text.DateTimePatternGenerator.FormatParser;
import com.ibm.icu.text.DateTimePatternGenerator.PatternInfo;
import com.ibm.icu.text.DateTimePatternGenerator.VariableField;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.DateInterval;
import com.ibm.icu.util.ICUUncheckedIOException;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
public class DateTimeFormats {
private static final String DIR = CLDRPaths.CHART_DIRECTORY + "/verify/dates/";
private static SupplementalDataInfo sdi = SupplementalDataInfo.getInstance();
private static Map<String, PreferredAndAllowedHour> timeData = sdi.getTimeData();
final static Options myOptions = new Options();
enum MyOptions {
organization(".*", "CLDR", "organization"),
filter(".*", ".*", "locale filter (regex)");
// boilerplate
final Option option;
MyOptions(String argumentPattern, String defaultArgument, String helpText) {
option = myOptions.add(this, argumentPattern, defaultArgument, helpText);
}
}
private static final String TIMES_24H_TITLE = "Times 24h";
private static final boolean DEBUG = false;
private static final String DEBUG_SKELETON = "y";
private static final ULocale DEBUG_LIST_PATTERNS = ULocale.JAPANESE; // or null;
private static final String FIELDS_TITLE = "Fields";
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
private static final String[] STOCK = { "short", "medium", "long", "full" };
private static final String[] CALENDAR_FIELD_TO_PATTERN_LETTER =
{
"G", "y", "M",
"w", "W", "d",
"D", "E", "F",
"a", "h", "H",
"m",
};
private static final Date SAMPLE_DATE = new Date(2012 - 1900, 0, 13, 14, 45, 59);
private static final String SAMPLE_DATE_STRING = CldrUtility.isoFormat(SAMPLE_DATE);
private static final Date[] SAMPLE_DATE_END = {
// "G", "y", "M",
null, new Date(2013 - 1900, 0, 13, 14, 45, 59), new Date(2012 - 1900, 1, 13, 14, 45, 59),
// "w", "W", "d",
null, null, new Date(2012 - 1900, 0, 14, 14, 45, 59),
// "D", "E", "F",
null, new Date(2012 - 1900, 0, 14, 14, 45, 59), null,
// "a", "h", "H",
new Date(2012 - 1900, 0, 13, 2, 45, 59), new Date(2012 - 1900, 0, 13, 15, 45, 59),
new Date(2012 - 1900, 0, 13, 15, 45, 59),
// "m",
new Date(2012 - 1900, 0, 13, 14, 46, 59)
};;
private DateTimePatternGenerator generator;
private ULocale locale;
private ICUServiceBuilder icuServiceBuilder;
private ICUServiceBuilder icuServiceBuilderEnglish = new ICUServiceBuilder().setCldrFile(CLDRConfig.getInstance().getEnglish());
private DateIntervalInfo dateIntervalInfo = new DateIntervalInfo();
private String calendarID;
private CLDRFile file;
private static String surveyUrl = CLDRConfig.getInstance().getProperty("CLDR_SURVEY_URL",
"http://st.unicode.org/cldr-apps/survey");
/**
* Set a CLDRFile and calendar. Must be done before calling addTable.
*
* @param file
* @param calendarID
* @return
*/
public DateTimeFormats set(CLDRFile file, String calendarID) {
return set(file, calendarID, true);
}
/**
* Set a CLDRFile and calendar. Must be done before calling addTable.
*
* @param file
* @param calendarID
* @return
*/
public DateTimeFormats set(CLDRFile file, String calendarID, boolean useStock) {
this.file = file;
locale = new ULocale(file.getLocaleID());
if (useStock) {
icuServiceBuilder = new ICUServiceBuilder().setCldrFile(file);
}
PatternInfo returnInfo = new PatternInfo();
XPathParts parts = new XPathParts();
generator = DateTimePatternGenerator.getEmptyInstance();
this.calendarID = calendarID;
boolean haveDefaultHourChar = false;
for (String stock : STOCK) {
String path = "//ldml/dates/calendars/calendar[@type=\"" + calendarID
+ "\"]/dateFormats/dateFormatLength[@type=\"" +
stock +
"\"]/dateFormat[@type=\"standard\"]/pattern[@type=\"standard\"]";
String dateTimePattern = file.getStringValue(path);
if (useStock) {
generator.addPattern(dateTimePattern, true, returnInfo);
}
path = "//ldml/dates/calendars/calendar[@type=\"" + calendarID
+ "\"]/timeFormats/timeFormatLength[@type=\"" +
stock +
"\"]/timeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]";
dateTimePattern = file.getStringValue(path);
if (useStock) {
generator.addPattern(dateTimePattern, true, returnInfo);
}
if (DEBUG
&& DEBUG_LIST_PATTERNS.equals(locale)) {
System.out.println("* Adding: " + locale + "\t" + dateTimePattern);
}
if (!haveDefaultHourChar) {
// use hour style in SHORT time pattern as the default
// hour style for the locale
FormatParser fp = new FormatParser();
fp.set(dateTimePattern);
List<Object> items = fp.getItems();
for (int idx = 0; idx < items.size(); idx++) {
Object item = items.get(idx);
if (item instanceof VariableField) {
VariableField fld = (VariableField) item;
if (fld.getType() == DateTimePatternGenerator.HOUR) {
generator.setDefaultHourFormatChar(fld.toString().charAt(0));
haveDefaultHourChar = true;
break;
}
}
}
}
}
// appendItems result.setAppendItemFormat(getAppendFormatNumber(formatName), value);
for (String path : With.in(file.iterator("//ldml/dates/calendars/calendar[@type=\"" + calendarID
+ "\"]/dateTimeFormats/appendItems/appendItem"))) {
String request = parts.set(path).getAttributeValue(-1, "request");
int requestNumber = DateTimePatternGenerator.getAppendFormatNumber(request);
String value = file.getStringValue(path);
generator.setAppendItemFormat(requestNumber, value);
if (DEBUG
&& DEBUG_LIST_PATTERNS.equals(locale)) {
System.out.println("* Adding: " + locale + "\t" + request + "\t" + value);
}
}
// field names result.setAppendItemName(i, value);
// ldml/dates/fields/field[@type="day"]/displayName
for (String path : With.in(file.iterator("//ldml/dates/fields/field"))) {
if (!path.contains("displayName")) {
continue;
}
String type = parts.set(path).getAttributeValue(-2, "type");
int requestNumber = find(FIELD_NAMES, type);
String value = file.getStringValue(path);
generator.setAppendItemName(requestNumber, value);
if (DEBUG
&& DEBUG_LIST_PATTERNS.equals(locale)) {
System.out.println("* Adding: " + locale + "\t" + type + "\t" + value);
}
}
for (String path : With.in(file.iterator("//ldml/dates/calendars/calendar[@type=\"" + calendarID
+ "\"]/dateTimeFormats/availableFormats/dateFormatItem"))) {
String key = parts.set(path).getAttributeValue(-1, "id");
String value = file.getStringValue(path);
if (key.equals(DEBUG_SKELETON)) {
int debug = 0;
}
generator.addPatternWithSkeleton(value, key, true, returnInfo);
if (DEBUG
&& DEBUG_LIST_PATTERNS.equals(locale)) {
System.out.println("* Adding: " + locale + "\t" + key + "\t" + value);
}
}
generator
.setDateTimeFormat(Calendar.getDateTimePattern(Calendar.getInstance(locale), locale, DateFormat.MEDIUM));
// ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\"yMMMEd\"]/greatestDifference[@id=\"d\"]
for (String path : With.in(file.iterator("//ldml/dates/calendars/calendar[@type=\"" + calendarID
+ "\"]/dateTimeFormats/intervalFormats/intervalFormatItem"))) {
String skeleton = parts.set(path).getAttributeValue(-2, "id");
String diff = parts.set(path).getAttributeValue(-1, "id");
int diffNumber = find(CALENDAR_FIELD_TO_PATTERN_LETTER, diff);
String intervalPattern = file.getStringValue(path);
dateIntervalInfo.setIntervalPattern(skeleton, diffNumber, intervalPattern);
}
if (useStock) {
dateIntervalInfo.setFallbackIntervalPattern(
file.getStringValue("//ldml/dates/calendars/calendar[@type=\""
+ calendarID + "\"]/dateTimeFormats/intervalFormats/intervalFormatFallback"));
}
return this;
}
private static final String[] FIELD_NAMES = {
"era", "year", "quarter", "month", "week", "week_of_month",
"weekday", "day", "day_of_year", "day_of_week_in_month",
"dayperiod", "hour", "minute", "second", "fractional_second", "zone"
};
static {
if (FIELD_NAMES.length != DateTimePatternGenerator.TYPE_LIMIT) {
throw new IllegalArgumentException("Internal error " + FIELD_NAMES.length + "\t"
+ DateTimePatternGenerator.TYPE_LIMIT);
}
}
private <T> int find(T[] array, T item) {
for (int i = 0; i < array.length; ++i) {
if (array[i].equals(item)) {
return i;
}
}
return 0;
}
private static final String[][] NAME_AND_PATTERN = {
{ "-", "Full Month" },
{ "year month", "yMMMM" },
{ " to month+1", "yMMMM/M" },
{ " to year+1", "yMMMM/y" },
{ "year month day", "yMMMMd" },
{ " to day+1", "yMMMMd/d" },
{ " to month+1", "yMMMMd/M" },
{ " to year+1", "yMMMMd/y" },
{ "year month day weekday", "yMMMMEEEEd" },
{ " to day+1", "yMMMMEEEEd/d" },
{ " to month+1", "yMMMMEEEEd/M" },
{ " to year+1", "yMMMMEEEEd/y" },
{ "month day", "MMMMd" },
{ " to day+1", "MMMMd/d" },
{ " to month+1", "MMMMd/M" },
{ "month day weekday", "MMMMEEEEd" },
{ " to day+1", "MMMMEEEEd/d" },
{ " to month+1", "MMMMEEEEd/M" },
{ "-", "Abbreviated Month" },
{ "year month<sub>a</sub>", "yMMM" },
{ " to month+1", "yMMM/M" },
{ " to year+1", "yMMM/y" },
{ "year month<sub>a</sub> day", "yMMMd" },
{ " to day+1", "yMMMd/d" },
{ " to month+1", "yMMMd/M" },
{ " to year+1", "yMMMd/y" },
{ "year month<sub>a</sub> day weekday", "yMMMEd" },
{ " to day+1", "yMMMEd/d" },
{ " to month+1", "yMMMEd/M" },
{ " to year+1", "yMMMEd/y" },
{ "month<sub>a</sub> day", "MMMd" },
{ " to day+1", "MMMd/d" },
{ " to month+1", "MMMd/M" },
{ "month<sub>a</sub> day weekday", "MMMEd" },
{ " to day+1", "MMMEd/d" },
{ " to month+1", "MMMEd/M" },
{ "-", "Numeric Month" },
{ "year month<sub>n</sub>", "yM" },
{ " to month+1", "yM/M" },
{ " to year+1", "yM/y" },
{ "year month<sub>n</sub> day", "yMd" },
{ " to day+1", "yMd/d" },
{ " to month+1", "yMd/M" },
{ " to year+1", "yMd/y" },
{ "year month<sub>n</sub> day weekday", "yMEd" },
{ " to day+1", "yMEd/d" },
{ " to month+1", "yMEd/M" },
{ " to year+1", "yMEd/y" },
{ "month<sub>n</sub> day", "Md" },
{ " to day+1", "Md/d" },
{ " to month+1", "Md/M" },
{ "month<sub>n</sub> day weekday", "MEd" },
{ " to day+1", "MEd/d" },
{ " to month+1", "MEd/M" },
{ "-", "Other Dates" },
{ "year", "y" },
{ " to year+1", "y/y" },
{ "year quarter", "yQQQQ" },
{ "year quarter<sub>a</sub>", "yQQQ" },
{ "quarter", "QQQQ" },
{ "quarter<sub>a</sub>", "QQQ" },
{ "month", "MMMM" },
{ " to month+1", "MMMM/M" },
{ "month<sub>a</sub>", "MMM" },
{ " to month+1", "MMM/M" },
{ "month<sub>n</sub>", "M" },
{ " to month+1", "M/M" },
{ "day", "d" },
{ " to day+1", "d/d" },
{ "day weekday", "Ed" },
{ " to day+1", "Ed/d" },
{ "weekday", "EEEE" },
{ " to weekday+1", "EEEE/E" },
{ "weekday<sub>a</sub>", "E" },
{ " to weekday+1", "E/E" },
{ "-", "Times" },
{ "hour", "j" },
{ " to hour+1", "j/j" },
{ "hour minute", "jm" },
{ " to minute+1", "jm/m" },
{ " to hour+1", "jm/j" },
{ "hour minute second", "jms" },
{ "minute second", "ms" },
{ "minute", "m" },
{ "second", "s" },
{ "-", TIMES_24H_TITLE },
{ "hour<sub>24</sub>", "H" },
{ " to hour+1", "H/H" },
{ "hour<sub>24</sub> minute", "Hm" },
{ " to minute+1", "Hm/m" },
{ " to hour+1", "Hm/H" },
{ "hour<sub>24</sub> minute second", "Hms" },
{ "-", "Dates and Times" },
{ "month, day, hour, minute", "Mdjm" },
{ "month, day, hour, minute", "MMMdjm" },
{ "month, day, hour, minute", "MMMMdjm" },
{ "year month, day, hour, minute", "yMdjms" },
{ "year month, day, hour, minute", "yMMMdjms" },
{ "year month, day, hour, minute", "yMMMMdjms" },
{ "year month, day, hour, minute, zone", "yMMMMdjmsv" },
{ "year month, day, hour, minute, zone (long)", "yMMMMdjmsvvvv" },
{ "-", "Relative Dates" },
{ "3 years ago", "®year-past-long-3" },
{ "2 years ago", "®year-past-long-2" },
{ "Last year", "®year-1" },
{ "This year", "®year0" },
{ "Next year", "®year1" },
{ "2 years from now", "®year-future-long-2" },
{ "3 years from now", "®year-future-long-3" },
{ "3 months ago", "®month-past-long-3" },
{ "Last month", "®month-1" },
{ "This month", "®month0" },
{ "Next month", "®month1" },
{ "3 months from now", "®month-future-long-3" },
{ "6 weeks ago", "®week-past-long-3" },
{ "Last week", "®week-1" },
{ "This week", "®week0" },
{ "Next week", "®week1" },
{ "6 weeks from now", "®week-future-long-3" },
{ "Last Sunday", "®sun-1" },
{ "This Sunday", "®sun0" },
{ "Next Sunday", "®sun1" },
{ "Last Sunday + time", "®sun-1jm" },
{ "This Sunday + time", "®sun0jm" },
{ "Next Sunday + time", "®sun1jm" },
{ "3 days ago", "®day-past-long-3" },
{ "Yesterday", "®day-1" },
{ "This day", "®day0" },
{ "Tomorrow", "®day1" },
{ "3 days from now", "®day-future-long-3" },
{ "3 days ago + time", "®day-past-long-3jm" },
{ "Last day + time", "®day-1jm" },
{ "This day + time", "®day0jm" },
{ "Next day + time", "®day1jm" },
{ "3 days from now + time", "®day-future-long-3jm" },
};
private class Diff {
Set<String> availablePatterns = generator.getBaseSkeletons(new LinkedHashSet<String>());
{
for (Entry<String, Set<String>> pat : dateIntervalInfo.getPatterns().entrySet()) {
for (String patDiff : pat.getValue()) {
availablePatterns.add(pat.getKey() + "/" + patDiff);
}
}
}
public boolean isPresent(String skeleton) {
return availablePatterns.remove(skeleton.replace('j', generator.getDefaultHourFormatChar()));
}
}
/**
* Generate a table of date examples.
*
* @param comparison
* @param output
*/
public void addTable(DateTimeFormats comparison, Appendable output) {
try {
output.append("<h2>" + hackDoubleLinked("Patterns") + "</h2>\n<table class='dtf-table'>");
Diff diff = new Diff();
boolean is24h = generator.getDefaultHourFormatChar() == 'H';
showRow(output, RowStyle.header, FIELDS_TITLE, "Skeleton", "English Example", "Native Example", false);
for (String[] nameAndSkeleton : NAME_AND_PATTERN) {
String name = nameAndSkeleton[0];
String skeleton = nameAndSkeleton[1];
if (skeleton.equals(DEBUG_SKELETON)) {
int debug = 0;
}
if (name.equals("-")) {
if (is24h && skeleton.equals(TIMES_24H_TITLE)) {
continue;
}
showRow(output, RowStyle.separator, skeleton, null, null, null, false);
} else {
if (is24h && skeleton.contains("H")) {
continue;
}
showRow(output, RowStyle.normal, name, skeleton, comparison.getExample(skeleton), getExample(skeleton), diff.isPresent(skeleton));
}
}
if (!diff.availablePatterns.isEmpty()) {
showRow(output, RowStyle.separator, "Additional Patterns in Locale data", null, null, null, false);
for (String skeleton : diff.availablePatterns) {
if (skeleton.equals(DEBUG_SKELETON)) {
int debug = 0;
}
if (is24h && (skeleton.contains("h") || skeleton.contains("a"))) {
continue;
}
// skip zones, day_of_year, Day of Week in Month, numeric quarter, week in month, week in year,
// frac.sec
if (skeleton.contains("v") || skeleton.contains("z")
|| skeleton.contains("Q") && !skeleton.contains("QQ")
|| skeleton.equals("D") || skeleton.equals("F")
|| skeleton.equals("S")
|| skeleton.equals("W") || skeleton.equals("w")) {
continue;
}
showRow(output, RowStyle.normal, skeleton, skeleton, comparison.getExample(skeleton), getExample(skeleton), true);
}
}
output.append("</table>");
} catch (IOException e) {
throw new ICUUncheckedIOException(e);
}
}
/**
* Get an example from the "enhanced" skeleton.
*
* @param skeleton
* @return
*/
private String getExample(String skeleton) {
String example;
if (skeleton.contains("®")) {
return getRelativeExampleFromSkeleton(skeleton);
} else {
int slashPos = skeleton.indexOf('/');
if (slashPos >= 0) {
String mainSkeleton = skeleton.substring(0, slashPos);
DateIntervalFormat dateIntervalFormat = new DateIntervalFormat(mainSkeleton, dateIntervalInfo,
icuServiceBuilder.getDateFormat(calendarID, generator.getBestPattern(mainSkeleton)));
String diffString = skeleton.substring(slashPos + 1).replace('j', 'H');
int diffNumber = find(CALENDAR_FIELD_TO_PATTERN_LETTER, diffString);
Date endDate = SAMPLE_DATE_END[diffNumber];
try {
example = dateIntervalFormat.format(new DateInterval(SAMPLE_DATE.getTime(), endDate.getTime()));
} catch (Exception e) {
throw new IllegalArgumentException(skeleton + ", " + endDate, e);
}
} else {
if (skeleton.equals(DEBUG_SKELETON)) {
int debug = 0;
}
SimpleDateFormat format = getDateFormatFromSkeleton(skeleton);
format.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
example = format.format(SAMPLE_DATE);
}
}
return TransliteratorUtilities.toHTML.transform(example);
}
static final Pattern RELATIVE_DATE = PatternCache.get("®([a-z]+(?:-[a-z]+)?)+(-[a-z]+)?([+-]?\\d+)([a-zA-Z]+)?");
class RelativePattern {
private static final String UNIT_PREFIX = "//ldml/units/unitLength[@type=\"long\"]/unit[@type=\"duration-";
final String type;
final int offset;
final String time;
final String path;
final String value;
public RelativePattern(CLDRFile file, String skeleton) {
Matcher m = RELATIVE_DATE.matcher(skeleton);
if (m.matches()) {
type = m.group(1);
String length = m.group(2);
offset = Integer.parseInt(m.group(3));
String temp = m.group(4);
time = temp == null ? null : temp.replace('j', generator.getDefaultHourFormatChar());
if (-1 <= offset && offset <= 1) {
//ldml/dates/fields/field[@type="year"]/relative[@type="-1"]
path = "//ldml/dates/fields/field[@type=\"" + type + "\"]/relative[@type=\"" + offset + "\"]";
value = file.getStringValue(path);
} else {
// //ldml/units/unit[@type="hour"]/unitPattern[@count="other"]
PluralInfo plurals = sdi.getPlurals(file.getLocaleID());
String base = UNIT_PREFIX + type + "\"]/unitPattern[@count=\"";
String tempPath = base + plurals.getCount(offset) + "\"]";
String tempValue = file.getStringValue(tempPath);
if (tempValue == null) {
tempPath = base + Count.other + "\"]";
tempValue = file.getStringValue(tempPath);
}
path = tempPath;
value = tempValue;
}
} else {
throw new IllegalArgumentException(skeleton);
}
}
}
private String getRelativeExampleFromSkeleton(String skeleton) {
RelativePattern rp = new RelativePattern(file, skeleton);
String value = rp.value;
if (value == null) {
value = "ⓜⓘⓢⓢⓘⓝⓖ";
} else {
DecimalFormat format = icuServiceBuilder.getNumberFormat(0);
value = value.replace("{0}", format.format(Math.abs(rp.offset)).replace("'", "''"));
}
if (rp.time == null) {
return value;
} else {
SimpleDateFormat format2 = getDateFormatFromSkeleton(rp.time);
format2.setTimeZone(GMT);
String formattedTime = format2.format(SAMPLE_DATE);
// String length = skeleton.contains("MMMM") ? skeleton.contains("E") ? "full" : "long"
// : skeleton.contains("MMM") ? "medium" : "short";
String path2 = getDTSeparator("full");
String datetimePattern = file.getStringValue(path2).replace("'", "");
return MessageFormat.format(datetimePattern, formattedTime, value);
}
}
private String getDTSeparator(String length) {
String path = "//ldml/dates/calendars/calendar[@type=\"" +
calendarID +
"\"]/dateTimeFormats/dateTimeFormatLength[@type=\"" +
length +
"\"]/dateTimeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]";
return path;
}
public SimpleDateFormat getDateFormatFromSkeleton(String skeleton) {
String pattern = getBestPattern(skeleton);
return getDateFormat(pattern);
}
private SimpleDateFormat getDateFormat(String pattern) {
SimpleDateFormat format = icuServiceBuilder.getDateFormat(calendarID, pattern);
format.setTimeZone(GMT);
return format;
}
public String getBestPattern(String skeleton) {
String pattern = generator.getBestPattern(skeleton);
return pattern;
}
enum RowStyle {
header, separator, normal
}
/**
* Show a single row
*
* @param output
* @param rowStyle
* @param name
* @param skeleton
* @param english
* @param example
* @param isPresent
* @throws IOException
*/
private void showRow(Appendable output, RowStyle rowStyle, String name, String skeleton, String english,
String example, boolean isPresent)
throws IOException {
output.append("<tr>");
switch (rowStyle) {
case separator:
String link = name.replace(' ', '_');
output.append("<th colSpan='3' class='dtf-sep'>")
.append(hackDoubleLinked(link, name))
.append("</th>");
break;
case header:
case normal:
String startCell = rowStyle == RowStyle.header ? "<th class='dtf-h'>" : "<td class='dtf-s'>";
String endCell = rowStyle == RowStyle.header ? "</th>" : "</td>";
if (name.equals(FIELDS_TITLE)) {
output.append("<th class='dtf-th'>").append(name).append("</a></th>");
} else {
String indent = "";
if (name.startsWith(" ")) {
indent = "&nbsp;&nbsp;&nbsp;";
name = name.trim();
}
output.append("<th class='dtf-left'>" + indent + hackDoubleLinked(skeleton, name) + "</th>");
}
// .append(startCell).append(skeleton).append(endCell)
output.append(startCell).append(english).append(endCell)
.append(startCell).append(example).append(endCell)
//.append(startCell).append(isPresent ? " " : "c").append(endCell)
;
if (rowStyle != RowStyle.header) {
String fix = getFix(skeleton);
if (fix != null) {
output.append(startCell).append(fix).append(endCell);
}
}
}
output.append("</tr>\n");
}
private String getFix(String skeleton) {
String path;
String value;
if (skeleton.contains("®")) {
RelativePattern rp = new RelativePattern(file, skeleton);
path = rp.path;
value = rp.value;
} else {
skeleton = skeleton.replace('j', generator.getDefaultHourFormatChar());
int slashPos = skeleton.indexOf('/');
if (slashPos >= 0) {
String mainSkeleton = skeleton.substring(0, slashPos);
String diff = skeleton.substring(slashPos + 1);
path = "//ldml/dates/calendars/calendar[@type=\"" + calendarID +
"\"]/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\"" + mainSkeleton +
"\"]/greatestDifference[@id=\"" + diff +
"\"]";
} else {
path = getAvailableFormatPath(skeleton);
}
value = file.getStringValue(path);
}
if (value == null) {
String skeleton2 = skeleton.replace("MMMM", "MMM").replace("EEEE", "E").replace("QQQQ", "QQQ");
if (!skeleton.equals(skeleton2)) {
return getFix(skeleton2);
}
if (DEBUG) {
System.out.println("No pattern for " + skeleton + ", " + path);
}
return null;
}
return getFixFromPath(path);
}
private String getAvailableFormatPath(String skeleton) {
String path = "//ldml/dates/calendars/calendar[@type=\"" + calendarID +
"\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"" + skeleton +
"\"]";
return path;
}
public String getFixFromPath(String path) {
String result = PathHeader.getLinkedView(surveyUrl, file, path);
return result == null ? "" : result;
}
/**
* Add a table of date comparisons
*
* @param english
* @param output
*/
public void addDateTable(CLDRFile english, Appendable output) {
// ldml/dates/calendars/calendar[@type="gregorian"]/months/monthContext[@type="format"]/monthWidth[@type="abbreviated"]/month[@type="1"]
// ldml/dates/calendars/calendar[@type="gregorian"]/quarters/quarterContext[@type="stand-alone"]/quarterWidth[@type="wide"]/quarter[@type="1"]
// ldml/dates/calendars/calendar[@type="gregorian"]/days/dayContext[@type="stand-alone"]/dayWidth[@type="abbreviated"]/day[@type="sun"]
try {
output.append("<h2>" + hackDoubleLinked("Weekdays") + "</h2>\n");
addDateSubtable(
"//ldml/dates/calendars/calendar[@type=\"CALENDAR\"]/days/dayContext[@type=\"FORMAT\"]/dayWidth[@type=\"WIDTH\"]/day[@type=\"TYPE\"]",
english, output, "sun", "mon", "tue", "wed", "thu", "fri", "sat");
output.append("<h2>" + hackDoubleLinked("Months") + "</h2>\n");
addDateSubtable(
"//ldml/dates/calendars/calendar[@type=\"CALENDAR\"]/months/monthContext[@type=\"FORMAT\"]/monthWidth[@type=\"WIDTH\"]/month[@type=\"TYPE\"]",
english, output, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12");
output.append("<h2>" + hackDoubleLinked("Quarters") + "</h2>\n");
addDateSubtable(
"//ldml/dates/calendars/calendar[@type=\"CALENDAR\"]/quarters/quarterContext[@type=\"FORMAT\"]/quarterWidth[@type=\"WIDTH\"]/quarter[@type=\"TYPE\"]",
english, output, "1", "2", "3", "4");
// add24HourInfo();
} catch (IOException e) {
throw new ICUUncheckedIOException(e);
}
}
// private void add24HourInfo() {
// PreferredAndAllowedHour timeInfo = timeData.get(locale);
//
// for (String loc : fac)
// }
private void addDateSubtable(String path, CLDRFile english, Appendable output, String... types) throws IOException {
path = path.replace("CALENDAR", calendarID);
output
.append("<table class='dtf-table'>\n"
+
"<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>"
+
"\n");
for (String type : types) {
String path1 = path.replace("TYPE", type);
output.append("<tr>");
boolean first = true;
for (String width : Arrays.asList("wide", "abbreviated", "narrow")) {
String path2 = path1.replace("WIDTH", width);
String last = null;
String lastPath = null;
for (String format : Arrays.asList("format", "stand-alone")) {
String path3 = path2.replace("FORMAT", format);
if (first) {
String value = english.getStringValue(path3);
output.append("<th class='dtf-left'>").append(TransliteratorUtilities.toHTML.transform(value))
.append("</th>");
first = false;
}
String value = file.getStringValue(path3);
if (last == null) {
last = value;
lastPath = path3;
} else {
String lastFix = getFixFromPath(lastPath);
output.append("<td class='dtf-nopad'><table class='dtf-int'><tr><td>").append(
TransliteratorUtilities.toHTML.transform(last));
if (lastFix != null) {
output.append("</td><td class='dtf-fix'>").append(lastFix);
}
if (!value.equals(last)) {
String fix = getFixFromPath(path3);
output.append("</td></tr><tr><td>").append(TransliteratorUtilities.toHTML.transform(value));
if (fix != null) {
output.append("</td><td class='dtf-fix'>").append(fix);
}
}
output.append("</td></tr></table></td>");
}
}
}
output.append("</tr>\n");
}
output.append("</table>\n");
}
private static final boolean RETIRE = false;
private static final String LOCALES = ".*"; // "da|zh|de|ta";
/**
* Produce a set of static tables from the vxml data. Only a stopgap until the above is integrated into ST.
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
myOptions.parse(MyOptions.organization, args, true);
String organization = MyOptions.organization.option.getValue();
String filter = MyOptions.filter.option.getValue();
Factory englishFactory = Factory.make(CLDRPaths.MAIN_DIRECTORY, filter);
CLDRFile englishFile = englishFactory.make("en", true);
Factory factory = Factory.make(CLDRPaths.MAIN_DIRECTORY, LOCALES);
System.out.println("Total locales: " + factory.getAvailableLanguages().size());
DateTimeFormats english = new DateTimeFormats().set(englishFile, "gregorian");
PrintWriter index = openIndex(DIR, "Date/Time");
Map<String, String> sorted = new TreeMap<String, String>();
SupplementalDataInfo sdi = SupplementalDataInfo.getInstance();
Set<String> defaultContent = sdi.getDefaultContentLocales();
for (String localeID : factory.getAvailableLanguages()) {
Level level = StandardCodes.make().getLocaleCoverageLevel(organization, localeID);
if (Level.MODERN.compareTo(level) > 0) {
continue;
}
if (defaultContent.contains(localeID)) {
System.out.println("Skipping default content: " + localeID);
continue;
}
sorted.put(englishFile.getName(localeID, true), localeID);
}
writeCss(DIR);
PrintWriter out;
// http://st.unicode.org/cldr-apps/survey?_=LOCALE&x=r_datetime&calendar=gregorian
int oldFirst = 0;
for (Entry<String, String> nameAndLocale : sorted.entrySet()) {
String name = nameAndLocale.getKey();
String localeID = nameAndLocale.getValue();
DateTimeFormats formats = new DateTimeFormats().set(factory.make(localeID, true), "gregorian");
String filename = localeID + ".html";
out = FileUtilities.openUTF8Writer(DIR, filename);
String redirect = "http://st.unicode.org/cldr-apps/survey?_=" + localeID
+ "&x=r_datetime&calendar=gregorian";
out.println(
"<!doctype HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'><html><head>\n"
+
(RETIRE ? "<meta http-equiv='REFRESH' content='0;url=" + redirect + "'>\n" : "")
+
"<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>\n"
+
"<title>Date/Time Charts: "
+ name
+ "</title>\n"
+
"<link rel='stylesheet' type='text/css' href='index.css'>\n"
+
"</head><body><h1>Date/Time Charts: "
+ name
+ "</h1>"
+
"<p><a href='index.html'>Index</a></p>\n"
+
"<p>The following chart shows typical usage of date and time formatting with the Gregorian calendar. "
+
"<i>There is important information on <a target='CLDR_ST_DOCS' href='http://cldr.unicode.org/translation/date-time-review'>Date/Time Review</a>, "
+
"so please read that page before starting!</i></p>\n");
formats.addTable(english, out);
formats.addDateTable(englishFile, out);
formats.addDayPeriods(englishFile, out);
out.println("<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>"
+
"<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>");
out.println("</body></html>");
out.close();
int first = name.codePointAt(0);
if (oldFirst != first) {
index.append("<hr>");
oldFirst = first;
} else {
index.append("  ");
}
index.append("<a href='").append(filename).append("'>").append(name).append("</a>\n");
index.flush();
}
index.println("</div></body></html>");
index.close();
}
public static PrintWriter openIndex(String directory, String title) throws IOException {
String dateString = CldrUtility.isoFormatDateOnly(new Date());
PrintWriter index = FileUtilities.openUTF8Writer(directory, "index.html");
index
.println(
"<!doctype HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'><html><head>\n"
+
"<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>\n"
+
"<title>"
+ title
+ " Charts</title>\n"
+
"</head><body><h1>"
+ title
+ " Charts</h1>"
+
"<p style='float:left; text-align:left'><a href='../index.html'>Index</a></p>\n"
+
// "<p style='float:left; text-align:left'><a href='index.html'>Index</a></p>\n" +
"<p style='float:right; text-align:right'>"
+ dateString
+ "</p>\n"
+ "<div style='clear:both; margin:2em'>");
return index;
}
public static void writeCss(String directory) throws IOException {
PrintWriter out = FileUtilities.openUTF8Writer(directory, "index.css");
out.println(".dtf-table, .dtf-int {margin-left:auto; margin-right:auto; border-collapse:collapse;}\n"
+
".dtf-table, .dtf-s, .dtf-nopad, .dtf-fix, .dtf-th, .dtf-h, .dtf-sep, .dtf-left, .dtf-int {border:1px solid gray;}\n"
+
".dtf-th {background-color:#EEE; padding:4px}\n" +
".dtf-s, .dtf-nopad, .dtf-fix {padding:3px; text-align:center}\n" +
".dtf-sep {background-color:#EEF; text-align:center}\n" +
".dtf-s {text-align:center;}\n" +
".dtf-int {width:100%; height:100%}\n" +
".dtf-fix {width:1px}\n" +
".dtf-left {text-align:left;}\n" +
".dtf-nopad {padding:0px; align:top}\n" +
".dtf-gray {background-color:#EEF}\n"
);
out.close();
}
public void addDayPeriods(CLDRFile englishFile, Appendable output) {
try {
output.append("<h2>" + hackDoubleLinked("Day Periods") + "</h2>\n");
output
.append("<p>Please review these and correct if needed. The Wide fields are the most important. "
+ "To correct them, go to "
+ getFixFromPath(ICUServiceBuilder.getDayPeriodPath(DayPeriodInfo.DayPeriod.am, Context.format, Width.wide))
+ " and following. "
+ "<b>Note: </b>Day Periods can be a bit tricky; "
+ "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");
output
.append("<table class='dtf-table'>\n"
+ "<tr>"
+ "<th class='dtf-th' rowSpan='3'>DayPeriodID</th>"
+ "<th class='dtf-th' rowSpan='3'>Time Span(s)</th>"
+ "<th class='dtf-th' colSpan='4'>Format</th>"
+ "<th class='dtf-th' colSpan='4'>Standalone</th>"
+ "</tr>\n"
+ "<tr>"
+ "<th class='dtf-th' colSpan='2'>Wide</th>"
+ "<th class='dtf-th'>Abbreviated</th>"
+ "<th class='dtf-th'>Narrow</th>"
+ "<th class='dtf-th' colSpan='2'>Wide</th>"
+ "<th class='dtf-th'>Abbreviated</th>"
+ "<th class='dtf-th'>Narrow</th>"
+ "</tr>\n"
+ "<tr>"
+ "<th class='dtf-th'>English</th>"
+ "<th class='dtf-th'>Native</th>"
+ "<th class='dtf-th'>Native</th>"
+ "<th class='dtf-th'>Native</th>"
+ "<th class='dtf-th'>English</th>"
+ "<th class='dtf-th'>Native</th>"
+ "<th class='dtf-th'>Native</th>"
+ "<th class='dtf-th'>Native</th>"
+ "</tr>\n"
);
DayPeriodInfo dayPeriodInfo = sdi.getDayPeriods(DayPeriodInfo.Type.format, file.getLocaleID());
Set<DayPeriodInfo.DayPeriod> dayPeriods = new LinkedHashSet<>(dayPeriodInfo.getPeriods());
DayPeriodInfo dayPeriodInfo2 = sdi.getDayPeriods(DayPeriodInfo.Type.format, "en");
Set<DayPeriodInfo.DayPeriod> eDayPeriods = EnumSet.copyOf(dayPeriodInfo2.getPeriods());
Output<Boolean> real = new Output<>();
Output<Boolean> realEnglish = new Output<>();
for (DayPeriodInfo.DayPeriod period : dayPeriods) {
R3<Integer, Integer, Boolean> first = dayPeriodInfo.getFirstDayPeriodInfo(period);
int midPoint = (first.get0() + first.get1()) / 2;
output.append("<tr>");
output.append("<th class='dtf-left'>").append(TransliteratorUtilities.toHTML.transform(period.toString()))
.append("</th>\n");
String periods = dayPeriodInfo.toString(period);
output.append("<th class='dtf-left'>").append(TransliteratorUtilities.toHTML.transform(periods))
.append("</th>\n");
for (Context context : Context.values()) {
for (Width width : Width.values()) {
final String dayPeriodPath = ICUServiceBuilder.getDayPeriodPath(period, context, width);
if (width == Width.wide) {
String englishValue;
if (context == Context.format) {
englishValue = icuServiceBuilderEnglish.formatDayPeriod(midPoint, context, width);
realEnglish.value = true;
} else {
englishValue = icuServiceBuilderEnglish.getDayPeriodValue(dayPeriodPath, null, realEnglish);
}
output.append("<th class='dtf-left" + (realEnglish.value ? "" : " dtf-gray") + "'" + ">")
.append(getCleanValue(englishValue, width, "<i>unused</i>"))
.append("</th>\n");
}
String nativeValue = icuServiceBuilder.getDayPeriodValue(dayPeriodPath, "�", real);
if (context == Context.format) {
nativeValue = icuServiceBuilder.formatDayPeriod(midPoint, nativeValue);
}
output.append("<td class='dtf-left" + (real.value ? "" : " dtf-gray") + "'>")
.append(getCleanValue(nativeValue, width, "<i>missing</i>"))
.append("</td>\n");
}
}
output.append("</tr>\n");
}
output.append("</table>\n");
} catch (IOException e) {
throw new ICUUncheckedIOException(e);
}
}
private String getCleanValue(String evalue, Width width, String fallback) {
String replacement = width == Width.wide ? fallback : "<i>optional</i>";
String qevalue = evalue != null ? TransliteratorUtilities.toHTML.transform(evalue) : replacement;
return qevalue.replace("�", replacement);
}
// static final String SHORT_PATH = "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/timeFormats/timeFormatLength[@type=\"short\"]/timeFormat[@type=\"standard\"]/pattern[@type=\"standard\"]";
// static final String HM_PATH = "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"hm\"]";
//
// private String format(CLDRFile file, String evalue, int timeInDay) {
// String pattern = file.getStringValue(HM_PATH);
// if (pattern == null) {
// pattern = "h:mm \uE000";
// } else {
// pattern = pattern.replace('a', '\uE000');
// }
// SimpleDateFormat df = icuServiceBuilder.getDateFormat("gregorian", pattern);
// String formatted = df.format(timeInDay);
// String result = formatted.replace("\uE000", evalue);
// return result;
// }
private String hackDoubleLinked(String link, String name) {
return name;
}
private String hackDoubleLinked(String string) {
return string;
}
static void writeIndexMap(Map<String, String> nameToFile, PrintWriter index) {
int oldFirst = 0;
for (Entry<String, String> entry : nameToFile.entrySet()) {
String name = entry.getKey();
String file = entry.getValue();
int first = name.codePointAt(0);
if (oldFirst != first) {
index.append("<hr>");
oldFirst = first;
} else {
index.append("  ");
}
index.append("<a href='").append(file).append("'>").append(name).append("</a>\n");
index.flush();
}
index.println("</div></body></html>");
}
}