diff --git a/make/jdk/src/classes/build/tools/cldrconverter/Bundle.java b/make/jdk/src/classes/build/tools/cldrconverter/Bundle.java index 4331cdcb77f..a51fe64ecac 100644 --- a/make/jdk/src/classes/build/tools/cldrconverter/Bundle.java +++ b/make/jdk/src/classes/build/tools/cldrconverter/Bundle.java @@ -375,6 +375,16 @@ class Bundle { } } + // rules + String rule = CLDRConverter.pluralRules.get(id); + if (rule != null) { + myMap.put("PluralRules", rule); + } + rule = CLDRConverter.dayPeriodRules.get(id); + if (rule != null) { + myMap.put("DayPeriodRules", rule); + } + // Remove all duplicates if (Objects.nonNull(parentsMap)) { for (Iterator it = myMap.keySet().iterator(); it.hasNext();) { @@ -522,8 +532,6 @@ class Bundle { if (pattern != null) { // Perform date-time format pattern conversion which is // applicable to both SimpleDateFormat and j.t.f.DateTimeFormatter. - // For example, character 'B' is mapped with 'a', as 'B' is not - // supported in either SimpleDateFormat or j.t.f.DateTimeFormatter String transPattern = translateDateFormatLetters(calendarType, pattern, this::convertDateTimePatternLetter); dateTimePatterns.add(i, transPattern); // Additionally, perform SDF specific date-time format pattern conversion @@ -653,17 +661,6 @@ class Bundle { // as the best approximation appendN('y', count, sb); break; - case 'B': - // 'B' character (day period) is not supported by - // SimpleDateFormat and j.t.f.DateTimeFormatter, - // this is a workaround in which 'B' character - // appearing in CLDR date-time pattern is replaced - // with 'a' character and hence resolved with am/pm strings. - // This workaround is based on the the fallback mechanism - // specified in LDML spec for 'B' character, when a locale - // does not have data for day period ('B') - appendN('a', count, sb); - break; default: appendN(cldrLetter, count, sb); break; @@ -720,6 +717,17 @@ class Bundle { } break; + case 'B': + // 'B' character (day period) is not supported by SimpleDateFormat, + // this is a workaround in which 'B' character + // appearing in CLDR date-time pattern is replaced + // with 'a' character and hence resolved with am/pm strings. + // This workaround is based on the the fallback mechanism + // specified in LDML spec for 'B' character, when a locale + // does not have data for day period ('B') + appendN('a', count, sb); + break; + default: appendN(cldrLetter, count, sb); break; diff --git a/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java b/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java index 10d8c8fd547..4e801b7b3fc 100644 --- a/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java +++ b/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java @@ -70,6 +70,7 @@ public class CLDRConverter { private static String TIMEZONE_SOURCE_FILE; private static String WINZONES_SOURCE_FILE; private static String PLURALS_SOURCE_FILE; + private static String DAYPERIODRULE_SOURCE_FILE; static String DESTINATION_DIR = "build/gensrc"; static final String LOCALE_NAME_PREFIX = "locale.displayname."; @@ -100,6 +101,7 @@ public class CLDRConverter { static NumberingSystemsParseHandler handlerNumbering; static MetaZonesParseHandler handlerMetaZones; static TimeZoneParseHandler handlerTimeZone; + static DayPeriodRuleParseHandler handlerDayPeriodRule; private static BundleGenerator bundleGenerator; // java.base module related @@ -116,6 +118,10 @@ public class CLDRConverter { private static String tzDataDir; private static final Map canonicalTZMap = new HashMap<>(); + // rules maps + static Map pluralRules; + static Map dayPeriodRules; + static enum DraftType { UNCONFIRMED, PROVISIONAL, @@ -248,6 +254,7 @@ public class CLDRConverter { SPPL_META_SOURCE_FILE = CLDR_BASE + "/supplemental/supplementalMetadata.xml"; WINZONES_SOURCE_FILE = CLDR_BASE + "/supplemental/windowsZones.xml"; PLURALS_SOURCE_FILE = CLDR_BASE + "/supplemental/plurals.xml"; + DAYPERIODRULE_SOURCE_FILE = CLDR_BASE + "/supplemental/dayPeriods.xml"; if (BASE_LOCALES.isEmpty()) { setupBaseLocales("en-US"); @@ -259,6 +266,10 @@ public class CLDRConverter { parseSupplemental(); parseBCP47(); + // rules maps + pluralRules = generateRules(handlerPlurals); + dayPeriodRules = generateRules(handlerDayPeriodRule); + List bundles = readBundleList(); convertBundles(bundles); @@ -268,9 +279,6 @@ public class CLDRConverter { // Generate Windows tzmappings generateWindowsTZMappings(); - - // Generate Plural rules - generatePluralRules(); } } @@ -462,6 +470,10 @@ public class CLDRConverter { // Parse plurals handlerPlurals = new PluralsParseHandler(); parseLDMLFile(new File(PLURALS_SOURCE_FILE), handlerPlurals); + + // Parse day period rules + handlerDayPeriodRule = new DayPeriodRuleParseHandler(); + parseLDMLFile(new File(DAYPERIODRULE_SOURCE_FILE), handlerDayPeriodRule); } // Parsers for data in "bcp47" directory @@ -809,7 +821,9 @@ public class CLDRConverter { "TimePatterns", "DatePatterns", "DateTimePatterns", - "DateTimePatternChars" + "DateTimePatternChars", + "PluralRules", + "DayPeriodRules", }; private static Map extractFormatData(Map map, String id) { @@ -1125,49 +1139,21 @@ public class CLDRConverter { } /** - * Generate ResourceBundle source file for plural rules. The generated - * class is {@code sun.text.resources.PluralRules} which has one public - * two dimensional array {@code rulesArray}. Each array element consists - * of two elements that designate the locale and the locale's plural rules - * string. The latter has the syntax from Unicode Consortium's - * - * Plural rules syntax. {@code samples} and {@code "other"} are being ommited. - * - * @throws Exception + * Generates rules map for Plural rules and DayPeriod rules. The key is the locale id, + * and the value is rules, defined by the LDML spec. Each rule consists of {@code type:rule} + * notation, concatenated with a ";" as a delimiter. + * @param handler handler containing rules + * @return the map */ - private static void generatePluralRules() throws Exception { - Files.createDirectories(Paths.get(DESTINATION_DIR, "sun", "text", "resources")); - Files.write(Paths.get(DESTINATION_DIR, "sun", "text", "resources", "PluralRules.java"), - Stream.concat( - Stream.concat( - Stream.of( - "package sun.text.resources;", - "public final class PluralRules {", - " public static final String[][] rulesArray = {" - ), - pluralRulesStream().sorted() - ), - Stream.of( - " };", - "}" - ) - ) - .collect(Collectors.toList()), - StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - } - - private static Stream pluralRulesStream() { - return handlerPlurals.getData().entrySet().stream() - .filter(e -> !(e.getValue()).isEmpty()) - .map(e -> { - String loc = e.getKey(); + private static Map generateRules(AbstractLDMLHandler> handler) { + return handler.getData().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> { Map rules = e.getValue(); - return " {\"" + loc + "\", \"" + - rules.entrySet().stream() - .map(rule -> rule.getKey() + ":" + rule.getValue().replaceFirst("@.*", "")) - .map(String::trim) - .collect(Collectors.joining(";")) + "\"},"; - }); + return rules.entrySet().stream() + .map(rule -> rule.getKey() + ":" + rule.getValue().replaceFirst("@.*", "")) + .map(String::trim) + .collect(Collectors.joining(";")); + })); } // for debug @@ -1188,4 +1174,3 @@ public class CLDRConverter { .forEach(System.out::println); } } - diff --git a/make/jdk/src/classes/build/tools/cldrconverter/DayPeriodRuleParseHandler.java b/make/jdk/src/classes/build/tools/cldrconverter/DayPeriodRuleParseHandler.java new file mode 100644 index 00000000000..912e3061740 --- /dev/null +++ b/make/jdk/src/classes/build/tools/cldrconverter/DayPeriodRuleParseHandler.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package build.tools.cldrconverter; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Handles parsing of dayPeriods.xml for day period rules. + */ + +class DayPeriodRuleParseHandler extends AbstractLDMLHandler> { + + @Override + public InputSource resolveEntity(String publicID, String systemID) throws IOException, SAXException { + // avoid HTTP traffic to unicode.org + if (systemID.startsWith(CLDRConverter.SPPL_LDML_DTD_SYSTEM_ID)) { + return new InputSource((new File(CLDRConverter.LOCAL_SPPL_LDML_DTD)).toURI().toString()); + } + return null; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + switch (qName) { + case "dayPeriodRuleSet": + if (attributes.getValue("type") != null) { + // ignore "selection" + pushIgnoredContainer(qName); + } else { + pushContainer(qName, attributes); + } + break; + case "dayPeriodRules": + if (!isIgnored(attributes)) { + pushKeyContainer(qName, attributes, attributes.getValue("locales")); + } else { + pushIgnoredContainer(qName); + } + break; + case "dayPeriodRule": + if (!isIgnored(attributes) && currentContainer instanceof KeyContainer) { + String at = attributes.getValue("at"); + String from = attributes.getValue("from"); + String before = attributes.getValue("before"); + String output = ""; + if (at == null || at.isEmpty()) { + output = from + "-" + before; + } else { + output = at; + } + pushStringEntry(qName, attributes, attributes.getValue("type"), output); + } else { + pushIgnoredContainer(qName); + } + break; + default: + // treat anything else as a container + pushContainer(qName, attributes); + break; + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + assert qName.equals(currentContainer.getqName()) : "current=" + currentContainer.getqName() + ", param=" + qName; + switch (qName) { + case "dayPeriodRule": + if (currentContainer instanceof Entry) { + Entry entry = (Entry) currentContainer; + final String type = entry.getKey(); + final String rule = (String) entry.getValue(); + String locales = ((KeyContainer) (currentContainer.getParent())).getKey(); + Arrays.stream(locales.split("\\s")) + .forEach(loc -> { + Map rules = get(loc); + if (rules == null) { + rules = new HashMap<>(); + put(loc, rules); + } + rules.put(type, rule); + }); + } + break; + } + + currentContainer = currentContainer.getParent(); + } +} diff --git a/make/jdk/src/classes/build/tools/cldrconverter/LDMLParseHandler.java b/make/jdk/src/classes/build/tools/cldrconverter/LDMLParseHandler.java index 5a348305afa..745796d96cf 100644 --- a/make/jdk/src/classes/build/tools/cldrconverter/LDMLParseHandler.java +++ b/make/jdk/src/classes/build/tools/cldrconverter/LDMLParseHandler.java @@ -300,8 +300,6 @@ class LDMLParseHandler extends AbstractLDMLHandler { pushStringArrayElement(qName, attributes, Integer.parseInt(DAY_OF_WEEK_MAP.get(attributes.getValue("type"))) - 1); break; case "dayPeriodContext": - // for FormatData - // need to keep stand-alone and format, to allow for multiple inheritance in CLDR // for FormatData // need to keep stand-alone and format, to allow for multiple inheritance in CLDR { @@ -316,17 +314,17 @@ class LDMLParseHandler extends AbstractLDMLHandler { break; case "dayPeriodWidth": // for FormatData - // create string array entry for am/pm. only keeping wide + // create string array entry for am/pm. currentWidth = attributes.getValue("type"); switch (currentWidth) { case "wide": - pushStringArrayEntry(qName, attributes, "AmPmMarkers/" + getContainerKey(), 2); + pushStringArrayEntry(qName, attributes, "AmPmMarkers/" + getContainerKey(), 12); break; case "narrow": - pushStringArrayEntry(qName, attributes, "narrow.AmPmMarkers/" + getContainerKey(), 2); + pushStringArrayEntry(qName, attributes, "narrow.AmPmMarkers/" + getContainerKey(), 12); break; case "abbreviated": - pushStringArrayEntry(qName, attributes, "abbreviated.AmPmMarkers/" + getContainerKey(), 2); + pushStringArrayEntry(qName, attributes, "abbreviated.AmPmMarkers/" + getContainerKey(), 12); break; default: pushIgnoredContainer(qName); @@ -344,6 +342,36 @@ class LDMLParseHandler extends AbstractLDMLHandler { case "pm": pushStringArrayElement(qName, attributes, 1); break; + case "midnight": + pushStringArrayElement(qName, attributes, 2); + break; + case "noon": + pushStringArrayElement(qName, attributes, 3); + break; + case "morning1": + pushStringArrayElement(qName, attributes, 4); + break; + case "morning2": + pushStringArrayElement(qName, attributes, 5); + break; + case "afternoon1": + pushStringArrayElement(qName, attributes, 6); + break; + case "afternoon2": + pushStringArrayElement(qName, attributes, 7); + break; + case "evening1": + pushStringArrayElement(qName, attributes, 8); + break; + case "evening2": + pushStringArrayElement(qName, attributes, 9); + break; + case "night1": + pushStringArrayElement(qName, attributes, 10); + break; + case "night2": + pushStringArrayElement(qName, attributes, 11); + break; default: pushIgnoredContainer(qName); break; @@ -971,6 +999,7 @@ class LDMLParseHandler extends AbstractLDMLHandler { } @Override + @SuppressWarnings("fallthrough") public void endElement(String uri, String localName, String qName) throws SAXException { assert qName.equals(currentContainer.getqName()) : "current=" + currentContainer.getqName() + ", param=" + qName; switch (qName) { diff --git a/make/jdk/src/classes/build/tools/cldrconverter/PluralsParseHandler.java b/make/jdk/src/classes/build/tools/cldrconverter/PluralsParseHandler.java index a4f07712390..3d1bc6ba0a7 100644 --- a/make/jdk/src/classes/build/tools/cldrconverter/PluralsParseHandler.java +++ b/make/jdk/src/classes/build/tools/cldrconverter/PluralsParseHandler.java @@ -84,19 +84,19 @@ class PluralsParseHandler extends AbstractLDMLHandler> { assert !(currentContainer instanceof Entry); Entry entry = (Entry)currentContainer; final String count = entry.getKey(); - final String rule = (String)entry.getValue(); - String locales = ((KeyContainer)(currentContainer.getParent())).getKey(); - Arrays.stream(locales.split("\\s")) + if (!count.equals("other")) { + final String rule = (String)entry.getValue(); + String locales = ((KeyContainer)(currentContainer.getParent())).getKey(); + Arrays.stream(locales.split("\\s")) .forEach(loc -> { Map rules = get(loc); if (rules == null) { rules = new HashMap<>(); put(loc, rules); } - if (!count.equals("other")) { - rules.put(count, rule); - } + rules.put(count, rule); }); + } break; } diff --git a/make/jdk/src/classes/build/tools/cldrconverter/ResourceBundleGenerator.java b/make/jdk/src/classes/build/tools/cldrconverter/ResourceBundleGenerator.java index 285f33610d1..9c95717edb2 100644 --- a/make/jdk/src/classes/build/tools/cldrconverter/ResourceBundleGenerator.java +++ b/make/jdk/src/classes/build/tools/cldrconverter/ResourceBundleGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ import java.util.Formatter; import java.util.HashSet; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Locale; import java.util.Objects; @@ -69,6 +70,7 @@ class ResourceBundleGenerator implements BundleGenerator { private static final String META_VALUE_PREFIX = "metaValue_"; @Override + @SuppressWarnings("unchecked") public void generateBundle(String packageName, String baseName, String localeID, boolean useJava, Map map, BundleType type) throws IOException { String suffix = useJava ? ".java" : ".properties"; @@ -162,7 +164,13 @@ class ResourceBundleGenerator implements BundleGenerator { if (val instanceof String[]) { fmt.format(" final String[] %s = new String[] {\n", metaVal); - for (String s : (String[])val) { + for (String s : (String[]) val) { + fmt.format(" \"%s\",\n", CLDRConverter.saveConvert(s, useJava)); + } + fmt.format(" };\n"); + } else if (val instanceof List) { + fmt.format(" final String[] %s = new String[] {\n", metaVal); + for (String s : (List) val) { fmt.format(" \"%s\",\n", CLDRConverter.saveConvert(s, useJava)); } fmt.format(" };\n"); diff --git a/src/java.base/share/classes/java/text/DateFormatSymbols.java b/src/java.base/share/classes/java/text/DateFormatSymbols.java index 7a272450f48..e7919e33e69 100644 --- a/src/java.base/share/classes/java/text/DateFormatSymbols.java +++ b/src/java.base/share/classes/java/text/DateFormatSymbols.java @@ -758,6 +758,11 @@ public class DateFormatSymbols implements Serializable, Cloneable { dfs.months = resource.getStringArray("MonthNames"); dfs.shortMonths = resource.getStringArray("MonthAbbreviations"); dfs.ampms = resource.getStringArray("AmPmMarkers"); + // the array in the resource bundle may contain more elements for day periods. + // Extract only am/pm. + if (dfs.ampms.length > 2) { + dfs.ampms = Arrays.copyOf(dfs.ampms, 2); + } dfs.localPatternChars = resource.getString("DateTimePatternChars"); // Day of week names are stored in a 1-based array. diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatter.java b/src/java.base/share/classes/java/time/format/DateTimeFormatter.java index 36987844f92..0e4f8d36aff 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatter.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatter.java @@ -300,6 +300,7 @@ import sun.util.locale.provider.TimeZoneNameUtility; * F day-of-week-in-month number 3 * * a am-pm-of-day text PM + * B period-of-day text in the morning * h clock-hour-of-am-pm (1-12) number 12 * K hour-of-am-pm (0-11) number 0 * k clock-hour-of-day (1-24) number 24 diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java index 521eca47fd4..d0160907158 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java @@ -102,6 +102,7 @@ import java.time.zone.ZoneRulesProvider; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -117,10 +118,13 @@ import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import sun.text.spi.JavaTimeDateTimePatternProvider; import sun.util.locale.provider.CalendarDataUtility; import sun.util.locale.provider.LocaleProviderAdapter; +import sun.util.locale.provider.LocaleResources; import sun.util.locale.provider.TimeZoneNameUtility; /** @@ -218,10 +222,9 @@ public final class DateTimeFormatterBuilder { } LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(JavaTimeDateTimePatternProvider.class, locale); JavaTimeDateTimePatternProvider provider = adapter.getJavaTimeDateTimePatternProvider(); - String pattern = provider.getJavaTimeDateTimePattern(convertStyle(timeStyle), + return provider.getJavaTimeDateTimePattern(convertStyle(timeStyle), convertStyle(dateStyle), chrono.getCalendarType(), CalendarDataUtility.findRegionOverride(locale)); - return pattern; } /** @@ -678,7 +681,7 @@ public final class DateTimeFormatterBuilder { * the minimum and maximum width. In strict mode, if the minimum and maximum widths * are equal and there is no decimal point then the parser will * participate in adjacent value parsing, see - * {@link appendValue(java.time.temporal.TemporalField, int)}. When parsing in lenient mode, + * {@link #appendValue(java.time.temporal.TemporalField, int)}. When parsing in lenient mode, * the minimum width is considered to be zero and the maximum is nine. *

