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) 2009-2011, International Business Machines Corporation and * 7 * others. All Rights Reserved. * 8 ****************************************************************************** 9 */ 10 11 package ohos.global.icu.impl.duration.impl; 12 13 import java.util.Arrays; 14 15 import ohos.global.icu.impl.duration.TimeUnit; 16 import ohos.global.icu.impl.duration.impl.DataRecord.ECountVariant; 17 import ohos.global.icu.impl.duration.impl.DataRecord.EDecimalHandling; 18 import ohos.global.icu.impl.duration.impl.DataRecord.EFractionHandling; 19 import ohos.global.icu.impl.duration.impl.DataRecord.EGender; 20 import ohos.global.icu.impl.duration.impl.DataRecord.EHalfPlacement; 21 import ohos.global.icu.impl.duration.impl.DataRecord.EHalfSupport; 22 import ohos.global.icu.impl.duration.impl.DataRecord.ENumberSystem; 23 import ohos.global.icu.impl.duration.impl.DataRecord.EPluralization; 24 import ohos.global.icu.impl.duration.impl.DataRecord.EUnitVariant; 25 import ohos.global.icu.impl.duration.impl.DataRecord.EZeroHandling; 26 import ohos.global.icu.impl.duration.impl.DataRecord.ScopeData; 27 28 29 /** 30 * PeriodFormatterData provides locale-specific data used to format 31 * relative dates and times, and convenience api to access it. 32 * 33 * An instance of PeriodFormatterData is usually created by requesting 34 * data for a given locale from an PeriodFormatterDataService. 35 * @hide exposed on OHOS 36 */ 37 public class PeriodFormatterData { 38 final DataRecord dr; 39 String localeName; 40 41 // debug 42 public static boolean trace = false; 43 PeriodFormatterData(String localeName, DataRecord dr)44 public PeriodFormatterData(String localeName, DataRecord dr) { 45 this.dr = dr; 46 this.localeName = localeName; 47 if(localeName == null) { 48 throw new NullPointerException("localename is null"); 49 } 50 // System.err.println("** localeName is " + localeName); 51 if (dr == null) { 52 // Thread.dumpStack(); 53 throw new NullPointerException("data record is null"); 54 } 55 } 56 57 // none - chinese (all forms the same) 58 // plural - english, special form for 1 59 // dual - special form for 1 and 2 60 // paucal - russian, special form for 1, for 2-4 and n > 20 && n % 10 == 2-4 61 // rpt_dual_few - slovenian, special form for 1, 2, 3-4 and n as above 62 // hebrew, dual plus singular form for years > 11 63 // arabic, dual, plus singular form for all terms > 10 64 65 /** 66 * Return the pluralization format used by this locale. 67 * @return the pluralization format 68 */ pluralization()69 public int pluralization() { 70 return dr.pl; 71 } 72 73 /** 74 * Return true if zeros are allowed in the display. 75 * @return true if zeros should be allowed 76 */ allowZero()77 public boolean allowZero() { 78 return dr.allowZero; 79 } 80 weeksAloneOnly()81 public boolean weeksAloneOnly() { 82 return dr.weeksAloneOnly; 83 } 84 useMilliseconds()85 public int useMilliseconds() { 86 return dr.useMilliseconds; 87 } 88 89 /** 90 * Append the appropriate prefix to the string builder, depending on whether and 91 * how a limit and direction are to be displayed. 92 * 93 * @param tl how and whether to display the time limit 94 * @param td how and whether to display the time direction 95 * @param sb the string builder to which to append the text 96 * @return true if a following digit will require a digit prefix 97 */ appendPrefix(int tl, int td, StringBuffer sb)98 public boolean appendPrefix(int tl, int td, StringBuffer sb) { 99 if (dr.scopeData != null) { 100 int ix = tl * 3 + td; 101 ScopeData sd = dr.scopeData[ix]; 102 if (sd != null) { 103 String prefix = sd.prefix; 104 if (prefix != null) { 105 sb.append(prefix); 106 return sd.requiresDigitPrefix; 107 } 108 } 109 } 110 return false; 111 } 112 113 /** 114 * Append the appropriate suffix to the string builder, depending on whether and 115 * how a limit and direction are to be displayed. 116 * 117 * @param tl how and whether to display the time limit 118 * @param td how and whether to display the time direction 119 * @param sb the string builder to which to append the text 120 */ appendSuffix(int tl, int td, StringBuffer sb)121 public void appendSuffix(int tl, int td, StringBuffer sb) { 122 if (dr.scopeData != null) { 123 int ix = tl * 3 + td; 124 ScopeData sd = dr.scopeData[ix]; 125 if (sd != null) { 126 String suffix = sd.suffix; 127 if (suffix != null) { 128 if (trace) { 129 System.out.println("appendSuffix '" + suffix + "'"); 130 } 131 sb.append(suffix); 132 } 133 } 134 } 135 } 136 137 /** 138 * Append the count and unit to the string builder. 139 * 140 * @param unit the unit to append 141 * @param count the count of units, * 1000 142 * @param cv the format to use for displaying the count 143 * @param uv the format to use for displaying the unit 144 * @param useCountSep if false, force no separator between count and unit 145 * @param useDigitPrefix if true, use the digit prefix 146 * @param multiple true if there are multiple units in this string 147 * @param last true if this is the last unit 148 * @param wasSkipped true if the unit(s) before this were skipped 149 * @param sb the string builder to which to append the text 150 * @return true if will require skip marker 151 */ 152 @SuppressWarnings("fallthrough") appendUnit(TimeUnit unit, int count, int cv, int uv, boolean useCountSep, boolean useDigitPrefix, boolean multiple, boolean last, boolean wasSkipped, StringBuffer sb)153 public boolean appendUnit(TimeUnit unit, int count, int cv, 154 int uv, boolean useCountSep, 155 boolean useDigitPrefix, boolean multiple, 156 boolean last, boolean wasSkipped, 157 StringBuffer sb) { 158 int px = unit.ordinal(); 159 160 boolean willRequireSkipMarker = false; 161 if (dr.requiresSkipMarker != null && dr.requiresSkipMarker[px] && 162 dr.skippedUnitMarker != null) { 163 if (!wasSkipped && last) { 164 sb.append(dr.skippedUnitMarker); 165 } 166 willRequireSkipMarker = true; 167 } 168 169 if (uv != EUnitVariant.PLURALIZED) { 170 boolean useMedium = uv == EUnitVariant.MEDIUM; 171 String[] names = useMedium ? dr.mediumNames : dr.shortNames; 172 if (names == null || names[px] == null) { 173 names = useMedium ? dr.shortNames : dr.mediumNames; 174 } 175 if (names != null && names[px] != null) { 176 appendCount(unit, false, false, count, cv, useCountSep, 177 names[px], last, sb); // omit suffix, ok? 178 return false; // omit skip marker 179 } 180 } 181 182 // check cv 183 if (cv == ECountVariant.HALF_FRACTION && dr.halfSupport != null) { 184 switch (dr.halfSupport[px]) { 185 case EHalfSupport.YES: break; 186 case EHalfSupport.ONE_PLUS: 187 if (count > 1000) { 188 break; 189 } 190 // else fall through to decimal 191 case EHalfSupport.NO: { 192 count = (count / 500) * 500; // round to 1/2 193 cv = ECountVariant.DECIMAL1; 194 } break; 195 } 196 } 197 198 String name = null; 199 int form = computeForm(unit, count, cv, multiple && last); 200 if (form == FORM_SINGULAR_SPELLED) { 201 if (dr.singularNames == null) { 202 form = FORM_SINGULAR; 203 name = dr.pluralNames[px][form]; 204 } else { 205 name = dr.singularNames[px]; 206 } 207 } else if (form == FORM_SINGULAR_NO_OMIT) { 208 name = dr.pluralNames[px][FORM_SINGULAR]; 209 } else if (form == FORM_HALF_SPELLED) { 210 name = dr.halfNames[px]; 211 } else { 212 try { 213 name = dr.pluralNames[px][form]; 214 } catch (NullPointerException e) { 215 System.out.println("Null Pointer in PeriodFormatterData["+localeName+"].au px: " + px + " form: " + form + " pn: " + Arrays.toString(dr.pluralNames)); 216 throw e; 217 } 218 } 219 if (name == null) { 220 form = FORM_PLURAL; 221 name = dr.pluralNames[px][form]; 222 } 223 224 boolean omitCount = 225 (form == FORM_SINGULAR_SPELLED || form == FORM_HALF_SPELLED) || 226 (dr.omitSingularCount && form == FORM_SINGULAR) || 227 (dr.omitDualCount && form == FORM_DUAL); 228 229 int suffixIndex = appendCount(unit, omitCount, useDigitPrefix, count, cv, 230 useCountSep, name, last, sb); 231 if (last && suffixIndex >= 0) { 232 String suffix = null; 233 if (dr.rqdSuffixes != null && suffixIndex < dr.rqdSuffixes.length) { 234 suffix = dr.rqdSuffixes[suffixIndex]; 235 } 236 if (suffix == null && dr.optSuffixes != null && 237 suffixIndex < dr.optSuffixes.length) { 238 suffix = dr.optSuffixes[suffixIndex]; 239 } 240 if (suffix != null) { 241 sb.append(suffix); 242 } 243 } 244 return willRequireSkipMarker; 245 } 246 247 /** 248 * Append a count to the string builder. 249 * 250 * @param unit the unit 251 * @param count the count 252 * @param cv the format to use for displaying the count 253 * @param useSep whether to use the count separator, if available 254 * @param name the term name 255 * @param last true if this is the last unit to be formatted 256 * @param sb the string builder to which to append the text 257 * @return index to use if might have required or optional suffix, or -1 if none required 258 */ appendCount(TimeUnit unit, boolean omitCount, boolean useDigitPrefix, int count, int cv, boolean useSep, String name, boolean last, StringBuffer sb)259 public int appendCount(TimeUnit unit, boolean omitCount, 260 boolean useDigitPrefix, 261 int count, int cv, boolean useSep, 262 String name, boolean last, StringBuffer sb) { 263 if (cv == ECountVariant.HALF_FRACTION && dr.halves == null) { 264 cv = ECountVariant.INTEGER; 265 } 266 267 if (!omitCount && useDigitPrefix && dr.digitPrefix != null) { 268 sb.append(dr.digitPrefix); 269 } 270 271 int index = unit.ordinal(); 272 switch (cv) { 273 case ECountVariant.INTEGER: { 274 if (!omitCount) { 275 appendInteger(count/1000, 1, 10, sb); 276 } 277 } break; 278 279 case ECountVariant.INTEGER_CUSTOM: { 280 int val = count / 1000; 281 // only custom names we have for now 282 if (unit == TimeUnit.MINUTE && 283 (dr.fiveMinutes != null || dr.fifteenMinutes != null)) { 284 if (val != 0 && val % 5 == 0) { 285 if (dr.fifteenMinutes != null && (val == 15 || val == 45)) { 286 val = val == 15 ? 1 : 3; 287 if (!omitCount) appendInteger(val, 1, 10, sb); 288 name = dr.fifteenMinutes; 289 index = 8; // hack 290 break; 291 } 292 if (dr.fiveMinutes != null) { 293 val = val / 5; 294 if (!omitCount) appendInteger(val, 1, 10, sb); 295 name = dr.fiveMinutes; 296 index = 9; // hack 297 break; 298 } 299 } 300 } 301 if (!omitCount) appendInteger(val, 1, 10, sb); 302 } break; 303 304 case ECountVariant.HALF_FRACTION: { 305 // 0, 1/2, 1, 1-1/2... 306 int v = count / 500; 307 if (v != 1) { 308 if (!omitCount) appendCountValue(count, 1, 0, sb); 309 } 310 if ((v & 0x1) == 1) { 311 // hack, using half name 312 if (v == 1 && dr.halfNames != null && dr.halfNames[index] != null) { 313 sb.append(name); 314 return last ? index : -1; 315 } 316 317 int solox = v == 1 ? 0 : 1; 318 if (dr.genders != null && dr.halves.length > 2) { 319 if (dr.genders[index] == EGender.F) { 320 solox += 2; 321 } 322 } 323 int hp = dr.halfPlacements == null 324 ? EHalfPlacement.PREFIX 325 : dr.halfPlacements[solox & 0x1]; 326 String half = dr.halves[solox]; 327 String measure = dr.measures == null ? null : dr.measures[index]; 328 switch (hp) { 329 case EHalfPlacement.PREFIX: 330 sb.append(half); 331 break; 332 case EHalfPlacement.AFTER_FIRST: { 333 if (measure != null) { 334 sb.append(measure); 335 sb.append(half); 336 if (useSep && !omitCount) { 337 sb.append(dr.countSep); 338 } 339 sb.append(name); 340 } else { // ignore sep completely 341 sb.append(name); 342 sb.append(half); 343 return last ? index : -1; // might use suffix 344 } 345 } return -1; // exit early 346 case EHalfPlacement.LAST: { 347 if (measure != null) { 348 sb.append(measure); 349 } 350 if (useSep && !omitCount) { 351 sb.append(dr.countSep); 352 } 353 sb.append(name); 354 sb.append(half); 355 } return last ? index : -1; // might use suffix 356 } 357 } 358 } break; 359 default: { 360 int decimals = 1; 361 switch (cv) { 362 case ECountVariant.DECIMAL2: decimals = 2; break; 363 case ECountVariant.DECIMAL3: decimals = 3; break; 364 default: break; 365 } 366 if (!omitCount) appendCountValue(count, 1, decimals, sb); 367 } break; 368 } 369 if (!omitCount && useSep) { 370 sb.append(dr.countSep); 371 } 372 if (!omitCount && dr.measures != null && index < dr.measures.length) { 373 String measure = dr.measures[index]; 374 if (measure != null) { 375 sb.append(measure); 376 } 377 } 378 sb.append(name); 379 return last ? index : -1; 380 } 381 382 /** 383 * Append a count value to the builder. 384 * 385 * @param count the count 386 * @param integralDigits the number of integer digits to display 387 * @param decimalDigits the number of decimal digits to display, <= 3 388 * @param sb the string builder to which to append the text 389 */ appendCountValue(int count, int integralDigits, int decimalDigits, StringBuffer sb)390 public void appendCountValue(int count, int integralDigits, 391 int decimalDigits, StringBuffer sb) { 392 int ival = count / 1000; 393 if (decimalDigits == 0) { 394 appendInteger(ival, integralDigits, 10, sb); 395 return; 396 } 397 398 if (dr.requiresDigitSeparator && sb.length() > 0) { 399 sb.append(' '); 400 } 401 appendDigits(ival, integralDigits, 10, sb); 402 int dval = count % 1000; 403 if (decimalDigits == 1) { 404 dval /= 100; 405 } else if (decimalDigits == 2) { 406 dval /= 10; 407 } 408 sb.append(dr.decimalSep); 409 appendDigits(dval, decimalDigits, decimalDigits, sb); 410 if (dr.requiresDigitSeparator) { 411 sb.append(' '); 412 } 413 } 414 appendInteger(int num, int mindigits, int maxdigits, StringBuffer sb)415 public void appendInteger(int num, int mindigits, int maxdigits, 416 StringBuffer sb) { 417 if (dr.numberNames != null && num < dr.numberNames.length) { 418 String name = dr.numberNames[num]; 419 if (name != null) { 420 sb.append(name); 421 return; 422 } 423 } 424 425 if (dr.requiresDigitSeparator && sb.length() > 0) { 426 sb.append(' '); 427 } 428 switch (dr.numberSystem) { 429 case ENumberSystem.DEFAULT: appendDigits(num, mindigits, maxdigits, sb); break; 430 case ENumberSystem.CHINESE_TRADITIONAL: sb.append( 431 Utils.chineseNumber(num, Utils.ChineseDigits.TRADITIONAL)); break; 432 case ENumberSystem.CHINESE_SIMPLIFIED: sb.append( 433 Utils.chineseNumber(num, Utils.ChineseDigits.SIMPLIFIED)); break; 434 case ENumberSystem.KOREAN: sb.append( 435 Utils.chineseNumber(num, Utils.ChineseDigits.KOREAN)); break; 436 } 437 if (dr.requiresDigitSeparator) { 438 sb.append(' '); 439 } 440 } 441 442 /** 443 * Append digits to the string builder, using this.zero for '0' etc. 444 * 445 * @param num the integer to append 446 * @param mindigits the minimum number of digits to append 447 * @param maxdigits the maximum number of digits to append 448 * @param sb the string builder to which to append the text 449 */ appendDigits(long num, int mindigits, int maxdigits, StringBuffer sb)450 public void appendDigits(long num, int mindigits, int maxdigits, 451 StringBuffer sb) { 452 char[] buf = new char[maxdigits]; 453 int ix = maxdigits; 454 while (ix > 0 && num > 0) { 455 buf[--ix] = (char)(dr.zero + (num % 10)); 456 num /= 10; 457 } 458 for (int e = maxdigits - mindigits; ix > e;) { 459 buf[--ix] = dr.zero; 460 } 461 sb.append(buf, ix, maxdigits - ix); 462 } 463 464 /** 465 * Append a marker for skipped units internal to a string. 466 * @param sb the string builder to which to append the text 467 */ appendSkippedUnit(StringBuffer sb)468 public void appendSkippedUnit(StringBuffer sb) { 469 if (dr.skippedUnitMarker != null) { 470 sb.append(dr.skippedUnitMarker); 471 } 472 } 473 474 /** 475 * Append the appropriate separator between units 476 * 477 * @param unit the unit to which to append the separator 478 * @param afterFirst true if this is the first unit formatted 479 * @param beforeLast true if this is the next-to-last unit to be formatted 480 * @param sb the string builder to which to append the text 481 * @return true if a prefix will be required before a following unit 482 */ appendUnitSeparator(TimeUnit unit, boolean longSep, boolean afterFirst, boolean beforeLast, StringBuffer sb)483 public boolean appendUnitSeparator(TimeUnit unit, boolean longSep, 484 boolean afterFirst, boolean beforeLast, 485 StringBuffer sb) { 486 // long seps 487 // false, false "...b', '...d" 488 // false, true "...', and 'c" 489 // true, false - "a', '...c" 490 // true, true - "a' and 'b" 491 if ((longSep && dr.unitSep != null) || dr.shortUnitSep != null) { 492 if (longSep && dr.unitSep != null) { 493 int ix = (afterFirst ? 2 : 0) + (beforeLast ? 1 : 0); 494 sb.append(dr.unitSep[ix]); 495 return dr.unitSepRequiresDP != null && dr.unitSepRequiresDP[ix]; 496 } 497 sb.append(dr.shortUnitSep); // todo: investigate whether DP is required 498 } 499 return false; 500 } 501 502 private static final int 503 FORM_PLURAL = 0, 504 FORM_SINGULAR = 1, 505 FORM_DUAL = 2, 506 FORM_PAUCAL = 3, 507 FORM_SINGULAR_SPELLED = 4, // following are not in the pluralization list 508 FORM_SINGULAR_NO_OMIT = 5, // a hack 509 FORM_HALF_SPELLED = 6; 510 computeForm(TimeUnit unit, int count, int cv, boolean lastOfMultiple)511 private int computeForm(TimeUnit unit, int count, int cv, 512 boolean lastOfMultiple) { 513 // first check if a particular form is forced by the countvariant. if 514 // SO, just return that. otherwise convert the count to an integer 515 // and use pluralization rules to determine which form to use. 516 // careful, can't assume any forms but plural exist. 517 518 if (trace) { 519 System.err.println("pfd.cf unit: " + unit + " count: " + count + " cv: " + cv + " dr.pl: " + dr.pl); 520 Thread.dumpStack(); 521 } 522 if (dr.pl == EPluralization.NONE) { 523 return FORM_PLURAL; 524 } 525 // otherwise, assume we have at least a singular and plural form 526 527 int val = count/1000; 528 529 switch (cv) { 530 case ECountVariant.INTEGER: 531 case ECountVariant.INTEGER_CUSTOM: { 532 // do more analysis based on floor of count 533 } break; 534 case ECountVariant.HALF_FRACTION: { 535 switch (dr.fractionHandling) { 536 case EFractionHandling.FPLURAL: 537 return FORM_PLURAL; 538 539 case EFractionHandling.FSINGULAR_PLURAL_ANDAHALF: 540 case EFractionHandling.FSINGULAR_PLURAL: { 541 // if half-floor is 1/2, use singular 542 // else if half-floor is not integral, use plural 543 // else do more analysis 544 int v = count / 500; 545 if (v == 1) { 546 if (dr.halfNames != null && dr.halfNames[unit.ordinal()] != null) { 547 return FORM_HALF_SPELLED; 548 } 549 return FORM_SINGULAR_NO_OMIT; 550 } 551 if ((v & 0x1) == 1) { 552 if (dr.pl == EPluralization.ARABIC && v > 21) { // hack 553 return FORM_SINGULAR_NO_OMIT; 554 } 555 if (v == 3 && dr.pl == EPluralization.PLURAL && 556 dr.fractionHandling != EFractionHandling.FSINGULAR_PLURAL_ANDAHALF) { 557 return FORM_PLURAL; 558 } 559 } 560 561 // it will display like an integer, so do more analysis 562 } break; 563 564 case EFractionHandling.FPAUCAL: { 565 int v = count / 500; 566 if (v == 1 || v == 3) { 567 return FORM_PAUCAL; 568 } 569 // else use integral form 570 } break; 571 572 default: 573 throw new IllegalStateException(); 574 } 575 } break; 576 default: { // for all decimals 577 switch (dr.decimalHandling) { 578 case EDecimalHandling.DPLURAL: break; 579 case EDecimalHandling.DSINGULAR: return FORM_SINGULAR_NO_OMIT; 580 case EDecimalHandling.DSINGULAR_SUBONE: 581 if (count < 1000) { 582 return FORM_SINGULAR_NO_OMIT; 583 } 584 break; 585 case EDecimalHandling.DPAUCAL: 586 if (dr.pl == EPluralization.PAUCAL) { 587 return FORM_PAUCAL; 588 } 589 break; 590 default: 591 break; 592 } 593 return FORM_PLURAL; 594 } 595 } 596 597 // select among pluralization forms 598 if (trace && count == 0) { 599 System.err.println("EZeroHandling = " + dr.zeroHandling); 600 } 601 if (count == 0 && dr.zeroHandling == EZeroHandling.ZSINGULAR) { 602 return FORM_SINGULAR_SPELLED; 603 } 604 605 int form = FORM_PLURAL; 606 switch(dr.pl) { 607 case EPluralization.NONE: break; // never get here 608 case EPluralization.PLURAL: { 609 if (val == 1) { 610 form = FORM_SINGULAR_SPELLED; // defaults to form_singular if no spelled forms 611 } 612 } break; 613 case EPluralization.DUAL: { 614 if (val == 2) { 615 form = FORM_DUAL; 616 } else if (val == 1) { 617 form = FORM_SINGULAR; 618 } 619 } break; 620 case EPluralization.PAUCAL: { 621 int v = val; 622 v = v % 100; 623 if (v > 20) { 624 v = v % 10; 625 } 626 if (v == 1) { 627 form = FORM_SINGULAR; 628 } else if (v > 1 && v < 5) { 629 form = FORM_PAUCAL; 630 } 631 } break; 632 /* 633 case EPluralization.RPT_DUAL_FEW: { 634 int v = val; 635 if (v > 20) { 636 v = v % 10; 637 } 638 if (v == 1) { 639 form = FORM_SINGULAR; 640 } else if (v == 2) { 641 form = FORM_DUAL; 642 } else if (v > 2 && v < 5) { 643 form = FORM_PAUCAL; 644 } 645 } break; 646 */ 647 case EPluralization.HEBREW: { 648 if (val == 2) { 649 form = FORM_DUAL; 650 } else if (val == 1) { 651 if (lastOfMultiple) { 652 form = FORM_SINGULAR_SPELLED; 653 } else { 654 form = FORM_SINGULAR; 655 } 656 } else if (unit == TimeUnit.YEAR && val > 11) { 657 form = FORM_SINGULAR_NO_OMIT; 658 } 659 } break; 660 case EPluralization.ARABIC: { 661 if (val == 2) { 662 form = FORM_DUAL; 663 } else if (val == 1) { 664 form = FORM_SINGULAR; 665 } else if (val > 10) { 666 form = FORM_SINGULAR_NO_OMIT; 667 } 668 } break; 669 default: 670 System.err.println("dr.pl is " + dr.pl); 671 throw new IllegalStateException(); 672 } 673 674 return form; 675 } 676 } 677