From 928ed1a63c5177425db60c2cfb09ec38d79f59ac Mon Sep 17 00:00:00 2001 From: macchiati Date: Tue, 10 Sep 2024 14:53:06 -0700 Subject: [PATCH] CLDR-17892 ISO 8601 calendar --- common/main/root.xml | 307 ++++++++++++++++++ .../unicode/cldr/unittest/TestDateOrder.java | 179 +++++++++- 2 files changed, 485 insertions(+), 1 deletion(-) diff --git a/common/main/root.xml b/common/main/root.xml index d398b8c2dc2..8c484d4bab7 100644 --- a/common/main/root.xml +++ b/common/main/root.xml @@ -1936,6 +1936,313 @@ Warnings: All cp values have U+FE0F characters removed. See /annotationsDerived/ + + + + + + + + + + + + + + + + + + + + y MMMM d + yMMMMd, EEEE + + + + + y MMMM d + yMMMMd + + + + + y MMM d + yMMMd + + + + + y-MM-dd + yMMdd + + + + + + + HH:mm:ss zzzz + HHmmsszzzz + + + + + HH:mm:ss z + HHmmssz + + + + + HH:mm:ss + HHmmss + + + + + HH:mm + HHmm + + + + + + + {1} {0} + + + + + + + + {1} {0} + + + + + + + + {1} {0} + + + + + + + + {1} {0} + + + + + + + h B + h:mm B + h:mm:ss B + d + ccc + E h:mm B + E h:mm:ss B + d, E + E HH:mm + E HH:mm + E HH:mm:ss + E HH:mm:ss + y + y-MM-dd + y MMM + y MMM d + y MMM d, E + HH + HH + HH:mm + HH:mm + HH:mm:ss + HH:mm:ss + HH:mm:ss v + HH:mm:ss v + HH:mm v + HH:mm v + L + MM-dd + MM-dd, E + LLL + MMM d + MMM d + MMMM d + MMMM 'week' W + mm:ss + y + y-MM + y-MM-dd + y-MM-dd, E + y MMM + y MMM d + y MMM d + y MMMM + y QQQ + y QQQQ + Y 'week' w + + + {0} ({2}: {1}) + {0} {1} + {1} {0} + {0} ({2}: {1}) + {0} ({2}: {1}) + {0} ({2}: {1}) + {0} ({2}: {1}) + {0} ({2}: {1}) + {0} {1} + {0} ({2}: {1}) + {1} {0} + + + {0} – {1} + + h B – h B + h–h B + + + h:mm B – h:mm B + h:mm–h:mm B + h:mm–h:mm B + + + d–d + + + y – y + y–y + + + y-MM – y-MM + y-MM – y-MM + y-MM – y-MM + + + y-MM-dd – y-MM-dd + y-MM-dd – y-MM-dd + y-MM-dd – y-MM-dd + y-MM-dd – y-MM-dd + + + y-MM-dd, E – y-MM-dd, E + y-MM-dd, E – GGGGG y-MM-dd, E + y-MM-dd, E – y-MM-dd, E + y-MM-dd, E – y-MM-dd, E + + + y MMM – G y MMM + y MMM–MMM + y MMM – y MMM + + + y MMM d–d + y MMM d – y MMM d + y MMM d – MMM d + y MMM d – y MMM d + + + y MMM d, E – MMM d, E + y MMM d, E – y MMM d, E + y MMM d, E – MMM d, E + y MMM d, E – y MMM d, E + + + HH–HH + HH–HH + + + HH–HH + + + HH:mm–HH:mm + HH:mm–HH:mm + HH:mm–HH:mm + + + HH:mm–HH:mm + HH:mm–HH:mm + + + HH:mm–HH:mm v + HH:mm–HH:mm v + HH:mm–HH:mm v + + + HH:mm–HH:mm v + HH:mm–HH:mm v + + + HH–HH v + HH–HH v + + + HH–HH v + + + MM–MM + + + MM-dd – MM-dd + MM-dd – MM-dd + + + MM-dd, E – MM-dd, E + MM-dd, E – MM-dd, E + + + LLL–LLL + + + MMM d–d + MMM d – MMM d + + + MMM d, E – MMM d, E + MMM d, E – MMM d, E + + + y–y + + + y-MM – y-MM + y-MM – y-MM + + + y-MM-dd – y-MM-dd + y-MM-dd – y-MM-dd + y-MM-dd – y-MM-dd + + + y-MM-dd, E – y-MM-dd, E + y-MM-dd, E – y-MM-dd, E + y-MM-dd, E – y-MM-dd, E + + + y MMM–MMM + y MMM – y MMM + + + y MMM d–d + y MMM d – MMM d + y MMM d – y MMM d + + + y MMM d, E – MMM d, E + y MMM d, E – MMM d, E + y MMM d, E – y MMM d, E + + + y MMMM–MMMM + y MMMM – y MMMM + + + + diff --git a/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestDateOrder.java b/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestDateOrder.java index e6650b0b38e..9b1aaaba9ae 100644 --- a/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestDateOrder.java +++ b/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestDateOrder.java @@ -1,16 +1,33 @@ package org.unicode.cldr.unittest; +import com.google.common.base.Joiner; import com.ibm.icu.dev.test.TestFmwk; import com.ibm.icu.text.DateTimePatternGenerator; +import com.ibm.icu.text.DateTimePatternGenerator.VariableField; +import com.ibm.icu.text.SimpleDateFormat; +import com.ibm.icu.text.UnicodeSet; +import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import org.unicode.cldr.test.DateOrder; +import org.unicode.cldr.util.CLDRConfig; import org.unicode.cldr.util.CLDRFile; +import org.unicode.cldr.util.CLDRLocale; +import org.unicode.cldr.util.ICUServiceBuilder; +import org.unicode.cldr.util.PathHeader; +import org.unicode.cldr.util.PathHeader.Factory; import org.unicode.cldr.util.SimpleXMLSource; import org.unicode.cldr.util.XMLSource; +import org.unicode.cldr.util.XPathParts; public class TestDateOrder extends TestFmwk { + private static final Joiner JOIN_TAB = Joiner.on('\t'); + public static void main(String[] args) { new TestDateOrder().run(args); } @@ -51,7 +68,7 @@ public void TestDateImportance() { source.putValueAtPath(fullDate, "EEEE, y MMMM dd"); order = DateOrder.getOrderingInfo(cldrFile, cldrFile, fp); - values = new HashSet(order.get(fullDate).values()); // filter + values = new HashSet<>(order.get(fullDate).values()); // filter // duplicates assertEquals("There should be a conflict with other date values", 1, values.size()); assertTrue("No conflict with long date", values.contains(longDate)); @@ -68,4 +85,164 @@ public void TestDateImportance() { assertTrue("Available format conflict not found", values.contains(availableFormat)); assertTrue("Date format conflict not found", values.contains(fullDate)); } + + static final String stockPathPrefix = + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateFormats/dateFormatLength"; + static final String availableFormatPathPrefix = + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/availableFormats/dateFormatItem"; + static final String intervalFormatPathPrefix = + "//ldml/dates/calendars/calendar[@type=\"gregorian\"]/dateTimeFormats/intervalFormats/"; + + public void TestIso8601() { + ICUServiceBuilder isb = ICUServiceBuilder.forLocale(CLDRLocale.getInstance("en")); + CLDRFile english = CLDRConfig.getInstance().getEnglish(); + Factory phf = PathHeader.getFactory(); + Set paths = new TreeSet<>(); + for (String path : english) { + if (!path.startsWith("//ldml/dates/calendars/calendar[@type=\"gregorian\"]")) { + continue; + } else if (path.startsWith(stockPathPrefix)) { + if (!path.contains("datetimeSkeleton")) { + paths.add(phf.fromPath(path)); + } + } else if (path.startsWith(availableFormatPathPrefix)) { + paths.add(phf.fromPath(path)); + } else if (path.startsWith(intervalFormatPathPrefix)) { + if (!path.contains("intervalFormatFallback")) { + paths.add(phf.fromPath(path)); + } + } else { + int debug = 0; + } + } + Date sample = new Date(2024 - 1900, 0, 9, 19, 8, 9); + System.out.println(); + for (PathHeader pathHeader : paths) { + final String originalPath = pathHeader.getOriginalPath(); + String gregPat = english.getStringValue(originalPath); + String isoPat = + english.getStringValue(originalPath.replace("\"gregorian\"", "\"iso8601\"")); + String gregFormatted = null; + String isoFormatted = null; + + if (originalPath.contains("intervalFormats")) { + Date sample1 = (Date) sample.clone(); + Date sample2 = (Date) sample.clone(); + XPathParts parts = XPathParts.getFrozenInstance(originalPath); + String greatestDifference = parts.getAttributeValue(-1, "id"); + + switch (greatestDifference) { + case "G": + sample1.setYear(-sample2.getYear()); + break; + case "y": + sample2.setYear(sample2.getYear() + 1); + break; + case "M": + sample2.setMonth(sample2.getMonth() + 1); + break; + case "d": + sample2.setDate(sample2.getDate() + 1); + break; + case "h": + case "H": + sample2.setHours(sample2.getHours() + 1); + break; + case "a": + case "B": + sample2.setHours(sample2.getHours() + 12); + break; + case "m": + sample2.setMinutes(sample2.getMinutes() + 1); + break; + case "s": + sample2.setSeconds(sample2.getSeconds() + 1); + break; + default: + System.out.println("Missing" + greatestDifference); + break; + } + + gregFormatted = formatInterval(isb, sample1, sample2, gregPat); + isoFormatted = formatInterval(isb, sample1, sample2, isoPat); + } else { + SimpleDateFormat gregFormat = isb.getDateFormat("gregorian", gregPat); + SimpleDateFormat isoFormat = isb.getDateFormat("iso8601", isoPat); + + gregFormatted = gregFormat.format(sample); + isoFormatted = isoFormat.format(sample); + } + System.out.println( + JOIN_TAB.join( + pathHeader.getCode(), gregPat, gregFormatted, isoPat, isoFormatted)); + } + } + + public String formatInterval(ICUServiceBuilder isb, Date sample, Date sample2, String gregPat) { + List parts = splitIntervalPattern(gregPat); + SimpleDateFormat gregFormat1 = isb.getDateFormat("gregorian", parts.get(0)); + SimpleDateFormat gregFormat2 = isb.getDateFormat("gregorian", parts.get(2)); + return gregFormat1.format(sample) + parts.get(1) + gregFormat2.format(sample2); + } + + private List splitIntervalPattern(String intervalPattern) { + DateTimePatternGenerator.FormatParser parser = new DateTimePatternGenerator.FormatParser(); + List result = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + Set soFar = new HashSet<>(); + + // we have something of the form (literal? field)* sepLiteral (field literal?)* + // that is, there are never 2 literals in a row. + // a literal is a sepLiteral if the field after it is already present (or rather, if its + // type is) + String lastString = null; + + for (Object p : parser.set(intervalPattern).getItems()) { + if (p instanceof String) { + lastString = (String) p; + } else if (p instanceof VariableField) { + VariableField pv = (VariableField) p; + if (soFar != null && soFar.contains(pv.getType())) { + // we hit the first repeated field + result.add(current.toString()); + current.setLength(0); + result.add( + lastString == null + ? "" + : lastString); // it would be strange to have "", but... + lastString = null; + soFar = null; + } else { + if (soFar != null) { + soFar.add(pv.getType()); + } + if (lastString != null) { + current.append(quoteIfNeeded(lastString)); + lastString = null; + } + } + current.append(p); + } else { + throw new IllegalArgumentException(); + } + } + if (lastString != null) { + current.append(quoteIfNeeded(lastString)); + } + result.add(current.toString()); + if (result.size() != 3) { + throw new IllegalArgumentException(); + } + return result; + } + + static final UnicodeSet VARIABLE = new UnicodeSet("[a-zA-Z']").freeze(); + + private Object quoteIfNeeded(String lastString) { + if (VARIABLE.containsSome(lastString)) { + lastString = lastString.replace("'", "''"); + lastString = "'" + lastString + "'"; + } + return lastString; + } }