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 android.icu.impl.duration.impl; 12 13 import java.util.Arrays; 14 15 import android.icu.impl.duration.TimeUnit; 16 import android.icu.impl.duration.impl.DataRecord.ECountVariant; 17 import android.icu.impl.duration.impl.DataRecord.EDecimalHandling; 18 import android.icu.impl.duration.impl.DataRecord.EFractionHandling; 19 import android.icu.impl.duration.impl.DataRecord.EGender; 20 import android.icu.impl.duration.impl.DataRecord.EHalfPlacement; 21 import android.icu.impl.duration.impl.DataRecord.EHalfSupport; 22 import android.icu.impl.duration.impl.DataRecord.ENumberSystem; 23 import android.icu.impl.duration.impl.DataRecord.EPluralization; 24 import android.icu.impl.duration.impl.DataRecord.EUnitVariant; 25 import android.icu.impl.duration.impl.DataRecord.EZeroHandling; 26 import android.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 Only a subset of ICU is exposed in Android 36 */ 37 public class PeriodFormatterData { 38 final DataRecord dr; 39 String localeName; 40 41 // debug 42 public static boolean trace = false; 43 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 */ 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 */ 77 public boolean allowZero() { 78 return dr.allowZero; 79 } 80 81 public boolean weeksAloneOnly() { 82 return dr.weeksAloneOnly; 83 } 84 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 */ 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 */ 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") 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 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