* If the value cannot be obtained then an exception will be thrown. @@ -1447,6 +1450,55 @@ public final class DateTimeFormatterBuilder { return this; } + /** + * Appends the day period text to the formatter. + *

+ * This appends an instruction to format/parse the textual name of the day period + * to the builder. Day periods are defined in LDML's + * "day periods" + * element. + *

+ * During formatting, the day period is obtained from {@code HOUR_OF_DAY}, and + * optionally {@code MINUTE_OF_HOUR} if exist. It will be mapped to a day period + * type defined in LDML, such as "morning1" and then it will be translated into + * text. Mapping to a day period type and its translation both depend on the + * locale in the formatter. + *

+ * During parsing, the text will be parsed into a day period type first. Then + * the parsed day period is combined with other fields to make a {@code LocalTime} in + * the resolving phase. If the {@code HOUR_OF_AMPM} field is present, it is combined + * with the day period to make {@code HOUR_OF_DAY} taking into account any + * {@code MINUTE_OF_HOUR} value. If {@code HOUR_OF_DAY} is present, it is validated + * against the day period taking into account any {@code MINUTE_OF_HOUR} value. If a + * day period is present without {@code HOUR_OF_DAY}, {@code MINUTE_OF_HOUR}, + * {@code SECOND_OF_MINUTE} and {@code NANO_OF_SECOND} then the midpoint of the + * day period is set as the time in {@code SMART} and {@code LENIENT} mode. + * For example, if the parsed day period type is "night1" and the period defined + * for it in the formatter locale is from 21:00 to 06:00, then it results in + * the {@code LocalTime} of 01:30. + * If the resolved time conflicts with the day period, {@code DateTimeException} is + * thrown in {@code STRICT} and {@code SMART} mode. In {@code LENIENT} mode, no + * exception is thrown and the parsed day period is ignored. + *

+ * The "midnight" type allows both "00:00" as the start-of-day and "24:00" as the + * end-of-day, as long as they are valid with the resolved hour field. + * + * @param style the text style to use, not null + * @return this, for chaining, not null + * @since 16 + */ + public DateTimeFormatterBuilder appendDayPeriodText(TextStyle style) { + Objects.requireNonNull(style, "style"); + 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; + } + //----------------------------------------------------------------------- /** * Appends all the elements of a formatter to the builder. @@ -1510,6 +1562,7 @@ public final class DateTimeFormatterBuilder { * F day-of-week-in-month number 3 * * a am-pm-of-day text PM + * B period-of-day text in the morning * h clock-hour-of-am-pm (1-12) number 12 * K hour-of-am-pm (0-11) number 0 * k clock-hour-of-day (1-24) number 24 @@ -1640,6 +1693,15 @@ public final class DateTimeFormatterBuilder { * N..N 1..n appendValue(ChronoField.NANO_OF_DAY, n, 19, SignStyle.NOT_NEGATIVE) * *

