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