From 229b63527315f96216671946e6fd9260a9e0513e Mon Sep 17 00:00:00 2001
From: Peter Edberg <42151464+pedberg-icu@users.noreply.github.com>
Date: Tue, 9 Jul 2024 10:57:36 -0700
Subject: [PATCH] CLDR-16933 Allow names for noon, midnight to match adjacent
non-fixed dayPeriods; add tests & spec note (#3857)
---
docs/ldml/tr35-dates.md | 4 +-
docs/ldml/tr35.md | 6 ++-
.../org/unicode/cldr/test/CheckDates.java | 9 +++-
.../org/unicode/cldr/util/DayPeriodInfo.java | 36 ++++++++++++++++
.../unittest/TestCheckDisplayCollisions.java | 42 +++++++++++++++++++
5 files changed, 92 insertions(+), 5 deletions(-)
diff --git a/docs/ldml/tr35-dates.md b/docs/ldml/tr35-dates.md
index 0e63be46c8c..77c8bf09b50 100644
--- a/docs/ldml/tr35-dates.md
+++ b/docs/ldml/tr35-dates.md
@@ -439,7 +439,9 @@ The former `am`/`pm` elements have been deprecated, and replaced by the more fle
These behave like months, days, and so on in terms of having context and width. Each locale has an associated dayPeriodRuleSet in the supplemental data, rules that specify when the day periods start and end for that locale. Each type in the rules needs to have a translation in a dayPeriod (but if translation data is missing for a particular variable dayPeriod in the locale’s language and script, formatting should fall back to using the am/pm values). For more information, see _[Day Period Rules](#Day_Period_Rules)_.
-The dayPeriod names should be distinct within each of the context/width combinations, including narrow; as with era names, there is less disambiguating information for them, and they are more likely to be used in a format that requires parsing. In some unambiguous cases, it is acceptable for certain overlapping dayPeriods to be the same, such as the names for "am" and "morning", or the names for "pm" and "afternoon".
+The dayPeriod names should be distinct within each of the context/width combinations, including narrow; as with era names, there is less disambiguating information for them, and they are more likely to be used in a format that requires parsing. In some unambiguous cases, it is acceptable for certain overlapping dayPeriods to be the same, such as the names for `am` and `morning`, or the names for `pm` and `afternoon`.
+
+If dayPeriods are specified for `noon` and `midnight`, they can often be formatted without also specifying the numeric time, e.g. "May 6, noon" instead of "May 6, 12:00 noon" or "May 6, 12:00 PM". To prevent parse issues, this should only be done if the names for `noon` and `midnight` are not also used for any other day periods, such as for `morning2` or `night1`.
Example:
diff --git a/docs/ldml/tr35.md b/docs/ldml/tr35.md
index cafaddfeac8..c68f106d62c 100644
--- a/docs/ldml/tr35.md
+++ b/docs/ldml/tr35.md
@@ -5,7 +5,7 @@
|Version|46 (draft)|
|-------|----------|
|Editors|Mark Davis (markdavis@google.com) and other CLDR committee members|
-|Date|2024-04-22|
+|Date|2024-07-08|
|This Version|https://www.unicode.org/reports/tr35/tr35-73/tr35.html|
|Previous Version|https://www.unicode.org/reports/tr35/tr35-72/tr35.html|
|Latest Version|https://www.unicode.org/reports/tr35/|
@@ -4095,7 +4095,9 @@ Other contributors to CLDR are listed on the [CLDR Project Page](https://www.uni
**Differences from LDML Version 45**
-(in progress)
+* Part 4: [Dates](tr35-dates.md#Contents)
+ * In [Element dayPeriods](tr35-dates.md#dayPeriods), added a note on special formatting usable with
+ dayPeriods `noon` and `midnight`.
**Differences from LDML Version 44.1**
diff --git a/tools/cldr-code/src/main/java/org/unicode/cldr/test/CheckDates.java b/tools/cldr-code/src/main/java/org/unicode/cldr/test/CheckDates.java
index c61170383c4..82f1cec20c1 100644
--- a/tools/cldr-code/src/main/java/org/unicode/cldr/test/CheckDates.java
+++ b/tools/cldr-code/src/main/java/org/unicode/cldr/test/CheckDates.java
@@ -548,7 +548,9 @@ && isTooMuchWiderThan(value, abbrValue)
final boolean isDayPeriod = path.contains("dayPeriod");
if (isDayPeriod) {
XPathParts parts = XPathParts.getFrozenInstance(fullPath);
- type = Type.fromString(parts.getAttributeValue(5, "type"));
+ type =
+ Type.fromString(
+ parts.getAttributeValue(5, "type")); // format, stand-alone
dayPeriod = DayPeriod.valueOf(parts.getAttributeValue(-1, "type"));
}
@@ -577,7 +579,10 @@ && isTooMuchWiderThan(value, abbrValue)
}
if (isDayPeriod) {
// ldml/dates/calendars/calendar[@type="gregorian"]/dayPeriods/dayPeriodContext[@type="format"]/dayPeriodWidth[@type="wide"]/dayPeriod[@type="am"]
- Type itemType = Type.fromString(itemParts.getAttributeValue(5, "type"));
+ Type itemType =
+ Type.fromString(
+ itemParts.getAttributeValue(
+ 5, "type")); // format, stand-alone
DayPeriod itemDayPeriod =
DayPeriod.valueOf(itemParts.getAttributeValue(-1, "type"));
diff --git a/tools/cldr-code/src/main/java/org/unicode/cldr/util/DayPeriodInfo.java b/tools/cldr-code/src/main/java/org/unicode/cldr/util/DayPeriodInfo.java
index 7e532e79af7..8776b9c3cd3 100644
--- a/tools/cldr-code/src/main/java/org/unicode/cldr/util/DayPeriodInfo.java
+++ b/tools/cldr-code/src/main/java/org/unicode/cldr/util/DayPeriodInfo.java
@@ -459,6 +459,42 @@ public boolean collisionIsError(
}
}
+ // Fix for CLDR-16933, for format types only
+ // Let midnight match a non-fixed period that starts at, ends at, or contains midnight (both
+ // versions);
+ // Let noon match a non-fixed period that starts at, ends at, or contains noon (or just
+ // before noon);
+ if (type1 == Type.format && type2 == Type.format) {
+ if (dayPeriod1 == DayPeriod.midnight && !dayPeriod2.isFixed()) {
+ for (Span s : dayPeriodsToSpans.get(dayPeriod2)) {
+ if (s.contains(MIDNIGHT) || s.contains(DAY_LIMIT)) {
+ return false;
+ }
+ }
+ }
+ if (dayPeriod2 == DayPeriod.midnight && !dayPeriod1.isFixed()) {
+ for (Span s : dayPeriodsToSpans.get(dayPeriod1)) {
+ if (s.contains(MIDNIGHT) || s.contains(DAY_LIMIT)) {
+ return false;
+ }
+ }
+ }
+ if (dayPeriod1 == DayPeriod.noon && !dayPeriod2.isFixed()) {
+ for (Span s : dayPeriodsToSpans.get(dayPeriod2)) {
+ if (s.contains(NOON) || s.contains(NOON - 1)) {
+ return false;
+ }
+ }
+ }
+ if (dayPeriod2 == DayPeriod.noon && !dayPeriod1.isFixed()) {
+ for (Span s : dayPeriodsToSpans.get(dayPeriod1)) {
+ if (s.contains(NOON) || s.contains(NOON - 1)) {
+ return false;
+ }
+ }
+ }
+ }
+
// we use the more lenient if they are mixed types
if (type2 == Type.format) {
type1 = Type.format;
diff --git a/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestCheckDisplayCollisions.java b/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestCheckDisplayCollisions.java
index 09cc8f1a22c..984580bf54f 100644
--- a/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestCheckDisplayCollisions.java
+++ b/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestCheckDisplayCollisions.java
@@ -233,6 +233,48 @@ public void TestDotPixel14031() {
checkDisplayCollisions("de", pathValuePairs, factory);
}
+ public void checkDayPeriodCollisions() {
+ // CLDR-16933 (uk, ...)
+ // Let midnight match a non-fixed period that starts at, ends at, or contains midnight (both
+ // versions);
+ // Let noon match a non-fixed period that starts at, ends at, or contains noon (or just
+ // before noon);
+ Map ukPathValuePairs =
+ ImmutableMap.of(
+ "ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\"format\"]/dayPeriodWidth[@type=\"abbreviated\"]/dayPeriod[@type=\"midnight\"]",
+ "ночі",
+ "ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\"format\"]/dayPeriodWidth[@type=\"abbreviated\"]/dayPeriod[@type=\"night1\"]",
+ "ночі",
+ "ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\"format\"]/dayPeriodWidth[@type=\"abbreviated\"]/dayPeriod[@type=\"noon\"]",
+ "дня",
+ "ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\"format\"]/dayPeriodWidth[@type=\"abbreviated\"]/dayPeriod[@type=\"afternoon1\"]",
+ "дня");
+ TestFactory ukFactory = makeFakeCldrFile("uk", ukPathValuePairs);
+ checkDisplayCollisions("uk", ukPathValuePairs, ukFactory);
+
+ // CLDR-17132 (fr, ...)
+ // Let night1 have the same name as morning1/am if night1 starts at 00:00
+ Map frPathValuePairs =
+ ImmutableMap.of(
+ "ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\"format\"]/dayPeriodWidth[@type=\"abbreviated\"]/dayPeriod[@type=\"morning1\"]",
+ "matin",
+ "ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\"format\"]/dayPeriodWidth[@type=\"abbreviated\"]/dayPeriod[@type=\"night1\"]",
+ "matin");
+ TestFactory frFactory = makeFakeCldrFile("fr", frPathValuePairs);
+ checkDisplayCollisions("fr", frPathValuePairs, frFactory);
+
+ // CLDR-17139 (fil, ...)
+ // Let night1 have the same name as evening1 if night1 ends at 24:00
+ Map filPathValuePairs =
+ ImmutableMap.of(
+ "ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\"format\"]/dayPeriodWidth[@type=\"abbreviated\"]/dayPeriod[@type=\"evening1\"]",
+ "ng gabi",
+ "ldml/dates/calendars/calendar[@type=\"gregorian\"]/dayPeriods/dayPeriodContext[@type=\"format\"]/dayPeriodWidth[@type=\"abbreviated\"]/dayPeriod[@type=\"night1\"]",
+ "ng gabi");
+ TestFactory filFactory = makeFakeCldrFile("fil", filPathValuePairs);
+ checkDisplayCollisions("fil", filPathValuePairs, filFactory);
+ }
+
public void checkDisplayCollisions(
String locale, Map pathValuePairs, TestFactory factory) {
CheckDisplayCollisions cdc = new CheckDisplayCollisions(factory);