+ * Day periods: Pattern letters to output a day period. + *

+     *  Pattern  Count  Equivalent builder methods
+     *  -------  -----  --------------------------
+     *    B       1      appendDayPeriodText(TextStyle.SHORT)
+     *    BBBB    4      appendDayPeriodText(TextStyle.FULL)
+     *    BBBBB   5      appendDayPeriodText(TextStyle.NARROW)
+     * 
+ *

* Zone ID: Pattern letters to output {@code ZoneId}. *

      *  Pattern  Count  Equivalent builder methods
@@ -1759,7 +1821,7 @@ public final class DateTimeFormatterBuilder {
                     } else if (count == 4) {
                         appendGenericZoneText(TextStyle.FULL);
                     } else {
-                        throw new IllegalArgumentException("Wrong number of  pattern letters: " + cur);
+                        throw new IllegalArgumentException("Wrong number of pattern letters: " + cur);
                     }
                 } else if (cur == 'Z') {
                     if (count < 4) {
@@ -1809,6 +1871,13 @@ public final class DateTimeFormatterBuilder {
                     } else {
                         appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 19));
                     }
+                } else if (cur == 'B') {
+                    switch (count) {
+                        case 1 -> appendDayPeriodText(TextStyle.SHORT);
+                        case 4 -> appendDayPeriodText(TextStyle.FULL);
+                        case 5 -> appendDayPeriodText(TextStyle.NARROW);
+                        default -> throw new IllegalArgumentException("Wrong number of pattern letters: " + cur);
+                    }
                 } else {
                     throw new IllegalArgumentException("Unknown pattern letter: " + cur);
                 }
