8247781: Day periods support

Reviewed-by: scolebourne, rriggs, joehw
This commit is contained in:
Naoto Sato 2020-11-16 23:12:45 +00:00
parent 0357db3581
commit bf84dac4cf
20 changed files with 1155 additions and 203 deletions

View File

@ -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 // Remove all duplicates
if (Objects.nonNull(parentsMap)) { if (Objects.nonNull(parentsMap)) {
for (Iterator<String> it = myMap.keySet().iterator(); it.hasNext();) { for (Iterator<String> it = myMap.keySet().iterator(); it.hasNext();) {
@ -522,8 +532,6 @@ class Bundle {
if (pattern != null) { if (pattern != null) {
// Perform date-time format pattern conversion which is // Perform date-time format pattern conversion which is
// applicable to both SimpleDateFormat and j.t.f.DateTimeFormatter. // 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); String transPattern = translateDateFormatLetters(calendarType, pattern, this::convertDateTimePatternLetter);
dateTimePatterns.add(i, transPattern); dateTimePatterns.add(i, transPattern);
// Additionally, perform SDF specific date-time format pattern conversion // Additionally, perform SDF specific date-time format pattern conversion
@ -653,17 +661,6 @@ class Bundle {
// as the best approximation // as the best approximation
appendN('y', count, sb); appendN('y', count, sb);
break; 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: default:
appendN(cldrLetter, count, sb); appendN(cldrLetter, count, sb);
break; break;
@ -720,6 +717,17 @@ class Bundle {
} }
break; 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: default:
appendN(cldrLetter, count, sb); appendN(cldrLetter, count, sb);
break; break;

View File

@ -70,6 +70,7 @@ public class CLDRConverter {
private static String TIMEZONE_SOURCE_FILE; private static String TIMEZONE_SOURCE_FILE;
private static String WINZONES_SOURCE_FILE; private static String WINZONES_SOURCE_FILE;
private static String PLURALS_SOURCE_FILE; private static String PLURALS_SOURCE_FILE;
private static String DAYPERIODRULE_SOURCE_FILE;
static String DESTINATION_DIR = "build/gensrc"; static String DESTINATION_DIR = "build/gensrc";
static final String LOCALE_NAME_PREFIX = "locale.displayname."; static final String LOCALE_NAME_PREFIX = "locale.displayname.";
@ -100,6 +101,7 @@ public class CLDRConverter {
static NumberingSystemsParseHandler handlerNumbering; static NumberingSystemsParseHandler handlerNumbering;
static MetaZonesParseHandler handlerMetaZones; static MetaZonesParseHandler handlerMetaZones;
static TimeZoneParseHandler handlerTimeZone; static TimeZoneParseHandler handlerTimeZone;
static DayPeriodRuleParseHandler handlerDayPeriodRule;
private static BundleGenerator bundleGenerator; private static BundleGenerator bundleGenerator;
// java.base module related // java.base module related
@ -116,6 +118,10 @@ public class CLDRConverter {
private static String tzDataDir; private static String tzDataDir;
private static final Map<String, String> canonicalTZMap = new HashMap<>(); private static final Map<String, String> canonicalTZMap = new HashMap<>();
// rules maps
static Map<String, String> pluralRules;
static Map<String, String> dayPeriodRules;
static enum DraftType { static enum DraftType {
UNCONFIRMED, UNCONFIRMED,
PROVISIONAL, PROVISIONAL,
@ -248,6 +254,7 @@ public class CLDRConverter {
SPPL_META_SOURCE_FILE = CLDR_BASE + "/supplemental/supplementalMetadata.xml"; SPPL_META_SOURCE_FILE = CLDR_BASE + "/supplemental/supplementalMetadata.xml";
WINZONES_SOURCE_FILE = CLDR_BASE + "/supplemental/windowsZones.xml"; WINZONES_SOURCE_FILE = CLDR_BASE + "/supplemental/windowsZones.xml";
PLURALS_SOURCE_FILE = CLDR_BASE + "/supplemental/plurals.xml"; PLURALS_SOURCE_FILE = CLDR_BASE + "/supplemental/plurals.xml";
DAYPERIODRULE_SOURCE_FILE = CLDR_BASE + "/supplemental/dayPeriods.xml";
if (BASE_LOCALES.isEmpty()) { if (BASE_LOCALES.isEmpty()) {
setupBaseLocales("en-US"); setupBaseLocales("en-US");
@ -259,6 +266,10 @@ public class CLDRConverter {
parseSupplemental(); parseSupplemental();
parseBCP47(); parseBCP47();
// rules maps
pluralRules = generateRules(handlerPlurals);
dayPeriodRules = generateRules(handlerDayPeriodRule);
List<Bundle> bundles = readBundleList(); List<Bundle> bundles = readBundleList();
convertBundles(bundles); convertBundles(bundles);
@ -268,9 +279,6 @@ public class CLDRConverter {
// Generate Windows tzmappings // Generate Windows tzmappings
generateWindowsTZMappings(); generateWindowsTZMappings();
// Generate Plural rules
generatePluralRules();
} }
} }
@ -462,6 +470,10 @@ public class CLDRConverter {
// Parse plurals // Parse plurals
handlerPlurals = new PluralsParseHandler(); handlerPlurals = new PluralsParseHandler();
parseLDMLFile(new File(PLURALS_SOURCE_FILE), handlerPlurals); 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 // Parsers for data in "bcp47" directory
@ -809,7 +821,9 @@ public class CLDRConverter {
"TimePatterns", "TimePatterns",
"DatePatterns", "DatePatterns",
"DateTimePatterns", "DateTimePatterns",
"DateTimePatternChars" "DateTimePatternChars",
"PluralRules",
"DayPeriodRules",
}; };
private static Map<String, Object> extractFormatData(Map<String, Object> map, String id) { private static Map<String, Object> extractFormatData(Map<String, Object> map, String id) {
@ -1125,49 +1139,21 @@ public class CLDRConverter {
} }
/** /**
* Generate ResourceBundle source file for plural rules. The generated * Generates rules map for Plural rules and DayPeriod rules. The key is the locale id,
* class is {@code sun.text.resources.PluralRules} which has one public * and the value is rules, defined by the LDML spec. Each rule consists of {@code type:rule}
* two dimensional array {@code rulesArray}. Each array element consists * notation, concatenated with a ";" as a delimiter.
* of two elements that designate the locale and the locale's plural rules * @param handler handler containing rules
* string. The latter has the syntax from Unicode Consortium's * @return the map
* <a href="http://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax">
* Plural rules syntax</a>. {@code samples} and {@code "other"} are being ommited.
*
* @throws Exception
*/ */
private static void generatePluralRules() throws Exception { private static Map<String, String> generateRules(AbstractLDMLHandler<Map<String, String>> handler) {
Files.createDirectories(Paths.get(DESTINATION_DIR, "sun", "text", "resources")); return handler.getData().entrySet().stream()
Files.write(Paths.get(DESTINATION_DIR, "sun", "text", "resources", "PluralRules.java"), .collect(Collectors.toMap(Map.Entry::getKey, e -> {
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<String> pluralRulesStream() {
return handlerPlurals.getData().entrySet().stream()
.filter(e -> !(e.getValue()).isEmpty())
.map(e -> {
String loc = e.getKey();
Map<String, String> rules = e.getValue(); Map<String, String> rules = e.getValue();
return " {\"" + loc + "\", \"" + return rules.entrySet().stream()
rules.entrySet().stream() .map(rule -> rule.getKey() + ":" + rule.getValue().replaceFirst("@.*", ""))
.map(rule -> rule.getKey() + ":" + rule.getValue().replaceFirst("@.*", "")) .map(String::trim)
.map(String::trim) .collect(Collectors.joining(";"));
.collect(Collectors.joining(";")) + "\"},"; }));
});
} }
// for debug // for debug
@ -1188,4 +1174,3 @@ public class CLDRConverter {
.forEach(System.out::println); .forEach(System.out::println);
} }
} }

View File

@ -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<Map<String, String>> {
@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<String, String> rules = get(loc);
if (rules == null) {
rules = new HashMap<>();
put(loc, rules);
}
rules.put(type, rule);
});
}
break;
}
currentContainer = currentContainer.getParent();
}
}

View File

@ -300,8 +300,6 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
pushStringArrayElement(qName, attributes, Integer.parseInt(DAY_OF_WEEK_MAP.get(attributes.getValue("type"))) - 1); pushStringArrayElement(qName, attributes, Integer.parseInt(DAY_OF_WEEK_MAP.get(attributes.getValue("type"))) - 1);
break; break;
case "dayPeriodContext": case "dayPeriodContext":
// for FormatData
// need to keep stand-alone and format, to allow for multiple inheritance in CLDR
// for FormatData // for FormatData
// need to keep stand-alone and format, to allow for multiple inheritance in CLDR // need to keep stand-alone and format, to allow for multiple inheritance in CLDR
{ {
@ -316,17 +314,17 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
break; break;
case "dayPeriodWidth": case "dayPeriodWidth":
// for FormatData // for FormatData
// create string array entry for am/pm. only keeping wide // create string array entry for am/pm.
currentWidth = attributes.getValue("type"); currentWidth = attributes.getValue("type");
switch (currentWidth) { switch (currentWidth) {
case "wide": case "wide":
pushStringArrayEntry(qName, attributes, "AmPmMarkers/" + getContainerKey(), 2); pushStringArrayEntry(qName, attributes, "AmPmMarkers/" + getContainerKey(), 12);
break; break;
case "narrow": case "narrow":
pushStringArrayEntry(qName, attributes, "narrow.AmPmMarkers/" + getContainerKey(), 2); pushStringArrayEntry(qName, attributes, "narrow.AmPmMarkers/" + getContainerKey(), 12);
break; break;
case "abbreviated": case "abbreviated":
pushStringArrayEntry(qName, attributes, "abbreviated.AmPmMarkers/" + getContainerKey(), 2); pushStringArrayEntry(qName, attributes, "abbreviated.AmPmMarkers/" + getContainerKey(), 12);
break; break;
default: default:
pushIgnoredContainer(qName); pushIgnoredContainer(qName);
@ -344,6 +342,36 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
case "pm": case "pm":
pushStringArrayElement(qName, attributes, 1); pushStringArrayElement(qName, attributes, 1);
break; 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: default:
pushIgnoredContainer(qName); pushIgnoredContainer(qName);
break; break;
@ -971,6 +999,7 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
} }
@Override @Override
@SuppressWarnings("fallthrough")
public void endElement(String uri, String localName, String qName) throws SAXException { public void endElement(String uri, String localName, String qName) throws SAXException {
assert qName.equals(currentContainer.getqName()) : "current=" + currentContainer.getqName() + ", param=" + qName; assert qName.equals(currentContainer.getqName()) : "current=" + currentContainer.getqName() + ", param=" + qName;
switch (qName) { switch (qName) {

View File

@ -84,19 +84,19 @@ class PluralsParseHandler extends AbstractLDMLHandler<Map<String, String>> {
assert !(currentContainer instanceof Entry); assert !(currentContainer instanceof Entry);
Entry<?> entry = (Entry<?>)currentContainer; Entry<?> entry = (Entry<?>)currentContainer;
final String count = entry.getKey(); final String count = entry.getKey();
final String rule = (String)entry.getValue(); if (!count.equals("other")) {
String locales = ((KeyContainer)(currentContainer.getParent())).getKey(); final String rule = (String)entry.getValue();
Arrays.stream(locales.split("\\s")) String locales = ((KeyContainer)(currentContainer.getParent())).getKey();
Arrays.stream(locales.split("\\s"))
.forEach(loc -> { .forEach(loc -> {
Map<String, String> rules = get(loc); Map<String, String> rules = get(loc);
if (rules == null) { if (rules == null) {
rules = new HashMap<>(); rules = new HashMap<>();
put(loc, rules); put(loc, rules);
} }
if (!count.equals("other")) { rules.put(count, rule);
rules.put(count, rule);
}
}); });
}
break; break;
} }

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.HashSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
@ -69,6 +70,7 @@ class ResourceBundleGenerator implements BundleGenerator {
private static final String META_VALUE_PREFIX = "metaValue_"; private static final String META_VALUE_PREFIX = "metaValue_";
@Override @Override
@SuppressWarnings("unchecked")
public void generateBundle(String packageName, String baseName, String localeID, boolean useJava, public void generateBundle(String packageName, String baseName, String localeID, boolean useJava,
Map<String, ?> map, BundleType type) throws IOException { Map<String, ?> map, BundleType type) throws IOException {
String suffix = useJava ? ".java" : ".properties"; String suffix = useJava ? ".java" : ".properties";
@ -162,7 +164,13 @@ class ResourceBundleGenerator implements BundleGenerator {
if (val instanceof String[]) { if (val instanceof String[]) {
fmt.format(" final String[] %s = new String[] {\n", metaVal); 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<String>) val) {
fmt.format(" \"%s\",\n", CLDRConverter.saveConvert(s, useJava)); fmt.format(" \"%s\",\n", CLDRConverter.saveConvert(s, useJava));
} }
fmt.format(" };\n"); fmt.format(" };\n");

View File

@ -758,6 +758,11 @@ public class DateFormatSymbols implements Serializable, Cloneable {
dfs.months = resource.getStringArray("MonthNames"); dfs.months = resource.getStringArray("MonthNames");
dfs.shortMonths = resource.getStringArray("MonthAbbreviations"); dfs.shortMonths = resource.getStringArray("MonthAbbreviations");
dfs.ampms = resource.getStringArray("AmPmMarkers"); 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"); dfs.localPatternChars = resource.getString("DateTimePatternChars");
// Day of week names are stored in a 1-based array. // Day of week names are stored in a 1-based array.

View File

@ -300,6 +300,7 @@ import sun.util.locale.provider.TimeZoneNameUtility;
* <tr><th scope="row">F</th> <td>day-of-week-in-month</td> <td>number</td> <td>3</td> * <tr><th scope="row">F</th> <td>day-of-week-in-month</td> <td>number</td> <td>3</td>
* *
* <tr><th scope="row">a</th> <td>am-pm-of-day</td> <td>text</td> <td>PM</td> * <tr><th scope="row">a</th> <td>am-pm-of-day</td> <td>text</td> <td>PM</td>
* <tr><th scope="row">B</th> <td>period-of-day</td> <td>text</td> <td>in the morning</td>
* <tr><th scope="row">h</th> <td>clock-hour-of-am-pm (1-12)</td> <td>number</td> <td>12</td> * <tr><th scope="row">h</th> <td>clock-hour-of-am-pm (1-12)</td> <td>number</td> <td>12</td>
* <tr><th scope="row">K</th> <td>hour-of-am-pm (0-11)</td> <td>number</td> <td>0</td> * <tr><th scope="row">K</th> <td>hour-of-am-pm (0-11)</td> <td>number</td> <td>0</td>
* <tr><th scope="row">k</th> <td>clock-hour-of-day (1-24)</td> <td>number</td> <td>24</td> * <tr><th scope="row">k</th> <td>clock-hour-of-day (1-24)</td> <td>number</td> <td>24</td>

View File

@ -102,6 +102,7 @@ import java.time.zone.ZoneRulesProvider;
import java.util.AbstractMap.SimpleImmutableEntry; import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@ -117,10 +118,13 @@ import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import sun.text.spi.JavaTimeDateTimePatternProvider; import sun.text.spi.JavaTimeDateTimePatternProvider;
import sun.util.locale.provider.CalendarDataUtility; import sun.util.locale.provider.CalendarDataUtility;
import sun.util.locale.provider.LocaleProviderAdapter; import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleResources;
import sun.util.locale.provider.TimeZoneNameUtility; import sun.util.locale.provider.TimeZoneNameUtility;
/** /**
@ -218,10 +222,9 @@ public final class DateTimeFormatterBuilder {
} }
LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(JavaTimeDateTimePatternProvider.class, locale); LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(JavaTimeDateTimePatternProvider.class, locale);
JavaTimeDateTimePatternProvider provider = adapter.getJavaTimeDateTimePatternProvider(); JavaTimeDateTimePatternProvider provider = adapter.getJavaTimeDateTimePatternProvider();
String pattern = provider.getJavaTimeDateTimePattern(convertStyle(timeStyle), return provider.getJavaTimeDateTimePattern(convertStyle(timeStyle),
convertStyle(dateStyle), chrono.getCalendarType(), convertStyle(dateStyle), chrono.getCalendarType(),
CalendarDataUtility.findRegionOverride(locale)); 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 * 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 * are equal and there is no decimal point then the parser will
* participate in adjacent value parsing, see * 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. * the minimum width is considered to be zero and the maximum is nine.
* <p> * <p>
* If the value cannot be obtained then an exception will be thrown. * If the value cannot be obtained then an exception will be thrown.
@ -1447,6 +1450,55 @@ public final class DateTimeFormatterBuilder {
return this; return this;
} }
/**
* Appends the day period text to the formatter.
* <p>
* This appends an instruction to format/parse the textual name of the day period
* to the builder. Day periods are defined in LDML's
* <a href="https://unicode.org/reports/tr35/tr35-dates.html#dayPeriods">"day periods"
* </a> element.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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. * 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 * F day-of-week-in-month number 3
* *
* a am-pm-of-day text PM * 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 * h clock-hour-of-am-pm (1-12) number 12
* K hour-of-am-pm (0-11) number 0 * K hour-of-am-pm (0-11) number 0
* k clock-hour-of-day (1-24) number 24 * 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) * N..N 1..n appendValue(ChronoField.NANO_OF_DAY, n, 19, SignStyle.NOT_NEGATIVE)
* </pre> * </pre>
* <p> * <p>
* <b>Day periods</b>: Pattern letters to output a day period.
* <pre>
* Pattern Count Equivalent builder methods
* ------- ----- --------------------------
* B 1 appendDayPeriodText(TextStyle.SHORT)
* BBBB 4 appendDayPeriodText(TextStyle.FULL)
* BBBBB 5 appendDayPeriodText(TextStyle.NARROW)
* </pre>
* <p>
* <b>Zone ID</b>: Pattern letters to output {@code ZoneId}. * <b>Zone ID</b>: Pattern letters to output {@code ZoneId}.
* <pre> * <pre>
* Pattern Count Equivalent builder methods * Pattern Count Equivalent builder methods
@ -1759,7 +1821,7 @@ public final class DateTimeFormatterBuilder {
} else if (count == 4) { } else if (count == 4) {
appendGenericZoneText(TextStyle.FULL); appendGenericZoneText(TextStyle.FULL);
} else { } else {
throw new IllegalArgumentException("Wrong number of pattern letters: " + cur); throw new IllegalArgumentException("Wrong number of pattern letters: " + cur);
} }
} else if (cur == 'Z') { } else if (cur == 'Z') {
if (count < 4) { if (count < 4) {
@ -1809,6 +1871,13 @@ public final class DateTimeFormatterBuilder {
} else { } else {
appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 19)); 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 { } else {
throw new IllegalArgumentException("Unknown pattern letter: " + cur); throw new IllegalArgumentException("Unknown pattern letter: " + cur);
} }
@ -1920,19 +1989,10 @@ public final class DateTimeFormatterBuilder {
break; break;
case 'G': case 'G':
switch (count) { switch (count) {
case 1: case 1, 2, 3 -> appendText(field, TextStyle.SHORT);
case 2: case 4 -> appendText(field, TextStyle.FULL);
case 3: case 5 -> appendText(field, TextStyle.NARROW);
appendText(field, TextStyle.SHORT); default -> throw new IllegalArgumentException("Too many pattern letters: " + cur);
break;
case 4:
appendText(field, TextStyle.FULL);
break;
case 5:
appendText(field, TextStyle.NARROW);
break;
default:
throw new IllegalArgumentException("Too many pattern letters: " + cur);
} }
break; break;
case 'S': 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, almost matches SDF for 1, exact match 2&3, extended 4&5
// 310 - x - matches LDML // 310 - x - matches LDML
// 310 - w, W, and Y are localized forms matching 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 - U - cycle year name, not supported by 310 yet
// LDML - l - deprecated // LDML - l - deprecated
// LDML - j - not relevant // LDML - j - not relevant
@ -2160,9 +2221,7 @@ public final class DateTimeFormatterBuilder {
private int appendInternal(DateTimePrinterParser pp) { private int appendInternal(DateTimePrinterParser pp) {
Objects.requireNonNull(pp, "pp"); Objects.requireNonNull(pp, "pp");
if (active.padNextWidth > 0) { 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.padNextWidth = 0;
active.padNextChar = 0; active.padNextChar = 0;
} }
@ -2311,7 +2370,7 @@ public final class DateTimeFormatterBuilder {
private final boolean optional; private final boolean optional;
CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boolean optional) { CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boolean optional) {
this(printerParsers.toArray(new DateTimePrinterParser[printerParsers.size()]), optional); this(printerParsers.toArray(new DateTimePrinterParser[0]), optional);
} }
CompositePrinterParser(DateTimePrinterParser[] printerParsers, boolean 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. * minWidth equal to maxWidth and decimalpoint is absent.
* @param context the context * @param context the context
* @return if the field is fixed width * @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 @Override
boolean isFixedWidth(DateTimeParseContext context) { boolean isFixedWidth(DateTimeParseContext context) {
@ -4975,14 +5034,296 @@ public final class DateTimeFormatterBuilder {
} }
} }
//------------------------------------------------------------------------- //-----------------------------------------------------------------------
/** /**
* Length comparator. * Prints or parses day periods.
*/ */
static final Comparator<String> LENGTH_SORT = new Comparator<String>() { static final class DayPeriodPrinterParser implements DateTimePrinterParser {
@Override private final TextStyle textStyle;
public int compare(String str1, String str2) { private final static ConcurrentMap<Locale, LocaleStore> DAYPERIOD_LOCALESTORE = new ConcurrentHashMap<>();
return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length();
/**
* 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<Entry<String, Long>> it;
LocaleStore store = findDayPeriodStore(context.getLocale());
it = store.getTextIterator(style);
if (it != null) {
while (it.hasNext()) {
Entry<String, Long> 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<TextStyle, Map<Long, String>> styleMap = new HashMap<>();
for (TextStyle textStyle : TextStyle.values()) {
if (textStyle.isStandalone()) {
// Stand-alone isn't applicable to day period.
continue;
}
Map<Long, String> 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
* <a href="https://www.unicode.org/reports/tr35/tr35-dates.html#dayPeriods">DayPeriod</a> defined in CLDR.
* This is a value-based class.
*/
static final class DayPeriod {
/**
* DayPeriod cache
*/
private final static Map<Locale, Map<DayPeriod, Long>> DAYPERIOD_CACHE = new ConcurrentHashMap<>();
/**
* comparator based on the duration of the day period.
*/
private final static Comparator<DayPeriod> DPCOMPARATOR = (dp1, dp2) -> (int)(dp1.duration() - dp2.duration());
/**
* Pattern to parse day period rules
*/
private final static Pattern RULE = Pattern.compile("(?<type>[a-z12]+):(?<from>\\d{2}):00(-(?<to>\\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<DayPeriod, Long> getDayPeriodMap(Locale locale) {
return DAYPERIOD_CACHE.computeIfAbsent(locale, l -> {
LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
.getLocaleResources(CalendarDataUtility.findRegionOverride(l));
String dayPeriodRules = lr.getRules()[1];
final Map<DayPeriod, Long> 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));
}
}
} }

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -424,6 +424,15 @@ final class DateTimeParseContext {
currentParsed().leapSecond = true; 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. * Returns a string version of the context for debugging.

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -61,6 +61,7 @@
*/ */
package java.time.format; 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.AMPM_OF_DAY;
import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM; import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_AMPM;
import static java.time.temporal.ChronoField.CLOCK_HOUR_OF_DAY; 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. * The excess period from time-only parsing.
*/ */
Period excessDays = Period.ZERO; Period excessDays = Period.ZERO;
/**
* The parsed day period.
*/
DayPeriod dayPeriod;
/** /**
* Creates an instance. * Creates an instance.
@ -172,6 +177,7 @@ final class Parsed implements TemporalAccessor {
cloned.zone = this.zone; cloned.zone = this.zone;
cloned.chrono = this.chrono; cloned.chrono = this.chrono;
cloned.leapSecond = this.leapSecond; cloned.leapSecond = this.leapSecond;
cloned.dayPeriod = this.dayPeriod;
return cloned; return cloned;
} }
@ -332,7 +338,8 @@ final class Parsed implements TemporalAccessor {
} }
} }
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
private void resolveInstantFields() { private void resolveInstantFields() {
// resolve parsed instant seconds to date and time if zone available // resolve parsed instant seconds to date and time if zone available
if (fieldValues.containsKey(INSTANT_SECONDS)) { 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) // convert to time if all four fields available (optimization)
if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) && if (fieldValues.containsKey(HOUR_OF_DAY) && fieldValues.containsKey(MINUTE_OF_HOUR) &&
fieldValues.containsKey(SECOND_OF_MINUTE) && fieldValues.containsKey(NANO_OF_SECOND)) { 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); 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 // merge hour/minute/second/nano leniently
Long hod = fieldValues.get(HOUR_OF_DAY); Long hod = fieldValues.get(HOUR_OF_DAY);
if (hod != null) { if (hod != null) {
@ -523,6 +564,15 @@ final class Parsed implements TemporalAccessor {
long mohVal = (moh != null ? moh : 0); long mohVal = (moh != null ? moh : 0);
long somVal = (som != null ? som : 0); long somVal = (som != null ? som : 0);
long nosVal = (nos != null ? nos : 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); resolveTime(hod, mohVal, somVal, nosVal);
fieldValues.remove(HOUR_OF_DAY); fieldValues.remove(HOUR_OF_DAY);
fieldValues.remove(MINUTE_OF_HOUR); fieldValues.remove(MINUTE_OF_HOUR);

View File

@ -333,8 +333,9 @@ public enum ChronoField implements TemporalField {
* When parsing this field it behaves equivalent to the following: * When parsing this field it behaves equivalent to the following:
* The value is validated from 0 to 1 in strict and smart mode. * 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 * In lenient mode the value is not validated. It is combined with
* {@code HOUR_OF_AMPM} to form {@code HOUR_OF_DAY} by multiplying * {@code HOUR_OF_AMPM} (if not present, it defaults to '6') to form
* the {@code AMPM_OF_DAY} value by 12. * {@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"), AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1), "dayperiod"),
/** /**

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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, // If standalone names are requested and no "standalone." resources are found,
// try the default ones instead. // try the default ones instead.
if (strings == null && key.indexOf("standalone.") != -1) { if (strings == null && key.contains("standalone.")) {
key = key.replaceFirst("standalone.", ""); key = key.replaceFirst("standalone.", "");
strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key); strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
} }
@ -129,7 +129,7 @@ public class CalendarNameProviderImpl extends CalendarNameProvider implements Av
return name; return name;
} }
private static int[] REST_OF_STYLES = { private static final int[] REST_OF_STYLES = {
SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE, SHORT_STANDALONE, LONG_FORMAT, LONG_STANDALONE,
NARROW_FORMAT, NARROW_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, // If standalone names are requested and no "standalone." resources are found,
// try the default ones instead. // try the default ones instead.
if (strings == null && key.indexOf("standalone.") != -1) { if (strings == null && key.contains("standalone.")) {
key = key.replaceFirst("standalone.", ""); key = key.replaceFirst("standalone.", "");
strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key); strings = javatime ? lr.getJavaTimeNames(key) : lr.getCalendarNames(key);
} }
if (strings != null) { if (strings != null) {
if (!hasDuplicates(strings)) { if (!hasDuplicates(strings) || field == AM_PM) {
if (field == YEAR) { if (field == YEAR) {
if (strings.length > 0) { if (strings.length > 0) {
map.put(strings[0], 1); map.put(strings[0], 1);
} }
} else { } else {
int base = (field == DAY_OF_WEEK) ? 1 : 0; 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]; String name = strings[i];
// Ignore any empty string (some standalone month names // Ignore any empty string (some standalone month names
// are not defined) // or flexible day periods are not defined)
if (name.isEmpty()) { if (name.isEmpty()) {
continue; continue;
} }

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Set; import java.util.Set;
@ -91,6 +90,7 @@ public class LocaleResources {
private static final String NUMBER_PATTERNS_CACHEKEY = "NP"; private static final String NUMBER_PATTERNS_CACHEKEY = "NP";
private static final String COMPACT_NUMBER_PATTERNS_CACHEKEY = "CNP"; private static final String COMPACT_NUMBER_PATTERNS_CACHEKEY = "CNP";
private static final String DATE_TIME_PATTERN = "DTP."; private static final String DATE_TIME_PATTERN = "DTP.";
private static final String RULES_CACHEKEY = "RULE";
// TimeZoneNamesBundle exemplar city prefix // TimeZoneNamesBundle exemplar city prefix
private static final String TZNB_EXCITY_PREFIX = "timezone.excity."; private static final String TZNB_EXCITY_PREFIX = "timezone.excity.";
@ -125,7 +125,6 @@ public class LocaleResources {
return biInfo; return biInfo;
} }
@SuppressWarnings("unchecked")
byte[] getBreakIteratorResources(String key) { byte[] getBreakIteratorResources(String key) {
return (byte[]) localeData.getBreakIteratorResources(locale).getObject(key); return (byte[]) localeData.getBreakIteratorResources(locale).getObject(key);
} }
@ -162,7 +161,7 @@ public class LocaleResources {
coldata = rb.getString(key); coldata = rb.getString(key);
} }
cache.put(COLLATION_DATA_CACHEKEY, cache.put(COLLATION_DATA_CACHEKEY,
new ResourceReference(COLLATION_DATA_CACHEKEY, (Object) coldata, referenceQueue)); new ResourceReference(COLLATION_DATA_CACHEKEY, coldata, referenceQueue));
} }
return coldata; return coldata;
@ -181,7 +180,7 @@ public class LocaleResources {
dfsdata[0] = getNumberStrings(rb, "NumberElements"); dfsdata[0] = getNumberStrings(rb, "NumberElements");
cache.put(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, 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; return dfsdata;
@ -314,7 +313,7 @@ public class LocaleResources {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Set<String> getZoneIDs() { Set<String> getZoneIDs() {
Set<String> zoneIDs = null; Set<String> zoneIDs;
removeEmptyReferences(); removeEmptyReferences();
ResourceReference data = cache.get(ZONE_IDS_CACHEKEY); ResourceReference data = cache.get(ZONE_IDS_CACHEKEY);
@ -322,7 +321,7 @@ public class LocaleResources {
TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale); TimeZoneNamesBundle rb = localeData.getTimeZoneNames(locale);
zoneIDs = rb.keySet(); zoneIDs = rb.keySet();
cache.put(ZONE_IDS_CACHEKEY, cache.put(ZONE_IDS_CACHEKEY,
new ResourceReference(ZONE_IDS_CACHEKEY, (Object) zoneIDs, referenceQueue)); new ResourceReference(ZONE_IDS_CACHEKEY, zoneIDs, referenceQueue));
} }
return zoneIDs; return zoneIDs;
@ -380,7 +379,7 @@ public class LocaleResources {
if (rb.containsKey(key)) { if (rb.containsKey(key)) {
names = rb.getStringArray(key); names = rb.getStringArray(key);
cache.put(cacheKey, 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)) { if (rb.containsKey(key)) {
names = rb.getStringArray(key); names = rb.getStringArray(key);
cache.put(cacheKey, cache.put(cacheKey,
new ResourceReference(cacheKey, (Object) names, referenceQueue)); new ResourceReference(cacheKey, names, referenceQueue));
} }
} }
@ -463,17 +462,11 @@ public class LocaleResources {
if (dateTimePattern == null) { if (dateTimePattern == null) {
dateTimePattern = getDateTimePattern(null, "DateTimePatterns", dateTimeStyle, calType); dateTimePattern = getDateTimePattern(null, "DateTimePatterns", dateTimeStyle, calType);
} }
switch (dateTimePattern) { pattern = switch (Objects.requireNonNull(dateTimePattern)) {
case "{1} {0}": case "{1} {0}" -> datePattern + " " + timePattern;
pattern = datePattern + " " + timePattern; case "{0} {1}" -> timePattern + " " + datePattern;
break; default -> MessageFormat.format(dateTimePattern.replaceAll("'", "''"), timePattern, datePattern);
case "{0} {1}": };
pattern = timePattern + " " + datePattern;
break;
default:
pattern = MessageFormat.format(dateTimePattern.replaceAll("'", "''"), timePattern, datePattern);
break;
}
} else { } else {
pattern = timePattern; pattern = timePattern;
} }
@ -486,7 +479,7 @@ public class LocaleResources {
} }
public String[] getNumberPatterns() { public String[] getNumberPatterns() {
String[] numberPatterns = null; String[] numberPatterns;
removeEmptyReferences(); removeEmptyReferences();
ResourceReference data = cache.get(NUMBER_PATTERNS_CACHEKEY); ResourceReference data = cache.get(NUMBER_PATTERNS_CACHEKEY);
@ -495,7 +488,7 @@ public class LocaleResources {
ResourceBundle resource = localeData.getNumberFormatData(locale); ResourceBundle resource = localeData.getNumberFormatData(locale);
numberPatterns = getNumberStrings(resource, "NumberPatterns"); numberPatterns = getNumberStrings(resource, "NumberPatterns");
cache.put(NUMBER_PATTERNS_CACHEKEY, cache.put(NUMBER_PATTERNS_CACHEKEY,
new ResourceReference(NUMBER_PATTERNS_CACHEKEY, (Object) numberPatterns, referenceQueue)); new ResourceReference(NUMBER_PATTERNS_CACHEKEY, numberPatterns, referenceQueue));
} }
return numberPatterns; return numberPatterns;
@ -506,11 +499,10 @@ public class LocaleResources {
* @param formatStyle the style for formatting a number * @param formatStyle the style for formatting a number
* @return an array of compact number patterns * @return an array of compact number patterns
*/ */
@SuppressWarnings("unchecked")
public String[] getCNPatterns(NumberFormat.Style formatStyle) { public String[] getCNPatterns(NumberFormat.Style formatStyle) {
Objects.requireNonNull(formatStyle); Objects.requireNonNull(formatStyle);
String[] compactNumberPatterns = null; String[] compactNumberPatterns;
removeEmptyReferences(); removeEmptyReferences();
String width = (formatStyle == NumberFormat.Style.LONG) ? "long" : "short"; String width = (formatStyle == NumberFormat.Style.LONG) ? "long" : "short";
String cacheKey = width + "." + COMPACT_NUMBER_PATTERNS_CACHEKEY; String cacheKey = width + "." + COMPACT_NUMBER_PATTERNS_CACHEKEY;
@ -520,8 +512,7 @@ public class LocaleResources {
ResourceBundle resource = localeData.getNumberFormatData(locale); ResourceBundle resource = localeData.getNumberFormatData(locale);
compactNumberPatterns = (String[]) resource compactNumberPatterns = (String[]) resource
.getObject(width + ".CompactNumberPatterns"); .getObject(width + ".CompactNumberPatterns");
cache.put(cacheKey, new ResourceReference(cacheKey, cache.put(cacheKey, new ResourceReference(cacheKey, compactNumberPatterns, referenceQueue));
(Object) compactNumberPatterns, referenceQueue));
} }
return compactNumberPatterns; return compactNumberPatterns;
} }
@ -579,6 +570,28 @@ public class LocaleResources {
return (styles.length > 1 ? styles[styleIndex] : styles[0]); 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<Object> { private static class ResourceReference extends SoftReference<Object> {
private final String cacheKey; private final String cacheKey;

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.DecimalFormatSymbols;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.spi.NumberFormatProvider; import java.text.spi.NumberFormatProvider;
import java.util.Arrays;
import java.util.Currency; import java.util.Currency;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import sun.text.resources.PluralRules;
/** /**
* Concrete implementation of the {@link java.text.spi.NumberFormatProvider * 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 LocaleProviderAdapter.Type type;
private final Set<String> langtags; private final Set<String> langtags;
private static Map<String, String> rulesMap =
Arrays.stream(PluralRules.rulesArray).collect(Collectors.toMap(
sa -> sa[0],
sa -> sa[1])
);
public NumberFormatProviderImpl(LocaleProviderAdapter.Type type, Set<String> langtags) { public NumberFormatProviderImpl(LocaleProviderAdapter.Type type, Set<String> langtags) {
this.type = type; this.type = type;
this.langtags = langtags; this.langtags = langtags;
@ -282,12 +272,10 @@ public class NumberFormatProviderImpl extends NumberFormatProvider implements Av
String[] cnPatterns = resource.getCNPatterns(formatStyle); String[] cnPatterns = resource.getCNPatterns(formatStyle);
// plural rules // plural rules
String pluralRules = rulesMap.getOrDefault(override.toString(), String[] rules = resource.getRules();
rulesMap.getOrDefault(override.getLanguage(), ""));
CompactNumberFormat format = new CompactNumberFormat(numberPatterns[0], return new CompactNumberFormat(numberPatterns[0],
symbols, cnPatterns, pluralRules); symbols, cnPatterns, rules[0]);
return format;
} }
@Override @Override

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -854,32 +854,32 @@ public class TCKDateTimeParseResolver {
@DataProvider(name="resolveAmPm") @DataProvider(name="resolveAmPm")
Object[][] data_resolveAmPm() { Object[][] data_resolveAmPm() {
return new Object[][]{ return new Object[][]{
{STRICT, 0, 0}, {STRICT, 0, null, 0},
{STRICT, 1, 1}, {STRICT, 1, null, 1},
{STRICT, -1, null}, {STRICT, -1, null, null},
{STRICT, 2, null}, {STRICT, 2, null, null},
{SMART, 0, 0}, {SMART, 0, LocalTime.of(6, 0), 0},
{SMART, 1, 1}, {SMART, 1, LocalTime.of(18, 0), 1},
{SMART, -1, null}, {SMART, -1, null, null},
{SMART, 2, null}, {SMART, 2, null, null},
{LENIENT, 0, 0}, {LENIENT, 0, LocalTime.of(6, 0), 0},
{LENIENT, 1, 1}, {LENIENT, 1, LocalTime.of(18, 0), 1},
{LENIENT, -1, -1}, {LENIENT, -1, LocalTime.of(18, 0), 1},
{LENIENT, 2, 2}, {LENIENT, 2, LocalTime.of(6, 0), 0},
}; };
} }
@Test(dataProvider="resolveAmPm") @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); String str = Long.toString(value);
DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(AMPM_OF_DAY).toFormatter(); DateTimeFormatter f = new DateTimeFormatterBuilder().appendValue(AMPM_OF_DAY).toFormatter();
if (expectedValue != null) { if (expectedValue != null) {
TemporalAccessor accessor = f.withResolverStyle(style).parse(str); TemporalAccessor accessor = f.withResolverStyle(style).parse(str);
assertEquals(accessor.query(TemporalQueries.localDate()), null); 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.isSupported(AMPM_OF_DAY), true);
assertEquals(accessor.getLong(AMPM_OF_DAY), expectedValue.longValue()); assertEquals(accessor.getLong(AMPM_OF_DAY), expectedValue.longValue());
} else { } else {

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -59,29 +59,38 @@
*/ */
package test.java.time.format; 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_MONTH;
import static java.time.temporal.ChronoField.DAY_OF_WEEK; 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.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR; import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.YEAR; import static java.time.temporal.ChronoField.YEAR;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.fail;
import java.text.ParsePosition; import java.text.ParsePosition;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;
import java.time.YearMonth; import java.time.YearMonth;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.chrono.Chronology; import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology; import java.time.chrono.IsoChronology;
import java.time.chrono.JapaneseChronology;
import java.time.chrono.MinguoChronology;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.format.FormatStyle; import java.time.format.FormatStyle;
import java.time.format.ResolverStyle;
import java.time.format.SignStyle; import java.time.format.SignStyle;
import java.time.format.TextStyle; import java.time.format.TextStyle;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal; import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor; 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.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -526,6 +535,360 @@ public class TestDateTimeFormatterBuilder {
builder.appendZoneText(null); 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 extends Temporal> R adjustInto(R temporal, long newValue) {
return null;
}
@Override
public TemporalAccessor resolve(
Map<TemporalField, Long> 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)"}, {"w", "Localized(WeekOfWeekBasedYear,1)"},
{"ww", "Localized(WeekOfWeekBasedYear,2)"}, {"ww", "Localized(WeekOfWeekBasedYear,2)"},
{"W", "Localized(WeekOfMonth,1)"}, {"W", "Localized(WeekOfMonth,1)"},
{"B", "DayPeriod(SHORT)"},
{"BBBB", "DayPeriod(FULL)"},
{"BBBBB", "DayPeriod(NARROW)"},
}; };
} }
@ -867,6 +1234,10 @@ public class TestDateTimeFormatterBuilder {
{"www"}, {"www"},
{"WW"}, {"WW"},
{"BB"},
{"BBB"},
{"BBBBBB"},
}; };
} }

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -22,12 +22,12 @@
*/ */
/* /*
* @test * @test
* @bug 8209175 * @bug 8209175 8247781
* @summary Checks the 'B' character added in the CLDR date-time patterns is * @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. * getting resolved with 'a' character (am/pm strings) for burmese locale.
* This test case assumes that the 'B' character is added in CLDRv33 update * 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 * for burmese locale in the time patterns. Since it is supported by
* DateTimeFormatter it is replaced with the 'a' while CLDR resource * DateTimeFormatter it should not be replaced with the 'a' while CLDR resource
* conversion. * conversion.
* @modules jdk.localedata * @modules jdk.localedata
*/ */
@ -56,10 +56,11 @@ public class TestDayPeriodWithDTF {
@DataProvider(name = "timePatternData") @DataProvider(name = "timePatternData")
Object[][] timePatternData() { Object[][] timePatternData() {
return new Object[][] { 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_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_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"},
}; };
} }

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,12 +23,14 @@
/* /*
* @test * @test
* @bug 8007038 * @bug 8007038 8247781
* @summary Verify ArrayIndexOutOfBoundsException is not thrown on * @summary Verify ArrayIndexOutOfBoundsException is not thrown on
* on calling localizedDateTime().print() with JapaneseChrono * on calling localizedDateTime().print() with JapaneseChrono
* @modules java.base/sun.util.locale.provider * @modules java.base/sun.util.locale.provider
* @modules jdk.localedata
* @compile -XDignore.symbol.file Bug8007038.java * @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.*; import java.util.*;
@ -88,11 +90,12 @@ public class Bug8007038 {
checkValueRange(calTypes[calIdx], DAY_OF_WEEK, SATURDAY+1, LONG, testLocs[locIdx], false); checkValueRange(calTypes[calIdx], DAY_OF_WEEK, SATURDAY+1, LONG, testLocs[locIdx], false);
// am/pm // 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, fieldIdx, LONG, testLocs[locIdx], true);
} }
checkValueRange(calTypes[calIdx], AM_PM, AM-1, LONG, testLocs[locIdx], false); 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);
} }
} }
} }

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,12 +23,13 @@
/* /*
* @test * @test
* @bug 8000983 8008577 * @bug 8000983 8008577 8247781
* @summary Unit test for narrow names support. This test is locale data-dependent * @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 * @modules jdk.localedata
* @comment Locale providers: JRE,SPI * @comment Locale providers: COMPAT,SPI
* @run main/othervm -Djava.locale.providers=JRE,SPI NarrowNamesTest JRE,SPI * @run main/othervm -Djava.locale.providers=COMPAT,SPI NarrowNamesTest COMPAT,SPI
* @comment Locale providers: CLDR * @comment Locale providers: CLDR
* @run main/othervm -Djava.locale.providers=CLDR NarrowNamesTest CLDR * @run main/othervm -Djava.locale.providers=CLDR NarrowNamesTest CLDR
*/ */
@ -50,11 +51,9 @@ public class NarrowNamesTest {
private static int errors = 0; private static int errors = 0;
private static String providers;
// This test is locale data-dependent. // This test is locale data-dependent.
public static void main(String[] args) { public static void main(String[] args) {
providers = args[0]; String providers = args[0];
test(US, ERA, "B", test(US, ERA, "B",
ERA, BC, YEAR, 1); ERA, BC, YEAR, 1);
@ -95,10 +94,28 @@ public class NarrowNamesTest {
"Sat" // abb Saturday "Sat" // abb Saturday
); );
testMap(US, DAY_OF_WEEK, NARROW_FORMAT); // expect null testMap(US, DAY_OF_WEEK, NARROW_FORMAT); // expect null
testMap(US, AM_PM, ALL_STYLES, if (providers.startsWith("CLDR")) {
"AM", "PM", testMap(US, AM_PM, ALL_STYLES,
RESET_INDEX, "AM",
"a", "p"); "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, testMap(JAJPJP, DAY_OF_WEEK, NARROW_STANDALONE,
"", // 1-based indexing for DAY_OF_WEEK "", // 1-based indexing for DAY_OF_WEEK
"\u65e5", "\u65e5",
@ -176,16 +193,16 @@ public class NarrowNamesTest {
if (expected.length > 0) { if (expected.length > 0) {
expectedMap = new TreeMap<>(LengthBasedComparator.INSTANCE); expectedMap = new TreeMap<>(LengthBasedComparator.INSTANCE);
int index = 0; int index = 0;
for (int i = 0; i < expected.length; i++) { for (String s : expected) {
if (expected[i].isEmpty()) { if (s.isEmpty()) {
index++; index++;
continue; continue;
} }
if (expected[i] == RESET_INDEX) { if (s == RESET_INDEX) {
index = 0; index = 0;
continue; continue;
} }
expectedMap.put(expected[i], index++); expectedMap.put(s, index++);
} }
} }
Calendar cal = Calendar.getInstance(locale); Calendar cal = Calendar.getInstance(locale);