/src/mozilla-central/intl/icu/source/i18n/measfmt.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // © 2016 and later: Unicode, Inc. and others. |
2 | | // License & terms of use: http://www.unicode.org/copyright.html |
3 | | /* |
4 | | ********************************************************************** |
5 | | * Copyright (c) 2004-2016, International Business Machines |
6 | | * Corporation and others. All Rights Reserved. |
7 | | ********************************************************************** |
8 | | * Author: Alan Liu |
9 | | * Created: April 20, 2004 |
10 | | * Since: ICU 3.0 |
11 | | ********************************************************************** |
12 | | */ |
13 | | #include "utypeinfo.h" // for 'typeid' to work |
14 | | #include "unicode/utypes.h" |
15 | | |
16 | | #if !UCONFIG_NO_FORMATTING |
17 | | |
18 | | #include "unicode/measfmt.h" |
19 | | #include "unicode/numfmt.h" |
20 | | #include "currfmt.h" |
21 | | #include "unicode/localpointer.h" |
22 | | #include "resource.h" |
23 | | #include "unicode/simpleformatter.h" |
24 | | #include "quantityformatter.h" |
25 | | #include "unicode/plurrule.h" |
26 | | #include "unicode/decimfmt.h" |
27 | | #include "uresimp.h" |
28 | | #include "unicode/ures.h" |
29 | | #include "unicode/ustring.h" |
30 | | #include "ureslocs.h" |
31 | | #include "cstring.h" |
32 | | #include "mutex.h" |
33 | | #include "ucln_in.h" |
34 | | #include "unicode/listformatter.h" |
35 | | #include "charstr.h" |
36 | | #include "unicode/putil.h" |
37 | | #include "unicode/smpdtfmt.h" |
38 | | #include "uassert.h" |
39 | | |
40 | | #include "sharednumberformat.h" |
41 | | #include "sharedpluralrules.h" |
42 | | #include "standardplural.h" |
43 | | #include "unifiedcache.h" |
44 | | |
45 | | |
46 | | U_NAMESPACE_BEGIN |
47 | | |
48 | | static constexpr int32_t PER_UNIT_INDEX = StandardPlural::COUNT; |
49 | | static constexpr int32_t PATTERN_COUNT = PER_UNIT_INDEX + 1; |
50 | | static constexpr int32_t MEAS_UNIT_COUNT = 138; // see assertion in MeasureFormatCacheData constructor |
51 | | static constexpr int32_t WIDTH_INDEX_COUNT = UMEASFMT_WIDTH_NARROW + 1; |
52 | | |
53 | | UOBJECT_DEFINE_RTTI_IMPLEMENTATION(MeasureFormat) |
54 | | |
55 | | // Used to format durations like 5:47 or 21:35:42. |
56 | | class NumericDateFormatters : public UMemory { |
57 | | public: |
58 | | // Formats like H:mm |
59 | | SimpleDateFormat hourMinute; |
60 | | |
61 | | // formats like M:ss |
62 | | SimpleDateFormat minuteSecond; |
63 | | |
64 | | // formats like H:mm:ss |
65 | | SimpleDateFormat hourMinuteSecond; |
66 | | |
67 | | // Constructor that takes the actual patterns for hour-minute, |
68 | | // minute-second, and hour-minute-second respectively. |
69 | | NumericDateFormatters( |
70 | | const UnicodeString &hm, |
71 | | const UnicodeString &ms, |
72 | | const UnicodeString &hms, |
73 | | UErrorCode &status) : |
74 | | hourMinute(hm, status), |
75 | | minuteSecond(ms, status), |
76 | 0 | hourMinuteSecond(hms, status) { |
77 | 0 | const TimeZone *gmt = TimeZone::getGMT(); |
78 | 0 | hourMinute.setTimeZone(*gmt); |
79 | 0 | minuteSecond.setTimeZone(*gmt); |
80 | 0 | hourMinuteSecond.setTimeZone(*gmt); |
81 | 0 | } |
82 | | private: |
83 | | NumericDateFormatters(const NumericDateFormatters &other); |
84 | | NumericDateFormatters &operator=(const NumericDateFormatters &other); |
85 | | }; |
86 | | |
87 | 0 | static UMeasureFormatWidth getRegularWidth(UMeasureFormatWidth width) { |
88 | 0 | if (width >= WIDTH_INDEX_COUNT) { |
89 | 0 | return UMEASFMT_WIDTH_NARROW; |
90 | 0 | } |
91 | 0 | return width; |
92 | 0 | } |
93 | | |
94 | | /** |
95 | | * Instances contain all MeasureFormat specific data for a particular locale. |
96 | | * This data is cached. It is never copied, but is shared via shared pointers. |
97 | | * |
98 | | * Note: We might change the cache data to have an array[WIDTH_INDEX_COUNT] of |
99 | | * complete sets of unit & per patterns, |
100 | | * to correspond to the resource data and its aliases. |
101 | | * |
102 | | * TODO: Maybe store more sparsely in general, with pointers rather than potentially-empty objects. |
103 | | */ |
104 | | class MeasureFormatCacheData : public SharedObject { |
105 | | public: |
106 | | |
107 | | /** |
108 | | * Redirection data from root-bundle, top-level sideways aliases. |
109 | | * - UMEASFMT_WIDTH_COUNT: initial value, just fall back to root |
110 | | * - UMEASFMT_WIDTH_WIDE/SHORT/NARROW: sideways alias for missing data |
111 | | */ |
112 | | UMeasureFormatWidth widthFallback[WIDTH_INDEX_COUNT]; |
113 | | /** Measure unit -> format width -> array of patterns ("{0} meters") (plurals + PER_UNIT_INDEX) */ |
114 | | SimpleFormatter* patterns[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT][PATTERN_COUNT]; |
115 | | const UChar* dnams[MEAS_UNIT_COUNT][WIDTH_INDEX_COUNT]; |
116 | | SimpleFormatter perFormatters[WIDTH_INDEX_COUNT]; |
117 | | |
118 | | MeasureFormatCacheData(); |
119 | | virtual ~MeasureFormatCacheData(); |
120 | | |
121 | 0 | UBool hasPerFormatter(int32_t width) const { |
122 | 0 | // TODO: Create a more obvious way to test if the per-formatter has been set? |
123 | 0 | // Use pointers, check for NULL? Or add an isValid() method? |
124 | 0 | return perFormatters[width].getArgumentLimit() == 2; |
125 | 0 | } |
126 | | |
127 | 0 | void adoptCurrencyFormat(int32_t widthIndex, NumberFormat *nfToAdopt) { |
128 | 0 | delete currencyFormats[widthIndex]; |
129 | 0 | currencyFormats[widthIndex] = nfToAdopt; |
130 | 0 | } |
131 | 0 | const NumberFormat *getCurrencyFormat(UMeasureFormatWidth width) const { |
132 | 0 | return currencyFormats[getRegularWidth(width)]; |
133 | 0 | } |
134 | 0 | void adoptIntegerFormat(NumberFormat *nfToAdopt) { |
135 | 0 | delete integerFormat; |
136 | 0 | integerFormat = nfToAdopt; |
137 | 0 | } |
138 | 0 | const NumberFormat *getIntegerFormat() const { |
139 | 0 | return integerFormat; |
140 | 0 | } |
141 | 0 | void adoptNumericDateFormatters(NumericDateFormatters *formattersToAdopt) { |
142 | 0 | delete numericDateFormatters; |
143 | 0 | numericDateFormatters = formattersToAdopt; |
144 | 0 | } |
145 | 0 | const NumericDateFormatters *getNumericDateFormatters() const { |
146 | 0 | return numericDateFormatters; |
147 | 0 | } |
148 | | |
149 | | private: |
150 | | NumberFormat* currencyFormats[WIDTH_INDEX_COUNT]; |
151 | | NumberFormat* integerFormat; |
152 | | NumericDateFormatters* numericDateFormatters; |
153 | | |
154 | | MeasureFormatCacheData(const MeasureFormatCacheData &other); |
155 | | MeasureFormatCacheData &operator=(const MeasureFormatCacheData &other); |
156 | | }; |
157 | | |
158 | | MeasureFormatCacheData::MeasureFormatCacheData() |
159 | 0 | : integerFormat(nullptr), numericDateFormatters(nullptr) { |
160 | 0 | // Please update MEAS_UNIT_COUNT if it gets out of sync with the true count! |
161 | 0 | U_ASSERT(MEAS_UNIT_COUNT == MeasureUnit::getIndexCount()); |
162 | 0 |
|
163 | 0 | for (int32_t i = 0; i < WIDTH_INDEX_COUNT; ++i) { |
164 | 0 | widthFallback[i] = UMEASFMT_WIDTH_COUNT; |
165 | 0 | } |
166 | 0 | memset(&patterns[0][0][0], 0, sizeof(patterns)); |
167 | 0 | memset(&dnams[0][0], 0, sizeof(dnams)); |
168 | 0 | memset(currencyFormats, 0, sizeof(currencyFormats)); |
169 | 0 | } |
170 | | |
171 | 0 | MeasureFormatCacheData::~MeasureFormatCacheData() { |
172 | 0 | for (int32_t i = 0; i < UPRV_LENGTHOF(currencyFormats); ++i) { |
173 | 0 | delete currencyFormats[i]; |
174 | 0 | } |
175 | 0 | for (int32_t i = 0; i < MEAS_UNIT_COUNT; ++i) { |
176 | 0 | for (int32_t j = 0; j < WIDTH_INDEX_COUNT; ++j) { |
177 | 0 | for (int32_t k = 0; k < PATTERN_COUNT; ++k) { |
178 | 0 | delete patterns[i][j][k]; |
179 | 0 | } |
180 | 0 | } |
181 | 0 | } |
182 | 0 | // Note: the contents of 'dnams' are pointers into the resource bundle |
183 | 0 | delete integerFormat; |
184 | 0 | delete numericDateFormatters; |
185 | 0 | } |
186 | | |
187 | 0 | static UBool isCurrency(const MeasureUnit &unit) { |
188 | 0 | return (uprv_strcmp(unit.getType(), "currency") == 0); |
189 | 0 | } |
190 | | |
191 | | static UBool getString( |
192 | | const UResourceBundle *resource, |
193 | | UnicodeString &result, |
194 | 0 | UErrorCode &status) { |
195 | 0 | int32_t len = 0; |
196 | 0 | const UChar *resStr = ures_getString(resource, &len, &status); |
197 | 0 | if (U_FAILURE(status)) { |
198 | 0 | return FALSE; |
199 | 0 | } |
200 | 0 | result.setTo(TRUE, resStr, len); |
201 | 0 | return TRUE; |
202 | 0 | } |
203 | | |
204 | | namespace { |
205 | | |
206 | | static const UChar g_LOCALE_units[] = { |
207 | | 0x2F, 0x4C, 0x4F, 0x43, 0x41, 0x4C, 0x45, 0x2F, |
208 | | 0x75, 0x6E, 0x69, 0x74, 0x73 |
209 | | }; |
210 | | static const UChar gShort[] = { 0x53, 0x68, 0x6F, 0x72, 0x74 }; |
211 | | static const UChar gNarrow[] = { 0x4E, 0x61, 0x72, 0x72, 0x6F, 0x77 }; |
212 | | |
213 | | /** |
214 | | * Sink for enumerating all of the measurement unit display names. |
215 | | * Contains inner sink classes, each one corresponding to a type of resource table. |
216 | | * The outer sink handles the top-level units, unitsNarrow, and unitsShort tables. |
217 | | * |
218 | | * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root): |
219 | | * Only store a value if it is still missing, that is, it has not been overridden. |
220 | | * |
221 | | * C++: Each inner sink class has a reference to the main outer sink. |
222 | | * Java: Use non-static inner classes instead. |
223 | | */ |
224 | | struct UnitDataSink : public ResourceSink { |
225 | | |
226 | | // Output data. |
227 | | MeasureFormatCacheData &cacheData; |
228 | | |
229 | | // Path to current data. |
230 | | UMeasureFormatWidth width; |
231 | | const char *type; |
232 | | int32_t unitIndex; |
233 | | |
234 | | UnitDataSink(MeasureFormatCacheData &outputData) |
235 | | : cacheData(outputData), |
236 | 0 | width(UMEASFMT_WIDTH_COUNT), type(NULL), unitIndex(0) {} |
237 | | ~UnitDataSink(); |
238 | | |
239 | | void setFormatterIfAbsent(int32_t index, const ResourceValue &value, |
240 | 0 | int32_t minPlaceholders, UErrorCode &errorCode) { |
241 | 0 | U_ASSERT(unitIndex < MEAS_UNIT_COUNT); |
242 | 0 | U_ASSERT(width < WIDTH_INDEX_COUNT); |
243 | 0 | U_ASSERT(index < PATTERN_COUNT); |
244 | 0 | SimpleFormatter **patterns = &cacheData.patterns[unitIndex][width][0]; |
245 | 0 | if (U_SUCCESS(errorCode) && patterns[index] == NULL) { |
246 | 0 | if (minPlaceholders >= 0) { |
247 | 0 | patterns[index] = new SimpleFormatter( |
248 | 0 | value.getUnicodeString(errorCode), minPlaceholders, 1, errorCode); |
249 | 0 | } |
250 | 0 | if (U_SUCCESS(errorCode) && patterns[index] == NULL) { |
251 | 0 | errorCode = U_MEMORY_ALLOCATION_ERROR; |
252 | 0 | } |
253 | 0 | } |
254 | 0 | } |
255 | | |
256 | 0 | void setDnamIfAbsent(const ResourceValue &value, UErrorCode& errorCode) { |
257 | 0 | U_ASSERT(unitIndex < MEAS_UNIT_COUNT); |
258 | 0 | U_ASSERT(width < WIDTH_INDEX_COUNT); |
259 | 0 | if (cacheData.dnams[unitIndex][width] == NULL) { |
260 | 0 | int32_t length; |
261 | 0 | cacheData.dnams[unitIndex][width] = value.getString(length, errorCode); |
262 | 0 | } |
263 | 0 | } |
264 | | |
265 | | /** |
266 | | * Consume a display pattern. For example, |
267 | | * unitsShort/duration/hour contains other{"{0} hrs"}. |
268 | | */ |
269 | 0 | void consumePattern(const char *key, const ResourceValue &value, UErrorCode &errorCode) { |
270 | 0 | if (U_FAILURE(errorCode)) { return; } |
271 | 0 | if (uprv_strcmp(key, "dnam") == 0) { |
272 | 0 | // The display name for the unit in the current width. |
273 | 0 | setDnamIfAbsent(value, errorCode); |
274 | 0 | } else if (uprv_strcmp(key, "per") == 0) { |
275 | 0 | // For example, "{0}/h". |
276 | 0 | setFormatterIfAbsent(PER_UNIT_INDEX, value, 1, errorCode); |
277 | 0 | } else { |
278 | 0 | // The key must be one of the plural form strings. For example: |
279 | 0 | // one{"{0} hr"} |
280 | 0 | // other{"{0} hrs"} |
281 | 0 | setFormatterIfAbsent(StandardPlural::indexFromString(key, errorCode), value, 0, |
282 | 0 | errorCode); |
283 | 0 | } |
284 | 0 | } |
285 | | |
286 | | /** |
287 | | * Consume a table of per-unit tables. For example, |
288 | | * unitsShort/duration contains tables for duration-unit subtypes day & hour. |
289 | | */ |
290 | 0 | void consumeSubtypeTable(const char *key, ResourceValue &value, UErrorCode &errorCode) { |
291 | 0 | if (U_FAILURE(errorCode)) { return; } |
292 | 0 | unitIndex = MeasureUnit::internalGetIndexForTypeAndSubtype(type, key); |
293 | 0 | if (unitIndex < 0) { |
294 | 0 | // TODO: How to handle unexpected data? |
295 | 0 | // See http://bugs.icu-project.org/trac/ticket/12597 |
296 | 0 | return; |
297 | 0 | } |
298 | 0 | |
299 | 0 | // We no longer handle units like "coordinate" here (which do not have plural variants) |
300 | 0 | if (value.getType() == URES_TABLE) { |
301 | 0 | // Units that have plural variants |
302 | 0 | ResourceTable patternTableTable = value.getTable(errorCode); |
303 | 0 | if (U_FAILURE(errorCode)) { return; } |
304 | 0 | for (int i = 0; patternTableTable.getKeyAndValue(i, key, value); ++i) { |
305 | 0 | consumePattern(key, value, errorCode); |
306 | 0 | } |
307 | 0 | } else { |
308 | 0 | // TODO: How to handle unexpected data? |
309 | 0 | // See http://bugs.icu-project.org/trac/ticket/12597 |
310 | 0 | return; |
311 | 0 | } |
312 | 0 | } |
313 | | |
314 | | /** |
315 | | * Consume compound x-per-y display pattern. For example, |
316 | | * unitsShort/compound/per may be "{0}/{1}". |
317 | | */ |
318 | 0 | void consumeCompoundPattern(const char *key, const ResourceValue &value, UErrorCode &errorCode) { |
319 | 0 | if (U_SUCCESS(errorCode) && uprv_strcmp(key, "per") == 0) { |
320 | 0 | cacheData.perFormatters[width]. |
321 | 0 | applyPatternMinMaxArguments(value.getUnicodeString(errorCode), 2, 2, errorCode); |
322 | 0 | } |
323 | 0 | } |
324 | | |
325 | | /** |
326 | | * Consume a table of unit type tables. For example, |
327 | | * unitsShort contains tables for area & duration. |
328 | | * It also contains a table for the compound/per pattern. |
329 | | */ |
330 | 0 | void consumeUnitTypesTable(const char *key, ResourceValue &value, UErrorCode &errorCode) { |
331 | 0 | if (U_FAILURE(errorCode)) { return; } |
332 | 0 | if (uprv_strcmp(key, "currency") == 0) { |
333 | 0 | // Skip. |
334 | 0 | } else if (uprv_strcmp(key, "compound") == 0) { |
335 | 0 | if (!cacheData.hasPerFormatter(width)) { |
336 | 0 | ResourceTable compoundTable = value.getTable(errorCode); |
337 | 0 | if (U_FAILURE(errorCode)) { return; } |
338 | 0 | for (int i = 0; compoundTable.getKeyAndValue(i, key, value); ++i) { |
339 | 0 | consumeCompoundPattern(key, value, errorCode); |
340 | 0 | } |
341 | 0 | } |
342 | 0 | } else if (uprv_strcmp(key, "coordinate") == 0) { |
343 | 0 | // special handling but we need to determine what that is |
344 | 0 | } else { |
345 | 0 | type = key; |
346 | 0 | ResourceTable subtypeTable = value.getTable(errorCode); |
347 | 0 | if (U_FAILURE(errorCode)) { return; } |
348 | 0 | for (int i = 0; subtypeTable.getKeyAndValue(i, key, value); ++i) { |
349 | 0 | consumeSubtypeTable(key, value, errorCode); |
350 | 0 | } |
351 | 0 | } |
352 | 0 | } |
353 | | |
354 | 0 | void consumeAlias(const char *key, const ResourceValue &value, UErrorCode &errorCode) { |
355 | 0 | // Handle aliases like |
356 | 0 | // units:alias{"/LOCALE/unitsShort"} |
357 | 0 | // which should only occur in the root bundle. |
358 | 0 | UMeasureFormatWidth sourceWidth = widthFromKey(key); |
359 | 0 | if (sourceWidth == UMEASFMT_WIDTH_COUNT) { |
360 | 0 | // Alias from something we don't care about. |
361 | 0 | return; |
362 | 0 | } |
363 | 0 | UMeasureFormatWidth targetWidth = widthFromAlias(value, errorCode); |
364 | 0 | if (targetWidth == UMEASFMT_WIDTH_COUNT) { |
365 | 0 | // We do not recognize what to fall back to. |
366 | 0 | errorCode = U_INVALID_FORMAT_ERROR; |
367 | 0 | return; |
368 | 0 | } |
369 | 0 | // Check that we do not fall back to another fallback. |
370 | 0 | if (cacheData.widthFallback[targetWidth] != UMEASFMT_WIDTH_COUNT) { |
371 | 0 | errorCode = U_INVALID_FORMAT_ERROR; |
372 | 0 | return; |
373 | 0 | } |
374 | 0 | cacheData.widthFallback[sourceWidth] = targetWidth; |
375 | 0 | } |
376 | | |
377 | 0 | void consumeTable(const char *key, ResourceValue &value, UErrorCode &errorCode) { |
378 | 0 | if (U_SUCCESS(errorCode) && (width = widthFromKey(key)) != UMEASFMT_WIDTH_COUNT) { |
379 | 0 | ResourceTable unitTypesTable = value.getTable(errorCode); |
380 | 0 | if (U_FAILURE(errorCode)) { return; } |
381 | 0 | for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) { |
382 | 0 | consumeUnitTypesTable(key, value, errorCode); |
383 | 0 | } |
384 | 0 | } |
385 | 0 | } |
386 | | |
387 | 0 | static UMeasureFormatWidth widthFromKey(const char *key) { |
388 | 0 | if (uprv_strncmp(key, "units", 5) == 0) { |
389 | 0 | key += 5; |
390 | 0 | if (*key == 0) { |
391 | 0 | return UMEASFMT_WIDTH_WIDE; |
392 | 0 | } else if (uprv_strcmp(key, "Short") == 0) { |
393 | 0 | return UMEASFMT_WIDTH_SHORT; |
394 | 0 | } else if (uprv_strcmp(key, "Narrow") == 0) { |
395 | 0 | return UMEASFMT_WIDTH_NARROW; |
396 | 0 | } |
397 | 0 | } |
398 | 0 | return UMEASFMT_WIDTH_COUNT; |
399 | 0 | } |
400 | | |
401 | 0 | static UMeasureFormatWidth widthFromAlias(const ResourceValue &value, UErrorCode &errorCode) { |
402 | 0 | int32_t length; |
403 | 0 | const UChar *s = value.getAliasString(length, errorCode); |
404 | 0 | // For example: "/LOCALE/unitsShort" |
405 | 0 | if (U_SUCCESS(errorCode) && length >= 13 && u_memcmp(s, g_LOCALE_units, 13) == 0) { |
406 | 0 | s += 13; |
407 | 0 | length -= 13; |
408 | 0 | if (*s == 0) { |
409 | 0 | return UMEASFMT_WIDTH_WIDE; |
410 | 0 | } else if (u_strCompare(s, length, gShort, 5, FALSE) == 0) { |
411 | 0 | return UMEASFMT_WIDTH_SHORT; |
412 | 0 | } else if (u_strCompare(s, length, gNarrow, 6, FALSE) == 0) { |
413 | 0 | return UMEASFMT_WIDTH_NARROW; |
414 | 0 | } |
415 | 0 | } |
416 | 0 | return UMEASFMT_WIDTH_COUNT; |
417 | 0 | } |
418 | | |
419 | | virtual void put(const char *key, ResourceValue &value, UBool /*noFallback*/, |
420 | 0 | UErrorCode &errorCode) { |
421 | 0 | // Main entry point to sink |
422 | 0 | ResourceTable widthsTable = value.getTable(errorCode); |
423 | 0 | if (U_FAILURE(errorCode)) { return; } |
424 | 0 | for (int i = 0; widthsTable.getKeyAndValue(i, key, value); ++i) { |
425 | 0 | if (value.getType() == URES_ALIAS) { |
426 | 0 | consumeAlias(key, value, errorCode); |
427 | 0 | } else { |
428 | 0 | consumeTable(key, value, errorCode); |
429 | 0 | } |
430 | 0 | } |
431 | 0 | } |
432 | | }; |
433 | | |
434 | | // Virtual destructors must be defined out of line. |
435 | | UnitDataSink::~UnitDataSink() {} |
436 | | |
437 | | } // namespace |
438 | | |
439 | | static UBool loadMeasureUnitData( |
440 | | const UResourceBundle *resource, |
441 | | MeasureFormatCacheData &cacheData, |
442 | 0 | UErrorCode &status) { |
443 | 0 | UnitDataSink sink(cacheData); |
444 | 0 | ures_getAllItemsWithFallback(resource, "", sink, status); |
445 | 0 | return U_SUCCESS(status); |
446 | 0 | } |
447 | | |
448 | | static UnicodeString loadNumericDateFormatterPattern( |
449 | | const UResourceBundle *resource, |
450 | | const char *pattern, |
451 | 0 | UErrorCode &status) { |
452 | 0 | UnicodeString result; |
453 | 0 | if (U_FAILURE(status)) { |
454 | 0 | return result; |
455 | 0 | } |
456 | 0 | CharString chs; |
457 | 0 | chs.append("durationUnits", status) |
458 | 0 | .append("/", status).append(pattern, status); |
459 | 0 | LocalUResourceBundlePointer patternBundle( |
460 | 0 | ures_getByKeyWithFallback( |
461 | 0 | resource, |
462 | 0 | chs.data(), |
463 | 0 | NULL, |
464 | 0 | &status)); |
465 | 0 | if (U_FAILURE(status)) { |
466 | 0 | return result; |
467 | 0 | } |
468 | 0 | getString(patternBundle.getAlias(), result, status); |
469 | 0 | // Replace 'h' with 'H' |
470 | 0 | int32_t len = result.length(); |
471 | 0 | UChar *buffer = result.getBuffer(len); |
472 | 0 | for (int32_t i = 0; i < len; ++i) { |
473 | 0 | if (buffer[i] == 0x68) { // 'h' |
474 | 0 | buffer[i] = 0x48; // 'H' |
475 | 0 | } |
476 | 0 | } |
477 | 0 | result.releaseBuffer(len); |
478 | 0 | return result; |
479 | 0 | } |
480 | | |
481 | | static NumericDateFormatters *loadNumericDateFormatters( |
482 | | const UResourceBundle *resource, |
483 | 0 | UErrorCode &status) { |
484 | 0 | if (U_FAILURE(status)) { |
485 | 0 | return NULL; |
486 | 0 | } |
487 | 0 | NumericDateFormatters *result = new NumericDateFormatters( |
488 | 0 | loadNumericDateFormatterPattern(resource, "hm", status), |
489 | 0 | loadNumericDateFormatterPattern(resource, "ms", status), |
490 | 0 | loadNumericDateFormatterPattern(resource, "hms", status), |
491 | 0 | status); |
492 | 0 | if (U_FAILURE(status)) { |
493 | 0 | delete result; |
494 | 0 | return NULL; |
495 | 0 | } |
496 | 0 | return result; |
497 | 0 | } |
498 | | |
499 | | template<> U_I18N_API |
500 | | const MeasureFormatCacheData *LocaleCacheKey<MeasureFormatCacheData>::createObject( |
501 | 0 | const void * /*unused*/, UErrorCode &status) const { |
502 | 0 | const char *localeId = fLoc.getName(); |
503 | 0 | LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_UNIT, localeId, &status)); |
504 | 0 | static UNumberFormatStyle currencyStyles[] = { |
505 | 0 | UNUM_CURRENCY_PLURAL, UNUM_CURRENCY_ISO, UNUM_CURRENCY}; |
506 | 0 | LocalPointer<MeasureFormatCacheData> result(new MeasureFormatCacheData(), status); |
507 | 0 | if (U_FAILURE(status)) { |
508 | 0 | return NULL; |
509 | 0 | } |
510 | 0 | if (!loadMeasureUnitData( |
511 | 0 | unitsBundle.getAlias(), |
512 | 0 | *result, |
513 | 0 | status)) { |
514 | 0 | return NULL; |
515 | 0 | } |
516 | 0 | result->adoptNumericDateFormatters(loadNumericDateFormatters( |
517 | 0 | unitsBundle.getAlias(), status)); |
518 | 0 | if (U_FAILURE(status)) { |
519 | 0 | return NULL; |
520 | 0 | } |
521 | 0 | |
522 | 0 | for (int32_t i = 0; i < WIDTH_INDEX_COUNT; ++i) { |
523 | 0 | // NumberFormat::createInstance can erase warning codes from status, so pass it |
524 | 0 | // a separate status instance |
525 | 0 | UErrorCode localStatus = U_ZERO_ERROR; |
526 | 0 | result->adoptCurrencyFormat(i, NumberFormat::createInstance( |
527 | 0 | localeId, currencyStyles[i], localStatus)); |
528 | 0 | if (localStatus != U_ZERO_ERROR) { |
529 | 0 | status = localStatus; |
530 | 0 | } |
531 | 0 | if (U_FAILURE(status)) { |
532 | 0 | return NULL; |
533 | 0 | } |
534 | 0 | } |
535 | 0 | NumberFormat *inf = NumberFormat::createInstance( |
536 | 0 | localeId, UNUM_DECIMAL, status); |
537 | 0 | if (U_FAILURE(status)) { |
538 | 0 | return NULL; |
539 | 0 | } |
540 | 0 | inf->setMaximumFractionDigits(0); |
541 | 0 | DecimalFormat *decfmt = dynamic_cast<DecimalFormat *>(inf); |
542 | 0 | if (decfmt != NULL) { |
543 | 0 | decfmt->setRoundingMode(DecimalFormat::kRoundDown); |
544 | 0 | } |
545 | 0 | result->adoptIntegerFormat(inf); |
546 | 0 | result->addRef(); |
547 | 0 | return result.orphan(); |
548 | 0 | } |
549 | | |
550 | 0 | static UBool isTimeUnit(const MeasureUnit &mu, const char *tu) { |
551 | 0 | return uprv_strcmp(mu.getType(), "duration") == 0 && |
552 | 0 | uprv_strcmp(mu.getSubtype(), tu) == 0; |
553 | 0 | } |
554 | | |
555 | | // Converts a composite measure into hours-minutes-seconds and stores at hms |
556 | | // array. [0] is hours; [1] is minutes; [2] is seconds. Returns a bit map of |
557 | | // units found: 1=hours, 2=minutes, 4=seconds. For example, if measures |
558 | | // contains hours-minutes, this function would return 3. |
559 | | // |
560 | | // If measures cannot be converted into hours, minutes, seconds or if amounts |
561 | | // are negative, or if hours, minutes, seconds are out of order, returns 0. |
562 | | static int32_t toHMS( |
563 | | const Measure *measures, |
564 | | int32_t measureCount, |
565 | | Formattable *hms, |
566 | 0 | UErrorCode &status) { |
567 | 0 | if (U_FAILURE(status)) { |
568 | 0 | return 0; |
569 | 0 | } |
570 | 0 | int32_t result = 0; |
571 | 0 | if (U_FAILURE(status)) { |
572 | 0 | return 0; |
573 | 0 | } |
574 | 0 | // We use copy constructor to ensure that both sides of equality operator |
575 | 0 | // are instances of MeasureUnit base class and not a subclass. Otherwise, |
576 | 0 | // operator== will immediately return false. |
577 | 0 | for (int32_t i = 0; i < measureCount; ++i) { |
578 | 0 | if (isTimeUnit(measures[i].getUnit(), "hour")) { |
579 | 0 | // hour must come first |
580 | 0 | if (result >= 1) { |
581 | 0 | return 0; |
582 | 0 | } |
583 | 0 | hms[0] = measures[i].getNumber(); |
584 | 0 | if (hms[0].getDouble() < 0.0) { |
585 | 0 | return 0; |
586 | 0 | } |
587 | 0 | result |= 1; |
588 | 0 | } else if (isTimeUnit(measures[i].getUnit(), "minute")) { |
589 | 0 | // minute must come after hour |
590 | 0 | if (result >= 2) { |
591 | 0 | return 0; |
592 | 0 | } |
593 | 0 | hms[1] = measures[i].getNumber(); |
594 | 0 | if (hms[1].getDouble() < 0.0) { |
595 | 0 | return 0; |
596 | 0 | } |
597 | 0 | result |= 2; |
598 | 0 | } else if (isTimeUnit(measures[i].getUnit(), "second")) { |
599 | 0 | // second must come after hour and minute |
600 | 0 | if (result >= 4) { |
601 | 0 | return 0; |
602 | 0 | } |
603 | 0 | hms[2] = measures[i].getNumber(); |
604 | 0 | if (hms[2].getDouble() < 0.0) { |
605 | 0 | return 0; |
606 | 0 | } |
607 | 0 | result |= 4; |
608 | 0 | } else { |
609 | 0 | return 0; |
610 | 0 | } |
611 | 0 | } |
612 | 0 | return result; |
613 | 0 | } |
614 | | |
615 | | |
616 | | MeasureFormat::MeasureFormat( |
617 | | const Locale &locale, UMeasureFormatWidth w, UErrorCode &status) |
618 | | : cache(NULL), |
619 | | numberFormat(NULL), |
620 | | pluralRules(NULL), |
621 | | width(w), |
622 | 0 | listFormatter(NULL) { |
623 | 0 | initMeasureFormat(locale, w, NULL, status); |
624 | 0 | } |
625 | | |
626 | | MeasureFormat::MeasureFormat( |
627 | | const Locale &locale, |
628 | | UMeasureFormatWidth w, |
629 | | NumberFormat *nfToAdopt, |
630 | | UErrorCode &status) |
631 | | : cache(NULL), |
632 | | numberFormat(NULL), |
633 | | pluralRules(NULL), |
634 | | width(w), |
635 | 0 | listFormatter(NULL) { |
636 | 0 | initMeasureFormat(locale, w, nfToAdopt, status); |
637 | 0 | } |
638 | | |
639 | | MeasureFormat::MeasureFormat(const MeasureFormat &other) : |
640 | | Format(other), |
641 | | cache(other.cache), |
642 | | numberFormat(other.numberFormat), |
643 | | pluralRules(other.pluralRules), |
644 | | width(other.width), |
645 | 0 | listFormatter(NULL) { |
646 | 0 | cache->addRef(); |
647 | 0 | numberFormat->addRef(); |
648 | 0 | pluralRules->addRef(); |
649 | 0 | if (other.listFormatter != NULL) { |
650 | 0 | listFormatter = new ListFormatter(*other.listFormatter); |
651 | 0 | } |
652 | 0 | } |
653 | | |
654 | 0 | MeasureFormat &MeasureFormat::operator=(const MeasureFormat &other) { |
655 | 0 | if (this == &other) { |
656 | 0 | return *this; |
657 | 0 | } |
658 | 0 | Format::operator=(other); |
659 | 0 | SharedObject::copyPtr(other.cache, cache); |
660 | 0 | SharedObject::copyPtr(other.numberFormat, numberFormat); |
661 | 0 | SharedObject::copyPtr(other.pluralRules, pluralRules); |
662 | 0 | width = other.width; |
663 | 0 | delete listFormatter; |
664 | 0 | if (other.listFormatter != NULL) { |
665 | 0 | listFormatter = new ListFormatter(*other.listFormatter); |
666 | 0 | } else { |
667 | 0 | listFormatter = NULL; |
668 | 0 | } |
669 | 0 | return *this; |
670 | 0 | } |
671 | | |
672 | | MeasureFormat::MeasureFormat() : |
673 | | cache(NULL), |
674 | | numberFormat(NULL), |
675 | | pluralRules(NULL), |
676 | | width(UMEASFMT_WIDTH_SHORT), |
677 | 0 | listFormatter(NULL) { |
678 | 0 | } |
679 | | |
680 | 0 | MeasureFormat::~MeasureFormat() { |
681 | 0 | if (cache != NULL) { |
682 | 0 | cache->removeRef(); |
683 | 0 | } |
684 | 0 | if (numberFormat != NULL) { |
685 | 0 | numberFormat->removeRef(); |
686 | 0 | } |
687 | 0 | if (pluralRules != NULL) { |
688 | 0 | pluralRules->removeRef(); |
689 | 0 | } |
690 | 0 | delete listFormatter; |
691 | 0 | } |
692 | | |
693 | 0 | UBool MeasureFormat::operator==(const Format &other) const { |
694 | 0 | if (this == &other) { // Same object, equal |
695 | 0 | return TRUE; |
696 | 0 | } |
697 | 0 | if (!Format::operator==(other)) { |
698 | 0 | return FALSE; |
699 | 0 | } |
700 | 0 | const MeasureFormat &rhs = static_cast<const MeasureFormat &>(other); |
701 | 0 |
|
702 | 0 | // Note: Since the ListFormatter depends only on Locale and width, we |
703 | 0 | // don't have to check it here. |
704 | 0 |
|
705 | 0 | // differing widths aren't equivalent |
706 | 0 | if (width != rhs.width) { |
707 | 0 | return FALSE; |
708 | 0 | } |
709 | 0 | // Width the same check locales. |
710 | 0 | // We don't need to check locales if both objects have same cache. |
711 | 0 | if (cache != rhs.cache) { |
712 | 0 | UErrorCode status = U_ZERO_ERROR; |
713 | 0 | const char *localeId = getLocaleID(status); |
714 | 0 | const char *rhsLocaleId = rhs.getLocaleID(status); |
715 | 0 | if (U_FAILURE(status)) { |
716 | 0 | // On failure, assume not equal |
717 | 0 | return FALSE; |
718 | 0 | } |
719 | 0 | if (uprv_strcmp(localeId, rhsLocaleId) != 0) { |
720 | 0 | return FALSE; |
721 | 0 | } |
722 | 0 | } |
723 | 0 | // Locales same, check NumberFormat if shared data differs. |
724 | 0 | return ( |
725 | 0 | numberFormat == rhs.numberFormat || |
726 | 0 | **numberFormat == **rhs.numberFormat); |
727 | 0 | } |
728 | | |
729 | 0 | Format *MeasureFormat::clone() const { |
730 | 0 | return new MeasureFormat(*this); |
731 | 0 | } |
732 | | |
733 | | UnicodeString &MeasureFormat::format( |
734 | | const Formattable &obj, |
735 | | UnicodeString &appendTo, |
736 | | FieldPosition &pos, |
737 | 0 | UErrorCode &status) const { |
738 | 0 | if (U_FAILURE(status)) return appendTo; |
739 | 0 | if (obj.getType() == Formattable::kObject) { |
740 | 0 | const UObject* formatObj = obj.getObject(); |
741 | 0 | const Measure* amount = dynamic_cast<const Measure*>(formatObj); |
742 | 0 | if (amount != NULL) { |
743 | 0 | return formatMeasure( |
744 | 0 | *amount, **numberFormat, appendTo, pos, status); |
745 | 0 | } |
746 | 0 | } |
747 | 0 | status = U_ILLEGAL_ARGUMENT_ERROR; |
748 | 0 | return appendTo; |
749 | 0 | } |
750 | | |
751 | | void MeasureFormat::parseObject( |
752 | | const UnicodeString & /*source*/, |
753 | | Formattable & /*result*/, |
754 | 0 | ParsePosition& /*pos*/) const { |
755 | 0 | return; |
756 | 0 | } |
757 | | |
758 | | UnicodeString &MeasureFormat::formatMeasurePerUnit( |
759 | | const Measure &measure, |
760 | | const MeasureUnit &perUnit, |
761 | | UnicodeString &appendTo, |
762 | | FieldPosition &pos, |
763 | 0 | UErrorCode &status) const { |
764 | 0 | if (U_FAILURE(status)) { |
765 | 0 | return appendTo; |
766 | 0 | } |
767 | 0 | bool isResolved = false; |
768 | 0 | MeasureUnit resolvedUnit = |
769 | 0 | MeasureUnit::resolveUnitPerUnit(measure.getUnit(), perUnit, &isResolved); |
770 | 0 | if (isResolved) { |
771 | 0 | Measure newMeasure(measure.getNumber(), new MeasureUnit(resolvedUnit), status); |
772 | 0 | return formatMeasure( |
773 | 0 | newMeasure, **numberFormat, appendTo, pos, status); |
774 | 0 | } |
775 | 0 | FieldPosition fpos(pos.getField()); |
776 | 0 | UnicodeString result; |
777 | 0 | int32_t offset = withPerUnitAndAppend( |
778 | 0 | formatMeasure( |
779 | 0 | measure, **numberFormat, result, fpos, status), |
780 | 0 | perUnit, |
781 | 0 | appendTo, |
782 | 0 | status); |
783 | 0 | if (U_FAILURE(status)) { |
784 | 0 | return appendTo; |
785 | 0 | } |
786 | 0 | if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) { |
787 | 0 | pos.setBeginIndex(fpos.getBeginIndex() + offset); |
788 | 0 | pos.setEndIndex(fpos.getEndIndex() + offset); |
789 | 0 | } |
790 | 0 | return appendTo; |
791 | 0 | } |
792 | | |
793 | | UnicodeString &MeasureFormat::formatMeasures( |
794 | | const Measure *measures, |
795 | | int32_t measureCount, |
796 | | UnicodeString &appendTo, |
797 | | FieldPosition &pos, |
798 | 0 | UErrorCode &status) const { |
799 | 0 | if (U_FAILURE(status)) { |
800 | 0 | return appendTo; |
801 | 0 | } |
802 | 0 | if (measureCount == 0) { |
803 | 0 | return appendTo; |
804 | 0 | } |
805 | 0 | if (measureCount == 1) { |
806 | 0 | return formatMeasure(measures[0], **numberFormat, appendTo, pos, status); |
807 | 0 | } |
808 | 0 | if (width == UMEASFMT_WIDTH_NUMERIC) { |
809 | 0 | Formattable hms[3]; |
810 | 0 | int32_t bitMap = toHMS(measures, measureCount, hms, status); |
811 | 0 | if (bitMap > 0) { |
812 | 0 | return formatNumeric(hms, bitMap, appendTo, status); |
813 | 0 | } |
814 | 0 | } |
815 | 0 | if (pos.getField() != FieldPosition::DONT_CARE) { |
816 | 0 | return formatMeasuresSlowTrack( |
817 | 0 | measures, measureCount, appendTo, pos, status); |
818 | 0 | } |
819 | 0 | UnicodeString *results = new UnicodeString[measureCount]; |
820 | 0 | if (results == NULL) { |
821 | 0 | status = U_MEMORY_ALLOCATION_ERROR; |
822 | 0 | return appendTo; |
823 | 0 | } |
824 | 0 | for (int32_t i = 0; i < measureCount; ++i) { |
825 | 0 | const NumberFormat *nf = cache->getIntegerFormat(); |
826 | 0 | if (i == measureCount - 1) { |
827 | 0 | nf = numberFormat->get(); |
828 | 0 | } |
829 | 0 | formatMeasure( |
830 | 0 | measures[i], |
831 | 0 | *nf, |
832 | 0 | results[i], |
833 | 0 | pos, |
834 | 0 | status); |
835 | 0 | } |
836 | 0 | listFormatter->format(results, measureCount, appendTo, status); |
837 | 0 | delete [] results; |
838 | 0 | return appendTo; |
839 | 0 | } |
840 | | |
841 | 0 | UnicodeString MeasureFormat::getUnitDisplayName(const MeasureUnit& unit, UErrorCode& /*status*/) const { |
842 | 0 | UMeasureFormatWidth width = getRegularWidth(this->width); |
843 | 0 | const UChar* const* styleToDnam = cache->dnams[unit.getIndex()]; |
844 | 0 | const UChar* dnam = styleToDnam[width]; |
845 | 0 | if (dnam == NULL) { |
846 | 0 | int32_t fallbackWidth = cache->widthFallback[width]; |
847 | 0 | dnam = styleToDnam[fallbackWidth]; |
848 | 0 | } |
849 | 0 |
|
850 | 0 | UnicodeString result; |
851 | 0 | if (dnam == NULL) { |
852 | 0 | result.setToBogus(); |
853 | 0 | } else { |
854 | 0 | result.setTo(dnam, -1); |
855 | 0 | } |
856 | 0 | return result; |
857 | 0 | } |
858 | | |
859 | | void MeasureFormat::initMeasureFormat( |
860 | | const Locale &locale, |
861 | | UMeasureFormatWidth w, |
862 | | NumberFormat *nfToAdopt, |
863 | 0 | UErrorCode &status) { |
864 | 0 | static const char *listStyles[] = {"unit", "unit-short", "unit-narrow"}; |
865 | 0 | LocalPointer<NumberFormat> nf(nfToAdopt); |
866 | 0 | if (U_FAILURE(status)) { |
867 | 0 | return; |
868 | 0 | } |
869 | 0 | const char *name = locale.getName(); |
870 | 0 | setLocaleIDs(name, name); |
871 | 0 |
|
872 | 0 | UnifiedCache::getByLocale(locale, cache, status); |
873 | 0 | if (U_FAILURE(status)) { |
874 | 0 | return; |
875 | 0 | } |
876 | 0 | |
877 | 0 | const SharedPluralRules *pr = PluralRules::createSharedInstance( |
878 | 0 | locale, UPLURAL_TYPE_CARDINAL, status); |
879 | 0 | if (U_FAILURE(status)) { |
880 | 0 | return; |
881 | 0 | } |
882 | 0 | SharedObject::copyPtr(pr, pluralRules); |
883 | 0 | pr->removeRef(); |
884 | 0 | if (nf.isNull()) { |
885 | 0 | const SharedNumberFormat *shared = NumberFormat::createSharedInstance( |
886 | 0 | locale, UNUM_DECIMAL, status); |
887 | 0 | if (U_FAILURE(status)) { |
888 | 0 | return; |
889 | 0 | } |
890 | 0 | SharedObject::copyPtr(shared, numberFormat); |
891 | 0 | shared->removeRef(); |
892 | 0 | } else { |
893 | 0 | adoptNumberFormat(nf.orphan(), status); |
894 | 0 | if (U_FAILURE(status)) { |
895 | 0 | return; |
896 | 0 | } |
897 | 0 | } |
898 | 0 | width = w; |
899 | 0 | delete listFormatter; |
900 | 0 | listFormatter = ListFormatter::createInstance( |
901 | 0 | locale, |
902 | 0 | listStyles[getRegularWidth(width)], |
903 | 0 | status); |
904 | 0 | } |
905 | | |
906 | | void MeasureFormat::adoptNumberFormat( |
907 | 0 | NumberFormat *nfToAdopt, UErrorCode &status) { |
908 | 0 | LocalPointer<NumberFormat> nf(nfToAdopt); |
909 | 0 | if (U_FAILURE(status)) { |
910 | 0 | return; |
911 | 0 | } |
912 | 0 | SharedNumberFormat *shared = new SharedNumberFormat(nf.getAlias()); |
913 | 0 | if (shared == NULL) { |
914 | 0 | status = U_MEMORY_ALLOCATION_ERROR; |
915 | 0 | return; |
916 | 0 | } |
917 | 0 | nf.orphan(); |
918 | 0 | SharedObject::copyPtr(shared, numberFormat); |
919 | 0 | } |
920 | | |
921 | 0 | UBool MeasureFormat::setMeasureFormatLocale(const Locale &locale, UErrorCode &status) { |
922 | 0 | if (U_FAILURE(status) || locale == getLocale(status)) { |
923 | 0 | return FALSE; |
924 | 0 | } |
925 | 0 | initMeasureFormat(locale, width, NULL, status); |
926 | 0 | return U_SUCCESS(status); |
927 | 0 | } |
928 | | |
929 | 0 | const NumberFormat &MeasureFormat::getNumberFormat() const { |
930 | 0 | return **numberFormat; |
931 | 0 | } |
932 | | |
933 | 0 | const PluralRules &MeasureFormat::getPluralRules() const { |
934 | 0 | return **pluralRules; |
935 | 0 | } |
936 | | |
937 | 0 | Locale MeasureFormat::getLocale(UErrorCode &status) const { |
938 | 0 | return Format::getLocale(ULOC_VALID_LOCALE, status); |
939 | 0 | } |
940 | | |
941 | 0 | const char *MeasureFormat::getLocaleID(UErrorCode &status) const { |
942 | 0 | return Format::getLocaleID(ULOC_VALID_LOCALE, status); |
943 | 0 | } |
944 | | |
945 | | UnicodeString &MeasureFormat::formatMeasure( |
946 | | const Measure &measure, |
947 | | const NumberFormat &nf, |
948 | | UnicodeString &appendTo, |
949 | | FieldPosition &pos, |
950 | 0 | UErrorCode &status) const { |
951 | 0 | if (U_FAILURE(status)) { |
952 | 0 | return appendTo; |
953 | 0 | } |
954 | 0 | const Formattable& amtNumber = measure.getNumber(); |
955 | 0 | const MeasureUnit& amtUnit = measure.getUnit(); |
956 | 0 | if (isCurrency(amtUnit)) { |
957 | 0 | UChar isoCode[4]; |
958 | 0 | u_charsToUChars(amtUnit.getSubtype(), isoCode, 4); |
959 | 0 | return cache->getCurrencyFormat(width)->format( |
960 | 0 | new CurrencyAmount(amtNumber, isoCode, status), |
961 | 0 | appendTo, |
962 | 0 | pos, |
963 | 0 | status); |
964 | 0 | } |
965 | 0 | UnicodeString formattedNumber; |
966 | 0 | StandardPlural::Form pluralForm = QuantityFormatter::selectPlural( |
967 | 0 | amtNumber, nf, **pluralRules, formattedNumber, pos, status); |
968 | 0 | const SimpleFormatter *formatter = getPluralFormatter(amtUnit, width, pluralForm, status); |
969 | 0 | return QuantityFormatter::format(*formatter, formattedNumber, appendTo, pos, status); |
970 | 0 | } |
971 | | |
972 | | // Formats hours-minutes-seconds as 5:37:23 or similar. |
973 | | UnicodeString &MeasureFormat::formatNumeric( |
974 | | const Formattable *hms, // always length 3 |
975 | | int32_t bitMap, // 1=hourset, 2=minuteset, 4=secondset |
976 | | UnicodeString &appendTo, |
977 | 0 | UErrorCode &status) const { |
978 | 0 | if (U_FAILURE(status)) { |
979 | 0 | return appendTo; |
980 | 0 | } |
981 | 0 | UDate millis = |
982 | 0 | (UDate) (((uprv_trunc(hms[0].getDouble(status)) * 60.0 |
983 | 0 | + uprv_trunc(hms[1].getDouble(status))) * 60.0 |
984 | 0 | + uprv_trunc(hms[2].getDouble(status))) * 1000.0); |
985 | 0 | switch (bitMap) { |
986 | 0 | case 5: // hs |
987 | 0 | case 7: // hms |
988 | 0 | return formatNumeric( |
989 | 0 | millis, |
990 | 0 | cache->getNumericDateFormatters()->hourMinuteSecond, |
991 | 0 | UDAT_SECOND_FIELD, |
992 | 0 | hms[2], |
993 | 0 | appendTo, |
994 | 0 | status); |
995 | 0 | break; |
996 | 0 | case 6: // ms |
997 | 0 | return formatNumeric( |
998 | 0 | millis, |
999 | 0 | cache->getNumericDateFormatters()->minuteSecond, |
1000 | 0 | UDAT_SECOND_FIELD, |
1001 | 0 | hms[2], |
1002 | 0 | appendTo, |
1003 | 0 | status); |
1004 | 0 | break; |
1005 | 0 | case 3: // hm |
1006 | 0 | return formatNumeric( |
1007 | 0 | millis, |
1008 | 0 | cache->getNumericDateFormatters()->hourMinute, |
1009 | 0 | UDAT_MINUTE_FIELD, |
1010 | 0 | hms[1], |
1011 | 0 | appendTo, |
1012 | 0 | status); |
1013 | 0 | break; |
1014 | 0 | default: |
1015 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
1016 | 0 | return appendTo; |
1017 | 0 | break; |
1018 | 0 | } |
1019 | 0 | return appendTo; |
1020 | 0 | } |
1021 | | |
1022 | | static void appendRange( |
1023 | | const UnicodeString &src, |
1024 | | int32_t start, |
1025 | | int32_t end, |
1026 | 0 | UnicodeString &dest) { |
1027 | 0 | dest.append(src, start, end - start); |
1028 | 0 | } |
1029 | | |
1030 | | static void appendRange( |
1031 | | const UnicodeString &src, |
1032 | | int32_t end, |
1033 | 0 | UnicodeString &dest) { |
1034 | 0 | dest.append(src, end, src.length() - end); |
1035 | 0 | } |
1036 | | |
1037 | | // Formats time like 5:37:23 |
1038 | | UnicodeString &MeasureFormat::formatNumeric( |
1039 | | UDate date, // Time since epoch 1:30:00 would be 5400000 |
1040 | | const DateFormat &dateFmt, // h:mm, m:ss, or h:mm:ss |
1041 | | UDateFormatField smallestField, // seconds in 5:37:23.5 |
1042 | | const Formattable &smallestAmount, // 23.5 for 5:37:23.5 |
1043 | | UnicodeString &appendTo, |
1044 | 0 | UErrorCode &status) const { |
1045 | 0 | if (U_FAILURE(status)) { |
1046 | 0 | return appendTo; |
1047 | 0 | } |
1048 | 0 | // Format the smallest amount with this object's NumberFormat |
1049 | 0 | UnicodeString smallestAmountFormatted; |
1050 | 0 |
|
1051 | 0 | // We keep track of the integer part of smallest amount so that |
1052 | 0 | // we can replace it later so that we get '0:00:09.3' instead of |
1053 | 0 | // '0:00:9.3' |
1054 | 0 | FieldPosition intFieldPosition(UNUM_INTEGER_FIELD); |
1055 | 0 | (*numberFormat)->format( |
1056 | 0 | smallestAmount, smallestAmountFormatted, intFieldPosition, status); |
1057 | 0 | if ( |
1058 | 0 | intFieldPosition.getBeginIndex() == 0 && |
1059 | 0 | intFieldPosition.getEndIndex() == 0) { |
1060 | 0 | status = U_INTERNAL_PROGRAM_ERROR; |
1061 | 0 | return appendTo; |
1062 | 0 | } |
1063 | 0 | |
1064 | 0 | // Format time. draft becomes something like '5:30:45' |
1065 | 0 | // #13606: DateFormat is not thread-safe, but MeasureFormat advertises itself as thread-safe. |
1066 | 0 | FieldPosition smallestFieldPosition(smallestField); |
1067 | 0 | UnicodeString draft; |
1068 | 0 | static UMutex dateFmtMutex = U_MUTEX_INITIALIZER; |
1069 | 0 | umtx_lock(&dateFmtMutex); |
1070 | 0 | dateFmt.format(date, draft, smallestFieldPosition, status); |
1071 | 0 | umtx_unlock(&dateFmtMutex); |
1072 | 0 |
|
1073 | 0 | // If we find field for smallest amount replace it with the formatted |
1074 | 0 | // smallest amount from above taking care to replace the integer part |
1075 | 0 | // with what is in original time. For example, If smallest amount |
1076 | 0 | // is 9.35s and the formatted time is 0:00:09 then 9.35 becomes 09.35 |
1077 | 0 | // and replacing yields 0:00:09.35 |
1078 | 0 | if (smallestFieldPosition.getBeginIndex() != 0 || |
1079 | 0 | smallestFieldPosition.getEndIndex() != 0) { |
1080 | 0 | appendRange(draft, 0, smallestFieldPosition.getBeginIndex(), appendTo); |
1081 | 0 | appendRange( |
1082 | 0 | smallestAmountFormatted, |
1083 | 0 | 0, |
1084 | 0 | intFieldPosition.getBeginIndex(), |
1085 | 0 | appendTo); |
1086 | 0 | appendRange( |
1087 | 0 | draft, |
1088 | 0 | smallestFieldPosition.getBeginIndex(), |
1089 | 0 | smallestFieldPosition.getEndIndex(), |
1090 | 0 | appendTo); |
1091 | 0 | appendRange( |
1092 | 0 | smallestAmountFormatted, |
1093 | 0 | intFieldPosition.getEndIndex(), |
1094 | 0 | appendTo); |
1095 | 0 | appendRange( |
1096 | 0 | draft, |
1097 | 0 | smallestFieldPosition.getEndIndex(), |
1098 | 0 | appendTo); |
1099 | 0 | } else { |
1100 | 0 | appendTo.append(draft); |
1101 | 0 | } |
1102 | 0 | return appendTo; |
1103 | 0 | } |
1104 | | |
1105 | | const SimpleFormatter *MeasureFormat::getFormatterOrNull( |
1106 | 0 | const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index) const { |
1107 | 0 | width = getRegularWidth(width); |
1108 | 0 | SimpleFormatter *const (*unitPatterns)[PATTERN_COUNT] = &cache->patterns[unit.getIndex()][0]; |
1109 | 0 | if (unitPatterns[width][index] != NULL) { |
1110 | 0 | return unitPatterns[width][index]; |
1111 | 0 | } |
1112 | 0 | int32_t fallbackWidth = cache->widthFallback[width]; |
1113 | 0 | if (fallbackWidth != UMEASFMT_WIDTH_COUNT && unitPatterns[fallbackWidth][index] != NULL) { |
1114 | 0 | return unitPatterns[fallbackWidth][index]; |
1115 | 0 | } |
1116 | 0 | return NULL; |
1117 | 0 | } |
1118 | | |
1119 | | const SimpleFormatter *MeasureFormat::getFormatter( |
1120 | | const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index, |
1121 | 0 | UErrorCode &errorCode) const { |
1122 | 0 | if (U_FAILURE(errorCode)) { |
1123 | 0 | return NULL; |
1124 | 0 | } |
1125 | 0 | const SimpleFormatter *pattern = getFormatterOrNull(unit, width, index); |
1126 | 0 | if (pattern == NULL) { |
1127 | 0 | errorCode = U_MISSING_RESOURCE_ERROR; |
1128 | 0 | } |
1129 | 0 | return pattern; |
1130 | 0 | } |
1131 | | |
1132 | | const SimpleFormatter *MeasureFormat::getPluralFormatter( |
1133 | | const MeasureUnit &unit, UMeasureFormatWidth width, int32_t index, |
1134 | 0 | UErrorCode &errorCode) const { |
1135 | 0 | if (U_FAILURE(errorCode)) { |
1136 | 0 | return NULL; |
1137 | 0 | } |
1138 | 0 | if (index != StandardPlural::OTHER) { |
1139 | 0 | const SimpleFormatter *pattern = getFormatterOrNull(unit, width, index); |
1140 | 0 | if (pattern != NULL) { |
1141 | 0 | return pattern; |
1142 | 0 | } |
1143 | 0 | } |
1144 | 0 | return getFormatter(unit, width, StandardPlural::OTHER, errorCode); |
1145 | 0 | } |
1146 | | |
1147 | | const SimpleFormatter *MeasureFormat::getPerFormatter( |
1148 | | UMeasureFormatWidth width, |
1149 | 0 | UErrorCode &status) const { |
1150 | 0 | if (U_FAILURE(status)) { |
1151 | 0 | return NULL; |
1152 | 0 | } |
1153 | 0 | width = getRegularWidth(width); |
1154 | 0 | const SimpleFormatter * perFormatters = cache->perFormatters; |
1155 | 0 | if (perFormatters[width].getArgumentLimit() == 2) { |
1156 | 0 | return &perFormatters[width]; |
1157 | 0 | } |
1158 | 0 | int32_t fallbackWidth = cache->widthFallback[width]; |
1159 | 0 | if (fallbackWidth != UMEASFMT_WIDTH_COUNT && |
1160 | 0 | perFormatters[fallbackWidth].getArgumentLimit() == 2) { |
1161 | 0 | return &perFormatters[fallbackWidth]; |
1162 | 0 | } |
1163 | 0 | status = U_MISSING_RESOURCE_ERROR; |
1164 | 0 | return NULL; |
1165 | 0 | } |
1166 | | |
1167 | | int32_t MeasureFormat::withPerUnitAndAppend( |
1168 | | const UnicodeString &formatted, |
1169 | | const MeasureUnit &perUnit, |
1170 | | UnicodeString &appendTo, |
1171 | 0 | UErrorCode &status) const { |
1172 | 0 | int32_t offset = -1; |
1173 | 0 | if (U_FAILURE(status)) { |
1174 | 0 | return offset; |
1175 | 0 | } |
1176 | 0 | const SimpleFormatter *perUnitFormatter = getFormatterOrNull(perUnit, width, PER_UNIT_INDEX); |
1177 | 0 | if (perUnitFormatter != NULL) { |
1178 | 0 | const UnicodeString *params[] = {&formatted}; |
1179 | 0 | perUnitFormatter->formatAndAppend( |
1180 | 0 | params, |
1181 | 0 | UPRV_LENGTHOF(params), |
1182 | 0 | appendTo, |
1183 | 0 | &offset, |
1184 | 0 | 1, |
1185 | 0 | status); |
1186 | 0 | return offset; |
1187 | 0 | } |
1188 | 0 | const SimpleFormatter *perFormatter = getPerFormatter(width, status); |
1189 | 0 | const SimpleFormatter *pattern = |
1190 | 0 | getPluralFormatter(perUnit, width, StandardPlural::ONE, status); |
1191 | 0 | if (U_FAILURE(status)) { |
1192 | 0 | return offset; |
1193 | 0 | } |
1194 | 0 | UnicodeString perUnitString = pattern->getTextWithNoArguments(); |
1195 | 0 | perUnitString.trim(); |
1196 | 0 | const UnicodeString *params[] = {&formatted, &perUnitString}; |
1197 | 0 | perFormatter->formatAndAppend( |
1198 | 0 | params, |
1199 | 0 | UPRV_LENGTHOF(params), |
1200 | 0 | appendTo, |
1201 | 0 | &offset, |
1202 | 0 | 1, |
1203 | 0 | status); |
1204 | 0 | return offset; |
1205 | 0 | } |
1206 | | |
1207 | | UnicodeString &MeasureFormat::formatMeasuresSlowTrack( |
1208 | | const Measure *measures, |
1209 | | int32_t measureCount, |
1210 | | UnicodeString& appendTo, |
1211 | | FieldPosition& pos, |
1212 | 0 | UErrorCode& status) const { |
1213 | 0 | if (U_FAILURE(status)) { |
1214 | 0 | return appendTo; |
1215 | 0 | } |
1216 | 0 | FieldPosition dontCare(FieldPosition::DONT_CARE); |
1217 | 0 | FieldPosition fpos(pos.getField()); |
1218 | 0 | UnicodeString *results = new UnicodeString[measureCount]; |
1219 | 0 | int32_t fieldPositionFoundIndex = -1; |
1220 | 0 | for (int32_t i = 0; i < measureCount; ++i) { |
1221 | 0 | const NumberFormat *nf = cache->getIntegerFormat(); |
1222 | 0 | if (i == measureCount - 1) { |
1223 | 0 | nf = numberFormat->get(); |
1224 | 0 | } |
1225 | 0 | if (fieldPositionFoundIndex == -1) { |
1226 | 0 | formatMeasure(measures[i], *nf, results[i], fpos, status); |
1227 | 0 | if (U_FAILURE(status)) { |
1228 | 0 | delete [] results; |
1229 | 0 | return appendTo; |
1230 | 0 | } |
1231 | 0 | if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) { |
1232 | 0 | fieldPositionFoundIndex = i; |
1233 | 0 | } |
1234 | 0 | } else { |
1235 | 0 | formatMeasure(measures[i], *nf, results[i], dontCare, status); |
1236 | 0 | } |
1237 | 0 | } |
1238 | 0 | int32_t offset; |
1239 | 0 | listFormatter->format( |
1240 | 0 | results, |
1241 | 0 | measureCount, |
1242 | 0 | appendTo, |
1243 | 0 | fieldPositionFoundIndex, |
1244 | 0 | offset, |
1245 | 0 | status); |
1246 | 0 | if (U_FAILURE(status)) { |
1247 | 0 | delete [] results; |
1248 | 0 | return appendTo; |
1249 | 0 | } |
1250 | 0 | if (offset != -1) { |
1251 | 0 | pos.setBeginIndex(fpos.getBeginIndex() + offset); |
1252 | 0 | pos.setEndIndex(fpos.getEndIndex() + offset); |
1253 | 0 | } |
1254 | 0 | delete [] results; |
1255 | 0 | return appendTo; |
1256 | 0 | } |
1257 | | |
1258 | | MeasureFormat* U_EXPORT2 MeasureFormat::createCurrencyFormat(const Locale& locale, |
1259 | 0 | UErrorCode& ec) { |
1260 | 0 | CurrencyFormat* fmt = NULL; |
1261 | 0 | if (U_SUCCESS(ec)) { |
1262 | 0 | fmt = new CurrencyFormat(locale, ec); |
1263 | 0 | if (U_FAILURE(ec)) { |
1264 | 0 | delete fmt; |
1265 | 0 | fmt = NULL; |
1266 | 0 | } |
1267 | 0 | } |
1268 | 0 | return fmt; |
1269 | 0 | } |
1270 | | |
1271 | 0 | MeasureFormat* U_EXPORT2 MeasureFormat::createCurrencyFormat(UErrorCode& ec) { |
1272 | 0 | if (U_FAILURE(ec)) { |
1273 | 0 | return NULL; |
1274 | 0 | } |
1275 | 0 | return MeasureFormat::createCurrencyFormat(Locale::getDefault(), ec); |
1276 | 0 | } |
1277 | | |
1278 | | U_NAMESPACE_END |
1279 | | |
1280 | | #endif /* #if !UCONFIG_NO_FORMATTING */ |