@@ -1920,19 +1989,10 @@ public final class DateTimeFormatterBuilder {
                 break;
             case 'G':
                 switch (count) {
-                    case 1:
-                    case 2:
-                    case 3:
-                        appendText(field, TextStyle.SHORT);
-                        break;
-                    case 4:
-                        appendText(field, TextStyle.FULL);
-                        break;
-                    case 5:
-                        appendText(field, TextStyle.NARROW);
-                        break;
-                    default:
-                        throw new IllegalArgumentException("Too many pattern letters: " + cur);
+                    case 1, 2, 3 -> appendText(field, TextStyle.SHORT);
+                    case 4 -> appendText(field, TextStyle.FULL);
+                    case 5 -> appendText(field, TextStyle.NARROW);
+                    default -> throw new IllegalArgumentException("Too many pattern letters: " + cur);
                 }
                 break;
             case 'S':
@@ -2025,6 +2085,7 @@ public final class DateTimeFormatterBuilder {
         // 310 - X - matches LDML, almost matches SDF for 1, exact match 2&3, extended 4&5
         // 310 - x - matches LDML
         // 310 - w, W, and Y are localized forms matching LDML
+        // LDML - B - day periods
         // LDML - U - cycle year name, not supported by 310 yet
         // LDML - l - deprecated
         // LDML - j - not relevant
@@ -2160,9 +2221,7 @@ public final class DateTimeFormatterBuilder {
     private int appendInternal(DateTimePrinterParser pp) {
         Objects.requireNonNull(pp, "pp");
         if (active.padNextWidth > 0) {
-            if (pp != null) {
-                pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar);
-            }
+            pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar);
             active.padNextWidth = 0;
             active.padNextChar = 0;
         }
@@ -2311,7 +2370,7 @@ public final class DateTimeFormatterBuilder {
         private final boolean optional;
 
         CompositePrinterParser(List printerParsers, boolean optional) {
-            this(printerParsers.toArray(new DateTimePrinterParser[printerParsers.size()]), optional);
+            this(printerParsers.toArray(new DateTimePrinterParser[0]), optional);
         }
 
         CompositePrinterParser(DateTimePrinterParser[] printerParsers, boolean optional) {
@@ -3129,11 +3188,11 @@ public final class DateTimeFormatterBuilder {
         }
 
         /**
-         * For FractionPrinterPrinterParser, the width is fixed if context is sttrict,
+         * For FractionPrinterPrinterParser, the width is fixed if context is strict,
          * minWidth equal to maxWidth and decimalpoint is absent.
          * @param context the context
          * @return if the field is fixed width
-         * @see DateTimeFormatterBuilder#appendValueFraction(java.time.temporal.TemporalField, int, int, boolean)
+         * @see #appendFraction(java.time.temporal.TemporalField, int, int, boolean)
          */
         @Override
         boolean isFixedWidth(DateTimeParseContext context) {
@@ -4975,14 +5034,296 @@ public final class DateTimeFormatterBuilder {
         }
     }
 
-    //-------------------------------------------------------------------------
+    //-----------------------------------------------------------------------
+
     /**
-     * Length comparator.
+     * Prints or parses day periods.
      */
-    static final Comparator LENGTH_SORT = new Comparator() {
-        @Override
-        public int compare(String str1, String str2) {
-            return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length();
+    static final class DayPeriodPrinterParser implements DateTimePrinterParser {
+        private final TextStyle textStyle;
+        private final static ConcurrentMap DAYPERIOD_LOCALESTORE = new ConcurrentHashMap<>();
+
+        /**
+         * Constructor.
+         *
+         * @param textStyle  the text style, not null
+         */
+        DayPeriodPrinterParser(TextStyle textStyle) {
+            // validated by caller
+            this.textStyle = textStyle;
         }
-    };
+
+        @Override
+        public boolean format(DateTimePrintContext context, StringBuilder buf) {
+            Long hod = context.getValue(HOUR_OF_DAY);
+            if (hod == null) {
+                return false;
+            }
+            Long moh = context.getValue(MINUTE_OF_HOUR);
+            long value = Math.floorMod(hod, 24) * 60 + (moh != null ? Math.floorMod(moh, 60) : 0);
+            Locale locale = context.getLocale();
+            LocaleStore store = findDayPeriodStore(locale);
+            final long val = value;
+            final var map = DayPeriod.getDayPeriodMap(locale);
+            value = map.keySet().stream()
+                    .filter(k -> k.includes(val))
+                    .min(DayPeriod.DPCOMPARATOR)
+                    .map(map::get)
+                    .orElse(val / 720); // fall back to am/pm
+            String text = store.getText(value, textStyle);
+            buf.append(text);
+            return true;
+        }
+
+        @Override
+        public int parse(DateTimeParseContext context, CharSequence parseText, int position) {
+            int length = parseText.length();
+            if (position < 0 || position > length) {
+                throw new IndexOutOfBoundsException();
+            }
+            TextStyle style = (context.isStrict() ? textStyle : null);
+            Iterator> it;
+            LocaleStore store = findDayPeriodStore(context.getLocale());
+            it = store.getTextIterator(style);
+            if (it != null) {
+                while (it.hasNext()) {
+                    Entry entry = it.next();
+                    String itText = entry.getKey();
+                    if (context.subSequenceEquals(itText, 0, parseText, position, itText.length())) {
+                        context.setParsedDayPeriod(DayPeriod.ofLocale(context.getLocale(), entry.getValue()));
+                        return position + itText.length();
+                    }
+                }
+            }
+            return ~position;
+        }
+
+        @Override
+        public String toString() {
+            return "DayPeriod(" + textStyle + ")";
+        }
+
+        /**
+         * Returns the day period locale store for the locale
+         * @param locale locale to be examined
+         * @return locale store for the locale
+         */
+        private static LocaleStore findDayPeriodStore(Locale locale) {
+            return DAYPERIOD_LOCALESTORE.computeIfAbsent(locale, loc -> {
+                Map> styleMap = new HashMap<>();
+
+                for (TextStyle textStyle : TextStyle.values()) {
+                    if (textStyle.isStandalone()) {
+                        // Stand-alone isn't applicable to day period.
+                        continue;
+                    }
+
+                    Map map = new HashMap<>();
+                    int calStyle = textStyle.toCalendarStyle();
+                    var periodMap = DayPeriod.getDayPeriodMap(loc);
+                    periodMap.forEach((key, value) -> {
+                        String displayName = CalendarDataUtility.retrieveJavaTimeFieldValueName(
+                                "gregory", Calendar.AM_PM, value.intValue(), calStyle, loc);
+                        if (displayName != null) {
+                            map.put(value, displayName);
+                        } else {
+                            periodMap.remove(key);
+                        }
+                    });
+                    if (!map.isEmpty()) {
+                        styleMap.put(textStyle, map);
+                    }
+                }
+                return new LocaleStore(styleMap);
+            });
+        }
+    }
+
+    /**
+     * DayPeriod class that represents a
+     * DayPeriod defined in CLDR.
+     * This is a value-based class.
+     */
+    static final class DayPeriod {
+        /**
+         *  DayPeriod cache
+         */
+        private final static Map> DAYPERIOD_CACHE = new ConcurrentHashMap<>();
+        /**
+         * comparator based on the duration of the day period.
+         */
+        private final static Comparator DPCOMPARATOR = (dp1, dp2) -> (int)(dp1.duration() - dp2.duration());
+        /**
+         * Pattern to parse day period rules
+         */
+        private final static Pattern RULE = Pattern.compile("(?[a-z12]+):(?\\d{2}):00(-(?\\d{2}))*");
+        /**
+         * minute-of-day of "at" or "from" attribute
+         */
+        private final long from;
+        /**
+         * minute-of-day of "before" attribute (exclusive), or if it is
+         * the same value with "from", it indicates this day period
+         * designates "fixed" periods, i.e, "midnight" or "noon"
+         */
+        private final long to;
+        /**
+         * day period type index. (cf. {@link #mapToIndex})
+         */
+        private final long index;
+
+        /**
+         * Sole constructor
+         *
+         * @param from "from" in minute-of-day
+         * @param to "to" in minute-of-day
+         * @param index day period type index
+         */
+        private DayPeriod(long from, long to, long index) {
+            this.from = from;
+            this.to = to;
+            this.index = index;
+        }
+
+        /**
+         * Gets the index of this day period
+         *
+         * @return index
+         */
+        long getIndex() {
+            return index;
+        }
+
+        /**
+         * Returns the midpoint of this day period in minute-of-day
+         * @return midpoint
+         */
+        long mid() {
+            return (from + duration() / 2) % 1_440;
+        }
+
+        /**
+         * Checks whether the passed minute-of-day is within this
+         * day period or not.
+         *
+         * @param mod minute-of-day to check
+         * @return true if {@code mod} is within this day period
+         */
+        boolean includes(long mod) {
+            // special check for 24:00 for midnight in hour-of-day
+            if (from == 0 && to == 0 && mod == 1_440) {
+                return true;
+            }
+            return (from == mod && to == mod || // midnight/noon
+                    from <= mod && mod < to || // contiguous from-to
+                    from > to && (from <= mod || to > mod)); // beyond midnight
+        }
+
+        /**
+         * Calculates the duration of this day period
+         * @return the duration in minutes
+         */
+        private long duration() {
+            return from > to ? 1_440 - from + to: to - from;
+        }
+
+        /**
+         * Maps the day period type defined in LDML to the index to the am/pm array
+         * returned from the Calendar resource bundle.
+         *
+         * @param type day period type defined in LDML
+         * @return the array index
+         */
+        static long mapToIndex(String type) {
+            return switch (type) {
+                case "am"           -> Calendar.AM;
+                case "pm"           -> Calendar.PM;
+                case "midnight"     -> 2;
+                case "noon"         -> 3;
+                case "morning1"     -> 4;
+                case "morning2"     -> 5;
+                case "afternoon1"   -> 6;
+                case "afternoon2"   -> 7;
+                case "evening1"     -> 8;
+                case "evening2"     -> 9;
+                case "night1"       -> 10;
+                case "night2"       -> 11;
+                default -> throw new InternalError("invalid day period type");
+            };
+        }
+
+        /**
+         * Returns the DayPeriod to array index map for a locale.
+         *
+         * @param locale  the locale, not null
+         * @return the DayPeriod to type index map
+         */
+        static Map getDayPeriodMap(Locale locale) {
+            return DAYPERIOD_CACHE.computeIfAbsent(locale, l -> {
+                LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
+                        .getLocaleResources(CalendarDataUtility.findRegionOverride(l));
+                String dayPeriodRules = lr.getRules()[1];
+                final Map periodMap = new ConcurrentHashMap<>();
+                Arrays.stream(dayPeriodRules.split(";"))
+                    .forEach(rule -> {
+                        Matcher m = RULE.matcher(rule);
+                        if (m.find()) {
+                            String from = m.group("from");
+                            String to = m.group("to");
+                            long index = DayPeriod.mapToIndex(m.group("type"));
+                            if (to == null) {
+                                to = from;
+                            }
+                            periodMap.putIfAbsent(
+                                new DayPeriod(
+                                    Long.parseLong(from) * 60,
+                                    Long.parseLong(to) * 60,
+                                        index),
+                                index);
+                        }
+                    });
+
+                // add am/pm
+                periodMap.putIfAbsent(new DayPeriod(0, 720, 0), 0L);
+                periodMap.putIfAbsent(new DayPeriod(720, 1_440, 1), 1L);
+                return periodMap;
+            });
+        }
+
+        /**
+         * Returns the DayPeriod singleton for the locale and index.
+         * @param locale desired locale
+         * @param index resource bundle array index
+         * @return a DayPeriod instance
+         */
+        static DayPeriod ofLocale(Locale locale, long index) {
+            return getDayPeriodMap(locale).keySet().stream()
+                .filter(dp -> dp.getIndex() == index)
+                .findAny()
+                .orElseThrow(() -> new DateTimeException(
+                    "DayPeriod could not be determined for the locale " +
+                    locale + " at type index " + index));
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            DayPeriod dayPeriod = (DayPeriod) o;
+            return from == dayPeriod.from &&
+                    to == dayPeriod.to &&
+                    index == dayPeriod.index;
+        }
+
+        @Override
+        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));
+        }
+    }
 }
diff --git a/src/java.base/share/classes/java/time/format/DateTimeParseContext.java b/src/java.base/share/classes/java/time/format/DateTimeParseContext.java
index 6b8dd98501c..32eefcecc30 100644
--- a/src/java.base/share/classes/java/time/format/DateTimeParseContext.java
+++ b/src/java.base/share/classes/java/time/format/DateTimeParseContext.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -424,6 +424,15 @@ final class DateTimeParseContext {
         currentParsed().leapSecond = true;
     }
 
+    /**
+     * Stores the parsed day period.
+     *
+     * @param dayPeriod the parsed day period
+     */
+    void setParsedDayPeriod(DateTimeFormatterBuilder.DayPeriod dayPeriod) {
+        currentParsed().dayPeriod = dayPeriod;
+    }
+
     //-----------------------------------------------------------------------
     /**
      * Returns a string version of the context for debugging.
diff --git a/src/java.base/share/classes/java/time/format/Parsed.java b/src/java.base/share/classes/java/time/format/Parsed.java
index 41b3f856a5b..60ad5396953 100644
--- a/src/java.base/share/classes/java/time/format/Parsed.java
+++ b/src/java.base/share/classes/java/time/format/Parsed.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -61,6 +61,7 @@
  */
 package java.time.format;
 
+import static java.time.format.DateTimeFormatterBuilder.DayPeriod;
 import static java.time.temporal.ChronoField.AMPM_OF_DAY;
 import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM;
 import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY;
@@ -155,6 +156,10 @@ final class Parsed implements TemporalAccessor {
      * The excess period from time-only parsing.
      */
     Period excessDays = Period.ZERO;
+    /**
+     * The parsed day period.
+     */
+    DayPeriod dayPeriod;
 
     /**
      * Creates an instance.
@@ -172,6 +177,7 @@ final class Parsed implements TemporalAccessor {
         cloned.zone = this.zone;
         cloned.chrono = this.chrono;
         cloned.leapSecond = this.leapSecond;
+        cloned.dayPeriod = this.dayPeriod;
         return cloned;
     }
 
@@ -332,7 +338,8 @@ final class Parsed implements TemporalAccessor {
         }
     }
 
-    //-----------------------------------------------------------------------
+
+//-----------------------------------------------------------------------
     private void resolveInstantFields() {
         // resolve parsed instant seconds to date and time if zone available
         if (fieldValues.containsKey(INSTANT_SECONDS)) {
@@ -470,6 +477,19 @@ final class Parsed implements TemporalAccessor {
             }
         }
 
+        if (dayPeriod != null && fieldValues.containsKey(HOUR_OF_AMPM)) {
+            long hoap = fieldValues.remove(HOUR_OF_AMPM);
+            if (resolverStyle != ResolverStyle.LENIENT) {
+                HOUR_OF_AMPM.checkValidValue(hoap);
+            }
+            Long mohObj = fieldValues.get(MINUTE_OF_HOUR);
+            long moh = mohObj != null ? Math.floorMod(mohObj, 60) : 0;
+            long excessHours = dayPeriod.includes((Math.floorMod(hoap, 12) + 12) * 60 + moh) ? 12 : 0;
+            long hod = Math.addExact(hoap, excessHours);
+            updateCheckConflict(HOUR_OF_AMPM, HOUR_OF_DAY, hod);
+            dayPeriod = null;
+        }
+
         // convert to time if all four fields available (optimization)
         if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) &&
                 fieldValues.containsKey(SECOND_OF_MINUTE) && fieldValues.containsKey(NANO_OF_SECOND)) {
@@ -506,6 +526,27 @@ final class Parsed implements TemporalAccessor {
                 fieldValues.put(NANO_OF_SECOND, cos * 1_000L);
             }
 
+            // Set the hour-of-day, if not exist and not in STRICT, to the mid point of the day period or am/pm.
+            if (!fieldValues.containsKey(HOUR_OF_DAY) &&
+                    !fieldValues.containsKey(MINUTE_OF_HOUR) &&
+                    !fieldValues.containsKey(SECOND_OF_MINUTE) &&
+                    !fieldValues.containsKey(NANO_OF_SECOND) &&
+                    resolverStyle != ResolverStyle.STRICT) {
+                if (dayPeriod != null) {
+                    long midpoint = dayPeriod.mid();
+                    resolveTime(midpoint / 60, midpoint % 60, 0, 0);
+                    dayPeriod = null;
+                } else if (fieldValues.containsKey(AMPM_OF_DAY)) {
+                    long ap = fieldValues.remove(AMPM_OF_DAY);
+                    if (resolverStyle == ResolverStyle.LENIENT) {
+                        resolveTime(Math.addExact(Math.multiplyExact(ap, 12), 6), 0, 0, 0);
+                    } else {  // SMART
+                        AMPM_OF_DAY.checkValidValue(ap);
+                        resolveTime(ap * 12 + 6, 0, 0, 0);
+                    }
+                }
+            }
+
             // merge hour/minute/second/nano leniently
             Long hod = fieldValues.get(HOUR_OF_DAY);
             if (hod != null) {
@@ -523,6 +564,15 @@ final class Parsed implements TemporalAccessor {
                 long mohVal = (moh != null ? moh : 0);
                 long somVal = (som != null ? som : 0);
                 long nosVal = (nos != null ? nos : 0);
+
+                if (dayPeriod != null && resolverStyle != ResolverStyle.LENIENT) {
+                    // Check whether the hod/mohVal is within the day period
+                    if (!dayPeriod.includes(hod * 60 + mohVal)) {
+                        throw new DateTimeException("Conflict found: Resolved time %02d:%02d".formatted(hod, mohVal) +
+                                " conflicts with " + dayPeriod);
+                    }
+                }
+
                 resolveTime(hod, mohVal, somVal, nosVal);
                 fieldValues.remove(HOUR_OF_DAY);
                 fieldValues.remove(MINUTE_OF_HOUR);
diff --git a/src/java.base/share/classes/java/time/temporal/ChronoField.java b/src/java.base/share/classes/java/time/temporal/ChronoField.java
index 867544b3bd0..3728f9e6d91 100644
--- a/src/java.base/share/classes/java/time/temporal/ChronoField.java
+++ b/src/java.base/share/classes/java/time/temporal/ChronoField.java
@@ -333,8 +333,9 @@ public enum ChronoField implements TemporalField {
      * When parsing this field it behaves equivalent to the following:
      * The value is validated from 0 to 1 in strict and smart mode.
      * In lenient mode the value is not validated. It is combined with
-     * {@code HOUR_OF_AMPM} to form {@code HOUR_OF_DAY} by multiplying
-     * the {@code AMPM_OF_DAY} value by 12.
+     * {@code HOUR_OF_AMPM} (if not present, it defaults to '6') to form
+     * {@code HOUR_OF_DAY} by multiplying the {@code AMPM_OF_DAY} value
+     * by 12.
      */
     AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1), "dayperiod"),
     /**
diff --git a/src/java.base/share/classes/sun/util/locale/provider/CalendarNameProviderImpl.java b/src/java.base/share/classes/sun/util/locale/provider/CalendarNameProviderImpl.java
index fac72a50caf..8a1c3ced380 100644
--- a/src/java.base/share/classes/sun/util/locale/provider/CalendarNameProviderImpl.java
+++ b/src/java.base/share/classes/sun/util/locale/provider/CalendarNameProviderImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -68,7 +68,7 @@ public class CalendarNameProviderImpl extends CalendarNameProvider implements Av
 
             // If standalone names are requested and no "standalone." resources are found,
             // try the default ones instead.
-            if (strings == null && key.indexOf("standalone.") != -1) {
+            if (strings == null && key.contains("standalone.")) {
                 key = key.replaceFirst("standalone.", "");
                 strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
             }
@@ -129,7 +129,7 @@ public class CalendarNameProviderImpl extends CalendarNameProvider implements Av
         return name;
     }
 
-    private static int[] REST_OF_STYLES = {
+    private static final int[] REST_OF_STYLES = {
         SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE,
         NARROW_FORMAT, NARROW_STANDALONE
     };
@@ -166,23 +166,26 @@ public class CalendarNameProviderImpl extends CalendarNameProvider implements Av
 
             // If standalone names are requested and no "standalone." resources are found,
             // try the default ones instead.
-            if (strings == null && key.indexOf("standalone.") != -1) {
+            if (strings == null && key.contains("standalone.")) {
                 key = key.replaceFirst("standalone.", "");
                 strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
             }
 
             if (strings != null) {
-                if (!hasDuplicates(strings)) {
+                if (!hasDuplicates(strings) || field == AM_PM) {
                     if (field == YEAR) {
                         if (strings.length > 0) {
                             map.put(strings[0], 1);
                         }
                     } else {
                         int base = (field == DAY_OF_WEEK) ? 1 : 0;
-                        for (int i = 0; i < strings.length; i++) {
+                        // Duplicates can happen with AM_PM field. In such a case,
+                        // am/pm (index 0 and 1) have precedence over day
+                        // periods.
+                        for (int i = strings.length - 1; i >= 0; i--) {
                             String name = strings[i];
                             // Ignore any empty string (some standalone month names
-                            // are not defined)
+                            // or flexible day periods are not defined)
                             if (name.isEmpty()) {
                                 continue;
                             }
diff --git a/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java b/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java
index 2391406832b..b6351cf1aca 100644
--- a/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java
+++ b/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -49,7 +49,6 @@ import java.util.Calendar;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
 import java.util.ResourceBundle;
 import java.util.Set;
@@ -91,6 +90,7 @@ public class LocaleResources {
     private static final String NUMBER_PATTERNS_CACHEKEY = "NP";
     private static final String COMPACT_NUMBER_PATTERNS_CACHEKEY = "CNP";
     private static final String DATE_TIME_PATTERN = "DTP.";
+    private static final String RULES_CACHEKEY = "RULE";
 
     // TimeZoneNamesBundle exemplar city prefix
     private static final String TZNB_EXCITY_PREFIX = "timezone.excity.";
@@ -125,7 +125,6 @@ public class LocaleResources {
        return biInfo;
     }
 
-    @SuppressWarnings("unchecked")
     byte[] getBreakIteratorResources(String key) {
         return (byte[]) localeData.getBreakIteratorResources(locale).getObject(key);
     }
@@ -162,7 +161,7 @@ public class LocaleResources {
                 coldata = rb.getString(key);
             }
             cache.put(COLLATION_DATA_CACHEKEY,
-                      new ResourceReference(COLLATION_DATA_CACHEKEY, (Object) coldata, referenceQueue));
+                      new ResourceReference(COLLATION_DATA_CACHEKEY, coldata, referenceQueue));
         }
 
         return coldata;
@@ -181,7 +180,7 @@ public class LocaleResources {
             dfsdata[0] = getNumberStrings(rb, "NumberElements");
 
             cache.put(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY,
-                      new ResourceReference(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, (Object) dfsdata, referenceQueue));
+                      new ResourceReference(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, dfsdata, referenceQueue));
         }
 
         return dfsdata;
@@ -314,7 +313,7 @@ public class LocaleResources {
 
     @SuppressWarnings("unchecked")
     Set getZoneIDs() {
-        Set zoneIDs = null;
+        Set zoneIDs;
 
         removeEmptyReferences();
         ResourceReference data = cache.get(ZONE_IDS_CACHEKEY);
@@ -322,7 +321,7 @@ public class LocaleResources {
             TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale);
             zoneIDs = rb.keySet();
             cache.put(ZONE_IDS_CACHEKEY,
-                      new ResourceReference(ZONE_IDS_CACHEKEY, (Object) zoneIDs, referenceQueue));
+                      new ResourceReference(ZONE_IDS_CACHEKEY, zoneIDs, referenceQueue));
         }
 
         return zoneIDs;
@@ -380,7 +379,7 @@ public class LocaleResources {
             if (rb.containsKey(key)) {
                 names = rb.getStringArray(key);
                 cache.put(cacheKey,
-                          new ResourceReference(cacheKey, (Object) names, referenceQueue));
+                          new ResourceReference(cacheKey, names, referenceQueue));
             }
         }
 
@@ -399,7 +398,7 @@ public class LocaleResources {
             if (rb.containsKey(key)) {
                 names = rb.getStringArray(key);
                 cache.put(cacheKey,
-                          new ResourceReference(cacheKey, (Object) names, referenceQueue));
+                          new ResourceReference(cacheKey, names, referenceQueue));
             }
         }
 
@@ -463,17 +462,11 @@ public class LocaleResources {
                 if (dateTimePattern == null) {
                     dateTimePattern = getDateTimePattern(null, "DateTimePatterns", dateTimeStyle, calType);
                 }
-                switch (dateTimePattern) {
-                case "{1} {0}":
-                    pattern = datePattern + " " + timePattern;
-                    break;
-                case "{0} {1}":
-                    pattern = timePattern + " " + datePattern;
-                    break;
-                default:
-                    pattern = MessageFormat.format(dateTimePattern.replaceAll("'", "''"), timePattern, datePattern);
-                    break;
-                }
+                pattern = switch (Objects.requireNonNull(dateTimePattern)) {
+                    case "{1} {0}" -> datePattern + " " + timePattern;
+                    case "{0} {1}" -> timePattern + " " + datePattern;
+                    default -> MessageFormat.format(dateTimePattern.replaceAll("'", "''"), timePattern, datePattern);
+                };
             } else {
                 pattern = timePattern;
             }
@@ -486,7 +479,7 @@ public class LocaleResources {
     }
 
     public String[] getNumberPatterns() {
-        String[] numberPatterns = null;
+        String[] numberPatterns;
 
         removeEmptyReferences();
         ResourceReference data = cache.get(NUMBER_PATTERNS_CACHEKEY);
@@ -495,7 +488,7 @@ public class LocaleResources {
             ResourceBundle resource = localeData.getNumberFormatData(locale);
             numberPatterns = getNumberStrings(resource, "NumberPatterns");
             cache.put(NUMBER_PATTERNS_CACHEKEY,
-                      new ResourceReference(NUMBER_PATTERNS_CACHEKEY, (Object) numberPatterns, referenceQueue));
+                      new ResourceReference(NUMBER_PATTERNS_CACHEKEY, numberPatterns, referenceQueue));
         }
 
         return numberPatterns;
@@ -506,11 +499,10 @@ public class LocaleResources {
      * @param formatStyle the style for formatting a number
      * @return an array of compact number patterns
      */
