3 ********************************************************************************
\r
4 * Copyright (C) 2006-2009, Google, International Business Machines Corporation *
\r
5 * and others. All Rights Reserved. *
\r
6 ********************************************************************************
\r
8 package com.ibm.icu.text;
\r
10 import com.ibm.icu.impl.ICUCache;
\r
11 import com.ibm.icu.impl.ICUResourceBundle;
\r
12 import com.ibm.icu.impl.PatternTokenizer;
\r
13 import com.ibm.icu.impl.SimpleCache;
\r
14 import com.ibm.icu.impl.Utility;
\r
15 import com.ibm.icu.util.Calendar;
\r
16 import com.ibm.icu.util.Freezable;
\r
17 import com.ibm.icu.util.ULocale;
\r
18 import com.ibm.icu.util.UResourceBundle;
\r
20 import java.util.ArrayList;
\r
21 import java.util.Arrays;
\r
22 import java.util.BitSet;
\r
23 import java.util.Collection;
\r
24 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
25 //##import java.util.HashMap;
\r
27 import java.util.HashSet;
\r
28 import java.util.Iterator;
\r
29 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
31 //import java.util.LinkedHashMap;
\r
32 //import java.util.LinkedHashSet;
\r
34 import java.util.List;
\r
35 import java.util.Map;
\r
36 import java.util.Set;
\r
37 import java.util.TreeMap;
\r
40 * This class provides flexible generation of date format patterns, like
\r
41 * "yy-MM-dd". The user can build up the generator by adding successive
\r
42 * patterns. Once that is done, a query can be made using a "skeleton", which is
\r
43 * a pattern which just includes the desired fields and lengths. The generator
\r
44 * will return the "best fit" pattern corresponding to that skeleton.
\r
46 * The main method people will use is getBestPattern(String skeleton), since
\r
47 * normally this class is pre-built with data from a particular locale. However,
\r
48 * generators can be built directly from other data as well.
\r
50 * // some simple use cases
\r
51 * Date sampleDate = new Date(99, 9, 13, 23, 58, 59);
\r
52 * ULocale locale = ULocale.GERMANY;
\r
53 * TimeZone zone = TimeZone.getTimeZone("Europe/Paris");
\r
55 * // make from locale
\r
57 * DateTimePatternGenerator gen = DateTimePatternGenerator.getInstance(locale);
\r
58 * SimpleDateFormat format = new SimpleDateFormat(gen.getBestPattern("MMMddHmm"),
\r
60 * format.setTimeZone(zone);
\r
61 * assertEquals("simple format: MMMddHmm",
\r
62 * "8:58 14. Okt",
\r
63 * format.format(sampleDate));
\r
64 * // (a generator can be built from scratch, but that is not a typical use case)
\r
66 * // modify the generator by adding patterns
\r
67 * DateTimePatternGenerator.PatternInfo returnInfo = new DateTimePatternGenerator.PatternInfo();
\r
68 * gen.add("d'. von' MMMM", true, returnInfo);
\r
69 * // the returnInfo is mostly useful for debugging problem cases
\r
70 * format.applyPattern(gen.getBestPattern("MMMMddHmm"));
\r
71 * assertEquals("modified format: MMMddHmm",
\r
72 * "8:58 14. von Oktober",
\r
73 * format.format(sampleDate));
\r
75 * // get a pattern and modify it
\r
76 * format = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.FULL,
\r
77 * DateFormat.FULL, locale);
\r
78 * format.setTimeZone(zone);
\r
79 * String pattern = format.toPattern();
\r
80 * assertEquals("full-date",
\r
81 * "Donnerstag, 14. Oktober 1999 8:58 Uhr GMT+02:00",
\r
82 * format.format(sampleDate));
\r
84 * // modify it to change the zone.
\r
85 * String newPattern = gen.replaceFieldTypes(pattern, "vvvv");
\r
86 * format.applyPattern(newPattern);
\r
87 * assertEquals("full-date, modified zone",
\r
88 * "Donnerstag, 14. Oktober 1999 8:58 Uhr Frankreich",
\r
89 * format.format(sampleDate));
\r
93 public class DateTimePatternGenerator implements Freezable, Cloneable {
\r
95 //static boolean SHOW_DISTANCE = false;
\r
96 // TODO add hack to fix months for CJK, as per bug ticket 1099
\r
99 * Create empty generator, to be constructed with add(...) etc.
\r
102 public static DateTimePatternGenerator getEmptyInstance() {
\r
103 return new DateTimePatternGenerator();
\r
107 * Only for use by subclasses
\r
110 protected DateTimePatternGenerator() {
\r
114 * Construct a flexible generator according to data for a given locale.
\r
117 public static DateTimePatternGenerator getInstance() {
\r
118 return getInstance(ULocale.getDefault());
\r
122 * Construct a flexible generator according to data for a given locale.
\r
126 public static DateTimePatternGenerator getInstance(ULocale uLocale) {
\r
127 String localeKey = uLocale.toString();
\r
128 DateTimePatternGenerator result = (DateTimePatternGenerator)DTPNG_CACHE.get(localeKey);
\r
129 if (result != null) {
\r
132 result = new DateTimePatternGenerator();
\r
133 String lang = uLocale.getLanguage();
\r
134 if (lang.equals("zh") || lang.equals("ko") || lang.equals("ja")) {
\r
135 result.chineseMonthHack = true;
\r
137 PatternInfo returnInfo = new PatternInfo();
\r
138 String shortTimePattern = null;
\r
139 // first load with the ICU patterns
\r
140 for (int i = DateFormat.FULL; i <= DateFormat.SHORT; ++i) {
\r
141 SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(i, uLocale);
\r
142 result.addPattern(df.toPattern(), false, returnInfo);
\r
143 df = (SimpleDateFormat) DateFormat.getTimeInstance(i, uLocale);
\r
144 result.addPattern(df.toPattern(), false, returnInfo);
\r
145 if (i == DateFormat.SHORT) {
\r
146 // keep this pattern to populate other time field
\r
147 // combination patterns by hackTimes later in this method.
\r
148 shortTimePattern = df.toPattern();
\r
150 // use hour style in SHORT time pattern as the default
\r
151 // hour style for the locale
\r
152 FormatParser fp = new FormatParser();
\r
153 fp.set(shortTimePattern);
\r
154 List items = fp.getItems();
\r
155 for (int idx = 0; idx < items.size(); idx++) {
\r
156 Object item = items.get(idx);
\r
157 if (item instanceof VariableField) {
\r
158 VariableField fld = (VariableField)item;
\r
159 if (fld.getType() == HOUR) {
\r
160 result.defaultHourFormatChar = fld.toString().charAt(0);
\r
168 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, uLocale);
\r
169 rb = rb.getWithFallback("calendar");
\r
170 ICUResourceBundle gregorianBundle = rb.getWithFallback("gregorian");
\r
171 // CLDR item formats
\r
173 ICUResourceBundle itemBundle = gregorianBundle.getWithFallback("appendItems");
\r
174 for (int i=0; i<itemBundle.getSize(); ++i) {
\r
175 ICUResourceBundle formatBundle = (ICUResourceBundle)itemBundle.get(i);
\r
176 String formatName = itemBundle.get(i).getKey();
\r
177 String value = formatBundle.getString();
\r
178 result.setAppendItemFormat(getAppendFormatNumber(formatName), value);
\r
182 itemBundle = gregorianBundle.getWithFallback("fields");
\r
183 ICUResourceBundle fieldBundle, dnBundle;
\r
184 for (int i=0; i<TYPE_LIMIT; ++i) {
\r
185 if ( isCLDRFieldName(i) ) {
\r
186 fieldBundle = itemBundle.getWithFallback(CLDR_FIELD_NAME[i]);
\r
187 dnBundle = fieldBundle.getWithFallback("dn");
\r
188 String value = dnBundle.getString();
\r
189 //System.out.println("Field name:"+value);
\r
190 result.setAppendItemName(i, value);
\r
194 // set the AvailableFormat in CLDR
\r
196 ICUResourceBundle formatBundle = gregorianBundle.getWithFallback("availableFormats");
\r
197 //System.out.println("available format from current locale:"+uLocale.getName());
\r
198 for (int i=0; i<formatBundle.getSize(); ++i) {
\r
199 String formatKey = formatBundle.get(i).getKey();
\r
200 String formatValue = formatBundle.get(i).getString();
\r
201 //System.out.println(" availableFormat:"+formatValue);
\r
202 result.setAvailableFormat(formatKey);
\r
203 result.addPattern(formatValue, false, returnInfo);
\r
205 }catch(Exception e) {
\r
208 ULocale parentLocale=uLocale;
\r
209 while ( (parentLocale=parentLocale.getFallback()) != null) {
\r
210 ICUResourceBundle prb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, parentLocale);
\r
211 prb = prb.getWithFallback("calendar");
\r
212 ICUResourceBundle pGregorianBundle = prb.getWithFallback("gregorian");
\r
214 ICUResourceBundle formatBundle = pGregorianBundle.getWithFallback("availableFormats");
\r
215 //System.out.println("available format from parent locale:"+parentLocale.getName());
\r
216 for (int i=0; i<formatBundle.getSize(); ++i) {
\r
217 String formatKey = formatBundle.get(i).getKey();
\r
218 String formatValue = formatBundle.get(i).getString();
\r
219 //System.out.println(" availableFormat:"+formatValue);
\r
220 if (!result.isAvailableFormatSet(formatKey)) {
\r
221 result.setAvailableFormat(formatKey);
\r
222 result.addPattern(formatValue, false, returnInfo);
\r
223 //System.out.println(" availableFormat:"+formatValue);
\r
227 }catch(Exception e) {
\r
232 // assume it is always big endian (ok for CLDR right now)
\r
233 // some languages didn't add mm:ss or HH:mm, so put in a hack to compute that from the short time.
\r
234 if (shortTimePattern != null) {
\r
235 hackTimes(result, returnInfo, shortTimePattern);
\r
238 result.setDateTimeFormat(Calendar.getDateTimePattern(Calendar.getInstance(uLocale), uLocale, DateFormat.MEDIUM));
\r
240 // decimal point for seconds
\r
241 DecimalFormatSymbols dfs = new DecimalFormatSymbols(uLocale);
\r
242 result.setDecimal(String.valueOf(dfs.getDecimalSeparator()));
\r
243 DTPNG_CACHE.put(localeKey, result);
\r
247 private static void hackTimes(DateTimePatternGenerator result, PatternInfo returnInfo, String hackPattern) {
\r
248 result.fp.set(hackPattern);
\r
249 String mmss = new String();
\r
250 // to get mm:ss, we strip all but mm literal ss
\r
251 boolean gotMm = false;
\r
252 for (int i = 0; i < result.fp.items.size(); ++i) {
\r
253 Object item = result.fp.items.get(i);
\r
254 if (item instanceof String) {
\r
256 mmss += result.fp.quoteLiteral(item.toString());
\r
259 char ch = item.toString().charAt(0);
\r
263 } else if (ch == 's') {
\r
268 result.addPattern(mmss, false, returnInfo);
\r
270 } else if (gotMm || ch == 'z' || ch == 'Z' || ch == 'v' || ch == 'V') {
\r
275 // to get hh:mm, we strip (literal ss) and (literal S)
\r
276 // the easiest way to do this is to mark the stuff we want to nuke, then remove it in a second pass.
\r
277 BitSet variables = new BitSet();
\r
278 BitSet nuke = new BitSet();
\r
279 for (int i = 0; i < result.fp.items.size(); ++i) {
\r
280 Object item = result.fp.items.get(i);
\r
281 if (item instanceof VariableField) {
\r
283 char ch = item.toString().charAt(0);
\r
284 if (ch == 's' || ch == 'S') {
\r
286 for (int j = i-1; j >= 0; ++j) {
\r
287 if (variables.get(j)) break;
\r
293 String hhmm = getFilteredPattern(result.fp, nuke);
\r
294 result.addPattern(hhmm, false, returnInfo);
\r
297 private static String getFilteredPattern(FormatParser fp, BitSet nuke) {
\r
298 String result = new String();
\r
299 for (int i = 0; i < fp.items.size(); ++i) {
\r
300 if (nuke.get(i)) continue;
\r
301 Object item = fp.items.get(i);
\r
302 if (item instanceof String) {
\r
303 result += fp.quoteLiteral(item.toString());
\r
305 result += item.toString();
\r
311 /*private static int getAppendNameNumber(String string) {
\r
312 for (int i = 0; i < CLDR_FIELD_NAME.length; ++i) {
\r
313 if (CLDR_FIELD_NAME[i].equals(string)) return i;
\r
318 private static int getAppendFormatNumber(String string) {
\r
319 for (int i = 0; i < CLDR_FIELD_APPEND.length; ++i) {
\r
320 if (CLDR_FIELD_APPEND[i].equals(string)) return i;
\r
326 private static boolean isCLDRFieldName(int index) {
\r
327 if ((index<0) && (index>=TYPE_LIMIT)) {
\r
330 if (CLDR_FIELD_NAME[index].charAt(0) == '*') {
\r
340 * Return the best pattern matching the input skeleton. It is guaranteed to
\r
341 * have all of the fields in the skeleton.
\r
344 * The skeleton is a pattern containing only the variable fields.
\r
345 * For example, "MMMdd" and "mmhh" are skeletons.
\r
348 public String getBestPattern(String skeleton) {
\r
349 //if (!isComplete) complete();
\r
350 if (chineseMonthHack) {
\r
351 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
352 //## int monidx = skeleton.indexOf("MMM");
\r
353 //## if (monidx >= 0) {
\r
354 //## StringBuffer tmp = new StringBuffer(skeleton.substring(0, monidx));
\r
355 //## tmp.append("MM");
\r
357 //## while (monidx < skeleton.length()) {
\r
358 //## if (skeleton.charAt(monidx) != 'M') {
\r
363 //## tmp.append(skeleton.substring(monidx));
\r
364 //## skeleton = tmp.toString();
\r
367 skeleton = skeleton.replaceAll("MMM+", "MM");
\r
370 // if skeleton contains meta hour field 'j', then
\r
371 // replace it with the default hour format char
\r
372 skeleton = Utility.replaceAll(skeleton, "j", String.valueOf(defaultHourFormatChar));
\r
374 current.set(skeleton, fp);
\r
375 String best = getBestRaw(current, -1, _distanceInfo);
\r
376 if (_distanceInfo.missingFieldMask == 0 && _distanceInfo.extraFieldMask == 0) {
\r
377 // we have a good item. Adjust the field types
\r
378 return adjustFieldTypes(best, current, false);
\r
380 int neededFields = current.getFieldMask();
\r
381 // otherwise break up by date and time.
\r
382 String datePattern = getBestAppending(neededFields & DATE_MASK);
\r
383 String timePattern = getBestAppending(neededFields & TIME_MASK);
\r
385 if (datePattern == null) return timePattern == null ? "" : timePattern;
\r
386 if (timePattern == null) return datePattern;
\r
387 return MessageFormat.format(getDateTimeFormat(), new Object[]{timePattern, datePattern});
\r
391 * PatternInfo supplies output parameters for add(...). It is used because
\r
392 * Java doesn't have real output parameters. It is treated like a struct (eg
\r
393 * Point), so all fields are public.
\r
397 public static final class PatternInfo { // struct for return information
\r
401 public static final int OK = 0;
\r
406 public static final int BASE_CONFLICT = 1;
\r
411 public static final int CONFLICT = 2;
\r
421 public String conflictingPattern;
\r
424 * Simple constructor, since this is treated like a struct.
\r
427 public PatternInfo() {
\r
432 * Adds a pattern to the generator. If the pattern has the same skeleton as
\r
433 * an existing pattern, and the override parameter is set, then the previous
\r
434 * value is overriden. Otherwise, the previous value is retained. In either
\r
435 * case, the conflicting information is returned in PatternInfo.
\r
437 * Note that single-field patterns (like "MMM") are automatically added, and
\r
438 * don't need to be added explicitly!
\r
441 * when existing values are to be overridden use true, otherwise
\r
445 public DateTimePatternGenerator addPattern(String pattern, boolean override, PatternInfo returnInfo) {
\r
447 DateTimeMatcher matcher = new DateTimeMatcher().set(pattern, fp);
\r
448 String basePattern = matcher.getBasePattern();
\r
449 String previousPatternWithSameBase = (String)basePattern_pattern.get(basePattern);
\r
450 if (previousPatternWithSameBase != null) {
\r
451 returnInfo.status = PatternInfo.BASE_CONFLICT;
\r
452 returnInfo.conflictingPattern = previousPatternWithSameBase;
\r
453 if (!override) return this;
\r
455 String previousValue = (String)skeleton2pattern.get(matcher);
\r
456 if (previousValue != null) {
\r
457 returnInfo.status = PatternInfo.CONFLICT;
\r
458 returnInfo.conflictingPattern = previousValue;
\r
459 if (!override) return this;
\r
461 returnInfo.status = PatternInfo.OK;
\r
462 returnInfo.conflictingPattern = "";
\r
463 skeleton2pattern.put(matcher, pattern);
\r
464 basePattern_pattern.put(basePattern, pattern);
\r
469 * Utility to return a unique skeleton from a given pattern. For example,
\r
470 * both "MMM-dd" and "dd/MMM" produce the skeleton "MMMdd".
\r
473 * Input pattern, such as "dd/MMM"
\r
474 * @return skeleton, such as "MMMdd"
\r
477 public String getSkeleton(String pattern) {
\r
478 synchronized (this) { // synchronized since a getter must be thread-safe
\r
479 current.set(pattern, fp);
\r
480 return current.toString();
\r
485 * Utility to return a unique base skeleton from a given pattern. This is
\r
486 * the same as the skeleton, except that differences in length are minimized
\r
487 * so as to only preserve the difference between string and numeric form. So
\r
488 * for example, both "MMM-dd" and "d/MMM" produce the skeleton "MMMd"
\r
489 * (notice the single d).
\r
492 * Input pattern, such as "dd/MMM"
\r
493 * @return skeleton, such as "MMMdd"
\r
496 public String getBaseSkeleton(String pattern) {
\r
497 synchronized (this) { // synchronized since a getter must be thread-safe
\r
498 current.set(pattern, fp);
\r
499 return current.getBasePattern();
\r
504 * Return a list of all the skeletons (in canonical form) from this class,
\r
505 * and the patterns that they map to.
\r
508 * an output Map in which to place the mapping from skeleton to
\r
509 * pattern. If you want to see the internal order being used,
\r
510 * supply a LinkedHashMap. If the input value is null, then a
\r
511 * LinkedHashMap is allocated.
\r
513 * <i>Issue: an alternate API would be to just return a list of
\r
514 * the skeletons, and then have a separate routine to get from
\r
515 * skeleton to pattern.</i>
\r
516 * @return the input Map containing the values.
\r
519 public Map getSkeletons(Map result) {
\r
520 if (result == null) {
\r
521 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
522 //## result = new HashMap();
\r
524 // result = new LinkedHashMap();
\r
527 for (Iterator it = skeleton2pattern.keySet().iterator(); it.hasNext();) {
\r
528 DateTimeMatcher item = (DateTimeMatcher) it.next();
\r
529 String pattern = (String) skeleton2pattern.get(item);
\r
530 if (CANONICAL_SET.contains(pattern)) continue;
\r
531 result.put(item.toString(), pattern);
\r
537 * Return a list of all the base skeletons (in canonical form) from this class
\r
540 public Set getBaseSkeletons(Set result) {
\r
541 if (result == null) result = new HashSet();
\r
542 result.addAll(basePattern_pattern.keySet());
\r
547 * Adjusts the field types (width and subtype) of a pattern to match what is
\r
548 * in a skeleton. That is, if you supply a pattern like "d-M H:m", and a
\r
549 * skeleton of "MMMMddhhmm", then the input pattern is adjusted to be
\r
550 * "dd-MMMM hh:mm". This is used internally to get the best match for the
\r
551 * input skeleton, but can also be used externally.
\r
556 * @return pattern adjusted to match the skeleton fields widths and
\r
560 public String replaceFieldTypes(String pattern, String skeleton) {
\r
561 synchronized (this) { // synchronized since a getter must be thread-safe
\r
562 return adjustFieldTypes(pattern, current.set(skeleton, fp), false);
\r
567 * The date time format is a message format pattern used to compose date and
\r
568 * time patterns. The default value is "{1} {0}", where {1} will be replaced
\r
569 * by the date pattern and {0} will be replaced by the time pattern.
\r
571 * This is used when the input skeleton contains both date and time fields,
\r
572 * but there is not a close match among the added patterns. For example,
\r
573 * suppose that this object was created by adding "dd-MMM" and "hh:mm", and
\r
574 * its datetimeFormat is the default "{1} {0}". Then if the input skeleton
\r
575 * is "MMMdhmm", there is not an exact match, so the input skeleton is
\r
576 * broken up into two components "MMMd" and "hmm". There are close matches
\r
577 * for those two skeletons, so the result is put together with this pattern,
\r
578 * resulting in "d-MMM h:mm".
\r
580 * @param dateTimeFormat
\r
581 * message format pattern, where {1} will be replaced by the date
\r
582 * pattern and {0} will be replaced by the time pattern.
\r
585 public void setDateTimeFormat(String dateTimeFormat) {
\r
587 this.dateTimeFormat = dateTimeFormat;
\r
591 * Getter corresponding to setDateTimeFormat.
\r
596 public String getDateTimeFormat() {
\r
597 return dateTimeFormat;
\r
601 * The decimal value is used in formatting fractions of seconds. If the
\r
602 * skeleton contains fractional seconds, then this is used with the
\r
603 * fractional seconds. For example, suppose that the input pattern is
\r
604 * "hhmmssSSSS", and the best matching pattern internally is "H:mm:ss", and
\r
605 * the decimal string is ",". Then the resulting pattern is modified to be
\r
611 public void setDecimal(String decimal) {
\r
613 this.decimal = decimal;
\r
617 * Getter corresponding to setDecimal.
\r
618 * @return string corresponding to the decimal point
\r
621 public String getDecimal() {
\r
626 * Redundant patterns are those which if removed, make no difference in the
\r
627 * resulting getBestPattern values. This method returns a list of them, to
\r
628 * help check the consistency of the patterns used to build this generator.
\r
631 * stores the redundant patterns that are removed. To get these
\r
632 * in internal order, supply a LinkedHashSet. If null, a
\r
633 * collection is allocated.
\r
634 * @return the collection with added elements.
\r
636 * @deprecated This API is ICU internal only.
\r
638 public Collection getRedundants(Collection output) {
\r
639 synchronized (this) { // synchronized since a getter must be thread-safe
\r
640 if (output == null) {
\r
641 //#if defined(FOUNDATION10) || defined(J2SE13)
\r
642 //## output = new HashSet();
\r
644 // output = new LinkedHashSet();
\r
647 for (Iterator it = skeleton2pattern.keySet().iterator(); it.hasNext();) {
\r
648 DateTimeMatcher cur = (DateTimeMatcher) it.next();
\r
649 String pattern = (String) skeleton2pattern.get(cur);
\r
650 if (CANONICAL_SET.contains(pattern)) continue;
\r
652 String trial = getBestPattern(cur.toString());
\r
653 if (trial.equals(pattern)) {
\r
654 output.add(pattern);
\r
657 if (false) { // ordered
\r
658 DateTimePatternGenerator results = new DateTimePatternGenerator();
\r
659 PatternInfo pinfo = new PatternInfo();
\r
660 for (Iterator it = skeleton2pattern.keySet().iterator(); it.hasNext();) {
\r
661 DateTimeMatcher cur = (DateTimeMatcher) it.next();
\r
662 String pattern = (String) skeleton2pattern.get(cur);
\r
663 if (CANONICAL_SET.contains(pattern)) continue;
\r
664 //skipMatcher = current;
\r
665 String trial = results.getBestPattern(cur.toString());
\r
666 if (trial.equals(pattern)) {
\r
667 output.add(pattern);
\r
669 results.addPattern(pattern, false, pinfo);
\r
677 // Field numbers, used for AppendItem functions
\r
682 static final public int ERA = 0;
\r
687 static final public int YEAR = 1;
\r
692 static final public int QUARTER = 2;
\r
697 static final public int MONTH = 3;
\r
702 static final public int WEEK_OF_YEAR = 4;
\r
707 static final public int WEEK_OF_MONTH = 5;
\r
712 static final public int WEEKDAY = 6;
\r
717 static final public int DAY = 7;
\r
722 static final public int DAY_OF_YEAR = 8;
\r
727 static final public int DAY_OF_WEEK_IN_MONTH = 9;
\r
732 static final public int DAYPERIOD = 10;
\r
737 static final public int HOUR = 11;
\r
742 static final public int MINUTE = 12;
\r
747 static final public int SECOND = 13;
\r
752 static final public int FRACTIONAL_SECOND = 14;
\r
757 static final public int ZONE = 15;
\r
762 static final public int TYPE_LIMIT = 16;
\r
765 * An AppendItem format is a pattern used to append a field if there is no
\r
766 * good match. For example, suppose that the input skeleton is "GyyyyMMMd",
\r
767 * and there is no matching pattern internally, but there is a pattern
\r
768 * matching "yyyyMMMd", say "d-MM-yyyy". Then that pattern is used, plus the
\r
769 * G. The way these two are conjoined is by using the AppendItemFormat for G
\r
770 * (era). So if that value is, say "{0}, {1}" then the final resulting
\r
771 * pattern is "d-MM-yyyy, G".
\r
773 * There are actually three available variables: {0} is the pattern so far,
\r
774 * {1} is the element we are adding, and {2} is the name of the element.
\r
776 * This reflects the way that the CLDR data is organized.
\r
781 * pattern, such as "{0}, {1}"
\r
784 public void setAppendItemFormat(int field, String value) {
\r
786 appendItemFormats[field] = value;
\r
790 * Getter corresponding to setAppendItemFormats. Values below 0 or at or
\r
791 * above TYPE_LIMIT are illegal arguments.
\r
794 * @return append pattern for field
\r
797 public String getAppendItemFormat(int field) {
\r
798 return appendItemFormats[field];
\r
802 * Sets the names of fields, eg "era" in English for ERA. These are only
\r
803 * used if the corresponding AppendItemFormat is used, and if it contains a
\r
806 * This reflects the way that the CLDR data is organized.
\r
812 public void setAppendItemName(int field, String value) {
\r
814 appendItemNames[field] = value;
\r
818 * Getter corresponding to setAppendItemNames. Values below 0 or at or above
\r
819 * TYPE_LIMIT are illegal arguments.
\r
822 * @return name for field
\r
825 public String getAppendItemName(int field) {
\r
826 return appendItemNames[field];
\r
830 * Determines whether a skeleton contains a single field
\r
833 * @return true or not
\r
835 * @deprecated This API is ICU internal only.
\r
837 public static boolean isSingleField(String skeleton) {
\r
838 char first = skeleton.charAt(0);
\r
839 for (int i = 1; i < skeleton.length(); ++i) {
\r
840 if (skeleton.charAt(i) != first) return false;
\r
846 * Add key to HashSet cldrAvailableFormatKeys.
\r
848 * @param key of the availableFormats in CLDR
\r
851 private void setAvailableFormat(String key) {
\r
853 cldrAvailableFormatKeys.add(key);
\r
857 * This function checks the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
\r
858 * has been added to DateTimePatternGenerator.
\r
859 * The function is to avoid the duplicate availableFomats added to
\r
860 * the pattern map from parent locales.
\r
862 * @param key of the availableFormatMask in CLDR
\r
863 * @return TRUE if the corresponding slot of CLDR_AVAIL_FORMAT_KEY[]
\r
864 * has been added to DateTimePatternGenerator.
\r
867 private boolean isAvailableFormatSet(String key) {
\r
868 return cldrAvailableFormatKeys.contains(key);
\r
872 * Boilerplate for Freezable
\r
875 public boolean isFrozen() {
\r
880 * Boilerplate for Freezable
\r
883 public Object freeze() {
\r
889 * Boilerplate for Freezable
\r
892 public Object cloneAsThawed() {
\r
893 DateTimePatternGenerator result = (DateTimePatternGenerator) (this.clone());
\r
902 public Object clone() {
\r
904 DateTimePatternGenerator result = (DateTimePatternGenerator) (super.clone());
\r
905 result.skeleton2pattern = (TreeMap) skeleton2pattern.clone();
\r
906 result.basePattern_pattern = (TreeMap) basePattern_pattern.clone();
\r
907 result.appendItemFormats = (String[]) appendItemFormats.clone();
\r
908 result.appendItemNames = (String[]) appendItemNames.clone();
\r
909 result.current = new DateTimeMatcher();
\r
910 result.fp = new FormatParser();
\r
911 result._distanceInfo = new DistanceInfo();
\r
913 result.frozen = false;
\r
915 } catch (CloneNotSupportedException e) {
\r
916 throw new IllegalArgumentException("Internal Error");
\r
921 * Utility class for FormatParser. Immutable class that is only used to mark
\r
922 * the difference between a variable field and a literal string. Each
\r
923 * variable field must consist of 1 to n variable characters, representing
\r
924 * date format fields. For example, "VVVV" is valid while "V4" is not, nor
\r
928 * @deprecated This API is ICU internal only.
\r
930 public static class VariableField {
\r
931 private final String string;
\r
932 private final int canonicalIndex;
\r
935 * Create a variable field: equivalent to VariableField(string,false);
\r
938 * @deprecated This API is ICU internal only.
\r
940 public VariableField(String string) {
\r
941 this(string, false);
\r
944 * Create a variable field
\r
946 * @param strict TODO
\r
947 * @throws IllegalArgumentException if the variable field is not valid.
\r
949 * @deprecated This API is ICU internal only.
\r
951 public VariableField(String string, boolean strict) {
\r
952 canonicalIndex = DateTimePatternGenerator.getCanonicalIndex(string, strict);
\r
953 if (canonicalIndex < 0) {
\r
954 throw new IllegalArgumentException("Illegal datetime field:\t"
\r
957 this.string = string;
\r
961 * Get the main type of this variable. These types are ERA, QUARTER,
\r
962 * MONTH, DAY, WEEK_OF_YEAR, WEEK_OF_MONTH, WEEKDAY, DAY, DAYPERIOD
\r
963 * (am/pm), HOUR, MINUTE, SECOND,FRACTIONAL_SECOND, ZONE.
\r
964 * @return main type.
\r
966 * @deprecated This API is ICU internal only.
\r
968 public int getType() {
\r
969 return types[canonicalIndex][1];
\r
975 private int getCanonicalIndex() {
\r
976 return canonicalIndex;
\r
980 * Get the string represented by this variable.
\r
982 * @deprecated This API is ICU internal only.
\r
984 public String toString() {
\r
990 * This class provides mechanisms for parsing a SimpleDateFormat pattern
\r
991 * or generating a new pattern, while handling the quoting. It represents
\r
992 * the result of the parse as a list of items, where each item is either a
\r
993 * literal string or a variable field. When parsing It can be used to find
\r
994 * out which variable fields are in a date format, and in what order, such
\r
995 * as for presentation in a UI as separate text entry fields. It can also be
\r
996 * used to construct new SimpleDateFormats.
\r
999 public boolean containsZone(String pattern) {
\r
1000 for (Iterator it = formatParser.set(pattern).getItems().iterator(); it.hasNext();) {
\r
1001 Object item = it.next();
\r
1002 if (item instanceof VariableField) {
\r
1003 VariableField variableField = (VariableField) item;
\r
1004 if (variableField.getType() == DateTimePatternGenerator.ZONE) {
\r
1013 * @deprecated This API is ICU internal only.
\r
1015 static public class FormatParser {
\r
1016 private transient PatternTokenizer tokenizer = new PatternTokenizer()
\r
1017 .setSyntaxCharacters(new UnicodeSet("[a-zA-Z]"))
\r
1018 .setExtraQuotingCharacters(new UnicodeSet("[[[:script=Latn:][:script=Cyrl:]]&[[:L:][:M:]]]"))
\r
1019 //.setEscapeCharacters(new UnicodeSet("[^\\u0020-\\u007E]")) // WARNING: DateFormat doesn't accept \\uXXXX
\r
1020 .setUsingQuote(true);
\r
1021 private List items = new ArrayList();
\r
1024 * Construct an empty date format parser, to which strings and variables can be added with set(...).
\r
1026 * @deprecated This API is ICU internal only.
\r
1028 public FormatParser() {
\r
1032 * Parses the string into a list of items.
\r
1034 * @return this, for chaining
\r
1036 * @deprecated This API is ICU internal only.
\r
1038 final public FormatParser set(String string) {
\r
1039 return set(string, false);
\r
1043 * Parses the string into a list of items, taking into account all of the quoting that may be going on.
\r
1045 * @param strict If true, then only allows exactly those lengths specified by CLDR for variables. For example, "hh:mm aa" would throw an exception.
\r
1046 * @return this, for chaining
\r
1048 * @deprecated This API is ICU internal only.
\r
1050 public FormatParser set(String string, boolean strict) {
\r
1052 if (string.length() == 0) return this;
\r
1053 tokenizer.setPattern(string);
\r
1054 StringBuffer buffer = new StringBuffer();
\r
1055 StringBuffer variable = new StringBuffer();
\r
1057 buffer.setLength(0);
\r
1058 int status = tokenizer.next(buffer);
\r
1059 if (status == PatternTokenizer.DONE) break;
\r
1060 if (status == PatternTokenizer.SYNTAX) {
\r
1061 if (variable.length() != 0 && buffer.charAt(0) != variable.charAt(0)) {
\r
1062 addVariable(variable, false);
\r
1064 variable.append(buffer);
\r
1066 addVariable(variable, false);
\r
1067 items.add(buffer.toString());
\r
1070 addVariable(variable, false);
\r
1074 private void addVariable(StringBuffer variable, boolean strict) {
\r
1075 if (variable.length() != 0) {
\r
1076 items.add(new VariableField(variable.toString(), strict));
\r
1077 variable.setLength(0);
\r
1081 // /** Private method. Return a collection of fields. These will be a mixture of literal Strings and VariableFields. Any "a" variable field is removed.
\r
1082 // * @param output List to append the items to. If null, is allocated as an ArrayList.
\r
1085 // private List getVariableFields(List output) {
\r
1086 // if (output == null) output = new ArrayList();
\r
1088 // for (Iterator it = items.iterator(); it.hasNext();) {
\r
1089 // Object item = it.next();
\r
1090 // if (item instanceof VariableField) {
\r
1091 // String s = item.toString();
\r
1092 // switch(s.charAt(0)) {
\r
1093 // //case 'Q': continue main; // HACK
\r
1094 // case 'a': continue main; // remove
\r
1096 // output.add(item);
\r
1099 // //System.out.println(output);
\r
1104 // * Produce a string which concatenates all the variables. That is, it is the logically the same as the input with all literals removed.
\r
1105 // * @return a string which is a concatenation of all the variable fields
\r
1107 // * @deprecated This API is ICU internal only.
\r
1109 // public String getVariableFieldString() {
\r
1110 // List list = getVariableFields(null);
\r
1111 // StringBuffer result = new StringBuffer();
\r
1112 // for (Iterator it = list.iterator(); it.hasNext();) {
\r
1113 // String item = it.next().toString();
\r
1114 // result.append(item);
\r
1116 // return result.toString();
\r
1120 * 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:
\r
1122 * VariableField: dd
\r
1124 * VariableField: MM
\r
1126 * The list is modifiable, so you can add any strings or variables to it, or remove any items.
\r
1127 * @return modifiable list of items.
\r
1129 * @deprecated This API is ICU internal only.
\r
1131 public List getItems() {
\r
1135 /** 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().
\r
1136 * @return printable output string
\r
1138 * @deprecated This API is ICU internal only.
\r
1140 public String toString() {
\r
1141 return toString(0, items.size());
\r
1145 * 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().
\r
1146 * @param start item to start from
\r
1147 * @param limit last item +1
\r
1148 * @return printable output string
\r
1150 * @deprecated This API is ICU internal only.
\r
1152 public String toString(int start, int limit) {
\r
1153 StringBuffer result = new StringBuffer();
\r
1154 for (int i = start; i < limit; ++i) {
\r
1155 Object item = items.get(i);
\r
1156 if (item instanceof String) {
\r
1157 String itemString = (String) item;
\r
1158 result.append(tokenizer.quoteLiteral(itemString));
\r
1160 result.append(items.get(i).toString());
\r
1163 return result.toString();
\r
1167 * 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.
\r
1168 * @return true or false
\r
1170 * @deprecated This API is ICU internal only.
\r
1172 public boolean hasDateAndTimeFields() {
\r
1173 int foundMask = 0;
\r
1174 for (Iterator it = items.iterator(); it.hasNext();) {
\r
1175 Object item = it.next();
\r
1176 if (item instanceof VariableField) {
\r
1177 int type = ((VariableField)item).getType();
\r
1178 foundMask |= 1 << type;
\r
1181 boolean isDate = (foundMask & DATE_MASK) != 0;
\r
1182 boolean isTime = (foundMask & TIME_MASK) != 0;
\r
1183 return isDate && isTime;
\r
1187 // * Internal routine
\r
1189 // * @param result
\r
1192 // * @deprecated This API is ICU internal only.
\r
1194 // public List getAutoPatterns(String value, List result) {
\r
1195 // if (result == null) result = new ArrayList();
\r
1196 // int fieldCount = 0;
\r
1197 // int minField = Integer.MAX_VALUE;
\r
1198 // int maxField = Integer.MIN_VALUE;
\r
1199 // for (Iterator it = items.iterator(); it.hasNext();) {
\r
1200 // Object item = it.next();
\r
1201 // if (item instanceof VariableField) {
\r
1203 // int type = ((VariableField)item).getType();
\r
1204 // if (minField > type) minField = type;
\r
1205 // if (maxField < type) maxField = type;
\r
1206 // if (type == ZONE || type == DAYPERIOD || type == WEEKDAY) return result; // skip anything with zones
\r
1208 // } catch (Exception e) {
\r
1209 // return result; // if there are any funny fields, return
\r
1213 // if (fieldCount < 3) return result; // skip
\r
1214 // // trim from start
\r
1215 // // trim first field IF there are no letters around it
\r
1216 // // and it is either the min or the max field
\r
1217 // // first field is either 0 or 1
\r
1218 // for (int i = 0; i < items.size(); ++i) {
\r
1219 // Object item = items.get(i);
\r
1220 // if (item instanceof VariableField) {
\r
1221 // int type = ((VariableField)item).getType();
\r
1222 // if (type != minField && type != maxField) break;
\r
1225 // Object previousItem = items.get(0);
\r
1226 // if (alpha.containsSome(previousItem.toString())) break;
\r
1228 // int start = i+1;
\r
1229 // if (start < items.size()) {
\r
1230 // Object nextItem = items.get(start);
\r
1231 // if (nextItem instanceof String) {
\r
1232 // if (alpha.containsSome(nextItem.toString())) break;
\r
1233 // start++; // otherwise skip over string
\r
1236 // result.add(toString(start, items.size()));
\r
1240 // // now trim from end
\r
1241 // for (int i = items.size()-1; i >= 0; --i) {
\r
1242 // Object item = items.get(i);
\r
1243 // if (item instanceof VariableField) {
\r
1244 // int type = ((VariableField)item).getType();
\r
1245 // if (type != minField && type != maxField) break;
\r
1246 // if (i < items.size() - 1) {
\r
1247 // Object previousItem = items.get(items.size() - 1);
\r
1248 // if (alpha.containsSome(previousItem.toString())) break;
\r
1252 // Object nextItem = items.get(end);
\r
1253 // if (nextItem instanceof String) {
\r
1254 // if (alpha.containsSome(nextItem.toString())) break;
\r
1255 // end--; // otherwise skip over string
\r
1258 // result.add(toString(0, end+1));
\r
1266 // private static UnicodeSet alpha = new UnicodeSet("[:alphabetic:]");
\r
1268 // private int getType(Object item) {
\r
1269 // String s = item.toString();
\r
1270 // int canonicalIndex = getCanonicalIndex(s);
\r
1271 // if (canonicalIndex < 0) {
\r
1272 // throw new IllegalArgumentException("Illegal field:\t"
\r
1275 // int type = types[canonicalIndex][1];
\r
1280 * 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' ".
\r
1282 * @return string with quoted literals
\r
1284 * @deprecated This API is ICU internal only.
\r
1286 public Object quoteLiteral(String string) {
\r
1287 return tokenizer.quoteLiteral(string);
\r
1291 // ========= PRIVATES ============
\r
1293 private TreeMap skeleton2pattern = new TreeMap(); // items are in priority order
\r
1294 private TreeMap basePattern_pattern = new TreeMap(); // items are in priority order
\r
1295 private String decimal = "?";
\r
1296 private String dateTimeFormat = "{1} {0}";
\r
1297 private String[] appendItemFormats = new String[TYPE_LIMIT];
\r
1298 private String[] appendItemNames = new String[TYPE_LIMIT];
\r
1300 for (int i = 0; i < TYPE_LIMIT; ++i) {
\r
1301 appendItemFormats[i] = "{0} \u251C{2}: {1}\u2524";
\r
1302 appendItemNames[i] = "F" + i;
\r
1305 private char defaultHourFormatChar = 'H';
\r
1306 private boolean chineseMonthHack = false;
\r
1307 //private boolean isComplete = false;
\r
1308 private boolean frozen = false;
\r
1310 private transient DateTimeMatcher current = new DateTimeMatcher();
\r
1311 private transient FormatParser fp = new FormatParser();
\r
1312 private transient DistanceInfo _distanceInfo = new DistanceInfo();
\r
1313 private transient DateTimeMatcher skipMatcher = null; // only used temporarily, for internal purposes
\r
1316 private static final int FRACTIONAL_MASK = 1<<FRACTIONAL_SECOND;
\r
1317 private static final int SECOND_AND_FRACTIONAL_MASK = (1<<SECOND) | (1<<FRACTIONAL_SECOND);
\r
1319 // Cache for DateTimePatternGenerator
\r
1320 private static ICUCache DTPNG_CACHE = new SimpleCache();
\r
1322 private void checkFrozen() {
\r
1324 throw new UnsupportedOperationException("Attempt to modify frozen object");
\r
1329 * 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.
\r
1330 * If we fail to find a complete skeleton, we compose in a loop until we have all the fields.
\r
1332 private String getBestAppending(int missingFields) {
\r
1333 String resultPattern = null;
\r
1334 if (missingFields != 0) {
\r
1335 resultPattern = getBestRaw(current, missingFields, _distanceInfo);
\r
1336 resultPattern = adjustFieldTypes(resultPattern, current, false);
\r
1338 while (_distanceInfo.missingFieldMask != 0) { // precondition: EVERY single field must work!
\r
1340 // special hack for SSS. If we are missing SSS, and we had ss but found it, replace the s field according to the
\r
1341 // number separator
\r
1342 if ((_distanceInfo.missingFieldMask & SECOND_AND_FRACTIONAL_MASK) == FRACTIONAL_MASK
\r
1343 && (missingFields & SECOND_AND_FRACTIONAL_MASK) == SECOND_AND_FRACTIONAL_MASK) {
\r
1344 resultPattern = adjustFieldTypes(resultPattern, current, true);
\r
1345 _distanceInfo.missingFieldMask &= ~FRACTIONAL_MASK; // remove bit
\r
1349 int startingMask = _distanceInfo.missingFieldMask;
\r
1350 String temp = getBestRaw(current, _distanceInfo.missingFieldMask, _distanceInfo);
\r
1351 temp = adjustFieldTypes(temp, current, false);
\r
1352 int foundMask = startingMask & ~_distanceInfo.missingFieldMask;
\r
1353 int topField = getTopBitNumber(foundMask);
\r
1354 resultPattern = MessageFormat.format(getAppendFormat(topField), new Object[]{resultPattern, temp, getAppendName(topField)});
\r
1357 return resultPattern;
\r
1360 private String getAppendName(int foundMask) {
\r
1361 return "'" + appendItemNames[foundMask] + "'";
\r
1363 private String getAppendFormat(int foundMask) {
\r
1364 return appendItemFormats[foundMask];
\r
1368 // * @param current2
\r
1371 // private String adjustSeconds(DateTimeMatcher current2) {
\r
1372 // // TODO Auto-generated method stub
\r
1377 * @param foundMask
\r
1380 private int getTopBitNumber(int foundMask) {
\r
1382 while (foundMask != 0) {
\r
1392 private void complete() {
\r
1393 PatternInfo patternInfo = new PatternInfo();
\r
1394 // make sure that every valid field occurs once, with a "default" length
\r
1395 for (int i = 0; i < CANONICAL_ITEMS.length; ++i) {
\r
1396 //char c = (char)types[i][0];
\r
1397 addPattern(String.valueOf(CANONICAL_ITEMS[i]), false, patternInfo);
\r
1399 //isComplete = true;
\r
1408 private String getBestRaw(DateTimeMatcher source, int includeMask, DistanceInfo missingFields) {
\r
1409 // if (SHOW_DISTANCE) System.out.println("Searching for: " + source.pattern
\r
1410 // + ", mask: " + showMask(includeMask));
\r
1411 int bestDistance = Integer.MAX_VALUE;
\r
1412 String bestPattern = "";
\r
1413 DistanceInfo tempInfo = new DistanceInfo();
\r
1414 for (Iterator it = skeleton2pattern.keySet().iterator(); it.hasNext();) {
\r
1415 DateTimeMatcher trial = (DateTimeMatcher) it.next();
\r
1416 if (trial.equals(skipMatcher)) continue;
\r
1417 int distance = source.getDistance(trial, includeMask, tempInfo);
\r
1418 // if (SHOW_DISTANCE) System.out.println("\tDistance: " + trial.pattern + ":\t"
\r
1419 // + distance + ",\tmissing fields: " + tempInfo);
\r
1420 if (distance < bestDistance) {
\r
1421 bestDistance = distance;
\r
1422 bestPattern = (String) skeleton2pattern.get(trial);
\r
1423 missingFields.setTo(tempInfo);
\r
1424 if (distance == 0) break;
\r
1427 return bestPattern;
\r
1431 * @param fixFractionalSeconds TODO
\r
1434 private String adjustFieldTypes(String pattern, DateTimeMatcher inputRequest, boolean fixFractionalSeconds) {
\r
1436 StringBuffer newPattern = new StringBuffer();
\r
1437 for (Iterator it = fp.getItems().iterator(); it.hasNext();) {
\r
1438 Object item = it.next();
\r
1439 if (item instanceof String) {
\r
1440 newPattern.append(fp.quoteLiteral((String)item));
\r
1442 final VariableField variableField = (VariableField) item;
\r
1443 String field = variableField.toString();
\r
1444 // int canonicalIndex = getCanonicalIndex(field, true);
\r
1445 // if (canonicalIndex < 0) {
\r
1446 // continue; // don't adjust
\r
1448 // int type = types[canonicalIndex][1];
\r
1449 int type = variableField.getType();
\r
1451 if (fixFractionalSeconds && type == SECOND) {
\r
1452 String newField = inputRequest.original[FRACTIONAL_SECOND];
\r
1453 field = field + decimal + newField;
\r
1454 } else if (inputRequest.type[type] != 0) {
\r
1455 String newField = inputRequest.original[type];
\r
1456 // normally we just replace the field. However HOUR is special; we only change the length
\r
1457 if (type != HOUR) {
\r
1459 } else if (field.length() != newField.length()){
\r
1460 char c = field.charAt(0);
\r
1462 for (int i = newField.length(); i > 0; --i) field += c;
\r
1465 newPattern.append(field);
\r
1468 //if (SHOW_DISTANCE) System.out.println("\tRaw: " + pattern);
\r
1469 return newPattern.toString();
\r
1472 // public static String repeat(String s, int count) {
\r
1473 // StringBuffer result = new StringBuffer();
\r
1474 // for (int i = 0; i < count; ++i) {
\r
1475 // result.append(s);
\r
1477 // return result.toString();
\r
1481 * internal routine
\r
1483 * @return field value
\r
1485 * @deprecated This API is ICU internal only.
\r
1487 public String getFields(String pattern) {
\r
1489 StringBuffer newPattern = new StringBuffer();
\r
1490 for (Iterator it = fp.getItems().iterator(); it.hasNext();) {
\r
1491 Object item = it.next();
\r
1492 if (item instanceof String) {
\r
1493 newPattern.append(fp.quoteLiteral((String)item));
\r
1495 newPattern.append("{" + getName(item.toString()) + "}");
\r
1498 return newPattern.toString();
\r
1501 private static String showMask(int mask) {
\r
1502 String result = "";
\r
1503 for (int i = 0; i < TYPE_LIMIT; ++i) {
\r
1504 if ((mask & (1<<i)) == 0) continue;
\r
1505 if (result.length() != 0) result += " | ";
\r
1506 result += FIELD_NAME[i] + " ";
\r
1511 static private String[] CLDR_FIELD_APPEND = {
\r
1512 "Era", "Year", "Quarter", "Month", "Week", "*", "Day-Of-Week",
\r
1513 "Day", "*", "*", "*",
\r
1514 "Hour", "Minute", "Second", "*", "Timezone"
\r
1517 static private String[] CLDR_FIELD_NAME = {
\r
1518 "era", "year", "*", "month", "week", "*", "weekday",
\r
1519 "day", "*", "*", "dayperiod",
\r
1520 "hour", "minute", "second", "*", "zone"
\r
1523 static private String[] FIELD_NAME = {
\r
1524 "Era", "Year", "Quarter", "Month", "Week_in_Year", "Week_in_Month", "Weekday",
\r
1525 "Day", "Day_Of_Year", "Day_of_Week_in_Month", "Dayperiod",
\r
1526 "Hour", "Minute", "Second", "Fractional_Second", "Zone"
\r
1530 static private String[] CANONICAL_ITEMS = {
\r
1531 "G", "y", "Q", "M", "w", "W", "e",
\r
1533 "H", "m", "s", "S", "v"
\r
1536 static private Set CANONICAL_SET = new HashSet(Arrays.asList(CANONICAL_ITEMS));
\r
1537 private Set cldrAvailableFormatKeys = new HashSet(20);
\r
1539 static final private int
\r
1540 DATE_MASK = (1<<DAYPERIOD) - 1,
\r
1541 TIME_MASK = (1<<TYPE_LIMIT) - 1 - DATE_MASK;
\r
1543 static final private int // numbers are chosen to express 'distance'
\r
1550 EXTRA_FIELD = 0x10000,
\r
1551 MISSING_FIELD = 0x1000;
\r
1554 static private String getName(String s) {
\r
1555 int i = getCanonicalIndex(s, true);
\r
1556 String name = FIELD_NAME[types[i][1]];
\r
1557 int subtype = types[i][2];
\r
1558 boolean string = subtype < 0;
\r
1559 if (string) subtype = -subtype;
\r
1560 if (subtype < 0) name += ":S";
\r
1561 else name += ":N";
\r
1566 * Get the canonical index, or return -1 if illegal.
\r
1568 * @param strict TODO
\r
1571 static private int getCanonicalIndex(String s, boolean strict) {
\r
1572 int len = s.length();
\r
1576 int ch = s.charAt(0);
\r
1577 // verify that all are the same character
\r
1578 for (int i = 1; i < len; ++i) {
\r
1579 if (s.charAt(i) != ch) {
\r
1584 for (int i = 0; i < types.length; ++i) {
\r
1585 int[] row = types[i];
\r
1586 if (row[0] != ch) continue;
\r
1588 if (row[3] > len) continue;
\r
1589 if (row[row.length-1] < len) continue;
\r
1592 return strict ? -1 : bestRow;
\r
1595 static private int[][] types = {
\r
1596 // the order here makes a difference only when searching for single field.
\r
1598 // pattern character, main type, weight, min length, weight
\r
1599 {'G', ERA, SHORT, 1, 3},
\r
1600 {'G', ERA, LONG, 4},
\r
1602 {'y', YEAR, NUMERIC, 1, 20},
\r
1603 {'Y', YEAR, NUMERIC + DELTA, 1, 20},
\r
1604 {'u', YEAR, NUMERIC + 2*DELTA, 1, 20},
\r
1606 {'Q', QUARTER, NUMERIC, 1, 2},
\r
1607 {'Q', QUARTER, SHORT, 3},
\r
1608 {'Q', QUARTER, LONG, 4},
\r
1610 {'q', QUARTER, NUMERIC + DELTA, 1, 2},
\r
1611 {'q', QUARTER, SHORT + DELTA, 3},
\r
1612 {'q', QUARTER, LONG + DELTA, 4},
\r
1614 {'M', MONTH, NUMERIC, 1, 2},
\r
1615 {'M', MONTH, SHORT, 3},
\r
1616 {'M', MONTH, LONG, 4},
\r
1617 {'M', MONTH, NARROW, 5},
\r
1618 {'L', MONTH, NUMERIC + DELTA, 1, 2},
\r
1619 {'L', MONTH, SHORT - DELTA, 3},
\r
1620 {'L', MONTH, LONG - DELTA, 4},
\r
1621 {'L', MONTH, NARROW - DELTA, 5},
\r
1623 {'w', WEEK_OF_YEAR, NUMERIC, 1, 2},
\r
1624 {'W', WEEK_OF_MONTH, NUMERIC + DELTA, 1},
\r
1626 {'e', WEEKDAY, NUMERIC + DELTA, 1, 2},
\r
1627 {'e', WEEKDAY, SHORT - DELTA, 3},
\r
1628 {'e', WEEKDAY, LONG - DELTA, 4},
\r
1629 {'e', WEEKDAY, NARROW - DELTA, 5},
\r
1630 {'E', WEEKDAY, SHORT, 1, 3},
\r
1631 {'E', WEEKDAY, LONG, 4},
\r
1632 {'E', WEEKDAY, NARROW, 5},
\r
1633 {'c', WEEKDAY, NUMERIC + 2*DELTA, 1, 2},
\r
1634 {'c', WEEKDAY, SHORT - 2*DELTA, 3},
\r
1635 {'c', WEEKDAY, LONG - 2*DELTA, 4},
\r
1636 {'c', WEEKDAY, NARROW - 2*DELTA, 5},
\r
1638 {'d', DAY, NUMERIC, 1, 2},
\r
1639 {'D', DAY_OF_YEAR, NUMERIC + DELTA, 1, 3},
\r
1640 {'F', DAY_OF_WEEK_IN_MONTH, NUMERIC + 2*DELTA, 1},
\r
1641 {'g', DAY, NUMERIC + 3*DELTA, 1, 20}, // really internal use, so we don't care
\r
1643 {'a', DAYPERIOD, SHORT, 1},
\r
1645 {'H', HOUR, NUMERIC + 10*DELTA, 1, 2}, // 24 hour
\r
1646 {'k', HOUR, NUMERIC + 11*DELTA, 1, 2},
\r
1647 {'h', HOUR, NUMERIC, 1, 2}, // 12 hour
\r
1648 {'K', HOUR, NUMERIC + DELTA, 1, 2},
\r
1650 {'m', MINUTE, NUMERIC, 1, 2},
\r
1652 {'s', SECOND, NUMERIC, 1, 2},
\r
1653 {'S', FRACTIONAL_SECOND, NUMERIC + DELTA, 1, 1000},
\r
1654 {'A', SECOND, NUMERIC + 2*DELTA, 1, 1000},
\r
1656 {'v', ZONE, SHORT - 2*DELTA, 1},
\r
1657 {'v', ZONE, LONG - 2*DELTA, 4},
\r
1658 {'z', ZONE, SHORT, 1, 3},
\r
1659 {'z', ZONE, LONG, 4},
\r
1660 {'Z', ZONE, SHORT - DELTA, 1, 3},
\r
1661 {'Z', ZONE, LONG - DELTA, 4},
\r
1662 {'V', ZONE, SHORT - DELTA, 1, 3},
\r
1663 {'V', ZONE, LONG - DELTA, 4},
\r
1666 private static class DateTimeMatcher implements Comparable {
\r
1667 //private String pattern = null;
\r
1668 private int[] type = new int[TYPE_LIMIT];
\r
1669 private String[] original = new String[TYPE_LIMIT];
\r
1670 private String[] baseOriginal = new String[TYPE_LIMIT];
\r
1672 // just for testing; fix to make multi-threaded later
\r
1673 // private static FormatParser fp = new FormatParser();
\r
1675 public String toString() {
\r
1676 StringBuffer result = new StringBuffer();
\r
1677 for (int i = 0; i < TYPE_LIMIT; ++i) {
\r
1678 if (original[i].length() != 0) result.append(original[i]);
\r
1680 return result.toString();
\r
1683 String getBasePattern() {
\r
1684 StringBuffer result = new StringBuffer();
\r
1685 for (int i = 0; i < TYPE_LIMIT; ++i) {
\r
1686 if (baseOriginal[i].length() != 0) result.append(baseOriginal[i]);
\r
1688 return result.toString();
\r
1691 DateTimeMatcher set(String pattern, FormatParser fp) {
\r
1692 for (int i = 0; i < TYPE_LIMIT; ++i) {
\r
1695 baseOriginal[i] = "";
\r
1698 for (Iterator it = fp.getItems().iterator(); it.hasNext();) {
\r
1699 Object obj = it.next();
\r
1700 if (!(obj instanceof VariableField)) {
\r
1703 VariableField item = (VariableField)obj;
\r
1704 String field = item.toString();
\r
1705 if (field.charAt(0) == 'a') continue; // skip day period, special case
\r
1706 int canonicalIndex = item.getCanonicalIndex();
\r
1707 // if (canonicalIndex < 0) {
\r
1708 // throw new IllegalArgumentException("Illegal field:\t"
\r
1709 // + field + "\t in " + pattern);
\r
1711 int[] row = types[canonicalIndex];
\r
1712 int typeValue = row[1];
\r
1713 if (original[typeValue].length() != 0) {
\r
1714 throw new IllegalArgumentException("Conflicting fields:\t"
\r
1715 + original[typeValue] + ", " + field + "\t in " + pattern);
\r
1717 original[typeValue] = field;
\r
1718 char repeatChar = (char)row[0];
\r
1719 int repeatCount = row[3];
\r
1720 if (repeatCount > 3) repeatCount = 3; // hack to discard differences
\r
1721 if ("GEzvQ".indexOf(repeatChar) >= 0) repeatCount = 1;
\r
1722 baseOriginal[typeValue] = Utility.repeat(String.valueOf(repeatChar),repeatCount);
\r
1723 int subTypeValue = row[2];
\r
1724 if (subTypeValue > 0) subTypeValue += field.length();
\r
1725 type[typeValue] = subTypeValue;
\r
1733 int getFieldMask() {
\r
1735 for (int i = 0; i < type.length; ++i) {
\r
1736 if (type[i] != 0) result |= (1<<i);
\r
1744 void extractFrom(DateTimeMatcher source, int fieldMask) {
\r
1745 for (int i = 0; i < type.length; ++i) {
\r
1746 if ((fieldMask & (1<<i)) != 0) {
\r
1747 type[i] = source.type[i];
\r
1748 original[i] = source.original[i];
\r
1756 int getDistance(DateTimeMatcher other, int includeMask, DistanceInfo distanceInfo) {
\r
1758 distanceInfo.clear();
\r
1759 for (int i = 0; i < type.length; ++i) {
\r
1760 int myType = (includeMask & (1<<i)) == 0 ? 0 : type[i];
\r
1761 int otherType = other.type[i];
\r
1762 if (myType == otherType) continue; // identical (maybe both zero) add 0
\r
1763 if (myType == 0) { // and other is not
\r
1764 result += EXTRA_FIELD;
\r
1765 distanceInfo.addExtra(i);
\r
1766 } else if (otherType == 0) { // and mine is not
\r
1767 result += MISSING_FIELD;
\r
1768 distanceInfo.addMissing(i);
\r
1770 result += Math.abs(myType - otherType); // square of mismatch
\r
1776 public int compareTo(Object o) {
\r
1777 DateTimeMatcher that = (DateTimeMatcher) o;
\r
1778 for (int i = 0; i < original.length; ++i) {
\r
1779 int comp = original[i].compareTo(that.original[i]);
\r
1780 if (comp != 0) return -comp;
\r
1785 public boolean equals(Object other) {
\r
1786 if (other == null) return false;
\r
1787 DateTimeMatcher that = (DateTimeMatcher) other;
\r
1788 for (int i = 0; i < original.length; ++i) {
\r
1789 if (!original[i].equals(that.original[i])) return false;
\r
1793 public int hashCode() {
\r
1795 for (int i = 0; i < original.length; ++i) {
\r
1796 result ^= original[i].hashCode();
\r
1802 private static class DistanceInfo {
\r
1803 int missingFieldMask;
\r
1804 int extraFieldMask;
\r
1806 missingFieldMask = extraFieldMask = 0;
\r
1811 void setTo(DistanceInfo other) {
\r
1812 missingFieldMask = other.missingFieldMask;
\r
1813 extraFieldMask = other.extraFieldMask;
\r
1815 void addMissing(int field) {
\r
1816 missingFieldMask |= (1<<field);
\r
1818 void addExtra(int field) {
\r
1819 extraFieldMask |= (1<<field);
\r
1821 public String toString() {
\r
1822 return "missingFieldMask: " + DateTimePatternGenerator.showMask(missingFieldMask)
\r
1823 + ", extraFieldMask: " + DateTimePatternGenerator.showMask(extraFieldMask);
\r