mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8176706: Additional Date-Time Formats
Reviewed-by: joehw, rriggs
This commit is contained in:
parent
0f3d3ac32c
commit
9b74c3f2e7
12 changed files with 809 additions and 96 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -25,6 +25,9 @@
|
||||||
|
|
||||||
package build.tools.cldrconverter;
|
package build.tools.cldrconverter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
@ -34,6 +37,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
class Bundle {
|
class Bundle {
|
||||||
|
@ -47,21 +51,21 @@ class Bundle {
|
||||||
FORMATDATA);
|
FORMATDATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static Map<String, Bundle> bundles = new HashMap<>();
|
private static final Map<String, Bundle> bundles = new HashMap<>();
|
||||||
|
|
||||||
private final static String[] NUMBER_PATTERN_KEYS = {
|
private static final String[] NUMBER_PATTERN_KEYS = {
|
||||||
"NumberPatterns/decimal",
|
"NumberPatterns/decimal",
|
||||||
"NumberPatterns/currency",
|
"NumberPatterns/currency",
|
||||||
"NumberPatterns/percent",
|
"NumberPatterns/percent",
|
||||||
"NumberPatterns/accounting"
|
"NumberPatterns/accounting"
|
||||||
};
|
};
|
||||||
|
|
||||||
private final static String[] COMPACT_NUMBER_PATTERN_KEYS = {
|
private static final String[] COMPACT_NUMBER_PATTERN_KEYS = {
|
||||||
"short.CompactNumberPatterns",
|
"short.CompactNumberPatterns",
|
||||||
"long.CompactNumberPatterns"
|
"long.CompactNumberPatterns"
|
||||||
};
|
};
|
||||||
|
|
||||||
private final static String[] NUMBER_ELEMENT_KEYS = {
|
private static final String[] NUMBER_ELEMENT_KEYS = {
|
||||||
"NumberElements/decimal",
|
"NumberElements/decimal",
|
||||||
"NumberElements/group",
|
"NumberElements/group",
|
||||||
"NumberElements/list",
|
"NumberElements/list",
|
||||||
|
@ -77,41 +81,45 @@ class Bundle {
|
||||||
"NumberElements/currencyGroup",
|
"NumberElements/currencyGroup",
|
||||||
};
|
};
|
||||||
|
|
||||||
private final static String[] TIME_PATTERN_KEYS = {
|
private static final String[] TIME_PATTERN_KEYS = {
|
||||||
"DateTimePatterns/full-time",
|
"DateTimePatterns/full-time",
|
||||||
"DateTimePatterns/long-time",
|
"DateTimePatterns/long-time",
|
||||||
"DateTimePatterns/medium-time",
|
"DateTimePatterns/medium-time",
|
||||||
"DateTimePatterns/short-time",
|
"DateTimePatterns/short-time",
|
||||||
};
|
};
|
||||||
|
|
||||||
private final static String[] DATE_PATTERN_KEYS = {
|
private static final String[] DATE_PATTERN_KEYS = {
|
||||||
"DateTimePatterns/full-date",
|
"DateTimePatterns/full-date",
|
||||||
"DateTimePatterns/long-date",
|
"DateTimePatterns/long-date",
|
||||||
"DateTimePatterns/medium-date",
|
"DateTimePatterns/medium-date",
|
||||||
"DateTimePatterns/short-date",
|
"DateTimePatterns/short-date",
|
||||||
};
|
};
|
||||||
|
|
||||||
private final static String[] DATETIME_PATTERN_KEYS = {
|
private static final String[] DATETIME_PATTERN_KEYS = {
|
||||||
"DateTimePatterns/full-dateTime",
|
"DateTimePatterns/full-dateTime",
|
||||||
"DateTimePatterns/long-dateTime",
|
"DateTimePatterns/long-dateTime",
|
||||||
"DateTimePatterns/medium-dateTime",
|
"DateTimePatterns/medium-dateTime",
|
||||||
"DateTimePatterns/short-dateTime",
|
"DateTimePatterns/short-dateTime",
|
||||||
};
|
};
|
||||||
|
|
||||||
private final static String[] ERA_KEYS = {
|
private static final String[] ERA_KEYS = {
|
||||||
"long.Eras",
|
"long.Eras",
|
||||||
"Eras",
|
"Eras",
|
||||||
"narrow.Eras"
|
"narrow.Eras"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// DateFormatItem prefix
|
||||||
|
static final String DATEFORMATITEM_KEY_PREFIX = "DateFormatItem.";
|
||||||
|
static final String DATEFORMATITEM_INPUT_REGIONS_PREFIX = "DateFormatItemInputRegions.";
|
||||||
|
|
||||||
// Keys for individual time zone names
|
// Keys for individual time zone names
|
||||||
private final static String TZ_GEN_LONG_KEY = "timezone.displayname.generic.long";
|
private static final String TZ_GEN_LONG_KEY = "timezone.displayname.generic.long";
|
||||||
private final static String TZ_GEN_SHORT_KEY = "timezone.displayname.generic.short";
|
private static final String TZ_GEN_SHORT_KEY = "timezone.displayname.generic.short";
|
||||||
private final static String TZ_STD_LONG_KEY = "timezone.displayname.standard.long";
|
private static final String TZ_STD_LONG_KEY = "timezone.displayname.standard.long";
|
||||||
private final static String TZ_STD_SHORT_KEY = "timezone.displayname.standard.short";
|
private static final String TZ_STD_SHORT_KEY = "timezone.displayname.standard.short";
|
||||||
private final static String TZ_DST_LONG_KEY = "timezone.displayname.daylight.long";
|
private static final String TZ_DST_LONG_KEY = "timezone.displayname.daylight.long";
|
||||||
private final static String TZ_DST_SHORT_KEY = "timezone.displayname.daylight.short";
|
private static final String TZ_DST_SHORT_KEY = "timezone.displayname.daylight.short";
|
||||||
private final static String[] ZONE_NAME_KEYS = {
|
private static final String[] ZONE_NAME_KEYS = {
|
||||||
TZ_STD_LONG_KEY,
|
TZ_STD_LONG_KEY,
|
||||||
TZ_STD_SHORT_KEY,
|
TZ_STD_SHORT_KEY,
|
||||||
TZ_DST_LONG_KEY,
|
TZ_DST_LONG_KEY,
|
||||||
|
@ -262,7 +270,7 @@ class Bundle {
|
||||||
CLDRConverter.handleAliases(myMap);
|
CLDRConverter.handleAliases(myMap);
|
||||||
|
|
||||||
// another hack: parentsMap is not used for date-time resources.
|
// another hack: parentsMap is not used for date-time resources.
|
||||||
if ("root".equals(id)) {
|
if (isRoot()) {
|
||||||
parentsMap = null;
|
parentsMap = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +295,14 @@ class Bundle {
|
||||||
handleDateTimeFormatPatterns(TIME_PATTERN_KEYS, myMap, parentsMap, calendarType, "TimePatterns");
|
handleDateTimeFormatPatterns(TIME_PATTERN_KEYS, myMap, parentsMap, calendarType, "TimePatterns");
|
||||||
handleDateTimeFormatPatterns(DATE_PATTERN_KEYS, myMap, parentsMap, calendarType, "DatePatterns");
|
handleDateTimeFormatPatterns(DATE_PATTERN_KEYS, myMap, parentsMap, calendarType, "DatePatterns");
|
||||||
handleDateTimeFormatPatterns(DATETIME_PATTERN_KEYS, myMap, parentsMap, calendarType, "DateTimePatterns");
|
handleDateTimeFormatPatterns(DATETIME_PATTERN_KEYS, myMap, parentsMap, calendarType, "DateTimePatterns");
|
||||||
|
|
||||||
|
// Skeleton
|
||||||
|
handleSkeletonPatterns(myMap, calendarType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skeleton input regions
|
||||||
|
if (isRoot()) {
|
||||||
|
skeletonInputRegions(myMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, weed out any empty timezone or metazone names from myMap.
|
// First, weed out any empty timezone or metazone names from myMap.
|
||||||
|
@ -647,8 +663,9 @@ class Bundle {
|
||||||
private void convertDateTimePatternLetter(CalendarType calendarType, char cldrLetter, int count, StringBuilder sb) {
|
private void convertDateTimePatternLetter(CalendarType calendarType, char cldrLetter, int count, StringBuilder sb) {
|
||||||
switch (cldrLetter) {
|
switch (cldrLetter) {
|
||||||
case 'u':
|
case 'u':
|
||||||
// Change cldr letter 'u' to 'y', as 'u' is interpreted as
|
case 'U':
|
||||||
// "Extended year (numeric)" in CLDR/LDML,
|
// Change cldr letter 'u'/'U' to 'y', as 'u' is interpreted as
|
||||||
|
// "Extended year (numeric)", and 'U' as "Cyclic year" in CLDR/LDML,
|
||||||
// which is not supported in SimpleDateFormat and
|
// which is not supported in SimpleDateFormat and
|
||||||
// j.t.f.DateTimeFormatter, so it is replaced with 'y'
|
// j.t.f.DateTimeFormatter, so it is replaced with 'y'
|
||||||
// as the best approximation
|
// as the best approximation
|
||||||
|
@ -742,6 +759,19 @@ class Bundle {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleSkeletonPatterns(Map<String, Object> myMap, CalendarType calendarType) {
|
||||||
|
String calendarPrefix = calendarType.keyElementName();
|
||||||
|
myMap.putAll(myMap.entrySet().stream()
|
||||||
|
.filter(e -> e.getKey().startsWith(Bundle.DATEFORMATITEM_KEY_PREFIX))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
e -> calendarPrefix + e.getKey(),
|
||||||
|
e -> translateDateFormatLetters(calendarType,
|
||||||
|
(String)e.getValue(),
|
||||||
|
this::convertDateTimePatternLetter)
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
private interface ConvertDateTimeLetters {
|
private interface ConvertDateTimeLetters {
|
||||||
void convert(CalendarType calendarType, char cldrLetter, int count, StringBuilder sb);
|
void convert(CalendarType calendarType, char cldrLetter, int count, StringBuilder sb);
|
||||||
|
@ -790,4 +820,14 @@ class Bundle {
|
||||||
}
|
}
|
||||||
return numArray;
|
return numArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void skeletonInputRegions(Map<String, Object> myMap) {
|
||||||
|
myMap.putAll(myMap.entrySet().stream()
|
||||||
|
.filter(e -> e.getKey().startsWith(Bundle.DATEFORMATITEM_INPUT_REGIONS_PREFIX))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
e -> e.getKey(),
|
||||||
|
e -> ((String)e.getValue()).trim()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -847,20 +847,19 @@ public class CLDRConverter {
|
||||||
"DateTimePatternChars",
|
"DateTimePatternChars",
|
||||||
"PluralRules",
|
"PluralRules",
|
||||||
"DayPeriodRules",
|
"DayPeriodRules",
|
||||||
|
"DateFormatItem",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static Map<String, Object> extractFormatData(Map<String, Object> map, String id) {
|
private static Map<String, Object> extractFormatData(Map<String, Object> map, String id) {
|
||||||
Map<String, Object> formatData = new LinkedHashMap<>();
|
Map<String, Object> formatData = new LinkedHashMap<>();
|
||||||
for (CalendarType calendarType : CalendarType.values()) {
|
for (CalendarType calendarType : CalendarType.values()) {
|
||||||
if (calendarType == CalendarType.GENERIC) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String prefix = calendarType.keyElementName();
|
String prefix = calendarType.keyElementName();
|
||||||
for (String element : FORMAT_DATA_ELEMENTS) {
|
Arrays.stream(FORMAT_DATA_ELEMENTS)
|
||||||
String key = prefix + element;
|
.flatMap(elem -> map.keySet().stream().filter(k -> k.startsWith(prefix + elem)))
|
||||||
|
.forEach(key -> {
|
||||||
copyIfPresent(map, "java.time." + key, formatData);
|
copyIfPresent(map, "java.time." + key, formatData);
|
||||||
copyIfPresent(map, key, formatData);
|
copyIfPresent(map, key, formatData);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String key : map.keySet()) {
|
for (String key : map.keySet()) {
|
||||||
|
@ -868,9 +867,6 @@ public class CLDRConverter {
|
||||||
if (key.startsWith(CLDRConverter.LOCALE_TYPE_PREFIX_CA)) {
|
if (key.startsWith(CLDRConverter.LOCALE_TYPE_PREFIX_CA)) {
|
||||||
String type = key.substring(CLDRConverter.LOCALE_TYPE_PREFIX_CA.length());
|
String type = key.substring(CLDRConverter.LOCALE_TYPE_PREFIX_CA.length());
|
||||||
for (CalendarType calendarType : CalendarType.values()) {
|
for (CalendarType calendarType : CalendarType.values()) {
|
||||||
if (calendarType == CalendarType.GENERIC) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (type.equals(calendarType.lname())) {
|
if (type.equals(calendarType.lname())) {
|
||||||
Object value = map.get(key);
|
Object value = map.get(key);
|
||||||
String dataKey = key.replace(LOCALE_TYPE_PREFIX_CA,
|
String dataKey = key.replace(LOCALE_TYPE_PREFIX_CA,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -756,6 +756,14 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
|
||||||
pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-dateTime");
|
pushStringEntry(qName, attributes, prefix + "DateTimePatterns/" + attributes.getValue("type") + "-dateTime");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "dateFormatItem":
|
||||||
|
{
|
||||||
|
// for FormatData
|
||||||
|
String prefix = (currentCalendarType == null) ? "" : currentCalendarType.keyElementName();
|
||||||
|
pushStringEntry(qName, attributes,
|
||||||
|
prefix + Bundle.DATEFORMATITEM_KEY_PREFIX + attributes.getValue("id"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "localizedPatternChars":
|
case "localizedPatternChars":
|
||||||
{
|
{
|
||||||
// for FormatData
|
// for FormatData
|
||||||
|
@ -1113,7 +1121,7 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
|
||||||
if (id.equals("root") && key.startsWith("MonthNames")) {
|
if (id.equals("root") && key.startsWith("MonthNames")) {
|
||||||
value = new DateFormatSymbols(Locale.US).getShortMonths();
|
value = new DateFormatSymbols(Locale.US).getShortMonths();
|
||||||
}
|
}
|
||||||
return put(entry.getKey(), value);
|
return put(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -27,8 +27,12 @@ package build.tools.cldrconverter;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.xml.sax.Attributes;
|
import org.xml.sax.Attributes;
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
@ -62,10 +66,15 @@ class SupplementDataParseHandler extends AbstractLDMLHandler<Object> {
|
||||||
// parentLocale.<parent_locale_id>=<child_locale_id>(" "<child_locale_id>)+
|
// parentLocale.<parent_locale_id>=<child_locale_id>(" "<child_locale_id>)+
|
||||||
private final Map<String, String> parentLocalesMap;
|
private final Map<String, String> parentLocalesMap;
|
||||||
|
|
||||||
|
// Input Skeleton map for "preferred" and "allowed"
|
||||||
|
// Map<"preferred"/"allowed", Map<"skeleton", SortedSet<"regions">>>
|
||||||
|
private final Map<String, Map<String, SortedSet<String>>> inputSkeletonMap;
|
||||||
|
|
||||||
SupplementDataParseHandler() {
|
SupplementDataParseHandler() {
|
||||||
firstDayMap = new HashMap<>();
|
firstDayMap = new HashMap<>();
|
||||||
minDaysMap = new HashMap<>();
|
minDaysMap = new HashMap<>();
|
||||||
parentLocalesMap = new HashMap<>();
|
parentLocalesMap = new HashMap<>();
|
||||||
|
inputSkeletonMap = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,22 +85,25 @@ class SupplementDataParseHandler extends AbstractLDMLHandler<Object> {
|
||||||
* It returns null when there is no firstDay and minDays for the country
|
* It returns null when there is no firstDay and minDays for the country
|
||||||
* although this should not happen because supplementalData.xml includes
|
* although this should not happen because supplementalData.xml includes
|
||||||
* default value for the world ("001") for firstDay and minDays.
|
* default value for the world ("001") for firstDay and minDays.
|
||||||
|
*
|
||||||
|
* This method also returns Maps for "preferred" and "allowed" skeletons,
|
||||||
|
* which are grouped by regions. E.g, "h:XX YY ZZ;" which means 'h' pattern
|
||||||
|
* is "preferred"/"allowed" in "XX", "YY", and "ZZ" regions.
|
||||||
*/
|
*/
|
||||||
Map<String, Object> getData(String id) {
|
Map<String, Object> getData(String id) {
|
||||||
Map<String, Object> values = new HashMap<>();
|
Map<String, Object> values = new HashMap<>();
|
||||||
if ("root".equals(id)) {
|
if ("root".equals(id)) {
|
||||||
parentLocalesMap.keySet().forEach(key -> {
|
parentLocalesMap.forEach((k, v) -> values.put(CLDRConverter.PARENT_LOCALE_PREFIX + k, v));
|
||||||
values.put(CLDRConverter.PARENT_LOCALE_PREFIX+key,
|
firstDayMap.forEach((k, v) -> values.put(CLDRConverter.CALENDAR_FIRSTDAY_PREFIX + v, k));
|
||||||
parentLocalesMap.get(key));
|
minDaysMap.forEach((k, v) -> values.put(CLDRConverter.CALENDAR_MINDAYS_PREFIX + v, k));
|
||||||
});
|
inputSkeletonMap.get("preferred").forEach((k, v) ->
|
||||||
firstDayMap.keySet().forEach(key -> {
|
values.merge(Bundle.DATEFORMATITEM_INPUT_REGIONS_PREFIX + "preferred",
|
||||||
values.put(CLDRConverter.CALENDAR_FIRSTDAY_PREFIX+firstDayMap.get(key),
|
k + ":" + v.stream().collect(Collectors.joining(" ")) + ";",
|
||||||
key);
|
(old, newVal) -> old + (String)newVal));
|
||||||
});
|
inputSkeletonMap.get("allowed").forEach((k, v) ->
|
||||||
minDaysMap.keySet().forEach(key -> {
|
values.merge(Bundle.DATEFORMATITEM_INPUT_REGIONS_PREFIX + "allowed",
|
||||||
values.put(CLDRConverter.CALENDAR_MINDAYS_PREFIX+minDaysMap.get(key),
|
k + ":" + v.stream().collect(Collectors.joining(" ")) + ";",
|
||||||
key);
|
(old, newVal) -> old + (String)newVal));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return values.isEmpty() ? null : values;
|
return values.isEmpty() ? null : values;
|
||||||
}
|
}
|
||||||
|
@ -158,11 +170,23 @@ class SupplementDataParseHandler extends AbstractLDMLHandler<Object> {
|
||||||
attributes.getValue("locales").replaceAll("_", "-"));
|
attributes.getValue("locales").replaceAll("_", "-"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "hours":
|
||||||
|
if (!isIgnored(attributes)) {
|
||||||
|
var preferred = attributes.getValue("preferred");
|
||||||
|
var allowed = attributes.getValue("allowed").replaceFirst(" .*", "").replaceFirst("b", "B"); // take only the first one, "b" -> "B"
|
||||||
|
var regions = Arrays.stream(attributes.getValue("regions").split(" "))
|
||||||
|
.map(r -> r.replaceAll("_", "-"))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
var pmap = inputSkeletonMap.computeIfAbsent("preferred", k -> new HashMap<>());
|
||||||
|
var amap = inputSkeletonMap.computeIfAbsent("allowed", k -> new HashMap<>());
|
||||||
|
pmap.computeIfAbsent(preferred, k -> new TreeSet<>()).addAll(regions);
|
||||||
|
amap.computeIfAbsent(allowed, k -> new TreeSet<>()).addAll(regions);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// treat anything else as a container
|
// treat anything else as a container
|
||||||
pushContainer(qName, attributes);
|
pushContainer(qName, attributes);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -81,7 +81,6 @@ import java.time.Period;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.chrono.ChronoLocalDateTime;
|
import java.time.chrono.ChronoLocalDateTime;
|
||||||
import java.time.chrono.ChronoZonedDateTime;
|
|
||||||
import java.time.chrono.Chronology;
|
import java.time.chrono.Chronology;
|
||||||
import java.time.chrono.IsoChronology;
|
import java.time.chrono.IsoChronology;
|
||||||
import java.time.format.DateTimeFormatterBuilder.CompositePrinterParser;
|
import java.time.format.DateTimeFormatterBuilder.CompositePrinterParser;
|
||||||
|
@ -718,6 +717,57 @@ public final class DateTimeFormatter {
|
||||||
.toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE);
|
.toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Creates a locale specific formatter derived from the requested template for
|
||||||
|
* the ISO chronology. The requested template is a series of typical pattern
|
||||||
|
* symbols in canonical order from the largest date or time unit to the smallest,
|
||||||
|
* which can be expressed with the following regular expression:
|
||||||
|
* {@snippet :
|
||||||
|
* "G{0,5}" + // Era
|
||||||
|
* "y*" + // Year
|
||||||
|
* "Q{0,5}" + // Quarter
|
||||||
|
* "M{0,5}" + // Month
|
||||||
|
* "w*" + // Week of Week Based Year
|
||||||
|
* "E{0,5}" + // Day of Week
|
||||||
|
* "d{0,2}" + // Day of Month
|
||||||
|
* "B{0,5}" + // Period/AmPm of Day
|
||||||
|
* "[hHjC]{0,2}" + // Hour of Day/AmPm (refer to LDML for 'j' and 'C')
|
||||||
|
* "m{0,2}" + // Minute of Hour
|
||||||
|
* "s{0,2}" + // Second of Minute
|
||||||
|
* "[vz]{0,4}" // Zone
|
||||||
|
* }
|
||||||
|
* All pattern symbols are optional, and each pattern symbol represents a field,
|
||||||
|
* for example, 'M' represents the Month field. The number of the pattern symbol letters follows the
|
||||||
|
* same presentation, such as "number" or "text" as in the <a href="#patterns">Patterns for
|
||||||
|
* Formatting and Parsing</a> section. Other pattern symbols in the requested template are
|
||||||
|
* invalid.
|
||||||
|
* <p>
|
||||||
|
* The mapping of the requested template to the closest of the available localized formats
|
||||||
|
* is defined by the
|
||||||
|
* <a href="https://www.unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems">
|
||||||
|
* Unicode LDML specification</a>. For example, the formatter created from the requested template
|
||||||
|
* {@code yMMM} will format the date '2020-06-16' to 'Jun 2020' in the {@link Locale#US US locale}.
|
||||||
|
* <p>
|
||||||
|
* The locale is determined from the formatter. The formatter returned directly by
|
||||||
|
* this method uses the {@link Locale#getDefault() default FORMAT locale}.
|
||||||
|
* The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)}
|
||||||
|
* on the result of this method.
|
||||||
|
* <p>
|
||||||
|
* The returned formatter has no override zone.
|
||||||
|
* It uses {@link ResolverStyle#SMART SMART} resolver style.
|
||||||
|
*
|
||||||
|
* @param requestedTemplate the requested template, not null
|
||||||
|
* @return the formatter based on the {@code requestedTemplate} pattern, not null
|
||||||
|
* @throws IllegalArgumentException if {@code requestedTemplate} is invalid
|
||||||
|
* @see #ofPattern(String)
|
||||||
|
* @since 19
|
||||||
|
*/
|
||||||
|
public static DateTimeFormatter ofLocalizedPattern(String requestedTemplate) {
|
||||||
|
return new DateTimeFormatterBuilder().appendLocalized(requestedTemplate)
|
||||||
|
.toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
//-----------------------------------------------------------------------
|
||||||
/**
|
/**
|
||||||
* The ISO date formatter that formats or parses a date without an
|
* The ISO date formatter that formats or parses a date without an
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -227,6 +227,42 @@ public final class DateTimeFormatterBuilder {
|
||||||
CalendarDataUtility.findRegionOverride(locale));
|
CalendarDataUtility.findRegionOverride(locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the formatting pattern for the requested template for a locale and chronology.
|
||||||
|
* The locale and chronology are used to lookup the locale specific format
|
||||||
|
* for the requested template.
|
||||||
|
* <p>
|
||||||
|
* If the locale contains the "rg" (region override)
|
||||||
|
* <a href="../../util/Locale.html#def_locale_extension">Unicode extensions</a>,
|
||||||
|
* the formatting pattern is overridden with the one appropriate for the region.
|
||||||
|
* <p>
|
||||||
|
* Refer to {@link #appendLocalized(String)} for the detail of {@code requestedTemplate}
|
||||||
|
* argument.
|
||||||
|
*
|
||||||
|
* @param requestedTemplate the requested template, not null
|
||||||
|
* @param chrono the Chronology, non-null
|
||||||
|
* @param locale the locale, non-null
|
||||||
|
* @return the locale and Chronology specific formatting pattern
|
||||||
|
* @throws IllegalArgumentException if {@code requestedTemplate} does not match
|
||||||
|
* the regular expression syntax described in {@link #appendLocalized(String)}.
|
||||||
|
* @throws DateTimeException if a match for the localized pattern for
|
||||||
|
* {@code requestedTemplate} is not available
|
||||||
|
* @see #appendLocalized(String)
|
||||||
|
* @since 19
|
||||||
|
*/
|
||||||
|
public static String getLocalizedDateTimePattern(String requestedTemplate,
|
||||||
|
Chronology chrono, Locale locale) {
|
||||||
|
Objects.requireNonNull(requestedTemplate, "requestedTemplate");
|
||||||
|
Objects.requireNonNull(chrono, "chrono");
|
||||||
|
Objects.requireNonNull(locale, "locale");
|
||||||
|
Locale override = CalendarDataUtility.findRegionOverride(locale);
|
||||||
|
LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(JavaTimeDateTimePatternProvider.class, override);
|
||||||
|
JavaTimeDateTimePatternProvider provider = adapter.getJavaTimeDateTimePatternProvider();
|
||||||
|
return provider.getJavaTimeDateTimePattern(requestedTemplate,
|
||||||
|
chrono.getCalendarType(),
|
||||||
|
override);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given FormatStyle to the java.text.DateFormat style.
|
* Converts the given FormatStyle to the java.text.DateFormat style.
|
||||||
*
|
*
|
||||||
|
@ -1423,6 +1459,84 @@ public final class DateTimeFormatterBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
// RegEx pattern for skeleton validity checking
|
||||||
|
private static final Pattern VALID_TEMPLATE_PATTERN = Pattern.compile(
|
||||||
|
"G{0,5}" + // Era
|
||||||
|
"y*" + // Year
|
||||||
|
"Q{0,5}" + // Quarter
|
||||||
|
"M{0,5}" + // Month
|
||||||
|
"w*" + // Week of Week Based Year
|
||||||
|
"E{0,5}" + // Day of Week
|
||||||
|
"d{0,2}" + // Day of Month
|
||||||
|
"B{0,5}" + // Period/AmPm of Day
|
||||||
|
"[hHjC]{0,2}" + // Hour of Day/AmPm
|
||||||
|
"m{0,2}" + // Minute of Hour
|
||||||
|
"s{0,2}" + // Second of Minute
|
||||||
|
"[vz]{0,4}"); // Zone
|
||||||
|
/**
|
||||||
|
* Appends a localized pattern to the formatter using the requested template.
|
||||||
|
* <p>
|
||||||
|
* This appends a localized section to the builder, suitable for outputting
|
||||||
|
* a date, time or date-time combination. The format of the localized
|
||||||
|
* section is lazily looked up based on three items:
|
||||||
|
* <ul>
|
||||||
|
* <li>the {@code requestedTemplate} specified to this method
|
||||||
|
* <li>the {@code Locale} of the {@code DateTimeFormatter}
|
||||||
|
* <li>the {@code Chronology} of the {@code DateTimeFormatter} unless overridden
|
||||||
|
* </ul>
|
||||||
|
* During formatting, the chronology is obtained from the temporal object
|
||||||
|
* being formatted, which may have been overridden by
|
||||||
|
* {@link DateTimeFormatter#withChronology(Chronology)}.
|
||||||
|
* <p>
|
||||||
|
* During parsing, if a chronology has already been parsed, then it is used.
|
||||||
|
* Otherwise the default from {@code DateTimeFormatter.withChronology(Chronology)}
|
||||||
|
* is used, with {@code IsoChronology} as the fallback.
|
||||||
|
* <p>
|
||||||
|
* The requested template is a series of typical pattern
|
||||||
|
* symbols in canonical order from the largest date or time unit to the smallest,
|
||||||
|
* which can be expressed with the following regular expression:
|
||||||
|
* {@snippet :
|
||||||
|
* "G{0,5}" + // Era
|
||||||
|
* "y*" + // Year
|
||||||
|
* "Q{0,5}" + // Quarter
|
||||||
|
* "M{0,5}" + // Month
|
||||||
|
* "w*" + // Week of Week Based Year
|
||||||
|
* "E{0,5}" + // Day of Week
|
||||||
|
* "d{0,2}" + // Day of Month
|
||||||
|
* "B{0,5}" + // Period/AmPm of Day
|
||||||
|
* "[hHjC]{0,2}" + // Hour of Day/AmPm (refer to LDML for 'j' and 'C')
|
||||||
|
* "m{0,2}" + // Minute of Hour
|
||||||
|
* "s{0,2}" + // Second of Minute
|
||||||
|
* "[vz]{0,4}" // Zone
|
||||||
|
* }
|
||||||
|
* All pattern symbols are optional, and each pattern symbol represents a field,
|
||||||
|
* for example, 'M' represents the Month field. The number of the pattern symbol letters follows the
|
||||||
|
* same presentation, such as "number" or "text" as in the
|
||||||
|
* <a href="./DateTimeFormatter.html#patterns">Patterns for Formatting and Parsing</a> section.
|
||||||
|
* Other pattern symbols in the requested template are invalid.
|
||||||
|
* <p>
|
||||||
|
* The mapping of the requested template to the closest of the available localized formats
|
||||||
|
* is defined by the
|
||||||
|
* <a href="https://www.unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems">
|
||||||
|
* Unicode LDML specification</a>. For example, the formatter created from the requested template
|
||||||
|
* {@code yMMM} will format the date '2020-06-16' to 'Jun 2020' in the {@link Locale#US US locale}.
|
||||||
|
*
|
||||||
|
* @param requestedTemplate the requested template to use, not null
|
||||||
|
* @return this, for chaining, not null
|
||||||
|
* @throws IllegalArgumentException if {@code requestedTemplate} is invalid
|
||||||
|
* @see #appendPattern(String)
|
||||||
|
* @since 19
|
||||||
|
*/
|
||||||
|
public DateTimeFormatterBuilder appendLocalized(String requestedTemplate) {
|
||||||
|
Objects.requireNonNull(requestedTemplate, "requestedTemplate");
|
||||||
|
if (!VALID_TEMPLATE_PATTERN.matcher(requestedTemplate).matches()) {
|
||||||
|
throw new IllegalArgumentException("Requested template is invalid: " + requestedTemplate);
|
||||||
|
}
|
||||||
|
appendInternal(new LocalizedPrinterParser(requestedTemplate));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
//-----------------------------------------------------------------------
|
||||||
/**
|
/**
|
||||||
* Appends a character literal to the formatter.
|
* Appends a character literal to the formatter.
|
||||||
|
@ -2378,11 +2492,11 @@ public final class DateTimeFormatterBuilder {
|
||||||
private final DateTimePrinterParser[] printerParsers;
|
private final DateTimePrinterParser[] printerParsers;
|
||||||
private final boolean optional;
|
private final boolean optional;
|
||||||
|
|
||||||
CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boolean optional) {
|
private CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boolean optional) {
|
||||||
this(printerParsers.toArray(new DateTimePrinterParser[0]), optional);
|
this(printerParsers.toArray(new DateTimePrinterParser[0]), optional);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompositePrinterParser(DateTimePrinterParser[] printerParsers, boolean optional) {
|
private CompositePrinterParser(DateTimePrinterParser[] printerParsers, boolean optional) {
|
||||||
this.printerParsers = printerParsers;
|
this.printerParsers = printerParsers;
|
||||||
this.optional = optional;
|
this.optional = optional;
|
||||||
}
|
}
|
||||||
|
@ -2476,7 +2590,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param padWidth the width to pad to, 1 or greater
|
* @param padWidth the width to pad to, 1 or greater
|
||||||
* @param padChar the pad character
|
* @param padChar the pad character
|
||||||
*/
|
*/
|
||||||
PadPrinterParserDecorator(DateTimePrinterParser printerParser, int padWidth, char padChar) {
|
private PadPrinterParserDecorator(DateTimePrinterParser printerParser, int padWidth, char padChar) {
|
||||||
// input checked by DateTimeFormatterBuilder
|
// input checked by DateTimeFormatterBuilder
|
||||||
this.printerParser = printerParser;
|
this.printerParser = printerParser;
|
||||||
this.padWidth = padWidth;
|
this.padWidth = padWidth;
|
||||||
|
@ -2584,7 +2698,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
private final TemporalField field;
|
private final TemporalField field;
|
||||||
private final long value;
|
private final long value;
|
||||||
|
|
||||||
DefaultValueParser(TemporalField field, long value) {
|
private DefaultValueParser(TemporalField field, long value) {
|
||||||
this.field = field;
|
this.field = field;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
@ -2608,7 +2722,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
static final class CharLiteralPrinterParser implements DateTimePrinterParser {
|
static final class CharLiteralPrinterParser implements DateTimePrinterParser {
|
||||||
private final char literal;
|
private final char literal;
|
||||||
|
|
||||||
CharLiteralPrinterParser(char literal) {
|
private CharLiteralPrinterParser(char literal) {
|
||||||
this.literal = literal;
|
this.literal = literal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2651,7 +2765,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
static final class StringLiteralPrinterParser implements DateTimePrinterParser {
|
static final class StringLiteralPrinterParser implements DateTimePrinterParser {
|
||||||
private final String literal;
|
private final String literal;
|
||||||
|
|
||||||
StringLiteralPrinterParser(String literal) {
|
private StringLiteralPrinterParser(String literal) {
|
||||||
this.literal = literal; // validated by caller
|
this.literal = literal; // validated by caller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2717,7 +2831,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param maxWidth the maximum field width, from minWidth to 19
|
* @param maxWidth the maximum field width, from minWidth to 19
|
||||||
* @param signStyle the positive/negative sign style, not null
|
* @param signStyle the positive/negative sign style, not null
|
||||||
*/
|
*/
|
||||||
NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) {
|
private NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) {
|
||||||
// validated by caller
|
// validated by caller
|
||||||
this.field = field;
|
this.field = field;
|
||||||
this.minWidth = minWidth;
|
this.minWidth = minWidth;
|
||||||
|
@ -3008,7 +3122,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param baseValue the base value
|
* @param baseValue the base value
|
||||||
* @param baseDate the base date
|
* @param baseDate the base date
|
||||||
*/
|
*/
|
||||||
ReducedPrinterParser(TemporalField field, int minWidth, int maxWidth,
|
private ReducedPrinterParser(TemporalField field, int minWidth, int maxWidth,
|
||||||
int baseValue, ChronoLocalDate baseDate) {
|
int baseValue, ChronoLocalDate baseDate) {
|
||||||
this(field, minWidth, maxWidth, baseValue, baseDate, 0);
|
this(field, minWidth, maxWidth, baseValue, baseDate, 0);
|
||||||
if (minWidth < 1 || minWidth > 10) {
|
if (minWidth < 1 || minWidth > 10) {
|
||||||
|
@ -3161,7 +3275,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param maxWidth the maximum width to output, from 0 to 9
|
* @param maxWidth the maximum width to output, from 0 to 9
|
||||||
* @param decimalPoint whether to output the localized decimal point symbol
|
* @param decimalPoint whether to output the localized decimal point symbol
|
||||||
*/
|
*/
|
||||||
NanosPrinterParser(int minWidth, int maxWidth, boolean decimalPoint) {
|
private NanosPrinterParser(int minWidth, int maxWidth, boolean decimalPoint) {
|
||||||
this(minWidth, maxWidth, decimalPoint, 0);
|
this(minWidth, maxWidth, decimalPoint, 0);
|
||||||
if (minWidth < 0 || minWidth > 9) {
|
if (minWidth < 0 || minWidth > 9) {
|
||||||
throw new IllegalArgumentException("Minimum width must be from 0 to 9 inclusive but was " + minWidth);
|
throw new IllegalArgumentException("Minimum width must be from 0 to 9 inclusive but was " + minWidth);
|
||||||
|
@ -3183,7 +3297,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param decimalPoint whether to output the localized decimal point symbol
|
* @param decimalPoint whether to output the localized decimal point symbol
|
||||||
* @param subsequentWidth the subsequentWidth for this instance
|
* @param subsequentWidth the subsequentWidth for this instance
|
||||||
*/
|
*/
|
||||||
NanosPrinterParser(int minWidth, int maxWidth, boolean decimalPoint, int subsequentWidth) {
|
private NanosPrinterParser(int minWidth, int maxWidth, boolean decimalPoint, int subsequentWidth) {
|
||||||
super(NANO_OF_SECOND, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
|
super(NANO_OF_SECOND, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
|
||||||
this.decimalPoint = decimalPoint;
|
this.decimalPoint = decimalPoint;
|
||||||
}
|
}
|
||||||
|
@ -3366,7 +3480,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param maxWidth the maximum width to output, from 0 to 9
|
* @param maxWidth the maximum width to output, from 0 to 9
|
||||||
* @param decimalPoint whether to output the localized decimal point symbol
|
* @param decimalPoint whether to output the localized decimal point symbol
|
||||||
*/
|
*/
|
||||||
FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
|
private FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
|
||||||
this(field, minWidth, maxWidth, decimalPoint, 0);
|
this(field, minWidth, maxWidth, decimalPoint, 0);
|
||||||
Objects.requireNonNull(field, "field");
|
Objects.requireNonNull(field, "field");
|
||||||
if (field.range().isFixed() == false) {
|
if (field.range().isFixed() == false) {
|
||||||
|
@ -3393,7 +3507,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param decimalPoint whether to output the localized decimal point symbol
|
* @param decimalPoint whether to output the localized decimal point symbol
|
||||||
* @param subsequentWidth the subsequentWidth for this instance
|
* @param subsequentWidth the subsequentWidth for this instance
|
||||||
*/
|
*/
|
||||||
FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint, int subsequentWidth) {
|
private FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint, int subsequentWidth) {
|
||||||
super(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
|
super(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
|
||||||
this.decimalPoint = decimalPoint;
|
this.decimalPoint = decimalPoint;
|
||||||
ValueRange range = field.range();
|
ValueRange range = field.range();
|
||||||
|
@ -3583,7 +3697,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param textStyle the text style, not null
|
* @param textStyle the text style, not null
|
||||||
* @param provider the text provider, not null
|
* @param provider the text provider, not null
|
||||||
*/
|
*/
|
||||||
TextPrinterParser(TemporalField field, TextStyle textStyle, DateTimeTextProvider provider) {
|
private TextPrinterParser(TemporalField field, TextStyle textStyle, DateTimeTextProvider provider) {
|
||||||
// validated by caller
|
// validated by caller
|
||||||
this.field = field;
|
this.field = field;
|
||||||
this.textStyle = textStyle;
|
this.textStyle = textStyle;
|
||||||
|
@ -3681,7 +3795,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L;
|
private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L;
|
||||||
private final int fractionalDigits;
|
private final int fractionalDigits;
|
||||||
|
|
||||||
InstantPrinterParser(int fractionalDigits) {
|
private InstantPrinterParser(int fractionalDigits) {
|
||||||
this.fractionalDigits = fractionalDigits;
|
this.fractionalDigits = fractionalDigits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3830,7 +3944,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param pattern the pattern
|
* @param pattern the pattern
|
||||||
* @param noOffsetText the text to use for UTC, not null
|
* @param noOffsetText the text to use for UTC, not null
|
||||||
*/
|
*/
|
||||||
OffsetIdPrinterParser(String pattern, String noOffsetText) {
|
private OffsetIdPrinterParser(String pattern, String noOffsetText) {
|
||||||
Objects.requireNonNull(pattern, "pattern");
|
Objects.requireNonNull(pattern, "pattern");
|
||||||
Objects.requireNonNull(noOffsetText, "noOffsetText");
|
Objects.requireNonNull(noOffsetText, "noOffsetText");
|
||||||
this.type = checkPattern(pattern);
|
this.type = checkPattern(pattern);
|
||||||
|
@ -4312,7 +4426,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
|
|
||||||
/** Display in generic time-zone format. True in case of pattern letter 'v' */
|
/** Display in generic time-zone format. True in case of pattern letter 'v' */
|
||||||
private final boolean isGeneric;
|
private final boolean isGeneric;
|
||||||
ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones, boolean isGeneric) {
|
private ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones, boolean isGeneric) {
|
||||||
super(TemporalQueries.zone(), "ZoneText(" + textStyle + ")");
|
super(TemporalQueries.zone(), "ZoneText(" + textStyle + ")");
|
||||||
this.textStyle = Objects.requireNonNull(textStyle, "textStyle");
|
this.textStyle = Objects.requireNonNull(textStyle, "textStyle");
|
||||||
this.isGeneric = isGeneric;
|
this.isGeneric = isGeneric;
|
||||||
|
@ -4484,7 +4598,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
private final TemporalQuery<ZoneId> query;
|
private final TemporalQuery<ZoneId> query;
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|
||||||
ZoneIdPrinterParser(TemporalQuery<ZoneId> query, String description) {
|
private ZoneIdPrinterParser(TemporalQuery<ZoneId> query, String description) {
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
@ -4903,7 +5017,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
/** The text style to output, null means the ID. */
|
/** The text style to output, null means the ID. */
|
||||||
private final TextStyle textStyle;
|
private final TextStyle textStyle;
|
||||||
|
|
||||||
ChronoPrinterParser(TextStyle textStyle) {
|
private ChronoPrinterParser(TextStyle textStyle) {
|
||||||
// validated by caller
|
// validated by caller
|
||||||
this.textStyle = textStyle;
|
this.textStyle = textStyle;
|
||||||
}
|
}
|
||||||
|
@ -4978,6 +5092,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
|
|
||||||
private final FormatStyle dateStyle;
|
private final FormatStyle dateStyle;
|
||||||
private final FormatStyle timeStyle;
|
private final FormatStyle timeStyle;
|
||||||
|
private final String requestedTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
|
@ -4985,10 +5100,23 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param dateStyle the date style to use, may be null
|
* @param dateStyle the date style to use, may be null
|
||||||
* @param timeStyle the time style to use, may be null
|
* @param timeStyle the time style to use, may be null
|
||||||
*/
|
*/
|
||||||
LocalizedPrinterParser(FormatStyle dateStyle, FormatStyle timeStyle) {
|
private LocalizedPrinterParser(FormatStyle dateStyle, FormatStyle timeStyle) {
|
||||||
// validated by caller
|
// params validated by caller
|
||||||
this.dateStyle = dateStyle;
|
this.dateStyle = dateStyle;
|
||||||
this.timeStyle = timeStyle;
|
this.timeStyle = timeStyle;
|
||||||
|
this.requestedTemplate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param requestedTemplate the requested template to use, not null
|
||||||
|
*/
|
||||||
|
private LocalizedPrinterParser(String requestedTemplate) {
|
||||||
|
// param validated by caller
|
||||||
|
this.dateStyle = null;
|
||||||
|
this.timeStyle = null;
|
||||||
|
this.requestedTemplate = requestedTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -5006,7 +5134,8 @@ public final class DateTimeFormatterBuilder {
|
||||||
/**
|
/**
|
||||||
* Gets the formatter to use.
|
* Gets the formatter to use.
|
||||||
* <p>
|
* <p>
|
||||||
* The formatter will be the most appropriate to use for the date and time style in the locale.
|
* The formatter will be the most appropriate to use for the date and time style, or
|
||||||
|
* the requested template for the locale.
|
||||||
* For example, some locales will use the month name while others will use the number.
|
* For example, some locales will use the month name while others will use the number.
|
||||||
*
|
*
|
||||||
* @param locale the locale to use, not null
|
* @param locale the locale to use, not null
|
||||||
|
@ -5015,23 +5144,22 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @throws IllegalArgumentException if the formatter cannot be found
|
* @throws IllegalArgumentException if the formatter cannot be found
|
||||||
*/
|
*/
|
||||||
private DateTimeFormatter formatter(Locale locale, Chronology chrono) {
|
private DateTimeFormatter formatter(Locale locale, Chronology chrono) {
|
||||||
String key = chrono.getId() + '|' + locale.toString() + '|' + dateStyle + timeStyle;
|
String key = chrono.getId() + '|' + locale.toString() + '|' +
|
||||||
DateTimeFormatter formatter = FORMATTER_CACHE.get(key);
|
(requestedTemplate != null ? requestedTemplate : Objects.toString(dateStyle) + timeStyle);
|
||||||
if (formatter == null) {
|
|
||||||
String pattern = getLocalizedDateTimePattern(dateStyle, timeStyle, chrono, locale);
|
return FORMATTER_CACHE.computeIfAbsent(key, k ->
|
||||||
formatter = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale);
|
new DateTimeFormatterBuilder()
|
||||||
DateTimeFormatter old = FORMATTER_CACHE.putIfAbsent(key, formatter);
|
.appendPattern(requestedTemplate != null ?
|
||||||
if (old != null) {
|
getLocalizedDateTimePattern(requestedTemplate, chrono, locale) :
|
||||||
formatter = old;
|
getLocalizedDateTimePattern(dateStyle, timeStyle, chrono, locale))
|
||||||
}
|
.toFormatter(locale));
|
||||||
}
|
|
||||||
return formatter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Localized(" + (dateStyle != null ? dateStyle : "") + "," +
|
return "Localized(" + (requestedTemplate != null ? requestedTemplate :
|
||||||
(timeStyle != null ? timeStyle : "") + ")";
|
(dateStyle != null ? dateStyle : "") + "," +
|
||||||
|
(timeStyle != null ? timeStyle : "")) + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5056,7 +5184,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param minWidth the minimum field width, from 1 to 19
|
* @param minWidth the minimum field width, from 1 to 19
|
||||||
* @param maxWidth the maximum field width, from minWidth to 19
|
* @param maxWidth the maximum field width, from minWidth to 19
|
||||||
*/
|
*/
|
||||||
WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth) {
|
private WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth) {
|
||||||
this(chr, count, minWidth, maxWidth, 0);
|
this(chr, count, minWidth, maxWidth, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5070,7 +5198,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
* @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater,
|
* @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater,
|
||||||
* -1 if fixed width due to active adjacent parsing
|
* -1 if fixed width due to active adjacent parsing
|
||||||
*/
|
*/
|
||||||
WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth,
|
private WeekBasedFieldPrinterParser(char chr, int count, int minWidth, int maxWidth,
|
||||||
int subsequentWidth) {
|
int subsequentWidth) {
|
||||||
super(null, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
|
super(null, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
|
||||||
this.chr = chr;
|
this.chr = chr;
|
||||||
|
@ -5201,7 +5329,7 @@ public final class DateTimeFormatterBuilder {
|
||||||
*
|
*
|
||||||
* @param textStyle the text style, not null
|
* @param textStyle the text style, not null
|
||||||
*/
|
*/
|
||||||
DayPeriodPrinterParser(TextStyle textStyle) {
|
private DayPeriodPrinterParser(TextStyle textStyle) {
|
||||||
// validated by caller
|
// validated by caller
|
||||||
this.textStyle = textStyle;
|
this.textStyle = textStyle;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
package sun.text.spi;
|
package sun.text.spi;
|
||||||
|
|
||||||
|
import java.time.DateTimeException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.spi.LocaleServiceProvider;
|
import java.util.spi.LocaleServiceProvider;
|
||||||
|
|
||||||
|
@ -41,10 +42,10 @@ public abstract class JavaTimeDateTimePatternProvider extends LocaleServiceProvi
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the formatting pattern for a timeStyle
|
* Returns the formatting pattern for a timeStyle
|
||||||
* dateStyle, calendarType and locale.
|
* dateStyle, calendarType and locale.
|
||||||
* Concrete implementation of this method will retrieve
|
* Concrete implementation of this method will retrieve
|
||||||
* a java.time specific dateTime Pattern from selected Locale Provider.
|
* a java.time specific dateTime Pattern from the selected Locale Provider.
|
||||||
*
|
*
|
||||||
* @param timeStyle an {@code int} value, representing FormatStyle constant, -1
|
* @param timeStyle an {@code int} value, representing FormatStyle constant, -1
|
||||||
* for date-only pattern
|
* for date-only pattern
|
||||||
|
@ -58,4 +59,26 @@ public abstract class JavaTimeDateTimePatternProvider extends LocaleServiceProvi
|
||||||
* @since 9
|
* @since 9
|
||||||
*/
|
*/
|
||||||
public abstract String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType, Locale locale);
|
public abstract String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType, Locale locale);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the formatting pattern for the requested template, calendarType, and locale.
|
||||||
|
* Concrete implementation of this method will retrieve
|
||||||
|
* a java.time specific pattern from selected Locale Provider.
|
||||||
|
*
|
||||||
|
* @param requestedTemplate the requested template, not null
|
||||||
|
* @param calType a {@code String}, non-null representing CalendarType such as "japanese",
|
||||||
|
* "iso8601"
|
||||||
|
* @param locale {@code locale}, non-null
|
||||||
|
* @throws IllegalArgumentException if {@code requestedTemplate} does not match
|
||||||
|
* the regular expression syntax described in
|
||||||
|
* {@link java.time.format.DateTimeFormatterBuilder#appendLocalized(String)}.
|
||||||
|
* @throws DateTimeException if a match for the formatting pattern for
|
||||||
|
* {@code requestedTemplate} is not available
|
||||||
|
* @return formatting pattern {@code String}
|
||||||
|
* @since 19
|
||||||
|
*/
|
||||||
|
public String getJavaTimeDateTimePattern(String requestedTemplate, String calType, Locale locale) {
|
||||||
|
// default implementation throws exception
|
||||||
|
throw new DateTimeException("Formatting pattern is not available for the requested template: " + requestedTemplate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -24,7 +24,10 @@
|
||||||
*/
|
*/
|
||||||
package sun.util.locale.provider;
|
package sun.util.locale.provider;
|
||||||
|
|
||||||
|
import java.time.DateTimeException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import sun.text.spi.JavaTimeDateTimePatternProvider;
|
import sun.text.spi.JavaTimeDateTimePatternProvider;
|
||||||
|
|
||||||
|
@ -63,10 +66,21 @@ public class JavaTimeDateTimePatternImpl extends JavaTimeDateTimePatternProvider
|
||||||
@Override
|
@Override
|
||||||
public String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType, Locale locale) {
|
public String getJavaTimeDateTimePattern(int timeStyle, int dateStyle, String calType, Locale locale) {
|
||||||
LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale);
|
LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale);
|
||||||
String pattern = lr.getJavaTimeDateTimePattern(
|
return lr.getJavaTimeDateTimePattern(timeStyle, dateStyle, calType);
|
||||||
timeStyle, dateStyle, calType);
|
}
|
||||||
return pattern;
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getJavaTimeDateTimePattern(String requestedTemplate, String calType, Locale locale) {
|
||||||
|
LocaleProviderAdapter lpa = LocaleProviderAdapter.getResourceBundleBased();
|
||||||
|
return ((ResourceBundleBasedAdapter)lpa).getCandidateLocales("", locale).stream()
|
||||||
|
.map(lpa::getLocaleResources)
|
||||||
|
.map(lr -> lr.getLocalizedPattern(requestedTemplate, calType))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst()
|
||||||
|
.or(() -> calType.equals("generic") ? Optional.empty():
|
||||||
|
Optional.of(getJavaTimeDateTimePattern(requestedTemplate, "generic", locale)))
|
||||||
|
.orElseThrow(() -> new DateTimeException("Requested template \"" + requestedTemplate +
|
||||||
|
"\" cannot be resolved in the locale \"" + locale + "\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -46,15 +46,21 @@ import java.text.MessageFormat;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import sun.security.action.GetPropertyAction;
|
import sun.security.action.GetPropertyAction;
|
||||||
import sun.util.resources.LocaleData;
|
import sun.util.resources.LocaleData;
|
||||||
import sun.util.resources.OpenListResourceBundle;
|
import sun.util.resources.OpenListResourceBundle;
|
||||||
|
@ -91,6 +97,10 @@ public class LocaleResources {
|
||||||
private static final String COMPACT_NUMBER_PATTERNS_CACHEKEY = "CNP";
|
private static final String COMPACT_NUMBER_PATTERNS_CACHEKEY = "CNP";
|
||||||
private static final String DATE_TIME_PATTERN = "DTP.";
|
private static final String DATE_TIME_PATTERN = "DTP.";
|
||||||
private static final String RULES_CACHEKEY = "RULE";
|
private static final String RULES_CACHEKEY = "RULE";
|
||||||
|
private static final String SKELETON_PATTERN = "SP.";
|
||||||
|
|
||||||
|
// ResourceBundle key names for skeletons
|
||||||
|
private static final String SKELETON_INPUT_REGIONS_KEY = "DateFormatItemInputRegions";
|
||||||
|
|
||||||
// TimeZoneNamesBundle exemplar city prefix
|
// TimeZoneNamesBundle exemplar city prefix
|
||||||
private static final String TZNB_EXCITY_PREFIX = "timezone.excity.";
|
private static final String TZNB_EXCITY_PREFIX = "timezone.excity.";
|
||||||
|
@ -98,6 +108,31 @@ public class LocaleResources {
|
||||||
// null singleton cache value
|
// null singleton cache value
|
||||||
private static final Object NULLOBJECT = new Object();
|
private static final Object NULLOBJECT = new Object();
|
||||||
|
|
||||||
|
// RegEx pattern for skeleton validity checking
|
||||||
|
private static final Pattern VALID_SKELETON_PATTERN = Pattern.compile(
|
||||||
|
"(?<date>" +
|
||||||
|
"G{0,5}" + // Era
|
||||||
|
"y*" + // Year
|
||||||
|
"Q{0,5}" + // Quarter
|
||||||
|
"M{0,5}" + // Month
|
||||||
|
"w*" + // Week of Week Based Year
|
||||||
|
"E{0,5}" + // Day of Week
|
||||||
|
"d{0,2})" + // Day of Month
|
||||||
|
"(?<time>" +
|
||||||
|
"B{0,5}" + // Period/AmPm of Day
|
||||||
|
"[hHjC]{0,2}" + // Hour of Day/AmPm
|
||||||
|
"m{0,2}" + // Minute of Hour
|
||||||
|
"s{0,2}" + // Second of Minute
|
||||||
|
"[vz]{0,4})"); // Zone
|
||||||
|
|
||||||
|
// Input Skeleton map for "preferred" and "allowed"
|
||||||
|
// Map<"preferred"/"allowed", Map<"region", "skeleton">>
|
||||||
|
private static Map<String, Map<String, String>> inputSkeletons;
|
||||||
|
|
||||||
|
// Skeletons for "j" and "C" input skeleton symbols for this locale
|
||||||
|
private String jPattern;
|
||||||
|
private String CPattern;
|
||||||
|
|
||||||
LocaleResources(ResourceBundleBasedAdapter adapter, Locale locale) {
|
LocaleResources(ResourceBundleBasedAdapter adapter, Locale locale) {
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
this.localeData = adapter.getLocaleData();
|
this.localeData = adapter.getLocaleData();
|
||||||
|
@ -531,6 +566,202 @@ public class LocaleResources {
|
||||||
return rb;
|
return rb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the actual format pattern string based on the requested template
|
||||||
|
* and calendar type for this locale.
|
||||||
|
*
|
||||||
|
* @param requestedTemplate requested template
|
||||||
|
* @param calType calendar type
|
||||||
|
* @throws IllegalArgumentException if the requested template is invalid
|
||||||
|
* @return format pattern string for this locale, null if not found
|
||||||
|
*/
|
||||||
|
public String getLocalizedPattern(String requestedTemplate, String calType) {
|
||||||
|
String pattern;
|
||||||
|
String cacheKey = SKELETON_PATTERN + calType + "." + requestedTemplate;
|
||||||
|
|
||||||
|
removeEmptyReferences();
|
||||||
|
ResourceReference data = cache.get(cacheKey);
|
||||||
|
|
||||||
|
if (data == null || ((pattern = (String) data.get()) == null)) {
|
||||||
|
pattern = getLocalizedPatternImpl(requestedTemplate, calType);
|
||||||
|
cache.put(cacheKey,
|
||||||
|
new ResourceReference(cacheKey, pattern != null ? pattern : "", referenceQueue));
|
||||||
|
} else if ("".equals(pattern)) {
|
||||||
|
// non-existent pattern
|
||||||
|
pattern = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLocalizedPatternImpl(String requestedTemplate, String calType) {
|
||||||
|
initSkeletonIfNeeded();
|
||||||
|
|
||||||
|
// input skeleton substitution
|
||||||
|
var skeleton = substituteInputSkeletons(requestedTemplate);
|
||||||
|
|
||||||
|
// validity check
|
||||||
|
var matcher = VALID_SKELETON_PATTERN.matcher(skeleton);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
throw new IllegalArgumentException("Requested template \"%s\" is invalid".formatted(requestedTemplate) +
|
||||||
|
(requestedTemplate.equals(skeleton) ? "." : ", which translated into \"%s\"".formatted(skeleton) +
|
||||||
|
" after the 'j' or 'C' substitution."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to match entire requested template first
|
||||||
|
String matched = matchSkeleton(skeleton, calType);
|
||||||
|
if (matched == null) {
|
||||||
|
// 2.6.2.2 Missing Skeleton Fields
|
||||||
|
var dateMatched = matchSkeleton(matcher.group("date"), calType);
|
||||||
|
var timeMatched = matchSkeleton(matcher.group("time"), calType);
|
||||||
|
if (dateMatched != null && timeMatched != null) {
|
||||||
|
// combine both matches
|
||||||
|
var style = switch (requestedTemplate.replaceAll("[^M]+", "").length()) {
|
||||||
|
case 4 -> requestedTemplate.indexOf('E') >= 0 ? 0 : 1;
|
||||||
|
case 3 -> 2;
|
||||||
|
default -> 3;
|
||||||
|
};
|
||||||
|
var dateTimePattern = getDateTimePattern(null, "DateTimePatterns", style, calType);
|
||||||
|
matched = MessageFormat.format(dateTimePattern.replaceAll("'", "''"), timeMatched, dateMatched);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace("requested: %s, locale: %s, calType: %s, matched: %s\n", requestedTemplate, locale, calType, matched);
|
||||||
|
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String matchSkeleton(String skeleton, String calType) {
|
||||||
|
// Expand it with possible inferred skeleton stream based on its priority
|
||||||
|
var inferred = possibleInferred(skeleton);
|
||||||
|
|
||||||
|
// Search the closest format pattern string from the resource bundle
|
||||||
|
ResourceBundle r = localeData.getDateFormatData(locale);
|
||||||
|
return inferred
|
||||||
|
.map(s -> ("gregory".equals(calType) ? "" : calType + ".") + "DateFormatItem." + s)
|
||||||
|
.map(key -> r.containsKey(key) ? r.getString(key) : null)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initSkeletonIfNeeded() {
|
||||||
|
// "preferred"/"allowed" input skeleton maps
|
||||||
|
if (inputSkeletons == null) {
|
||||||
|
inputSkeletons = new HashMap<>();
|
||||||
|
Pattern p = Pattern.compile("([^:]+):([^;]+);");
|
||||||
|
ResourceBundle r = localeData.getDateFormatData(Locale.ROOT);
|
||||||
|
Stream.of("preferred", "allowed").forEach(type -> {
|
||||||
|
var inputRegionsKey = SKELETON_INPUT_REGIONS_KEY + "." + type;
|
||||||
|
Map<String, String> typeMap = new HashMap<>();
|
||||||
|
|
||||||
|
if (r.containsKey(inputRegionsKey)) {
|
||||||
|
p.matcher(r.getString(inputRegionsKey)).results()
|
||||||
|
.forEach(mr ->
|
||||||
|
Arrays.stream(mr.group(2).split(" "))
|
||||||
|
.forEach(region -> typeMap.put(region, mr.group(1))));
|
||||||
|
}
|
||||||
|
inputSkeletons.put(type, typeMap);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// j/C patterns for this locale
|
||||||
|
if (jPattern == null) {
|
||||||
|
jPattern = resolveInputSkeleton("preferred");
|
||||||
|
CPattern = resolveInputSkeleton("allowed");
|
||||||
|
// hack: "allowed" contains reversed order for hour/period, e.g, "hB" which should be "Bh" as a skeleton
|
||||||
|
if (CPattern.length() == 2) {
|
||||||
|
var ba = new byte[2];
|
||||||
|
ba[0] = (byte)CPattern.charAt(1);
|
||||||
|
ba[1] = (byte)CPattern.charAt(0);
|
||||||
|
CPattern = new String(ba);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve locale specific input skeletons. Fall back method is different from usual
|
||||||
|
* resource bundle's, as it has to be "lang-region" -> "region" -> "lang-001" -> "001"
|
||||||
|
* @param type type of the input skeleton
|
||||||
|
* @return resolved skeletons for this locale, defaults to "h" if none found.
|
||||||
|
*/
|
||||||
|
private String resolveInputSkeleton(String type) {
|
||||||
|
var regionToSkeletonMap = inputSkeletons.get(type);
|
||||||
|
return regionToSkeletonMap.getOrDefault(locale.getLanguage() + "-" + locale.getCountry(),
|
||||||
|
regionToSkeletonMap.getOrDefault(locale.getCountry(),
|
||||||
|
regionToSkeletonMap.getOrDefault(locale.getLanguage() + "-001",
|
||||||
|
regionToSkeletonMap.getOrDefault("001", "h"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace 'j' and 'C' input skeletons with locale specific patterns. Note that 'j'
|
||||||
|
* is guaranteed to be replaced with one char [hkHK], while 'C' may be replaced with
|
||||||
|
* multiple chars. Repeat each as much as 'C' count.
|
||||||
|
* @param requestedTemplate requested skeleton
|
||||||
|
* @return skeleton with j/C substituted with concrete patterns
|
||||||
|
*/
|
||||||
|
private String substituteInputSkeletons(String requestedTemplate) {
|
||||||
|
var cCount = requestedTemplate.chars().filter(c -> c == 'C').count();
|
||||||
|
return requestedTemplate.replaceAll("j", jPattern)
|
||||||
|
.replaceFirst("C+", CPattern.replaceAll("([hkHK])", "$1".repeat((int)cCount)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a stream of possible skeletons, inferring standalone/format (M/L and/or E/c) patterns
|
||||||
|
* and their styles. (cf. 2.6.2.1 Matching Skeletons)
|
||||||
|
*
|
||||||
|
* @param skeleton original skeleton
|
||||||
|
* @return inferred Stream of skeletons in its priority order
|
||||||
|
*/
|
||||||
|
private Stream<String> possibleInferred(String skeleton) {
|
||||||
|
return priorityList(skeleton, "M", "L").stream()
|
||||||
|
.flatMap(s -> priorityList(s, "E", "c").stream())
|
||||||
|
.distinct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inferring the possible format styles in priority order, based on the original
|
||||||
|
* skeleton length.
|
||||||
|
*
|
||||||
|
* @param skeleton skeleton
|
||||||
|
* @param pChar pattern character string
|
||||||
|
* @param subChar substitute character string
|
||||||
|
* @return list of skeletons
|
||||||
|
*/
|
||||||
|
private List<String> priorityList(String skeleton, String pChar, String subChar) {
|
||||||
|
int first = skeleton.indexOf(pChar);
|
||||||
|
int last = skeleton.lastIndexOf(pChar);
|
||||||
|
|
||||||
|
if (first >= 0) {
|
||||||
|
var prefix = skeleton.substring(0, first);
|
||||||
|
var suffix = skeleton.substring(last + 1);
|
||||||
|
|
||||||
|
// Priority are based on this chart. First column is the original count of `pChar`,
|
||||||
|
// then it is followed by inferred skeletons base on priority.
|
||||||
|
//
|
||||||
|
// 1->2->3->4 (number form (1-digit) -> number form (2-digit) -> Abbr. form -> Full form)
|
||||||
|
// 2->1->3->4
|
||||||
|
// 3->4->2->1
|
||||||
|
// 4->3->2->1
|
||||||
|
var o1 = prefix + pChar + suffix;
|
||||||
|
var o2 = prefix + pChar.repeat(2) + suffix;
|
||||||
|
var o3 = prefix + pChar.repeat(3) + suffix;
|
||||||
|
var o4 = prefix + pChar.repeat(4) + suffix;
|
||||||
|
var s1 = prefix + subChar + suffix;
|
||||||
|
var s2 = prefix + subChar.repeat(2) + suffix;
|
||||||
|
var s3 = prefix + subChar.repeat(3) + suffix;
|
||||||
|
var s4 = prefix + subChar.repeat(4) + suffix;
|
||||||
|
return switch (last - first) {
|
||||||
|
case 1 -> List.of(skeleton, o1, o2, o3, o4, s1, s2, s3, s4);
|
||||||
|
case 2 -> List.of(skeleton, o2, o1, o3, o4, s2, s1, s3, s4);
|
||||||
|
case 3 -> List.of(skeleton, o3, o4, o2, o1, s3, s4, s2, s1);
|
||||||
|
default -> List.of(skeleton, o4, o3, o2, o1, s4, s3, s2, s1);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return List.of(skeleton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String getDateTimePattern(String prefix, String key, int styleIndex, String calendarType) {
|
private String getDateTimePattern(String prefix, String key, int styleIndex, String calendarType) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
if (prefix != null) {
|
if (prefix != null) {
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
H=15
|
||||||
|
h=3 PM
|
||||||
|
j=3 PM
|
||||||
|
C=3 PM
|
||||||
|
Hm=15:32
|
||||||
|
hm=3:32 PM
|
||||||
|
jm=3:32 PM
|
||||||
|
Cm=3:32 PM
|
||||||
|
Hms=15:32:39
|
||||||
|
hms=3:32:39 PM
|
||||||
|
jms=3:32:39 PM
|
||||||
|
Cms=3:32:39 PM
|
||||||
|
Hmv=15:32 PT
|
||||||
|
hmv=3:32 PM PT
|
||||||
|
jmv=3:32 PM PT
|
||||||
|
Cmv=3:32 PM PT
|
||||||
|
Hmsv=15:32:39 PT
|
||||||
|
hmsv=3:32:39 PM PT
|
||||||
|
jmsv=3:32:39 PM PT
|
||||||
|
Cmsv=3:32:39 PM PT
|
||||||
|
Bh=3 in the afternoon
|
||||||
|
Bj=3 in the afternoon
|
||||||
|
BC=3 in the afternoon
|
||||||
|
Bhm=3:32 in the afternoon
|
||||||
|
Bjm=3:32 in the afternoon
|
||||||
|
BCm=3:32 in the afternoon
|
||||||
|
Bhms=3:32:39 in the afternoon
|
||||||
|
Bjms=3:32:39 in the afternoon
|
||||||
|
BCms=3:32:39 in the afternoon
|
||||||
|
|
||||||
|
M=1
|
||||||
|
MMM=Jan
|
||||||
|
MEd=Wed, 1/26
|
||||||
|
MMMMEd=Wed, Jan 26
|
||||||
|
d=26
|
||||||
|
y=2022
|
||||||
|
yM=1/2022
|
||||||
|
yMEd=Wed, 1/26/2022
|
||||||
|
yMMM=Jan 2022
|
||||||
|
yMMMMEd=Wed, Jan 26, 2022
|
||||||
|
GyM=Jan 2022 AD
|
||||||
|
GyMEd=Wed, Jan 26, 2022 AD
|
||||||
|
GyMMM=Jan 2022 AD
|
||||||
|
GyMMMMEd=Wed, Jan 26, 2022 AD
|
||||||
|
yQQQ=Q1 2022
|
||||||
|
yQQQQ=1st quarter 2022
|
||||||
|
|
||||||
|
MMMMEdBh=Wed, Jan 26 at 3 in the afternoon
|
||||||
|
MMMMdBh=January 26 at 3 in the afternoon
|
||||||
|
MMMdBh=Jan 26, 3 in the afternoon
|
||||||
|
MdBh=1/26, 3 in the afternoon
|
|
@ -0,0 +1,43 @@
|
||||||
|
H=15時
|
||||||
|
h=午後3時
|
||||||
|
j=15時
|
||||||
|
C=15時
|
||||||
|
Hm=15:32
|
||||||
|
hm=午後3:32
|
||||||
|
jm=15:32
|
||||||
|
Cm=15:32
|
||||||
|
Hms=15:32:39
|
||||||
|
hms=午後3:32:39
|
||||||
|
jms=15:32:39
|
||||||
|
Cms=15:32:39
|
||||||
|
Hmv=15:32 PT
|
||||||
|
hmv=午後3:32 PT
|
||||||
|
jmv=15:32 PT
|
||||||
|
Cmv=15:32 PT
|
||||||
|
Hmsv=15:32:39 PT
|
||||||
|
hmsv=午後3:32:39 PT
|
||||||
|
jmsv=15:32:39 PT
|
||||||
|
Cmsv=15:32:39 PT
|
||||||
|
Bh=昼3時
|
||||||
|
Bhm=昼3:32
|
||||||
|
Bhms=昼3:32:39
|
||||||
|
|
||||||
|
M=1月
|
||||||
|
MMM=1月
|
||||||
|
MEd=1/26(水)
|
||||||
|
MMMMEd=1月26日(水)
|
||||||
|
d=26日
|
||||||
|
y=4年
|
||||||
|
yM=4/1
|
||||||
|
yMEd=4/1/26(水)
|
||||||
|
yMMM=4年1月
|
||||||
|
yMMMMEd=4年1月26日(水)
|
||||||
|
GyM=令和4年1月
|
||||||
|
GyMEd=令和4年1月26日(水)
|
||||||
|
GyMMM=令和4年1月
|
||||||
|
GyMMMMEd=令和4年1月26日(水)
|
||||||
|
|
||||||
|
MMMMEdBh=1月26日(水) 昼3時
|
||||||
|
MMMMdBh=1月26日 昼3時
|
||||||
|
MMMdBh=1月26日 昼3時
|
||||||
|
MdBh=1/26 昼3時
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022, 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.
|
||||||
|
*
|
||||||
|
* 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 test.java.time.format;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.time.DateTimeException;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.chrono.IsoChronology;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
import org.testng.annotations.DataProvider;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test DateTimeFormatter.ofLocalizedPattern() related methods.
|
||||||
|
* @bug 8176706
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public class TestLocalizedPattern {
|
||||||
|
|
||||||
|
private static final ZonedDateTime ZDT =
|
||||||
|
ZonedDateTime.of(2022, 1, 26, 15, 32, 39, 0, ZoneId.of("America/Los_Angeles"));
|
||||||
|
|
||||||
|
private final static List<Locale> SAMPLE_LOCALES = List.of(
|
||||||
|
Locale.US,
|
||||||
|
Locale.forLanguageTag("ja-JP-u-ca-japanese")
|
||||||
|
);
|
||||||
|
|
||||||
|
@DataProvider(name = "validSkeletons")
|
||||||
|
Object[][] data_validSkeletons() {
|
||||||
|
return SAMPLE_LOCALES.stream()
|
||||||
|
.flatMap(l -> {
|
||||||
|
var rb = ResourceBundle.getBundle("test.java.time.format.Skeletons", l);
|
||||||
|
return rb.keySet().stream().map(key -> new Object[]{key, rb.getString(key), l});
|
||||||
|
})
|
||||||
|
.toList()
|
||||||
|
.toArray(new Object[0][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataProvider(name = "invalidSkeletons")
|
||||||
|
Object[][] data_invalidSkeletons() {
|
||||||
|
return new Object[][] {
|
||||||
|
{"afo"}, {"hB"}, {"uMMM"}, {"MMMMMM"}, {"BhmsyMMM"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataProvider(name = "unavailableSkeletons")
|
||||||
|
Object[][] data_unavailableSkeletons() {
|
||||||
|
return new Object[][] {
|
||||||
|
{"yyyyyy"}, {"BBh"}, {"yMMMMEdBBh"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "validSkeletons")
|
||||||
|
public void test_ofLocalizedPattern(String skeleton, String expected, Locale l) {
|
||||||
|
var dtf = DateTimeFormatter.ofLocalizedPattern(skeleton).localizedBy(l);
|
||||||
|
assertEquals(dtf.format(ZDT), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "invalidSkeletons", expectedExceptions = IllegalArgumentException.class)
|
||||||
|
public void test_ofLocalizedPattern_invalid(String skeleton) {
|
||||||
|
DateTimeFormatter.ofLocalizedPattern(skeleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "invalidSkeletons", expectedExceptions = IllegalArgumentException.class)
|
||||||
|
public void test_appendLocalized_invalid(String skeleton) {
|
||||||
|
new DateTimeFormatterBuilder().appendLocalized(skeleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "unavailableSkeletons", expectedExceptions = DateTimeException.class)
|
||||||
|
public void test_ofLocalizedPattern_unavailable(String skeleton) {
|
||||||
|
DateTimeFormatter.ofLocalizedPattern(skeleton).format(ZDT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "unavailableSkeletons", expectedExceptions = DateTimeException.class)
|
||||||
|
public void test_getLocalizedDateTimePattern_unavailable(String skeleton) {
|
||||||
|
DateTimeFormatterBuilder.getLocalizedDateTimePattern(skeleton, IsoChronology.INSTANCE, Locale.US);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue