1 /* 2 ******************************************************************************** 3 * Copyright (C) 2006-2015, Google, International Business Machines Corporation * 4 * and others. All Rights Reserved. * 5 ******************************************************************************** 6 */ 7 package com.ibm.icu.text; 8 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; 19 import java.util.Locale; 20 import java.util.Map; 21 import java.util.MissingResourceException; 22 import java.util.Set; 23 import java.util.TreeMap; 24 import java.util.TreeSet; 25 26 import com.ibm.icu.impl.ICUCache; 27 import com.ibm.icu.impl.ICUResourceBundle; 28 import com.ibm.icu.impl.PatternTokenizer; 29 import com.ibm.icu.impl.SimpleCache; 30 import com.ibm.icu.impl.Utility; 31 import com.ibm.icu.util.Calendar; 32 import com.ibm.icu.util.Freezable; 33 import com.ibm.icu.util.ICUCloneNotSupportedException; 34 import com.ibm.icu.util.ULocale; 35 import com.ibm.icu.util.ULocale.Category; 36 import com.ibm.icu.util.UResourceBundle; 37 38 /** 39 * This class provides flexible generation of date format patterns, like 40 * "yy-MM-dd". The user can build up the generator by adding successive 41 * patterns. Once that is done, a query can be made using a "skeleton", which is 42 * a pattern which just includes the desired fields and lengths. The generator 43 * will return the "best fit" pattern corresponding to that skeleton. 44 * <p> 45 * The main method people will use is getBestPattern(String skeleton), since 46 * normally this class is pre-built with data from a particular locale. However, 47 * generators can be built directly from other data as well. 48 * @stable ICU 3.6 49 */ 50 public class DateTimePatternGenerator implements Freezable<DateTimePatternGenerator>, Cloneable { 51 private static final boolean DEBUG = false; 52 53 // debugging flags 54 //static boolean SHOW_DISTANCE = false; 55 // TODO add hack to fix months for CJK, as per bug ticket 1099 56 57 /** 58 * Create empty generator, to be constructed with addPattern(...) etc. 59 * @stable ICU 3.6 60 */ getEmptyInstance()61 public static DateTimePatternGenerator getEmptyInstance() { 62 return new DateTimePatternGenerator(); 63 } 64 65 /** 66 * Only for use by subclasses 67 * @stable ICU 3.6 68 */ DateTimePatternGenerator()69 protected DateTimePatternGenerator() { 70 } 71 72 /** 73 * Construct a flexible generator according to data for the default <code>FORMAT</code> locale. 74 * @see Category#FORMAT 75 * @stable ICU 3.6 76 */ getInstance()77 public static DateTimePatternGenerator getInstance() { 78 return getInstance(ULocale.getDefault(Category.FORMAT)); 79 } 80 81 /** 82 * Construct a flexible generator according to data for a given locale. 83 * @param uLocale The locale to pass. 84 * @stable ICU 3.6 85 */ getInstance(ULocale uLocale)86 public static DateTimePatternGenerator getInstance(ULocale uLocale) { 87 return getFrozenInstance(uLocale).cloneAsThawed(); 88 } 89 90 /** 91 * Construct a flexible generator according to data for a given locale. 92 * @param locale The JDK locale to pass. 93 * @draft ICU 54 94 * @provisional This API might change or be removed in a future release. 95 */ getInstance(Locale locale)96 public static DateTimePatternGenerator getInstance(Locale locale) { 97 return getInstance(ULocale.forLocale(locale)); 98 } 99 100 /** 101 * Construct a frozen instance of DateTimePatternGenerator for a 102 * given locale. This method returns a cached frozen instance of 103 * DateTimePatternGenerator, so less expensive than the regular 104 * factory method. 105 * @param uLocale The locale to pass. 106 * @return A frozen DateTimePatternGenerator. 107 * @internal 108 * @deprecated This API is ICU internal only. 109 */ 110 @Deprecated getFrozenInstance(ULocale uLocale)111 public static DateTimePatternGenerator getFrozenInstance(ULocale uLocale) { 112 String localeKey = uLocale.toString(); 113 DateTimePatternGenerator result = DTPNG_CACHE.get(localeKey); 114 if (result != null) { 115 return result; 116 } 117 result = new DateTimePatternGenerator(); 118 PatternInfo returnInfo = new PatternInfo(); 119 String shortTimePattern = null; 120 // first load with the ICU patterns 121 for (int i = DateFormat.FULL; i <= DateFormat.SHORT; ++i) { 122 SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(i, uLocale); 123 result.addPattern(df.toPattern(), false, returnInfo); 124 df = (SimpleDateFormat) DateFormat.getTimeInstance(i, uLocale); 125 result.addPattern(df.toPattern(), false, returnInfo); 126 if (i == DateFormat.SHORT) { 127 // keep this pattern to populate other time field 128 // combination patterns by hackTimes later in this method. 129 shortTimePattern = df.toPattern(); 130 131 // use hour style in SHORT time pattern as the default 132 // hour style for the locale 133 FormatParser fp = new FormatParser(); 134 fp.set(shortTimePattern); 135 List<Object> items = fp.getItems(); 136 for (int idx = 0; idx < items.size(); idx++) { 137 Object item = items.get(idx); 138 if (item instanceof VariableField) { 139 VariableField fld = (VariableField)item; 140 if (fld.getType() == HOUR) { 141 result.defaultHourFormatChar = fld.toString().charAt(0); 142 break; 143 } 144 } 145 } 146 } 147 } 148 149 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, uLocale); 150 // Get the correct calendar type 151 String calendarTypeToUse = uLocale.getKeywordValue("calendar"); 152 if ( calendarTypeToUse == null ) { 153 String[] preferredCalendarTypes = Calendar.getKeywordValuesForLocale("calendar", uLocale, true); 154 calendarTypeToUse = preferredCalendarTypes[0]; // the most preferred calendar 155 } 156 if ( calendarTypeToUse == null ) { 157 calendarTypeToUse = "gregorian"; // fallback 158 } 159 160 // Get data for that calendar 161 try { 162 // ICU4J getWithFallback does not work well when 163 // 1) A nested table is an alias to /LOCALE/... 164 // 2) getWithFallback is called multiple times for going down hierarchical resource path 165 // #9987 resolved the issue of alias table when full path is specified in getWithFallback, 166 // but there is no easy solution when the equivalent operation is done by multiple operations. 167 // This issue is addressed in #9964. 168 ICUResourceBundle itemBundle = rb.getWithFallback("calendar/" + calendarTypeToUse + "/appendItems"); 169 for (int i=0; i<itemBundle.getSize(); ++i) { 170 ICUResourceBundle formatBundle = (ICUResourceBundle)itemBundle.get(i); 171 String formatName = itemBundle.get(i).getKey(); 172 String value = formatBundle.getString(); 173 result.setAppendItemFormat(getAppendFormatNumber(formatName), value); 174 } 175 }catch(MissingResourceException e) { 176 } 177 178 // CLDR item names 179 try { 180 ICUResourceBundle itemBundle = rb.getWithFallback("fields"); 181 ICUResourceBundle fieldBundle, dnBundle; 182 for (int i=0; i<TYPE_LIMIT; ++i) { 183 if ( isCLDRFieldName(i) ) { 184 fieldBundle = itemBundle.getWithFallback(CLDR_FIELD_NAME[i]); 185 dnBundle = fieldBundle.getWithFallback("dn"); 186 String value = dnBundle.getString(); 187 //System.out.println("Field name:"+value); 188 result.setAppendItemName(i, value); 189 } 190 } 191 }catch(MissingResourceException e) { 192 } 193 194 // set the AvailableFormat in CLDR 195 ICUResourceBundle availFormatsBundle = null; 196 try { 197 // ICU4J getWithFallback does not work well when 198 // 1) A nested table is an alias to /LOCALE/... 199 // 2) getWithFallback is called multiple times for going down hierarchical resource path 200 // #9987 resolved the issue of alias table when full path is specified in getWithFallback, 201 // but there is no easy solution when the equivalent operation is done by multiple operations. 202 // This issue is addressed in #9964. 203 availFormatsBundle = rb.getWithFallback("calendar/" + calendarTypeToUse + "/availableFormats"); 204 } catch (MissingResourceException e) { 205 // fall through 206 } 207 208 boolean override = true; 209 while (availFormatsBundle != null) { 210 for (int i = 0; i < availFormatsBundle.getSize(); i++) { 211 String formatKey = availFormatsBundle.get(i).getKey(); 212 213 if (!result.isAvailableFormatSet(formatKey)) { 214 result.setAvailableFormat(formatKey); 215 // Add pattern with its associated skeleton. Override any duplicate derived from std patterns, 216 // but not a previous availableFormats entry: 217 String formatValue = availFormatsBundle.get(i).getString(); 218 result.addPatternWithSkeleton(formatValue, formatKey, override, returnInfo); 219 } 220 } 221 222 ICUResourceBundle pbundle = (ICUResourceBundle)availFormatsBundle.getParent(); 223 if (pbundle == null) { 224 break; 225 } 226 try { 227 availFormatsBundle = pbundle.getWithFallback("calendar/" + calendarTypeToUse + "/availableFormats"); 228 } catch (MissingResourceException e) { 229 availFormatsBundle = null; 230 } 231 if (availFormatsBundle != null && pbundle.getULocale().getBaseName().equals("root")) { 232 override = false; 233 } 234 } 235 236 // assume it is always big endian (ok for CLDR right now) 237 // some languages didn't add mm:ss or HH:mm, so put in a hack to compute that from the short time. 238 if (shortTimePattern != null) { 239 hackTimes(result, returnInfo, shortTimePattern); 240 } 241 242 result.setDateTimeFormat(Calendar.getDateTimePattern(Calendar.getInstance(uLocale), uLocale, DateFormat.MEDIUM)); 243 244 // decimal point for seconds 245 DecimalFormatSymbols dfs = new DecimalFormatSymbols(uLocale); 246 result.setDecimal(String.valueOf(dfs.getDecimalSeparator())); 247 248 // freeze and cache 249 result.freeze(); 250 DTPNG_CACHE.put(localeKey, result); 251 return result; 252 } 253 254 /** 255 * @internal 256 * @deprecated This API is ICU internal only. 257 */ 258 @Deprecated getDefaultHourFormatChar()259 public char getDefaultHourFormatChar() { 260 return defaultHourFormatChar; 261 } 262 263 /** 264 * @internal 265 * @deprecated This API is ICU internal only. 266 */ 267 @Deprecated setDefaultHourFormatChar(char defaultHourFormatChar)268 public void setDefaultHourFormatChar(char defaultHourFormatChar) { 269 this.defaultHourFormatChar = defaultHourFormatChar; 270 } 271 hackTimes(DateTimePatternGenerator result, PatternInfo returnInfo, String hackPattern)272 private static void hackTimes(DateTimePatternGenerator result, PatternInfo returnInfo, String hackPattern) { 273 result.fp.set(hackPattern); 274 StringBuilder mmss = new StringBuilder(); 275 // to get mm:ss, we strip all but mm literal ss 276 boolean gotMm = false; 277 for (int i = 0; i < result.fp.items.size(); ++i) { 278 Object item = result.fp.items.get(i); 279 if (item instanceof String) { 280 if (gotMm) { 281 mmss.append(result.fp.quoteLiteral(item.toString())); 282 } 283 } else { 284 char ch = item.toString().charAt(0); 285 if (ch == 'm') { 286 gotMm = true; 287 mmss.append(item); 288 } else if (ch == 's') { 289 if (!gotMm) { 290 break; // failed 291 } 292 mmss.append(item); 293 result.addPattern(mmss.toString(), false, returnInfo); 294 break; 295 } else if (gotMm || ch == 'z' || ch == 'Z' || ch == 'v' || ch == 'V') { 296 break; // failed 297 } 298 } 299 } 300 // to get hh:mm, we strip (literal ss) and (literal S) 301 // the easiest way to do this is to mark the stuff we want to nuke, then remove it in a second pass. 302 BitSet variables = new BitSet(); 303 BitSet nuke = new BitSet(); 304 for (int i = 0; i < result.fp.items.size(); ++i) { 305 Object item = result.fp.items.get(i); 306 if (item instanceof VariableField) { 307 variables.set(i); 308 char ch = item.toString().charAt(0); 309 if (ch == 's' || ch == 'S') { 310 nuke.set(i); 311 for (int j = i-1; j >= 0; ++j) { 312 if (variables.get(j)) break; 313 nuke.set(i); 314 } 315 } 316 } 317 } 318 String hhmm = getFilteredPattern(result.fp, nuke); 319 result.addPattern(hhmm, false, returnInfo); 320 } 321 getFilteredPattern(FormatParser fp, BitSet nuke)322 private static String getFilteredPattern(FormatParser fp, BitSet nuke) { 323 StringBuilder result = new StringBuilder(); 324 for (int i = 0; i < fp.items.size(); ++i) { 325 if (nuke.get(i)) continue; 326 Object item = fp.items.get(i); 327 if (item instanceof String) { 328 result.append(fp.quoteLiteral(item.toString())); 329 } else { 330 result.append(item.toString()); 331 } 332 } 333 return result.toString(); 334 } 335 336 /*private static int getAppendNameNumber(String string) { 337 for (int i = 0; i < CLDR_FIELD_NAME.length; ++i) { 338 if (CLDR_FIELD_NAME[i].equals(string)) return i; 339 } 340 return -1; 341 }*/ 342 343 /** 344 * @internal CLDR 345 * @deprecated This API is ICU internal only. 346 */ 347 @Deprecated getAppendFormatNumber(String string)348 public static int getAppendFormatNumber(String string) { 349 for (int i = 0; i < CLDR_FIELD_APPEND.length; ++i) { 350 if (CLDR_FIELD_APPEND[i].equals(string)) return i; 351 } 352 return -1; 353 354 } 355 isCLDRFieldName(int index)356 private static boolean isCLDRFieldName(int index) { 357 if ((index<0) && (index>=TYPE_LIMIT)) { 358 return false; 359 } 360 if (CLDR_FIELD_NAME[index].charAt(0) == '*') { 361 return false; 362 } 363 else { 364 return true; 365 } 366 } 367 368 /** 369 * Return the best pattern matching the input skeleton. It is guaranteed to 370 * have all of the fields in the skeleton. 371 * <p>Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---getBestPatternExample} 372 * @param skeleton The skeleton is a pattern containing only the variable fields. 373 * For example, "MMMdd" and "mmhh" are skeletons. 374 * @return Best pattern matching the input skeleton. 375 * @stable ICU 3.6 376 */ getBestPattern(String skeleton)377 public String getBestPattern(String skeleton) { 378 return getBestPattern(skeleton, null, MATCH_NO_OPTIONS); 379 } 380 381 /** 382 * Return the best pattern matching the input skeleton. It is guaranteed to 383 * have all of the fields in the skeleton. 384 * 385 * @param skeleton The skeleton is a pattern containing only the variable fields. 386 * For example, "MMMdd" and "mmhh" are skeletons. 387 * @param options MATCH_xxx options for forcing the length of specified fields in 388 * the returned pattern to match those in the skeleton (when this would 389 * not happen otherwise). For default behavior, use MATCH_NO_OPTIONS. 390 * @return Best pattern matching the input skeleton (and options). 391 * @stable ICU 4.4 392 */ getBestPattern(String skeleton, int options)393 public String getBestPattern(String skeleton, int options) { 394 return getBestPattern(skeleton, null, options); 395 } 396 397 /* 398 * getBestPattern which takes optional skip matcher 399 */ getBestPattern(String skeleton, DateTimeMatcher skipMatcher, int options)400 private String getBestPattern(String skeleton, DateTimeMatcher skipMatcher, int options) { 401 EnumSet<DTPGflags> flags = EnumSet.noneOf(DTPGflags.class); 402 // Replace hour metacharacters 'j' and 'J', set flags as necessary 403 StringBuilder skeletonCopy = new StringBuilder(skeleton); 404 boolean inQuoted = false; 405 for (int patPos = 0; patPos < skeletonCopy.length(); patPos++) { 406 char patChr = skeletonCopy.charAt(patPos); 407 if (patChr == '\'') { 408 inQuoted = !inQuoted; 409 } else if (!inQuoted) { 410 if (patChr == 'j') { 411 skeletonCopy.setCharAt(patPos, defaultHourFormatChar); 412 } else if (patChr == 'J') { 413 // Get pattern for skeleton with H, then (in adjustFieldTypes) 414 // replace H or k with defaultHourFormatChar 415 skeletonCopy.setCharAt(patPos, 'H'); 416 flags.add(DTPGflags.SKELETON_USES_CAP_J); 417 } 418 } 419 } 420 421 String datePattern, timePattern; 422 synchronized(this) { 423 current.set(skeletonCopy.toString(), fp, false); 424 PatternWithMatcher bestWithMatcher = getBestRaw(current, -1, _distanceInfo, skipMatcher); 425 if (_distanceInfo.missingFieldMask == 0 && _distanceInfo.extraFieldMask == 0) { 426 // we have a good item. Adjust the field types 427 return adjustFieldTypes(bestWithMatcher, current, flags, options); 428 } 429 int neededFields = current.getFieldMask(); 430 431 // otherwise break up by date and time. 432 datePattern = getBestAppending(current, neededFields & DATE_MASK, _distanceInfo, skipMatcher, flags, options); 433 timePattern = getBestAppending(current, neededFields & TIME_MASK, _distanceInfo, skipMatcher, flags, options); 434 } 435 436 if (datePattern == null) return timePattern == null ? "" : timePattern; 437 if (timePattern == null) return datePattern; 438 return MessageFormat.format(getDateTimeFormat(), new Object[]{timePattern, datePattern}); 439 } 440 441 /** 442 * PatternInfo supplies output parameters for addPattern(...). It is used because 443 * Java doesn't have real output parameters. It is treated like a struct (eg 444 * Point), so all fields are public. 445 * 446 * @stable ICU 3.6 447 */ 448 public static final class PatternInfo { // struct for return information 449 /** 450 * @stable ICU 3.6 451 */ 452 public static final int OK = 0; 453 454 /** 455 * @stable ICU 3.6 456 */ 457 public static final int BASE_CONFLICT = 1; 458 459 /** 460 * @stable ICU 3.6 461 */ 462 public static final int CONFLICT = 2; 463 464 /** 465 * @stable ICU 3.6 466 */ 467 public int status; 468 469 /** 470 * @stable ICU 3.6 471 */ 472 public String conflictingPattern; 473 474 /** 475 * Simple constructor, since this is treated like a struct. 476 * @stable ICU 3.6 477 */ PatternInfo()478 public PatternInfo() { 479 } 480 } 481 482 /** 483 * Adds a pattern to the generator. If the pattern has the same skeleton as 484 * an existing pattern, and the override parameter is set, then the previous 485 * value is overridden. Otherwise, the previous value is retained. In either 486 * case, the conflicting information is returned in PatternInfo. 487 * <p> 488 * Note that single-field patterns (like "MMM") are automatically added, and 489 * don't need to be added explicitly! 490 * * <p>Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---addPatternExample} 491 * @param pattern Pattern to add. 492 * @param override When existing values are to be overridden use true, otherwise 493 * use false. 494 * @param returnInfo Returned information. 495 * @stable ICU 3.6 496 */ addPattern(String pattern, boolean override, PatternInfo returnInfo)497 public DateTimePatternGenerator addPattern(String pattern, boolean override, PatternInfo returnInfo) { 498 return addPatternWithSkeleton(pattern, null, override, returnInfo); 499 } 500 501 /** 502 * addPatternWithSkeleton: 503 * If skeletonToUse is specified, then an availableFormats entry is being added. In this case: 504 * 1. We pass that skeleton to DateTimeMatcher().set instead of having it derive a skeleton from the pattern. 505 * 2. If the new entry's skeleton or basePattern does match an existing entry but that entry also had a skeleton specified 506 * (i.e. it was also from availableFormats), then the new entry does not override it regardless of the value of the override 507 * parameter. This prevents later availableFormats entries from a parent locale overriding earlier ones from the actual 508 * specified locale. However, availableFormats entries *should* override entries with matching skeleton whose skeleton was 509 * derived (i.e. entries derived from the standard date/time patters for the specified locale). 510 * 3. When adding the pattern (skeleton2pattern.put, basePattern_pattern.put), we set a field to indicate that the added 511 * entry had a specified skeleton. 512 * @internal 513 * @deprecated This API is ICU internal only. 514 */ 515 @Deprecated addPatternWithSkeleton(String pattern, String skeletonToUse, boolean override, PatternInfo returnInfo)516 public DateTimePatternGenerator addPatternWithSkeleton(String pattern, String skeletonToUse, boolean override, PatternInfo returnInfo) { 517 checkFrozen(); 518 DateTimeMatcher matcher; 519 if (skeletonToUse == null) { 520 matcher = new DateTimeMatcher().set(pattern, fp, false); 521 } else { 522 matcher = new DateTimeMatcher().set(skeletonToUse, fp, false); 523 } 524 String basePattern = matcher.getBasePattern(); 525 // We only care about base conflicts - and replacing the pattern associated with a base - if: 526 // 1. the conflicting previous base pattern did *not* have an explicit skeleton; in that case the previous 527 // base + pattern combination was derived from either (a) a canonical item, (b) a standard format, or 528 // (c) a pattern specified programmatically with a previous call to addPattern (which would only happen 529 // if we are getting here from a subsequent call to addPattern). 530 // 2. a skeleton is specified for the current pattern, but override=false; in that case we are checking 531 // availableFormats items from root, which should not override any previous entry with the same base. 532 PatternWithSkeletonFlag previousPatternWithSameBase = basePattern_pattern.get(basePattern); 533 if (previousPatternWithSameBase != null && (!previousPatternWithSameBase.skeletonWasSpecified || (skeletonToUse != null && !override))) { 534 returnInfo.status = PatternInfo.BASE_CONFLICT; 535 returnInfo.conflictingPattern = previousPatternWithSameBase.pattern; 536 if (!override) { 537 return this; 538 } 539 } 540 // The only time we get here with override=true and skeletonToUse!=null is when adding availableFormats 541 // items from CLDR data. In that case, we don't want an item from a parent locale to replace an item with 542 // same skeleton from the specified locale, so skip the current item if skeletonWasSpecified is true for 543 // the previously-specified conflicting item. 544 PatternWithSkeletonFlag previousValue = skeleton2pattern.get(matcher); 545 if (previousValue != null) { 546 returnInfo.status = PatternInfo.CONFLICT; 547 returnInfo.conflictingPattern = previousValue.pattern; 548 if (!override || (skeletonToUse != null && previousValue.skeletonWasSpecified)) return this; 549 } 550 returnInfo.status = PatternInfo.OK; 551 returnInfo.conflictingPattern = ""; 552 PatternWithSkeletonFlag patWithSkelFlag = new PatternWithSkeletonFlag(pattern,skeletonToUse != null); 553 if (DEBUG) { 554 System.out.println(matcher + " => " + patWithSkelFlag); 555 } 556 skeleton2pattern.put(matcher, patWithSkelFlag); 557 basePattern_pattern.put(basePattern, patWithSkelFlag); 558 return this; 559 } 560 561 /** 562 * Utility to return a unique skeleton from a given pattern. For example, 563 * both "MMM-dd" and "dd/MMM" produce the skeleton "MMMdd". 564 * 565 * @param pattern Input pattern, such as "dd/MMM" 566 * @return skeleton, such as "MMMdd" 567 * @stable ICU 3.6 568 */ getSkeleton(String pattern)569 public String getSkeleton(String pattern) { 570 synchronized (this) { // synchronized since a getter must be thread-safe 571 current.set(pattern, fp, false); 572 return current.toString(); 573 } 574 } 575 576 /** 577 * Same as getSkeleton, but allows duplicates 578 * 579 * @param pattern Input pattern, such as "dd/MMM" 580 * @return skeleton, such as "MMMdd" 581 * @internal 582 * @deprecated This API is ICU internal only. 583 */ 584 @Deprecated getSkeletonAllowingDuplicates(String pattern)585 public String getSkeletonAllowingDuplicates(String pattern) { 586 synchronized (this) { // synchronized since a getter must be thread-safe 587 current.set(pattern, fp, true); 588 return current.toString(); 589 } 590 } 591 592 /** 593 * Same as getSkeleton, but allows duplicates 594 * and returns a string using canonical pattern chars 595 * 596 * @param pattern Input pattern, such as "ccc, d LLL" 597 * @return skeleton, such as "MMMEd" 598 * @internal 599 * @deprecated This API is ICU internal only. 600 */ 601 @Deprecated getCanonicalSkeletonAllowingDuplicates(String pattern)602 public String getCanonicalSkeletonAllowingDuplicates(String pattern) { 603 synchronized (this) { // synchronized since a getter must be thread-safe 604 current.set(pattern, fp, true); 605 return current.toCanonicalString(); 606 } 607 } 608 609 /** 610 * Utility to return a unique base skeleton from a given pattern. This is 611 * the same as the skeleton, except that differences in length are minimized 612 * so as to only preserve the difference between string and numeric form. So 613 * for example, both "MMM-dd" and "d/MMM" produce the skeleton "MMMd" 614 * (notice the single d). 615 * 616 * @param pattern Input pattern, such as "dd/MMM" 617 * @return skeleton, such as "MMMdd" 618 * @stable ICU 3.6 619 */ getBaseSkeleton(String pattern)620 public String getBaseSkeleton(String pattern) { 621 synchronized (this) { // synchronized since a getter must be thread-safe 622 current.set(pattern, fp, false); 623 return current.getBasePattern(); 624 } 625 } 626 627 /** 628 * Return a list of all the skeletons (in canonical form) from this class, 629 * and the patterns that they map to. 630 * 631 * @param result an output Map in which to place the mapping from skeleton to 632 * pattern. If you want to see the internal order being used, 633 * supply a LinkedHashMap. If the input value is null, then a 634 * LinkedHashMap is allocated. 635 * <p> 636 * <i>Issue: an alternate API would be to just return a list of 637 * the skeletons, and then have a separate routine to get from 638 * skeleton to pattern.</i> 639 * @return the input Map containing the values. 640 * @stable ICU 3.6 641 */ getSkeletons(Map<String, String> result)642 public Map<String, String> getSkeletons(Map<String, String> result) { 643 if (result == null) { 644 result = new LinkedHashMap<String, String>(); 645 } 646 for (DateTimeMatcher item : skeleton2pattern.keySet()) { 647 PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(item); 648 String pattern = patternWithSkelFlag.pattern; 649 if (CANONICAL_SET.contains(pattern)) { 650 continue; 651 } 652 result.put(item.toString(), pattern); 653 } 654 return result; 655 } 656 657 /** 658 * Return a list of all the base skeletons (in canonical form) from this class 659 * @stable ICU 3.6 660 */ getBaseSkeletons(Set<String> result)661 public Set<String> getBaseSkeletons(Set<String> result) { 662 if (result == null) { 663 result = new HashSet<String>(); 664 } 665 result.addAll(basePattern_pattern.keySet()); 666 return result; 667 } 668 669 /** 670 * Adjusts the field types (width and subtype) of a pattern to match what is 671 * in a skeleton. That is, if you supply a pattern like "d-M H:m", and a 672 * skeleton of "MMMMddhhmm", then the input pattern is adjusted to be 673 * "dd-MMMM hh:mm". This is used internally to get the best match for the 674 * input skeleton, but can also be used externally. 675 * <p>Example code:{@.jcite com.ibm.icu.samples.text.datetimepatterngenerator.DateTimePatternGeneratorSample:---replaceFieldTypesExample} 676 * @param pattern input pattern 677 * @param skeleton For the pattern to match to. 678 * @return pattern adjusted to match the skeleton fields widths and subtypes. 679 * @stable ICU 3.6 680 */ replaceFieldTypes(String pattern, String skeleton)681 public String replaceFieldTypes(String pattern, String skeleton) { 682 return replaceFieldTypes(pattern, skeleton, MATCH_NO_OPTIONS); 683 } 684 685 /** 686 * Adjusts the field types (width and subtype) of a pattern to match what is 687 * in a skeleton. That is, if you supply a pattern like "d-M H:m", and a 688 * skeleton of "MMMMddhhmm", then the input pattern is adjusted to be 689 * "dd-MMMM hh:mm". This is used internally to get the best match for the 690 * input skeleton, but can also be used externally. 691 * 692 * @param pattern input pattern 693 * @param skeleton For the pattern to match to. 694 * @param options MATCH_xxx options for forcing the length of specified fields in 695 * the returned pattern to match those in the skeleton (when this would 696 * not happen otherwise). For default behavior, use MATCH_NO_OPTIONS. 697 * @return pattern adjusted to match the skeleton fields widths and subtypes. 698 * @stable ICU 4.4 699 */ replaceFieldTypes(String pattern, String skeleton, int options)700 public String replaceFieldTypes(String pattern, String skeleton, int options) { 701 synchronized (this) { // synchronized since a getter must be thread-safe 702 PatternWithMatcher patternNoMatcher = new PatternWithMatcher(pattern, null); 703 return adjustFieldTypes(patternNoMatcher, current.set(skeleton, fp, false), EnumSet.noneOf(DTPGflags.class), options); 704 } 705 } 706 707 /** 708 * The date time format is a message format pattern used to compose date and 709 * time patterns. The default value is "{1} {0}", where {1} will be replaced 710 * by the date pattern and {0} will be replaced by the time pattern. 711 * <p> 712 * This is used when the input skeleton contains both date and time fields, 713 * but there is not a close match among the added patterns. For example, 714 * suppose that this object was created by adding "dd-MMM" and "hh:mm", and 715 * its datetimeFormat is the default "{1} {0}". Then if the input skeleton 716 * is "MMMdhmm", there is not an exact match, so the input skeleton is 717 * broken up into two components "MMMd" and "hmm". There are close matches 718 * for those two skeletons, so the result is put together with this pattern, 719 * resulting in "d-MMM h:mm". 720 * 721 * @param dateTimeFormat message format pattern, where {1} will be replaced by the date 722 * pattern and {0} will be replaced by the time pattern. 723 * @stable ICU 3.6 724 */ setDateTimeFormat(String dateTimeFormat)725 public void setDateTimeFormat(String dateTimeFormat) { 726 checkFrozen(); 727 this.dateTimeFormat = dateTimeFormat; 728 } 729 730 /** 731 * Getter corresponding to setDateTimeFormat. 732 * 733 * @return pattern 734 * @stable ICU 3.6 735 */ getDateTimeFormat()736 public String getDateTimeFormat() { 737 return dateTimeFormat; 738 } 739 740 /** 741 * The decimal value is used in formatting fractions of seconds. If the 742 * skeleton contains fractional seconds, then this is used with the 743 * fractional seconds. For example, suppose that the input pattern is 744 * "hhmmssSSSS", and the best matching pattern internally is "H:mm:ss", and 745 * the decimal string is ",". Then the resulting pattern is modified to be 746 * "H:mm:ss,SSSS" 747 * 748 * @param decimal The decimal to set to. 749 * @stable ICU 3.6 750 */ setDecimal(String decimal)751 public void setDecimal(String decimal) { 752 checkFrozen(); 753 this.decimal = decimal; 754 } 755 756 /** 757 * Getter corresponding to setDecimal. 758 * @return string corresponding to the decimal point 759 * @stable ICU 3.6 760 */ getDecimal()761 public String getDecimal() { 762 return decimal; 763 } 764 765 /** 766 * Redundant patterns are those which if removed, make no difference in the 767 * resulting getBestPattern values. This method returns a list of them, to 768 * help check the consistency of the patterns used to build this generator. 769 * 770 * @param output stores the redundant patterns that are removed. To get these 771 * in internal order, supply a LinkedHashSet. If null, a 772 * collection is allocated. 773 * @return the collection with added elements. 774 * @internal 775 * @deprecated This API is ICU internal only. 776 */ 777 @Deprecated getRedundants(Collection<String> output)778 public Collection<String> getRedundants(Collection<String> output) { 779 synchronized (this) { // synchronized since a getter must be thread-safe 780 if (output == null) { 781 output = new LinkedHashSet<String>(); 782 } 783 for (DateTimeMatcher cur : skeleton2pattern.keySet()) { 784 PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(cur); 785 String pattern = patternWithSkelFlag.pattern; 786 if (CANONICAL_SET.contains(pattern)) { 787 continue; 788 } 789 String trial = getBestPattern(cur.toString(), cur, MATCH_NO_OPTIONS); 790 if (trial.equals(pattern)) { 791 output.add(pattern); 792 } 793 } 794 ///CLOVER:OFF 795 //The following would never be called since the parameter is false 796 //Eclipse stated the following is "dead code" 797 /*if (false) { // ordered 798 DateTimePatternGenerator results = new DateTimePatternGenerator(); 799 PatternInfo pinfo = new PatternInfo(); 800 for (DateTimeMatcher cur : skeleton2pattern.keySet()) { 801 String pattern = skeleton2pattern.get(cur); 802 if (CANONICAL_SET.contains(pattern)) { 803 continue; 804 } 805 //skipMatcher = current; 806 String trial = results.getBestPattern(cur.toString()); 807 if (trial.equals(pattern)) { 808 output.add(pattern); 809 } else { 810 results.addPattern(pattern, false, pinfo); 811 } 812 } 813 }*/ 814 ///CLOVER:ON 815 return output; 816 } 817 } 818 819 // Field numbers, used for AppendItem functions 820 821 /** 822 * @stable ICU 3.6 823 */ 824 static final public int ERA = 0; 825 826 /** 827 * @stable ICU 3.6 828 */ 829 static final public int YEAR = 1; 830 831 /** 832 * @stable ICU 3.6 833 */ 834 static final public int QUARTER = 2; 835 836 /** 837 * @stable ICU 3.6 838 */ 839 static final public int MONTH = 3; 840 841 /** 842 * @stable ICU 3.6 843 */ 844 static final public int WEEK_OF_YEAR = 4; 845 846 /** 847 * @stable ICU 3.6 848 */ 849 static final public int WEEK_OF_MONTH = 5; 850 851 /** 852 * @stable ICU 3.6 853 */ 854 static final public int WEEKDAY = 6; 855 856 /** 857 * @stable ICU 3.6 858 */ 859 static final public int DAY = 7; 860 861 /** 862 * @stable ICU 3.6 863 */ 864 static final public int DAY_OF_YEAR = 8; 865 866 /** 867 * @stable ICU 3.6 868 */ 869 static final public int DAY_OF_WEEK_IN_MONTH = 9; 870 871 /** 872 * @stable ICU 3.6 873 */ 874 static final public int DAYPERIOD = 10; 875 876 /** 877 * @stable ICU 3.6 878 */ 879 static final public int HOUR = 11; 880 881 /** 882 * @stable ICU 3.6 883 */ 884 static final public int MINUTE = 12; 885 886 /** 887 * @stable ICU 3.6 888 */ 889 static final public int SECOND = 13; 890 891 /** 892 * @stable ICU 3.6 893 */ 894 static final public int FRACTIONAL_SECOND = 14; 895 896 /** 897 * @stable ICU 3.6 898 */ 899 static final public int ZONE = 15; 900 901 /** 902 * @stable ICU 3.6 903 */ 904 static final public int TYPE_LIMIT = 16; 905 906 // Option masks for getBestPattern, replaceFieldTypes (individual masks may be ORed together) 907 908 /** 909 * Default option mask used for {@link #getBestPattern(String, int)} 910 * and {@link #replaceFieldTypes(String, String, int)}. 911 * @stable ICU 4.4 912 * @see #getBestPattern(String, int) 913 * @see #replaceFieldTypes(String, String, int) 914 */ 915 public static final int MATCH_NO_OPTIONS = 0; 916 917 /** 918 * Option mask for forcing the width of hour field. 919 * @stable ICU 4.4 920 * @see #getBestPattern(String, int) 921 * @see #replaceFieldTypes(String, String, int) 922 */ 923 public static final int MATCH_HOUR_FIELD_LENGTH = 1 << HOUR; 924 925 /** 926 * Option mask for forcing the width of minute field. 927 * @internal 928 * @deprecated This API is ICU internal only. 929 */ 930 @Deprecated 931 public static final int MATCH_MINUTE_FIELD_LENGTH = 1 << MINUTE; 932 933 /** 934 * Option mask for forcing the width of second field. 935 * @internal 936 * @deprecated This API is ICU internal only. 937 */ 938 @Deprecated 939 public static final int MATCH_SECOND_FIELD_LENGTH = 1 << SECOND; 940 941 /** 942 * Option mask for forcing the width of all date and time fields. 943 * @stable ICU 4.4 944 * @see #getBestPattern(String, int) 945 * @see #replaceFieldTypes(String, String, int) 946 */ 947 public static final int MATCH_ALL_FIELDS_LENGTH = (1 << TYPE_LIMIT) - 1; 948 949 /** 950 * An AppendItem format is a pattern used to append a field if there is no 951 * good match. For example, suppose that the input skeleton is "GyyyyMMMd", 952 * and there is no matching pattern internally, but there is a pattern 953 * matching "yyyyMMMd", say "d-MM-yyyy". Then that pattern is used, plus the 954 * G. The way these two are conjoined is by using the AppendItemFormat for G 955 * (era). So if that value is, say "{0}, {1}" then the final resulting 956 * pattern is "d-MM-yyyy, G". 957 * <p> 958 * There are actually three available variables: {0} is the pattern so far, 959 * {1} is the element we are adding, and {2} is the name of the element. 960 * <p> 961 * This reflects the way that the CLDR data is organized. 962 * 963 * @param field such as ERA 964 * @param value pattern, such as "{0}, {1}" 965 * @stable ICU 3.6 966 */ setAppendItemFormat(int field, String value)967 public void setAppendItemFormat(int field, String value) { 968 checkFrozen(); 969 appendItemFormats[field] = value; 970 } 971 972 /** 973 * Getter corresponding to setAppendItemFormats. Values below 0 or at or 974 * above TYPE_LIMIT are illegal arguments. 975 * 976 * @param field The index to retrieve the append item formats. 977 * @return append pattern for field 978 * @stable ICU 3.6 979 */ getAppendItemFormat(int field)980 public String getAppendItemFormat(int field) { 981 return appendItemFormats[field]; 982 } 983 984 /** 985 * Sets the names of fields, eg "era" in English for ERA. These are only 986 * used if the corresponding AppendItemFormat is used, and if it contains a 987 * {2} variable. 988 * <p> 989 * This reflects the way that the CLDR data is organized. 990 * 991 * @param field Index of the append item names. 992 * @param value The value to set the item to. 993 * @stable ICU 3.6 994 */ setAppendItemName(int field, String value)995 public void setAppendItemName(int field, String value) { 996 checkFrozen(); 997 appendItemNames[field] = value; 998 } 999 1000 /** 1001 * Getter corresponding to setAppendItemNames. Values below 0 or at or above 1002 * TYPE_LIMIT are illegal arguments. 1003 * 1004 * @param field The index to get the append item name. 1005 * @return name for field 1006 * @stable ICU 3.6 1007 */ getAppendItemName(int field)1008 public String getAppendItemName(int field) { 1009 return appendItemNames[field]; 1010 } 1011 1012 /** 1013 * Determines whether a skeleton contains a single field 1014 * 1015 * @param skeleton The skeleton to determine if it contains a single field. 1016 * @return true or not 1017 * @internal 1018 * @deprecated This API is ICU internal only. 1019 */ 1020 @Deprecated isSingleField(String skeleton)1021 public static boolean isSingleField(String skeleton) { 1022 char first = skeleton.charAt(0); 1023 for (int i = 1; i < skeleton.length(); ++i) { 1024 if (skeleton.charAt(i) != first) return false; 1025 } 1026 return true; 1027 } 1028 1029 /** 1030 * Add key to HashSet cldrAvailableFormatKeys. 1031 * 1032 * @param key of the availableFormats in CLDR 1033 * @stable ICU 3.6 1034 */ setAvailableFormat(String key)1035 private void setAvailableFormat(String key) { 1036 checkFrozen(); 1037 cldrAvailableFormatKeys.add(key); 1038 } 1039 1040 /** 1041 * This function checks the corresponding slot of CLDR_AVAIL_FORMAT_KEY[] 1042 * has been added to DateTimePatternGenerator. 1043 * The function is to avoid the duplicate availableFomats added to 1044 * the pattern map from parent locales. 1045 * 1046 * @param key of the availableFormatMask in CLDR 1047 * @return TRUE if the corresponding slot of CLDR_AVAIL_FORMAT_KEY[] 1048 * has been added to DateTimePatternGenerator. 1049 * @stable ICU 3.6 1050 */ isAvailableFormatSet(String key)1051 private boolean isAvailableFormatSet(String key) { 1052 return cldrAvailableFormatKeys.contains(key); 1053 } 1054 1055 /** 1056 * Boilerplate for Freezable 1057 * @stable ICU 3.6 1058 */ isFrozen()1059 public boolean isFrozen() { 1060 return frozen; 1061 } 1062 1063 /** 1064 * Boilerplate for Freezable 1065 * @stable ICU 4.4 1066 */ freeze()1067 public DateTimePatternGenerator freeze() { 1068 frozen = true; 1069 return this; 1070 } 1071 1072 /** 1073 * Boilerplate for Freezable 1074 * @stable ICU 4.4 1075 */ cloneAsThawed()1076 public DateTimePatternGenerator cloneAsThawed() { 1077 DateTimePatternGenerator result = (DateTimePatternGenerator) (this.clone()); 1078 frozen = false; 1079 return result; 1080 } 1081 1082 /** 1083 * Boilerplate 1084 * @stable ICU 3.6 1085 */ 1086 @SuppressWarnings("unchecked") clone()1087 public Object clone() { 1088 try { 1089 DateTimePatternGenerator result = (DateTimePatternGenerator) (super.clone()); 1090 result.skeleton2pattern = (TreeMap<DateTimeMatcher, PatternWithSkeletonFlag>) skeleton2pattern.clone(); 1091 result.basePattern_pattern = (TreeMap<String, PatternWithSkeletonFlag>) basePattern_pattern.clone(); 1092 result.appendItemFormats = appendItemFormats.clone(); 1093 result.appendItemNames = appendItemNames.clone(); 1094 result.current = new DateTimeMatcher(); 1095 result.fp = new FormatParser(); 1096 result._distanceInfo = new DistanceInfo(); 1097 1098 result.frozen = false; 1099 return result; 1100 } catch (CloneNotSupportedException e) { 1101 ///CLOVER:OFF 1102 throw new ICUCloneNotSupportedException("Internal Error", e); 1103 ///CLOVER:ON 1104 } 1105 } 1106 1107 /** 1108 * Utility class for FormatParser. Immutable class that is only used to mark 1109 * the difference between a variable field and a literal string. Each 1110 * variable field must consist of 1 to n variable characters, representing 1111 * date format fields. For example, "VVVV" is valid while "V4" is not, nor 1112 * is "44". 1113 * 1114 * @internal 1115 * @deprecated This API is ICU internal only. 1116 */ 1117 @Deprecated 1118 public static class VariableField { 1119 private final String string; 1120 private final int canonicalIndex; 1121 1122 /** 1123 * Create a variable field: equivalent to VariableField(string,false); 1124 * @param string The string for the variable field. 1125 * @internal 1126 * @deprecated This API is ICU internal only. 1127 */ 1128 @Deprecated VariableField(String string)1129 public VariableField(String string) { 1130 this(string, false); 1131 } 1132 /** 1133 * Create a variable field 1134 * @param string The string for the variable field 1135 * @param strict If true, then only allows exactly those lengths specified by CLDR for variables. For example, "hh:mm aa" would throw an exception. 1136 * @throws IllegalArgumentException if the variable field is not valid. 1137 * @internal 1138 * @deprecated This API is ICU internal only. 1139 */ 1140 @Deprecated VariableField(String string, boolean strict)1141 public VariableField(String string, boolean strict) { 1142 canonicalIndex = DateTimePatternGenerator.getCanonicalIndex(string, strict); 1143 if (canonicalIndex < 0) { 1144 throw new IllegalArgumentException("Illegal datetime field:\t" 1145 + string); 1146 } 1147 this.string = string; 1148 } 1149 1150 /** 1151 * Get the main type of this variable. These types are ERA, QUARTER, 1152 * MONTH, DAY, WEEK_OF_YEAR, WEEK_OF_MONTH, WEEKDAY, DAY, DAYPERIOD 1153 * (am/pm), HOUR, MINUTE, SECOND,FRACTIONAL_SECOND, ZONE. 1154 * @return main type. 1155 * @internal 1156 * @deprecated This API is ICU internal only. 1157 */ 1158 @Deprecated getType()1159 public int getType() { 1160 return types[canonicalIndex][1]; 1161 } 1162 1163 /** 1164 * @internal 1165 * @deprecated This API is ICU internal only. 1166 */ 1167 @Deprecated getCanonicalCode(int type)1168 public static String getCanonicalCode(int type) { 1169 try { 1170 return CANONICAL_ITEMS[type]; 1171 } catch (Exception e) { 1172 return String.valueOf(type); 1173 } 1174 } 1175 /** 1176 * Check if the type of this variable field is numeric. 1177 * @return true if the type of this variable field is numeric. 1178 * @internal 1179 * @deprecated This API is ICU internal only. 1180 */ 1181 @Deprecated isNumeric()1182 public boolean isNumeric() { 1183 return types[canonicalIndex][2] > 0; 1184 } 1185 1186 /** 1187 * Private method. 1188 */ getCanonicalIndex()1189 private int getCanonicalIndex() { 1190 return canonicalIndex; 1191 } 1192 1193 /** 1194 * Get the string represented by this variable. 1195 * @internal 1196 * @deprecated This API is ICU internal only. 1197 */ 1198 @Deprecated toString()1199 public String toString() { 1200 return string; 1201 } 1202 } 1203 1204 /** 1205 * This class provides mechanisms for parsing a SimpleDateFormat pattern 1206 * or generating a new pattern, while handling the quoting. It represents 1207 * the result of the parse as a list of items, where each item is either a 1208 * literal string or a variable field. When parsing It can be used to find 1209 * out which variable fields are in a date format, and in what order, such 1210 * as for presentation in a UI as separate text entry fields. It can also be 1211 * used to construct new SimpleDateFormats. 1212 * <p>Example: 1213 * <pre> 1214 public boolean containsZone(String pattern) { 1215 for (Iterator it = formatParser.set(pattern).getItems().iterator(); it.hasNext();) { 1216 Object item = it.next(); 1217 if (item instanceof VariableField) { 1218 VariableField variableField = (VariableField) item; 1219 if (variableField.getType() == DateTimePatternGenerator.ZONE) { 1220 return true; 1221 } 1222 } 1223 } 1224 return false; 1225 } 1226 * </pre> 1227 * @internal 1228 * @deprecated This API is ICU internal only. 1229 */ 1230 @Deprecated 1231 static public class FormatParser { 1232 private static final UnicodeSet SYNTAX_CHARS = new UnicodeSet("[a-zA-Z]").freeze(); 1233 private static final UnicodeSet QUOTING_CHARS = new UnicodeSet("[[[:script=Latn:][:script=Cyrl:]]&[[:L:][:M:]]]").freeze(); 1234 private transient PatternTokenizer tokenizer = new PatternTokenizer() 1235 .setSyntaxCharacters(SYNTAX_CHARS) 1236 .setExtraQuotingCharacters(QUOTING_CHARS) 1237 .setUsingQuote(true); 1238 private List<Object> items = new ArrayList<Object>(); 1239 1240 /** 1241 * Construct an empty date format parser, to which strings and variables can be added with set(...). 1242 * @internal 1243 * @deprecated This API is ICU internal only. 1244 */ 1245 @Deprecated FormatParser()1246 public FormatParser() { 1247 } 1248 1249 /** 1250 * Parses the string into a list of items. 1251 * @param string The string to parse. 1252 * @return this, for chaining 1253 * @internal 1254 * @deprecated This API is ICU internal only. 1255 */ 1256 @Deprecated set(String string)1257 final public FormatParser set(String string) { 1258 return set(string, false); 1259 } 1260 1261 /** 1262 * Parses the string into a list of items, taking into account all of the quoting that may be going on. 1263 * @param string The string to parse. 1264 * @param strict If true, then only allows exactly those lengths specified by CLDR for variables. For example, "hh:mm aa" would throw an exception. 1265 * @return this, for chaining 1266 * @internal 1267 * @deprecated This API is ICU internal only. 1268 */ 1269 @Deprecated set(String string, boolean strict)1270 public FormatParser set(String string, boolean strict) { 1271 items.clear(); 1272 if (string.length() == 0) return this; 1273 tokenizer.setPattern(string); 1274 StringBuffer buffer = new StringBuffer(); 1275 StringBuffer variable = new StringBuffer(); 1276 while (true) { 1277 buffer.setLength(0); 1278 int status = tokenizer.next(buffer); 1279 if (status == PatternTokenizer.DONE) break; 1280 if (status == PatternTokenizer.SYNTAX) { 1281 if (variable.length() != 0 && buffer.charAt(0) != variable.charAt(0)) { 1282 addVariable(variable, false); 1283 } 1284 variable.append(buffer); 1285 } else { 1286 addVariable(variable, false); 1287 items.add(buffer.toString()); 1288 } 1289 } 1290 addVariable(variable, false); 1291 return this; 1292 } 1293 addVariable(StringBuffer variable, boolean strict)1294 private void addVariable(StringBuffer variable, boolean strict) { 1295 if (variable.length() != 0) { 1296 items.add(new VariableField(variable.toString(), strict)); 1297 variable.setLength(0); 1298 } 1299 } 1300 1301 // /** Private method. Return a collection of fields. These will be a mixture of literal Strings and VariableFields. Any "a" variable field is removed. 1302 // * @param output List to append the items to. If null, is allocated as an ArrayList. 1303 // * @return list 1304 // */ 1305 // private List getVariableFields(List output) { 1306 // if (output == null) output = new ArrayList(); 1307 // main: 1308 // for (Iterator it = items.iterator(); it.hasNext();) { 1309 // Object item = it.next(); 1310 // if (item instanceof VariableField) { 1311 // String s = item.toString(); 1312 // switch(s.charAt(0)) { 1313 // //case 'Q': continue main; // HACK 1314 // case 'a': continue main; // remove 1315 // } 1316 // output.add(item); 1317 // } 1318 // } 1319 // //System.out.println(output); 1320 // return output; 1321 // } 1322 1323 // /** 1324 // * Produce a string which concatenates all the variables. That is, it is the logically the same as the input with all literals removed. 1325 // * @return a string which is a concatenation of all the variable fields 1326 // */ 1327 // public String getVariableFieldString() { 1328 // List list = getVariableFields(null); 1329 // StringBuffer result = new StringBuffer(); 1330 // for (Iterator it = list.iterator(); it.hasNext();) { 1331 // String item = it.next().toString(); 1332 // result.append(item); 1333 // } 1334 // return result.toString(); 1335 // } 1336 1337 /** 1338 * 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: 1339 * <pre> 1340 * VariableField: dd 1341 * String: " de " 1342 * VariableField: MM 1343 * </pre> 1344 * The list is modifiable, so you can add any strings or variables to it, or remove any items. 1345 * @return modifiable list of items. 1346 * @internal 1347 * @deprecated This API is ICU internal only. 1348 */ 1349 @Deprecated getItems()1350 public List<Object> getItems() { 1351 return items; 1352 } 1353 1354 /** 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(). 1355 * @return printable output string 1356 * @internal 1357 * @deprecated This API is ICU internal only. 1358 */ 1359 @Deprecated toString()1360 public String toString() { 1361 return toString(0, items.size()); 1362 } 1363 1364 /** 1365 * 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(). 1366 * @param start item to start from 1367 * @param limit last item +1 1368 * @return printable output string 1369 * @internal 1370 * @deprecated This API is ICU internal only. 1371 */ 1372 @Deprecated toString(int start, int limit)1373 public String toString(int start, int limit) { 1374 StringBuilder result = new StringBuilder(); 1375 for (int i = start; i < limit; ++i) { 1376 Object item = items.get(i); 1377 if (item instanceof String) { 1378 String itemString = (String) item; 1379 result.append(tokenizer.quoteLiteral(itemString)); 1380 } else { 1381 result.append(items.get(i).toString()); 1382 } 1383 } 1384 return result.toString(); 1385 } 1386 1387 /** 1388 * 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. 1389 * @return true or false 1390 * @internal 1391 * @deprecated This API is ICU internal only. 1392 */ 1393 @Deprecated hasDateAndTimeFields()1394 public boolean hasDateAndTimeFields() { 1395 int foundMask = 0; 1396 for (Object item : items) { 1397 if (item instanceof VariableField) { 1398 int type = ((VariableField)item).getType(); 1399 foundMask |= 1 << type; 1400 } 1401 } 1402 boolean isDate = (foundMask & DATE_MASK) != 0; 1403 boolean isTime = (foundMask & TIME_MASK) != 0; 1404 return isDate && isTime; 1405 } 1406 1407 // /** 1408 // * Internal routine 1409 // * @param value 1410 // * @param result 1411 // * @return list 1412 // */ 1413 // public List getAutoPatterns(String value, List result) { 1414 // if (result == null) result = new ArrayList(); 1415 // int fieldCount = 0; 1416 // int minField = Integer.MAX_VALUE; 1417 // int maxField = Integer.MIN_VALUE; 1418 // for (Iterator it = items.iterator(); it.hasNext();) { 1419 // Object item = it.next(); 1420 // if (item instanceof VariableField) { 1421 // try { 1422 // int type = ((VariableField)item).getType(); 1423 // if (minField > type) minField = type; 1424 // if (maxField < type) maxField = type; 1425 // if (type == ZONE || type == DAYPERIOD || type == WEEKDAY) return result; // skip anything with zones 1426 // fieldCount++; 1427 // } catch (Exception e) { 1428 // return result; // if there are any funny fields, return 1429 // } 1430 // } 1431 // } 1432 // if (fieldCount < 3) return result; // skip 1433 // // trim from start 1434 // // trim first field IF there are no letters around it 1435 // // and it is either the min or the max field 1436 // // first field is either 0 or 1 1437 // for (int i = 0; i < items.size(); ++i) { 1438 // Object item = items.get(i); 1439 // if (item instanceof VariableField) { 1440 // int type = ((VariableField)item).getType(); 1441 // if (type != minField && type != maxField) break; 1442 // 1443 // if (i > 0) { 1444 // Object previousItem = items.get(0); 1445 // if (alpha.containsSome(previousItem.toString())) break; 1446 // } 1447 // int start = i+1; 1448 // if (start < items.size()) { 1449 // Object nextItem = items.get(start); 1450 // if (nextItem instanceof String) { 1451 // if (alpha.containsSome(nextItem.toString())) break; 1452 // start++; // otherwise skip over string 1453 // } 1454 // } 1455 // result.add(toString(start, items.size())); 1456 // break; 1457 // } 1458 // } 1459 // // now trim from end 1460 // for (int i = items.size()-1; i >= 0; --i) { 1461 // Object item = items.get(i); 1462 // if (item instanceof VariableField) { 1463 // int type = ((VariableField)item).getType(); 1464 // if (type != minField && type != maxField) break; 1465 // if (i < items.size() - 1) { 1466 // Object previousItem = items.get(items.size() - 1); 1467 // if (alpha.containsSome(previousItem.toString())) break; 1468 // } 1469 // int end = i-1; 1470 // if (end > 0) { 1471 // Object nextItem = items.get(end); 1472 // if (nextItem instanceof String) { 1473 // if (alpha.containsSome(nextItem.toString())) break; 1474 // end--; // otherwise skip over string 1475 // } 1476 // } 1477 // result.add(toString(0, end+1)); 1478 // break; 1479 // } 1480 // } 1481 // 1482 // return result; 1483 // } 1484 1485 // private static UnicodeSet alpha = new UnicodeSet("[:alphabetic:]"); 1486 1487 // private int getType(Object item) { 1488 // String s = item.toString(); 1489 // int canonicalIndex = getCanonicalIndex(s); 1490 // if (canonicalIndex < 0) { 1491 // throw new IllegalArgumentException("Illegal field:\t" 1492 // + s); 1493 // } 1494 // int type = types[canonicalIndex][1]; 1495 // return type; 1496 // } 1497 1498 /** 1499 * 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' ". 1500 * @param string The string to check. 1501 * @return string with quoted literals 1502 * @internal 1503 * @deprecated This API is ICU internal only. 1504 */ 1505 @Deprecated quoteLiteral(String string)1506 public Object quoteLiteral(String string) { 1507 return tokenizer.quoteLiteral(string); 1508 } 1509 1510 } 1511 1512 /** 1513 * Used by CLDR tooling; not in ICU4C. 1514 * Note, this will not work correctly with normal skeletons, since fields 1515 * that should be related in the two skeletons being compared - like EEE and 1516 * ccc, or y and U - will not be sorted in the same relative place as each 1517 * other when iterating over both TreeSets being compare, using TreeSet's 1518 * "natural" code point ordering (this could be addressed by initializing 1519 * the TreeSet with a comparator that compares fields first by their index 1520 * from getCanonicalIndex()). However if comparing canonical skeletons from 1521 * getCanonicalSkeletonAllowingDuplicates it will be OK regardless, since 1522 * in these skeletons all fields are normalized to the canonical pattern 1523 * char for those fields - M or L to M, E or c to E, y or U to y, etc. - 1524 * so corresponding fields will sort in the same way for both TreeMaps. 1525 * @internal 1526 * @deprecated This API is ICU internal only. 1527 */ 1528 @Deprecated skeletonsAreSimilar(String id, String skeleton)1529 public boolean skeletonsAreSimilar(String id, String skeleton) { 1530 if (id.equals(skeleton)) { 1531 return true; // fast path 1532 } 1533 // must clone array, make sure items are in same order. 1534 TreeSet<String> parser1 = getSet(id); 1535 TreeSet<String> parser2 = getSet(skeleton); 1536 if (parser1.size() != parser2.size()) { 1537 return false; 1538 } 1539 Iterator<String> it2 = parser2.iterator(); 1540 for (String item : parser1) { 1541 int index1 = getCanonicalIndex(item, false); 1542 String item2 = it2.next(); // same length so safe 1543 int index2 = getCanonicalIndex(item2, false); 1544 if (types[index1][1] != types[index2][1]) { 1545 return false; 1546 } 1547 } 1548 return true; 1549 } 1550 getSet(String id)1551 private TreeSet<String> getSet(String id) { 1552 final List<Object> items = fp.set(id).getItems(); 1553 TreeSet<String> result = new TreeSet<String>(); 1554 for (Object obj : items) { 1555 final String item = obj.toString(); 1556 if (item.startsWith("G") || item.startsWith("a")) { 1557 continue; 1558 } 1559 result.add(item); 1560 } 1561 return result; 1562 } 1563 1564 // ========= PRIVATES ============ 1565 1566 private static class PatternWithMatcher { 1567 public String pattern; 1568 public DateTimeMatcher matcherWithSkeleton; 1569 // Simple constructor PatternWithMatcher(String pat, DateTimeMatcher matcher)1570 public PatternWithMatcher(String pat, DateTimeMatcher matcher) { 1571 pattern = pat; 1572 matcherWithSkeleton = matcher; 1573 } 1574 } 1575 private static class PatternWithSkeletonFlag { 1576 public String pattern; 1577 public boolean skeletonWasSpecified; 1578 // Simple constructor PatternWithSkeletonFlag(String pat, boolean skelSpecified)1579 public PatternWithSkeletonFlag(String pat, boolean skelSpecified) { 1580 pattern = pat; 1581 skeletonWasSpecified = skelSpecified; 1582 } toString()1583 public String toString() { 1584 return pattern + "," + skeletonWasSpecified; 1585 } 1586 } 1587 private TreeMap<DateTimeMatcher, PatternWithSkeletonFlag> skeleton2pattern = new TreeMap<DateTimeMatcher, PatternWithSkeletonFlag>(); // items are in priority order 1588 private TreeMap<String, PatternWithSkeletonFlag> basePattern_pattern = new TreeMap<String, PatternWithSkeletonFlag>(); // items are in priority order 1589 private String decimal = "?"; 1590 private String dateTimeFormat = "{1} {0}"; 1591 private String[] appendItemFormats = new String[TYPE_LIMIT]; 1592 private String[] appendItemNames = new String[TYPE_LIMIT]; 1593 { 1594 for (int i = 0; i < TYPE_LIMIT; ++i) { 1595 appendItemFormats[i] = "{0} \u251C{2}: {1}\u2524"; 1596 appendItemNames[i] = "F" + i; 1597 } 1598 } 1599 private char defaultHourFormatChar = 'H'; 1600 //private boolean chineseMonthHack = false; 1601 //private boolean isComplete = false; 1602 private volatile boolean frozen = false; 1603 1604 private transient DateTimeMatcher current = new DateTimeMatcher(); 1605 private transient FormatParser fp = new FormatParser(); 1606 private transient DistanceInfo _distanceInfo = new DistanceInfo(); 1607 1608 private static final int FRACTIONAL_MASK = 1<<FRACTIONAL_SECOND; 1609 private static final int SECOND_AND_FRACTIONAL_MASK = (1<<SECOND) | (1<<FRACTIONAL_SECOND); 1610 1611 // Cache for DateTimePatternGenerator 1612 private static ICUCache<String, DateTimePatternGenerator> DTPNG_CACHE = new SimpleCache<String, DateTimePatternGenerator>(); 1613 checkFrozen()1614 private void checkFrozen() { 1615 if (isFrozen()) { 1616 throw new UnsupportedOperationException("Attempt to modify frozen object"); 1617 } 1618 } 1619 1620 /** 1621 * 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. 1622 * If we fail to find a complete skeleton, we compose in a loop until we have all the fields. 1623 */ getBestAppending(DateTimeMatcher source, int missingFields, DistanceInfo distInfo, DateTimeMatcher skipMatcher, EnumSet<DTPGflags> flags, int options)1624 private String getBestAppending(DateTimeMatcher source, int missingFields, DistanceInfo distInfo, DateTimeMatcher skipMatcher, EnumSet<DTPGflags> flags, int options) { 1625 String resultPattern = null; 1626 if (missingFields != 0) { 1627 PatternWithMatcher resultPatternWithMatcher = getBestRaw(source, missingFields, distInfo, skipMatcher); 1628 resultPattern = adjustFieldTypes(resultPatternWithMatcher, source, flags, options); 1629 1630 while (distInfo.missingFieldMask != 0) { // precondition: EVERY single field must work! 1631 1632 // special hack for SSS. If we are missing SSS, and we had ss but found it, replace the s field according to the 1633 // number separator 1634 if ((distInfo.missingFieldMask & SECOND_AND_FRACTIONAL_MASK) == FRACTIONAL_MASK 1635 && (missingFields & SECOND_AND_FRACTIONAL_MASK) == SECOND_AND_FRACTIONAL_MASK) { 1636 resultPatternWithMatcher.pattern = resultPattern; 1637 flags = EnumSet.copyOf(flags); 1638 flags.add(DTPGflags.FIX_FRACTIONAL_SECONDS); 1639 resultPattern = adjustFieldTypes(resultPatternWithMatcher, source, flags, options); 1640 distInfo.missingFieldMask &= ~FRACTIONAL_MASK; // remove bit 1641 continue; 1642 } 1643 1644 int startingMask = distInfo.missingFieldMask; 1645 PatternWithMatcher tempWithMatcher = getBestRaw(source, distInfo.missingFieldMask, distInfo, skipMatcher); 1646 String temp = adjustFieldTypes(tempWithMatcher, source, flags, options); 1647 int foundMask = startingMask & ~distInfo.missingFieldMask; 1648 int topField = getTopBitNumber(foundMask); 1649 resultPattern = MessageFormat.format(getAppendFormat(topField), new Object[]{resultPattern, temp, getAppendName(topField)}); 1650 } 1651 } 1652 return resultPattern; 1653 } 1654 getAppendName(int foundMask)1655 private String getAppendName(int foundMask) { 1656 return "'" + appendItemNames[foundMask] + "'"; 1657 } getAppendFormat(int foundMask)1658 private String getAppendFormat(int foundMask) { 1659 return appendItemFormats[foundMask]; 1660 } 1661 1662 // /** 1663 // * @param current2 1664 // * @return 1665 // */ 1666 // private String adjustSeconds(DateTimeMatcher current2) { 1667 // // TODO Auto-generated method stub 1668 // return null; 1669 // } 1670 1671 /** 1672 * @param foundMask 1673 */ getTopBitNumber(int foundMask)1674 private int getTopBitNumber(int foundMask) { 1675 int i = 0; 1676 while (foundMask != 0) { 1677 foundMask >>>= 1; 1678 ++i; 1679 } 1680 return i-1; 1681 } 1682 1683 /** 1684 * 1685 */ complete()1686 private void complete() { 1687 PatternInfo patternInfo = new PatternInfo(); 1688 // make sure that every valid field occurs once, with a "default" length 1689 for (int i = 0; i < CANONICAL_ITEMS.length; ++i) { 1690 //char c = (char)types[i][0]; 1691 addPattern(String.valueOf(CANONICAL_ITEMS[i]), false, patternInfo); 1692 } 1693 //isComplete = true; 1694 } 1695 { complete()1696 complete(); 1697 } 1698 1699 /** 1700 * 1701 */ getBestRaw(DateTimeMatcher source, int includeMask, DistanceInfo missingFields, DateTimeMatcher skipMatcher)1702 private PatternWithMatcher getBestRaw(DateTimeMatcher source, int includeMask, DistanceInfo missingFields, DateTimeMatcher skipMatcher) { 1703 // if (SHOW_DISTANCE) System.out.println("Searching for: " + source.pattern 1704 // + ", mask: " + showMask(includeMask)); 1705 int bestDistance = Integer.MAX_VALUE; 1706 PatternWithMatcher bestPatternWithMatcher = new PatternWithMatcher("", null); 1707 DistanceInfo tempInfo = new DistanceInfo(); 1708 for (DateTimeMatcher trial : skeleton2pattern.keySet()) { 1709 if (trial.equals(skipMatcher)) { 1710 continue; 1711 } 1712 int distance = source.getDistance(trial, includeMask, tempInfo); 1713 // if (SHOW_DISTANCE) System.out.println("\tDistance: " + trial.pattern + ":\t" 1714 // + distance + ",\tmissing fields: " + tempInfo); 1715 if (distance < bestDistance) { 1716 bestDistance = distance; 1717 PatternWithSkeletonFlag patternWithSkelFlag = skeleton2pattern.get(trial); 1718 bestPatternWithMatcher.pattern = patternWithSkelFlag.pattern; 1719 // If the best raw match had a specified skeleton then return it too. 1720 // This can be passed through to adjustFieldTypes to help it do a better job. 1721 if (patternWithSkelFlag.skeletonWasSpecified) { 1722 bestPatternWithMatcher.matcherWithSkeleton = trial; 1723 } else { 1724 bestPatternWithMatcher.matcherWithSkeleton = null; 1725 } 1726 missingFields.setTo(tempInfo); 1727 if (distance == 0) { 1728 break; 1729 } 1730 } 1731 } 1732 return bestPatternWithMatcher; 1733 } 1734 1735 /* 1736 * @param fixFractionalSeconds TODO 1737 */ 1738 // flags values 1739 private enum DTPGflags { FIX_FRACTIONAL_SECONDS, SKELETON_USES_CAP_J }; 1740 adjustFieldTypes(PatternWithMatcher patternWithMatcher, DateTimeMatcher inputRequest, EnumSet<DTPGflags> flags, int options)1741 private String adjustFieldTypes(PatternWithMatcher patternWithMatcher, DateTimeMatcher inputRequest, EnumSet<DTPGflags> flags, int options) { 1742 fp.set(patternWithMatcher.pattern); 1743 StringBuilder newPattern = new StringBuilder(); 1744 for (Object item : fp.getItems()) { 1745 if (item instanceof String) { 1746 newPattern.append(fp.quoteLiteral((String)item)); 1747 } else { 1748 final VariableField variableField = (VariableField) item; 1749 StringBuilder fieldBuilder = new StringBuilder(variableField.toString()); 1750 // int canonicalIndex = getCanonicalIndex(field, true); 1751 // if (canonicalIndex < 0) { 1752 // continue; // don't adjust 1753 // } 1754 // int type = types[canonicalIndex][1]; 1755 int type = variableField.getType(); 1756 1757 if (flags.contains(DTPGflags.FIX_FRACTIONAL_SECONDS) && type == SECOND) { 1758 String newField = inputRequest.original[FRACTIONAL_SECOND]; 1759 fieldBuilder.append(decimal); 1760 fieldBuilder.append(newField); 1761 } else if (inputRequest.type[type] != 0) { 1762 // Here: 1763 // - "reqField" is the field from the originally requested skeleton, with length 1764 // "reqFieldLen". 1765 // - "field" is the field from the found pattern. 1766 // 1767 // The adjusted field should consist of characters from the originally requested 1768 // skeleton, except in the case of HOUR or MONTH or WEEKDAY or YEAR, in which case it 1769 // should consist of characters from the found pattern. 1770 // 1771 // The length of the adjusted field (adjFieldLen) should match that in the originally 1772 // requested skeleton, except that in the following cases the length of the adjusted field 1773 // should match that in the found pattern (i.e. the length of this pattern field should 1774 // not be adjusted): 1775 // 1. type is HOUR and the corresponding bit in options is not set (ticket #7180). 1776 // Note, we may want to implement a similar change for other numeric fields (MM, dd, 1777 // etc.) so the default behavior is to get locale preference for field length, but 1778 // options bits can be used to override this. 1779 // 2. There is a specified skeleton for the found pattern and one of the following is true: 1780 // a) The length of the field in the skeleton (skelFieldLen) is equal to reqFieldLen. 1781 // b) The pattern field is numeric and the skeleton field is not, or vice versa. 1782 // 1783 // Old behavior was: 1784 // normally we just replace the field. However HOUR is special; we only change the length 1785 1786 String reqField = inputRequest.original[type]; 1787 int reqFieldLen = reqField.length(); 1788 if ( reqField.charAt(0) == 'E' && reqFieldLen < 3 ) { 1789 reqFieldLen = 3; // 1-3 for E are equivalent to 3 for c,e 1790 } 1791 int adjFieldLen = reqFieldLen; 1792 DateTimeMatcher matcherWithSkeleton = patternWithMatcher.matcherWithSkeleton; 1793 if ( (type == HOUR && (options & MATCH_HOUR_FIELD_LENGTH)==0) || 1794 (type == MINUTE && (options & MATCH_MINUTE_FIELD_LENGTH)==0) || 1795 (type == SECOND && (options & MATCH_SECOND_FIELD_LENGTH)==0) ) { 1796 adjFieldLen = fieldBuilder.length(); 1797 } else if (matcherWithSkeleton != null) { 1798 String skelField = matcherWithSkeleton.origStringForField(type); 1799 int skelFieldLen = skelField.length(); 1800 boolean patFieldIsNumeric = variableField.isNumeric(); 1801 boolean skelFieldIsNumeric = matcherWithSkeleton.fieldIsNumeric(type); 1802 if (skelFieldLen == reqFieldLen || (patFieldIsNumeric && !skelFieldIsNumeric) || (skelFieldIsNumeric && !patFieldIsNumeric)) { 1803 // don't adjust the field length in the found pattern 1804 adjFieldLen = fieldBuilder.length(); 1805 } 1806 } 1807 char c = (type != HOUR && type != MONTH && type != WEEKDAY && (type != YEAR || reqField.charAt(0)=='Y'))? 1808 reqField.charAt(0): fieldBuilder.charAt(0); 1809 if (type == HOUR && flags.contains(DTPGflags.SKELETON_USES_CAP_J)) { 1810 c = defaultHourFormatChar; 1811 } 1812 fieldBuilder = new StringBuilder(); 1813 for (int i = adjFieldLen; i > 0; --i) fieldBuilder.append(c); 1814 } 1815 newPattern.append(fieldBuilder); 1816 } 1817 } 1818 //if (SHOW_DISTANCE) System.out.println("\tRaw: " + pattern); 1819 return newPattern.toString(); 1820 } 1821 1822 // public static String repeat(String s, int count) { 1823 // StringBuffer result = new StringBuffer(); 1824 // for (int i = 0; i < count; ++i) { 1825 // result.append(s); 1826 // } 1827 // return result.toString(); 1828 // } 1829 1830 /** 1831 * internal routine 1832 * @param pattern The pattern that is passed. 1833 * @return field value 1834 * @internal 1835 * @deprecated This API is ICU internal only. 1836 */ 1837 @Deprecated getFields(String pattern)1838 public String getFields(String pattern) { 1839 fp.set(pattern); 1840 StringBuilder newPattern = new StringBuilder(); 1841 for (Object item : fp.getItems()) { 1842 if (item instanceof String) { 1843 newPattern.append(fp.quoteLiteral((String)item)); 1844 } else { 1845 newPattern.append("{" + getName(item.toString()) + "}"); 1846 } 1847 } 1848 return newPattern.toString(); 1849 } 1850 showMask(int mask)1851 private static String showMask(int mask) { 1852 StringBuilder result = new StringBuilder(); 1853 for (int i = 0; i < TYPE_LIMIT; ++i) { 1854 if ((mask & (1<<i)) == 0) 1855 continue; 1856 if (result.length() != 0) 1857 result.append(" | "); 1858 result.append(FIELD_NAME[i]); 1859 result.append(" "); 1860 } 1861 return result.toString(); 1862 } 1863 1864 private static final String[] CLDR_FIELD_APPEND = { 1865 "Era", "Year", "Quarter", "Month", "Week", "*", "Day-Of-Week", 1866 "Day", "*", "*", "*", 1867 "Hour", "Minute", "Second", "*", "Timezone" 1868 }; 1869 1870 private static final String[] CLDR_FIELD_NAME = { 1871 "era", "year", "*", "month", "week", "*", "weekday", 1872 "day", "*", "*", "dayperiod", 1873 "hour", "minute", "second", "*", "zone" 1874 }; 1875 1876 private static final String[] FIELD_NAME = { 1877 "Era", "Year", "Quarter", "Month", "Week_in_Year", "Week_in_Month", "Weekday", 1878 "Day", "Day_Of_Year", "Day_of_Week_in_Month", "Dayperiod", 1879 "Hour", "Minute", "Second", "Fractional_Second", "Zone" 1880 }; 1881 1882 1883 private static final String[] CANONICAL_ITEMS = { 1884 "G", "y", "Q", "M", "w", "W", "E", 1885 "d", "D", "F", 1886 "H", "m", "s", "S", "v" 1887 }; 1888 1889 private static final Set<String> CANONICAL_SET = new HashSet<String>(Arrays.asList(CANONICAL_ITEMS)); 1890 private Set<String> cldrAvailableFormatKeys = new HashSet<String>(20); 1891 1892 private static final int 1893 DATE_MASK = (1<<DAYPERIOD) - 1, 1894 TIME_MASK = (1<<TYPE_LIMIT) - 1 - DATE_MASK; 1895 1896 private static final int // numbers are chosen to express 'distance' 1897 DELTA = 0x10, 1898 NUMERIC = 0x100, 1899 NONE = 0, 1900 NARROW = -0x101, 1901 SHORT = -0x102, 1902 LONG = -0x103, 1903 EXTRA_FIELD = 0x10000, 1904 MISSING_FIELD = 0x1000; 1905 1906 getName(String s)1907 static private String getName(String s) { 1908 int i = getCanonicalIndex(s, true); 1909 String name = FIELD_NAME[types[i][1]]; 1910 int subtype = types[i][2]; 1911 boolean string = subtype < 0; 1912 if (string) subtype = -subtype; 1913 if (subtype < 0) name += ":S"; 1914 else name += ":N"; 1915 return name; 1916 } 1917 1918 /** 1919 * Get the canonical index, or return -1 if illegal. 1920 * @param s 1921 * @param strict TODO 1922 */ 1923 private static int getCanonicalIndex(String s, boolean strict) { 1924 int len = s.length(); 1925 if (len == 0) { 1926 return -1; 1927 } 1928 int ch = s.charAt(0); 1929 // verify that all are the same character 1930 for (int i = 1; i < len; ++i) { 1931 if (s.charAt(i) != ch) { 1932 return -1; 1933 } 1934 } 1935 int bestRow = -1; 1936 for (int i = 0; i < types.length; ++i) { 1937 int[] row = types[i]; 1938 if (row[0] != ch) continue; 1939 bestRow = i; 1940 if (row[3] > len) continue; 1941 if (row[row.length-1] < len) continue; 1942 return i; 1943 } 1944 return strict ? -1 : bestRow; 1945 } 1946 1947 private static final int[][] types = { 1948 // the order here makes a difference only when searching for single field. 1949 // format is: 1950 // pattern character, main type, weight, min length, weight 1951 {'G', ERA, SHORT, 1, 3}, 1952 {'G', ERA, LONG, 4}, 1953 1954 {'y', YEAR, NUMERIC, 1, 20}, 1955 {'Y', YEAR, NUMERIC + DELTA, 1, 20}, 1956 {'u', YEAR, NUMERIC + 2*DELTA, 1, 20}, 1957 {'r', YEAR, NUMERIC + 3*DELTA, 1, 20}, 1958 {'U', YEAR, SHORT, 1, 3}, 1959 {'U', YEAR, LONG, 4}, 1960 {'U', YEAR, NARROW, 5}, 1961 1962 {'Q', QUARTER, NUMERIC, 1, 2}, 1963 {'Q', QUARTER, SHORT, 3}, 1964 {'Q', QUARTER, LONG, 4}, 1965 1966 {'q', QUARTER, NUMERIC + DELTA, 1, 2}, 1967 {'q', QUARTER, SHORT + DELTA, 3}, 1968 {'q', QUARTER, LONG + DELTA, 4}, 1969 1970 {'M', MONTH, NUMERIC, 1, 2}, 1971 {'M', MONTH, SHORT, 3}, 1972 {'M', MONTH, LONG, 4}, 1973 {'M', MONTH, NARROW, 5}, 1974 {'L', MONTH, NUMERIC + DELTA, 1, 2}, 1975 {'L', MONTH, SHORT - DELTA, 3}, 1976 {'L', MONTH, LONG - DELTA, 4}, 1977 {'L', MONTH, NARROW - DELTA, 5}, 1978 1979 {'l', MONTH, NUMERIC + DELTA, 1, 1}, 1980 1981 {'w', WEEK_OF_YEAR, NUMERIC, 1, 2}, 1982 {'W', WEEK_OF_MONTH, NUMERIC + DELTA, 1}, 1983 1984 {'E', WEEKDAY, SHORT, 1, 3}, 1985 {'E', WEEKDAY, LONG, 4}, 1986 {'E', WEEKDAY, NARROW, 5}, 1987 {'c', WEEKDAY, NUMERIC + 2*DELTA, 1, 2}, 1988 {'c', WEEKDAY, SHORT - 2*DELTA, 3}, 1989 {'c', WEEKDAY, LONG - 2*DELTA, 4}, 1990 {'c', WEEKDAY, NARROW - 2*DELTA, 5}, 1991 {'e', WEEKDAY, NUMERIC + DELTA, 1, 2}, // 'e' is currently not used in CLDR data, should not be canonical 1992 {'e', WEEKDAY, SHORT - DELTA, 3}, 1993 {'e', WEEKDAY, LONG - DELTA, 4}, 1994 {'e', WEEKDAY, NARROW - DELTA, 5}, 1995 1996 {'d', DAY, NUMERIC, 1, 2}, 1997 {'D', DAY_OF_YEAR, NUMERIC + DELTA, 1, 3}, 1998 {'F', DAY_OF_WEEK_IN_MONTH, NUMERIC + 2*DELTA, 1}, 1999 {'g', DAY, NUMERIC + 3*DELTA, 1, 20}, // really internal use, so we don't care 2000 2001 {'a', DAYPERIOD, SHORT, 1}, 2002 2003 {'H', HOUR, NUMERIC + 10*DELTA, 1, 2}, // 24 hour 2004 {'k', HOUR, NUMERIC + 11*DELTA, 1, 2}, 2005 {'h', HOUR, NUMERIC, 1, 2}, // 12 hour 2006 {'K', HOUR, NUMERIC + DELTA, 1, 2}, 2007 2008 {'m', MINUTE, NUMERIC, 1, 2}, 2009 2010 {'s', SECOND, NUMERIC, 1, 2}, 2011 {'S', FRACTIONAL_SECOND, NUMERIC + DELTA, 1, 1000}, 2012 {'A', SECOND, NUMERIC + 2*DELTA, 1, 1000}, 2013 2014 {'v', ZONE, SHORT - 2*DELTA, 1}, 2015 {'v', ZONE, LONG - 2*DELTA, 4}, 2016 {'z', ZONE, SHORT, 1, 3}, 2017 {'z', ZONE, LONG, 4}, 2018 {'Z', ZONE, NARROW - DELTA, 1, 3}, 2019 {'Z', ZONE, LONG - DELTA, 4}, 2020 {'Z', ZONE, SHORT - DELTA, 5}, 2021 {'O', ZONE, SHORT - DELTA, 1}, 2022 {'O', ZONE, LONG - DELTA, 4}, 2023 {'V', ZONE, SHORT - DELTA, 1}, 2024 {'V', ZONE, LONG - DELTA, 2}, 2025 {'X', ZONE, NARROW - DELTA, 1}, 2026 {'X', ZONE, SHORT - DELTA, 2}, 2027 {'X', ZONE, LONG - DELTA, 4}, 2028 {'x', ZONE, NARROW - DELTA, 1}, 2029 {'x', ZONE, SHORT - DELTA, 2}, 2030 {'x', ZONE, LONG - DELTA, 4}, 2031 }; 2032 2033 private static class DateTimeMatcher implements Comparable<DateTimeMatcher> { 2034 //private String pattern = null; 2035 private int[] type = new int[TYPE_LIMIT]; 2036 private String[] original = new String[TYPE_LIMIT]; 2037 private String[] baseOriginal = new String[TYPE_LIMIT]; 2038 2039 // just for testing; fix to make multi-threaded later 2040 // private static FormatParser fp = new FormatParser(); 2041 2042 public String origStringForField(int field) { 2043 return original[field]; 2044 } 2045 2046 public boolean fieldIsNumeric(int field) { 2047 return type[field] > 0; 2048 } 2049 2050 public String toString() { 2051 StringBuilder result = new StringBuilder(); 2052 for (int i = 0; i < TYPE_LIMIT; ++i) { 2053 if (original[i].length() != 0) result.append(original[i]); 2054 } 2055 return result.toString(); 2056 } 2057 2058 // returns a string like toString but using the canonical character for most types, 2059 // e.g. M for M or L, E for E or c, y for y or U, etc. The hour field is canonicalized 2060 // to 'H' (for 24-hour types) or 'h' (for 12-hour types) 2061 public String toCanonicalString() { 2062 StringBuilder result = new StringBuilder(); 2063 for (int i = 0; i < TYPE_LIMIT; ++i) { 2064 if (original[i].length() != 0) { 2065 // append a string of the same length using the canonical character 2066 for (int j = 0; j < types.length; ++j) { 2067 int[] row = types[j]; 2068 if (row[1] == i) { 2069 char originalChar = original[i].charAt(0); 2070 char repeatChar = (originalChar=='h' || originalChar=='K')? 'h': (char)row[0]; 2071 result.append(Utility.repeat(String.valueOf(repeatChar), original[i].length())); 2072 break; 2073 } 2074 } 2075 } 2076 } 2077 return result.toString(); 2078 } 2079 2080 String getBasePattern() { 2081 StringBuilder result = new StringBuilder(); 2082 for (int i = 0; i < TYPE_LIMIT; ++i) { 2083 if (baseOriginal[i].length() != 0) result.append(baseOriginal[i]); 2084 } 2085 return result.toString(); 2086 } 2087 2088 DateTimeMatcher set(String pattern, FormatParser fp, boolean allowDuplicateFields) { 2089 for (int i = 0; i < TYPE_LIMIT; ++i) { 2090 type[i] = NONE; 2091 original[i] = ""; 2092 baseOriginal[i] = ""; 2093 } 2094 fp.set(pattern); 2095 for (Object obj : fp.getItems()) { 2096 if (!(obj instanceof VariableField)) { 2097 continue; 2098 } 2099 VariableField item = (VariableField)obj; 2100 String field = item.toString(); 2101 if (field.charAt(0) == 'a') continue; // skip day period, special case 2102 int canonicalIndex = item.getCanonicalIndex(); 2103 // if (canonicalIndex < 0) { 2104 // throw new IllegalArgumentException("Illegal field:\t" 2105 // + field + "\t in " + pattern); 2106 // } 2107 int[] row = types[canonicalIndex]; 2108 int typeValue = row[1]; 2109 if (original[typeValue].length() != 0) { 2110 if ( allowDuplicateFields || 2111 (original[typeValue].charAt(0) == 'r' && field.charAt(0) == 'U') || 2112 (original[typeValue].charAt(0) == 'U' && field.charAt(0) == 'r') ) { 2113 continue; 2114 } 2115 throw new IllegalArgumentException("Conflicting fields:\t" 2116 + original[typeValue] + ", " + field + "\t in " + pattern); 2117 } 2118 original[typeValue] = field; 2119 char repeatChar = (char)row[0]; 2120 int repeatCount = row[3]; 2121 // #7930 removes hack to cap repeatCount at 3 2122 if ("GEzvQ".indexOf(repeatChar) >= 0) repeatCount = 1; 2123 baseOriginal[typeValue] = Utility.repeat(String.valueOf(repeatChar),repeatCount); 2124 int subTypeValue = row[2]; 2125 if (subTypeValue > 0) subTypeValue += field.length(); 2126 type[typeValue] = subTypeValue; 2127 } 2128 return this; 2129 } 2130 2131 /** 2132 * 2133 */ 2134 int getFieldMask() { 2135 int result = 0; 2136 for (int i = 0; i < type.length; ++i) { 2137 if (type[i] != 0) result |= (1<<i); 2138 } 2139 return result; 2140 } 2141 2142 /** 2143 * 2144 */ 2145 @SuppressWarnings("unused") 2146 void extractFrom(DateTimeMatcher source, int fieldMask) { 2147 for (int i = 0; i < type.length; ++i) { 2148 if ((fieldMask & (1<<i)) != 0) { 2149 type[i] = source.type[i]; 2150 original[i] = source.original[i]; 2151 } else { 2152 type[i] = NONE; 2153 original[i] = ""; 2154 } 2155 } 2156 } 2157 2158 int getDistance(DateTimeMatcher other, int includeMask, DistanceInfo distanceInfo) { 2159 int result = 0; 2160 distanceInfo.clear(); 2161 for (int i = 0; i < type.length; ++i) { 2162 int myType = (includeMask & (1<<i)) == 0 ? 0 : type[i]; 2163 int otherType = other.type[i]; 2164 if (myType == otherType) continue; // identical (maybe both zero) add 0 2165 if (myType == 0) { // and other is not 2166 result += EXTRA_FIELD; 2167 distanceInfo.addExtra(i); 2168 } else if (otherType == 0) { // and mine is not 2169 result += MISSING_FIELD; 2170 distanceInfo.addMissing(i); 2171 } else { 2172 result += Math.abs(myType - otherType); // square of mismatch 2173 } 2174 } 2175 return result; 2176 } 2177 2178 public int compareTo(DateTimeMatcher that) { 2179 for (int i = 0; i < original.length; ++i) { 2180 int comp = original[i].compareTo(that.original[i]); 2181 if (comp != 0) return -comp; 2182 } 2183 return 0; 2184 } 2185 2186 public boolean equals(Object other) { 2187 if (!(other instanceof DateTimeMatcher)) { 2188 return false; 2189 } 2190 DateTimeMatcher that = (DateTimeMatcher) other; 2191 for (int i = 0; i < original.length; ++i) { 2192 if (!original[i].equals(that.original[i])) return false; 2193 } 2194 return true; 2195 } 2196 public int hashCode() { 2197 int result = 0; 2198 for (int i = 0; i < original.length; ++i) { 2199 result ^= original[i].hashCode(); 2200 } 2201 return result; 2202 } 2203 } 2204 2205 private static class DistanceInfo { 2206 int missingFieldMask; 2207 int extraFieldMask; 2208 void clear() { 2209 missingFieldMask = extraFieldMask = 0; 2210 } 2211 /** 2212 * 2213 */ 2214 void setTo(DistanceInfo other) { 2215 missingFieldMask = other.missingFieldMask; 2216 extraFieldMask = other.extraFieldMask; 2217 } 2218 void addMissing(int field) { 2219 missingFieldMask |= (1<<field); 2220 } 2221 void addExtra(int field) { 2222 extraFieldMask |= (1<<field); 2223 } 2224 public String toString() { 2225 return "missingFieldMask: " + DateTimePatternGenerator.showMask(missingFieldMask) 2226 + ", extraFieldMask: " + DateTimePatternGenerator.showMask(extraFieldMask); 2227 } 2228 } 2229 } 2230 //eof 2231