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