• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*******************************************************************************
4 * Copyright (C) 2008-2016, International Business Machines Corporation and
5 * others. All Rights Reserved.
6 *******************************************************************************
7 *
8 * File DTITVINF.CPP
9 *
10 *******************************************************************************
11 */
12 
13 #include "unicode/dtitvinf.h"
14 
15 
16 #if !UCONFIG_NO_FORMATTING
17 
18 //TODO: define it in compiler time
19 //#define DTITVINF_DEBUG 1
20 
21 
22 #ifdef DTITVINF_DEBUG
23 #include <iostream>
24 #endif
25 
26 #include "cmemory.h"
27 #include "cstring.h"
28 #include "unicode/msgfmt.h"
29 #include "unicode/uloc.h"
30 #include "unicode/ures.h"
31 #include "dtitv_impl.h"
32 #include "charstr.h"
33 #include "hash.h"
34 #include "gregoimp.h"
35 #include "uresimp.h"
36 #include "hash.h"
37 #include "gregoimp.h"
38 #include "uresimp.h"
39 
40 
41 U_NAMESPACE_BEGIN
42 
43 
44 #ifdef DTITVINF_DEBUG
45 #define PRINTMESG(msg) UPRV_BLOCK_MACRO_BEGIN { \
46     std::cout << "(" << __FILE__ << ":" << __LINE__ << ") " << msg << "\n"; \
47 } UPRV_BLOCK_MACRO_END
48 #endif
49 
50 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalInfo)
51 
52 static const char gCalendarTag[]="calendar";
53 static const char gGenericTag[]="generic";
54 static const char gGregorianTag[]="gregorian";
55 static const char gIntervalDateTimePatternTag[]="intervalFormats";
56 static const char gFallbackPatternTag[]="fallback";
57 
58 // {0}
59 static const UChar gFirstPattern[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET};
60 // {1}
61 static const UChar gSecondPattern[] = {LEFT_CURLY_BRACKET, DIGIT_ONE, RIGHT_CURLY_BRACKET};
62 
63 // default fall-back
64 static const UChar gDefaultFallbackPattern[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, EN_DASH, SPACE, LEFT_CURLY_BRACKET, DIGIT_ONE, RIGHT_CURLY_BRACKET, 0};
65 
DateIntervalInfo(UErrorCode & status)66 DateIntervalInfo::DateIntervalInfo(UErrorCode& status)
67 :   fFallbackIntervalPattern(gDefaultFallbackPattern),
68     fFirstDateInPtnIsLaterDate(false),
69     fIntervalPatterns(nullptr)
70 {
71     fIntervalPatterns = initHash(status);
72 }
73 
74 
75 
DateIntervalInfo(const Locale & locale,UErrorCode & status)76 DateIntervalInfo::DateIntervalInfo(const Locale& locale, UErrorCode& status)
77 :   fFallbackIntervalPattern(gDefaultFallbackPattern),
78     fFirstDateInPtnIsLaterDate(false),
79     fIntervalPatterns(nullptr)
80 {
81     initializeData(locale, status);
82 }
83 
84 
85 
86 void
setIntervalPattern(const UnicodeString & skeleton,UCalendarDateFields lrgDiffCalUnit,const UnicodeString & intervalPattern,UErrorCode & status)87 DateIntervalInfo::setIntervalPattern(const UnicodeString& skeleton,
88                                      UCalendarDateFields lrgDiffCalUnit,
89                                      const UnicodeString& intervalPattern,
90                                      UErrorCode& status) {
91 
92     if ( lrgDiffCalUnit == UCAL_HOUR_OF_DAY ) {
93         setIntervalPatternInternally(skeleton, UCAL_AM_PM, intervalPattern, status);
94         setIntervalPatternInternally(skeleton, UCAL_HOUR, intervalPattern, status);
95     } else if ( lrgDiffCalUnit == UCAL_DAY_OF_MONTH ||
96                 lrgDiffCalUnit == UCAL_DAY_OF_WEEK ) {
97         setIntervalPatternInternally(skeleton, UCAL_DATE, intervalPattern, status);
98     } else {
99         setIntervalPatternInternally(skeleton, lrgDiffCalUnit, intervalPattern, status);
100     }
101 }
102 
103 
104 void
setFallbackIntervalPattern(const UnicodeString & fallbackPattern,UErrorCode & status)105 DateIntervalInfo::setFallbackIntervalPattern(
106                                     const UnicodeString& fallbackPattern,
107                                     UErrorCode& status) {
108     if ( U_FAILURE(status) ) {
109         return;
110     }
111     int32_t firstPatternIndex = fallbackPattern.indexOf(gFirstPattern,
112                         UPRV_LENGTHOF(gFirstPattern), 0);
113     int32_t secondPatternIndex = fallbackPattern.indexOf(gSecondPattern,
114                         UPRV_LENGTHOF(gSecondPattern), 0);
115     if ( firstPatternIndex == -1 || secondPatternIndex == -1 ) {
116         status = U_ILLEGAL_ARGUMENT_ERROR;
117         return;
118     }
119     if ( firstPatternIndex > secondPatternIndex ) {
120         fFirstDateInPtnIsLaterDate = true;
121     }
122     fFallbackIntervalPattern = fallbackPattern;
123 }
124 
125 
126 
DateIntervalInfo(const DateIntervalInfo & dtitvinf)127 DateIntervalInfo::DateIntervalInfo(const DateIntervalInfo& dtitvinf)
128 :   UObject(dtitvinf),
129     fIntervalPatterns(nullptr)
130 {
131     *this = dtitvinf;
132 }
133 
134 
135 
136 DateIntervalInfo&
operator =(const DateIntervalInfo & dtitvinf)137 DateIntervalInfo::operator=(const DateIntervalInfo& dtitvinf) {
138     if ( this == &dtitvinf ) {
139         return *this;
140     }
141 
142     UErrorCode status = U_ZERO_ERROR;
143     deleteHash(fIntervalPatterns);
144     fIntervalPatterns = initHash(status);
145     copyHash(dtitvinf.fIntervalPatterns, fIntervalPatterns, status);
146     if ( U_FAILURE(status) ) {
147         return *this;
148     }
149 
150     fFallbackIntervalPattern = dtitvinf.fFallbackIntervalPattern;
151     fFirstDateInPtnIsLaterDate = dtitvinf.fFirstDateInPtnIsLaterDate;
152     return *this;
153 }
154 
155 
156 DateIntervalInfo*
clone() const157 DateIntervalInfo::clone() const {
158     return new DateIntervalInfo(*this);
159 }
160 
161 
~DateIntervalInfo()162 DateIntervalInfo::~DateIntervalInfo() {
163     deleteHash(fIntervalPatterns);
164     fIntervalPatterns = nullptr;
165 }
166 
167 
168 UBool
operator ==(const DateIntervalInfo & other) const169 DateIntervalInfo::operator==(const DateIntervalInfo& other) const {
170     UBool equal = (
171       fFallbackIntervalPattern == other.fFallbackIntervalPattern &&
172       fFirstDateInPtnIsLaterDate == other.fFirstDateInPtnIsLaterDate );
173 
174     if ( equal == TRUE ) {
175         equal = fIntervalPatterns->equals(*(other.fIntervalPatterns));
176     }
177 
178     return equal;
179 }
180 
181 
182 UnicodeString&
getIntervalPattern(const UnicodeString & skeleton,UCalendarDateFields field,UnicodeString & result,UErrorCode & status) const183 DateIntervalInfo::getIntervalPattern(const UnicodeString& skeleton,
184                                      UCalendarDateFields field,
185                                      UnicodeString& result,
186                                      UErrorCode& status) const {
187     if ( U_FAILURE(status) ) {
188         return result;
189     }
190 
191     const UnicodeString* patternsOfOneSkeleton = (UnicodeString*) fIntervalPatterns->get(skeleton);
192     if ( patternsOfOneSkeleton != nullptr ) {
193         IntervalPatternIndex index = calendarFieldToIntervalIndex(field, status);
194         if ( U_FAILURE(status) ) {
195             return result;
196         }
197         const UnicodeString& intervalPattern =  patternsOfOneSkeleton[index];
198         if ( !intervalPattern.isEmpty() ) {
199             result = intervalPattern;
200         }
201     }
202     return result;
203 }
204 
205 
206 UBool
getDefaultOrder() const207 DateIntervalInfo::getDefaultOrder() const {
208     return fFirstDateInPtnIsLaterDate;
209 }
210 
211 
212 UnicodeString&
getFallbackIntervalPattern(UnicodeString & result) const213 DateIntervalInfo::getFallbackIntervalPattern(UnicodeString& result) const {
214     result = fFallbackIntervalPattern;
215     return result;
216 }
217 
218 #define ULOC_LOCALE_IDENTIFIER_CAPACITY (ULOC_FULLNAME_CAPACITY + 1 + ULOC_KEYWORD_AND_VALUES_CAPACITY)
219 
220 
221 static const int32_t PATH_PREFIX_LENGTH = 17;
222 static const UChar PATH_PREFIX[] = {SOLIDUS, CAP_L, CAP_O, CAP_C, CAP_A, CAP_L, CAP_E, SOLIDUS,
223                                     LOW_C, LOW_A, LOW_L, LOW_E, LOW_N, LOW_D, LOW_A, LOW_R, SOLIDUS};
224 static const int32_t PATH_SUFFIX_LENGTH = 16;
225 static const UChar PATH_SUFFIX[] = {SOLIDUS, LOW_I, LOW_N, LOW_T, LOW_E, LOW_R, LOW_V, LOW_A,
226                                     LOW_L, CAP_F, LOW_O, LOW_R, LOW_M, LOW_A, LOW_T, LOW_S};
227 
228 /**
229  * Sink for enumerating all of the date interval skeletons.
230  */
231 struct DateIntervalInfo::DateIntervalSink : public ResourceSink {
232 
233     // Output data
234     DateIntervalInfo &dateIntervalInfo;
235 
236     // Next calendar type
237     UnicodeString nextCalendarType;
238 
DateIntervalSinkDateIntervalInfo::DateIntervalSink239     DateIntervalSink(DateIntervalInfo &diInfo, const char *currentCalendarType)
240             : dateIntervalInfo(diInfo), nextCalendarType(currentCalendarType, -1, US_INV) { }
241     virtual ~DateIntervalSink();
242 
putDateIntervalInfo::DateIntervalSink243     virtual void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &errorCode) {
244         if (U_FAILURE(errorCode)) { return; }
245 
246         // Iterate over all the calendar entries and only pick the 'intervalFormats' table.
247         ResourceTable dateIntervalData = value.getTable(errorCode);
248         if (U_FAILURE(errorCode)) { return; }
249         for (int32_t i = 0; dateIntervalData.getKeyAndValue(i, key, value); i++) {
250             if (uprv_strcmp(key, gIntervalDateTimePatternTag) != 0) {
251                 continue;
252             }
253 
254             // Handle aliases and tables. Ignore the rest.
255             if (value.getType() == URES_ALIAS) {
256                 // Get the calendar type for the alias path.
257                 const UnicodeString &aliasPath = value.getAliasUnicodeString(errorCode);
258                 if (U_FAILURE(errorCode)) { return; }
259 
260                 nextCalendarType.remove();
261                 getCalendarTypeFromPath(aliasPath, nextCalendarType, errorCode);
262 
263                 if (U_FAILURE(errorCode)) {
264                     resetNextCalendarType();
265                 }
266                 break;
267 
268             } else if (value.getType() == URES_TABLE) {
269                 // Iterate over all the skeletons in the 'intervalFormat' table.
270                 ResourceTable skeletonData = value.getTable(errorCode);
271                 if (U_FAILURE(errorCode)) { return; }
272                 for (int32_t j = 0; skeletonData.getKeyAndValue(j, key, value); j++) {
273                     if (value.getType() == URES_TABLE) {
274                         // Process the skeleton
275                         processSkeletonTable(key, value, errorCode);
276                         if (U_FAILURE(errorCode)) { return; }
277                     }
278                 }
279                 break;
280             }
281         }
282     }
283 
284     /**
285      * Processes the patterns for a skeleton table
286      */
processSkeletonTableDateIntervalInfo::DateIntervalSink287     void processSkeletonTable(const char *key, ResourceValue &value, UErrorCode &errorCode) {
288         if (U_FAILURE(errorCode)) { return; }
289 
290         // Iterate over all the patterns in the current skeleton table
291         const char *currentSkeleton = key;
292         ResourceTable patternData = value.getTable(errorCode);
293         if (U_FAILURE(errorCode)) { return; }
294         for (int32_t k = 0; patternData.getKeyAndValue(k, key, value); k++) {
295             if (value.getType() == URES_STRING) {
296                 // Process the key
297                 UCalendarDateFields calendarField = validateAndProcessPatternLetter(key);
298 
299                 // If the calendar field has a valid value
300                 if (calendarField < UCAL_FIELD_COUNT) {
301                     // Set the interval pattern
302                     setIntervalPatternIfAbsent(currentSkeleton, calendarField, value, errorCode);
303                     if (U_FAILURE(errorCode)) { return; }
304                 }
305             }
306         }
307     }
308 
309     /**
310      * Extracts the calendar type from the path.
311      */
getCalendarTypeFromPathDateIntervalInfo::DateIntervalSink312     static void getCalendarTypeFromPath(const UnicodeString &path, UnicodeString &calendarType,
313                                         UErrorCode &errorCode) {
314         if (U_FAILURE(errorCode)) { return; }
315 
316         if (!path.startsWith(PATH_PREFIX, PATH_PREFIX_LENGTH) || !path.endsWith(PATH_SUFFIX, PATH_SUFFIX_LENGTH)) {
317             errorCode = U_INVALID_FORMAT_ERROR;
318             return;
319         }
320 
321         path.extractBetween(PATH_PREFIX_LENGTH, path.length() - PATH_SUFFIX_LENGTH, calendarType);
322     }
323 
324     /**
325      * Validates and processes the pattern letter
326      */
validateAndProcessPatternLetterDateIntervalInfo::DateIntervalSink327     UCalendarDateFields validateAndProcessPatternLetter(const char *patternLetter) {
328         // Check that patternLetter is just one letter
329         char c0;
330         if ((c0 = patternLetter[0]) != 0 && patternLetter[1] == 0) {
331             // Check that the pattern letter is accepted
332             if (c0 == 'G') {
333                 return UCAL_ERA;
334             } else if (c0 == 'y') {
335                 return UCAL_YEAR;
336             } else if (c0 == 'M') {
337                 return UCAL_MONTH;
338             } else if (c0 == 'd') {
339                 return UCAL_DATE;
340             } else if (c0 == 'a') {
341                 return UCAL_AM_PM;
342             } else if (c0 == 'B') {
343                 // TODO: Using AM/PM as a proxy for flexible day period isn't really correct, but it's close
344                 return UCAL_AM_PM;
345             } else if (c0 == 'h' || c0 == 'H') {
346                 return UCAL_HOUR;
347             } else if (c0 == 'm') {
348                 return UCAL_MINUTE;
349             }// TODO(ticket:12190): Why icu4c doesn't accept the calendar field "s" but icu4j does?
350         }
351         return UCAL_FIELD_COUNT;
352     }
353 
354     /**
355      * Stores the interval pattern for the current skeleton in the internal data structure
356      * if it's not present.
357      */
setIntervalPatternIfAbsentDateIntervalInfo::DateIntervalSink358     void setIntervalPatternIfAbsent(const char *currentSkeleton, UCalendarDateFields lrgDiffCalUnit,
359                                     const ResourceValue &value, UErrorCode &errorCode) {
360         // Check if the pattern has already been stored on the data structure
361         IntervalPatternIndex index =
362             dateIntervalInfo.calendarFieldToIntervalIndex(lrgDiffCalUnit, errorCode);
363         if (U_FAILURE(errorCode)) { return; }
364 
365         UnicodeString skeleton(currentSkeleton, -1, US_INV);
366         UnicodeString* patternsOfOneSkeleton =
367             (UnicodeString*)(dateIntervalInfo.fIntervalPatterns->get(skeleton));
368 
369         if (patternsOfOneSkeleton == nullptr || patternsOfOneSkeleton[index].isEmpty()) {
370             UnicodeString pattern = value.getUnicodeString(errorCode);
371             dateIntervalInfo.setIntervalPatternInternally(skeleton, lrgDiffCalUnit,
372                                                           pattern, errorCode);
373         }
374     }
375 
getNextCalendarTypeDateIntervalInfo::DateIntervalSink376     const UnicodeString &getNextCalendarType() {
377         return nextCalendarType;
378     }
379 
resetNextCalendarTypeDateIntervalInfo::DateIntervalSink380     void resetNextCalendarType() {
381         nextCalendarType.setToBogus();
382     }
383 };
384 
385 // Virtual destructors must be defined out of line.
~DateIntervalSink()386 DateIntervalInfo::DateIntervalSink::~DateIntervalSink() {}
387 
388 
389 
390 void
initializeData(const Locale & locale,UErrorCode & status)391 DateIntervalInfo::initializeData(const Locale& locale, UErrorCode& status)
392 {
393     fIntervalPatterns = initHash(status);
394     if (U_FAILURE(status)) {
395       return;
396     }
397     const char *locName = locale.getName();
398 
399     // Get the correct calendar type
400     const char * calendarTypeToUse = gGregorianTag; // initial default
401     char         calendarType[ULOC_KEYWORDS_CAPACITY]; // to be filled in with the type to use, if all goes well
402     char         localeWithCalendarKey[ULOC_LOCALE_IDENTIFIER_CAPACITY];
403     // obtain a locale that always has the calendar key value that should be used
404     (void)ures_getFunctionalEquivalent(localeWithCalendarKey, ULOC_LOCALE_IDENTIFIER_CAPACITY, nullptr,
405                                      "calendar", "calendar", locName, nullptr, FALSE, &status);
406     localeWithCalendarKey[ULOC_LOCALE_IDENTIFIER_CAPACITY-1] = 0; // ensure null termination
407     // now get the calendar key value from that locale
408     int32_t calendarTypeLen = uloc_getKeywordValue(localeWithCalendarKey, "calendar", calendarType,
409                                                    ULOC_KEYWORDS_CAPACITY, &status);
410     if (U_SUCCESS(status) && calendarTypeLen < ULOC_KEYWORDS_CAPACITY) {
411         calendarTypeToUse = calendarType;
412     }
413     status = U_ZERO_ERROR;
414 
415     // Instantiate the resource bundles
416     UResourceBundle *rb, *calBundle;
417     rb = ures_open(nullptr, locName, &status);
418     if (U_FAILURE(status)) {
419         return;
420     }
421     calBundle = ures_getByKeyWithFallback(rb, gCalendarTag, nullptr, &status);
422 
423 
424     if (U_SUCCESS(status)) {
425         UResourceBundle *calTypeBundle, *itvDtPtnResource;
426 
427         // Get the fallback pattern
428         const UChar* resStr = nullptr;
429         int32_t resStrLen = 0;
430         calTypeBundle = ures_getByKeyWithFallback(calBundle, calendarTypeToUse, nullptr, &status);
431         itvDtPtnResource = ures_getByKeyWithFallback(calTypeBundle,
432                                                      gIntervalDateTimePatternTag, nullptr, &status);
433         // TODO(ICU-20400): After the fixing, we should find the "fallback" from
434         // the rb directly by the path "calendar/${calendar}/intervalFormats/fallback".
435         if ( U_SUCCESS(status) ) {
436             resStr = ures_getStringByKeyWithFallback(itvDtPtnResource, gFallbackPatternTag,
437                                                      &resStrLen, &status);
438             if ( U_FAILURE(status) ) {
439                 // Try to find "fallback" from "generic" to work around the bug in
440                 // ures_getByKeyWithFallback
441                 UErrorCode localStatus = U_ZERO_ERROR;
442                 UResourceBundle *genericCalBundle =
443                     ures_getByKeyWithFallback(calBundle, gGenericTag, nullptr, &localStatus);
444                 UResourceBundle *genericItvDtPtnResource =
445                     ures_getByKeyWithFallback(
446                         genericCalBundle, gIntervalDateTimePatternTag, nullptr, &localStatus);
447                 resStr = ures_getStringByKeyWithFallback(
448                     genericItvDtPtnResource, gFallbackPatternTag, &resStrLen, &localStatus);
449                 ures_close(genericItvDtPtnResource);
450                 ures_close(genericCalBundle);
451                 if ( U_SUCCESS(localStatus) ) {
452                     status = U_USING_FALLBACK_WARNING;;
453                 }
454             }
455         }
456 
457         if ( U_SUCCESS(status) && (resStr != nullptr)) {
458             UnicodeString pattern = UnicodeString(TRUE, resStr, resStrLen);
459             setFallbackIntervalPattern(pattern, status);
460         }
461         ures_close(itvDtPtnResource);
462         ures_close(calTypeBundle);
463 
464 
465         // Instantiate the sink
466         DateIntervalSink sink(*this, calendarTypeToUse);
467         const UnicodeString &calendarTypeToUseUString = sink.getNextCalendarType();
468 
469         // Already loaded calendar types
470         Hashtable loadedCalendarTypes(FALSE, status);
471 
472         if (U_SUCCESS(status)) {
473             while (!calendarTypeToUseUString.isBogus()) {
474                 // Set an error when a loop is detected
475                 if (loadedCalendarTypes.geti(calendarTypeToUseUString) == 1) {
476                     status = U_INVALID_FORMAT_ERROR;
477                     break;
478                 }
479 
480                 // Register the calendar type to avoid loops
481                 loadedCalendarTypes.puti(calendarTypeToUseUString, 1, status);
482                 if (U_FAILURE(status)) { break; }
483 
484                 // Get the calendar string
485                 CharString calTypeBuffer;
486                 calTypeBuffer.appendInvariantChars(calendarTypeToUseUString, status);
487                 if (U_FAILURE(status)) { break; }
488                 const char *calType = calTypeBuffer.data();
489 
490                 // Reset the next calendar type to load.
491                 sink.resetNextCalendarType();
492 
493                 // Get all resources for this calendar type
494                 ures_getAllItemsWithFallback(calBundle, calType, sink, status);
495             }
496         }
497     }
498 
499     // Close the opened resource bundles
500     ures_close(calBundle);
501     ures_close(rb);
502 }
503 
504 void
setIntervalPatternInternally(const UnicodeString & skeleton,UCalendarDateFields lrgDiffCalUnit,const UnicodeString & intervalPattern,UErrorCode & status)505 DateIntervalInfo::setIntervalPatternInternally(const UnicodeString& skeleton,
506                                       UCalendarDateFields lrgDiffCalUnit,
507                                       const UnicodeString& intervalPattern,
508                                       UErrorCode& status) {
509     IntervalPatternIndex index = calendarFieldToIntervalIndex(lrgDiffCalUnit,status);
510     if ( U_FAILURE(status) ) {
511         return;
512     }
513     UnicodeString* patternsOfOneSkeleton = (UnicodeString*)(fIntervalPatterns->get(skeleton));
514     UBool emptyHash = false;
515     if ( patternsOfOneSkeleton == nullptr ) {
516         patternsOfOneSkeleton = new UnicodeString[kIPI_MAX_INDEX];
517         if (patternsOfOneSkeleton == nullptr) {
518             status = U_MEMORY_ALLOCATION_ERROR;
519             return;
520         }
521         emptyHash = true;
522     }
523 
524     patternsOfOneSkeleton[index] = intervalPattern;
525     if ( emptyHash == TRUE ) {
526         fIntervalPatterns->put(skeleton, patternsOfOneSkeleton, status);
527     }
528 }
529 
530 
531 
532 void
parseSkeleton(const UnicodeString & skeleton,int32_t * skeletonFieldWidth)533 DateIntervalInfo::parseSkeleton(const UnicodeString& skeleton,
534                                 int32_t* skeletonFieldWidth) {
535     const int8_t PATTERN_CHAR_BASE = 0x41;
536     int32_t i;
537     for ( i = 0; i < skeleton.length(); ++i ) {
538         // it is an ASCII char in skeleton
539         int8_t ch = (int8_t)skeleton.charAt(i);
540         ++skeletonFieldWidth[ch - PATTERN_CHAR_BASE];
541     }
542 }
543 
544 
545 
546 UBool
stringNumeric(int32_t fieldWidth,int32_t anotherFieldWidth,char patternLetter)547 DateIntervalInfo::stringNumeric(int32_t fieldWidth, int32_t anotherFieldWidth,
548                                 char patternLetter) {
549     if ( patternLetter == 'M' ) {
550         if ( (fieldWidth <= 2 && anotherFieldWidth > 2) ||
551              (fieldWidth > 2 && anotherFieldWidth <= 2 )) {
552             return true;
553         }
554     }
555     return false;
556 }
557 
558 
559 
560 const UnicodeString*
getBestSkeleton(const UnicodeString & skeleton,int8_t & bestMatchDistanceInfo) const561 DateIntervalInfo::getBestSkeleton(const UnicodeString& skeleton,
562                                   int8_t& bestMatchDistanceInfo) const {
563 #ifdef DTITVINF_DEBUG
564     char result[1000];
565     char result_1[1000];
566     char mesg[2000];
567     skeleton.extract(0,  skeleton.length(), result, "UTF-8");
568     sprintf(mesg, "in getBestSkeleton: skeleton: %s; \n", result);
569     PRINTMESG(mesg)
570 #endif
571 
572 
573     int32_t inputSkeletonFieldWidth[] =
574     {
575     //       A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
576              0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
577     //   P   Q   R   S   T   U   V   W   X   Y   Z
578          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 0, 0,  0, 0, 0,
579     //       a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
580          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
581     //   p   q   r   s   t   u   v   w   x   y   z
582          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
583     };
584 
585     int32_t skeletonFieldWidth[] =
586     {
587     //       A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
588              0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
589     //   P   Q   R   S   T   U   V   W   X   Y   Z
590          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 0, 0,  0, 0, 0,
591     //       a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
592          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
593     //   p   q   r   s   t   u   v   w   x   y   z
594          0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
595     };
596 
597     const int32_t DIFFERENT_FIELD = 0x1000;
598     const int32_t STRING_NUMERIC_DIFFERENCE = 0x100;
599     const int32_t BASE = 0x41;
600 
601     // hack for certain alternate characters
602     // resource bundles only have time skeletons containing 'v', 'h', and 'H'
603     // but not time skeletons containing 'z', 'K', or 'k'
604     // the skeleton may also include 'a' or 'b', which never occur in the resource bundles, so strip them out too
605     UBool replacedAlternateChars = false;
606     const UnicodeString* inputSkeleton = &skeleton;
607     UnicodeString copySkeleton;
608     if ( skeleton.indexOf(LOW_Z) != -1 || skeleton.indexOf(LOW_K) != -1 || skeleton.indexOf(CAP_K) != -1 || skeleton.indexOf(LOW_A) != -1 || skeleton.indexOf(LOW_B) != -1 ) {
609         copySkeleton = skeleton;
610         copySkeleton.findAndReplace(UnicodeString(LOW_Z), UnicodeString(LOW_V));
611         copySkeleton.findAndReplace(UnicodeString(LOW_K), UnicodeString(CAP_H));
612         copySkeleton.findAndReplace(UnicodeString(CAP_K), UnicodeString(LOW_H));
613         copySkeleton.findAndReplace(UnicodeString(LOW_A), UnicodeString());
614         copySkeleton.findAndReplace(UnicodeString(LOW_B), UnicodeString());
615         inputSkeleton = &copySkeleton;
616         replacedAlternateChars = true;
617     }
618 
619     parseSkeleton(*inputSkeleton, inputSkeletonFieldWidth);
620     int32_t bestDistance = MAX_POSITIVE_INT;
621     const UnicodeString* bestSkeleton = nullptr;
622 
623     // 0 means exact the same skeletons;
624     // 1 means having the same field, but with different length,
625     // 2 means only z/v, h/K, or H/k differs
626     // -1 means having different field.
627     bestMatchDistanceInfo = 0;
628     int8_t fieldLength = UPRV_LENGTHOF(skeletonFieldWidth);
629 
630     int32_t pos = UHASH_FIRST;
631     const UHashElement* elem = nullptr;
632     while ( (elem = fIntervalPatterns->nextElement(pos)) != nullptr ) {
633         const UHashTok keyTok = elem->key;
634         UnicodeString* newSkeleton = (UnicodeString*)keyTok.pointer;
635 #ifdef DTITVINF_DEBUG
636     skeleton->extract(0,  skeleton->length(), result, "UTF-8");
637     sprintf(mesg, "available skeletons: skeleton: %s; \n", result);
638     PRINTMESG(mesg)
639 #endif
640 
641         // clear skeleton field width
642         int8_t i;
643         for ( i = 0; i < fieldLength; ++i ) {
644             skeletonFieldWidth[i] = 0;
645         }
646         parseSkeleton(*newSkeleton, skeletonFieldWidth);
647         // calculate distance
648         int32_t distance = 0;
649         int8_t fieldDifference = 1;
650         for ( i = 0; i < fieldLength; ++i ) {
651             int32_t inputFieldWidth = inputSkeletonFieldWidth[i];
652             int32_t fieldWidth = skeletonFieldWidth[i];
653             if ( inputFieldWidth == fieldWidth ) {
654                 continue;
655             }
656             if ( inputFieldWidth == 0 ) {
657                 fieldDifference = -1;
658                 distance += DIFFERENT_FIELD;
659             } else if ( fieldWidth == 0 ) {
660                 fieldDifference = -1;
661                 distance += DIFFERENT_FIELD;
662             } else if (stringNumeric(inputFieldWidth, fieldWidth,
663                                      (char)(i+BASE) ) ) {
664                 distance += STRING_NUMERIC_DIFFERENCE;
665             } else {
666                 distance += (inputFieldWidth > fieldWidth) ?
667                             (inputFieldWidth - fieldWidth) :
668                             (fieldWidth - inputFieldWidth);
669             }
670         }
671         if ( distance < bestDistance ) {
672             bestSkeleton = newSkeleton;
673             bestDistance = distance;
674             bestMatchDistanceInfo = fieldDifference;
675         }
676         if ( distance == 0 ) {
677             bestMatchDistanceInfo = 0;
678             break;
679         }
680     }
681     if ( replacedAlternateChars && bestMatchDistanceInfo != -1 ) {
682         bestMatchDistanceInfo = 2;
683     }
684     return bestSkeleton;
685 }
686 
687 
688 
689 DateIntervalInfo::IntervalPatternIndex
calendarFieldToIntervalIndex(UCalendarDateFields field,UErrorCode & status)690 DateIntervalInfo::calendarFieldToIntervalIndex(UCalendarDateFields field,
691                                                UErrorCode& status) {
692     if ( U_FAILURE(status) ) {
693         return kIPI_MAX_INDEX;
694     }
695     IntervalPatternIndex index = kIPI_MAX_INDEX;
696     switch ( field ) {
697       case UCAL_ERA:
698         index = kIPI_ERA;
699         break;
700       case UCAL_YEAR:
701         index = kIPI_YEAR;
702         break;
703       case UCAL_MONTH:
704         index = kIPI_MONTH;
705         break;
706       case UCAL_DATE:
707       case UCAL_DAY_OF_WEEK:
708       //case UCAL_DAY_OF_MONTH:
709         index = kIPI_DATE;
710         break;
711       case UCAL_AM_PM:
712         index = kIPI_AM_PM;
713         break;
714       case UCAL_HOUR:
715       case UCAL_HOUR_OF_DAY:
716         index = kIPI_HOUR;
717         break;
718       case UCAL_MINUTE:
719         index = kIPI_MINUTE;
720         break;
721       case UCAL_SECOND:
722         index = kIPI_SECOND;
723         break;
724       case UCAL_MILLISECOND:
725         index = kIPI_MILLISECOND;
726         break;
727       default:
728         status = U_ILLEGAL_ARGUMENT_ERROR;
729     }
730     return index;
731 }
732 
733 
734 
735 void
deleteHash(Hashtable * hTable)736 DateIntervalInfo::deleteHash(Hashtable* hTable)
737 {
738     if ( hTable == nullptr ) {
739         return;
740     }
741     int32_t pos = UHASH_FIRST;
742     const UHashElement* element = nullptr;
743     while ( (element = hTable->nextElement(pos)) != nullptr ) {
744         const UHashTok valueTok = element->value;
745         const UnicodeString* value = (UnicodeString*)valueTok.pointer;
746         delete[] value;
747     }
748     delete fIntervalPatterns;
749 }
750 
751 
752 U_CDECL_BEGIN
753 
754 /**
755  * set hash table value comparator
756  *
757  * @param val1  one value in comparison
758  * @param val2  the other value in comparison
759  * @return      TRUE if 2 values are the same, FALSE otherwise
760  */
761 static UBool U_CALLCONV dtitvinfHashTableValueComparator(UHashTok val1, UHashTok val2);
762 
763 static UBool
dtitvinfHashTableValueComparator(UHashTok val1,UHashTok val2)764 U_CALLCONV dtitvinfHashTableValueComparator(UHashTok val1, UHashTok val2) {
765     const UnicodeString* pattern1 = (UnicodeString*)val1.pointer;
766     const UnicodeString* pattern2 = (UnicodeString*)val2.pointer;
767     UBool ret = TRUE;
768     int8_t i;
769     for ( i = 0; i < DateIntervalInfo::kMaxIntervalPatternIndex && ret == TRUE; ++i ) {
770         ret = (pattern1[i] == pattern2[i]);
771     }
772     return ret;
773 }
774 
775 U_CDECL_END
776 
777 
778 Hashtable*
initHash(UErrorCode & status)779 DateIntervalInfo::initHash(UErrorCode& status) {
780     if ( U_FAILURE(status) ) {
781         return nullptr;
782     }
783     Hashtable* hTable;
784     if ( (hTable = new Hashtable(FALSE, status)) == nullptr ) {
785         status = U_MEMORY_ALLOCATION_ERROR;
786         return nullptr;
787     }
788     if ( U_FAILURE(status) ) {
789         delete hTable;
790         return nullptr;
791     }
792     hTable->setValueComparator(dtitvinfHashTableValueComparator);
793     return hTable;
794 }
795 
796 
797 void
copyHash(const Hashtable * source,Hashtable * target,UErrorCode & status)798 DateIntervalInfo::copyHash(const Hashtable* source,
799                            Hashtable* target,
800                            UErrorCode& status) {
801     if ( U_FAILURE(status) ) {
802         return;
803     }
804     int32_t pos = UHASH_FIRST;
805     const UHashElement* element = nullptr;
806     if ( source ) {
807         while ( (element = source->nextElement(pos)) != nullptr ) {
808             const UHashTok keyTok = element->key;
809             const UnicodeString* key = (UnicodeString*)keyTok.pointer;
810             const UHashTok valueTok = element->value;
811             const UnicodeString* value = (UnicodeString*)valueTok.pointer;
812             UnicodeString* copy = new UnicodeString[kIPI_MAX_INDEX];
813             if (copy == nullptr) {
814                 status = U_MEMORY_ALLOCATION_ERROR;
815                 return;
816             }
817             int8_t i;
818             for ( i = 0; i < kIPI_MAX_INDEX; ++i ) {
819                 copy[i] = value[i];
820             }
821             target->put(UnicodeString(*key), copy, status);
822             if ( U_FAILURE(status) ) {
823                 return;
824             }
825         }
826     }
827 }
828 
829 
830 U_NAMESPACE_END
831 
832 #endif
833