-    @SuppressWarnings("unchecked")
     public String[] getCNPatterns(NumberFormat.Style formatStyle) {
 
         Objects.requireNonNull(formatStyle);
-        String[] compactNumberPatterns = null;
+        String[] compactNumberPatterns;
         removeEmptyReferences();
         String width = (formatStyle == NumberFormat.Style.LONG) ? "long" : "short";
         String cacheKey = width + "." + COMPACT_NUMBER_PATTERNS_CACHEKEY;
@@ -520,8 +512,7 @@ public class LocaleResources {
             ResourceBundle resource = localeData.getNumberFormatData(locale);
             compactNumberPatterns = (String[]) resource
                     .getObject(width + ".CompactNumberPatterns");
-            cache.put(cacheKey, new ResourceReference(cacheKey,
-                    (Object) compactNumberPatterns, referenceQueue));
+            cache.put(cacheKey, new ResourceReference(cacheKey, compactNumberPatterns, referenceQueue));
         }
         return compactNumberPatterns;
     }
@@ -579,6 +570,28 @@ public class LocaleResources {
         return (styles.length > 1 ? styles[styleIndex] : styles[0]);
     }
 
+    public String[] getRules() {
+        String[] rules;
+
+        removeEmptyReferences();
+        ResourceReference data = cache.get(RULES_CACHEKEY);
+
+        if (data == null || ((rules = (String[]) data.get()) == null)) {
+            ResourceBundle rb = localeData.getDateFormatData(locale);
+            rules = new String[2];
+            rules[0] = rules[1] = "";
+            if (rb.containsKey("PluralRules")) {
+                rules[0] = rb.getString("PluralRules");
+            }
+            if (rb.containsKey("DayPeriodRules")) {
+                rules[1] = rb.getString("DayPeriodRules");
+            }
+            cache.put(RULES_CACHEKEY, new ResourceReference(RULES_CACHEKEY, rules, referenceQueue));
+        }
+
+        return rules;
+    }
+
     private static class ResourceReference extends SoftReference {
         private final String cacheKey;
 
diff --git a/src/java.base/share/classes/sun/util/locale/provider/NumberFormatProviderImpl.java b/src/java.base/share/classes/sun/util/locale/provider/NumberFormatProviderImpl.java
index 978de9ae9e2..b87aa06b691 100644
--- a/src/java.base/share/classes/sun/util/locale/provider/NumberFormatProviderImpl.java
+++ b/src/java.base/share/classes/sun/util/locale/provider/NumberFormatProviderImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -45,14 +45,10 @@ import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 import java.text.NumberFormat;
 import java.text.spi.NumberFormatProvider;
-import java.util.Arrays;
 import java.util.Currency;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.stream.Collectors;
-import sun.text.resources.PluralRules;
 
 /**
  * Concrete implementation of the  {@link java.text.spi.NumberFormatProvider
@@ -73,12 +69,6 @@ public class NumberFormatProviderImpl extends NumberFormatProvider implements Av
     private final LocaleProviderAdapter.Type type;
     private final Set langtags;
 
-    private static Map rulesMap =
-            Arrays.stream(PluralRules.rulesArray).collect(Collectors.toMap(
-                    sa -> sa[0],
-                    sa -> sa[1])
-            );
-
     public NumberFormatProviderImpl(LocaleProviderAdapter.Type type, Set langtags) {
         this.type = type;
         this.langtags = langtags;
@@ -282,12 +272,10 @@ public class NumberFormatProviderImpl extends NumberFormatProvider implements Av
         String[] cnPatterns = resource.getCNPatterns(formatStyle);
 
         // plural rules
-        String pluralRules = rulesMap.getOrDefault(override.toString(),
-                rulesMap.getOrDefault(override.getLanguage(), ""));
+        String[] rules = resource.getRules();
 
-        CompactNumberFormat format = new CompactNumberFormat(numberPatterns[0],
-                symbols, cnPatterns, pluralRules);
-        return format;
+        return new CompactNumberFormat(numberPatterns[0],
+                symbols, cnPatterns, rules[0]);
     }
 
     @Override
diff --git a/test/jdk/java/time/tck/java/time/format/TCKDateTimeParseResolver.java b/test/jdk/java/time/tck/java/time/format/TCKDateTimeParseResolver.java
index 4ff2f66b024..7a33064dfd8 100644
--- a/test/jdk/java/time/tck/java/time/format/TCKDateTimeParseResolver.java
+++ b/test/jdk/java/time/tck/java/time/format/TCKDateTimeParseResolver.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -854,32 +854,32 @@ public class TCKDateTimeParseResolver {
     @DataProvider(name="resolveAmPm")
     Object[][] data_resolveAmPm() {
         return new Object[][]{
-                {STRICT, 0, 0},
-                {STRICT, 1, 1},
-                {STRICT, -1, null},
-                {STRICT, 2, null},
+                {STRICT, 0, null, 0},
+                {STRICT, 1, null, 1},
+                {STRICT, -1, null, null},
+                {STRICT, 2, null, null},
 
-                {SMART, 0, 0},
-                {SMART, 1, 1},
-                {SMART, -1, null},
-                {SMART, 2, null},
+                {SMART, 0, LocalTime.of(6, 0), 0},
+                {SMART, 1, LocalTime.of(18, 0), 1},
+                {SMART, -1, null, null},
+                {SMART, 2, null, null},
 
-                {LENIENT, 0, 0},
-                {LENIENT, 1, 1},
-                {LENIENT, -1, -1},
-                {LENIENT, 2, 2},
+                {LENIENT, 0, LocalTime.of(6, 0), 0},
+                {LENIENT, 1, LocalTime.of(18, 0), 1},
+                {LENIENT, -1, LocalTime.of(18, 0), 1},
+                {LENIENT, 2, LocalTime.of(6, 0), 0},
         };
     }
 
     @Test(dataProvider="resolveAmPm")
-    public void test_resolveAmPm(ResolverStyle style, long value, Integer expectedValue) {
+    public void test_resolveAmPm(ResolverStyle style, long value, LocalTime expectedTime, Integer expectedValue) {
         String str = Long.toString(value);
         DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(AMPM_OF_DAY).toFormatter();
 
         if (expectedValue != null) {
             TemporalAccessor accessor = f.withResolverStyle(style).parse(str);
             assertEquals(accessor.query(TemporalQueries.localDate()), null);
-            assertEquals(accessor.query(TemporalQueries.localTime()), null);
+            assertEquals(accessor.query(TemporalQueries.localTime()), expectedTime);
             assertEquals(accessor.isSupported(AMPM_OF_DAY), true);
             assertEquals(accessor.getLong(AMPM_OF_DAY), expectedValue.longValue());
         } else {
diff --git a/test/jdk/java/time/test/java/time/format/TestDateTimeFormatterBuilder.java b/test/jdk/java/time/test/java/time/format/TestDateTimeFormatterBuilder.java
index 5b83d444faa..6dd54ad6880 100644
--- a/test/jdk/java/time/test/java/time/format/TestDateTimeFormatterBuilder.java
+++ b/test/jdk/java/time/test/java/time/format/TestDateTimeFormatterBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -59,29 +59,38 @@
  */
 package test.java.time.format;
 
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME;
 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
 import static java.time.temporal.ChronoField.DAY_OF_WEEK;
+import static java.time.temporal.ChronoField.HOUR_OF_DAY;
 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
 import static java.time.temporal.ChronoField.YEAR;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
 
 import java.text.ParsePosition;
 import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.Period;
 import java.time.YearMonth;
 import java.time.ZoneOffset;
 import java.time.chrono.Chronology;
 import java.time.chrono.IsoChronology;
-import java.time.chrono.JapaneseChronology;
-import java.time.chrono.MinguoChronology;
 import java.time.format.DateTimeFormatter;
 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;
 import java.time.temporal.Temporal;
 import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalField;
+import java.time.temporal.TemporalUnit;
+import java.time.temporal.ValueRange;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
@@ -526,6 +535,360 @@ public class TestDateTimeFormatterBuilder {
         builder.appendZoneText(null);
     }
 
+    //-----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
+    @Test
+    public void test_appendDayPeriodText_1arg() throws Exception {
+        builder.appendDayPeriodText(TextStyle.FULL);
+        DateTimeFormatter f = builder.toFormatter();
+        assertEquals(f.toString(), "DayPeriod(FULL)");
+    }
+
+    @Test(expectedExceptions=NullPointerException.class)
+    public void test_appendDayPeriodText_1arg_nullText() throws Exception {
+        builder.appendDayPeriodText(null);
+    }
+
+    @DataProvider(name="dayPeriodFormat")
+    Object[][] data_dayPeriodFormat() {
+        return new Object[][] {
+            {0, 0, TextStyle.FULL, Locale.US, "midnight"},
+            {0, 1, TextStyle.FULL, Locale.US, "at night"},
+            {6, 0, TextStyle.FULL, Locale.US, "in the morning"},
+            {12, 0, TextStyle.FULL, Locale.US, "noon"},
+            {12, 1, TextStyle.FULL, Locale.US, "in the afternoon"},
+            {18, 0, TextStyle.FULL, Locale.US, "in the evening"},
+            {22, 0, TextStyle.FULL, Locale.US, "at night"},
+
+            {0, 0, TextStyle.FULL, Locale.JAPAN, "\u771f\u591c\u4e2d"},
+            {0, 1, TextStyle.FULL, Locale.JAPAN, "\u591c\u4e2d"},
+            {6, 0, TextStyle.FULL, Locale.JAPAN, "\u671d"},
+            {12, 0, TextStyle.FULL, Locale.JAPAN, "\u6b63\u5348"},
+            {12, 1, TextStyle.FULL, Locale.JAPAN, "\u663c"},
+            {18, 0, TextStyle.FULL, Locale.JAPAN, "\u5915\u65b9"},
+            {19, 0, TextStyle.FULL, Locale.JAPAN, "\u591c"},
+            {23, 0, TextStyle.FULL, Locale.JAPAN, "\u591c\u4e2d"},
+
+            {0, 0, TextStyle.NARROW, Locale.US, "mi"},
+            {0, 1, TextStyle.NARROW, Locale.US, "at night"},
+            {6, 0, TextStyle.NARROW, Locale.US, "in the morning"},
+            {12, 0, TextStyle.NARROW, Locale.US, "n"},
+            {12, 1, TextStyle.NARROW, Locale.US, "in the afternoon"},
+            {18, 0, TextStyle.NARROW, Locale.US, "in the evening"},
+            {22, 0, TextStyle.NARROW, Locale.US, "at night"},
+
+            {0, 0, TextStyle.NARROW, Locale.JAPAN, "\u771f\u591c\u4e2d"},
+            {0, 1, TextStyle.NARROW, Locale.JAPAN, "\u591c\u4e2d"},
+            {6, 0, TextStyle.NARROW, Locale.JAPAN, "\u671d"},
+            {12, 0, TextStyle.NARROW, Locale.JAPAN, "\u6b63\u5348"},
+            {12, 1, TextStyle.NARROW, Locale.JAPAN, "\u663c"},
+            {18, 0, TextStyle.NARROW, Locale.JAPAN, "\u5915\u65b9"},
+            {19, 0, TextStyle.NARROW, Locale.JAPAN, "\u591c"},
+            {23, 0, TextStyle.NARROW, Locale.JAPAN, "\u591c\u4e2d"},
+        };
+    }
+    @Test (dataProvider="dayPeriodFormat")
+    public void test_dayPeriodFormat(int hod, int moh, TextStyle ts, Locale l, String expected) throws Exception {
+        builder.appendDayPeriodText(ts);
+        LocalTime t = LocalTime.of(hod, moh);
+        DateTimeFormatter f = builder.toFormatter().withLocale(l);
+        assertEquals(f.format(t), expected);
+    }
+
+    @DataProvider(name="dayPeriodParse")
+    Object[][] data_dayPeriodParse() {
+        return new Object[][] {
+                {TextStyle.FULL, Locale.US, 0, 0, "midnight"},
+                {TextStyle.FULL, Locale.US, 1, 30, "at night"},
+                {TextStyle.FULL, Locale.US, 6, 0, "AM"},
+                {TextStyle.FULL, Locale.US, 9, 0, "in the morning"},
+                {TextStyle.FULL, Locale.US, 12, 0, "noon"},
+                {TextStyle.FULL, Locale.US, 15, 0, "in the afternoon"},
+                {TextStyle.FULL, Locale.US, 18, 0, "PM"},
+                {TextStyle.FULL, Locale.US, 19, 30, "in the evening"},
+
+                {TextStyle.FULL, Locale.JAPAN, 0, 0, "\u771f\u591c\u4e2d"},
+                {TextStyle.FULL, Locale.JAPAN, 1, 30, "\u591c\u4e2d"},
+                {TextStyle.FULL, Locale.JAPAN, 6, 0, "\u5348\u524d"},
+                {TextStyle.FULL, Locale.JAPAN, 8, 0, "\u671d"},
+                {TextStyle.FULL, Locale.JAPAN, 12, 0, "\u6b63\u5348"},
+                {TextStyle.FULL, Locale.JAPAN, 14, 0, "\u663c"},
+                {TextStyle.FULL, Locale.JAPAN, 17, 30, "\u5915\u65b9"},
+                {TextStyle.FULL, Locale.JAPAN, 18, 0, "\u5348\u5f8c"},
+                {TextStyle.FULL, Locale.JAPAN, 21, 0, "\u591c"},
+
+                {TextStyle.NARROW, Locale.US, 0, 0, "mi"},
+                {TextStyle.NARROW, Locale.US, 1, 30, "at night"},
+                {TextStyle.NARROW, Locale.US, 6, 0, "a"},
+                {TextStyle.NARROW, Locale.US, 9, 0, "in the morning"},
+                {TextStyle.NARROW, Locale.US, 12, 0, "n"},
+                {TextStyle.NARROW, Locale.US, 15, 0, "in the afternoon"},
+                {TextStyle.NARROW, Locale.US, 18, 0, "p"},
+                {TextStyle.NARROW, Locale.US, 19, 30, "in the evening"},
+
+                {TextStyle.NARROW, Locale.JAPAN, 0, 0, "\u771f\u591c\u4e2d"},
+                {TextStyle.NARROW, Locale.JAPAN, 1, 30, "\u591c\u4e2d"},
+                {TextStyle.NARROW, Locale.JAPAN, 6, 0, "\u5348\u524d"},
+                {TextStyle.NARROW, Locale.JAPAN, 8, 0, "\u671d"},
+                {TextStyle.NARROW, Locale.JAPAN, 12, 0, "\u6b63\u5348"},
+                {TextStyle.NARROW, Locale.JAPAN, 14, 0, "\u663c"},
+                {TextStyle.NARROW, Locale.JAPAN, 17, 30, "\u5915\u65b9"},
+                {TextStyle.NARROW, Locale.JAPAN, 18, 0, "\u5348\u5f8c"},
+                {TextStyle.NARROW, Locale.JAPAN, 21, 0, "\u591c"},
+        };
+    }
+    @Test (dataProvider="dayPeriodParse")
+    public void test_dayPeriodParse(TextStyle ts, Locale l, long hod, long moh, String dayPeriod) throws Exception {
+        builder.appendDayPeriodText(ts);
+        DateTimeFormatter f = builder.toFormatter().withLocale(l);
+        var p = f.parse(dayPeriod);
+        assertEquals(p.getLong(HOUR_OF_DAY), hod);
+        assertEquals(p.getLong(MINUTE_OF_HOUR), moh);
+    }
+
+    @DataProvider(name="dayPeriodParsePattern")
+    Object[][] data_dayPeriodParsePattern() {
+        return new Object[][] {
+            {"H B", "23 at night", 23},
+            {"H B", "3 at night", 3},
+            {"K B", "11 at night", 23},
+            {"K B", "3 at night", 3},
+            {"K B", "11 in the morning", 11},
+            {"h B", "11 at night", 23},
+            {"h B", "3 at night", 3},
+            {"h B", "11 in the morning", 11},
+            {"a", "AM", 6},
+            {"a", "PM", 18},
+        };
+    }
+
+    @Test (dataProvider="dayPeriodParsePattern")
+    public void test_dayPeriodParsePattern(String pattern, String hourDayPeriod, long expected) throws Exception {
+        builder.appendPattern(pattern);
+        DateTimeFormatter f = builder.toFormatter().withLocale(Locale.US);
+        var p = f.parse(hourDayPeriod);
+        assertEquals(p.getLong(HOUR_OF_DAY), expected);
+    }
+
+    @DataProvider(name="dayPeriodParseMidnight")
+    Object[][] data_dayPeriodParseMidnight() {
+        return new Object[][] {
+            {"u-M-d H:m B", "2020-11-07 00:00 midnight", 7, 0},
+            {"u-M-d H:m B", "2020-11-07 24:00 midnight", 8, 0},
+        };
+    }
+
+    @Test (dataProvider="dayPeriodParseMidnight")
+    public void test_dayPeriodParseMidnight(String pattern, String dateTime, long expectedDOM, long expectedHOD) throws Exception {
+        builder.appendPattern(pattern);
+        DateTimeFormatter f = builder.toFormatter().withLocale(Locale.US);
+        var p = f.parse(dateTime);
+        assertEquals(p.getLong(DAY_OF_MONTH), expectedDOM);
+        assertEquals(p.getLong(HOUR_OF_DAY), expectedHOD);
+    }
+
+    @DataProvider(name="dayPeriodParseInvalid")
+    Object[][] data_dayPeriodParseInvalid() {
+        return new Object[][] {
+                {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, ResolverStyle rs, Locale l, String dayPeriod, String periodRange) throws Exception {
+        try {
+            builder.append(ISO_LOCAL_TIME).appendLiteral(' ').appendDayPeriodText(ts)
+                    .toFormatter()
+                    .withLocale(l)
+                    .parse(dayPeriod);
+            if (rs != ResolverStyle.LENIENT) {
+                throw new RuntimeException("DateTimeParseException should be thrown");
+            }
+        } catch (DateTimeParseException e) {
+            assertEquals(e.getCause().getMessage(),
+                    "Conflict found: Resolved time " + dayPeriod.substring(0, 5) + " conflicts with " +
+                    "DayPeriod(" + periodRange + ")");
+        }
+    }
+
+    @DataProvider(name="dayPeriodParsePatternInvalid")
+    Object[][] data_dayPeriodParsePatternInvalid() {
+        return new Object[][] {
+                {"H B", ResolverStyle.SMART, "47 at night", 23, null},
+                {"H B", ResolverStyle.SMART, "51 at night", 3, null},
+                {"H B", ResolverStyle.SMART, "-2 at night", 22, null},
+                {"K B", ResolverStyle.SMART, "59 at night", 23, null},
+                {"K B", ResolverStyle.SMART, "51 at night", 3, null},
+                {"K B", ResolverStyle.SMART, "59 in the morning", 11, null},
+                {"K B", ResolverStyle.SMART, "-2 in the morning", 22, null},
+                {"h B", ResolverStyle.SMART, "59 at night", 23, null},
+                {"h B", ResolverStyle.SMART, "51 at night", 3, null},
+                {"h B", ResolverStyle.SMART, "59 in the morning", 11, null},
+                {"h B", ResolverStyle.SMART, "-2 in the morning", 22, null},
+
+                {"H B", ResolverStyle.LENIENT, "47 at night", 23, Period.ofDays(1)},
+                {"H B", ResolverStyle.LENIENT, "51 at night", 3, Period.ofDays(2)},
+                {"H B", ResolverStyle.LENIENT, "-2 at night", 22, Period.ofDays(-1)},
+                {"K B", ResolverStyle.LENIENT, "59 at night", 23, Period.ofDays(2)},
+                {"K B", ResolverStyle.LENIENT, "51 at night", 3, Period.ofDays(2)},
+                {"K B", ResolverStyle.LENIENT, "59 in the morning", 11, Period.ofDays(2)},
+                {"K B", ResolverStyle.LENIENT, "-2 in the morning", 22, Period.ofDays(-1)},
+                {"h B", ResolverStyle.LENIENT, "59 at night", 23, Period.ofDays(2)},
+                {"h B", ResolverStyle.LENIENT, "51 at night", 3, Period.ofDays(2)},
+                {"h B", ResolverStyle.LENIENT, "59 in the morning", 11, Period.ofDays(2)},
+                {"h B", ResolverStyle.LENIENT, "-2 in the morning", 22, Period.ofDays(-1)},
+        };
+    }
+
+    @Test (dataProvider="dayPeriodParsePatternInvalid")
+    public void test_dayPeriodParsePatternInvalid(String pattern, ResolverStyle rs, String hourDayPeriod, long expected, Period expectedExcessDays) throws Exception {
+        try {
+            builder.appendPattern(pattern);
+            DateTimeFormatter f = builder.toFormatter().withLocale(Locale.US).withResolverStyle(rs);
+            var p = f.parse(hourDayPeriod);
+            if (rs != ResolverStyle.LENIENT) {
+                throw new RuntimeException("DateTimeParseException should be thrown");
+            }
+            assertEquals(p.getLong(HOUR_OF_DAY), expected);
+            assertEquals(p.query(DateTimeFormatter.parsedExcessDays()), expectedExcessDays);
+        } catch (DateTimeParseException e) {
+            // exception successfully thrown
+        }
+    }
+
+    @Test (expectedExceptions = DateTimeParseException.class)
+    public void test_dayPeriodParseStrictNoTime() {
+        builder.appendPattern("B");
+        DateTimeFormatter f = builder.toFormatter().withLocale(Locale.US).withResolverStyle(ResolverStyle.STRICT);
+        LocalTime.parse("at night", f);
+    }
+
+    @Test
+    public void test_dayPeriodUserFieldResolution() {
+        var dtf = builder
+                .appendValue(new TemporalField() {
+                                 @Override
+                                 public TemporalUnit getBaseUnit() {
+                                     return null;
+                                 }
+
+                                 @Override
+                                 public TemporalUnit getRangeUnit() {
+                                     return null;
+                                 }
+
+                                 @Override
+                                 public ValueRange range() {
+                                     return null;
+                                 }
+
+                                 @Override
+                                 public boolean isDateBased() {
+                                     return false;
+                                 }
+
+                                 @Override
+                                 public boolean isTimeBased() {
+                                     return false;
+                                 }
+
+                                 @Override
+                                 public boolean isSupportedBy(TemporalAccessor temporal) {
+                                     return false;
+                                 }
+
+                                 @Override
+                                 public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
+                                     return null;
+                                 }
+
+                                 @Override
+                                 public long getFrom(TemporalAccessor temporal) {
+                                     return 0;
+                                 }
+
+                                 @Override
+                                 public  R adjustInto(R temporal, long newValue) {
+                                     return null;
+                                 }
+
+                                 @Override
+                                 public TemporalAccessor resolve(
+                                         Map fieldValues,
+                                         TemporalAccessor partialTemporal,
+                                         ResolverStyle resolverStyle) {
+                                     fieldValues.remove(this);
+                                     fieldValues.put(ChronoField.HOUR_OF_DAY, 6L);
+                                     return null;
+                                 }
+                             },
+                        1)
+                .appendPattern(" B")
+                .toFormatter()
+                .withLocale(Locale.US);
+        assertEquals((long)dtf.parse("0 in the morning").getLong(ChronoField.HOUR_OF_DAY), 6L);
+        try {
+            dtf.parse("0 at night");
+            fail("DateTimeParseException should be thrown");
+        } catch (DateTimeParseException e) {
+            // success
+        }
+    }
+
     //-----------------------------------------------------------------------
     //-----------------------------------------------------------------------
     //-----------------------------------------------------------------------
@@ -792,6 +1155,10 @@ public class TestDateTimeFormatterBuilder {
             {"w", "Localized(WeekOfWeekBasedYear,1)"},
             {"ww", "Localized(WeekOfWeekBasedYear,2)"},
             {"W", "Localized(WeekOfMonth,1)"},
+
+            {"B", "DayPeriod(SHORT)"},
+            {"BBBB", "DayPeriod(FULL)"},
+            {"BBBBB", "DayPeriod(NARROW)"},
         };
     }
 
@@ -867,6 +1234,10 @@ public class TestDateTimeFormatterBuilder {
 
             {"www"},
             {"WW"},
+
+            {"BB"},
+            {"BBB"},
+            {"BBBBBB"},
         };
     }
 
