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
if (Objects.nonNull(parentsMap)) {
for (Iterator<String> 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;

View File

@ -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<String, String> canonicalTZMap = new HashMap<>();
// rules maps
static Map<String, String> pluralRules;
static Map<String, String> 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<Bundle> 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<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
* 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
* <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
* 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<String> pluralRulesStream() {
return handlerPlurals.getData().entrySet().stream()
.filter(e -> !(e.getValue()).isEmpty())
.map(e -> {
String loc = e.getKey();
private static Map<String, String> generateRules(AbstractLDMLHandler<Map<String, String>> handler) {
return handler.getData().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> {
Map<String, String> 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);
}
}

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);
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<Object> {
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<Object> {
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<Object> {
}
@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) {

View File

@ -84,19 +84,19 @@ class PluralsParseHandler extends AbstractLDMLHandler<Map<String, String>> {
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<String, String> 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;
}

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.
*
* 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<String, ?> 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<String>) val) {
fmt.format(" \"%s\",\n", CLDRConverter.saveConvert(s, useJava));
}
fmt.format(" };\n");

View File

@ -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.

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">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">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>

View File

@ -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.
* <p>
* 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.
* <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.
@ -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)
* </pre>
* <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}.
* <pre>
* 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<DateTimePrinterParser> 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<String> LENGTH_SORT = new Comparator<String>() {
@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<Locale, LocaleStore> 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<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.
*
* 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.

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.
*
* 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);

View File

@ -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"),
/**

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.
*
* 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;
}

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.
*
* 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<String> getZoneIDs() {
Set<String> zoneIDs = null;
Set<String> 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<Object> {
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.
*
* 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<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) {
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

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.
*
* 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 {

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.
*
* 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 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)"},
{"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"},
};
}

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.
*
* 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"},
};
}

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.
*
* 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);
}
}
}

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.
*
* 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);