2 ******************************************************************************
\r
3 * Copyright (C) 2007, 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 * @param return true if will require skip marker
\r
137 public boolean appendUnit(TimeUnit unit, int count, int cv,
\r
138 int uv, boolean useCountSep,
\r
139 boolean useDigitPrefix, boolean multiple,
\r
140 boolean last, boolean wasSkipped,
\r
142 int px = unit.ordinal();
\r
144 boolean willRequireSkipMarker = false;
\r
145 if (dr.requiresSkipMarker != null && dr.requiresSkipMarker[px] &&
\r
146 dr.skippedUnitMarker != null) {
\r
147 if (!wasSkipped && last) {
\r
148 sb.append(dr.skippedUnitMarker);
\r
150 willRequireSkipMarker = true;
\r
153 if (uv != EUnitVariant.PLURALIZED) {
\r
154 boolean useMedium = uv == EUnitVariant.MEDIUM;
\r
155 String[] names = useMedium ? dr.mediumNames : dr.shortNames;
\r
156 if (names == null || names[px] == null) {
\r
157 names = useMedium ? dr.shortNames : dr.mediumNames;
\r
159 if (names != null && names[px] != null) {
\r
160 appendCount(unit, false, false, count, cv, useCountSep,
\r
161 names[px], last, sb); // omit suffix, ok?
\r
162 return false; // omit skip marker
\r
167 if (cv == ECountVariant.HALF_FRACTION && dr.halfSupport != null) {
\r
168 switch (dr.halfSupport[px]) {
\r
169 case EHalfSupport.YES: break;
\r
170 case EHalfSupport.ONE_PLUS:
\r
171 if (count > 1000) {
\r
174 // else fall through to decimal
\r
175 case EHalfSupport.NO: {
\r
176 count = (count / 500) * 500; // round to 1/2
\r
177 cv = ECountVariant.DECIMAL1;
\r
182 String name = null;
\r
183 int form = computeForm(unit, count, cv, multiple && last);
\r
184 if (form == FORM_SINGULAR_SPELLED) {
\r
185 if (dr.singularNames == null) {
\r
186 form = FORM_SINGULAR;
\r
187 name = dr.pluralNames[px][form];
\r
189 name = dr.singularNames[px];
\r
191 } else if (form == FORM_SINGULAR_NO_OMIT) {
\r
192 name = dr.pluralNames[px][FORM_SINGULAR];
\r
193 } else if (form == FORM_HALF_SPELLED) {
\r
194 name = dr.halfNames[px];
\r
197 name = dr.pluralNames[px][form];
\r
198 } catch (NullPointerException e) {
\r
199 System.out.println("Null Pointer in PeriodFormatterData["+localeName+"].au px: " + px + " form: " + form + " pn: " + dr.pluralNames);
\r
203 if (name == null) {
\r
204 form = FORM_PLURAL;
\r
205 name = dr.pluralNames[px][form];
\r
208 boolean omitCount =
\r
209 (form == FORM_SINGULAR_SPELLED || form == FORM_HALF_SPELLED) ||
\r
210 (dr.omitSingularCount && form == FORM_SINGULAR) ||
\r
211 (dr.omitDualCount && form == FORM_DUAL);
\r
213 int suffixIndex = appendCount(unit, omitCount, useDigitPrefix, count, cv,
\r
214 useCountSep, name, last, sb);
\r
215 if (last && suffixIndex >= 0) {
\r
216 String suffix = null;
\r
217 if (dr.rqdSuffixes != null && suffixIndex < dr.rqdSuffixes.length) {
\r
218 suffix = dr.rqdSuffixes[suffixIndex];
\r
220 if (suffix == null && dr.optSuffixes != null &&
\r
221 suffixIndex < dr.optSuffixes.length) {
\r
222 suffix = dr.optSuffixes[suffixIndex];
\r
224 if (suffix != null) {
\r
228 return willRequireSkipMarker;
\r
232 * Append a count to the string builder.
\r
234 * @param unit the unit
\r
235 * @param count the count
\r
236 * @param cv the format to use for displaying the count
\r
237 * @param useSep whether to use the count separator, if available
\r
238 * @param name the term name
\r
239 * @param last true if this is the last unit to be formatted
\r
240 * @param sb the string builder to which to append the text
\r
241 * @return index to use if might have required or optional suffix, or -1 if none required
\r
243 public int appendCount(TimeUnit unit, boolean omitCount,
\r
244 boolean useDigitPrefix,
\r
245 int count, int cv, boolean useSep,
\r
246 String name, boolean last, StringBuffer sb) {
\r
247 if (cv == ECountVariant.HALF_FRACTION && dr.halves == null) {
\r
248 cv = ECountVariant.INTEGER;
\r
251 if (!omitCount && useDigitPrefix && dr.digitPrefix != null) {
\r
252 sb.append(dr.digitPrefix);
\r
255 int index = unit.ordinal();
\r
257 case ECountVariant.INTEGER: {
\r
259 appendInteger(count/1000, 1, 10, sb);
\r
263 case ECountVariant.INTEGER_CUSTOM: {
\r
264 int val = count / 1000;
\r
265 // only custom names we have for now
\r
266 if (unit == TimeUnit.MINUTE &&
\r
267 (dr.fiveMinutes != null || dr.fifteenMinutes != null)) {
\r
268 if (val != 0 && val % 5 == 0) {
\r
269 if (dr.fifteenMinutes != null && (val == 15 || val == 45)) {
\r
270 val = val == 15 ? 1 : 3;
\r
271 if (!omitCount) appendInteger(val, 1, 10, sb);
\r
272 name = dr.fifteenMinutes;
\r
276 if (dr.fiveMinutes != null) {
\r
278 if (!omitCount) appendInteger(val, 1, 10, sb);
\r
279 name = dr.fiveMinutes;
\r
285 if (!omitCount) appendInteger(val, 1, 10, sb);
\r
288 case ECountVariant.HALF_FRACTION: {
\r
289 // 0, 1/2, 1, 1-1/2...
\r
290 int v = count / 500;
\r
292 if (!omitCount) appendCountValue(count, 1, 0, sb);
\r
294 if ((v & 0x1) == 1) {
\r
295 // hack, using half name
\r
296 if (v == 1 && dr.halfNames != null && dr.halfNames[index] != null) {
\r
298 return last ? index : -1;
\r
301 int solox = v == 1 ? 0 : 1;
\r
302 if (dr.genders != null && dr.halves.length > 2) {
\r
303 if (dr.genders[index] == EGender.F) {
\r
307 int hp = dr.halfPlacements == null
\r
308 ? EHalfPlacement.PREFIX
\r
309 : dr.halfPlacements[solox & 0x1];
\r
310 String half = dr.halves[solox];
\r
311 String measure = dr.measures == null ? null : dr.measures[index];
\r
313 case EHalfPlacement.PREFIX:
\r
316 case EHalfPlacement.AFTER_FIRST: {
\r
317 if (measure != null) {
\r
318 sb.append(measure);
\r
320 if (useSep && !omitCount) {
\r
321 sb.append(dr.countSep);
\r
324 } else { // ignore sep completely
\r
327 return last ? index : -1; // might use suffix
\r
329 } return -1; // exit early
\r
330 case EHalfPlacement.LAST: {
\r
331 if (measure != null) {
\r
332 sb.append(measure);
\r
334 if (useSep && !omitCount) {
\r
335 sb.append(dr.countSep);
\r
339 } return last ? index : -1; // might use suffix
\r
346 case ECountVariant.DECIMAL2: decimals = 2; break;
\r
347 case ECountVariant.DECIMAL3: decimals = 3; break;
\r
350 if (!omitCount) appendCountValue(count, 1, decimals, sb);
\r
353 if (!omitCount && useSep) {
\r
354 sb.append(dr.countSep);
\r
356 if (!omitCount && dr.measures != null && index < dr.measures.length) {
\r
357 String measure = dr.measures[index];
\r
358 if (measure != null) {
\r
359 sb.append(measure);
\r
363 return last ? index : -1;
\r
367 * Append a count value to the builder.
\r
369 * @param count the count
\r
370 * @param integralDigits the number of integer digits to display
\r
371 * @param decimalDigits the number of decimal digits to display, <= 3
\r
372 * @param sb the string builder to which to append the text
\r
374 public void appendCountValue(int count, int integralDigits,
\r
375 int decimalDigits, StringBuffer sb) {
\r
376 int ival = count / 1000;
\r
377 if (decimalDigits == 0) {
\r
378 appendInteger(ival, integralDigits, 10, sb);
\r
382 if (dr.requiresDigitSeparator && sb.length() > 0) {
\r
385 appendDigits(ival, integralDigits, 10, sb);
\r
386 int dval = count % 1000;
\r
387 if (decimalDigits == 1) {
\r
389 } else if (decimalDigits == 2) {
\r
392 sb.append(dr.decimalSep);
\r
393 appendDigits(dval, decimalDigits, decimalDigits, sb);
\r
394 if (dr.requiresDigitSeparator) {
\r
399 public void appendInteger(int num, int mindigits, int maxdigits,
\r
401 if (dr.numberNames != null && num < dr.numberNames.length) {
\r
402 String name = dr.numberNames[num];
\r
403 if (name != null) {
\r
409 if (dr.requiresDigitSeparator && sb.length() > 0) {
\r
412 switch (dr.numberSystem) {
\r
413 case ENumberSystem.DEFAULT: appendDigits(num, mindigits, maxdigits, sb); break;
\r
414 case ENumberSystem.CHINESE_TRADITIONAL: sb.append(
\r
415 Utils.chineseNumber(num, Utils.ChineseDigits.TRADITIONAL)); break;
\r
416 case ENumberSystem.CHINESE_SIMPLIFIED: sb.append(
\r
417 Utils.chineseNumber(num, Utils.ChineseDigits.SIMPLIFIED)); break;
\r
418 case ENumberSystem.KOREAN: sb.append(
\r
419 Utils.chineseNumber(num, Utils.ChineseDigits.KOREAN)); break;
\r
421 if (dr.requiresDigitSeparator) {
\r
427 * Append digits to the string builder, using this.zero for '0' etc.
\r
429 * @param num the integer to append
\r
430 * @param mindigits the minimum number of digits to append
\r
431 * @param maxdigits the maximum number of digits to append
\r
432 * @param sb the string builder to which to append the text
\r
434 public void appendDigits(long num, int mindigits, int maxdigits,
\r
436 char[] buf = new char[maxdigits];
\r
437 int ix = maxdigits;
\r
438 while (ix > 0 && num > 0) {
\r
439 buf[--ix] = (char)(dr.zero + (num % 10));
\r
442 for (int e = maxdigits - mindigits; ix > e;) {
\r
443 buf[--ix] = dr.zero;
\r
445 sb.append(buf, ix, maxdigits - ix);
\r
449 * Append a marker for skipped units internal to a string.
\r
450 * @param sb the string builder to which to append the text
\r
452 public void appendSkippedUnit(StringBuffer sb) {
\r
453 if (dr.skippedUnitMarker != null) {
\r
454 sb.append(dr.skippedUnitMarker);
\r
459 * Append the appropriate separator between units
\r
461 * @param unit the unit to which to append the separator
\r
462 * @param afterFirst true if this is the first unit formatted
\r
463 * @param beforeLast true if this is the next-to-last unit to be formatted
\r
464 * @param sb the string builder to which to append the text
\r
465 * @return true if a prefix will be required before a following unit
\r
467 public boolean appendUnitSeparator(TimeUnit unit, boolean longSep,
\r
468 boolean afterFirst, boolean beforeLast,
\r
471 // false, false "...b', '...d"
\r
472 // false, true "...', and 'c"
\r
473 // true, false - "a', '...c"
\r
474 // true, true - "a' and 'b"
\r
475 if ((longSep && dr.unitSep != null) || dr.shortUnitSep != null) {
\r
476 if (longSep && dr.unitSep != null) {
\r
477 int ix = (afterFirst ? 2 : 0) + (beforeLast ? 1 : 0);
\r
478 sb.append(dr.unitSep[ix]);
\r
479 return dr.unitSepRequiresDP != null && dr.unitSepRequiresDP[ix];
\r
481 sb.append(dr.shortUnitSep); // todo: investigate whether DP is required
\r
486 private static final int
\r
491 FORM_SINGULAR_SPELLED = 4, // following are not in the pluralization list
\r
492 FORM_SINGULAR_NO_OMIT = 5, // a hack
\r
493 FORM_HALF_SPELLED = 6;
\r
495 private int computeForm(TimeUnit unit, int count, int cv,
\r
496 boolean lastOfMultiple) {
\r
497 // first check if a particular form is forced by the countvariant. if
\r
498 // SO, just return that. otherwise convert the count to an integer
\r
499 // and use pluralization rules to determine which form to use.
\r
500 // careful, can't assume any forms but plural exist.
\r
503 System.err.println("pfd.cf unit: " + unit + " count: " + count + " cv: " + cv + " dr.pl: " + dr.pl);
\r
504 Thread.dumpStack();
\r
506 if (dr.pl == EPluralization.NONE) {
\r
507 return FORM_PLURAL;
\r
509 // otherwise, assume we have at least a singular and plural form
\r
511 int val = count/1000;
\r
514 case ECountVariant.INTEGER:
\r
515 case ECountVariant.INTEGER_CUSTOM: {
\r
516 // do more analysis based on floor of count
\r
518 case ECountVariant.HALF_FRACTION: {
\r
519 switch (dr.fractionHandling) {
\r
520 case EFractionHandling.FPLURAL:
\r
521 return FORM_PLURAL;
\r
523 case EFractionHandling.FSINGULAR_PLURAL_ANDAHALF:
\r
524 case EFractionHandling.FSINGULAR_PLURAL: {
\r
525 // if half-floor is 1/2, use singular
\r
526 // else if half-floor is not integral, use plural
\r
527 // else do more analysis
\r
528 int v = (int)(count / 500);
\r
530 if (dr.halfNames != null && dr.halfNames[unit.ordinal()] != null) {
\r
531 return FORM_HALF_SPELLED;
\r
533 return FORM_SINGULAR_NO_OMIT;
\r
535 if ((v & 0x1) == 1) {
\r
536 if (dr.pl == EPluralization.ARABIC && v > 21) { // hack
\r
537 return FORM_SINGULAR_NO_OMIT;
\r
539 if (v == 3 && dr.pl == EPluralization.PLURAL &&
\r
540 dr.fractionHandling != EFractionHandling.FSINGULAR_PLURAL_ANDAHALF) {
\r
541 return FORM_PLURAL;
\r
545 // it will display like an integer, so do more analysis
\r
548 case EFractionHandling.FPAUCAL: {
\r
549 int v = (int)(count / 500);
\r
550 if (v == 1 || v == 3) {
\r
551 return FORM_PAUCAL;
\r
553 // else use integral form
\r
557 throw new IllegalStateException();
\r
560 default: { // for all decimals
\r
561 switch (dr.decimalHandling) {
\r
562 case EDecimalHandling.DPLURAL: break;
\r
563 case EDecimalHandling.DSINGULAR: return FORM_SINGULAR_NO_OMIT;
\r
564 case EDecimalHandling.DSINGULAR_SUBONE:
\r
565 if (count < 1000) {
\r
566 return FORM_SINGULAR_NO_OMIT;
\r
569 case EDecimalHandling.DPAUCAL:
\r
570 if (dr.pl == EPluralization.PAUCAL) {
\r
571 return FORM_PAUCAL;
\r
577 return FORM_PLURAL;
\r
581 // select among pluralization forms
\r
582 if (trace && count == 0) {
\r
583 System.err.println("EZeroHandling = " + dr.zeroHandling);
\r
585 if (count == 0 && dr.zeroHandling == EZeroHandling.ZSINGULAR) {
\r
586 return FORM_SINGULAR_SPELLED;
\r
589 int form = FORM_PLURAL;
\r
591 case EPluralization.NONE: break; // never get here
\r
592 case EPluralization.PLURAL: {
\r
594 form = FORM_SINGULAR_SPELLED; // defaults to form_singular if no spelled forms
\r
597 case EPluralization.DUAL: {
\r
600 } else if (val == 1) {
\r
601 form = FORM_SINGULAR;
\r
604 case EPluralization.PAUCAL: {
\r
611 form = FORM_SINGULAR;
\r
612 } else if (v > 1 && v < 5) {
\r
613 form = FORM_PAUCAL;
\r
617 case EPluralization.RPT_DUAL_FEW: {
\r
623 form = FORM_SINGULAR;
\r
624 } else if (v == 2) {
\r
626 } else if (v > 2 && v < 5) {
\r
627 form = FORM_PAUCAL;
\r
631 case EPluralization.HEBREW: {
\r
634 } else if (val == 1) {
\r
635 if (lastOfMultiple) {
\r
636 form = FORM_SINGULAR_SPELLED;
\r
638 form = FORM_SINGULAR;
\r
640 } else if (unit == TimeUnit.YEAR && val > 11) {
\r
641 form = FORM_SINGULAR_NO_OMIT;
\r
644 case EPluralization.ARABIC: {
\r
647 } else if (val == 1) {
\r
648 form = FORM_SINGULAR;
\r
649 } else if (val > 10) {
\r
650 form = FORM_SINGULAR_NO_OMIT;
\r
654 System.err.println("dr.pl is " + dr.pl);
\r
655 throw new IllegalStateException();
\r