Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8247781: Day periods support #938

Closed
wants to merge 18 commits into from
Closed
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1485,8 +1485,11 @@ public DateTimeFormatterBuilder appendLiteral(String literal) {
*/
public DateTimeFormatterBuilder appendDayPeriodText(TextStyle style) {
Objects.requireNonNull(style, "style");
if (style != TextStyle.FULL && style != TextStyle.SHORT && style != TextStyle.NARROW) {
throw new IllegalArgumentException("Style must be either full, short, or narrow");
switch (style) {
// Stand-alone is not applicable. Convert to standard text style
case FULL_STANDALONE -> style = TextStyle.FULL;
case SHORT_STANDALONE -> style = TextStyle.SHORT;
case NARROW_STANDALONE -> style = TextStyle.NARROW;
}
appendInternal(new DayPeriodPrinterParser(style));
return this;
@@ -1866,7 +1869,7 @@ private void parsePattern(String pattern) {
}
} else if (cur == 'B') {
switch (count) {
case 1, 2, 3 -> appendDayPeriodText(TextStyle.SHORT);
case 1 -> appendDayPeriodText(TextStyle.SHORT);
case 4 -> appendDayPeriodText(TextStyle.FULL);
case 5 -> appendDayPeriodText(TextStyle.NARROW);
default -> throw new IllegalArgumentException("Too many pattern letters: " + cur);
@@ -5091,7 +5094,7 @@ public int parse(DateTimeParseContext context, CharSequence parseText, int posit

@Override
public String toString() {
return "Text(DayPeriod," + textStyle + ")";
return "DayPeriod(" + textStyle + ")";
}

/**
@@ -5133,6 +5136,7 @@ private static LocaleStore findDayPeriodStore(Locale locale) {
/**
* DayPeriod class that represents a
* <a href="https://www.unicode.org/reports/tr35/tr35-dates.html#dayPeriods">DayPeriod</a> defined in CLDR.
* This is a value-based class.
*/
static final class DayPeriod {
This conversation was marked as resolved by naotoj

This comment has been minimized.

@jodastephen

jodastephen Oct 30, 2020
Contributor

Looks like this class could be ValueBased as per other PRs

This comment has been minimized.

@naotoj

naotoj Oct 30, 2020
Author Member

Fixed.

/**
@@ -5169,7 +5173,7 @@ private static LocaleStore findDayPeriodStore(Locale locale) {
* @param to "to" in minute-of-day
* @param index day period type index
*/
DayPeriod(long from, long to, long index) {
private DayPeriod(long from, long to, long index) {
this.from = from;
this.to = to;
this.index = index;
@@ -5249,7 +5253,7 @@ static long mapToIndex(String type) {
LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
.getLocaleResources(CalendarDataUtility.findRegionOverride(l));
String dayPeriodRules = lr.getRules()[1];
final Map<DayPeriod, Long> pm = new ConcurrentHashMap<>();
final Map<DayPeriod, Long> periodMap = new ConcurrentHashMap<>();
Arrays.stream(dayPeriodRules.split(";"))
.forEach(rule -> {
Matcher m = RULE.matcher(rule);
@@ -5260,7 +5264,7 @@ static long mapToIndex(String type) {
if (to == null) {
to = from;
}
pm.putIfAbsent(
periodMap.putIfAbsent(
new DayPeriod(
Long.parseLong(from) * 60,
Long.parseLong(to) * 60,
@@ -5270,9 +5274,9 @@ static long mapToIndex(String type) {
});

// add am/pm
pm.putIfAbsent(new DayPeriod(0, 720, 0), 0L);
pm.putIfAbsent(new DayPeriod(720, 1_440, 1), 1L);
return pm;
periodMap.putIfAbsent(new DayPeriod(0, 720, 0), 0L);
periodMap.putIfAbsent(new DayPeriod(720, 1_440, 1), 1L);
return periodMap;
});
}

@@ -5286,7 +5290,9 @@ static DayPeriod ofLocale(Locale locale, long index) {
return getDayPeriodMap(locale).keySet().stream()
.filter(dp -> dp.getIndex() == index)
.findAny()
.orElseThrow();
.orElseThrow(() -> new DateTimeException(
"DayPeriod could not be determined for the locale " +
locale + " at type index " + index));
}

@Override
@@ -5303,5 +5309,11 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(from, to, index);
}

@Override
public String toString() {
return "DayPeriod(%02d:%02d".formatted(from / 60, from % 60) +
(from == to ? ")" : "-%02d:%02d)".formatted(to / 60, to % 60));
}
}
}
@@ -344,12 +344,14 @@ private void updateCheckDayPeriodConflict(TemporalField changeField, Long change
if (old != null && old.longValue() != changeValue.longValue()) {
throw new DateTimeException("Conflict found: " + changeField + " " + old +
" differs from " + changeField + " " + changeValue +
" while resolving " + dayPeriod);
" while resolving " + dayPeriod);
}
long mod = fieldValues.get(HOUR_OF_DAY) * 60 +
(fieldValues.containsKey(MINUTE_OF_HOUR) ? fieldValues.get(MINUTE_OF_HOUR) : 0);
long hod = fieldValues.get(HOUR_OF_DAY);
long moh = fieldValues.containsKey(MINUTE_OF_HOUR) ? fieldValues.get(MINUTE_OF_HOUR) : 0;
long mod = hod * 60 + moh;
if (!dayPeriod.includes(mod)) {
throw new DateTimeException("Conflict found: " + changeField + " conflict with day period");
throw new DateTimeException("Conflict found: Resolved time %02d:%02d".formatted(hod, moh) +
" conflicts with " + dayPeriod);
}
}
}
@@ -81,6 +81,7 @@
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.FormatStyle;
import java.time.format.ResolverStyle;
import java.time.format.SignStyle;
import java.time.format.TextStyle;
import java.time.temporal.ChronoField;
@@ -537,7 +538,7 @@ public void test_appendZoneText_1arg_nullText() throws Exception {
public void test_appendDayPeriodText_1arg() throws Exception {
builder.appendDayPeriodText(TextStyle.FULL);
DateTimeFormatter f = builder.toFormatter();
assertEquals(f.toString(), "Text(DayPeriod,FULL)");
assertEquals(f.toString(), "DayPeriod(FULL)");
}

@Test(expectedExceptions=NullPointerException.class)
@@ -667,46 +668,77 @@ public void test_dayPeriodParsePattern(String pattern, String hourDayPeriod, lon
@DataProvider(name="dayPeriodParseInvalid")
Object[][] data_dayPeriodParseInvalid() {
return new Object[][] {
{TextStyle.FULL, Locale.US, "00:01 midnight"},
{TextStyle.FULL, Locale.US, "06:01 at night"},
{TextStyle.FULL, Locale.US, "05:59 in the morning"},
{TextStyle.FULL, Locale.US, "11:59 noon"},
{TextStyle.FULL, Locale.US, "18:00 in the afternoon"},
{TextStyle.FULL, Locale.US, "17:59 in the evening"},
{TextStyle.NARROW, Locale.US, "00:01 mi"},
{TextStyle.NARROW, Locale.US, "06:01 at night"},
{TextStyle.NARROW, Locale.US, "05:59 in the morning"},
{TextStyle.NARROW, Locale.US, "11:59 n"},
{TextStyle.NARROW, Locale.US, "18:00 in the afternoon"},
{TextStyle.NARROW, Locale.US, "17:59 in the evening"},

{TextStyle.FULL, Locale.JAPAN, "00:01 \u771f\u591c\u4e2d"},
{TextStyle.FULL, Locale.JAPAN, "04:00 \u591c\u4e2d"},
{TextStyle.FULL, Locale.JAPAN, "03:59 \u671d"},
{TextStyle.FULL, Locale.JAPAN, "12:01 \u6b63\u5348"},
{TextStyle.FULL, Locale.JAPAN, "16:00 \u663c"},
{TextStyle.FULL, Locale.JAPAN, "19:01 \u5915\u65b9"},
{TextStyle.FULL, Locale.JAPAN, "23:00 \u591c"},
{TextStyle.NARROW, Locale.JAPAN, "00:01 \u771f\u591c\u4e2d"},
{TextStyle.NARROW, Locale.JAPAN, "04:00 \u591c\u4e2d"},
{TextStyle.NARROW, Locale.JAPAN, "03:59 \u671d"},
{TextStyle.NARROW, Locale.JAPAN, "12:01 \u6b63\u5348"},
{TextStyle.NARROW, Locale.JAPAN, "16:00 \u663c"},
{TextStyle.NARROW, Locale.JAPAN, "19:01 \u5915\u65b9"},
{TextStyle.NARROW, Locale.JAPAN, "23:00 \u591c"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.US, "00:01 midnight", "00:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.US, "06:01 at night", "21:00-06:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.US, "05:59 in the morning", "06:00-12:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.US, "11:59 noon", "12:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.US, "18:00 in the afternoon", "12:00-18:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.US, "17:59 in the evening", "18:00-21:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "00:01 mi", "00:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "06:01 at night", "21:00-06:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "05:59 in the morning", "06:00-12:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "11:59 n", "12:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "18:00 in the afternoon", "12:00-18:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "17:59 in the evening", "18:00-21:00"},

{TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "00:01 \u771f\u591c\u4e2d", "00:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "04:00 \u591c\u4e2d", "23:00-04:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "03:59 \u671d", "04:00-12:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "12:01 \u6b63\u5348", "12:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "16:00 \u663c", "12:00-16:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "19:01 \u5915\u65b9", "16:00-19:00"},
{TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "23:00 \u591c", "19:00-23:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "00:01 \u771f\u591c\u4e2d", "00:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "04:00 \u591c\u4e2d", "23:00-04:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "03:59 \u671d", "04:00-12:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "12:01 \u6b63\u5348", "12:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "16:00 \u663c", "12:00-16:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "19:01 \u5915\u65b9", "16:00-19:00"},
{TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "23:00 \u591c", "19:00-23:00"},

{TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "00:01 midnight", "00:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "06:01 at night", "21:00-06:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "05:59 in the morning", "06:00-12:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "11:59 noon", "12:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "18:00 in the afternoon", "12:00-18:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "17:59 in the evening", "18:00-21:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "00:01 mi", "00:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "06:01 at night", "21:00-06:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "05:59 in the morning", "06:00-12:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "11:59 n", "12:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "18:00 in the afternoon", "12:00-18:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "17:59 in the evening", "18:00-21:00"},

{TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "00:01 \u771f\u591c\u4e2d", "00:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "04:00 \u591c\u4e2d", "23:00-04:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "03:59 \u671d", "04:00-12:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "12:01 \u6b63\u5348", "12:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "16:00 \u663c", "12:00-16:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "19:01 \u5915\u65b9", "16:00-19:00"},
{TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "23:00 \u591c", "19:00-23:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "00:01 \u771f\u591c\u4e2d", "00:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "04:00 \u591c\u4e2d", "23:00-04:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "03:59 \u671d", "04:00-12:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "12:01 \u6b63\u5348", "12:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "16:00 \u663c", "12:00-16:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "19:01 \u5915\u65b9", "16:00-19:00"},
{TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "23:00 \u591c", "19:00-23:00"},
};
}
@Test (dataProvider="dayPeriodParseInvalid")
public void test_dayPeriodParseInvalid(TextStyle ts, Locale l, String dayPeriod) throws Exception {
public void test_dayPeriodParseInvalid(TextStyle ts, ResolverStyle rs, Locale l, String dayPeriod, String periodRange) throws Exception {
try {
builder.append(ISO_LOCAL_TIME).appendLiteral(' ').appendDayPeriodText(ts)
.toFormatter()
.withLocale(l)
.parse(dayPeriod);
throw new RuntimeException("DateTimeParseException should be thrown");
if (rs != ResolverStyle.LENIENT) {
throw new RuntimeException("DateTimeParseException should be thrown");
}
} catch (DateTimeParseException e) {
assertEquals(e.getCause().getMessage(),
"Conflict found: " + HOUR_OF_DAY + " conflict with day period");
"Conflict found: Resolved time " + dayPeriod.substring(0, 5) + " conflicts with " +
"DayPeriod(" + periodRange + ")");
}
}

This conversation was marked as resolved by naotoj

This comment has been minimized.

@jodastephen

jodastephen Oct 30, 2020
Contributor

There don't seem to be any tests of the resolver logic in Parsed.

This comment has been minimized.

@naotoj

naotoj Oct 30, 2020
Author Member

Test case added.

@@ -977,11 +1009,9 @@ public void test_optionalEnd_noStart() throws Exception {
{"ww", "Localized(WeekOfWeekBasedYear,2)"},
{"W", "Localized(WeekOfMonth,1)"},

{"B", "Text(DayPeriod,SHORT)"},
{"BB", "Text(DayPeriod,SHORT)"},
{"BBB", "Text(DayPeriod,SHORT)"},
{"BBBB", "Text(DayPeriod,FULL)"},
{"BBBBB", "Text(DayPeriod,NARROW)"},
{"B", "DayPeriod(SHORT)"},
{"BBBB", "DayPeriod(FULL)"},
{"BBBBB", "DayPeriod(NARROW)"},
};
}

@@ -1058,6 +1088,8 @@ public void test_appendPattern_valid(String input, String expected) throws Excep
{"www"},
{"WW"},

{"BB"},
{"BBB"},
{"BBBBBB"},
};
}