diff --git a/test/jdk/java/time/test/java/time/format/TestDayPeriodWithDTF.java b/test/jdk/java/time/test/java/time/format/TestDayPeriodWithDTF.java
index d59e51db011..38f8292ae15 100644
--- a/test/jdk/java/time/test/java/time/format/TestDayPeriodWithDTF.java
+++ b/test/jdk/java/time/test/java/time/format/TestDayPeriodWithDTF.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,12 +22,12 @@
  */
 /*
  * @test
- * @bug 8209175
- * @summary Checks the 'B' character added in the CLDR date-time patterns is
+ * @bug 8209175 8247781
+ * @summary Checks the 'B' character added in the CLDR date-time patterns is no longer
  *          getting resolved with 'a' character (am/pm strings) for burmese locale.
  *          This test case assumes that the 'B' character is added in CLDRv33 update
- *          for burmese locale in the time patterns. Since it is not supported by
- *          DateTimeFormatter it is replaced with the 'a' while CLDR resource
+ *          for burmese locale in the time patterns. Since it is supported by
+ *          DateTimeFormatter it should not be replaced with the 'a' while CLDR resource
  *          conversion.
  * @modules jdk.localedata
  */
@@ -56,10 +56,11 @@ public class TestDayPeriodWithDTF {
     @DataProvider(name = "timePatternData")
     Object[][] timePatternData() {
         return new Object[][] {
+                // these messages are for day periods in Burmese.
                 {FORMAT_SHORT_BURMESE, LOCAL_TIME_AM, "\u1014\u1036\u1014\u1000\u103A 10:10"},
-                {FORMAT_SHORT_BURMESE, LOCAL_TIME_PM, "\u100A\u1014\u1031 12:12"},
+                {FORMAT_SHORT_BURMESE, LOCAL_TIME_PM, "\u1014\u1031\u1037\u101c\u101a\u103a 12:12"},
                 {FORMAT_MEDIUM_BURMESE, LOCAL_TIME_AM, "\u1014\u1036\u1014\u1000\u103A 10:10:10"},
-                {FORMAT_MEDIUM_BURMESE, LOCAL_TIME_PM, "\u100A\u1014\u1031 12:12:12"},
+                {FORMAT_MEDIUM_BURMESE, LOCAL_TIME_PM, "\u1014\u1031\u1037\u101c\u101a\u103a 12:12:12"},
         };
     }
 
diff --git a/test/jdk/java/util/Calendar/Bug8007038.java b/test/jdk/java/util/Calendar/Bug8007038.java
index 64a59d379c3..5c363063578 100644
--- a/test/jdk/java/util/Calendar/Bug8007038.java
+++ b/test/jdk/java/util/Calendar/Bug8007038.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,12 +23,14 @@
 
 /*
  * @test
- * @bug 8007038
+ * @bug 8007038 8247781
  * @summary Verify ArrayIndexOutOfBoundsException is not thrown on
  *     on calling localizedDateTime().print() with JapaneseChrono
  * @modules java.base/sun.util.locale.provider
+ * @modules jdk.localedata
  * @compile -XDignore.symbol.file Bug8007038.java
- * @run main Bug8007038
+ * @run main/othervm -Djava.locale.providers=COMPAT Bug8007038 COMPAT
+ * @run main/othervm -Djava.locale.providers=CLDR Bug8007038 CLDR
  */
 
 import java.util.*;
@@ -88,11 +90,12 @@ public class Bug8007038 {
                 checkValueRange(calTypes[calIdx], DAY_OF_WEEK, SATURDAY+1, LONG, testLocs[locIdx], false);
 
                 // am/pm
-                for (int fieldIdx = AM; fieldIdx <= PM; fieldIdx++) {
+                int lastIndex = args[0].equals("CLDR") ? 11 : PM;
+                for (int fieldIdx = AM; fieldIdx <= lastIndex; fieldIdx++) {
                     checkValueRange(calTypes[calIdx], AM_PM, fieldIdx, LONG, testLocs[locIdx], true);
                 }
                 checkValueRange(calTypes[calIdx], AM_PM, AM-1, LONG, testLocs[locIdx], false);
-                checkValueRange(calTypes[calIdx], AM_PM, PM+1, LONG, testLocs[locIdx], false);
+                checkValueRange(calTypes[calIdx], AM_PM, lastIndex+1, LONG, testLocs[locIdx], false);
             }
         }
     }
