2 ******************************************************************************
\r
3 * Copyright (C) 2009, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 ******************************************************************************
\r
8 package com.ibm.icu.impl.duration.impl;
\r
10 import com.ibm.icu.impl.duration.TimeUnit;
\r
12 import com.ibm.icu.impl.duration.impl.DataRecord.*;
\r
16 * PeriodFormatterData provides locale-specific data used to format
\r
17 * relative dates and times, and convenience api to access it.
\r
19 * An instance of PeriodFormatterData is usually created by requesting
\r
20 * data for a given locale from an PeriodFormatterDataService.
\r
22 public class PeriodFormatterData {
\r
23 final DataRecord dr;
\r
27 public static boolean trace = false;
\r
29 public PeriodFormatterData(String localeName, DataRecord dr) {
\r
31 this.localeName = localeName;
\r
32 if(localeName == null) {
\r
33 throw new NullPointerException("localename is null");
\r
35 // System.err.println("** localeName is " + localeName);
\r
37 // Thread.dumpStack();
\r
38 throw new NullPointerException("data record is null");
\r
42 // none - chinese (all forms the same)
\r
43 // plural - english, special form for 1
\r
44 // dual - special form for 1 and 2
\r
45 // paucal - russian, special form for 1, for 2-4 and n > 20 && n % 10 == 2-4
\r
46 // rpt_dual_few - slovenian, special form for 1, 2, 3-4 and n as above
\r
47 // hebrew, dual plus singular form for years > 11
\r
48 // arabic, dual, plus singular form for all terms > 10
\r
51 * Return the pluralization format used by this locale.
\r
52 * @return the pluralization format
\r
54 public int pluralization() {
\r
59 * Return true if zeros are allowed in the display.
\r
60 * @return true if zeros should be allowed
\r
62 public boolean allowZero() {
\r
63 return dr.allowZero;
\r
66 public boolean weeksAloneOnly() {
\r
67 return dr.weeksAloneOnly;
\r
70 public int useMilliseconds() {
\r
71 return dr.useMilliseconds;
\r
75 * Append the appropriate prefix to the string builder, depending on whether and
\r
76 * how a limit and direction are to be displayed.
\r
78 * @param tl how and whether to display the time limit
\r
79 * @param td how and whether to display the time direction
\r
80 * @param sb the string builder to which to append the text
\r
81 * @return true if a following digit will require a digit prefix
\r
83 public boolean appendPrefix(int tl, int td, StringBuffer sb) {
\r
84 if (dr.scopeData != null) {
\r
85 int ix = tl * 3 + td;
\r
86 ScopeData sd = dr.scopeData[ix];
\r
88 String prefix = sd.prefix;
\r
89 if (prefix != null) {
\r
91 return sd.requiresDigitPrefix;
\r
99 * Append the appropriate suffix to the string builder, depending on whether and
\r
100 * how a limit and direction are to be displayed.
\r
102 * @param tl how and whether to display the time limit
\r
103 * @param td how and whether to display the time direction
\r
104 * @param sb the string builder to which to append the text
\r
106 public void appendSuffix(int tl, int td, StringBuffer sb) {
\r
107 if (dr.scopeData != null) {
\r
108 int ix = tl * 3 + td;
\r
109 ScopeData sd = dr.scopeData[ix];
\r
111 String suffix = sd.suffix;
\r
112 if (suffix != null) {
\r
114 System.out.println("appendSuffix '" + suffix + "'");
\r
123 * Append the count and unit to the string builder.
\r
125 * @param unit the unit to append
\r
126 * @param count the count of units, * 1000
\r
127 * @param cv the format to use for displaying the count
\r
128 * @param uv the format to use for displaying the unit
\r
129 * @param useCountSep if false, force no separator between count and unit
\r
130 * @param useDigitPrefix if true, use the digit prefix
\r
131 * @param multiple true if there are multiple units in this string
\r
132 * @param last true if this is the last unit
\r
133 * @param wasSkipped true if the unit(s) before this were skipped
\r
134 * @param sb the string builder to which to append the text
\r
135 * @return true if will require skip marker
\r
137 @SuppressWarnings("fallthrough")
\r
138 public boolean appendUnit(TimeUnit unit, int count, int cv,
\r
139 int uv, boolean useCountSep,
\r
140 boolean useDigitPrefix, boolean multiple,
\r
141 boolean last, boolean wasSkipped,
\r
143 int px = unit.ordinal();
\r
145 boolean willRequireSkipMarker = false;
\r
146 if (dr.requiresSkipMarker != null && dr.requiresSkipMarker[px] &&
\r
147 dr.skippedUnitMarker != null) {
\r
148 if (!wasSkipped && last) {
\r
149 sb.append(dr.skippedUnitMarker);
\r
151 willRequireSkipMarker = true;
\r
154 if (uv != EUnitVariant.PLURALIZED) {
\r
155 boolean useMedium = uv == EUnitVariant.MEDIUM;
\r
156 String[] names = useMedium ? dr.mediumNames : dr.shortNames;
\r
157 if (names == null || names[px] == null) {
\r
158 names = useMedium ? dr.shortNames : dr.mediumNames;
\r
160 if (names != null && names[px] != null) {
\r
161 appendCount(unit, false, false, count, cv, useCountSep,
\r
162 names[px], last, sb); // omit suffix, ok?
\r
163 return false; // omit skip marker
\r
168 if (cv == ECountVariant.HALF_FRACTION && dr.halfSupport != null) {
\r
169 switch (dr.halfSupport[px]) {
\r
170 case EHalfSupport.YES: break;
\r
171 case EHalfSupport.ONE_PLUS:
\r
172 if (count > 1000) {
\r
175 // else fall through to decimal
\r
176 case EHalfSupport.NO: {
\r
177 count = (count / 500) * 500; // round to 1/2
\r
178 cv = ECountVariant.DECIMAL1;
\r
183 String name = null;
\r
184 int form = computeForm(unit, count, cv, multiple && last);
\r
185 if (form == FORM_SINGULAR_SPELLED) {
\r
186 if (dr.singularNames == null) {
\r
187 form = FORM_SINGULAR;
\r
188 name = dr.pluralNames[px][form];
\r
190 name = dr.singularNames[px];
\r
192 } else if (form == FORM_SINGULAR_NO_OMIT) {
\r
193 name = dr.pluralNames[px][FORM_SINGULAR];
\r
194 } else if (form == FORM_HALF_SPELLED) {
\r
195 name = dr.halfNames[px];
\r
198 name = dr.pluralNames[px][form];
\r
199 } catch (NullPointerException e) {
\r
200 System.out.println("Null Pointer in PeriodFormatterData["+localeName+"].au px: " + px + " form: " + form + " pn: " + dr.pluralNames);
\r
204 if (name == null) {
\r
205 form = FORM_PLURAL;
\r
206 name = dr.pluralNames[px][form];
\r
209 boolean omitCount =
\r
210 (form == FORM_SINGULAR_SPELLED || form == FORM_HALF_SPELLED) ||
\r
211 (dr.omitSingularCount && form == FORM_SINGULAR) ||
\r
212 (dr.omitDualCount && form == FORM_DUAL);
\r
214 int suffixIndex = appendCount(unit, omitCount, useDigitPrefix, count, cv,
\r
215 useCountSep, name, last, sb);
\r
216 if (last && suffixIndex >= 0) {
\r
217 String suffix = null;
\r
218 if (dr.rqdSuffixes != null && suffixIndex < dr.rqdSuffixes.length) {
\r
219 suffix = dr.rqdSuffixes[suffixIndex];
\r
221 if (suffix == null && dr.optSuffixes != null &&
\r
222 suffixIndex < dr.optSuffixes.length) {
\r
223 suffix = dr.optSuffixes[suffixIndex];
\r
225 if (suffix != null) {
\r
229 return willRequireSkipMarker;
\r
233 * Append a count to the string builder.
\r
235 * @param unit the unit
\r
236 * @param count the count
\r
237 * @param cv the format to use for displaying the count
\r
238 * @param useSep whether to use the count separator, if available
\r
239 * @param name the term name
\r
240 * @param last true if this is the last unit to be formatted
\r
241 * @param sb the string builder to which to append the text
\r
242 * @return index to use if might have required or optional suffix, or -1 if none required
\r
244 public int appendCount(TimeUnit unit, boolean omitCount,
\r
245 boolean useDigitPrefix,
\r
246 int count, int cv, boolean useSep,
\r
247 String name, boolean last, StringBuffer sb) {
\r
248 if (cv == ECountVariant.HALF_FRACTION && dr.halves == null) {
\r
249 cv = ECountVariant.INTEGER;
\r
252 if (!omitCount && useDigitPrefix && dr.digitPrefix != null) {
\r
253 sb.append(dr.digitPrefix);
\r
256 int index = unit.ordinal();
\r
258 case ECountVariant.INTEGER: {
\r
260 appendInteger(count/1000, 1, 10, sb);
\r
264 case ECountVariant.INTEGER_CUSTOM: {
\r
265 int val = count / 1000;
\r
266 // only custom names we have for now
\r
267 if (unit == TimeUnit.MINUTE &&
\r
268 (dr.fiveMinutes != null || dr.fifteenMinutes != null)) {
\r
269 if (val != 0 && val % 5 == 0) {
\r
270 if (dr.fifteenMinutes != null && (val == 15 || val == 45)) {
\r
271 val = val == 15 ? 1 : 3;
\r
272 if (!omitCount) appendInteger(val, 1, 10, sb);
\r
273 name = dr.fifteenMinutes;
\r
277 if (dr.fiveMinutes != null) {
\r
279 if (!omitCount) appendInteger(val, 1, 10, sb);
\r
280 name = dr.fiveMinutes;
\r
286 if (!omitCount) appendInteger(val, 1, 10, sb);
\r
289 case ECountVariant.HALF_FRACTION: {
\r
290 // 0, 1/2, 1, 1-1/2...
\r
291 int v = count / 500;
\r
293 if (!omitCount) appendCountValue(count, 1, 0, sb);
\r
295 if ((v & 0x1) == 1) {
\r
296 // hack, using half name
\r
297 if (v == 1 && dr.halfNames != null && dr.halfNames[index] != null) {
\r
299 return last ? index : -1;
\r
302 int solox = v == 1 ? 0 : 1;
\r
303 if (dr.genders != null && dr.halves.length > 2) {
\r
304 if (dr.genders[index] == EGender.F) {
\r
308 int hp = dr.halfPlacements == null
\r
309 ? EHalfPlacement.PREFIX
\r
310 : dr.halfPlacements[solox & 0x1];
\r
311 String half = dr.halves[solox];
\r
312 String measure = dr.measures == null ? null : dr.measures[index];
\r
314 case EHalfPlacement.PREFIX:
\r
317 case EHalfPlacement.AFTER_FIRST: {
\r
318 if (measure != null) {
\r
319 sb.append(measure);
\r
321 if (useSep && !omitCount) {
\r
322 sb.append(dr.countSep);
\r
325 } else { // ignore sep completely
\r
328 return last ? index : -1; // might use suffix
\r
330 } return -1; // exit early
\r
331 case EHalfPlacement.LAST: {
\r
332 if (measure != null) {
\r
333 sb.append(measure);
\r
335 if (useSep && !omitCount) {
\r
336 sb.append(dr.countSep);
\r
340 } return last ? index : -1; // might use suffix
\r
347 case ECountVariant.DECIMAL2: decimals = 2; break;
\r
348 case ECountVariant.DECIMAL3: decimals = 3; break;
\r
351 if (!omitCount) appendCountValue(count, 1, decimals, sb);
\r
354 if (!omitCount && useSep) {
\r
355 sb.append(dr.countSep);
\r
357 if (!omitCount && dr.measures != null && index < dr.measures.length) {
\r
358 String measure = dr.measures[index];
\r
359 if (measure != null) {
\r
360 sb.append(measure);
\r
364 return last ? index : -1;
\r
368 * Append a count value to the builder.
\r
370 * @param count the count
\r
371 * @param integralDigits the number of integer digits to display
\r
372 * @param decimalDigits the number of decimal digits to display, <= 3
\r
373 * @param sb the string builder to which to append the text
\r
375 public void appendCountValue(int count, int integralDigits,
\r
376 int decimalDigits, StringBuffer sb) {
\r
377 int ival = count / 1000;
\r
378 if (decimalDigits == 0) {
\r
379 appendInteger(ival, integralDigits, 10, sb);
\r
383 if (dr.requiresDigitSeparator && sb.length() > 0) {
\r
386 appendDigits(ival, integralDigits, 10, sb);
\r
387 int dval = count % 1000;
\r
388 if (decimalDigits == 1) {
\r
390 } else if (decimalDigits == 2) {
\r
393 sb.append(dr.decimalSep);
\r
394 appendDigits(dval, decimalDigits, decimalDigits, sb);
\r
395 if (dr.requiresDigitSeparator) {
\r
400 public void appendInteger(int num, int mindigits, int maxdigits,
\r
402 if (dr.numberNames != null && num < dr.numberNames.length) {
\r
403 String name = dr.numberNames[num];
\r
404 if (name != null) {
\r
410 if (dr.requiresDigitSeparator && sb.length() > 0) {
\r
413 switch (dr.numberSystem) {
\r
414 case ENumberSystem.DEFAULT: appendDigits(num, mindigits, maxdigits, sb); break;
\r
415 case ENumberSystem.CHINESE_TRADITIONAL: sb.append(
\r
416 Utils.chineseNumber(num, Utils.ChineseDigits.TRADITIONAL)); break;
\r
417 case ENumberSystem.CHINESE_SIMPLIFIED: sb.append(
\r
418 Utils.chineseNumber(num, Utils.ChineseDigits.SIMPLIFIED)); break;
\r
419 case ENumberSystem.KOREAN: sb.append(
\r
420 Utils.chineseNumber(num, Utils.ChineseDigits.KOREAN)); break;
\r
422 if (dr.requiresDigitSeparator) {
\r
428 * Append digits to the string builder, using this.zero for '0' etc.
\r
430 * @param num the integer to append
\r
431 * @param mindigits the minimum number of digits to append
\r
432 * @param maxdigits the maximum number of digits to append
\r
433 * @param sb the string builder to which to append the text
\r
435 public void appendDigits(long num, int mindigits, int maxdigits,
\r
437 char[] buf = new char[maxdigits];
\r
438 int ix = maxdigits;
\r
439 while (ix > 0 && num > 0) {
\r
440 buf[--ix] = (char)(dr.zero + (num % 10));
\r
443 for (int e = maxdigits - mindigits; ix > e;) {
\r
444 buf[--ix] = dr.zero;
\r
446 sb.append(buf, ix, maxdigits - ix);
\r
450 * Append a marker for skipped units internal to a string.
\r
451 * @param sb the string builder to which to append the text
\r
453 public void appendSkippedUnit(StringBuffer sb) {
\r
454 if (dr.skippedUnitMarker != null) {
\r
455 sb.append(dr.skippedUnitMarker);
\r
460 * Append the appropriate separator between units
\r
462 * @param unit the unit to which to append the separator
\r
463 * @param afterFirst true if this is the first unit formatted
\r
464 * @param beforeLast true if this is the next-to-last unit to be formatted
\r
465 * @param sb the string builder to which to append the text
\r
466 * @return true if a prefix will be required before a following unit
\r
468 public boolean appendUnitSeparator(TimeUnit unit, boolean longSep,
\r
469 boolean afterFirst, boolean beforeLast,
\r
472 // false, false "...b', '...d"
\r
473 // false, true "...', and 'c"
\r
474 // true, false - "a', '...c"
\r
475 // true, true - "a' and 'b"
\r
476 if ((longSep && dr.unitSep != null) || dr.shortUnitSep != null) {
\r
477 if (longSep && dr.unitSep != null) {
\r
478 int ix = (afterFirst ? 2 : 0) + (beforeLast ? 1 : 0);
\r
479 sb.append(dr.unitSep[ix]);
\r
480 return dr.unitSepRequiresDP != null && dr.unitSepRequiresDP[ix];
\r
482 sb.append(dr.shortUnitSep); // todo: investigate whether DP is required
\r
487 private static final int
\r
492 FORM_SINGULAR_SPELLED = 4, // following are not in the pluralization list
\r
493 FORM_SINGULAR_NO_OMIT = 5, // a hack
\r
494 FORM_HALF_SPELLED = 6;
\r
496 private int computeForm(TimeUnit unit, int count, int cv,
\r
497 boolean lastOfMultiple) {
\r
498 // first check if a particular form is forced by the countvariant. if
\r
499 // SO, just return that. otherwise convert the count to an integer
\r
500 // and use pluralization rules to determine which form to use.
\r
501 // careful, can't assume any forms but plural exist.
\r
504 System.err.println("pfd.cf unit: " + unit + " count: " + count + " cv: " + cv + " dr.pl: " + dr.pl);
\r
505 Thread.dumpStack();
\r
507 if (dr.pl == EPluralization.NONE) {
\r
508 return FORM_PLURAL;
\r
510 // otherwise, assume we have at least a singular and plural form
\r
512 int val = count/1000;
\r
515 case ECountVariant.INTEGER:
\r
516 case ECountVariant.INTEGER_CUSTOM: {
\r
517 // do more analysis based on floor of count
\r
519 case ECountVariant.HALF_FRACTION: {
\r
520 switch (dr.fractionHandling) {
\r
521 case EFractionHandling.FPLURAL:
\r
522 return FORM_PLURAL;
\r
524 case EFractionHandling.FSINGULAR_PLURAL_ANDAHALF:
\r
525 case EFractionHandling.FSINGULAR_PLURAL: {
\r
526 // if half-floor is 1/2, use singular
\r
527 // else if half-floor is not integral, use plural
\r
528 // else do more analysis
\r
529 int v = count / 500;
\r
531 if (dr.halfNames != null && dr.halfNames[unit.ordinal()] != null) {
\r
532 return FORM_HALF_SPELLED;
\r
534 return FORM_SINGULAR_NO_OMIT;
\r
536 if ((v & 0x1) == 1) {
\r
537 if (dr.pl == EPluralization.ARABIC && v > 21) { // hack
\r
538 return FORM_SINGULAR_NO_OMIT;
\r
540 if (v == 3 && dr.pl == EPluralization.PLURAL &&
\r
541 dr.fractionHandling != EFractionHandling.FSINGULAR_PLURAL_ANDAHALF) {
\r
542 return FORM_PLURAL;
\r
546 // it will display like an integer, so do more analysis
\r
549 case EFractionHandling.FPAUCAL: {
\r
550 int v = count / 500;
\r
551 if (v == 1 || v == 3) {
\r
552 return FORM_PAUCAL;
\r
554 // else use integral form
\r
558 throw new IllegalStateException();
\r
561 default: { // for all decimals
\r
562 switch (dr.decimalHandling) {
\r
563 case EDecimalHandling.DPLURAL: break;
\r
564 case EDecimalHandling.DSINGULAR: return FORM_SINGULAR_NO_OMIT;
\r
565 case EDecimalHandling.DSINGULAR_SUBONE:
\r
566 if (count < 1000) {
\r
567 return FORM_SINGULAR_NO_OMIT;
\r
570 case EDecimalHandling.DPAUCAL:
\r
571 if (dr.pl == EPluralization.PAUCAL) {
\r
572 return FORM_PAUCAL;
\r
578 return FORM_PLURAL;
\r
582 // select among pluralization forms
\r
583 if (trace && count == 0) {
\r
584 System.err.println("EZeroHandling = " + dr.zeroHandling);
\r
586 if (count == 0 && dr.zeroHandling == EZeroHandling.ZSINGULAR) {
\r
587 return FORM_SINGULAR_SPELLED;
\r
590 int form = FORM_PLURAL;
\r
592 case EPluralization.NONE: break; // never get here
\r
593 case EPluralization.PLURAL: {
\r
595 form = FORM_SINGULAR_SPELLED; // defaults to form_singular if no spelled forms
\r
598 case EPluralization.DUAL: {
\r
601 } else if (val == 1) {
\r
602 form = FORM_SINGULAR;
\r
605 case EPluralization.PAUCAL: {
\r
612 form = FORM_SINGULAR;
\r
613 } else if (v > 1 && v < 5) {
\r
614 form = FORM_PAUCAL;
\r
618 case EPluralization.RPT_DUAL_FEW: {
\r
624 form = FORM_SINGULAR;
\r
625 } else if (v == 2) {
\r
627 } else if (v > 2 && v < 5) {
\r
628 form = FORM_PAUCAL;
\r
632 case EPluralization.HEBREW: {
\r
635 } else if (val == 1) {
\r
636 if (lastOfMultiple) {
\r
637 form = FORM_SINGULAR_SPELLED;
\r
639 form = FORM_SINGULAR;
\r
641 } else if (unit == TimeUnit.YEAR && val > 11) {
\r
642 form = FORM_SINGULAR_NO_OMIT;
\r
645 case EPluralization.ARABIC: {
\r
648 } else if (val == 1) {
\r
649 form = FORM_SINGULAR;
\r
650 } else if (val > 10) {
\r
651 form = FORM_SINGULAR_NO_OMIT;
\r
655 System.err.println("dr.pl is " + dr.pl);
\r
656 throw new IllegalStateException();
\r