blob: 2a2ef14b20b62a1eea2b4174cd86cf7239245c7f [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.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import org.unicode.cldr.draft.FileUtilities;
import org.unicode.cldr.tool.Option;
import org.unicode.cldr.tool.Option.Options;
import org.unicode.cldr.tool.TablePrinter;
import org.unicode.cldr.util.SupplementalDataInfo.DateRange;
import org.unicode.cldr.util.SupplementalDataInfo.MetaZoneRange;
import org.unicode.cldr.util.TimezoneFormatter.Format;
import com.ibm.icu.impl.Row.R5;
import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
public class VerifyZones {
private static final CLDRConfig CLDR_CONFIG = CLDRConfig.getInstance();
private static final String DIR = CLDRPaths.CHART_DIRECTORY + "verify/zones/";
private static final boolean DEBUG = false;
final static Options myOptions = new Options();
enum MyOptions {
organization(".*", "CLDR", "organization"),
filter(".*", ".*", "locale filter (regex)"),
timezoneFilter(".*", null, "timezone filter (regex)"), ;
// boilerplate
final Option option;
MyOptions(String argumentPattern, String defaultArgument, String helpText) {
option = myOptions.add(this, argumentPattern, defaultArgument, helpText);
}
}
static long date = new Date(new Date().getYear(), 0, 15, 0, 0, 0).getTime();
static long date6 = date + 182L * 24 * 60 * 60 * 1000;
static class MetazoneRow extends R5<Long, String, String, Integer, String> {
public MetazoneRow(Integer order, Integer rawOffset, String container, int orderInMetazone, String metazone, String zone) {
super(((long) order << 32) + rawOffset, container, metazone, orderInMetazone, zone);
}
public String getContainer() {
return get1();
}
public String getMetazone() {
return get2();
}
public String getZone() {
return get4();
}
}
public static class ZoneFormats {
private String gmtFormat;
private String hourFormat;
private String[] hourFormatPlusMinus;
private ICUServiceBuilder icuServiceBuilder = new ICUServiceBuilder();
private CLDRFile cldrFile;
public enum Length {
LONG, SHORT;
public String toString() {
return name().toLowerCase(Locale.ENGLISH);
}
}
public enum Type {
generic, standard, daylight, genericOrStandard
}
public ZoneFormats set(CLDRFile cldrFile) {
this.cldrFile = cldrFile;
gmtFormat = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/gmtFormat");
hourFormat = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/hourFormat");
hourFormatPlusMinus = hourFormat.split(";");
icuServiceBuilder.setCldrFile(cldrFile);
return this;
}
public String formatGMT(TimeZone currentZone) {
int tzOffset = currentZone.getRawOffset();
SimpleDateFormat dateFormat = icuServiceBuilder.getDateFormat("gregorian",
hourFormatPlusMinus[tzOffset >= 0 ? 0 : 1]);
String hoursMinutes = dateFormat.format(tzOffset >= 0 ? tzOffset : -tzOffset);
return MessageFormat.format(gmtFormat, hoursMinutes);
}
public String getExemplarCity(String timezoneString) {
String exemplarCity = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/zone[@type=\"" + timezoneString
+ "\"]/exemplarCity");
if (exemplarCity == null) {
exemplarCity = timezoneString.substring(timezoneString.lastIndexOf('/') + 1).replace('_', ' ');
}
return exemplarCity;
}
public String getMetazoneName(String metazone, Length length, Type typeIn) {
Type type = typeIn == Type.genericOrStandard ? Type.generic : typeIn;
String name = cldrFile.getWinningValue("//ldml/dates/timeZoneNames/metazone[@type=\""
+ metazone + "\"]/" + length + "/" + type);
return name != null ? name : typeIn != Type.genericOrStandard ? "n/a" : getMetazoneName(metazone, length,
Type.standard);
}
}
private final static SupplementalDataInfo sdi = SupplementalDataInfo.getInstance();
private final static Map<String, Map<String, String>> metazoneToRegionToZone = sdi.getMetazoneToRegionToZone();
private final static Set<MetazoneRow> rows = new TreeSet<MetazoneRow>();
private final static Set<String> goldenZones = new HashSet<String>();
private final static Map<String, Integer> countryToOrder = new HashMap<String, Integer>();
private final static List<Format> FORMAT_LIST = Arrays.asList(Format.VVVV, Format.vvvv, Format.v, Format.zzzz,
Format.z, Format.zzzz, Format.z);
static {
// find out which canonical zones are not in a metazone
Map<String, String> nameToCountry = new TreeMap<String, String>();
String[] zones = TimeZone.getAvailableIDs();
Set<String> zoneSet = new LinkedHashSet<String>();
Set<String> noncanonical = new LinkedHashSet<String>();
for (String zone : zones) {
String countryCode = TimeZone.getRegion(zone);
String englishTerritory = ULocale.getDisplayCountry("und-" + countryCode, ULocale.ENGLISH);
nameToCountry.put(englishTerritory, countryCode);
String canon = TimeZone.getCanonicalID(zone);
if (canon.equals(zone)) {
zoneSet.add(canon);
} else {
noncanonical.add(zone);
}
}
// get mapping of country names to ints
int i = 0;
for (Entry<String, String> entry : nameToCountry.entrySet()) {
countryToOrder.put(entry.getValue(), i++);
}
//System.out.println("Canonical zones:\t" + zoneSet.size() + "\t" + zoneSet);
//System.out.println("Non-canonical zones:\t" + noncanonical.size() + "\t" + noncanonical);
Set<String> metazones = sdi.getAllMetazones();
if (DEBUG && !metazones.equals(metazoneToRegionToZone.keySet())) {
System.out.println("Mismatch between metazones");
showVennSets(metazones, metazoneToRegionToZone.keySet());
}
Set<String> zonesInMetazones = new LinkedHashSet<String>();
for (String metazone : metazones) {
//String container = PathHeader.getMetazonePageTerritory(metazone);
Map<String, String> regionToZone = metazoneToRegionToZone.get(metazone);
String zone = regionToZone.get("001");
goldenZones.add(zone);
zonesInMetazones.add(zone);
// TimeZone currentZone = TimeZone.getTimeZone(tz_string);
// int order = Containment.getOrder(container);
// int offsetOrder = currentZone.getRawOffset();
// MetazoneRow row = new MetazoneRow(order, offsetOrder, container, 0, metazone, tz_string);
// rows.add(row);
addRow(metazone, zone, 0);
}
//System.out.println("Zones. A = canonical zones, B = zones in metazonesToRegionToZone");
//showVennSets(zoneSet, zonesInMetazones);
vennSets(zoneSet, zonesInMetazones);
Set<String> found = new LinkedHashSet<String>();
for (String zone : zoneSet) {
Set<MetaZoneRange> metaZoneRanges = sdi.getMetaZoneRanges(zone);
if (metaZoneRanges == null) {
continue;
}
for (MetaZoneRange metaZoneRange : metaZoneRanges) {
if (metaZoneRange.dateRange.getTo() == DateRange.END_OF_TIME) {
found.add(zone);
addRow(metaZoneRange.metazone, zone, 1);
break;
}
}
}
// zoneSet.removeAll(found);
// for (String zone : zoneSet) {
// found.add(zone);
// // TimeZone currentZone = TimeZone.getTimeZone(tz_string);
// // int offsetOrder = currentZone.getRawOffset();
// // MetazoneRow row = new MetazoneRow(Integer.MAX_VALUE, offsetOrder, "001", 1, "None", tz_string);
// // rows.add(row);
// addRow("None", zone, 1);
// }
if (DEBUG) System.out.println("\nSorted");
for (MetazoneRow row : rows) {
if (row.getMetazone().equals("Europe_Central")) {
if (DEBUG) System.out.println(row);
}
}
}
private static void addRow(String metaZone, String tz_string, int orderInMetazone) {
TimeZone currentZone = TimeZone.getTimeZone(tz_string);
String container = PathHeader.getMetazonePageTerritory(metaZone);
if (container == null) {
return; // skip
}
int order = Containment.getOrder(container);
int offsetOrder = currentZone.getRawOffset();
orderInMetazone = (orderInMetazone << 16)
| (hasDaylight(currentZone) ? 0 : 1)
| countryToOrder.get(TimeZone.getRegion(tz_string));
MetazoneRow row = new MetazoneRow(order, offsetOrder, container,
orderInMetazone, metaZone, tz_string);
if (metaZone.equals("Europe_Central")) {
if (DEBUG) System.out.println(row);
}
rows.add(row);
}
private static void showVennSets(Set<String> zoneSet, Set<String> zonesInMetazones) {
Set<String> common = new LinkedHashSet<String>();
Set<String> firstMinusSecond = new LinkedHashSet<String>();
Set<String> secondMinusFirst = new LinkedHashSet<String>();
vennSets(zoneSet, zonesInMetazones, common, firstMinusSecond, secondMinusFirst);
if (!common.isEmpty()) System.out.println("A & B:\t" + common.size() + "\t" + common);
if (!firstMinusSecond.isEmpty()) System.out.println("A - B:\t" + firstMinusSecond.size() + "\t" + firstMinusSecond);
if (!secondMinusFirst.isEmpty()) System.out.println("B - A:\t" + secondMinusFirst.size() + "\t" + secondMinusFirst);
}
private static <T> void vennSets(Set<T> first, Set<T> second,
Set<T> common, Set<T> firstMinusSecond, Set<T> secondMinusFirst) {
common.clear();
common.addAll(first);
common.retainAll(second);
firstMinusSecond.clear();
firstMinusSecond.addAll(first);
firstMinusSecond.removeAll(common);
secondMinusFirst.clear();
secondMinusFirst.addAll(second);
secondMinusFirst.removeAll(common);
}
@SuppressWarnings("unused")
private static <T> void vennSets(Set<T> first, Set<T> second, Set<T> common) {
common.clear();
common.addAll(first);
common.retainAll(second);
first.removeAll(common);
second.removeAll(common);
}
private static <T> void vennSets(Set<T> first, Set<T> second) {
first.removeAll(second);
second.removeAll(first);
}
/**
* 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();
String timezoneFilterString = MyOptions.timezoneFilter.option.getValue();
Matcher timezoneFilter = timezoneFilterString == null ? null : PatternCache.get(timezoneFilterString)
.matcher("");
Factory factory2 = Factory.make(CLDRPaths.MAIN_DIRECTORY, filter);
CLDRFile englishCldrFile = factory2.make("en", true);
DateTimeFormats.writeCss(DIR);
final CLDRFile english = CLDR_CONFIG.getEnglish();
Map<String, String> indexMap = new TreeMap<>(CLDR_CONFIG.getCollator());
for (String localeID : factory2.getAvailableLanguages()) {
Level level = StandardCodes.make().getLocaleCoverageLevel(organization, localeID);
if (Level.MODERN.compareTo(level) > 0) {
continue;
}
CLDRFile cldrFile = factory2.make(localeID, true);
PrintWriter out = FileUtilities.openUTF8Writer(DIR, localeID + ".html");
String title = "Verify Time Zones: " + englishCldrFile.getName(localeID);
out.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 + "</title>\n" +
"<link rel='stylesheet' type='text/css' href='index.css'>\n" +
"</head><body><h1>" + title + "</h1>\n"
+ "<p><a href='index.html'>Index</a></p>\n"
);
showZones(timezoneFilter, englishCldrFile, cldrFile, out);
out.println("</body></html>");
out.close();
indexMap.put(english.getName(localeID), localeID + ".html");
}
try (PrintWriter index = DateTimeFormats.openIndex(DIR, "Time Zones")) {
DateTimeFormats.writeIndexMap(indexMap, index);
}
// Look at DateTimeFormats.java
if (true) return;
// Set<String> defaultContentLocales = sdi.getDefaultContentLocales();
// NumberFormat enf = NumberFormat.getIntegerInstance(ULocale.ENGLISH);
// enf.setGroupingUsed(false);
// Set<String> debugCreationErrors = new LinkedHashSet<String>();
// Set<String> errors = new LinkedHashSet<String>();
//
// for (String locale : factory2.getAvailableLanguages()) {
// if (defaultContentLocales.contains(locale)) {
// continue;
// }
// Level level = StandardCodes.make().getLocaleCoverageLevel(organization, locale);
// if (Level.MODERN.compareTo(level) > 0) {
// continue;
// }
//
// // one path for group-3, one for group-4
// int factor = USES_GROUPS_OF_4.contains(locale) ? 10000 : 1000;
//
// ULocale locale2 = new ULocale(locale);
// NumberFormat nf = NumberFormat.getIntegerInstance(locale2);
// nf.setMaximumFractionDigits(0);
// CLDRFile cldrFile = factory2.make(locale, true, DraftStatus.contributed);
// PluralInfo pluralInfo = sdi.getPlurals(locale);
// Set<Double> samples = new TreeSet<Double>();
// for (Entry<Count, List<Double>> entry : pluralInfo.getCountToExamplesMap().entrySet()) {
// samples.add(entry.getValue().get(0));
// }
// String[] debugOriginals = null;
// CompactDecimalFormat cdf = BuildIcuCompactDecimalFormat.build(cldrFile, debugCreationErrors, debugOriginals,
// Style.SHORT, locale2);
// captureErrors(debugCreationErrors, errors, locale, "short");
// CompactDecimalFormat cdfs = BuildIcuCompactDecimalFormat.build(cldrFile, debugCreationErrors, debugOriginals,
// Style.LONG, locale2);
// captureErrors(debugCreationErrors, errors, locale, "long");
//
// Set<Double> samples2 = new TreeSet<Double>();
// for (int i = 10; i < factor; i *= 10) {
// for (Double sample : samples) {
// samples2.add(sample*i);
// }
// }
// samples.addAll(samples2);
// samples.add(1.5d);
// System.out.println("———\t" + englishCldrFile.getName(locale) + "\t———");
//
// String column12 = (locale + "\t" + englishCldrFile.getName(locale));
// System.out.print(column12 +
// "\tNumeric\tCompact-Short\tCompact-Long\tFixed Numeric\tFixed Compact-Short\tFixed Compact-Long\n");
//
// try {
// // we print the __ so that it can be imported into a spreadsheet without problems.
// for (long i = factor; i <= 100000000000000L; i *= factor) {
// for (Double sample : samples) {
// double source = i * sample;
// if (false && source == 22000000 && locale.equals("cs")) {
// System.out.println("**");
// }
// System.out.print(locale + "\t__" + enf.format(source));
// System.out.print("\t__" + nf.format(source));
// String formatted = cdf.format(source);
// System.out.print("\t__" + formatted);
// formatted = cdfs.format(source);
// System.out.println("\t__" + formatted);
// }
// System.out.println();
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// for (String s : errors) {
// System.out.println(s);
// }
}
public static void showZones(Matcher timezoneFilter,
CLDRFile englishCldrFile, CLDRFile nativeCdrFile,
Appendable out) throws IOException {
TablePrinter tablePrinter = new TablePrinter() // .setCaption("Timezone Formats")
.setTableAttributes("class='dtf-table'")
.addColumn("Metazone").setHeaderCell(true).setSpanRows(true)
.setHeaderAttributes("class='dtf-th'").setCellAttributes("class='dtf-s'")
.addColumn("Region: TZID").setHeaderCell(true).setSpanRows(true)
.setHeaderAttributes("class='dtf-th'").setCellAttributes("class='dtf-s'")
//.setCellPattern(CldrUtility.getDoubleLinkMsg())
// HACK because anchors don't work any more
// .addColumn("Region: City").setHeaderCell(true).setSpanRows(true)
// .addColumn("Region/City").setSpanRows(true)
;
// .addColumn("Code", "class='source'", CldrUtility.getDoubleLinkMsg(), "class='source'", true).setSpanRows(true)
boolean daylight = false;
for (Format s : FORMAT_LIST) {
tablePrinter.addColumn(s.toString()
+ "<br>" + s.type.toString(daylight)
+ "<br>" + s.location
+ "<br>" + s.length).setSpanRows(true).setHeaderAttributes("class='dtf-th'")
.setCellAttributes("class='dtf-s'");
if (s == Format.z) {
daylight = true; // reset for final 2 items
}
}
tablePrinter.addColumn("View").setHeaderCell(true).setHeaderAttributes("class='dtf-th'").setCellAttributes("class='dtf-s'");
ZoneFormats englishZoneFormats = new ZoneFormats().set(englishCldrFile);
addZones(englishZoneFormats, nativeCdrFile, timezoneFilter, tablePrinter);
out.append(tablePrinter.toString() + "\n");
}
private static void addZones(ZoneFormats englishZoneFormats, CLDRFile cldrFile, Matcher timezoneFilter,
TablePrinter output) throws IOException {
CLDRFile englishCldrFile = englishZoneFormats.cldrFile;
//ZoneFormats nativeZoneFormats = new ZoneFormats().set(cldrFile);
TimezoneFormatter tzformatter = new TimezoneFormatter(cldrFile);
for (MetazoneRow row : rows) {
String grouping = row.getContainer();
String metazone = row.getMetazone();
String tzid = row.getZone();
TimeZone currentZone = TimeZone.getTimeZone(tzid);
TimeZone tz = currentZone;
String englishGrouping = englishCldrFile.getName(CLDRFile.TERRITORY_NAME, grouping);
String metazoneInfo = englishGrouping
+ "<br>" + englishZoneFormats.formatGMT(currentZone)
+ "<br>" + "MZ: " + metazone;
boolean isGolden = goldenZones.contains(tzid);
String countryCode2 = TimeZone.getRegion(tzid);
if (countryCode2.equals("001")) {
continue;
}
String englishTerritory = englishCldrFile.getName(CLDRFile.TERRITORY_NAME, countryCode2);
output.addRow()
.addCell(metazoneInfo)
.addCell(englishTerritory + ": " + tzid.replace("/", "/\u200B"));
long date2 = getStandardDate(tz);
for (Format pattern : FORMAT_LIST) {
String formattedZone = tzformatter.getFormattedZone(tzid, pattern.toString(), date2);
if (isGolden) {
formattedZone = "<b>" + formattedZone + "</b>";
}
output.addCell(formattedZone);
if (pattern == Format.z) {
if (!hasDaylight(tz)) {
output.addCell("<i>n/a</i>");
output.addCell("<i>n/a</i>");
break;
}
date2 = date2 == date ? date6 : date; // reverse for final 2 items
}
}
String view = PathHeader.getLinkedView(surveyUrl, cldrFile, METAZONE_PREFIX + metazone + METAZONE_SUFFIX);
if (view == null) {
view = PathHeader.getLinkedView(surveyUrl, cldrFile, METAZONE_PREFIX + metazone + METAZONE_SUFFIX2);
}
output.addCell(view == null
? ""
: view);
output.finishRow();
}
}
private static String surveyUrl = CLDR_CONFIG.getProperty("CLDR_SURVEY_URL",
"http://st.unicode.org/cldr-apps/survey");
static private String METAZONE_PREFIX = "//ldml/dates/timeZoneNames/metazone[@type=\"";
static private String METAZONE_SUFFIX = "\"]/long/generic";
static private String METAZONE_SUFFIX2 = "\"]/long/standard";
private static boolean hasDaylight(TimeZone tz) {
int dateOffset = tz.getOffset(date);
return dateOffset != tz.getRawOffset() || dateOffset != tz.getOffset(date6);
}
private static long getStandardDate(TimeZone tz) {
return tz.getOffset(date) == tz.getRawOffset() ? date : date6;
}
private static long getDaylightDate(TimeZone tz) {
return tz.getOffset(date) == tz.getRawOffset() ? date6 : date;
}
private static void captureErrors(Set<String> debugCreationErrors, Set<String> errors, String locale, String length) {
if (debugCreationErrors.size() != 0) {
for (String s : debugCreationErrors) {
errors.add(locale + "\t" + length + "\t" + s);
}
debugCreationErrors.clear();
}
}
}