1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 *******************************************************************************
5 * Copyright (C) 2008-2015, Google, International Business Machines Corporation
6 * and others. All Rights Reserved.
7 *******************************************************************************
8 */
9
10 #include "unicode/tmutfmt.h"
11
12 #if !UCONFIG_NO_FORMATTING
13
14 #include <utility>
15
16 #include "unicode/decimfmt.h"
17 #include "unicode/localpointer.h"
18 #include "plurrule_impl.h"
19 #include "uvector.h"
20 #include "charstr.h"
21 #include "cmemory.h"
22 #include "cstring.h"
23 #include "hash.h"
24 #include "ulocimp.h"
25 #include "uresimp.h"
26 #include "ureslocs.h"
27 #include "unicode/msgfmt.h"
28 #include "uassert.h"
29
30 #define LEFT_CURLY_BRACKET ((char16_t)0x007B)
31 #define RIGHT_CURLY_BRACKET ((char16_t)0x007D)
32 #define SPACE ((char16_t)0x0020)
33 #define DIGIT_ZERO ((char16_t)0x0030)
34 #define LOW_S ((char16_t)0x0073)
35 #define LOW_M ((char16_t)0x006D)
36 #define LOW_I ((char16_t)0x0069)
37 #define LOW_N ((char16_t)0x006E)
38 #define LOW_H ((char16_t)0x0068)
39 #define LOW_W ((char16_t)0x0077)
40 #define LOW_D ((char16_t)0x0064)
41 #define LOW_Y ((char16_t)0x0079)
42 #define LOW_Z ((char16_t)0x007A)
43 #define LOW_E ((char16_t)0x0065)
44 #define LOW_R ((char16_t)0x0072)
45 #define LOW_O ((char16_t)0x006F)
46 #define LOW_N ((char16_t)0x006E)
47 #define LOW_T ((char16_t)0x0074)
48
49
50 //TODO: define in compile time
51 //#define TMUTFMT_DEBUG 1
52
53 #ifdef TMUTFMT_DEBUG
54 #include <iostream>
55 #endif
56
57 U_NAMESPACE_BEGIN
58
59
60
61 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(TimeUnitFormat)
62
63 static const char gUnitsTag[] = "units";
64 static const char gShortUnitsTag[] = "unitsShort";
65 static const char gTimeUnitYear[] = "year";
66 static const char gTimeUnitMonth[] = "month";
67 static const char gTimeUnitDay[] = "day";
68 static const char gTimeUnitWeek[] = "week";
69 static const char gTimeUnitHour[] = "hour";
70 static const char gTimeUnitMinute[] = "minute";
71 static const char gTimeUnitSecond[] = "second";
72 static const char gPluralCountOther[] = "other";
73
74 static const char16_t DEFAULT_PATTERN_FOR_SECOND[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_S, 0};
75 static const char16_t DEFAULT_PATTERN_FOR_MINUTE[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, LOW_I, LOW_N, 0};
76 static const char16_t DEFAULT_PATTERN_FOR_HOUR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_H, 0};
77 static const char16_t DEFAULT_PATTERN_FOR_WEEK[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_W, 0};
78 static const char16_t DEFAULT_PATTERN_FOR_DAY[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_D, 0};
79 static const char16_t DEFAULT_PATTERN_FOR_MONTH[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, 0};
80 static const char16_t DEFAULT_PATTERN_FOR_YEAR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_Y, 0};
81
82 static const char16_t PLURAL_COUNT_ZERO[] = {LOW_Z, LOW_E, LOW_R, LOW_O, 0};
83 static const char16_t PLURAL_COUNT_ONE[] = {LOW_O, LOW_N, LOW_E, 0};
84 static const char16_t PLURAL_COUNT_TWO[] = {LOW_T, LOW_W, LOW_O, 0};
85
TimeUnitFormat(UErrorCode & status)86 TimeUnitFormat::TimeUnitFormat(UErrorCode& status) {
87 initMeasureFormat(Locale::getDefault(), UMEASFMT_WIDTH_WIDE, nullptr, status);
88 create(UTMUTFMT_FULL_STYLE, status);
89 }
90
91
TimeUnitFormat(const Locale & locale,UErrorCode & status)92 TimeUnitFormat::TimeUnitFormat(const Locale& locale, UErrorCode& status) {
93 initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, nullptr, status);
94 create(UTMUTFMT_FULL_STYLE, status);
95 }
96
97
TimeUnitFormat(const Locale & locale,UTimeUnitFormatStyle style,UErrorCode & status)98 TimeUnitFormat::TimeUnitFormat(const Locale& locale, UTimeUnitFormatStyle style, UErrorCode& status) {
99 switch (style) {
100 case UTMUTFMT_FULL_STYLE:
101 initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, nullptr, status);
102 break;
103 case UTMUTFMT_ABBREVIATED_STYLE:
104 initMeasureFormat(locale, UMEASFMT_WIDTH_SHORT, nullptr, status);
105 break;
106 default:
107 initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, nullptr, status);
108 break;
109 }
110 create(style, status);
111 }
112
TimeUnitFormat(const TimeUnitFormat & other)113 TimeUnitFormat::TimeUnitFormat(const TimeUnitFormat& other)
114 : MeasureFormat(other),
115 fStyle(other.fStyle)
116 {
117 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
118 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
119 i = (TimeUnit::UTimeUnitFields)(i+1)) {
120 UErrorCode status = U_ZERO_ERROR;
121 fTimeUnitToCountToPatterns[i] = initHash(status);
122 if (U_SUCCESS(status)) {
123 copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status);
124 } else {
125 delete fTimeUnitToCountToPatterns[i];
126 fTimeUnitToCountToPatterns[i] = nullptr;
127 }
128 }
129 }
130
131
~TimeUnitFormat()132 TimeUnitFormat::~TimeUnitFormat() {
133 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
134 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
135 i = (TimeUnit::UTimeUnitFields)(i+1)) {
136 deleteHash(fTimeUnitToCountToPatterns[i]);
137 fTimeUnitToCountToPatterns[i] = nullptr;
138 }
139 }
140
141
142 TimeUnitFormat*
clone() const143 TimeUnitFormat::clone() const {
144 return new TimeUnitFormat(*this);
145 }
146
147
148 TimeUnitFormat&
operator =(const TimeUnitFormat & other)149 TimeUnitFormat::operator=(const TimeUnitFormat& other) {
150 if (this == &other) {
151 return *this;
152 }
153 MeasureFormat::operator=(other);
154 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
155 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
156 i = (TimeUnit::UTimeUnitFields)(i+1)) {
157 deleteHash(fTimeUnitToCountToPatterns[i]);
158 fTimeUnitToCountToPatterns[i] = nullptr;
159 }
160 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
161 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
162 i = (TimeUnit::UTimeUnitFields)(i+1)) {
163 UErrorCode status = U_ZERO_ERROR;
164 fTimeUnitToCountToPatterns[i] = initHash(status);
165 if (U_SUCCESS(status)) {
166 copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status);
167 } else {
168 delete fTimeUnitToCountToPatterns[i];
169 fTimeUnitToCountToPatterns[i] = nullptr;
170 }
171 }
172 fStyle = other.fStyle;
173 return *this;
174 }
175
176 void
parseObject(const UnicodeString & source,Formattable & result,ParsePosition & pos) const177 TimeUnitFormat::parseObject(const UnicodeString& source,
178 Formattable& result,
179 ParsePosition& pos) const {
180 Formattable resultNumber(0.0);
181 UBool withNumberFormat = false;
182 TimeUnit::UTimeUnitFields resultTimeUnit = TimeUnit::UTIMEUNIT_FIELD_COUNT;
183 int32_t oldPos = pos.getIndex();
184 int32_t newPos = -1;
185 int32_t longestParseDistance = 0;
186 UnicodeString* countOfLongestMatch = nullptr;
187 #ifdef TMUTFMT_DEBUG
188 char res[1000];
189 source.extract(0, source.length(), res, "UTF-8");
190 std::cout << "parse source: " << res << "\n";
191 #endif
192 // parse by iterating through all available patterns
193 // and looking for the longest match.
194 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
195 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
196 i = (TimeUnit::UTimeUnitFields)(i+1)) {
197 Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i];
198 int32_t elemPos = UHASH_FIRST;
199 const UHashElement* elem = nullptr;
200 while ((elem = countToPatterns->nextElement(elemPos)) != nullptr){
201 const UHashTok keyTok = elem->key;
202 UnicodeString* count = (UnicodeString*)keyTok.pointer;
203 #ifdef TMUTFMT_DEBUG
204 count->extract(0, count->length(), res, "UTF-8");
205 std::cout << "parse plural count: " << res << "\n";
206 #endif
207 const UHashTok valueTok = elem->value;
208 // the value is a pair of MessageFormat*
209 MessageFormat** patterns = (MessageFormat**)valueTok.pointer;
210 for (UTimeUnitFormatStyle style = UTMUTFMT_FULL_STYLE; style < UTMUTFMT_FORMAT_STYLE_COUNT;
211 style = (UTimeUnitFormatStyle)(style + 1)) {
212 MessageFormat* pattern = patterns[style];
213 pos.setErrorIndex(-1);
214 pos.setIndex(oldPos);
215 // see if we can parse
216 Formattable parsed;
217 pattern->parseObject(source, parsed, pos);
218 if (pos.getErrorIndex() != -1 || pos.getIndex() == oldPos) {
219 continue;
220 }
221 #ifdef TMUTFMT_DEBUG
222 std::cout << "parsed.getType: " << parsed.getType() << "\n";
223 #endif
224 Formattable tmpNumber(0.0);
225 if (pattern->getArgTypeCount() != 0) {
226 Formattable& temp = parsed[0];
227 if (temp.getType() == Formattable::kString) {
228 UnicodeString tmpString;
229 UErrorCode pStatus = U_ZERO_ERROR;
230 getNumberFormatInternal().parse(temp.getString(tmpString), tmpNumber, pStatus);
231 if (U_FAILURE(pStatus)) {
232 continue;
233 }
234 } else if (temp.isNumeric()) {
235 tmpNumber = temp;
236 } else {
237 continue;
238 }
239 }
240 int32_t parseDistance = pos.getIndex() - oldPos;
241 if (parseDistance > longestParseDistance) {
242 if (pattern->getArgTypeCount() != 0) {
243 resultNumber = tmpNumber;
244 withNumberFormat = true;
245 } else {
246 withNumberFormat = false;
247 }
248 resultTimeUnit = i;
249 newPos = pos.getIndex();
250 longestParseDistance = parseDistance;
251 countOfLongestMatch = count;
252 }
253 }
254 }
255 }
256 /* After find the longest match, parse the number.
257 * Result number could be null for the pattern without number pattern.
258 * such as unit pattern in Arabic.
259 * When result number is null, use plural rule to set the number.
260 */
261 if (withNumberFormat == false && longestParseDistance != 0) {
262 // set the number using plurrual count
263 if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ZERO, 4)) {
264 resultNumber = Formattable(0.0);
265 } else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ONE, 3)) {
266 resultNumber = Formattable(1.0);
267 } else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_TWO, 3)) {
268 resultNumber = Formattable(2.0);
269 } else {
270 // should not happen.
271 // TODO: how to handle?
272 resultNumber = Formattable(3.0);
273 }
274 }
275 if (longestParseDistance == 0) {
276 pos.setIndex(oldPos);
277 pos.setErrorIndex(0);
278 } else {
279 UErrorCode status = U_ZERO_ERROR;
280 LocalPointer<TimeUnitAmount> tmutamt(new TimeUnitAmount(resultNumber, resultTimeUnit, status), status);
281 if (U_SUCCESS(status)) {
282 result.adoptObject(tmutamt.orphan());
283 pos.setIndex(newPos);
284 pos.setErrorIndex(-1);
285 } else {
286 pos.setIndex(oldPos);
287 pos.setErrorIndex(0);
288 }
289 }
290 }
291
292 void
create(UTimeUnitFormatStyle style,UErrorCode & status)293 TimeUnitFormat::create(UTimeUnitFormatStyle style, UErrorCode& status) {
294 // fTimeUnitToCountToPatterns[] must have its elements initialized to nullptr first
295 // before checking for failure status.
296 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
297 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
298 i = (TimeUnit::UTimeUnitFields)(i+1)) {
299 fTimeUnitToCountToPatterns[i] = nullptr;
300 }
301
302 if (U_FAILURE(status)) {
303 return;
304 }
305 if (style < UTMUTFMT_FULL_STYLE || style >= UTMUTFMT_FORMAT_STYLE_COUNT) {
306 status = U_ILLEGAL_ARGUMENT_ERROR;
307 return;
308 }
309 fStyle = style;
310
311 //TODO: format() and parseObj() are const member functions,
312 //so, can not do lazy initialization in C++.
313 //setup has to be done in constructors.
314 //and here, the behavior is not consistent with Java.
315 //In Java, create an empty instance does not setup locale as
316 //default locale. If it followed by setNumberFormat(),
317 //in format(), the locale will set up as the locale in fNumberFormat.
318 //But in C++, this sets the locale as the default locale.
319 setup(status);
320 }
321
322 void
setup(UErrorCode & err)323 TimeUnitFormat::setup(UErrorCode& err) {
324 initDataMembers(err);
325
326 UVector pluralCounts(nullptr, uhash_compareUnicodeString, 6, err);
327 LocalPointer<StringEnumeration> keywords(getPluralRules().getKeywords(err), err);
328 if (U_FAILURE(err)) {
329 return;
330 }
331 UnicodeString* pluralCount;
332 while ((pluralCount = const_cast<UnicodeString*>(keywords->snext(err))) != nullptr) {
333 pluralCounts.addElement(pluralCount, err);
334 }
335 readFromCurrentLocale(UTMUTFMT_FULL_STYLE, gUnitsTag, pluralCounts, err);
336 checkConsistency(UTMUTFMT_FULL_STYLE, gUnitsTag, err);
337 readFromCurrentLocale(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, pluralCounts, err);
338 checkConsistency(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, err);
339 }
340
341
342 void
initDataMembers(UErrorCode & err)343 TimeUnitFormat::initDataMembers(UErrorCode& err){
344 if (U_FAILURE(err)) {
345 return;
346 }
347 for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
348 i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
349 i = (TimeUnit::UTimeUnitFields)(i+1)) {
350 deleteHash(fTimeUnitToCountToPatterns[i]);
351 fTimeUnitToCountToPatterns[i] = nullptr;
352 }
353 }
354
355 struct TimeUnitFormatReadSink : public ResourceSink {
356 TimeUnitFormat *timeUnitFormatObj;
357 const UVector &pluralCounts;
358 UTimeUnitFormatStyle style;
359 UBool beenHere;
360
TimeUnitFormatReadSinkTimeUnitFormatReadSink361 TimeUnitFormatReadSink(TimeUnitFormat *timeUnitFormatObj,
362 const UVector &pluralCounts, UTimeUnitFormatStyle style) :
363 timeUnitFormatObj(timeUnitFormatObj), pluralCounts(pluralCounts),
364 style(style), beenHere(false){}
365
366 virtual ~TimeUnitFormatReadSink();
367
putTimeUnitFormatReadSink368 virtual void put(const char *key, ResourceValue &value, UBool, UErrorCode &errorCode) override {
369 // Skip all put() calls except the first one -- discard all fallback data.
370 if (beenHere) {
371 return;
372 } else {
373 beenHere = true;
374 }
375
376 ResourceTable units = value.getTable(errorCode);
377 if (U_FAILURE(errorCode)) { return; }
378
379 for (int32_t i = 0; units.getKeyAndValue(i, key, value); ++i) {
380 const char* timeUnitName = key;
381 if (timeUnitName == nullptr) {
382 continue;
383 }
384
385 TimeUnit::UTimeUnitFields timeUnitField = TimeUnit::UTIMEUNIT_FIELD_COUNT;
386 if ( uprv_strcmp(timeUnitName, gTimeUnitYear) == 0 ) {
387 timeUnitField = TimeUnit::UTIMEUNIT_YEAR;
388 } else if ( uprv_strcmp(timeUnitName, gTimeUnitMonth) == 0 ) {
389 timeUnitField = TimeUnit::UTIMEUNIT_MONTH;
390 } else if ( uprv_strcmp(timeUnitName, gTimeUnitDay) == 0 ) {
391 timeUnitField = TimeUnit::UTIMEUNIT_DAY;
392 } else if ( uprv_strcmp(timeUnitName, gTimeUnitHour) == 0 ) {
393 timeUnitField = TimeUnit::UTIMEUNIT_HOUR;
394 } else if ( uprv_strcmp(timeUnitName, gTimeUnitMinute) == 0 ) {
395 timeUnitField = TimeUnit::UTIMEUNIT_MINUTE;
396 } else if ( uprv_strcmp(timeUnitName, gTimeUnitSecond) == 0 ) {
397 timeUnitField = TimeUnit::UTIMEUNIT_SECOND;
398 } else if ( uprv_strcmp(timeUnitName, gTimeUnitWeek) == 0 ) {
399 timeUnitField = TimeUnit::UTIMEUNIT_WEEK;
400 } else {
401 continue;
402 }
403 LocalPointer<Hashtable> localCountToPatterns;
404 Hashtable *countToPatterns =
405 timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField];
406 if (countToPatterns == nullptr) {
407 localCountToPatterns.adoptInsteadAndCheckErrorCode(
408 timeUnitFormatObj->initHash(errorCode), errorCode);
409 countToPatterns = localCountToPatterns.getAlias();
410 if (U_FAILURE(errorCode)) {
411 return;
412 }
413 }
414
415 ResourceTable countsToPatternTable = value.getTable(errorCode);
416 if (U_FAILURE(errorCode)) {
417 continue;
418 }
419 for (int32_t j = 0; countsToPatternTable.getKeyAndValue(j, key, value); ++j) {
420 errorCode = U_ZERO_ERROR;
421 UnicodeString pattern = value.getUnicodeString(errorCode);
422 if (U_FAILURE(errorCode)) {
423 continue;
424 }
425 UnicodeString pluralCountUniStr(key, -1, US_INV);
426 if (!pluralCounts.contains(&pluralCountUniStr)) {
427 continue;
428 }
429 LocalPointer<MessageFormat> messageFormat(new MessageFormat(
430 pattern, timeUnitFormatObj->getLocale(errorCode), errorCode), errorCode);
431 if (U_FAILURE(errorCode)) {
432 return;
433 }
434 MessageFormat** formatters =
435 (MessageFormat**)countToPatterns->get(pluralCountUniStr);
436 if (formatters == nullptr) {
437 LocalMemory<MessageFormat *> localFormatters(
438 (MessageFormat **)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
439 if (localFormatters.isNull()) {
440 errorCode = U_MEMORY_ALLOCATION_ERROR;
441 return;
442 }
443 localFormatters[UTMUTFMT_FULL_STYLE] = nullptr;
444 localFormatters[UTMUTFMT_ABBREVIATED_STYLE] = nullptr;
445 countToPatterns->put(pluralCountUniStr, localFormatters.getAlias(), errorCode);
446 if (U_FAILURE(errorCode)) {
447 return;
448 }
449 formatters = localFormatters.orphan();
450 }
451 formatters[style] = messageFormat.orphan();
452 }
453
454 if (timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField] == nullptr) {
455 timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField] = localCountToPatterns.orphan();
456 }
457 }
458 }
459
460 };
461
~TimeUnitFormatReadSink()462 TimeUnitFormatReadSink::~TimeUnitFormatReadSink() {}
463
464 void
readFromCurrentLocale(UTimeUnitFormatStyle style,const char * key,const UVector & pluralCounts,UErrorCode & err)465 TimeUnitFormat::readFromCurrentLocale(UTimeUnitFormatStyle style, const char* key,
466 const UVector& pluralCounts, UErrorCode& err) {
467 if (U_FAILURE(err)) {
468 return;
469 }
470 // fill timeUnitToCountToPatterns from resource file
471 // err is used to indicate wrong status except missing resource.
472 // status is an error code used in resource lookup.
473 // status does not affect "err".
474 UErrorCode status = U_ZERO_ERROR;
475 LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_UNIT, getLocaleID(status), &status));
476
477 LocalUResourceBundlePointer unitsRes(ures_getByKey(rb.getAlias(), key, nullptr, &status));
478 ures_getByKey(unitsRes.getAlias(), "duration", unitsRes.getAlias(), &status);
479 if (U_FAILURE(status)) {
480 return;
481 }
482
483 TimeUnitFormatReadSink sink(this, pluralCounts, style);
484 ures_getAllItemsWithFallback(unitsRes.getAlias(), "", sink, status);
485 }
486
487 void
checkConsistency(UTimeUnitFormatStyle style,const char * key,UErrorCode & err)488 TimeUnitFormat::checkConsistency(UTimeUnitFormatStyle style, const char* key, UErrorCode& err) {
489 if (U_FAILURE(err)) {
490 return;
491 }
492 // there should be patterns for each plural rule in each time unit.
493 // For each time unit,
494 // for each plural rule, following is unit pattern fall-back rule:
495 // ( for example: "one" hour )
496 // look for its unit pattern in its locale tree.
497 // if pattern is not found in its own locale, such as de_DE,
498 // look for the pattern in its parent, such as de,
499 // keep looking till found or till root.
500 // if the pattern is not found in root either,
501 // fallback to plural count "other",
502 // look for the pattern of "other" in the locale tree:
503 // "de_DE" to "de" to "root".
504 // If not found, fall back to value of
505 // static variable DEFAULT_PATTERN_FOR_xxx, such as "{0} h".
506 //
507 // Following is consistency check to create pattern for each
508 // plural rule in each time unit using above fall-back rule.
509 //
510 LocalPointer<StringEnumeration> keywords(
511 getPluralRules().getKeywords(err), err);
512 const UnicodeString* pluralCount;
513 while (U_SUCCESS(err) && (pluralCount = keywords->snext(err)) != nullptr) {
514 for (int32_t i = 0; i < TimeUnit::UTIMEUNIT_FIELD_COUNT; ++i) {
515 // for each time unit,
516 // get all the patterns for each plural rule in this locale.
517 Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i];
518 if ( countToPatterns == nullptr ) {
519 fTimeUnitToCountToPatterns[i] = countToPatterns = initHash(err);
520 if (U_FAILURE(err)) {
521 return;
522 }
523 }
524 MessageFormat** formatters = (MessageFormat**)countToPatterns->get(*pluralCount);
525 if( formatters == nullptr || formatters[style] == nullptr ) {
526 // look through parents
527 const char* localeName = getLocaleID(err);
528 CharString pluralCountChars;
529 pluralCountChars.appendInvariantChars(*pluralCount, err);
530 searchInLocaleChain(style, key, localeName,
531 (TimeUnit::UTimeUnitFields)i,
532 *pluralCount, pluralCountChars.data(),
533 countToPatterns, err);
534 }
535 // TODO: what to do with U_FAILURE(err) at this point.
536 // As is, the outer loop continues to run, but does nothing.
537 }
538 }
539 }
540
541
542
543 // srcPluralCount is the original plural count on which the pattern is
544 // searched for.
545 // searchPluralCount is the fallback plural count.
546 // For example, to search for pattern for ""one" hour",
547 // "one" is the srcPluralCount,
548 // if the pattern is not found even in root, fallback to
549 // using patterns of plural count "other",
550 // then, "other" is the searchPluralCount.
551 void
searchInLocaleChain(UTimeUnitFormatStyle style,const char * key,const char * localeName,TimeUnit::UTimeUnitFields srcTimeUnitField,const UnicodeString & srcPluralCount,const char * searchPluralCount,Hashtable * countToPatterns,UErrorCode & err)552 TimeUnitFormat::searchInLocaleChain(UTimeUnitFormatStyle style, const char* key, const char* localeName,
553 TimeUnit::UTimeUnitFields srcTimeUnitField,
554 const UnicodeString& srcPluralCount,
555 const char* searchPluralCount,
556 Hashtable* countToPatterns,
557 UErrorCode& err) {
558 if (U_FAILURE(err)) {
559 return;
560 }
561 UErrorCode status = U_ZERO_ERROR;
562 CharString parentLocale(localeName, status);
563 U_ASSERT(countToPatterns != nullptr);
564 for (;;) {
565 parentLocale = ulocimp_getParent(parentLocale.data(), status);
566 // look for pattern for srcPluralCount in locale tree
567 LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_UNIT, parentLocale.data(), &status));
568 LocalUResourceBundlePointer unitsRes(ures_getByKey(rb.getAlias(), key, nullptr, &status));
569 const char* timeUnitName = getTimeUnitName(srcTimeUnitField, status);
570 LocalUResourceBundlePointer countsToPatternRB(ures_getByKey(unitsRes.getAlias(), timeUnitName, nullptr, &status));
571 const char16_t* pattern;
572 int32_t ptLength;
573 pattern = ures_getStringByKeyWithFallback(countsToPatternRB.getAlias(), searchPluralCount, &ptLength, &status);
574 if (U_SUCCESS(status)) {
575 //found
576 LocalPointer<MessageFormat> messageFormat(
577 new MessageFormat(UnicodeString(true, pattern, ptLength), getLocale(err), err), err);
578 if (U_FAILURE(err)) {
579 return;
580 }
581 MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
582 if (formatters == nullptr) {
583 LocalMemory<MessageFormat *> localFormatters(
584 (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
585 formatters = localFormatters.getAlias();
586 localFormatters[UTMUTFMT_FULL_STYLE] = nullptr;
587 localFormatters[UTMUTFMT_ABBREVIATED_STYLE] = nullptr;
588 countToPatterns->put(srcPluralCount, localFormatters.orphan(), err);
589 if (U_FAILURE(err)) {
590 return;
591 }
592 }
593 //delete formatters[style];
594 formatters[style] = messageFormat.orphan();
595 return;
596 }
597 status = U_ZERO_ERROR;
598 if (parentLocale.isEmpty()) {
599 break;
600 }
601 }
602
603 // if no unitsShort resource was found even after fallback to root locale
604 // then search the units resource fallback from the current level to root
605 if ( parentLocale.isEmpty() && uprv_strcmp(key, gShortUnitsTag) == 0) {
606 #ifdef TMUTFMT_DEBUG
607 std::cout << "loop into searchInLocaleChain since Short-Long-Alternative \n";
608 #endif
609 CharString pLocale(localeName, -1, err);
610 // Add an underscore at the tail of locale name,
611 // so that searchInLocaleChain will check the current locale before falling back
612 pLocale.append('_', err);
613 searchInLocaleChain(style, gUnitsTag, pLocale.data(), srcTimeUnitField, srcPluralCount,
614 searchPluralCount, countToPatterns, err);
615 if (U_FAILURE(err)) {
616 return;
617 }
618 MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
619 if (formatters != nullptr && formatters[style] != nullptr) {
620 return;
621 }
622 }
623
624 // if not found the pattern for this plural count at all,
625 // fall-back to plural count "other"
626 if ( uprv_strcmp(searchPluralCount, gPluralCountOther) == 0 ) {
627 // set default fall back the same as the resource in root
628 LocalPointer<MessageFormat> messageFormat;
629 const char16_t *pattern = nullptr;
630 if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_SECOND ) {
631 pattern = DEFAULT_PATTERN_FOR_SECOND;
632 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MINUTE ) {
633 pattern = DEFAULT_PATTERN_FOR_MINUTE;
634 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_HOUR ) {
635 pattern = DEFAULT_PATTERN_FOR_HOUR;
636 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_WEEK ) {
637 pattern = DEFAULT_PATTERN_FOR_WEEK;
638 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_DAY ) {
639 pattern = DEFAULT_PATTERN_FOR_DAY;
640 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MONTH ) {
641 pattern = DEFAULT_PATTERN_FOR_MONTH;
642 } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_YEAR ) {
643 pattern = DEFAULT_PATTERN_FOR_YEAR;
644 }
645 if (pattern != nullptr) {
646 messageFormat.adoptInsteadAndCheckErrorCode(
647 new MessageFormat(UnicodeString(true, pattern, -1), getLocale(err), err), err);
648 }
649 if (U_FAILURE(err)) {
650 return;
651 }
652 MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
653 if (formatters == nullptr) {
654 LocalMemory<MessageFormat *> localFormatters (
655 (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
656 if (localFormatters.isNull()) {
657 err = U_MEMORY_ALLOCATION_ERROR;
658 return;
659 }
660 formatters = localFormatters.getAlias();
661 formatters[UTMUTFMT_FULL_STYLE] = nullptr;
662 formatters[UTMUTFMT_ABBREVIATED_STYLE] = nullptr;
663 countToPatterns->put(srcPluralCount, localFormatters.orphan(), err);
664 }
665 if (U_SUCCESS(err)) {
666 //delete formatters[style];
667 formatters[style] = messageFormat.orphan();
668 }
669 } else {
670 // fall back to rule "other", and search in parents
671 searchInLocaleChain(style, key, localeName, srcTimeUnitField, srcPluralCount,
672 gPluralCountOther, countToPatterns, err);
673 }
674 }
675
676 void
setLocale(const Locale & locale,UErrorCode & status)677 TimeUnitFormat::setLocale(const Locale& locale, UErrorCode& status) {
678 if (setMeasureFormatLocale(locale, status)) {
679 setup(status);
680 }
681 }
682
683
684 void
setNumberFormat(const NumberFormat & format,UErrorCode & status)685 TimeUnitFormat::setNumberFormat(const NumberFormat& format, UErrorCode& status){
686 if (U_FAILURE(status)) {
687 return;
688 }
689 adoptNumberFormat(format.clone(), status);
690 }
691
692
693 void
deleteHash(Hashtable * htable)694 TimeUnitFormat::deleteHash(Hashtable* htable) {
695 int32_t pos = UHASH_FIRST;
696 const UHashElement* element = nullptr;
697 if ( htable ) {
698 while ( (element = htable->nextElement(pos)) != nullptr ) {
699 const UHashTok valueTok = element->value;
700 const MessageFormat** value = (const MessageFormat**)valueTok.pointer;
701 delete value[UTMUTFMT_FULL_STYLE];
702 delete value[UTMUTFMT_ABBREVIATED_STYLE];
703 //delete[] value;
704 uprv_free(value);
705 }
706 }
707 delete htable;
708 }
709
710
711 void
copyHash(const Hashtable * source,Hashtable * target,UErrorCode & status)712 TimeUnitFormat::copyHash(const Hashtable* source, Hashtable* target, UErrorCode& status) {
713 if ( U_FAILURE(status) ) {
714 return;
715 }
716 int32_t pos = UHASH_FIRST;
717 const UHashElement* element = nullptr;
718 if ( source ) {
719 while ( (element = source->nextElement(pos)) != nullptr ) {
720 const UHashTok keyTok = element->key;
721 const UnicodeString* key = (UnicodeString*)keyTok.pointer;
722 const UHashTok valueTok = element->value;
723 const MessageFormat** value = (const MessageFormat**)valueTok.pointer;
724 MessageFormat** newVal = (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*));
725 newVal[0] = value[0]->clone();
726 newVal[1] = value[1]->clone();
727 target->put(UnicodeString(*key), newVal, status);
728 if ( U_FAILURE(status) ) {
729 delete newVal[0];
730 delete newVal[1];
731 uprv_free(newVal);
732 return;
733 }
734 }
735 }
736 }
737
738
739 U_CDECL_BEGIN
740
741 /**
742 * set hash table value comparator
743 *
744 * @param val1 one value in comparison
745 * @param val2 the other value in comparison
746 * @return true if 2 values are the same, false otherwise
747 */
748 static UBool U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2);
749
750 static UBool
tmutfmtHashTableValueComparator(UHashTok val1,UHashTok val2)751 U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2) {
752 const MessageFormat** pattern1 = (const MessageFormat**)val1.pointer;
753 const MessageFormat** pattern2 = (const MessageFormat**)val2.pointer;
754 return *pattern1[0] == *pattern2[0] && *pattern1[1] == *pattern2[1];
755 }
756
757 U_CDECL_END
758
759 Hashtable*
initHash(UErrorCode & status)760 TimeUnitFormat::initHash(UErrorCode& status) {
761 if ( U_FAILURE(status) ) {
762 return nullptr;
763 }
764 Hashtable* hTable;
765 if ( (hTable = new Hashtable(true, status)) == nullptr ) {
766 status = U_MEMORY_ALLOCATION_ERROR;
767 return nullptr;
768 }
769 if ( U_FAILURE(status) ) {
770 delete hTable;
771 return nullptr;
772 }
773 hTable->setValueComparator(tmutfmtHashTableValueComparator);
774 return hTable;
775 }
776
777
778 const char*
getTimeUnitName(TimeUnit::UTimeUnitFields unitField,UErrorCode & status)779 TimeUnitFormat::getTimeUnitName(TimeUnit::UTimeUnitFields unitField,
780 UErrorCode& status) {
781 if (U_FAILURE(status)) {
782 return nullptr;
783 }
784 switch (unitField) {
785 case TimeUnit::UTIMEUNIT_YEAR:
786 return gTimeUnitYear;
787 case TimeUnit::UTIMEUNIT_MONTH:
788 return gTimeUnitMonth;
789 case TimeUnit::UTIMEUNIT_DAY:
790 return gTimeUnitDay;
791 case TimeUnit::UTIMEUNIT_WEEK:
792 return gTimeUnitWeek;
793 case TimeUnit::UTIMEUNIT_HOUR:
794 return gTimeUnitHour;
795 case TimeUnit::UTIMEUNIT_MINUTE:
796 return gTimeUnitMinute;
797 case TimeUnit::UTIMEUNIT_SECOND:
798 return gTimeUnitSecond;
799 default:
800 status = U_ILLEGAL_ARGUMENT_ERROR;
801 return nullptr;
802 }
803 }
804
805 U_NAMESPACE_END
806
807 #endif
808