8247781: Day periods support
Reviewed-by: scolebourne, rriggs, joehw
This commit is contained in:
parent
0357db3581
commit
bf84dac4cf
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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"),
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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"},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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"},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user