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