diff --git a/test/jdk/java/util/Calendar/NarrowNamesTest.java b/test/jdk/java/util/Calendar/NarrowNamesTest.java
index 9788f47b242..817f1d3f9aa 100644
--- a/test/jdk/java/util/Calendar/NarrowNamesTest.java
+++ b/test/jdk/java/util/Calendar/NarrowNamesTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,12 +23,13 @@
 
 /*
  * @test
- * @bug 8000983 8008577
+ * @bug 8000983 8008577 8247781
  * @summary Unit test for narrow names support. This test is locale data-dependent
- *          and assumes that both JRE and CLDR have the same narrow names.
+ *          and assumes that both COMPAT and CLDR have the same narrow names if not
+ *          explicitly specified.
  * @modules jdk.localedata
- * @comment Locale providers: JRE,SPI
- * @run main/othervm -Djava.locale.providers=JRE,SPI NarrowNamesTest JRE,SPI
+ * @comment Locale providers: COMPAT,SPI
+ * @run main/othervm -Djava.locale.providers=COMPAT,SPI NarrowNamesTest COMPAT,SPI
  * @comment Locale providers: CLDR
  * @run main/othervm -Djava.locale.providers=CLDR NarrowNamesTest CLDR
  */
@@ -50,11 +51,9 @@ public class NarrowNamesTest {
 
     private static int errors = 0;
 
-    private static String providers;
-
     // This test is locale data-dependent.
     public static void main(String[] args) {
-        providers = args[0];
+        String providers = args[0];
 
         test(US, ERA, "B",
              ERA, BC, YEAR, 1);
@@ -95,10 +94,28 @@ public class NarrowNamesTest {
                 "Sat"        // abb Saturday
                 );
         testMap(US, DAY_OF_WEEK, NARROW_FORMAT); // expect null
-        testMap(US, AM_PM, ALL_STYLES,
-                "AM", "PM",
-                RESET_INDEX,
-                "a", "p");
+        if (providers.startsWith("CLDR")) {
+            testMap(US, AM_PM, ALL_STYLES,
+                    "AM",
+                    "PM",
+                    "midnight",
+                    "noon",
+                    "in the morning",
+                    "",
+                    "in the afternoon",
+                    "",
+                    "in the evening",
+                    "",
+                    "at night",
+                    "",
+                    RESET_INDEX,
+                    "a", "p", "mi", "n", "", "", "", "", "", "", "", "");
+        } else {
+            testMap(US, AM_PM, ALL_STYLES,
+                    "AM", "PM",
+                    RESET_INDEX,
+                    "a", "p");
+        }
         testMap(JAJPJP, DAY_OF_WEEK, NARROW_STANDALONE,
                 "", // 1-based indexing for DAY_OF_WEEK
                 "\u65e5",
@@ -176,16 +193,16 @@ public class NarrowNamesTest {
         if (expected.length > 0) {
             expectedMap = new TreeMap<>(LengthBasedComparator.INSTANCE);
             int index = 0;
-            for (int i = 0; i < expected.length; i++) {
-                if (expected[i].isEmpty()) {
+            for (String s : expected) {
+                if (s.isEmpty()) {
                     index++;
                     continue;
                 }
-                if (expected[i] == RESET_INDEX) {
+                if (s == RESET_INDEX) {
                     index = 0;
                     continue;
                 }
-                expectedMap.put(expected[i], index++);
+                expectedMap.put(s, index++);
             }
         }
         Calendar cal = Calendar.getInstance(locale);