Skip to content

Commit

Permalink
CLDR-7401 Add examples for quarters and eras
Browse files Browse the repository at this point in the history
See #3917
  • Loading branch information
haytenf authored and Squash Bot committed Aug 20, 2024
1 parent 775920e commit 9e56b98
Show file tree
Hide file tree
Showing 2 changed files with 215 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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<String, List<Date>> CALENDAR_ERAS =
new HashMap<String, List<Date>>() {
{ // 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;
}
Expand Down Expand Up @@ -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")) {
Expand Down Expand Up @@ -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<Date> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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=\"([^\"]*+)\"]",
Expand Down Expand Up @@ -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=\"([^\"]*+)\"]",
Expand Down Expand Up @@ -1719,6 +1721,84 @@ public void TestUnicodeSetExamples() {
}
}

public void TestEraMap() {
ExampleGenerator exampleGenerator = getExampleGenerator("en");
Relation<String, String> keyToSubtypes = SupplementalDataInfo.getInstance().getBcp47Keys();
Set<String> calendars = keyToSubtypes.get("ca"); // gets calendar codes
Map<String, String> codeToType =
new HashMap<String, String>() {
{ // 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<String, List<Date>> 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<MissingKey> {
final SectionId sectionId;
final PageId pageId;
Expand Down

0 comments on commit 9e56b98

Please sign in to comment.