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 e3641e07fa9..a83dad880e8 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 @@ -26,6 +26,7 @@ import com.ibm.icu.text.UTF16; import com.ibm.icu.text.UnicodeSet; import com.ibm.icu.util.Calendar; +import com.ibm.icu.util.GregorianCalendar; import com.ibm.icu.util.Output; import com.ibm.icu.util.TimeZone; import com.ibm.icu.util.ULocale; @@ -176,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(); } @@ -271,6 +271,54 @@ 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. + * Dates are adjusted to be 2 days after official era start date and + * 2 days before era end date to avoid time zone issues. + * TODO: include methods for calendarData in supplementalDataInfo API + * to extract this data directly from supplementaldata.xml + */ + public static final Map> CALENDAR_ERAS = + new HashMap>() { + { // month 0-indexed. start/end days adjusted by +/- 2, respectively + put( + "gregorian", + List.of( + new GregorianCalendar(0, 11, 29).getTime(), + new GregorianCalendar(1, 0, 03).getTime())); + put( + "japanese", + List.of( + new GregorianCalendar(1989, 0, 10).getTime(), + new GregorianCalendar(2019, 4, 3).getTime())); + put("islamic", List.of(new GregorianCalendar(622, 6, 17).getTime())); + put("chinese", List.of(new GregorianCalendar(-2636, 0, 03).getTime())); + put("hebrew", List.of(new GregorianCalendar(-3760, 9, 9).getTime())); + put("buddhist", List.of(new GregorianCalendar(-542, 0, 03).getTime())); + put( + "coptic", + List.of( + new GregorianCalendar(284, 07, 26).getTime(), + new GregorianCalendar(284, 07, 31).getTime())); + put("persian", List.of(new GregorianCalendar(622, 0, 03).getTime())); + put("dangi", List.of(new GregorianCalendar(-2332, 0, 03).getTime())); + put( + "ethiopic", + List.of( + new GregorianCalendar(8, 07, 26).getTime(), + new GregorianCalendar(8, 07, 31).getTime())); + put( + "ethiopic-amete-alem", + List.of(new GregorianCalendar(-5492, 07, 27).getTime())); + put("indian", List.of(new GregorianCalendar(79, 0, 03).getTime())); + put( + "roc", + List.of( + new GregorianCalendar(1911, 11, 29).getTime(), + new GregorianCalendar(1912, 0, 03).getTime())); + } + }; + public CLDRFile getCldrFile() { return cldrFile; } @@ -465,6 +513,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")) { @@ -2876,6 +2928,86 @@ private String handleDateRangePattern(String value) { return result; } + /** + * Add examples for eras. First checks if there is info for this calendar type and this era type + * in the CALENDAR_ERAS 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.startsWith("islamic")) + ? "islamic" + : calendarId; // islamic variations map to same sample + if (!CALENDAR_ERAS.containsKey(id)) { + return null; + } + int typeIndex = Integer.parseInt(type); + if (calendarId.equals("japanese")) { + if (typeIndex < 235) { // examples only for 2 most recent eras + return null; + } else { + typeIndex %= 235; // map to length 2 list + } + } + List eraDates = CALENDAR_ERAS.get(id); + Date sample = eraDates.get(typeIndex); + 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(); + eraNames[typeIndex] = value; // overwrite era to current 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"); + if (width.equals("narrow")) { + return null; + } + String context = parts.findAttributeValue("quarterContext", "type"); + String type = parts.getAttributeValue(-1, "type"); // 1-indexed + int quarterIndex = Integer.parseInt(type) - 1; + 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); + DateFormatSymbols dfs = sdf.getDateFormatSymbols(); + int widthVal = width.equals("abbreviated") ? 0 : 1; + String[] quarterNames = dfs.getQuarters(0, widthVal); // 0 for formatting + quarterNames[quarterIndex] = value; + dfs.setQuarters(quarterNames, 0, widthVal); + sdf.setDateFormatSymbols(dfs); + sdf.setTimeZone(ZONE_SAMPLE); + 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 6eca391675e..cdfb17560b0 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 @@ -9,10 +9,12 @@ import com.google.common.collect.Multimap; import com.google.common.collect.TreeMultimap; import com.ibm.icu.dev.test.TestFmwk; +import com.ibm.icu.impl.Relation; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -154,7 +156,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=\"([^\"]*+)\"]", @@ -201,7 +203,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=\"([^\"]*+)\"]", @@ -1719,6 +1721,84 @@ public void TestUnicodeSetExamples() { } } + public void TestEraMap() { + ExampleGenerator exampleGenerator = getExampleGenerator("en"); + Relation keyToSubtypes = SupplementalDataInfo.getInstance().getBcp47Keys(); + Set calendars = keyToSubtypes.get("ca"); // gets calendar codes + Map codeToType = + new HashMap() { + { // calendars where code != type + put("gregory", "gregorian"); + put("iso8601", "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 (codeToType.containsKey(id)) { + id = codeToType.get(id); + } + Map> calendarMap = exampleGenerator.CALENDAR_ERAS; + assertTrue( + "CALENDAR_ERAS map contains calendar type \"" + id + "\"", + calendarMap.containsKey(id)); + } + } + + public void TestEraFormats() { + ExampleGenerator exampleGeneratorJa = getExampleGenerator("ja"); + ExampleGenerator exampleGeneratorEs = getExampleGenerator("es"); + ExampleGenerator exampleGeneratorZh = getExampleGenerator("zh"); + checkValue( + "japanese type=235 abbreviated", + "〖平成1年〗", + exampleGeneratorJa, + "//ldml/dates/calendars/calendar[@type=\"japanese\"]/eras/eraAbbr/era[@type=\"235\"]"); + checkValue( + "gregorian type=0 wide", + "〖1 antes de Cristo〗", + exampleGeneratorEs, + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/eras/eraNames/era[@type=\"0\"]"); + checkValue( + "gregorian type=0-variant wide", + "〖1 antes de la era común〗", + exampleGeneratorEs, + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/eras/eraNames/era[@type=\"0\"][@alt=\"variant\"]"); + checkValue( + "roc type=1 abbreviated", + "〖民国1年〗", + exampleGeneratorZh, + "//ldml/dates/calendars/calendar[@type=\"roc\"]/eras/eraAbbr/era[@type=\"1\"]"); + } + + public void TestQuarterFormats() { + ExampleGenerator exampleGenerator = getExampleGenerator("ti"); + checkValue( + "ti Q2 format wide", + "〖2ይ ርብዒ 1999〗", + exampleGenerator, + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarterContext[@type=\"format\"]/quarterWidth[@type=\"wide\"]/quarter[@type=\"2\"]"); + checkValue( + "ti Q2 format abbreviated", + "〖ር2 1999〗", + exampleGenerator, + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarterContext[@type=\"format\"]/quarterWidth[@type=\"abbreviated\"]/quarter[@type=\"2\"]"); + checkValue( + "ti Q4 stand-alone wide", + "〖4ይ ርብዒ 1999〗", + exampleGenerator, + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/quarters/quarterContext[@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/quarterContext[@type=\"stand-alone\"]/quarterWidth[@type=\"abbreviated\"]/quarter[@type=\"4\"]"); + } + static final class MissingKey implements Comparable { final SectionId sectionId; final PageId pageId;