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