2 ********************************************************************************
3 * Copyright (C) 2006-2013, Google, International Business Machines Corporation *
4 * and others. All Rights Reserved. *
5 ********************************************************************************
7 package com.ibm.icu.text;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.BitSet;
12 import java.util.Collection;
13 import java.util.EnumSet;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.LinkedHashMap;
17 import java.util.LinkedHashSet;
18 import java.util.List;
20 import java.util.MissingResourceException;
22 import java.util.TreeMap;
23 import java.util.TreeSet;
25 import com.ibm.icu.impl.ICUCache;
26 import com.ibm.icu.impl.ICUResourceBundle;
27 import com.ibm.icu.impl.PatternTokenizer;
28 import com.ibm.icu.impl.SimpleCache;
29 import com.ibm.icu.impl.Utility;
30 import com.ibm.icu.util.Calendar;
31 import com.ibm.icu.util.Freezable;
32 import com.ibm.icu.util.ULocale;
33 import com.ibm.icu.util.ULocale.Category;
34 import com.ibm.icu.util.UResourceBundle;
37 * This class provides flexible generation of date format patterns, like
38 * "yy-MM-dd". The user can build up the generator by adding successive
39 * patterns. Once that is done, a query can be made using a "skeleton", which is
40 * a pattern which just includes the desired fields and lengths. The generator
41 * will return the "best fit" pattern corresponding to that skeleton.
43 * The main method people will use is getBestPattern(String skeleton), since
44 * normally this class is pre-built with data from a particular locale. However,
45 * generators can be built directly from other data as well.
48 public class DateTimePatternGenerator implements Freezable<DateTimePatternGenerator>, Cloneable {
49 private static final boolean DEBUG = false;
52 //static boolean SHOW_DISTANCE = false;
53 // TODO add hack to fix months for CJK, as per bug ticket 1099
56 * Create empty generator, to be constructed with addPattern(...) etc.
59 public static DateTimePatternGenerator getEmptyInstance() {
60 return new DateTimePatternGenerator();
64 * Only for use by subclasses
67 protected DateTimePatternGenerator() {
71 * Construct a flexible generator according to data for the default <code>FORMAT</code> locale.
72 * @see Category#FORMAT
75 public static DateTimePatternGenerator getInstance() {
76 return getInstance(ULocale.getDefault(Category.FORMAT));
80 * Construct a flexible generator according to data for a given locale.
81 * @param uLocale The locale to pass.
84 public static DateTimePatternGenerator getInstance(ULocale uLocale) {
85 return getFrozenInstance(uLocale).cloneAsThawed();
89 * Construct a frozen instance of DateTimePatternGenerator for a
90 * given locale. This method returns a cached frozen instance of
91 * DateTimePatternGenerator, so less expensive than the regular
93 * @param uLocale The locale to pass.
94 * @return A frozen DateTimePatternGenerator.
96 * @deprecated This API is ICU internal only.
98 public static DateTimePatternGenerator getFrozenInstance(ULocale uLocale) {
99 String localeKey = uLocale.toString();
100 DateTimePatternGenerator result = DTPNG_CACHE.get(localeKey);
101 if (result != null) {
104 result = new DateTimePatternGenerator();
105 PatternInfo returnInfo = new PatternInfo();
106 String shortTimePattern = null;
107 // first load with the ICU patterns
108 for (int i = DateFormat.FULL; i <= DateFormat.SHORT; ++i) {
109 SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(i, uLocale);
110 result.addPattern(df.toPattern(), false, returnInfo);
111 df = (SimpleDateFormat) DateFormat.getTimeInstance(i, uLocale);
112 result.addPattern(df.toPattern(), false, returnInfo);
113 if (i == DateFormat.SHORT) {
114 // keep this pattern to populate other time field
115 // combination patterns by hackTimes later in this method.
116 shortTimePattern = df.toPattern();
118 // use hour style in SHORT time pattern as the default
119 // hour style for the locale
120 FormatParser fp = new FormatParser();
121 fp.set(shortTimePattern);
122 List<Object> items = fp.getItems();
123 for (int idx = 0; idx < items.size(); idx++) {
124 Object item = items.get(idx);
125 if (item instanceof VariableField) {
126 VariableField fld = (VariableField)item;
127 if (fld.getType() == HOUR) {
128 result.defaultHourFormatChar = fld.toString().charAt(0);
136 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, uLocale);
137 // Get the correct calendar type
138 String calendarTypeToUse = uLocale.getKeywordValue("calendar");
139 if ( calendarTypeToUse == null ) {
140 String[] preferredCalendarTypes = Calendar.getKeywordValuesForLocale("calendar", uLocale, true);
141 calendarTypeToUse = preferredCalendarTypes[0]; // the most preferred calendar
143 if ( calendarTypeToUse == null ) {
144 calendarTypeToUse = "gregorian"; // fallback
147 // Get data for that calendar
149 // ICU4J getWithFallback does not work well when
150 // 1) A nested table is an alias to /LOCALE/...
151 // 2) getWithFallback is called multiple times for going down hierarchical resource path
152 // #9987 resolved the issue of alias table when full path is specified in getWithFallback,
153 // but there is no easy solution when the equivalent operation is done by multiple operations.
154 // This issue is addressed in #9964.
155 ICUResourceBundle itemBundle = rb.getWithFallback("calendar/" + calendarTypeToUse + "/appendItems");
156 for (int i=0; i<itemBundle.getSize(); ++i) {
157 ICUResourceBundle formatBundle = (ICUResourceBundle)itemBundle.get(i);
158 String formatName = itemBundle.get(i).getKey();
159 String value = formatBundle.getString();
160 result.setAppendItemFormat(getAppendFormatNumber(formatName), value);
162 }catch(MissingResourceException e) {
167 ICUResourceBundle itemBundle = rb.getWithFallback("fields");
168 ICUResourceBundle fieldBundle, dnBundle;
169 for (int i=0; i<TYPE_LIMIT; ++i) {
170 if ( isCLDRFieldName(i) ) {
171 fieldBundle = itemBundle.getWithFallback(CLDR_FIELD_NAME[i]);
172 dnBundle = fieldBundle.getWithFallback("dn");
173 String value = dnBundle.getString();
174 //System.out.println("Field name:"+value);
175 result.setAppendItemName(i, value);
178 }catch(MissingResourceException e) {
181 // set the AvailableFormat in CLDR
182 ICUResourceBundle availFormatsBundle = null;
184 // ICU4J getWithFallback does not work well when
185 // 1) A nested table is an alias to /LOCALE/...
186 // 2) getWithFallback is called multiple times for going down hierarchical resource path
187 // #9987 resolved the issue of alias table when full path is specified in getWithFallback,
188 // but there is no easy solution when the equivalent operation is done by multiple operations.
189 // This issue is addressed in #9964.
190 availFormatsBundle = rb.getWithFallback("calendar/" + calendarTypeToUse + "/availableFormats");
191 } catch (MissingResourceException e) {
195 boolean override = true;
196 while (availFormatsBundle != null) {
197 for (int i = 0; i < availFormatsBundle.getSize(); i++) {
198 String formatKey = availFormatsBundle.get(i).getKey();
200 if (!result.isAvailableFormatSet(formatKey)) {
201 result.setAvailableFormat(formatKey);
202 // Add pattern with its associated skeleton. Override any duplicate derived from std patterns,
203 // but not a previous availableFormats entry:
204 String formatValue = availFormatsBundle.get(i).getString();
205 result.addPatternWithSkeleton(formatValue, formatKey, override, returnInfo);
209 ICUResourceBundle pbundle = (ICUResourceBundle)availFormatsBundle.getParent();
210 if (pbundle == null) {
214 availFormatsBundle = pbundle.getWithFallback("calendar/" + calendarTypeToUse + "/availableFormats");
215 } catch (MissingResourceException e) {
216 availFormatsBundle = null;
218 if (availFormatsBundle != null && pbundle.getULocale().getBaseName().equals("root")) {
223 // assume it is always big endian (ok for CLDR right now)
224 // some languages didn't add mm:ss or HH:mm, so put in a hack to compute that from the short time.
225 if (shortTimePattern != null) {
226 hackTimes(result, returnInfo, shortTimePattern);
229 result.setDateTimeFormat(Calendar.getDateTimePattern(Calendar.getInstance(uLocale), uLocale, DateFormat.MEDIUM));
231 // decimal point for seconds
232 DecimalFormatSymbols dfs = new DecimalFormatSymbols(uLocale);
233 result.setDecimal(String.valueOf(dfs.getDecimalSeparator()));
237 DTPNG_CACHE.put(localeKey, result);
243 * @deprecated This API is ICU internal only.
245 public char getDefaultHourFormatChar() {
246 return defaultHourFormatChar;
251 * @deprecated This API is ICU internal only.
253 public void setDefaultHourFormatChar(char defaultHourFormatChar) {
254 this.defaultHourFormatChar = defaultHourFormatChar;
257 private static void hackTimes(DateTimePatternGenerator result, PatternInfo returnInfo, String hackPattern) {
258 result.fp.set(hackPattern);
259 StringBuilder mmss = new StringBuilder();
260 // to get mm:ss, we strip all but mm literal ss
261 boolean gotMm = false;
262 for (int i = 0; i < result.fp.items.size(); ++i) {
263 Object item = result.fp.items.get(i);
264 if (item instanceof String) {
266 mmss.append(result.fp.quoteLiteral(item.toString()));
269 char ch = item.toString().charAt(0);
273 } else if (ch == 's') {
278 result.addPattern(mmss.toString(), false, returnInfo);
280 } else if (gotMm || ch == 'z' || ch == 'Z' || ch == 'v' || ch == 'V') {
285 // to get hh:mm, we strip (literal ss) and (literal S)
286 // the easiest way to do this is to mark the stuff we want to nuke, then remove it in a second pass.
287 BitSet variables = new BitSet();
288 BitSet nuke = new BitSet();
289 for (int i = 0; i < result.fp.items.size(); ++i) {
290 Object item = result.fp.items.get(i);
291 if (item instanceof VariableField) {
293 char ch = item.toString().charAt(0);
294 if (ch == 's' || ch == 'S') {
296 for (int j = i-1; j >= 0; ++j) {
297 if (variables.get(j)) break;
303 String hhmm = getFilteredPattern(result.fp, nuke);
304 result.addPattern(hhmm, false, returnInfo);
307 private static String getFilteredPattern(FormatParser fp, BitSet nuke) {
308 StringBuilder result = new StringBuilder();
309 for (int i = 0; i < fp.items.size(); ++i) {
310 if (nuke.get(i)) continue;
311 Object item = fp.items.get(i);
312 if (item instanceof String) {
313 result.append(fp.quoteLiteral(item.toString()));
315 result.append(item.toString());
318 return result.toString();
321 /*private static int getAppendNameNumber(String string) {
322 for (int i = 0; i < CLDR_FIELD_NAME.length; ++i) {
323 if (CLDR_FIELD_NAME[i].equals(string)) return i;
330 * @deprecated This API is ICU internal only.
332 public static int getAppendFormatNumber(String string) {
333 for (int i = 0; i < CLDR_FIELD_APPEND.length; ++i) {
334 if (CLDR_FIELD_APPEND[i].equals(string)) return i;
340 private static boolean isCLDRFieldName(int index) {
341 if ((index<0) && (index>=TYPE_LIMIT)) {
344 if (CLDR_FIELD_NAME[index].charAt(0) == '*') {
353 * Return the best pattern matching the input skeleton. It is guaranteed to
354 * have all of the fields in the skeleton.
355 * <p>Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---getBestPatternExample}
356 * @param skeleton The skeleton is a pattern containing only the variable fields.
357 * For example, "MMMdd" and "mmhh" are skeletons.
358 * @return Best pattern matching the input skeleton.
361 public String getBestPattern(String skeleton) {
362 return getBestPattern(skeleton, null, MATCH_NO_OPTIONS);
366 * Return the best pattern matching the input skeleton. It is guaranteed to
367 * have all of the fields in the skeleton.
369 * @param skeleton The skeleton is a pattern containing only the variable fields.
370 * For example, "MMMdd" and "mmhh" are skeletons.
371 * @param options MATCH_xxx options for forcing the length of specified fields in
372 * the returned pattern to match those in the skeleton (when this would
373 * not happen otherwise). For default behavior, use MATCH_NO_OPTIONS.
374 * @return Best pattern matching the input skeleton (and options).
377 public String getBestPattern(String skeleton, int options) {
378 return getBestPattern(skeleton, null, options);
382 * getBestPattern which takes optional skip matcher
384 private String getBestPattern(String skeleton, DateTimeMatcher skipMatcher, int options) {
385 EnumSet<DTPGflags> flags = EnumSet.noneOf(DTPGflags.class);
386 // Replace hour metacharacters 'j' and 'J', set flags as necessary
387 StringBuilder skeletonCopy = new StringBuilder(skeleton);
388 boolean inQuoted = false;
389 for (int patPos = 0; patPos < skeletonCopy.length(); patPos++) {
390 char patChr = skeletonCopy.charAt(patPos);
391 if (patChr == '\'') {
392 inQuoted = !inQuoted;
393 } else if (!inQuoted) {
395 skeletonCopy.setCharAt(patPos, defaultHourFormatChar);
396 } else if (patChr == 'J') {
397 // Get pattern for skeleton with H, then (in adjustFieldTypes)
398 // replace H or k with defaultHourFormatChar
399 skeletonCopy.setCharAt(patPos, 'H');
400 flags.add(DTPGflags.SKELETON_USES_CAP_J);
405 String datePattern, timePattern;
407 current.set(skeletonCopy.toString(), fp, false);
408 PatternWithMatcher bestWithMatcher = getBestRaw(current, -1, _distanceInfo, skipMatcher);
409 if (_distanceInfo.missingFieldMask == 0 && _distanceInfo.extraFieldMask == 0) {
410 // we have a good item. Adjust the field types
411 return adjustFieldTypes(bestWithMatcher, current, flags, options);
413 int neededFields = current.getFieldMask();
415 // otherwise break up by date and time.
416 datePattern = getBestAppending(current, neededFields & DATE_MASK, _distanceInfo, skipMatcher, flags, options);
417 timePattern = getBestAppending(current, neededFields & TIME_MASK, _distanceInfo, skipMatcher, flags, options);
420 if (datePattern == null) return timePattern == null ? "" : timePattern;
421 if (timePattern == null) return datePattern;
422 return MessageFormat.format(getDateTimeFormat(), new Object[]{timePattern, datePattern});
426 * PatternInfo supplies output parameters for addPattern(...). It is used because
427 * Java doesn't have real output parameters. It is treated like a struct (eg
428 * Point), so all fields are public.
432 public static final class PatternInfo { // struct for return information
436 public static final int OK = 0;
441 public static final int BASE_CONFLICT = 1;
446 public static final int CONFLICT = 2;
456 public String conflictingPattern;
459 * Simple constructor, since this is treated like a struct.
462 public PatternInfo() {
467 * Adds a pattern to the generator. If the pattern has the same skeleton as
468 * an existing pattern, and the override parameter is set, then the previous
469 * value is overridden. Otherwise, the previous value is retained. In either
470 * case, the conflicting information is returned in PatternInfo.
472 * Note that single-field patterns (like "MMM") are automatically added, and
473 * don't need to be added explicitly!
474 * * <p>Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---addPatternExample}
475 * @param pattern Pattern to add.
476 * @param override When existing values are to be overridden use true, otherwise
478 * @param returnInfo Returned information.
481 public DateTimePatternGenerator addPattern(String pattern, boolean override, PatternInfo returnInfo) {
482 return addPatternWithSkeleton(pattern, null, override, returnInfo);
486 * addPatternWithSkeleton:
487 * If skeletonToUse is specified, then an availableFormats entry is being added. In this case:
488 * 1. We pass that skeleton to DateTimeMatcher().set instead of having it derive a skeleton from the pattern.
489 * 2. If the new entry's skeleton or basePattern does match an existing entry but that entry also had a skeleton specified
490 * (i.e. it was also from availableFormats), then the new entry does not override it regardless of the value of the override
491 * parameter. This prevents later availableFormats entries from a parent locale overriding earlier ones from the actual
492 * specified locale. However, availableFormats entries *should* override entries with matching skeleton whose skeleton was
493 * derived (i.e. entries derived from the standard date/time patters for the specified locale).
494 * 3. When adding the pattern (skeleton2pattern.put, basePattern_pattern.put), we set a field to indicate that the added
495 * entry had a specified skeleton.
497 * @deprecated This API is ICU internal only.
499 public DateTimePatternGenerator addPatternWithSkeleton(String pattern, String skeletonToUse, boolean override, PatternInfo returnInfo) {
501 DateTimeMatcher matcher;
502 if (skeletonToUse == null) {
503 matcher = new DateTimeMatcher().set(pattern, fp, false);
505 matcher = new DateTimeMatcher().set(skeletonToUse, fp, false);
507 String basePattern = matcher.getBasePattern();
508 // We only care about base conflicts - and replacing the pattern associated with a base - if:
509 // 1. the conflicting previous base pattern did *not* have an explicit skeleton; in that case the previous
510 // base + pattern combination was derived from either (a) a canonical item, (b) a standard format, or
511 // (c) a pattern specified programmatically with a previous call to addPattern (which would only happen
512 // if we are getting here from a subsequent call to addPattern).
513 // 2. a skeleton is specified for the current pattern, but override=false; in that case we are checking
514 // availableFormats items from root, which should not override any previous entry with the same base.
515 PatternWithSkeletonFlag previousPatternWithSameBase = basePattern_pattern.get(basePattern);
516 if (previousPatternWithSameBase != null && (!previousPatternWithSameBase.skeletonWasSpecified || (skeletonToUse != null && !override))) {
517 returnInfo.status = PatternInfo.BASE_CONFLICT;
518 returnInfo.conflictingPattern = previousPatternWithSameBase.pattern;
523 // The only time we get here with override=true and skeletonToUse!=null is when adding availableFormats
524 // items from CLDR data. In that case, we don't want an item from a parent locale to replace an item with
525 // same skeleton from the specified locale, so skip the current item if skeletonWasSpecified is true for
526 // the previously-specified conflicting item.
527 PatternWithSkeletonFlag previousValue = skeleton2pattern.get(matcher);
528 if (previousValue != null) {
529 returnInfo.status = PatternInfo.CONFLICT;
530 returnInfo.conflictingPattern = previousValue.pattern;
531 if (!override || (skeletonToUse != null && previousValue.skeletonWasSpecified)) return this;
533 returnInfo.status = PatternInfo.OK;
534 returnInfo.conflictingPattern = "";
535 PatternWithSkeletonFlag patWithSkelFlag = new PatternWithSkeletonFlag(pattern,skeletonToUse != null);
537 System.out.println(matcher + " => " + patWithSkelFlag);
539 skeleton2pattern.put(matcher, patWithSkelFlag);
540 basePattern_pattern.put(basePattern, patWithSkelFlag);
545 * Utility to return a unique skeleton from a given pattern. For example,
546 * both "MMM-dd" and "dd/MMM" produce the skeleton "MMMdd".
548 * @param pattern Input pattern, such as "dd/MMM"
549 * @return skeleton, such as "MMMdd"
552 public String getSkeleton(String pattern) {
553 synchronized (this) { // synchronized since a getter must be thread-safe
554 current.set(pattern, fp, false);
555 return current.toString();
560 * Same as getSkeleton, but allows duplicates
562 * @param pattern Input pattern, such as "dd/MMM"
563 * @return skeleton, such as "MMMdd"
565 * @deprecated This API is ICU internal only.
567 public String getSkeletonAllowingDuplicates(String pattern) {
568 synchronized (this) { // synchronized since a getter must be thread-safe
569 current.set(pattern, fp, true);
570 return current.toString();
575 * Same as getSkeleton, but allows duplicates
576 * and returns a string using canonical pattern chars
578 * @param pattern Input pattern, such as "ccc, d LLL"
579 * @return skeleton, such as "MMMEd"
581 * @deprecated This API is ICU internal only.
583 public String getCanonicalSkeletonAllowingDuplicates(String pattern) {
584 synchronized (this) { // synchronized since a getter must be thread-safe
585 current.set(pattern, fp, true);
586 return current.toCanonicalString();
591 * Utility to return a unique base skeleton from a given pattern. This is
592 * the same as the skeleton, except that differences in length are minimized
593 * so as to only preserve the difference between string and numeric form. So
594 * for example, both "MMM-dd" and "d/MMM" produce the skeleton "MMMd"
595 * (notice the single d).
597 * @param pattern Input pattern, such as "dd/MMM"
598 * @return skeleton, such as "MMMdd"
601 public String getBaseSkeleton(String pattern) {
602 synchronized (this) { // synchronized since a getter must be thread-safe
603 current.set(pattern, fp, false);
604 return current.getBasePattern();
609 * Return a list of all the skeletons (in canonical form) from this class,
610 * and the patterns that they map to.
612 * @param result an output Map in which to place the mapping from skeleton to
613 * pattern. If you want to see the internal order being used,
614 * supply a LinkedHashMap. If the input value is null, then a
615 * LinkedHashMap is allocated.
617 * <i>Issue: an alternate API would be to just return a list of
618 * the skeletons, and then have a separate routine to get from
619 * skeleton to pattern.</i>
620 * @return the input Map containing the values.
623 public Map<String, String> getSkeletons(Map<String, String> result) {
624 if (result == null) {
625 result = new LinkedHashMap<String, String>();
627 for (DateTimeMatcher item : skeleton2pattern.keySet()) {
628 PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(item);
629 String pattern = patternWithSkelFlag.pattern;
630 if (CANONICAL_SET.contains(pattern)) {
633 result.put(item.toString(), pattern);
639 * Return a list of all the base skeletons (in canonical form) from this class
642 public Set<String> getBaseSkeletons(Set<String> result) {
643 if (result == null) {
644 result = new HashSet<String>();
646 result.addAll(basePattern_pattern.keySet());
651 * Adjusts the field types (width and subtype) of a pattern to match what is
652 * in a skeleton. That is, if you supply a pattern like "d-M H:m", and a
653 * skeleton of "MMMMddhhmm", then the input pattern is adjusted to be
654 * "dd-MMMM hh:mm". This is used internally to get the best match for the
655 * input skeleton, but can also be used externally.
656 * <p>Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---replaceFieldTypesExample}
657 * @param pattern input pattern
658 * @param skeleton For the pattern to match to.
659 * @return pattern adjusted to match the skeleton fields widths and subtypes.
662 public String replaceFieldTypes(String pattern, String skeleton) {
663 return replaceFieldTypes(pattern, skeleton, MATCH_NO_OPTIONS);
667 * Adjusts the field types (width and subtype) of a pattern to match what is
668 * in a skeleton. That is, if you supply a pattern like "d-M H:m", and a
669 * skeleton of "MMMMddhhmm", then the input pattern is adjusted to be
670 * "dd-MMMM hh:mm". This is used internally to get the best match for the
671 * input skeleton, but can also be used externally.
673 * @param pattern input pattern
674 * @param skeleton For the pattern to match to.
675 * @param options MATCH_xxx options for forcing the length of specified fields in
676 * the returned pattern to match those in the skeleton (when this would
677 * not happen otherwise). For default behavior, use MATCH_NO_OPTIONS.
678 * @return pattern adjusted to match the skeleton fields widths and subtypes.
681 public String replaceFieldTypes(String pattern, String skeleton, int options) {
682 synchronized (this) { // synchronized since a getter must be thread-safe
683 PatternWithMatcher patternNoMatcher = new PatternWithMatcher(pattern, null);
684 return adjustFieldTypes(patternNoMatcher, current.set(skeleton, fp, false), EnumSet.noneOf(DTPGflags.class), options);
689 * The date time format is a message format pattern used to compose date and
690 * time patterns. The default value is "{1} {0}", where {1} will be replaced
691 * by the date pattern and {0} will be replaced by the time pattern.
693 * This is used when the input skeleton contains both date and time fields,
694 * but there is not a close match among the added patterns. For example,
695 * suppose that this object was created by adding "dd-MMM" and "hh:mm", and
696 * its datetimeFormat is the default "{1} {0}". Then if the input skeleton
697 * is "MMMdhmm", there is not an exact match, so the input skeleton is
698 * broken up into two components "MMMd" and "hmm". There are close matches
699 * for those two skeletons, so the result is put together with this pattern,
700 * resulting in "d-MMM h:mm".
702 * @param dateTimeFormat message format pattern, where {1} will be replaced by the date
703 * pattern and {0} will be replaced by the time pattern.
706 public void setDateTimeFormat(String dateTimeFormat) {
708 this.dateTimeFormat = dateTimeFormat;
712 * Getter corresponding to setDateTimeFormat.
717 public String getDateTimeFormat() {
718 return dateTimeFormat;
722 * The decimal value is used in formatting fractions of seconds. If the
723 * skeleton contains fractional seconds, then this is used with the
724 * fractional seconds. For example, suppose that the input pattern is
725 * "hhmmssSSSS", and the best matching pattern internally is "H:mm:ss", and
726 * the decimal string is ",". Then the resulting pattern is modified to be
729 * @param decimal The decimal to set to.
732 public void setDecimal(String decimal) {
734 this.decimal = decimal;
738 * Getter corresponding to setDecimal.
739 * @return string corresponding to the decimal point
742 public String getDecimal() {
747 * Redundant patterns are those which if removed, make no difference in the
748 * resulting getBestPattern values. This method returns a list of them, to
749 * help check the consistency of the patterns used to build this generator.
751 * @param output stores the redundant patterns that are removed. To get these
752 * in internal order, supply a LinkedHashSet. If null, a
753 * collection is allocated.
754 * @return the collection with added elements.
756 * @deprecated This API is ICU internal only.
758 public Collection<String> getRedundants(Collection<String> output) {
759 synchronized (this) { // synchronized since a getter must be thread-safe
760 if (output == null) {
761 output = new LinkedHashSet<String>();
763 for (DateTimeMatcher cur : skeleton2pattern.keySet()) {
764 PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(cur);
765 String pattern = patternWithSkelFlag.pattern;
766 if (CANONICAL_SET.contains(pattern)) {
769 String trial = getBestPattern(cur.toString(), cur, MATCH_NO_OPTIONS);
770 if (trial.equals(pattern)) {
775 //The following would never be called since the parameter is false
776 //Eclipse stated the following is "dead code"
777 /*if (false) { // ordered
778 DateTimePatternGenerator results = new DateTimePatternGenerator();
779 PatternInfo pinfo = new PatternInfo();
780 for (DateTimeMatcher cur : skeleton2pattern.keySet()) {
781 String pattern = skeleton2pattern.get(cur);
782 if (CANONICAL_SET.contains(pattern)) {
785 //skipMatcher = current;
786 String trial = results.getBestPattern(cur.toString());
787 if (trial.equals(pattern)) {
790 results.addPattern(pattern, false, pinfo);
799 // Field numbers, used for AppendItem functions
804 static final public int ERA = 0;
809 static final public int YEAR = 1;
814 static final public int QUARTER = 2;
819 static final public int MONTH = 3;
824 static final public int WEEK_OF_YEAR = 4;
829 static final public int WEEK_OF_MONTH = 5;
834 static final public int WEEKDAY = 6;
839 static final public int DAY = 7;
844 static final public int DAY_OF_YEAR = 8;
849 static final public int DAY_OF_WEEK_IN_MONTH = 9;
854 static final public int DAYPERIOD = 10;
859 static final public int HOUR = 11;
864 static final public int MINUTE = 12;
869 static final public int SECOND = 13;
874 static final public int FRACTIONAL_SECOND = 14;
879 static final public int ZONE = 15;
884 static final public int TYPE_LIMIT = 16;
886 // Option masks for getBestPattern, replaceFieldTypes (individual masks may be ORed together)
889 * Default option mask used for {@link #getBestPattern(String, int)}
890 * and {@link #replaceFieldTypes(String, String, int)}.
892 * @see #getBestPattern(String, int)
893 * @see #replaceFieldTypes(String, String, int)
895 public static final int MATCH_NO_OPTIONS = 0;
898 * Option mask for forcing the width of hour field.
900 * @see #getBestPattern(String, int)
901 * @see #replaceFieldTypes(String, String, int)
903 public static final int MATCH_HOUR_FIELD_LENGTH = 1 << HOUR;
906 * Option mask for forcing the width of minute field.
908 * @deprecated This API is ICU internal only.
910 public static final int MATCH_MINUTE_FIELD_LENGTH = 1 << MINUTE;
913 * Option mask for forcing the width of second field.
915 * @deprecated This API is ICU internal only.
917 public static final int MATCH_SECOND_FIELD_LENGTH = 1 << SECOND;
920 * Option mask for forcing the width of all date and time fields.
922 * @see #getBestPattern(String, int)
923 * @see #replaceFieldTypes(String, String, int)
925 public static final int MATCH_ALL_FIELDS_LENGTH = (1 << TYPE_LIMIT) - 1;
928 * An AppendItem format is a pattern used to append a field if there is no
929 * good match. For example, suppose that the input skeleton is "GyyyyMMMd",
930 * and there is no matching pattern internally, but there is a pattern
931 * matching "yyyyMMMd", say "d-MM-yyyy". Then that pattern is used, plus the
932 * G. The way these two are conjoined is by using the AppendItemFormat for G
933 * (era). So if that value is, say "{0}, {1}" then the final resulting
934 * pattern is "d-MM-yyyy, G".
936 * There are actually three available variables: {0} is the pattern so far,
937 * {1} is the element we are adding, and {2} is the name of the element.
939 * This reflects the way that the CLDR data is organized.
941 * @param field such as ERA
942 * @param value pattern, such as "{0}, {1}"
945 public void setAppendItemFormat(int field, String value) {
947 appendItemFormats[field] = value;
951 * Getter corresponding to setAppendItemFormats. Values below 0 or at or
952 * above TYPE_LIMIT are illegal arguments.
954 * @param field The index to retrieve the append item formats.
955 * @return append pattern for field
958 public String getAppendItemFormat(int field) {
959 return appendItemFormats[field];
963 * Sets the names of fields, eg "era" in English for ERA. These are only
964 * used if the corresponding AppendItemFormat is used, and if it contains a
967 * This reflects the way that the CLDR data is organized.
969 * @param field Index of the append item names.
970 * @param value The value to set the item to.
973 public void setAppendItemName(int field, String value) {
975 appendItemNames[field] = value;
979 * Getter corresponding to setAppendItemNames. Values below 0 or at or above
980 * TYPE_LIMIT are illegal arguments.
982 * @param field The index to get the append item name.
983 * @return name for field
986 public String getAppendItemName(int field) {
987 return appendItemNames[field];
991 * Determines whether a skeleton contains a single field
993 * @param skeleton The skeleton to determine if it contains a single field.
994 * @return true or not
996 * @deprecated This API is ICU internal only.
998 public static boolean isSingleField(String skeleton) {
999 char first = skeleton.charAt(0);
1000 for (int i = 1; i < skeleton.length(); ++i) {
1001 if (skeleton.charAt(i) != first) return false;
1007 * Add key to HashSet cldrAvailableFormatKeys.
1009 * @param key of the availableFormats in CLDR
1012 private void setAvailableFormat(String key) {
1014 cldrAvailableFormatKeys.add(key);
1018 * This function checks the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
1019 * has been added to DateTimePatternGenerator.
1020 * The function is to avoid the duplicate availableFomats added to
1021 * the pattern map from parent locales.
1023 * @param key of the availableFormatMask in CLDR
1024 * @return TRUE if the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
1025 * has been added to DateTimePatternGenerator.
1028 private boolean isAvailableFormatSet(String key) {
1029 return cldrAvailableFormatKeys.contains(key);
1033 * Boilerplate for Freezable
1036 public boolean isFrozen() {
1041 * Boilerplate for Freezable
1044 public DateTimePatternGenerator freeze() {
1050 * Boilerplate for Freezable
1053 public DateTimePatternGenerator cloneAsThawed() {
1054 DateTimePatternGenerator result = (DateTimePatternGenerator) (this.clone());
1063 @SuppressWarnings("unchecked")
1064 public Object clone() {
1066 DateTimePatternGenerator result = (DateTimePatternGenerator) (super.clone());
1067 result.skeleton2pattern = (TreeMap<DateTimeMatcher, PatternWithSkeletonFlag>) skeleton2pattern.clone();
1068 result.basePattern_pattern = (TreeMap<String, PatternWithSkeletonFlag>) basePattern_pattern.clone();
1069 result.appendItemFormats = appendItemFormats.clone();
1070 result.appendItemNames = appendItemNames.clone();
1071 result.current = new DateTimeMatcher();
1072 result.fp = new FormatParser();
1073 result._distanceInfo = new DistanceInfo();
1075 result.frozen = false;
1077 } catch (CloneNotSupportedException e) {
1079 throw new IllegalArgumentException("Internal Error");
1085 * Utility class for FormatParser. Immutable class that is only used to mark
1086 * the difference between a variable field and a literal string. Each
1087 * variable field must consist of 1 to n variable characters, representing
1088 * date format fields. For example, "VVVV" is valid while "V4" is not, nor
1092 * @deprecated This API is ICU internal only.
1094 public static class VariableField {
1095 private final String string;
1096 private final int canonicalIndex;
1099 * Create a variable field: equivalent to VariableField(string,false);
1100 * @param string The string for the variable field.
1102 * @deprecated This API is ICU internal only.
1104 public VariableField(String string) {
1105 this(string, false);
1108 * Create a variable field
1109 * @param string The string for the variable field
1110 * @param strict If true, then only allows exactly those lengths specified by CLDR for variables. For example, "hh:mm aa" would throw an exception.
1111 * @throws IllegalArgumentException if the variable field is not valid.
1113 * @deprecated This API is ICU internal only.
1115 public VariableField(String string, boolean strict) {
1116 canonicalIndex = DateTimePatternGenerator.getCanonicalIndex(string, strict);
1117 if (canonicalIndex < 0) {
1118 throw new IllegalArgumentException("Illegal datetime field:\t"
1121 this.string = string;
1125 * Get the main type of this variable. These types are ERA, QUARTER,
1126 * MONTH, DAY, WEEK_OF_YEAR, WEEK_OF_MONTH, WEEKDAY, DAY, DAYPERIOD
1127 * (am/pm), HOUR, MINUTE, SECOND,FRACTIONAL_SECOND, ZONE.
1128 * @return main type.
1130 * @deprecated This API is ICU internal only.
1132 public int getType() {
1133 return types[canonicalIndex][1];
1138 * @deprecated This API is ICU internal only.
1140 public static String getCanonicalCode(int type) {
1142 return CANONICAL_ITEMS[type];
1143 } catch (Exception e) {
1144 return String.valueOf(type);
1148 * Check if the type of this variable field is numeric.
1149 * @return true if the type of this variable field is numeric.
1151 * @deprecated This API is ICU internal only.
1153 public boolean isNumeric() {
1154 return types[canonicalIndex][2] > 0;
1160 private int getCanonicalIndex() {
1161 return canonicalIndex;
1165 * Get the string represented by this variable.
1167 * @deprecated This API is ICU internal only.
1169 public String toString() {
1175 * This class provides mechanisms for parsing a SimpleDateFormat pattern
1176 * or generating a new pattern, while handling the quoting. It represents
1177 * the result of the parse as a list of items, where each item is either a
1178 * literal string or a variable field. When parsing It can be used to find
1179 * out which variable fields are in a date format, and in what order, such
1180 * as for presentation in a UI as separate text entry fields. It can also be
1181 * used to construct new SimpleDateFormats.
1184 public boolean containsZone(String pattern) {
1185 for (Iterator it = formatParser.set(pattern).getItems().iterator(); it.hasNext();) {
1186 Object item = it.next();
1187 if (item instanceof VariableField) {
1188 VariableField variableField = (VariableField) item;
1189 if (variableField.getType() == DateTimePatternGenerator.ZONE) {
1198 * @deprecated This API is ICU internal only.
1200 static public class FormatParser {
1201 private transient PatternTokenizer tokenizer = new PatternTokenizer()
1202 .setSyntaxCharacters(new UnicodeSet("[a-zA-Z]"))
1203 .setExtraQuotingCharacters(new UnicodeSet("[[[:script=Latn:][:script=Cyrl:]]&[[:L:][:M:]]]"))
1204 //.setEscapeCharacters(new UnicodeSet("[^\\u0020-\\u007E]")) // WARNING: DateFormat doesn't accept \\uXXXX
1205 .setUsingQuote(true);
1206 private List<Object> items = new ArrayList<Object>();
1209 * Construct an empty date format parser, to which strings and variables can be added with set(...).
1211 * @deprecated This API is ICU internal only.
1213 public FormatParser() {
1217 * Parses the string into a list of items.
1218 * @param string The string to parse.
1219 * @return this, for chaining
1221 * @deprecated This API is ICU internal only.
1223 final public FormatParser set(String string) {
1224 return set(string, false);
1228 * Parses the string into a list of items, taking into account all of the quoting that may be going on.
1229 * @param string The string to parse.
1230 * @param strict If true, then only allows exactly those lengths specified by CLDR for variables. For example, "hh:mm aa" would throw an exception.
1231 * @return this, for chaining
1233 * @deprecated This API is ICU internal only.
1235 public FormatParser set(String string, boolean strict) {
1237 if (string.length() == 0) return this;
1238 tokenizer.setPattern(string);
1239 StringBuffer buffer = new StringBuffer();
1240 StringBuffer variable = new StringBuffer();
1242 buffer.setLength(0);
1243 int status = tokenizer.next(buffer);
1244 if (status == PatternTokenizer.DONE) break;
1245 if (status == PatternTokenizer.SYNTAX) {
1246 if (variable.length() != 0 && buffer.charAt(0) != variable.charAt(0)) {
1247 addVariable(variable, false);
1249 variable.append(buffer);
1251 addVariable(variable, false);
1252 items.add(buffer.toString());
1255 addVariable(variable, false);
1259 private void addVariable(StringBuffer variable, boolean strict) {
1260 if (variable.length() != 0) {
1261 items.add(new VariableField(variable.toString(), strict));
1262 variable.setLength(0);
1266 // /** Private method. Return a collection of fields. These will be a mixture of literal Strings and VariableFields. Any "a" variable field is removed.
1267 // * @param output List to append the items to. If null, is allocated as an ArrayList.
1270 // private List getVariableFields(List output) {
1271 // if (output == null) output = new ArrayList();
1273 // for (Iterator it = items.iterator(); it.hasNext();) {
1274 // Object item = it.next();
1275 // if (item instanceof VariableField) {
1276 // String s = item.toString();
1277 // switch(s.charAt(0)) {
1278 // //case 'Q': continue main; // HACK
1279 // case 'a': continue main; // remove
1281 // output.add(item);
1284 // //System.out.println(output);
1289 // * Produce a string which concatenates all the variables. That is, it is the logically the same as the input with all literals removed.
1290 // * @return a string which is a concatenation of all the variable fields
1292 // * @deprecated This API is ICU internal only.
1294 // public String getVariableFieldString() {
1295 // List list = getVariableFields(null);
1296 // StringBuffer result = new StringBuffer();
1297 // for (Iterator it = list.iterator(); it.hasNext();) {
1298 // String item = it.next().toString();
1299 // result.append(item);
1301 // return result.toString();
1305 * Returns modifiable list which is a mixture of Strings and VariableFields, in the order found during parsing. The strings represent literals, and have all quoting removed. Thus the string "dd 'de' MM" will parse into three items:
1311 * The list is modifiable, so you can add any strings or variables to it, or remove any items.
1312 * @return modifiable list of items.
1314 * @deprecated This API is ICU internal only.
1316 public List<Object> getItems() {
1320 /** Provide display form of formatted input. Each literal string is quoted if necessary.. That is, if the input was "hh':'mm", the result would be "hh:mm", since the ":" doesn't need quoting. See quoteLiteral().
1321 * @return printable output string
1323 * @deprecated This API is ICU internal only.
1325 public String toString() {
1326 return toString(0, items.size());
1330 * Provide display form of a segment of the parsed input. Each literal string is minimally quoted. That is, if the input was "hh':'mm", the result would be "hh:mm", since the ":" doesn't need quoting. See quoteLiteral().
1331 * @param start item to start from
1332 * @param limit last item +1
1333 * @return printable output string
1335 * @deprecated This API is ICU internal only.
1337 public String toString(int start, int limit) {
1338 StringBuilder result = new StringBuilder();
1339 for (int i = start; i < limit; ++i) {
1340 Object item = items.get(i);
1341 if (item instanceof String) {
1342 String itemString = (String) item;
1343 result.append(tokenizer.quoteLiteral(itemString));
1345 result.append(items.get(i).toString());
1348 return result.toString();
1352 * Returns true if it has a mixture of date and time variable fields: that is, at least one date variable and at least one time variable.
1353 * @return true or false
1355 * @deprecated This API is ICU internal only.
1357 public boolean hasDateAndTimeFields() {
1359 for (Object item : items) {
1360 if (item instanceof VariableField) {
1361 int type = ((VariableField)item).getType();
1362 foundMask |= 1 << type;
1365 boolean isDate = (foundMask & DATE_MASK) != 0;
1366 boolean isTime = (foundMask & TIME_MASK) != 0;
1367 return isDate && isTime;
1371 // * Internal routine
1376 // * @deprecated This API is ICU internal only.
1378 // public List getAutoPatterns(String value, List result) {
1379 // if (result == null) result = new ArrayList();
1380 // int fieldCount = 0;
1381 // int minField = Integer.MAX_VALUE;
1382 // int maxField = Integer.MIN_VALUE;
1383 // for (Iterator it = items.iterator(); it.hasNext();) {
1384 // Object item = it.next();
1385 // if (item instanceof VariableField) {
1387 // int type = ((VariableField)item).getType();
1388 // if (minField > type) minField = type;
1389 // if (maxField < type) maxField = type;
1390 // if (type == ZONE || type == DAYPERIOD || type == WEEKDAY) return result; // skip anything with zones
1392 // } catch (Exception e) {
1393 // return result; // if there are any funny fields, return
1397 // if (fieldCount < 3) return result; // skip
1398 // // trim from start
1399 // // trim first field IF there are no letters around it
1400 // // and it is either the min or the max field
1401 // // first field is either 0 or 1
1402 // for (int i = 0; i < items.size(); ++i) {
1403 // Object item = items.get(i);
1404 // if (item instanceof VariableField) {
1405 // int type = ((VariableField)item).getType();
1406 // if (type != minField && type != maxField) break;
1409 // Object previousItem = items.get(0);
1410 // if (alpha.containsSome(previousItem.toString())) break;
1413 // if (start < items.size()) {
1414 // Object nextItem = items.get(start);
1415 // if (nextItem instanceof String) {
1416 // if (alpha.containsSome(nextItem.toString())) break;
1417 // start++; // otherwise skip over string
1420 // result.add(toString(start, items.size()));
1424 // // now trim from end
1425 // for (int i = items.size()-1; i >= 0; --i) {
1426 // Object item = items.get(i);
1427 // if (item instanceof VariableField) {
1428 // int type = ((VariableField)item).getType();
1429 // if (type != minField && type != maxField) break;
1430 // if (i < items.size() - 1) {
1431 // Object previousItem = items.get(items.size() - 1);
1432 // if (alpha.containsSome(previousItem.toString())) break;
1436 // Object nextItem = items.get(end);
1437 // if (nextItem instanceof String) {
1438 // if (alpha.containsSome(nextItem.toString())) break;
1439 // end--; // otherwise skip over string
1442 // result.add(toString(0, end+1));
1450 // private static UnicodeSet alpha = new UnicodeSet("[:alphabetic:]");
1452 // private int getType(Object item) {
1453 // String s = item.toString();
1454 // int canonicalIndex = getCanonicalIndex(s);
1455 // if (canonicalIndex < 0) {
1456 // throw new IllegalArgumentException("Illegal field:\t"
1459 // int type = types[canonicalIndex][1];
1464 * Each literal string is quoted as needed. That is, the ' quote marks will only be added if needed. The exact pattern of quoting is not guaranteed, thus " de la " could be quoted as " 'de la' " or as " 'de' 'la' ".
1465 * @param string The string to check.
1466 * @return string with quoted literals
1468 * @deprecated This API is ICU internal only.
1470 public Object quoteLiteral(String string) {
1471 return tokenizer.quoteLiteral(string);
1477 * Used by CLDR tooling; not in ICU4C.
1478 * Note, this will not work correctly with normal skeletons, since fields
1479 * that should be related in the two skeletons being compared - like EEE and
1480 * ccc, or y and U - will not be sorted in the same relative place as each
1481 * other when iterating over both TreeSets being compare, using TreeSet's
1482 * "natural" code point ordering (this could be addressed by initializing
1483 * the TreeSet with a comparator that compares fields first by their index
1484 * from getCanonicalIndex()). However if comparing canonical skeletons from
1485 * getCanonicalSkeletonAllowingDuplicates it will be OK regardless, since
1486 * in these skeletons all fields are normalized to the canonical pattern
1487 * char for those fields - M or L to M, E or c to E, y or U to y, etc. -
1488 * so corresponding fields will sort in the same way for both TreeMaps.
1490 * @deprecated This API is ICU internal only.
1492 public boolean skeletonsAreSimilar(String id, String skeleton) {
1493 if (id.equals(skeleton)) {
1494 return true; // fast path
1496 // must clone array, make sure items are in same order.
1497 TreeSet<String> parser1 = getSet(id);
1498 TreeSet<String> parser2 = getSet(skeleton);
1499 if (parser1.size() != parser2.size()) {
1502 Iterator<String> it2 = parser2.iterator();
1503 for (String item : parser1) {
1504 int index1 = getCanonicalIndex(item, false);
1505 String item2 = it2.next(); // same length so safe
1506 int index2 = getCanonicalIndex(item2, false);
1507 if (types[index1][1] != types[index2][1]) {
1514 private TreeSet<String> getSet(String id) {
1515 final List<Object> items = fp.set(id).getItems();
1516 TreeSet<String> result = new TreeSet<String>();
1517 for (Object obj : items) {
1518 final String item = obj.toString();
1519 if (item.startsWith("G") || item.startsWith("a")) {
1527 // ========= PRIVATES ============
1529 private static class PatternWithMatcher {
1530 public String pattern;
1531 public DateTimeMatcher matcherWithSkeleton;
1532 // Simple constructor
1533 public PatternWithMatcher(String pat, DateTimeMatcher matcher) {
1535 matcherWithSkeleton = matcher;
1538 private static class PatternWithSkeletonFlag {
1539 public String pattern;
1540 public boolean skeletonWasSpecified;
1541 // Simple constructor
1542 public PatternWithSkeletonFlag(String pat, boolean skelSpecified) {
1544 skeletonWasSpecified = skelSpecified;
1546 public String toString() {
1547 return pattern + "," + skeletonWasSpecified;
1550 private TreeMap<DateTimeMatcher, PatternWithSkeletonFlag> skeleton2pattern = new TreeMap<DateTimeMatcher, PatternWithSkeletonFlag>(); // items are in priority order
1551 private TreeMap<String, PatternWithSkeletonFlag> basePattern_pattern = new TreeMap<String, PatternWithSkeletonFlag>(); // items are in priority order
1552 private String decimal = "?";
1553 private String dateTimeFormat = "{1} {0}";
1554 private String[] appendItemFormats = new String[TYPE_LIMIT];
1555 private String[] appendItemNames = new String[TYPE_LIMIT];
1557 for (int i = 0; i < TYPE_LIMIT; ++i) {
1558 appendItemFormats[i] = "{0} \u251C{2}: {1}\u2524";
1559 appendItemNames[i] = "F" + i;
1562 private char defaultHourFormatChar = 'H';
1563 //private boolean chineseMonthHack = false;
1564 //private boolean isComplete = false;
1565 private boolean frozen = false;
1567 private transient DateTimeMatcher current = new DateTimeMatcher();
1568 private transient FormatParser fp = new FormatParser();
1569 private transient DistanceInfo _distanceInfo = new DistanceInfo();
1571 private static final int FRACTIONAL_MASK = 1<<FRACTIONAL_SECOND;
1572 private static final int SECOND_AND_FRACTIONAL_MASK = (1<<SECOND) | (1<<FRACTIONAL_SECOND);
1574 // Cache for DateTimePatternGenerator
1575 private static ICUCache<String, DateTimePatternGenerator> DTPNG_CACHE = new SimpleCache<String, DateTimePatternGenerator>();
1577 private void checkFrozen() {
1579 throw new UnsupportedOperationException("Attempt to modify frozen object");
1584 * We only get called here if we failed to find an exact skeleton. We have broken it into date + time, and look for the pieces.
1585 * If we fail to find a complete skeleton, we compose in a loop until we have all the fields.
1587 private String getBestAppending(DateTimeMatcher source, int missingFields, DistanceInfo distInfo, DateTimeMatcher skipMatcher, EnumSet<DTPGflags> flags, int options) {
1588 String resultPattern = null;
1589 if (missingFields != 0) {
1590 PatternWithMatcher resultPatternWithMatcher = getBestRaw(source, missingFields, distInfo, skipMatcher);
1591 resultPattern = adjustFieldTypes(resultPatternWithMatcher, source, flags, options);
1593 while (distInfo.missingFieldMask != 0) { // precondition: EVERY single field must work!
1595 // special hack for SSS. If we are missing SSS, and we had ss but found it, replace the s field according to the
1597 if ((distInfo.missingFieldMask & SECOND_AND_FRACTIONAL_MASK) == FRACTIONAL_MASK
1598 && (missingFields & SECOND_AND_FRACTIONAL_MASK) == SECOND_AND_FRACTIONAL_MASK) {
1599 resultPatternWithMatcher.pattern = resultPattern;
1600 flags = EnumSet.copyOf(flags);
1601 flags.add(DTPGflags.FIX_FRACTIONAL_SECONDS);
1602 resultPattern = adjustFieldTypes(resultPatternWithMatcher, source, flags, options);
1603 distInfo.missingFieldMask &= ~FRACTIONAL_MASK; // remove bit
1607 int startingMask = distInfo.missingFieldMask;
1608 PatternWithMatcher tempWithMatcher = getBestRaw(source, distInfo.missingFieldMask, distInfo, skipMatcher);
1609 String temp = adjustFieldTypes(tempWithMatcher, source, flags, options);
1610 int foundMask = startingMask & ~distInfo.missingFieldMask;
1611 int topField = getTopBitNumber(foundMask);
1612 resultPattern = MessageFormat.format(getAppendFormat(topField), new Object[]{resultPattern, temp, getAppendName(topField)});
1615 return resultPattern;
1618 private String getAppendName(int foundMask) {
1619 return "'" + appendItemNames[foundMask] + "'";
1621 private String getAppendFormat(int foundMask) {
1622 return appendItemFormats[foundMask];
1626 // * @param current2
1629 // private String adjustSeconds(DateTimeMatcher current2) {
1630 // // TODO Auto-generated method stub
1637 private int getTopBitNumber(int foundMask) {
1639 while (foundMask != 0) {
1649 private void complete() {
1650 PatternInfo patternInfo = new PatternInfo();
1651 // make sure that every valid field occurs once, with a "default" length
1652 for (int i = 0; i < CANONICAL_ITEMS.length; ++i) {
1653 //char c = (char)types[i][0];
1654 addPattern(String.valueOf(CANONICAL_ITEMS[i]), false, patternInfo);
1656 //isComplete = true;
1665 private PatternWithMatcher getBestRaw(DateTimeMatcher source, int includeMask, DistanceInfo missingFields, DateTimeMatcher skipMatcher) {
1666 // if (SHOW_DISTANCE) System.out.println("Searching for: " + source.pattern
1667 // + ", mask: " + showMask(includeMask));
1668 int bestDistance = Integer.MAX_VALUE;
1669 PatternWithMatcher bestPatternWithMatcher = new PatternWithMatcher("", null);
1670 DistanceInfo tempInfo = new DistanceInfo();
1671 for (DateTimeMatcher trial : skeleton2pattern.keySet()) {
1672 if (trial.equals(skipMatcher)) {
1675 int distance = source.getDistance(trial, includeMask, tempInfo);
1676 // if (SHOW_DISTANCE) System.out.println("\tDistance: " + trial.pattern + ":\t"
1677 // + distance + ",\tmissing fields: " + tempInfo);
1678 if (distance < bestDistance) {
1679 bestDistance = distance;
1680 PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(trial);
1681 bestPatternWithMatcher.pattern = patternWithSkelFlag.pattern;
1682 // If the best raw match had a specified skeleton then return it too.
1683 // This can be passed through to adjustFieldTypes to help it do a better job.
1684 if (patternWithSkelFlag.skeletonWasSpecified) {
1685 bestPatternWithMatcher.matcherWithSkeleton = trial;
1687 bestPatternWithMatcher.matcherWithSkeleton = null;
1689 missingFields.setTo(tempInfo);
1690 if (distance == 0) {
1695 return bestPatternWithMatcher;
1699 * @param fixFractionalSeconds TODO
1702 private enum DTPGflags { FIX_FRACTIONAL_SECONDS, SKELETON_USES_CAP_J };
1704 private String adjustFieldTypes(PatternWithMatcher patternWithMatcher, DateTimeMatcher inputRequest, EnumSet<DTPGflags> flags, int options) {
1705 fp.set(patternWithMatcher.pattern);
1706 StringBuilder newPattern = new StringBuilder();
1707 for (Object item : fp.getItems()) {
1708 if (item instanceof String) {
1709 newPattern.append(fp.quoteLiteral((String)item));
1711 final VariableField variableField = (VariableField) item;
1712 StringBuilder fieldBuilder = new StringBuilder(variableField.toString());
1713 // int canonicalIndex = getCanonicalIndex(field, true);
1714 // if (canonicalIndex < 0) {
1715 // continue; // don't adjust
1717 // int type = types[canonicalIndex][1];
1718 int type = variableField.getType();
1720 if (flags.contains(DTPGflags.FIX_FRACTIONAL_SECONDS) && type == SECOND) {
1721 String newField = inputRequest.original[FRACTIONAL_SECOND];
1722 fieldBuilder.append(decimal);
1723 fieldBuilder.append(newField);
1724 } else if (inputRequest.type[type] != 0) {
1726 // - "reqField" is the field from the originally requested skeleton, with length
1728 // - "field" is the field from the found pattern.
1730 // The adjusted field should consist of characters from the originally requested
1731 // skeleton, except in the case of HOUR or MONTH or WEEKDAY or YEAR, in which case it
1732 // should consist of characters from the found pattern.
1734 // The length of the adjusted field (adjFieldLen) should match that in the originally
1735 // requested skeleton, except that in the following cases the length of the adjusted field
1736 // should match that in the found pattern (i.e. the length of this pattern field should
1737 // not be adjusted):
1738 // 1. type is HOUR and the corresponding bit in options is not set (ticket #7180).
1739 // Note, we may want to implement a similar change for other numeric fields (MM, dd,
1740 // etc.) so the default behavior is to get locale preference for field length, but
1741 // options bits can be used to override this.
1742 // 2. There is a specified skeleton for the found pattern and one of the following is true:
1743 // a) The length of the field in the skeleton (skelFieldLen) is equal to reqFieldLen.
1744 // b) The pattern field is numeric and the skeleton field is not, or vice versa.
1746 // Old behavior was:
1747 // normally we just replace the field. However HOUR is special; we only change the length
1749 String reqField = inputRequest.original[type];
1750 int reqFieldLen = reqField.length();
1751 if ( reqField.charAt(0) == 'E' && reqFieldLen < 3 ) {
1752 reqFieldLen = 3; // 1-3 for E are equivalent to 3 for c,e
1754 int adjFieldLen = reqFieldLen;
1755 DateTimeMatcher matcherWithSkeleton = patternWithMatcher.matcherWithSkeleton;
1756 if ( (type == HOUR && (options & MATCH_HOUR_FIELD_LENGTH)==0) ||
1757 (type == MINUTE && (options & MATCH_MINUTE_FIELD_LENGTH)==0) ||
1758 (type == SECOND && (options & MATCH_SECOND_FIELD_LENGTH)==0) ) {
1759 adjFieldLen = fieldBuilder.length();
1760 } else if (matcherWithSkeleton != null) {
1761 String skelField = matcherWithSkeleton.origStringForField(type);
1762 int skelFieldLen = skelField.length();
1763 boolean patFieldIsNumeric = variableField.isNumeric();
1764 boolean skelFieldIsNumeric = matcherWithSkeleton.fieldIsNumeric(type);
1765 if (skelFieldLen == reqFieldLen || (patFieldIsNumeric && !skelFieldIsNumeric) || (skelFieldIsNumeric && !patFieldIsNumeric)) {
1766 // don't adjust the field length in the found pattern
1767 adjFieldLen = fieldBuilder.length();
1770 char c = (type != HOUR && type != MONTH && type != WEEKDAY && (type != YEAR || reqField.charAt(0)=='Y'))?
1771 reqField.charAt(0): fieldBuilder.charAt(0);
1772 if (type == HOUR && flags.contains(DTPGflags.SKELETON_USES_CAP_J)) {
1773 c = defaultHourFormatChar;
1775 fieldBuilder = new StringBuilder();
1776 for (int i = adjFieldLen; i > 0; --i) fieldBuilder.append(c);
1778 newPattern.append(fieldBuilder);
1781 //if (SHOW_DISTANCE) System.out.println("\tRaw: " + pattern);
1782 return newPattern.toString();
1785 // public static String repeat(String s, int count) {
1786 // StringBuffer result = new StringBuffer();
1787 // for (int i = 0; i < count; ++i) {
1788 // result.append(s);
1790 // return result.toString();
1795 * @param pattern The pattern that is passed.
1796 * @return field value
1798 * @deprecated This API is ICU internal only.
1800 public String getFields(String pattern) {
1802 StringBuilder newPattern = new StringBuilder();
1803 for (Object item : fp.getItems()) {
1804 if (item instanceof String) {
1805 newPattern.append(fp.quoteLiteral((String)item));
1807 newPattern.append("{" + getName(item.toString()) + "}");
1810 return newPattern.toString();
1813 private static String showMask(int mask) {
1814 StringBuilder result = new StringBuilder();
1815 for (int i = 0; i < TYPE_LIMIT; ++i) {
1816 if ((mask & (1<<i)) == 0)
1818 if (result.length() != 0)
1819 result.append(" | ");
1820 result.append(FIELD_NAME[i]);
1823 return result.toString();
1826 private static final String[] CLDR_FIELD_APPEND = {
1827 "Era", "Year", "Quarter", "Month", "Week", "*", "Day-Of-Week",
1828 "Day", "*", "*", "*",
1829 "Hour", "Minute", "Second", "*", "Timezone"
1832 private static final String[] CLDR_FIELD_NAME = {
1833 "era", "year", "*", "month", "week", "*", "weekday",
1834 "day", "*", "*", "dayperiod",
1835 "hour", "minute", "second", "*", "zone"
1838 private static final String[] FIELD_NAME = {
1839 "Era", "Year", "Quarter", "Month", "Week_in_Year", "Week_in_Month", "Weekday",
1840 "Day", "Day_Of_Year", "Day_of_Week_in_Month", "Dayperiod",
1841 "Hour", "Minute", "Second", "Fractional_Second", "Zone"
1845 private static final String[] CANONICAL_ITEMS = {
1846 "G", "y", "Q", "M", "w", "W", "E",
1848 "H", "m", "s", "S", "v"
1851 private static final Set<String> CANONICAL_SET = new HashSet<String>(Arrays.asList(CANONICAL_ITEMS));
1852 private Set<String> cldrAvailableFormatKeys = new HashSet<String>(20);
1854 private static final int
1855 DATE_MASK = (1<<DAYPERIOD) - 1,
1856 TIME_MASK = (1<<TYPE_LIMIT) - 1 - DATE_MASK;
1858 private static final int // numbers are chosen to express 'distance'
1865 EXTRA_FIELD = 0x10000,
1866 MISSING_FIELD = 0x1000;
1869 static private String getName(String s) {
1870 int i = getCanonicalIndex(s, true);
1871 String name = FIELD_NAME[types[i][1]];
1872 int subtype = types[i][2];
1873 boolean string = subtype < 0;
1874 if (string) subtype = -subtype;
1875 if (subtype < 0) name += ":S";
1881 * Get the canonical index, or return -1 if illegal.
1883 * @param strict TODO
1885 private static int getCanonicalIndex(String s, boolean strict) {
1886 int len = s.length();
1890 int ch = s.charAt(0);
1891 // verify that all are the same character
1892 for (int i = 1; i < len; ++i) {
1893 if (s.charAt(i) != ch) {
1898 for (int i = 0; i < types.length; ++i) {
1899 int[] row = types[i];
1900 if (row[0] != ch) continue;
1902 if (row[3] > len) continue;
1903 if (row[row.length-1] < len) continue;
1906 return strict ? -1 : bestRow;
1909 private static final int[][] types = {
1910 // the order here makes a difference only when searching for single field.
1912 // pattern character, main type, weight, min length, weight
1913 {'G', ERA, SHORT, 1, 3},
1914 {'G', ERA, LONG, 4},
1916 {'y', YEAR, NUMERIC, 1, 20},
1917 {'Y', YEAR, NUMERIC + DELTA, 1, 20},
1918 {'u', YEAR, NUMERIC + 2*DELTA, 1, 20},
1919 {'U', YEAR, SHORT, 1, 3},
1920 {'U', YEAR, LONG, 4},
1921 {'U', YEAR, NARROW, 5},
1923 {'Q', QUARTER, NUMERIC, 1, 2},
1924 {'Q', QUARTER, SHORT, 3},
1925 {'Q', QUARTER, LONG, 4},
1927 {'q', QUARTER, NUMERIC + DELTA, 1, 2},
1928 {'q', QUARTER, SHORT + DELTA, 3},
1929 {'q', QUARTER, LONG + DELTA, 4},
1931 {'M', MONTH, NUMERIC, 1, 2},
1932 {'M', MONTH, SHORT, 3},
1933 {'M', MONTH, LONG, 4},
1934 {'M', MONTH, NARROW, 5},
1935 {'L', MONTH, NUMERIC + DELTA, 1, 2},
1936 {'L', MONTH, SHORT - DELTA, 3},
1937 {'L', MONTH, LONG - DELTA, 4},
1938 {'L', MONTH, NARROW - DELTA, 5},
1940 {'l', MONTH, NUMERIC + DELTA, 1, 1},
1942 {'w', WEEK_OF_YEAR, NUMERIC, 1, 2},
1943 {'W', WEEK_OF_MONTH, NUMERIC + DELTA, 1},
1945 {'E', WEEKDAY, SHORT, 1, 3},
1946 {'E', WEEKDAY, LONG, 4},
1947 {'E', WEEKDAY, NARROW, 5},
1948 {'c', WEEKDAY, NUMERIC + 2*DELTA, 1, 2},
1949 {'c', WEEKDAY, SHORT - 2*DELTA, 3},
1950 {'c', WEEKDAY, LONG - 2*DELTA, 4},
1951 {'c', WEEKDAY, NARROW - 2*DELTA, 5},
1952 {'e', WEEKDAY, NUMERIC + DELTA, 1, 2}, // 'e' is currently not used in CLDR data, should not be canonical
1953 {'e', WEEKDAY, SHORT - DELTA, 3},
1954 {'e', WEEKDAY, LONG - DELTA, 4},
1955 {'e', WEEKDAY, NARROW - DELTA, 5},
1957 {'d', DAY, NUMERIC, 1, 2},
1958 {'D', DAY_OF_YEAR, NUMERIC + DELTA, 1, 3},
1959 {'F', DAY_OF_WEEK_IN_MONTH, NUMERIC + 2*DELTA, 1},
1960 {'g', DAY, NUMERIC + 3*DELTA, 1, 20}, // really internal use, so we don't care
1962 {'a', DAYPERIOD, SHORT, 1},
1964 {'H', HOUR, NUMERIC + 10*DELTA, 1, 2}, // 24 hour
1965 {'k', HOUR, NUMERIC + 11*DELTA, 1, 2},
1966 {'h', HOUR, NUMERIC, 1, 2}, // 12 hour
1967 {'K', HOUR, NUMERIC + DELTA, 1, 2},
1969 {'m', MINUTE, NUMERIC, 1, 2},
1971 {'s', SECOND, NUMERIC, 1, 2},
1972 {'S', FRACTIONAL_SECOND, NUMERIC + DELTA, 1, 1000},
1973 {'A', SECOND, NUMERIC + 2*DELTA, 1, 1000},
1975 {'v', ZONE, SHORT - 2*DELTA, 1},
1976 {'v', ZONE, LONG - 2*DELTA, 4},
1977 {'z', ZONE, SHORT, 1, 3},
1978 {'z', ZONE, LONG, 4},
1979 {'Z', ZONE, NARROW - DELTA, 1, 3},
1980 {'Z', ZONE, LONG - DELTA, 4},
1981 {'Z', ZONE, SHORT - DELTA, 5},
1982 {'O', ZONE, SHORT - DELTA, 1},
1983 {'O', ZONE, LONG - DELTA, 4},
1984 {'V', ZONE, SHORT - DELTA, 1},
1985 {'V', ZONE, LONG - DELTA, 2},
1986 {'X', ZONE, NARROW - DELTA, 1},
1987 {'X', ZONE, SHORT - DELTA, 2},
1988 {'X', ZONE, LONG - DELTA, 4},
1989 {'x', ZONE, NARROW - DELTA, 1},
1990 {'x', ZONE, SHORT - DELTA, 2},
1991 {'x', ZONE, LONG - DELTA, 4},
1994 private static class DateTimeMatcher implements Comparable<DateTimeMatcher> {
1995 //private String pattern = null;
1996 private int[] type = new int[TYPE_LIMIT];
1997 private String[] original = new String[TYPE_LIMIT];
1998 private String[] baseOriginal = new String[TYPE_LIMIT];
2000 // just for testing; fix to make multi-threaded later
2001 // private static FormatParser fp = new FormatParser();
2003 public String origStringForField(int field) {
2004 return original[field];
2007 public boolean fieldIsNumeric(int field) {
2008 return type[field] > 0;
2011 public String toString() {
2012 StringBuilder result = new StringBuilder();
2013 for (int i = 0; i < TYPE_LIMIT; ++i) {
2014 if (original[i].length() != 0) result.append(original[i]);
2016 return result.toString();
2019 // returns a string like toString but using the canonical character for most types,
2020 // e.g. M for M or L, E for E or c, y for y or U, etc. The hour field is canonicalized
2021 // to 'H' (for 24-hour types) or 'h' (for 12-hour types)
2022 public String toCanonicalString() {
2023 StringBuilder result = new StringBuilder();
2024 for (int i = 0; i < TYPE_LIMIT; ++i) {
2025 if (original[i].length() != 0) {
2026 // append a string of the same length using the canonical character
2027 for (int j = 0; j < types.length; ++j) {
2028 int[] row = types[j];
2030 char originalChar = original[i].charAt(0);
2031 char repeatChar = (originalChar=='h' || originalChar=='K')? 'h': (char)row[0];
2032 result.append(Utility.repeat(String.valueOf(repeatChar), original[i].length()));
2038 return result.toString();
2041 String getBasePattern() {
2042 StringBuilder result = new StringBuilder();
2043 for (int i = 0; i < TYPE_LIMIT; ++i) {
2044 if (baseOriginal[i].length() != 0) result.append(baseOriginal[i]);
2046 return result.toString();
2049 DateTimeMatcher set(String pattern, FormatParser fp, boolean allowDuplicateFields) {
2050 for (int i = 0; i < TYPE_LIMIT; ++i) {
2053 baseOriginal[i] = "";
2056 for (Object obj : fp.getItems()) {
2057 if (!(obj instanceof VariableField)) {
2060 VariableField item = (VariableField)obj;
2061 String field = item.toString();
2062 if (field.charAt(0) == 'a') continue; // skip day period, special case
2063 int canonicalIndex = item.getCanonicalIndex();
2064 // if (canonicalIndex < 0) {
2065 // throw new IllegalArgumentException("Illegal field:\t"
2066 // + field + "\t in " + pattern);
2068 int[] row = types[canonicalIndex];
2069 int typeValue = row[1];
2070 if (original[typeValue].length() != 0) {
2071 if (allowDuplicateFields) {
2074 throw new IllegalArgumentException("Conflicting fields:\t"
2075 + original[typeValue] + ", " + field + "\t in " + pattern);
2077 original[typeValue] = field;
2078 char repeatChar = (char)row[0];
2079 int repeatCount = row[3];
2080 // #7930 removes hack to cap repeatCount at 3
2081 if ("GEzvQ".indexOf(repeatChar) >= 0) repeatCount = 1;
2082 baseOriginal[typeValue] = Utility.repeat(String.valueOf(repeatChar),repeatCount);
2083 int subTypeValue = row[2];
2084 if (subTypeValue > 0) subTypeValue += field.length();
2085 type[typeValue] = subTypeValue;
2093 int getFieldMask() {
2095 for (int i = 0; i < type.length; ++i) {
2096 if (type[i] != 0) result |= (1<<i);
2104 @SuppressWarnings("unused")
2105 void extractFrom(DateTimeMatcher source, int fieldMask) {
2106 for (int i = 0; i < type.length; ++i) {
2107 if ((fieldMask & (1<<i)) != 0) {
2108 type[i] = source.type[i];
2109 original[i] = source.original[i];
2117 int getDistance(DateTimeMatcher other, int includeMask, DistanceInfo distanceInfo) {
2119 distanceInfo.clear();
2120 for (int i = 0; i < type.length; ++i) {
2121 int myType = (includeMask & (1<<i)) == 0 ? 0 : type[i];
2122 int otherType = other.type[i];
2123 if (myType == otherType) continue; // identical (maybe both zero) add 0
2124 if (myType == 0) { // and other is not
2125 result += EXTRA_FIELD;
2126 distanceInfo.addExtra(i);
2127 } else if (otherType == 0) { // and mine is not
2128 result += MISSING_FIELD;
2129 distanceInfo.addMissing(i);
2131 result += Math.abs(myType - otherType); // square of mismatch
2137 public int compareTo(DateTimeMatcher that) {
2138 for (int i = 0; i < original.length; ++i) {
2139 int comp = original[i].compareTo(that.original[i]);
2140 if (comp != 0) return -comp;
2145 public boolean equals(Object other) {
2146 if (!(other instanceof DateTimeMatcher)) {
2149 DateTimeMatcher that = (DateTimeMatcher) other;
2150 for (int i = 0; i < original.length; ++i) {
2151 if (!original[i].equals(that.original[i])) return false;
2155 public int hashCode() {
2157 for (int i = 0; i < original.length; ++i) {
2158 result ^= original[i].hashCode();
2164 private static class DistanceInfo {
2165 int missingFieldMask;
2168 missingFieldMask = extraFieldMask = 0;
2173 void setTo(DistanceInfo other) {
2174 missingFieldMask = other.missingFieldMask;
2175 extraFieldMask = other.extraFieldMask;
2177 void addMissing(int field) {
2178 missingFieldMask |= (1<<field);
2180 void addExtra(int field) {
2181 extraFieldMask |= (1<<field);
2183 public String toString() {
2184 return "missingFieldMask: " + DateTimePatternGenerator.showMask(missingFieldMask)
2185 + ", extraFieldMask: " + DateTimePatternGenerator.showMask(extraFieldMask);