diff --git a/tools/cldr-code/src/main/java/org/unicode/cldr/test/ExampleGenerator.java b/tools/cldr-code/src/main/java/org/unicode/cldr/test/ExampleGenerator.java index 913e4884462..59f660d9ebf 100644 --- a/tools/cldr-code/src/main/java/org/unicode/cldr/test/ExampleGenerator.java +++ b/tools/cldr-code/src/main/java/org/unicode/cldr/test/ExampleGenerator.java @@ -177,7 +177,6 @@ public class ExampleGenerator { DATE_SAMPLE = calendar.getTime(); calendar.set(1999, 9, 27, 13, 25, 59); // 1999-10-27 13:25:59 DATE_SAMPLE2 = calendar.getTime(); - calendar.set(1999, 8, 5, 7, 0, 0); // 1999-09-05 07:00:00 DATE_SAMPLE3 = calendar.getTime(); calendar.set(1999, 8, 5, 23, 0, 0); // 1999-09-05 23:00:00 @@ -274,6 +273,32 @@ public void setCachingEnabled(boolean enabled) { HelpMessages helpMessages; + /* For each calendar type, maps the closest two eras to 2025 + * defined in that calendar to their corresponding start/end date + * TODO: include methods for calendarData in supplementalDataInfo API + * to extract this data directly from supplementaldata.xml + */ + public static final Map calendarEras = new HashMap() {{ + put("calendar[@type=\"gregorian\"]/eras/era[@type=\"0\"]", "end=\"0-12-31\""); + put("calendar[@type=\"gregorian\"]/eras/era[@type=\"1\"]", "start=\"1-01-01\""); + put("calendar[@type=\"japanese\"]/eras/era[@type=\"235\"]", "start=\"1989-1-8\""); + put("calendar[@type=\"japanese\"]/eras/era[@type=\"236\"]", "start=\"2019-5-1\""); + put("calendar[@type=\"islamic\"]/eras/era[@type=\"0\"]", "start=\"622-7-15\""); + put("calendar[@type=\"chinese\"]/eras/era[@type=\"0\"]", "start=\"-2636-01-01\""); + put("calendar[@type=\"hebrew\"]/eras/era[@type=\"0\"]", "start=\"-3760-10-7\""); + put("calendar[@type=\"buddhist\"]/eras/era[@type=\"0\"]", "start=\"-542-01-01\""); + put("calendar[@type=\"coptic\"]/eras/era[@type=\"0\"]", "end=\"284-08-28\""); + put("calendar[@type=\"coptic\"]/eras/era[@type=\"1\"]", "start=\"284-08-29\""); + put("calendar[@type=\"persian\"]/eras/era[@type=\"0\"]", "start=\"622-01-01\""); + put("calendar[@type=\"dangi\"]/eras/era[@type=\"0\"]", "start=\"-2332-01-01\""); + put("calendar[@type=\"ethiopic\"]/eras/era[@type=\"0\"]", "end=\"8-08-28\""); + put("calendar[@type=\"ethiopic\"]/eras/era[@type=\"1\"]", "start=\"8-08-29\""); + put("calendar[@type=\"ethiopic-amete-alem\"]/eras/era[@type=\"0\"]", "end=\"-5492-08-29\""); + put("calendar[@type=\"indian\"]/eras/era[@type=\"0\"]", "start=\"79-01-01\""); + put("calendar[@type=\"roc\"]/eras/era[@type=\"0\"]", "end=\"1911-12-31\""); + put("calendar[@type=\"roc\"]/eras/era[@type=\"1\"]", "start=\"1912-01-01\""); + }}; + public CLDRFile getCldrFile() { return cldrFile; } @@ -468,6 +493,10 @@ private String constructExampleHtml(String xpath, String value, boolean nonTrivi result = handleDisplayNames(xpath, parts, value); } else if (parts.contains("currency")) { result = handleCurrency(xpath, parts, value); + } else if (parts.contains("eras")) { + result = handleEras(parts, value); + } else if (parts.contains("quarters")) { + result = handleQuarters(parts, value); } else if (parts.contains("dayPeriods")) { result = handleDayPeriod(parts, value); } else if (parts.contains("monthContext")) { @@ -2861,6 +2890,100 @@ private String handleDateRangePattern(String value) { return result; } + /** + * Extract the year, month, day for the given era start/end date + * + * @param eraInfo a value in the calendarEras map, formatted "start=\"year-month-day\"" or "end=\"year-month-day\"" + * @return + */ + public int[] getEraDateParts(String eraInfo) { + String[] parts = eraInfo.split("\""); // ex. ["start=", "-3760-10-7"] or ["end=", "8-08-28"] + if (parts.length != 2) { + throw new IllegalArgumentException("Values in calendarEras map must be formatted: end=\"year-month-day\" or start=\"year-month-day\""); + } + String eraDate = parts[1]; + int firstSeparator = eraDate.indexOf("-", 1); // start from index=1 in case negative year + int secondSeparator = eraDate.indexOf("-", firstSeparator + 1); + if (firstSeparator == -1 || secondSeparator == -1) { + throw new IllegalArgumentException("Dates in calendarEras map must be formatted: \"year-month-day\""); + } + int year = Integer.parseInt(eraDate.substring(0, firstSeparator)); + int month = Integer.parseInt(eraDate.substring(firstSeparator + 1, secondSeparator)); + int date = Integer.parseInt(eraDate.substring(secondSeparator + 1)); + int[] dateParts = new int[]{year, month, date}; + return dateParts; + } + + /** + * Add examples for eras. First checks if there is info for this + * calendar type and this era type in the calendarEras map, then + * generates a sample date based on this info and formats it + */ + private String handleEras(XPathParts parts, String value) { + String calendarId = parts.getAttributeValue(3, "type"); + String type = parts.getAttributeValue(-1, "type"); + String id = (calendarId.contains("islamic")) ? "islamic" : calendarId; // map all islamic variations to same entry + String curCalendar = "calendar[@type=\"" + id + "\"]/eras/era[@type=\"" + type + "\"]"; + if (!calendarEras.containsKey(curCalendar)) { + return null; + } + String eraInfo = calendarEras.get(curCalendar); + int[] dateParts = getEraDateParts(eraInfo); + calendar.set(dateParts[0], dateParts[1] - 1, dateParts[2], 0, 0, 0); // calendar.set month is 0 based + Date sample = calendar.getTime(); + String skeleton = "Gy"; + String checkPath = + "//ldml/dates/calendars/calendar[@type=\"" + + calendarId + + "\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"" + + skeleton + + "\"]"; + String dateFormat = cldrFile.getWinningValue(checkPath); + SimpleDateFormat sdf = + icuServiceBuilder.getDateFormat(calendarId, dateFormat); + DateFormatSymbols dfs = sdf.getDateFormatSymbols(); + String[] eraNames = dfs.getEras(); + int eraIndex = Integer.parseInt(type); + eraNames[eraIndex] = value; // overwrite era to curr value + dfs.setEras(eraNames); + sdf.setDateFormatSymbols(dfs); + return sdf.format(sample); + } + + /** + * Add examples for quarters for the gregorian calendar, + * matching each quarter type (1, 2, 3, 4) to a corresponding + * sample month and formatting an example with that date + */ + private String handleQuarters(XPathParts parts, String value) { + String calendarId = parts.getAttributeValue(3, "type"); + if (!calendarId.equals("gregorian")) { + return null; + } + String width = parts.findAttributeValue("quarterWidth", "type"); + String type = parts.getAttributeValue(-1, "type"); + if (width.equals("narrow")) { + return null; + } + String skeleton = (width.equals("wide")) ? "yQQQQ" : "yQQQ"; + String checkPath = + "//ldml/dates/calendars/calendar[@type=\"" + + calendarId + + "\"]/dateTimeFormats/availableFormats/dateFormatItem[@id=\"" + + skeleton + + "\"]"; + String dateFormat = cldrFile.getWinningValue(checkPath); + SimpleDateFormat sdf = + icuServiceBuilder.getDateFormat(calendarId, dateFormat); + sdf.setTimeZone(ZONE_SAMPLE); + int quarterIndex = Integer.parseInt(type) - 1; // quarter types are 1-indexed + final int[] monthSamples = new int[]{1, 4, 7, 10}; // {feb, may, oct, nov} + int month = monthSamples[quarterIndex]; + calendar.set(1999, month, 5, 13, 25, 59); + Date sample = calendar.getTime(); + return sdf.format(sample); + } + /** * @param elementToOverride the element that is to be overridden * @param element the overriding element diff --git a/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestExampleGenerator.java b/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestExampleGenerator.java index da1d251ee87..22bd2215739 100644 --- a/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestExampleGenerator.java +++ b/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestExampleGenerator.java @@ -34,6 +34,7 @@ import org.unicode.cldr.util.SupplementalDataInfo.PluralType; import org.unicode.cldr.util.UnitPathType; import org.unicode.cldr.util.With; +import com.ibm.icu.impl.Relation; public class TestExampleGenerator extends TestFmwk { @@ -118,7 +119,7 @@ public void testCurrency() { "//ldml/characters/parseLenients.*", "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/months/monthContext[@type=\"([^\"]*+)\"]/monthWidth[@type=\"([^\"]*+)\"]/month[@type=\"([^\"]*+)\"]", "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/days/dayContext[@type=\"([^\"]*+)\"]/dayWidth[@type=\"([^\"]*+)\"]/day[@type=\"([^\"]*+)\"]", - "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/quarters/quarterContext[@type=\"([^\"]*+)\"]/quarterWidth[@type=\"([^\"]*+)\"]/quarter[@type=\"([^\"]*+)\"]", + "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/quarters/quarterContext[@type=\"([^\"]*+)\"]/quarterWidth[@type=\"([^\"]*+)\"]/quarter[@type=\"([^\"]*+)\"]", // examples only for gregorian "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/displayName", "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/relative[@type=\"([^\"]*+)\"]", "//ldml/dates/fields/field[@type=\"([^\"]*+)\"]/relativeTime[@type=\"([^\"]*+)\"]/relativeTimePattern[@count=\"([^\"]*+)\"]", @@ -165,7 +166,7 @@ public void testCurrency() { "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/appendItems/appendItem[@request=\"([^\"]*+)\"]", "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/intervalFormats/intervalFormatFallback", "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\"([^\"]*+)\"]/greatestDifference[@id=\"([^\"]*+)\"]", - "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNames/era[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", + "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNames/era[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", // examples only for two closest eras to 2025 "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraAbbr/era[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/eras/eraNarrow/era[@type=\"([^\"]*+)\"][@alt=\"([^\"]*+)\"]", "//ldml/dates/calendars/calendar[@type=\"([^\"]*+)\"]/months/monthContext[@type=\"([^\"]*+)\"]/monthWidth[@type=\"([^\"]*+)\"]/month[@type=\"([^\"]*+)\"][@yeartype=\"([^\"]*+)\"]", @@ -1645,4 +1646,108 @@ public void TestUnicodeSetExamples() { assertEquals(locale + path + "=" + value, expected, actual); } } + + public void TestEraDateParser() { + ExampleGenerator exampleGenerator = getExampleGenerator("en"); + int[] result = new int[]{-4321, 10, 15}; + assertTrue("Parse end=\"-4321-10-15\" into array with year, month, date numeric entries", + Arrays.equals(exampleGenerator.getEraDateParts("end=\"-4321-10-15\""), result)); + int[] result2 = new int[]{645, 6, 19}; + assertTrue("Parse start=\"645-6-19\" into array with year, month, date numeric entries", + Arrays.equals(exampleGenerator.getEraDateParts("start=\"645-6-19\""), result2)); + } + + public void TestEraMap() { + ExampleGenerator exampleGenerator = getExampleGenerator("en"); + Relation keyToSubtypes = SupplementalDataInfo.getInstance().getBcp47Keys(); + Set calendars = keyToSubtypes.get("ca"); // gets calendar codes + Map codeToType = new HashMap() {{ // only calendars where their code != type + put("gregory", "gregorian"); + put("ethioaa", "ethiopic-amete-alem"); + put("islamic-civil", "islamic"); + put("islamic-rgsa", "islamic"); + put("islamic-tbla", "islamic"); + put("islamic-umalqura", "islamic"); + put("islamicc", "islamic"); + }}; + for (String id : calendars) { + if (id.equals("iso8601")) { // generic + continue; + } + if (codeToType.containsKey(id)) { + id = codeToType.get(id); + } + String type = (id.equals("japanese")) ? "235" : "0"; // japanese only has last 2 eras (235, 236) + String curCalendar = "calendar[@type=\"" + id + "\"]/eras/era[@type=\"" + type + "\"]"; + Map calendarMap = exampleGenerator.calendarEras; + assertTrue("calendarEras map contains calendar type \"" + id + "\"", calendarMap.containsKey(curCalendar)); + } + } + + public void TestEraFormats() { + String[][] tests = { + { + "ja", + "//ldml/dates/calendars/calendar[@type=\"japanese\"]/eras/eraAbbr/era[@type=\"235\"]", + "japanese type=235 abbreviated", + "〖平成1年〗" + }, + { + "es", + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/eras/eraNames/era[@type=\"0\"]", + "gregorian type=0 wide", + "〖1 antes de Cristo〗" + }, + { + "es", + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/eras/eraNames/era[@type=\"0\"][@alt=\"variant\"]", + "gregorian type=0-variant wide", + "〖1 antes de la era común〗" + }, + { + "zh", + "//ldml/dates/calendars/calendar[@type=\"roc\"]/eras/eraAbbr/era[@type=\"1\"]", + "roc type=1 abbreviated", + "〖民国1年〗" + }, + }; + for (String[] test : tests) { + final String locale = test[0]; + final String path = test[1]; + final String message = test[2]; + final String expected = test[3]; + ExampleGenerator exampleGenerator = getExampleGenerator(locale); + checkValue(message, expected, exampleGenerator, path); + } + } + + public void TestQuarterFormats() { + ExampleGenerator exampleGenerator = getExampleGenerator("ti"); + checkValue( + "ti Q2 format wide", + "〖2ይ ርብዒ 1999〗", + exampleGenerator, + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarter + Context[@type=\"format\"]/quarterWidth[@type=\"wide\"]/quarter[@type=\"2\"]"); + checkValue( + "ti Q2 format abbreviated", + "〖ር2 1999〗", + exampleGenerator, + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarter + Context[@type=\"format\"]/quarterWidth[@type=\"abbreviated\"]/quarter[@type=\"2\"]"); + checkValue( + "ti Q4 stand-alone wide", + "〖4ይ ርብዒ 1999〗", + exampleGenerator, + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarter + Context[@type=\"stand-alone\"]/quarterWidth[@type=\"wide\"]/quarter[@type=\"4\"]"); + checkValue( + "ti Q4 stand-alone abbreviated", + "〖ር4 1999〗", + exampleGenerator, + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarter + Context[@type=\"stand-alone\"]/quarterWidth[@type=\"abbreviated\"]/quarter[@type=\"4\"]"); + } } + +