]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-52_1/main/classes/core/src/com/ibm/icu/text/TimeUnitFormat.java
Added flags.
[Dictionary.git] / jars / icu4j-52_1 / main / classes / core / src / com / ibm / icu / text / TimeUnitFormat.java
1 /*
2  **************************************************************************
3  * Copyright (C) 2008-2013, Google, International Business Machines
4  * Corporation and others. All Rights Reserved.
5  **************************************************************************
6  */
7 package com.ibm.icu.text;
8
9 import java.text.FieldPosition;
10 import java.text.ParsePosition;
11 import java.util.HashMap;
12 import java.util.Locale;
13 import java.util.Map;
14 import java.util.Map.Entry;
15 import java.util.MissingResourceException;
16 import java.util.Set;
17 import java.util.TreeMap;
18
19 import com.ibm.icu.impl.ICUResourceBundle;
20 import com.ibm.icu.util.TimeUnit;
21 import com.ibm.icu.util.TimeUnitAmount;
22 import com.ibm.icu.util.ULocale;
23 import com.ibm.icu.util.ULocale.Category;
24 import com.ibm.icu.util.UResourceBundle;
25
26
27 /**
28  * Format or parse a TimeUnitAmount, using plural rules for the units where available.
29  *
30  * <P>
31  * Code Sample: 
32  * <pre>
33  *   // create a time unit instance.
34  *   // only SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, and YEAR are supported
35  *   TimeUnit timeUnit = TimeUnit.SECOND;
36  *   // create time unit amount instance - a combination of Number and time unit
37  *   TimeUnitAmount source = new TimeUnitAmount(2, timeUnit);
38  *   // create time unit format instance
39  *   TimeUnitFormat format = new TimeUnitFormat();
40  *   // set the locale of time unit format
41  *   format.setLocale(new ULocale("en"));
42  *   // format a time unit amount
43  *   String formatted = format.format(source);
44  *   System.out.println(formatted);
45  *   try {
46  *       // parse a string into time unit amount
47  *       TimeUnitAmount result = (TimeUnitAmount) format.parseObject(formatted);
48  *       // result should equal to source 
49  *   } catch (ParseException e) {
50  *   }
51  * </pre>
52  *
53  * <P>
54  * @see TimeUnitAmount
55  * @see TimeUnitFormat
56  * @author markdavis
57  * @stable ICU 4.0
58  */
59 public class TimeUnitFormat extends MeasureFormat {
60
61     /**
62      * Constant for full name style format. 
63      * For example, the full name for "hour" in English is "hour" or "hours".
64      * @stable ICU 4.2
65      */
66     public static final int FULL_NAME = 0;
67     /**
68      * Constant for abbreviated name style format. 
69      * For example, the abbreviated name for "hour" in English is "hr" or "hrs".
70      * @stable ICU 4.2
71      */
72     public static final int ABBREVIATED_NAME = 1;
73     
74     private static final int TOTAL_STYLES = 2;
75
76     private static final long serialVersionUID = -3707773153184971529L;
77   
78     private static final String DEFAULT_PATTERN_FOR_SECOND = "{0} s";
79     private static final String DEFAULT_PATTERN_FOR_MINUTE = "{0} min";
80     private static final String DEFAULT_PATTERN_FOR_HOUR = "{0} h";
81     private static final String DEFAULT_PATTERN_FOR_DAY = "{0} d";
82     private static final String DEFAULT_PATTERN_FOR_WEEK = "{0} w";
83     private static final String DEFAULT_PATTERN_FOR_MONTH = "{0} m";
84     private static final String DEFAULT_PATTERN_FOR_YEAR = "{0} y";
85
86     private NumberFormat format;
87     private ULocale locale;
88     private transient Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns;
89     private transient PluralRules pluralRules;
90     private transient boolean isReady;
91     private int style;
92
93     /**
94      * Create empty format using full name style, for example, "hours". 
95      * Use setLocale and/or setFormat to modify.
96      * @stable ICU 4.0
97      */
98     public TimeUnitFormat() {
99         isReady = false;
100         style = FULL_NAME;
101
102     }
103
104     /**
105      * Create TimeUnitFormat given a ULocale, and using full name style.
106      * @param locale   locale of this time unit formatter.
107      * @stable ICU 4.0
108      */
109     public TimeUnitFormat(ULocale locale) {
110         this(locale, FULL_NAME);
111     }
112
113     /**
114      * Create TimeUnitFormat given a Locale, and using full name style.
115      * @param locale   locale of this time unit formatter.
116      * @stable ICU 4.0
117      */
118     public TimeUnitFormat(Locale locale) {
119         this(locale, FULL_NAME);
120     }
121
122     /**
123      * Create TimeUnitFormat given a ULocale and a formatting style.
124      * @param locale   locale of this time unit formatter.
125      * @param style    format style, either FULL_NAME or ABBREVIATED_NAME style.
126      * @throws IllegalArgumentException if the style is not FULL_NAME or
127      *                                  ABBREVIATED_NAME style.
128      * @stable ICU 4.2
129      */
130     public TimeUnitFormat(ULocale locale, int style) {
131         if (style < FULL_NAME || style >= TOTAL_STYLES) {
132             throw new IllegalArgumentException("style should be either FULL_NAME or ABBREVIATED_NAME style");
133         }
134         this.style = style;
135         this.locale = locale;
136         isReady = false;
137     }
138
139     /**
140      * Create TimeUnitFormat given a Locale and a formatting style.
141      * @stable ICU 4.2
142      */
143     public TimeUnitFormat(Locale locale, int style) {
144         this(ULocale.forLocale(locale),  style);
145     }
146
147     /**
148      * Set the locale used for formatting or parsing.
149      * @param locale   locale of this time unit formatter.
150      * @return this, for chaining.
151      * @stable ICU 4.0
152      */
153     public TimeUnitFormat setLocale(ULocale locale) {
154         if ( locale != this.locale ) {
155             this.locale = locale;
156             isReady = false;
157         }
158         return this;
159     }
160     
161     /**
162      * Set the locale used for formatting or parsing.
163      * @param locale   locale of this time unit formatter.
164      * @return this, for chaining.
165      * @stable ICU 4.0
166      */
167     public TimeUnitFormat setLocale(Locale locale) {
168         return setLocale(ULocale.forLocale(locale));
169     }
170     
171     /**
172      * Set the format used for formatting or parsing. Passing null is equivalent to passing
173      * {@link NumberFormat#getNumberInstance(ULocale)}.
174      * @param format   the number formatter.
175      * @return this, for chaining.
176      * @stable ICU 4.0
177      */
178     public TimeUnitFormat setNumberFormat(NumberFormat format) {
179         if (format == this.format) {
180             return this;
181         }
182         if ( format == null ) {
183             if ( locale == null ) {
184                 isReady = false;
185                 return this;
186             } else {
187                 this.format = NumberFormat.getNumberInstance(locale);
188             }
189         } else {
190             this.format = format;
191         }
192         // reset the number formatter in the timeUnitToCountToPatterns map
193         if (isReady == false) {
194             return this;
195         }
196         for (Map<String, Object[]> countToPattern : timeUnitToCountToPatterns.values()) {
197             for (Object[] pair : countToPattern.values()) {
198                 MessageFormat pattern = (MessageFormat)pair[FULL_NAME];
199                 pattern.setFormatByArgumentIndex(0, format);
200                 pattern = (MessageFormat)pair[ABBREVIATED_NAME];
201                 pattern.setFormatByArgumentIndex(0, format);
202             }
203         }
204         return this;
205     }
206
207
208     /**
209      * Format a TimeUnitAmount.
210      * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
211      * @stable ICU 4.0
212      */
213     public StringBuffer format(Object obj, StringBuffer toAppendTo,
214             FieldPosition pos) {
215         if ( !(obj instanceof TimeUnitAmount) ) {
216             throw new IllegalArgumentException(
217                     "cannot format a non TimeUnitAmount object");
218         }
219         if (!isReady) {
220             setup();
221         }
222         TimeUnitAmount amount = (TimeUnitAmount) obj;
223         Map<String, Object[]> countToPattern = timeUnitToCountToPatterns.get(amount.getTimeUnit());
224         double number = amount.getNumber().doubleValue();
225         String count = pluralRules.select(number);
226         MessageFormat pattern = (MessageFormat)(countToPattern.get(count))[style];
227         return pattern.format(new Object[]{amount.getNumber()}, toAppendTo, pos);
228     }
229     
230     /**
231      * Parse a TimeUnitAmount.
232      * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
233      * @stable ICU 4.0
234      */
235     public Object parseObject(String source, ParsePosition pos) {
236         if (!isReady) {
237             setup();
238         }
239         Number resultNumber = null;
240         TimeUnit resultTimeUnit = null;
241         int oldPos = pos.getIndex();
242         int newPos = -1;
243         int longestParseDistance = 0;
244         String countOfLongestMatch = null;
245         // we don't worry too much about speed on parsing, but this can be optimized later if needed.
246         // Parse by iterating through all available patterns
247         // and looking for the longest match.
248         for (TimeUnit timeUnit : timeUnitToCountToPatterns.keySet()) {
249             Map<String, Object[]> countToPattern = timeUnitToCountToPatterns.get(timeUnit);
250             for (Entry<String, Object[]> patternEntry : countToPattern.entrySet()) {
251               String count = patternEntry.getKey();
252               for (int styl = FULL_NAME; styl < TOTAL_STYLES; ++styl) {
253                 MessageFormat pattern = (MessageFormat)(patternEntry.getValue())[styl];
254                 pos.setErrorIndex(-1);
255                 pos.setIndex(oldPos);
256                 // see if we can parse
257                 Object parsed = pattern.parseObject(source, pos);
258                 if ( pos.getErrorIndex() != -1 || pos.getIndex() == oldPos ) {
259                     // nothing parsed
260                     continue;
261                 }
262                 Number temp = null;
263                 if ( ((Object[])parsed).length != 0 ) {
264                     // pattern with Number as beginning,
265                     // such as "{0} d".
266                     // check to make sure that the timeUnit is consistent
267                     temp = (Number)((Object[])parsed)[0];
268                     String select = pluralRules.select(temp.doubleValue());
269                     if (!count.equals(select)) {
270                         continue;
271                     }
272                 }
273                 int parseDistance = pos.getIndex() - oldPos;
274                 if ( parseDistance > longestParseDistance ) {
275                     resultNumber = temp;
276                     resultTimeUnit = timeUnit;
277                     newPos = pos.getIndex();
278                     longestParseDistance = parseDistance;
279                     countOfLongestMatch = count;
280                 }
281             }
282           }
283         }
284         /* After find the longest match, parse the number.
285          * Result number could be null for the pattern without number pattern.
286          * such as unit pattern in Arabic.
287          * When result number is null, use plural rule to set the number.
288          */
289         if (resultNumber == null && longestParseDistance != 0) {
290             // set the number using plurrual count
291             if ( countOfLongestMatch.equals("zero") ) {
292                 resultNumber = Integer.valueOf(0);
293             } else if ( countOfLongestMatch.equals("one") ) {
294                 resultNumber = Integer.valueOf(1);
295             } else if ( countOfLongestMatch.equals("two") ) {
296                 resultNumber = Integer.valueOf(2);
297             } else {
298                 // should not happen.
299                 // TODO: how to handle?
300                 resultNumber = Integer.valueOf(3);
301             }
302         }
303         if (longestParseDistance == 0) {
304             pos.setIndex(oldPos);
305             pos.setErrorIndex(0);
306             return null;
307         } else {
308             pos.setIndex(newPos);
309             pos.setErrorIndex(-1);
310             return new TimeUnitAmount(resultNumber, resultTimeUnit);
311         }
312     }
313     
314     
315     /*
316      * Initialize locale, number formatter, plural rules, and
317      * time units patterns.
318      * Initially, we are storing all of these as MessageFormats.
319      * I think it might actually be simpler to make them Decimal Formats later.
320      */
321     private void setup() {
322         if (locale == null) {
323             if (format != null) {
324                 locale = format.getLocale(null);
325             } else {
326                 locale = ULocale.getDefault(Category.FORMAT);
327             }
328         }
329         if (format == null) {
330             format = NumberFormat.getNumberInstance(locale);
331         }
332         pluralRules = PluralRules.forLocale(locale);
333         timeUnitToCountToPatterns = new HashMap<TimeUnit, Map<String, Object[]>>();
334         Set<String> pluralKeywords = pluralRules.getKeywords();
335         setup("units/duration", timeUnitToCountToPatterns, FULL_NAME, pluralKeywords);
336         setup("unitsShort/duration", timeUnitToCountToPatterns, ABBREVIATED_NAME, pluralKeywords);
337         isReady = true;
338     }
339     
340     private void setup(String resourceKey, Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns,
341                        int style, Set<String> pluralKeywords) {
342         // fill timeUnitToCountToPatterns from resource file
343         try {
344             ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
345             ICUResourceBundle unitsRes = resource.getWithFallback(resourceKey);
346             int size = unitsRes.getSize();
347             for ( int index = 0; index < size; ++index) {
348                 String timeUnitName = unitsRes.get(index).getKey();
349                 TimeUnit timeUnit = null;
350                 if ( timeUnitName.equals("year") ) {
351                     timeUnit = TimeUnit.YEAR;
352                 } else if ( timeUnitName.equals("month") ) {
353                     timeUnit = TimeUnit.MONTH;
354                 } else if ( timeUnitName.equals("day") ) {
355                     timeUnit = TimeUnit.DAY;
356                 } else if ( timeUnitName.equals("hour") ) {
357                     timeUnit = TimeUnit.HOUR;
358                 } else if ( timeUnitName.equals("minute") ) {
359                     timeUnit = TimeUnit.MINUTE;
360                 } else if ( timeUnitName.equals("second") ) {
361                     timeUnit = TimeUnit.SECOND;
362                 } else if ( timeUnitName.equals("week") ) {
363                     timeUnit = TimeUnit.WEEK;
364                 } else {
365                     continue;
366                 }
367                 ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(timeUnitName);
368                 int count = oneUnitRes.getSize();
369                 Map<String, Object[]> countToPatterns = timeUnitToCountToPatterns.get(timeUnit);
370                 if (countToPatterns ==  null) {
371                     countToPatterns = new TreeMap<String, Object[]>();
372                     timeUnitToCountToPatterns.put(timeUnit, countToPatterns);
373                 } 
374                 for ( int pluralIndex = 0; pluralIndex < count; ++pluralIndex) {
375                     String pluralCount = oneUnitRes.get(pluralIndex).getKey();
376                     if (!pluralKeywords.contains(pluralCount))
377                         continue;
378                     String pattern = oneUnitRes.get(pluralIndex).getString();
379                     final MessageFormat messageFormat = new MessageFormat(pattern, locale);
380                     if (format != null) {
381                         messageFormat.setFormatByArgumentIndex(0, format);
382                     }
383                     // save both full name and abbreviated name in one table
384                     // is good space-wise, but it degrades performance, 
385                     // since it needs to check whether the needed space 
386                     // is already allocated or not.
387                     Object[] pair = countToPatterns.get(pluralCount);
388                     if (pair == null) {
389                         pair = new Object[2];
390                         countToPatterns.put(pluralCount, pair);
391                     } 
392                     pair[style] = messageFormat;
393                 }
394             }
395         } catch ( MissingResourceException e ) {
396         }
397
398         // there should be patterns for each plural rule in each time unit.
399         // For each time unit, 
400         //     for each plural rule, following is unit pattern fall-back rule:
401         //         ( for example: "one" hour )
402         //         look for its unit pattern in its locale tree.
403         //         if pattern is not found in its own locale, such as de_DE,
404         //         look for the pattern in its parent, such as de,
405         //         keep looking till found or till root.
406         //         if the pattern is not found in root either,
407         //         fallback to plural count "other",
408         //         look for the pattern of "other" in the locale tree:
409         //         "de_DE" to "de" to "root".
410         //         If not found, fall back to value of 
411         //         static variable DEFAULT_PATTERN_FOR_xxx, such as "{0} h". 
412         //
413         // Following is consistency check to create pattern for each
414         // plural rule in each time unit using above fall-back rule.
415         //
416         final TimeUnit[] timeUnits = TimeUnit.values();
417         Set<String> keywords = pluralRules.getKeywords();
418         for ( int i = 0; i < timeUnits.length; ++i ) {
419             // for each time unit, 
420             // get all the patterns for each plural rule in this locale.
421             final TimeUnit timeUnit = timeUnits[i];
422             Map<String, Object[]> countToPatterns = timeUnitToCountToPatterns.get(timeUnit);
423             if (countToPatterns == null) {
424                 countToPatterns = new TreeMap<String, Object[]>();
425                 timeUnitToCountToPatterns.put(timeUnit, countToPatterns);
426             }
427             for (String pluralCount : keywords) {
428                 if ( countToPatterns.get(pluralCount) == null ||
429                      countToPatterns.get(pluralCount)[style] == null ) {
430                     // look through parents
431                     searchInTree(resourceKey, style, timeUnit, pluralCount, pluralCount, countToPatterns);
432                 }
433             }
434         }
435     }
436
437
438
439     // srcPluralCount is the original plural count on which the pattern is
440     // searched for.
441     // searchPluralCount is the fallback plural count.
442     // For example, to search for pattern for ""one" hour",
443     // "one" is the srcPluralCount,
444     // if the pattern is not found even in root, fallback to 
445     // using patterns of plural count "other", 
446     // then, "other" is the searchPluralCount.
447     private void searchInTree(String resourceKey, int styl,
448                               TimeUnit timeUnit, String srcPluralCount,
449                               String searchPluralCount, Map<String, Object[]> countToPatterns) {
450         ULocale parentLocale=locale;
451         String srcTimeUnitName = timeUnit.toString();
452         while ( parentLocale != null ) {
453             try {
454                 // look for pattern for srcPluralCount in locale tree
455                 ICUResourceBundle unitsRes = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, parentLocale);
456                 unitsRes = unitsRes.getWithFallback(resourceKey);
457                 ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(srcTimeUnitName);
458                 String pattern = oneUnitRes.getStringWithFallback(searchPluralCount);
459                 final MessageFormat messageFormat = new MessageFormat(pattern, locale);
460                 if (format != null) {
461                     messageFormat.setFormatByArgumentIndex(0, format);
462                 }
463                 Object[] pair = countToPatterns.get(srcPluralCount);
464                 if (pair == null) {
465                     pair = new Object[2];
466                     countToPatterns.put(srcPluralCount, pair);
467                 }
468                 pair[styl] = messageFormat;
469                 return;
470             } catch ( MissingResourceException e ) {
471             }
472             parentLocale=parentLocale.getFallback();
473         }
474
475         // if no unitsShort resource was found even after fallback to root locale
476         // then search the units resource fallback from the current level to root
477         if ( parentLocale == null && resourceKey.equals("unitsShort") ) {
478             searchInTree("units", styl, timeUnit, srcPluralCount, searchPluralCount, countToPatterns);
479             if ( countToPatterns != null &&
480                     countToPatterns.get(srcPluralCount) != null &&
481                     countToPatterns.get(srcPluralCount)[styl] != null ) {
482                 return;
483             }
484         }
485
486         // if not found the pattern for this plural count at all,
487         // fall-back to plural count "other"
488         if ( searchPluralCount.equals("other") ) {
489             // set default fall back the same as the resource in root
490             MessageFormat messageFormat = null;
491             if ( timeUnit == TimeUnit.SECOND ) {
492                 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_SECOND, locale);
493             } else if ( timeUnit == TimeUnit.MINUTE ) {
494                 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_MINUTE, locale);
495             } else if ( timeUnit == TimeUnit.HOUR ) {
496                 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_HOUR, locale);
497             } else if ( timeUnit == TimeUnit.WEEK ) {
498                 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_WEEK, locale);
499             } else if ( timeUnit == TimeUnit.DAY ) {
500                 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_DAY, locale);
501             } else if ( timeUnit == TimeUnit.MONTH ) {
502                 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_MONTH, locale);
503             } else if ( timeUnit == TimeUnit.YEAR ) {
504                 messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_YEAR, locale);
505             }
506             if (format != null && messageFormat != null) {
507                 messageFormat.setFormatByArgumentIndex(0, format);
508             }
509             Object[] pair = countToPatterns.get(srcPluralCount);
510             if (pair == null) {
511                 pair = new Object[2];
512                 countToPatterns.put(srcPluralCount, pair);
513             }
514             pair[styl] = messageFormat;
515         } else {
516             // fall back to rule "other", and search in parents
517             searchInTree(resourceKey, styl, timeUnit, srcPluralCount, "other", countToPatterns);
518         }
519     }
520 }