com.ibm.icu.text.DateTimePatternGenerator Maven / Gradle / Ivy
Show all versions of icu4j Show documentation
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
********************************************************************************
* Copyright (C) 2006-2016, Google, International Business Machines Corporation
* and others. All Rights Reserved.
********************************************************************************
*/
package com.ibm.icu.text;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.PatternTokenizer;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.Freezable;
import com.ibm.icu.util.ICUCloneNotSupportedException;
import com.ibm.icu.util.Region;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
import com.ibm.icu.util.UResourceBundle;
/**
* This class provides flexible generation of date format patterns, like
* "yy-MM-dd". The user can build up the generator by adding successive
* patterns. Once that is done, a query can be made using a "skeleton", which is
* a pattern which just includes the desired fields and lengths. The generator
* will return the "best fit" pattern corresponding to that skeleton.
*
* The main method people will use is getBestPattern(String skeleton), since
* normally this class is pre-built with data from a particular locale. However,
* generators can be built directly from other data as well.
* @stable ICU 3.6
*/
public class DateTimePatternGenerator implements Freezable, Cloneable {
private static final boolean DEBUG = false;
// debugging flags
//static boolean SHOW_DISTANCE = false;
// TODO add hack to fix months for CJK, as per bug ticket 1099
/**
* Create empty generator, to be constructed with addPattern(...) etc.
* @stable ICU 3.6
*/
public static DateTimePatternGenerator getEmptyInstance() {
DateTimePatternGenerator instance = new DateTimePatternGenerator();
instance.addCanonicalItems();
instance.fillInMissing();
return instance;
}
/**
* Only for use by subclasses
* @stable ICU 3.6
*/
protected DateTimePatternGenerator() {
}
/**
* Construct a flexible generator according to data for the default FORMAT
locale.
* @see Category#FORMAT
* @stable ICU 3.6
*/
public static DateTimePatternGenerator getInstance() {
return getInstance(ULocale.getDefault(Category.FORMAT));
}
/**
* Construct a flexible generator according to data for a given locale.
* @param uLocale The locale to pass.
* @stable ICU 3.6
*/
public static DateTimePatternGenerator getInstance(ULocale uLocale) {
return getFrozenInstance(uLocale).cloneAsThawed();
}
/**
* Construct a flexible generator according to data for a given locale.
* @param locale The {@link java.util.Locale} to pass.
* @stable ICU 54
*/
public static DateTimePatternGenerator getInstance(Locale locale) {
return getInstance(ULocale.forLocale(locale));
}
/**
* Construct a frozen instance of DateTimePatternGenerator for a
* given locale. This method returns a cached frozen instance of
* DateTimePatternGenerator, so less expensive than the regular
* factory method.
* @param uLocale The locale to pass.
* @return A frozen DateTimePatternGenerator.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static DateTimePatternGenerator getFrozenInstance(ULocale uLocale) {
String localeKey = uLocale.toString();
DateTimePatternGenerator result = DTPNG_CACHE.get(localeKey);
if (result != null) {
return result;
}
result = new DateTimePatternGenerator();
result.initData(uLocale, false);
// freeze and cache
result.freeze();
DTPNG_CACHE.put(localeKey, result);
return result;
}
/**
* Construct a non-frozen instance of DateTimePatternGenerator for a
* given locale that skips using the standard date and time patterns.
* Because this is different than the normal instance for the locale,
* it does not set or use the cache.
* @param uLocale The locale to pass.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static DateTimePatternGenerator getInstanceNoStdPat(ULocale uLocale) {
DateTimePatternGenerator result = new DateTimePatternGenerator();
result.initData(uLocale, true);
return result;
}
private void initData(ULocale uLocale, boolean skipStdPatterns) {
// This instance of PatternInfo is required for calling some functions. It is used for
// passing additional information to the caller. We won't use this extra information, but
// we still need to make a temporary instance.
PatternInfo returnInfo = new PatternInfo();
addCanonicalItems();
if (!skipStdPatterns) { // skip to prevent circular dependency when used by Calendar
addICUPatterns(returnInfo, uLocale);
}
addCLDRData(returnInfo, uLocale);
if (!skipStdPatterns) { // also skip to prevent circular dependency from Calendar
setDateTimeFromCalendar(uLocale);
} else {
// instead, since from Calendar we do not care about dateTimePattern, use a fallback
setDateTimeFormat("{1} {0}");
}
setDecimalSymbols(uLocale);
getAllowedHourFormats(uLocale);
fillInMissing();
}
private void addICUPatterns(PatternInfo returnInfo, ULocale uLocale) {
// first load with the ICU patterns
for (int i = DateFormat.FULL; i <= DateFormat.SHORT; ++i) {
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(i, uLocale);
addPattern(df.toPattern(), false, returnInfo);
df = (SimpleDateFormat) DateFormat.getTimeInstance(i, uLocale);
addPattern(df.toPattern(), false, returnInfo);
if (i == DateFormat.SHORT) {
consumeShortTimePattern(df.toPattern(), returnInfo);
}
}
}
private String getCalendarTypeToUse(ULocale uLocale) {
// Get the correct calendar type
// TODO: C++ and Java are inconsistent (see #9952).
String calendarTypeToUse = uLocale.getKeywordValue("calendar");
if ( calendarTypeToUse == null ) {
String[] preferredCalendarTypes = Calendar.getKeywordValuesForLocale("calendar", uLocale, true);
calendarTypeToUse = preferredCalendarTypes[0]; // the most preferred calendar
}
if ( calendarTypeToUse == null ) {
calendarTypeToUse = "gregorian"; // fallback
}
return calendarTypeToUse;
}
private void consumeShortTimePattern(String shortTimePattern, PatternInfo returnInfo) {
// keep this pattern to populate other time field
// combination patterns by hackTimes later in this method.
// ICU-20383 No longer set defaultHourFormatChar to the hour format character from
// this pattern; instead it is set from LOCALE_TO_ALLOWED_HOUR which now
// includes entries for both preferred and allowed formats.
// some languages didn't add mm:ss or HH:mm, so put in a hack to compute that from the short time.
hackTimes(returnInfo, shortTimePattern);
}
private class AppendItemFormatsSink extends UResource.Sink {
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
UResource.Table itemsTable = value.getTable();
for (int i = 0; itemsTable.getKeyAndValue(i, key, value); ++i) {
int field = getAppendFormatNumber(key);
assert field != -1;
if (getAppendItemFormat(field) == null) {
setAppendItemFormat(field, value.toString());
}
}
}
}
private class AppendItemNamesSink extends UResource.Sink {
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
UResource.Table itemsTable = value.getTable();
for (int i = 0; itemsTable.getKeyAndValue(i, key, value); ++i) {
if (value.getType() != UResourceBundle.TABLE) {
// Typically get either UResourceBundle.TABLE = 2 or ICUResourceBundle.ALIAS = 3.
// Currently fillInMissing() is being used instead of following the ALIAS, so
// skip ALIAS entries which cause UResourceTypeMismatchException in the line
// UResource.Table detailsTable = value.getTable()
continue;
}
int fieldAndWidth = getCLDRFieldAndWidthNumber(key);
if (fieldAndWidth == -1) { continue; }
int field = fieldAndWidth / DisplayWidth.COUNT;
DisplayWidth width = CLDR_FIELD_WIDTH[fieldAndWidth % DisplayWidth.COUNT];
UResource.Table detailsTable = value.getTable();
for (int j = 0; detailsTable.getKeyAndValue(j, key, value); ++j) {
if (!key.contentEquals("dn")) continue;
if (getFieldDisplayName(field, width) == null) {
setFieldDisplayName(field, width, value.toString());
}
break;
}
}
}
}
private void fillInMissing() {
for (int i = 0; i < TYPE_LIMIT; ++i) {
if (getAppendItemFormat(i) == null) {
setAppendItemFormat(i, "{0} \u251C{2}: {1}\u2524");
}
if (getFieldDisplayName(i, DisplayWidth.WIDE) == null) {
setFieldDisplayName(i, DisplayWidth.WIDE, "F" + i);
}
if (getFieldDisplayName(i, DisplayWidth.ABBREVIATED) == null) {
setFieldDisplayName(i, DisplayWidth.ABBREVIATED, getFieldDisplayName(i, DisplayWidth.WIDE));
}
if (getFieldDisplayName(i, DisplayWidth.NARROW) == null) {
setFieldDisplayName(i, DisplayWidth.NARROW, getFieldDisplayName(i, DisplayWidth.ABBREVIATED));
}
}
}
private class AvailableFormatsSink extends UResource.Sink {
PatternInfo returnInfo;
public AvailableFormatsSink(PatternInfo returnInfo) {
this.returnInfo = returnInfo;
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean isRoot) {
UResource.Table formatsTable = value.getTable();
for (int i = 0; formatsTable.getKeyAndValue(i, key, value); ++i) {
String formatKey = key.toString();
if (!isAvailableFormatSet(formatKey)) {
setAvailableFormat(formatKey);
// Add pattern with its associated skeleton. Override any duplicate derived from std patterns,
// but not a previous availableFormats entry:
String formatValue = value.toString();
addPatternWithSkeleton(formatValue, formatKey, !isRoot, returnInfo);
}
}
}
}
private void addCLDRData(PatternInfo returnInfo, ULocale uLocale) {
ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, uLocale);
String calendarTypeToUse = getCalendarTypeToUse(uLocale);
// ICU4J getWithFallback does not work well when
// 1) A nested table is an alias to /LOCALE/...
// 2) getWithFallback is called multiple times for going down hierarchical resource path
// #9987 resolved the issue of alias table when full path is specified in getWithFallback,
// but there is no easy solution when the equivalent operation is done by multiple operations.
// This issue is addressed in #9964.
// Load append item formats.
AppendItemFormatsSink appendItemFormatsSink = new AppendItemFormatsSink();
try {
rb.getAllItemsWithFallback(
"calendar/" + calendarTypeToUse + "/appendItems",
appendItemFormatsSink);
}catch(MissingResourceException e) {
}
// Load CLDR item names.
AppendItemNamesSink appendItemNamesSink = new AppendItemNamesSink();
try {
rb.getAllItemsWithFallback(
"fields",
appendItemNamesSink);
}catch(MissingResourceException e) {
}
// Load the available formats from CLDR.
AvailableFormatsSink availableFormatsSink = new AvailableFormatsSink(returnInfo);
try {
rb.getAllItemsWithFallback(
"calendar/" + calendarTypeToUse + "/availableFormats",
availableFormatsSink);
} catch (MissingResourceException e) {
}
}
private void setDateTimeFromCalendar(ULocale uLocale) {
String dateTimeFormat = Calendar.getDateTimePattern(Calendar.getInstance(uLocale), uLocale, DateFormat.MEDIUM);
setDateTimeFormat(dateTimeFormat);
}
private void setDecimalSymbols(ULocale uLocale) {
// decimal point for seconds
DecimalFormatSymbols dfs = new DecimalFormatSymbols(uLocale);
setDecimal(String.valueOf(dfs.getDecimalSeparator()));
}
private static final String[] LAST_RESORT_ALLOWED_HOUR_FORMAT = {"H"};
private String[] getAllowedHourFormatsLangCountry(String language, String country) {
String langCountry = language + "_" + country;
String[] list = LOCALE_TO_ALLOWED_HOUR.get(langCountry);
if (list == null) {
list = LOCALE_TO_ALLOWED_HOUR.get(country);
}
return list;
}
private void getAllowedHourFormats(ULocale uLocale) {
// key can be either region or locale (lang_region)
// ZW{
// allowed{
// "h",
// "H",
// }
// preferred{"h"}
// }
// af_ZA{
// allowed{
// "h",
// "H",
// "hB",
// "hb",
// }
// preferred{"h"}
// }
String language = uLocale.getLanguage();
String country = uLocale.getCountry();
if (language.isEmpty() || country.isEmpty()) {
// Note: addLikelySubtags is documented not to throw in Java,
// unlike in C++.
ULocale max = ULocale.addLikelySubtags(uLocale);
language = max.getLanguage();
country = max.getCountry();
}
if (language.isEmpty()) {
// Unexpected, but fail gracefully
language = "und";
}
if (country.isEmpty()) {
country = "001";
}
String[] list = getAllowedHourFormatsLangCountry(language, country);
// We need to check if there is an hour cycle on locale
Character defaultCharFromLocale = null;
String hourCycle = uLocale.getKeywordValue("hours");
if (hourCycle != null) {
switch(hourCycle) {
case "h24":
defaultCharFromLocale = 'k';
break;
case "h23":
defaultCharFromLocale = 'H';
break;
case "h12":
defaultCharFromLocale = 'h';
break;
case "h11":
defaultCharFromLocale = 'K';
break;
}
}
// Check if the region has an alias
if (list == null) {
try {
Region region = Region.getInstance(country);
country = region.toString();
list = getAllowedHourFormatsLangCountry(language, country);
} catch (IllegalArgumentException e) {
// invalid region; fall through
}
}
if (list != null) {
defaultHourFormatChar = defaultCharFromLocale != null ? defaultCharFromLocale : list[0].charAt(0);
allowedHourFormats = Arrays.copyOfRange(list, 1, list.length - 1);
} else {
allowedHourFormats = LAST_RESORT_ALLOWED_HOUR_FORMAT;
defaultHourFormatChar = (defaultCharFromLocale != null) ? defaultCharFromLocale : allowedHourFormats[0].charAt(0);
}
}
private static class DayPeriodAllowedHoursSink extends UResource.Sink {
HashMap tempMap;
private DayPeriodAllowedHoursSink(HashMap tempMap) {
this.tempMap = tempMap;
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
UResource.Table timeData = value.getTable();
for (int i = 0; timeData.getKeyAndValue(i, key, value); ++i) {
String regionOrLocale = key.toString();
UResource.Table formatList = value.getTable();
String[] allowed = null;
String preferred = null;
for (int j = 0; formatList.getKeyAndValue(j, key, value); ++j) {
if (key.contentEquals("allowed")) {
allowed = value.getStringArrayOrStringAsArray();
} else if (key.contentEquals("preferred")) {
preferred = value.getString();
}
}
// below we construct a list[] that has an entry for the "preferred" value at [0],
// followed by 1 or more entries for the "allowed" values.
String[] list = null;
if (allowed!=null && allowed.length > 0) {
list = new String[allowed.length + 1];
list[0] = (preferred != null)? preferred: allowed[0];
System.arraycopy(allowed, 0, list, 1, allowed.length);
} else {
// fallback handling for missing data
list = new String[2];
list[0] = (preferred != null)? preferred: LAST_RESORT_ALLOWED_HOUR_FORMAT[0];
list[1] = list[0];
}
tempMap.put(regionOrLocale, list);
}
}
}
// Get the data for dayperiod C.
static final Map LOCALE_TO_ALLOWED_HOUR;
static {
HashMap temp = new HashMap<>();
ICUResourceBundle suppData = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(
ICUData.ICU_BASE_NAME,
"supplementalData",
ICUResourceBundle.ICU_DATA_CLASS_LOADER);
DayPeriodAllowedHoursSink allowedHoursSink = new DayPeriodAllowedHoursSink(temp);
suppData.getAllItemsWithFallback("timeData", allowedHoursSink);
LOCALE_TO_ALLOWED_HOUR = Collections.unmodifiableMap(temp);
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public char getDefaultHourFormatChar() {
return defaultHourFormatChar;
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public void setDefaultHourFormatChar(char defaultHourFormatChar) {
this.defaultHourFormatChar = defaultHourFormatChar;
}
private void hackTimes(PatternInfo returnInfo, String shortTimePattern) {
fp.set(shortTimePattern);
StringBuilder mmss = new StringBuilder();
// to get mm:ss, we strip all but mm literal ss
boolean gotMm = false;
for (int i = 0; i < fp.items.size(); ++i) {
Object item = fp.items.get(i);
if (item instanceof String) {
if (gotMm) {
mmss.append(fp.quoteLiteral(item.toString()));
}
} else {
char ch = item.toString().charAt(0);
if (ch == 'm') {
gotMm = true;
mmss.append(item);
} else if (ch == 's') {
if (!gotMm) {
break; // failed
}
mmss.append(item);
addPattern(mmss.toString(), false, returnInfo);
break;
} else if (gotMm || ch == 'z' || ch == 'Z' || ch == 'v' || ch == 'V') {
break; // failed
}
}
}
// to get hh:mm, we strip (literal ss) and (literal S)
// the easiest way to do this is to mark the stuff we want to nuke, then remove it in a second pass.
BitSet variables = new BitSet();
BitSet nuke = new BitSet();
for (int i = 0; i < fp.items.size(); ++i) {
Object item = fp.items.get(i);
if (item instanceof VariableField) {
variables.set(i);
char ch = item.toString().charAt(0);
if (ch == 's' || ch == 'S') {
nuke.set(i);
for (int j = i-1; j >= 0; ++j) {
if (variables.get(j)) break;
nuke.set(i);
}
}
}
}
String hhmm = getFilteredPattern(fp, nuke);
addPattern(hhmm, false, returnInfo);
}
private static String getFilteredPattern(FormatParser fp, BitSet nuke) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < fp.items.size(); ++i) {
if (nuke.get(i)) continue;
Object item = fp.items.get(i);
if (item instanceof String) {
result.append(fp.quoteLiteral(item.toString()));
} else {
result.append(item.toString());
}
}
return result.toString();
}
/*private static int getAppendNameNumber(String string) {
for (int i = 0; i < CLDR_FIELD_NAME.length; ++i) {
if (CLDR_FIELD_NAME[i].equals(string)) return i;
}
return -1;
}*/
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static int getAppendFormatNumber(UResource.Key key) {
for (int i = 0; i < CLDR_FIELD_APPEND.length; ++i) {
if (key.contentEquals(CLDR_FIELD_APPEND[i])) {
return i;
}
}
return -1;
}
/**
* @internal CLDR
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static int getAppendFormatNumber(String string) {
for (int i = 0; i < CLDR_FIELD_APPEND.length; ++i) {
if (CLDR_FIELD_APPEND[i].equals(string)) {
return i;
}
}
return -1;
}
private static int getCLDRFieldAndWidthNumber(UResource.Key key) {
for (int i = 0; i < CLDR_FIELD_NAME.length; ++i) {
for (int j = 0; j < DisplayWidth.COUNT; ++j) {
String fullKey = CLDR_FIELD_NAME[i].concat(CLDR_FIELD_WIDTH[j].cldrKey());
if (key.contentEquals(fullKey)) {
return i * DisplayWidth.COUNT + j;
}
}
}
return -1;
}
/**
* Return the best pattern matching the input skeleton. It is guaranteed to
* have all of the fields in the skeleton.
* Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---getBestPatternExample}
* @param skeleton The skeleton is a pattern containing only the variable fields.
* For example, "MMMdd" and "mmhh" are skeletons.
* @return Best pattern matching the input skeleton.
* @stable ICU 3.6
*/
public String getBestPattern(String skeleton) {
return getBestPattern(skeleton, null, MATCH_NO_OPTIONS);
}
/**
* Return the best pattern matching the input skeleton. It is guaranteed to
* have all of the fields in the skeleton.
*
* @param skeleton The skeleton is a pattern containing only the variable fields.
* For example, "MMMdd" and "mmhh" are skeletons.
* @param options MATCH_xxx options for forcing the length of specified fields in
* the returned pattern to match those in the skeleton (when this would
* not happen otherwise). For default behavior, use MATCH_NO_OPTIONS.
* @return Best pattern matching the input skeleton (and options).
* @stable ICU 4.4
*/
public String getBestPattern(String skeleton, int options) {
return getBestPattern(skeleton, null, options);
}
/*
* getBestPattern which takes optional skip matcher
*/
private String getBestPattern(String skeleton, DateTimeMatcher skipMatcher, int options) {
EnumSet flags = EnumSet.noneOf(DTPGflags.class);
// Replace hour metacharacters 'j', 'C', and 'J', set flags as necessary
String skeletonMapped = mapSkeletonMetacharacters(skeleton, flags);
String datePattern, timePattern;
synchronized(this) {
current.set(skeletonMapped, fp, false);
PatternWithMatcher bestWithMatcher = getBestRaw(current, -1, _distanceInfo, skipMatcher);
if (_distanceInfo.missingFieldMask == 0 && _distanceInfo.extraFieldMask == 0) {
// we have a good item. Adjust the field types
return adjustFieldTypes(bestWithMatcher, current, flags, options);
}
int neededFields = current.getFieldMask();
// otherwise break up by date and time.
datePattern = getBestAppending(current, neededFields & DATE_MASK, _distanceInfo, skipMatcher, flags, options);
timePattern = getBestAppending(current, neededFields & TIME_MASK, _distanceInfo, skipMatcher, flags, options);
}
if (datePattern == null) return timePattern == null ? "" : timePattern;
if (timePattern == null) return datePattern;
return SimpleFormatterImpl.formatRawPattern(
getDateTimeFormat(), 2, 2, timePattern, datePattern);
}
/*
* Map a skeleton that may have metacharacters jJC to one without, by replacing
* the metacharacters with locale-appropriate fields of of h/H/k/K and of a/b/B
* (depends on defaultHourFormatChar and allowedHourFormats being set, which in
* turn depends on initData having been run). This method also updates the flags
* as necessary. Returns the updated skeleton.
*/
private String mapSkeletonMetacharacters(String skeleton, EnumSet flags) {
StringBuilder skeletonCopy = new StringBuilder();
boolean inQuoted = false;
for (int patPos = 0; patPos < skeleton.length(); patPos++) {
char patChr = skeleton.charAt(patPos);
if (patChr == '\'') {
inQuoted = !inQuoted;
} else if (!inQuoted) {
// Handle special mappings for 'j' and 'C' in which fields lengths
// 1,3,5 => hour field length 1
// 2,4,6 => hour field length 2
// 1,2 => abbreviated dayPeriod (field length 1..3)
// 3,4 => long dayPeriod (field length 4)
// 5,6 => narrow dayPeriod (field length 5)
if (patChr == 'j' || patChr == 'C') {
int extraLen = 0; // 1 less than total field length
while (patPos+1 < skeleton.length() && skeleton.charAt(patPos+1) == patChr) {
extraLen++;
patPos++;
}
int hourLen = 1 + (extraLen & 1);
int dayPeriodLen = (extraLen < 2)? 1: 3 + (extraLen >> 1);
char hourChar = 'h';
char dayPeriodChar = 'a';
if (patChr == 'j') {
hourChar = defaultHourFormatChar;
} else { // patChr == 'C'
String bestAllowed = allowedHourFormats[0];
hourChar = bestAllowed.charAt(0);
// in #13183 just add b/B to skeleton, no longer need to set special flags
char last = bestAllowed.charAt(bestAllowed.length()-1);
if (last=='b' || last=='B') {
dayPeriodChar = last;
}
}
if (hourChar=='H' || hourChar=='k') {
dayPeriodLen = 0;
}
while (dayPeriodLen-- > 0) {
skeletonCopy.append(dayPeriodChar);
}
while (hourLen-- > 0) {
skeletonCopy.append(hourChar);
}
} else if (patChr == 'J') {
// Get pattern for skeleton with H, then (in adjustFieldTypes)
// replace H or k with defaultHourFormatChar
skeletonCopy.append('H');
flags.add(DTPGflags.SKELETON_USES_CAP_J);
} else {
skeletonCopy.append(patChr);
}
}
}
return skeletonCopy.toString();
}
/**
* PatternInfo supplies output parameters for addPattern(...). It is used because
* Java doesn't have real output parameters. It is treated like a struct (eg
* Point), so all fields are public.
*
* @stable ICU 3.6
*/
public static final class PatternInfo { // struct for return information
/**
* @stable ICU 3.6
*/
public static final int OK = 0;
/**
* @stable ICU 3.6
*/
public static final int BASE_CONFLICT = 1;
/**
* @stable ICU 3.6
*/
public static final int CONFLICT = 2;
/**
* @stable ICU 3.6
*/
public int status;
/**
* @stable ICU 3.6
*/
public String conflictingPattern;
/**
* Simple constructor, since this is treated like a struct.
* @stable ICU 3.6
*/
public PatternInfo() {
}
}
/**
* Adds a pattern to the generator. If the pattern has the same skeleton as
* an existing pattern, and the override parameter is set, then the previous
* value is overridden. Otherwise, the previous value is retained. In either
* case, the conflicting information is returned in PatternInfo.
*
* Note that single-field patterns (like "MMM") are automatically added, and
* don't need to be added explicitly!
* *
Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---addPatternExample}
* @param pattern Pattern to add.
* @param override When existing values are to be overridden use true, otherwise
* use false.
* @param returnInfo Returned information.
* @stable ICU 3.6
*/
public DateTimePatternGenerator addPattern(String pattern, boolean override, PatternInfo returnInfo) {
return addPatternWithSkeleton(pattern, null, override, returnInfo);
}
/**
* addPatternWithSkeleton:
* If skeletonToUse is specified, then an availableFormats entry is being added. In this case:
* 1. We pass that skeleton to DateTimeMatcher().set instead of having it derive a skeleton from the pattern.
* 2. If the new entry's skeleton or basePattern does match an existing entry but that entry also had a skeleton specified
* (i.e. it was also from availableFormats), then the new entry does not override it regardless of the value of the override
* parameter. This prevents later availableFormats entries from a parent locale overriding earlier ones from the actual
* specified locale. However, availableFormats entries *should* override entries with matching skeleton whose skeleton was
* derived (i.e. entries derived from the standard date/time patters for the specified locale).
* 3. When adding the pattern (skeleton2pattern.put, basePattern_pattern.put), we set a field to indicate that the added
* entry had a specified skeleton.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public DateTimePatternGenerator addPatternWithSkeleton(String pattern, String skeletonToUse, boolean override, PatternInfo returnInfo) {
checkFrozen();
DateTimeMatcher matcher;
if (skeletonToUse == null) {
matcher = new DateTimeMatcher().set(pattern, fp, false);
} else {
matcher = new DateTimeMatcher().set(skeletonToUse, fp, false);
}
String basePattern = matcher.getBasePattern();
// We only care about base conflicts - and replacing the pattern associated with a base - if:
// 1. the conflicting previous base pattern did *not* have an explicit skeleton; in that case the previous
// base + pattern combination was derived from either (a) a canonical item, (b) a standard format, or
// (c) a pattern specified programmatically with a previous call to addPattern (which would only happen
// if we are getting here from a subsequent call to addPattern).
// 2. a skeleton is specified for the current pattern, but override=false; in that case we are checking
// availableFormats items from root, which should not override any previous entry with the same base.
PatternWithSkeletonFlag previousPatternWithSameBase = basePattern_pattern.get(basePattern);
if (previousPatternWithSameBase != null && (!previousPatternWithSameBase.skeletonWasSpecified || (skeletonToUse != null && !override))) {
returnInfo.status = PatternInfo.BASE_CONFLICT;
returnInfo.conflictingPattern = previousPatternWithSameBase.pattern;
if (!override) {
return this;
}
}
// The only time we get here with override=true and skeletonToUse!=null is when adding availableFormats
// items from CLDR data. In that case, we don't want an item from a parent locale to replace an item with
// same skeleton from the specified locale, so skip the current item if skeletonWasSpecified is true for
// the previously-specified conflicting item.
PatternWithSkeletonFlag previousValue = skeleton2pattern.get(matcher);
if (previousValue != null) {
returnInfo.status = PatternInfo.CONFLICT;
returnInfo.conflictingPattern = previousValue.pattern;
if (!override || (skeletonToUse != null && previousValue.skeletonWasSpecified)) return this;
}
returnInfo.status = PatternInfo.OK;
returnInfo.conflictingPattern = "";
PatternWithSkeletonFlag patWithSkelFlag = new PatternWithSkeletonFlag(pattern,skeletonToUse != null);
if (DEBUG) {
System.out.println(matcher + " => " + patWithSkelFlag);
}
skeleton2pattern.put(matcher, patWithSkelFlag);
basePattern_pattern.put(basePattern, patWithSkelFlag);
return this;
}
/**
* Utility to return a unique skeleton from a given pattern. For example,
* both "MMM-dd" and "dd/MMM" produce the skeleton "MMMdd".
*
* @param pattern Input pattern, such as "dd/MMM"
* @return skeleton, such as "MMMdd"
* @stable ICU 3.6
*/
public String getSkeleton(String pattern) {
synchronized (this) { // synchronized since a getter must be thread-safe
current.set(pattern, fp, false);
return current.toString();
}
}
/**
* Same as getSkeleton, but allows duplicates
*
* @param pattern Input pattern, such as "dd/MMM"
* @return skeleton, such as "MMMdd"
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public String getSkeletonAllowingDuplicates(String pattern) {
synchronized (this) { // synchronized since a getter must be thread-safe
current.set(pattern, fp, true);
return current.toString();
}
}
/**
* Same as getSkeleton, but allows duplicates
* and returns a string using canonical pattern chars
*
* @param pattern Input pattern, such as "ccc, d LLL"
* @return skeleton, such as "MMMEd"
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public String getCanonicalSkeletonAllowingDuplicates(String pattern) {
synchronized (this) { // synchronized since a getter must be thread-safe
current.set(pattern, fp, true);
return current.toCanonicalString();
}
}
/**
* Utility to return a unique base skeleton from a given pattern. This is
* the same as the skeleton, except that differences in length are minimized
* so as to only preserve the difference between string and numeric form. So
* for example, both "MMM-dd" and "d/MMM" produce the skeleton "MMMd"
* (notice the single d).
*
* @param pattern Input pattern, such as "dd/MMM"
* @return skeleton, such as "MMMdd"
* @stable ICU 3.6
*/
public String getBaseSkeleton(String pattern) {
synchronized (this) { // synchronized since a getter must be thread-safe
current.set(pattern, fp, false);
return current.getBasePattern();
}
}
/**
* Return a list of all the skeletons (in canonical form) from this class,
* and the patterns that they map to.
*
* @param result an output Map in which to place the mapping from skeleton to
* pattern. If you want to see the internal order being used,
* supply a LinkedHashMap. If the input value is null, then a
* LinkedHashMap is allocated.
*
* Issue: an alternate API would be to just return a list of
* the skeletons, and then have a separate routine to get from
* skeleton to pattern.
* @return the input Map containing the values.
* @stable ICU 3.6
*/
public Map getSkeletons(Map result) {
if (result == null) {
result = new LinkedHashMap<>();
}
for (DateTimeMatcher item : skeleton2pattern.keySet()) {
PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(item);
String pattern = patternWithSkelFlag.pattern;
if (CANONICAL_SET.contains(pattern)) {
continue;
}
result.put(item.toString(), pattern);
}
return result;
}
/**
* Return a list of all the base skeletons (in canonical form) from this class
* @stable ICU 3.6
*/
public Set getBaseSkeletons(Set result) {
if (result == null) {
result = new HashSet<>();
}
result.addAll(basePattern_pattern.keySet());
return result;
}
/**
* Adjusts the field types (width and subtype) of a pattern to match what is
* in a skeleton. That is, if you supply a pattern like "d-M H:m", and a
* skeleton of "MMMMddhhmm", then the input pattern is adjusted to be
* "dd-MMMM hh:mm". This is used internally to get the best match for the
* input skeleton, but can also be used externally.
* Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---replaceFieldTypesExample}
* @param pattern input pattern
* @param skeleton For the pattern to match to.
* @return pattern adjusted to match the skeleton fields widths and subtypes.
* @stable ICU 3.6
*/
public String replaceFieldTypes(String pattern, String skeleton) {
return replaceFieldTypes(pattern, skeleton, MATCH_NO_OPTIONS);
}
/**
* Adjusts the field types (width and subtype) of a pattern to match what is
* in a skeleton. That is, if you supply a pattern like "d-M H:m", and a
* skeleton of "MMMMddhhmm", then the input pattern is adjusted to be
* "dd-MMMM hh:mm". This is used internally to get the best match for the
* input skeleton, but can also be used externally.
*
* @param pattern input pattern
* @param skeleton For the pattern to match to.
* @param options MATCH_xxx options for forcing the length of specified fields in
* the returned pattern to match those in the skeleton (when this would
* not happen otherwise). For default behavior, use MATCH_NO_OPTIONS.
* @return pattern adjusted to match the skeleton fields widths and subtypes.
* @stable ICU 4.4
*/
public String replaceFieldTypes(String pattern, String skeleton, int options) {
synchronized (this) { // synchronized since a getter must be thread-safe
PatternWithMatcher patternNoMatcher = new PatternWithMatcher(pattern, null);
return adjustFieldTypes(patternNoMatcher, current.set(skeleton, fp, false), EnumSet.noneOf(DTPGflags.class), options);
}
}
/**
* The date time format is a message format pattern used to compose date and
* time patterns. The default value is "{1} {0}", where {1} will be replaced
* by the date pattern and {0} will be replaced by the time pattern.
*
* This is used when the input skeleton contains both date and time fields,
* but there is not a close match among the added patterns. For example,
* suppose that this object was created by adding "dd-MMM" and "hh:mm", and
* its datetimeFormat is the default "{1} {0}". Then if the input skeleton
* is "MMMdhmm", there is not an exact match, so the input skeleton is
* broken up into two components "MMMd" and "hmm". There are close matches
* for those two skeletons, so the result is put together with this pattern,
* resulting in "d-MMM h:mm".
*
* @param dateTimeFormat message format pattern, where {1} will be replaced by the date
* pattern and {0} will be replaced by the time pattern.
* @stable ICU 3.6
*/
public void setDateTimeFormat(String dateTimeFormat) {
checkFrozen();
this.dateTimeFormat = dateTimeFormat;
}
/**
* Getter corresponding to setDateTimeFormat.
*
* @return pattern
* @stable ICU 3.6
*/
public String getDateTimeFormat() {
return dateTimeFormat;
}
/**
* The decimal value is used in formatting fractions of seconds. If the
* skeleton contains fractional seconds, then this is used with the
* fractional seconds. For example, suppose that the input pattern is
* "hhmmssSSSS", and the best matching pattern internally is "H:mm:ss", and
* the decimal string is ",". Then the resulting pattern is modified to be
* "H:mm:ss,SSSS"
*
* @param decimal The decimal to set to.
* @stable ICU 3.6
*/
public void setDecimal(String decimal) {
checkFrozen();
this.decimal = decimal;
}
/**
* Getter corresponding to setDecimal.
* @return string corresponding to the decimal point
* @stable ICU 3.6
*/
public String getDecimal() {
return decimal;
}
/**
* Redundant patterns are those which if removed, make no difference in the
* resulting getBestPattern values. This method returns a list of them, to
* help check the consistency of the patterns used to build this generator.
*
* @param output stores the redundant patterns that are removed. To get these
* in internal order, supply a LinkedHashSet. If null, a
* collection is allocated.
* @return the collection with added elements.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public Collection getRedundants(Collection output) {
synchronized (this) { // synchronized since a getter must be thread-safe
if (output == null) {
output = new LinkedHashSet<>();
}
for (DateTimeMatcher cur : skeleton2pattern.keySet()) {
PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(cur);
String pattern = patternWithSkelFlag.pattern;
if (CANONICAL_SET.contains(pattern)) {
continue;
}
String trial = getBestPattern(cur.toString(), cur, MATCH_NO_OPTIONS);
if (trial.equals(pattern)) {
output.add(pattern);
}
}
///CLOVER:OFF
//The following would never be called since the parameter is false
//Eclipse stated the following is "dead code"
/*if (false) { // ordered
DateTimePatternGenerator results = new DateTimePatternGenerator();
PatternInfo pinfo = new PatternInfo();
for (DateTimeMatcher cur : skeleton2pattern.keySet()) {
String pattern = skeleton2pattern.get(cur);
if (CANONICAL_SET.contains(pattern)) {
continue;
}
//skipMatcher = current;
String trial = results.getBestPattern(cur.toString());
if (trial.equals(pattern)) {
output.add(pattern);
} else {
results.addPattern(pattern, false, pinfo);
}
}
}*/
///CLOVER:ON
return output;
}
}
// Field numbers, used for AppendItem functions
/**
* @stable ICU 3.6
*/
public static final int ERA = 0;
/**
* @stable ICU 3.6
*/
public static final int YEAR = 1;
/**
* @stable ICU 3.6
*/
public static final int QUARTER = 2;
/**
* @stable ICU 3.6
*/
public static final int MONTH = 3;
/**
* @stable ICU 3.6
*/
public static final int WEEK_OF_YEAR = 4;
/**
* @stable ICU 3.6
*/
public static final int WEEK_OF_MONTH = 5;
/**
* @stable ICU 3.6
*/
public static final int WEEKDAY = 6;
/**
* @stable ICU 3.6
*/
public static final int DAY = 7;
/**
* @stable ICU 3.6
*/
public static final int DAY_OF_YEAR = 8;
/**
* @stable ICU 3.6
*/
public static final int DAY_OF_WEEK_IN_MONTH = 9;
/**
* @stable ICU 3.6
*/
public static final int DAYPERIOD = 10;
/**
* @stable ICU 3.6
*/
public static final int HOUR = 11;
/**
* @stable ICU 3.6
*/
public static final int MINUTE = 12;
/**
* @stable ICU 3.6
*/
public static final int SECOND = 13;
/**
* @stable ICU 3.6
*/
public static final int FRACTIONAL_SECOND = 14;
/**
* @stable ICU 3.6
*/
public static final int ZONE = 15;
/**
* One more than the highest normal field number.
* @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420.
*/
@Deprecated
public static final int TYPE_LIMIT = 16;
/**
* Field display name width constants for getFieldDisplayName
* @stable ICU 61
*/
public enum DisplayWidth {
/**
* The full field name
* @stable ICU 61
*/
WIDE(""),
/**
* An abbreviated field name
* (may be the same as the wide version, if short enough)
* @stable ICU 61
*/
ABBREVIATED("-short"),
/**
* The shortest possible field name
* (may be the same as the abbreviated version)
* @stable ICU 61
*/
NARROW("-narrow");
/**
* The count of available widths
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
private static int COUNT = DisplayWidth.values().length;
private final String cldrKey;
DisplayWidth(String cldrKey) {
this.cldrKey = cldrKey;
}
private String cldrKey() {
return cldrKey;
}
}
/**
* The field name width for use in appendItems
*/
private static final DisplayWidth APPENDITEM_WIDTH = DisplayWidth.WIDE;
private static final int APPENDITEM_WIDTH_INT = APPENDITEM_WIDTH.ordinal();
private static final DisplayWidth[] CLDR_FIELD_WIDTH = DisplayWidth.values();
// Option masks for getBestPattern, replaceFieldTypes (individual masks may be ORed together)
/**
* Default option mask used for {@link #getBestPattern(String, int)}
* and {@link #replaceFieldTypes(String, String, int)}.
* @stable ICU 4.4
* @see #getBestPattern(String, int)
* @see #replaceFieldTypes(String, String, int)
*/
public static final int MATCH_NO_OPTIONS = 0;
/**
* Option mask for forcing the width of hour field.
* @stable ICU 4.4
* @see #getBestPattern(String, int)
* @see #replaceFieldTypes(String, String, int)
*/
public static final int MATCH_HOUR_FIELD_LENGTH = 1 << HOUR;
/**
* Option mask for forcing the width of minute field.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static final int MATCH_MINUTE_FIELD_LENGTH = 1 << MINUTE;
/**
* Option mask for forcing the width of second field.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static final int MATCH_SECOND_FIELD_LENGTH = 1 << SECOND;
/**
* Option mask for forcing the width of all date and time fields.
* @stable ICU 4.4
* @see #getBestPattern(String, int)
* @see #replaceFieldTypes(String, String, int)
*/
public static final int MATCH_ALL_FIELDS_LENGTH = (1 << TYPE_LIMIT) - 1;
/**
* An AppendItem format is a pattern used to append a field if there is no
* good match. For example, suppose that the input skeleton is "GyyyyMMMd",
* and there is no matching pattern internally, but there is a pattern
* matching "yyyyMMMd", say "d-MM-yyyy". Then that pattern is used, plus the
* G. The way these two are conjoined is by using the AppendItemFormat for G
* (era). So if that value is, say "{0}, {1}" then the final resulting
* pattern is "d-MM-yyyy, G".
*
* There are actually three available variables: {0} is the pattern so far,
* {1} is the element we are adding, and {2} is the name of the element.
*
* This reflects the way that the CLDR data is organized.
*
* @param field such as ERA
* @param value pattern, such as "{0}, {1}"
* @stable ICU 3.6
*/
public void setAppendItemFormat(int field, String value) {
checkFrozen();
appendItemFormats[field] = value;
}
/**
* Getter corresponding to setAppendItemFormats. Values below 0 or at or
* above TYPE_LIMIT are illegal arguments.
*
* @param field The index to retrieve the append item formats.
* @return append pattern for field
* @stable ICU 3.6
*/
public String getAppendItemFormat(int field) {
return appendItemFormats[field];
}
/**
* Sets the names of fields, eg "era" in English for ERA. These are only
* used if the corresponding AppendItemFormat is used, and if it contains a
* {2} variable.
*
* This reflects the way that the CLDR data is organized.
*
* @param field Index of the append item names.
* @param value The value to set the item to.
* @stable ICU 3.6
*/
public void setAppendItemName(int field, String value) {
setFieldDisplayName(field, APPENDITEM_WIDTH, value);
}
/**
* Getter corresponding to setAppendItemName. Values below 0 or at or above
* TYPE_LIMIT are illegal arguments. Note: The more general method
* for getting date/time field display names is getFieldDisplayName.
*
* @param field The index to get the append item name.
* @return name for field
* @see #getFieldDisplayName(int, DisplayWidth)
* @stable ICU 3.6
*/
public String getAppendItemName(int field) {
return getFieldDisplayName(field, APPENDITEM_WIDTH);
}
/**
* Return the default hour cycle.
* @draft ICU 67
* @provisional This API might change or be removed in a future release.
*/
public DateFormat.HourCycle getDefaultHourCycle() {
switch(getDefaultHourFormatChar()) {
case 'h': return DateFormat.HourCycle.HOUR_CYCLE_12;
case 'H': return DateFormat.HourCycle.HOUR_CYCLE_23;
case 'k': return DateFormat.HourCycle.HOUR_CYCLE_24;
case 'K': return DateFormat.HourCycle.HOUR_CYCLE_11;
default: throw new AssertionError("should be unreachable");
}
}
/**
* The private interface to set a display name for a particular date/time field,
* in one of several possible display widths.
*
* @param field The field type, such as ERA.
* @param width The desired DisplayWidth, such as DisplayWidth.ABBREVIATED.
* @param value The display name to set
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
private void setFieldDisplayName(int field, DisplayWidth width, String value) {
checkFrozen();
if (field < TYPE_LIMIT && field >= 0) {
fieldDisplayNames[field][width.ordinal()] = value;
}
}
/**
* The general interface to get a display name for a particular date/time field,
* in one of several possible display widths.
*
* @param field The field type, such as ERA.
* @param width The desired DisplayWidth, such as DisplayWidth.ABBREVIATED.
* @return The display name for the field
* @stable ICU 61
*/
public String getFieldDisplayName(int field, DisplayWidth width) {
if (field >= TYPE_LIMIT || field < 0) {
return "";
}
return fieldDisplayNames[field][width.ordinal()];
}
/**
* Determines whether a skeleton contains a single field
*
* @param skeleton The skeleton to determine if it contains a single field.
* @return true or not
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static boolean isSingleField(String skeleton) {
char first = skeleton.charAt(0);
for (int i = 1; i < skeleton.length(); ++i) {
if (skeleton.charAt(i) != first) return false;
}
return true;
}
/**
* Add key to HashSet cldrAvailableFormatKeys.
*
* @param key of the availableFormats in CLDR
* @stable ICU 3.6
*/
private void setAvailableFormat(String key) {
checkFrozen();
cldrAvailableFormatKeys.add(key);
}
/**
* This function checks the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
* has been added to DateTimePatternGenerator.
* The function is to avoid the duplicate availableFomats added to
* the pattern map from parent locales.
*
* @param key of the availableFormatMask in CLDR
* @return TRUE if the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
* has been added to DateTimePatternGenerator.
* @stable ICU 3.6
*/
private boolean isAvailableFormatSet(String key) {
return cldrAvailableFormatKeys.contains(key);
}
/**
* {@inheritDoc}
* @stable ICU 3.6
*/
@Override
public boolean isFrozen() {
return frozen;
}
/**
* {@inheritDoc}
* @stable ICU 4.4
*/
@Override
public DateTimePatternGenerator freeze() {
frozen = true;
return this;
}
/**
* {@inheritDoc}
* @stable ICU 4.4
*/
@Override
public DateTimePatternGenerator cloneAsThawed() {
DateTimePatternGenerator result = (DateTimePatternGenerator) (this.clone());
frozen = false;
return result;
}
/**
* Returns a copy of this DateTimePatternGenerator
object.
* @return A copy of this DateTimePatternGenerator
object.
* @stable ICU 3.6
*/
@Override
@SuppressWarnings("unchecked")
public Object clone() {
try {
DateTimePatternGenerator result = (DateTimePatternGenerator) (super.clone());
result.skeleton2pattern = (TreeMap) skeleton2pattern.clone();
result.basePattern_pattern = (TreeMap) basePattern_pattern.clone();
result.appendItemFormats = appendItemFormats.clone();
result.fieldDisplayNames = fieldDisplayNames.clone();
result.current = new DateTimeMatcher();
result.fp = new FormatParser();
result._distanceInfo = new DistanceInfo();
result.frozen = false;
return result;
} catch (CloneNotSupportedException e) {
///CLOVER:OFF
throw new ICUCloneNotSupportedException("Internal Error", e);
///CLOVER:ON
}
}
/**
* Utility class for FormatParser. Immutable class that is only used to mark
* the difference between a variable field and a literal string. Each
* variable field must consist of 1 to n variable characters, representing
* date format fields. For example, "VVVV" is valid while "V4" is not, nor
* is "44".
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static class VariableField {
private final String string;
private final int canonicalIndex;
/**
* Create a variable field: equivalent to VariableField(string,false);
* @param string The string for the variable field.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public VariableField(String string) {
this(string, false);
}
/**
* Create a variable field
* @param string The string for the variable field
* @param strict If true, then only allows exactly those lengths specified by CLDR for variables. For example, "hh:mm aa" would throw an exception.
* @throws IllegalArgumentException if the variable field is not valid.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public VariableField(String string, boolean strict) {
canonicalIndex = DateTimePatternGenerator.getCanonicalIndex(string, strict);
if (canonicalIndex < 0) {
throw new IllegalArgumentException("Illegal datetime field:\t"
+ string);
}
this.string = string;
}
/**
* Get the main type of this variable. These types are ERA, QUARTER,
* MONTH, DAY, WEEK_OF_YEAR, WEEK_OF_MONTH, WEEKDAY, DAY, DAYPERIOD
* (am/pm), HOUR, MINUTE, SECOND,FRACTIONAL_SECOND, ZONE.
* @return main type.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public int getType() {
return types[canonicalIndex][1];
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static String getCanonicalCode(int type) {
try {
return CANONICAL_ITEMS[type];
} catch (Exception e) {
return String.valueOf(type);
}
}
/**
* Check if the type of this variable field is numeric.
* @return true if the type of this variable field is numeric.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public boolean isNumeric() {
return types[canonicalIndex][2] > 0;
}
/**
* Private method.
*/
private int getCanonicalIndex() {
return canonicalIndex;
}
/**
* Get the string represented by this variable.
* @internal
* @deprecated This API is ICU internal only.
*/
@Override
@Deprecated
public String toString() {
return string;
}
}
/**
* This class provides mechanisms for parsing a SimpleDateFormat pattern
* or generating a new pattern, while handling the quoting. It represents
* the result of the parse as a list of items, where each item is either a
* literal string or a variable field. When parsing It can be used to find
* out which variable fields are in a date format, and in what order, such
* as for presentation in a UI as separate text entry fields. It can also be
* used to construct new SimpleDateFormats.
* Example:
*
public boolean containsZone(String pattern) {
for (Iterator it = formatParser.set(pattern).getItems().iterator(); it.hasNext();) {
Object item = it.next();
if (item instanceof VariableField) {
VariableField variableField = (VariableField) item;
if (variableField.getType() == DateTimePatternGenerator.ZONE) {
return true;
}
}
}
return false;
}
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
static public class FormatParser {
private static final UnicodeSet SYNTAX_CHARS = new UnicodeSet("[a-zA-Z]").freeze();
private static final UnicodeSet QUOTING_CHARS = new UnicodeSet("[[[:script=Latn:][:script=Cyrl:]]&[[:L:][:M:]]]").freeze();
private transient PatternTokenizer tokenizer = new PatternTokenizer()
.setSyntaxCharacters(SYNTAX_CHARS)
.setExtraQuotingCharacters(QUOTING_CHARS)
.setUsingQuote(true);
private List