1 /*******************************************************************************
2 * Copyright (C) 2008-2015, International Business Machines Corporation and
3 * others. All Rights Reserved.
4 *******************************************************************************
8 *******************************************************************************
11 #include "unicode/dtitvinf.h"
14 #if !UCONFIG_NO_FORMATTING
16 //TODO: define it in compiler time
17 //#define DTITVINF_DEBUG 1
25 #include "unicode/msgfmt.h"
26 #include "unicode/uloc.h"
27 #include "unicode/ures.h"
28 #include "dtitv_impl.h"
41 #define PRINTMESG(msg) { std::cout << "(" << __FILE__ << ":" << __LINE__ << ") " << msg << "\n"; }
44 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalInfo
)
46 static const char gCalendarTag
[]="calendar";
47 static const char gGregorianTag
[]="gregorian";
48 static const char gIntervalDateTimePatternTag
[]="intervalFormats";
49 static const char gFallbackPatternTag
[]="fallback";
52 static const UChar gFirstPattern
[] = {LEFT_CURLY_BRACKET
, DIGIT_ZERO
, RIGHT_CURLY_BRACKET
};
54 static const UChar gSecondPattern
[] = {LEFT_CURLY_BRACKET
, DIGIT_ONE
, RIGHT_CURLY_BRACKET
};
57 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};
61 DateIntervalInfo::DateIntervalInfo(UErrorCode
& status
)
62 : fFallbackIntervalPattern(gDefaultFallbackPattern
),
63 fFirstDateInPtnIsLaterDate(false),
64 fIntervalPatterns(NULL
)
66 fIntervalPatterns
= initHash(status
);
71 DateIntervalInfo::DateIntervalInfo(const Locale
& locale
, UErrorCode
& status
)
72 : fFallbackIntervalPattern(gDefaultFallbackPattern
),
73 fFirstDateInPtnIsLaterDate(false),
74 fIntervalPatterns(NULL
)
76 initializeData(locale
, status
);
82 DateIntervalInfo::setIntervalPattern(const UnicodeString
& skeleton
,
83 UCalendarDateFields lrgDiffCalUnit
,
84 const UnicodeString
& intervalPattern
,
87 if ( lrgDiffCalUnit
== UCAL_HOUR_OF_DAY
) {
88 setIntervalPatternInternally(skeleton
, UCAL_AM_PM
, intervalPattern
, status
);
89 setIntervalPatternInternally(skeleton
, UCAL_HOUR
, intervalPattern
, status
);
90 } else if ( lrgDiffCalUnit
== UCAL_DAY_OF_MONTH
||
91 lrgDiffCalUnit
== UCAL_DAY_OF_WEEK
) {
92 setIntervalPatternInternally(skeleton
, UCAL_DATE
, intervalPattern
, status
);
94 setIntervalPatternInternally(skeleton
, lrgDiffCalUnit
, intervalPattern
, status
);
100 DateIntervalInfo::setFallbackIntervalPattern(
101 const UnicodeString
& fallbackPattern
,
102 UErrorCode
& status
) {
103 if ( U_FAILURE(status
) ) {
106 int32_t firstPatternIndex
= fallbackPattern
.indexOf(gFirstPattern
,
107 sizeof(gFirstPattern
)/sizeof(gFirstPattern
[0]), 0);
108 int32_t secondPatternIndex
= fallbackPattern
.indexOf(gSecondPattern
,
109 sizeof(gSecondPattern
)/sizeof(gSecondPattern
[0]), 0);
110 if ( firstPatternIndex
== -1 || secondPatternIndex
== -1 ) {
111 status
= U_ILLEGAL_ARGUMENT_ERROR
;
114 if ( firstPatternIndex
> secondPatternIndex
) {
115 fFirstDateInPtnIsLaterDate
= true;
117 fFallbackIntervalPattern
= fallbackPattern
;
122 DateIntervalInfo::DateIntervalInfo(const DateIntervalInfo
& dtitvinf
)
124 fIntervalPatterns(NULL
)
132 DateIntervalInfo::operator=(const DateIntervalInfo
& dtitvinf
) {
133 if ( this == &dtitvinf
) {
137 UErrorCode status
= U_ZERO_ERROR
;
138 deleteHash(fIntervalPatterns
);
139 fIntervalPatterns
= initHash(status
);
140 copyHash(dtitvinf
.fIntervalPatterns
, fIntervalPatterns
, status
);
141 if ( U_FAILURE(status
) ) {
145 fFallbackIntervalPattern
= dtitvinf
.fFallbackIntervalPattern
;
146 fFirstDateInPtnIsLaterDate
= dtitvinf
.fFirstDateInPtnIsLaterDate
;
152 DateIntervalInfo::clone() const {
153 return new DateIntervalInfo(*this);
157 DateIntervalInfo::~DateIntervalInfo() {
158 deleteHash(fIntervalPatterns
);
159 fIntervalPatterns
= NULL
;
164 DateIntervalInfo::operator==(const DateIntervalInfo
& other
) const {
166 fFallbackIntervalPattern
== other
.fFallbackIntervalPattern
&&
167 fFirstDateInPtnIsLaterDate
== other
.fFirstDateInPtnIsLaterDate
);
169 if ( equal
== TRUE
) {
170 equal
= fIntervalPatterns
->equals(*(other
.fIntervalPatterns
));
178 DateIntervalInfo::getIntervalPattern(const UnicodeString
& skeleton
,
179 UCalendarDateFields field
,
180 UnicodeString
& result
,
181 UErrorCode
& status
) const {
182 if ( U_FAILURE(status
) ) {
186 const UnicodeString
* patternsOfOneSkeleton
= (UnicodeString
*) fIntervalPatterns
->get(skeleton
);
187 if ( patternsOfOneSkeleton
!= NULL
) {
188 IntervalPatternIndex index
= calendarFieldToIntervalIndex(field
, status
);
189 if ( U_FAILURE(status
) ) {
192 const UnicodeString
& intervalPattern
= patternsOfOneSkeleton
[index
];
193 if ( !intervalPattern
.isEmpty() ) {
194 result
= intervalPattern
;
202 DateIntervalInfo::getDefaultOrder() const {
203 return fFirstDateInPtnIsLaterDate
;
208 DateIntervalInfo::getFallbackIntervalPattern(UnicodeString
& result
) const {
209 result
= fFallbackIntervalPattern
;
213 #define ULOC_LOCALE_IDENTIFIER_CAPACITY (ULOC_FULLNAME_CAPACITY + 1 + ULOC_KEYWORD_AND_VALUES_CAPACITY)
216 DateIntervalInfo::initializeData(const Locale
& locale
, UErrorCode
& err
)
218 fIntervalPatterns
= initHash(err
);
219 if ( U_FAILURE(err
) ) {
222 const char *locName
= locale
.getName();
223 char parentLocale
[ULOC_FULLNAME_CAPACITY
];
224 uprv_strcpy(parentLocale
, locName
);
225 UErrorCode status
= U_ZERO_ERROR
;
226 Hashtable
skeletonKeyPairs(FALSE
, status
);
227 if ( U_FAILURE(status
) ) {
231 // determine calendar type
232 const char * calendarTypeToUse
= gGregorianTag
; // initial default
233 char calendarType
[ULOC_KEYWORDS_CAPACITY
]; // to be filled in with the type to use, if all goes well
234 char localeWithCalendarKey
[ULOC_LOCALE_IDENTIFIER_CAPACITY
];
235 // obtain a locale that always has the calendar key value that should be used
236 (void)ures_getFunctionalEquivalent(localeWithCalendarKey
, ULOC_LOCALE_IDENTIFIER_CAPACITY
, NULL
,
237 "calendar", "calendar", locName
, NULL
, FALSE
, &status
);
238 localeWithCalendarKey
[ULOC_LOCALE_IDENTIFIER_CAPACITY
-1] = 0; // ensure null termination
239 // now get the calendar key value from that locale
240 int32_t calendarTypeLen
= uloc_getKeywordValue(localeWithCalendarKey
, "calendar", calendarType
, ULOC_KEYWORDS_CAPACITY
, &status
);
241 if (U_SUCCESS(status
) && calendarTypeLen
< ULOC_KEYWORDS_CAPACITY
) {
242 calendarTypeToUse
= calendarType
;
244 status
= U_ZERO_ERROR
;
247 UResourceBundle
*rb
, *calBundle
, *calTypeBundle
, *itvDtPtnResource
;
248 rb
= ures_open(NULL
, parentLocale
, &status
);
249 if ( U_FAILURE(status
) ) {
252 calBundle
= ures_getByKey(rb
, gCalendarTag
, NULL
, &status
);
253 calTypeBundle
= ures_getByKey(calBundle
, calendarTypeToUse
, NULL
, &status
);
254 itvDtPtnResource
= ures_getByKeyWithFallback(calTypeBundle
,
255 gIntervalDateTimePatternTag
, NULL
, &status
);
257 if ( U_SUCCESS(status
) ) {
258 // look for fallback first, since it establishes the default order
260 int32_t resStrLen
= 0;
261 resStr
= ures_getStringByKeyWithFallback(itvDtPtnResource
,
263 &resStrLen
, &status
);
264 if ( U_SUCCESS(status
) ) {
265 UnicodeString pattern
= UnicodeString(TRUE
, resStr
, resStrLen
);
266 setFallbackIntervalPattern(pattern
, status
);
269 int32_t size
= ures_getSize(itvDtPtnResource
);
271 for ( index
= 0; index
< size
; ++index
) {
272 LocalUResourceBundlePointer
oneRes(ures_getByIndex(itvDtPtnResource
, index
,
274 if ( U_SUCCESS(status
) ) {
275 const char* skeleton
= ures_getKey(oneRes
.getAlias());
276 if (skeleton
== NULL
) {
279 UnicodeString
skeletonUniStr(skeleton
, -1, US_INV
);
280 if ( uprv_strcmp(skeleton
, gFallbackPatternTag
) == 0 ) {
281 continue; // fallback
284 LocalUResourceBundlePointer
intervalPatterns(ures_getByKey(
285 itvDtPtnResource
, skeleton
, NULL
, &status
));
287 if ( U_FAILURE(status
) ) {
290 if ( intervalPatterns
== NULL
) {
295 int32_t ptnNum
= ures_getSize(intervalPatterns
.getAlias());
297 for ( ptnIndex
= 0; ptnIndex
< ptnNum
; ++ptnIndex
) {
298 UnicodeString pattern
=
299 ures_getNextUnicodeString(intervalPatterns
.getAlias(), &key
, &status
);
300 if ( U_FAILURE(status
) ) {
303 UnicodeString
keyUniStr(key
, -1, US_INV
);
304 UnicodeString
skeletonKeyPair(skeletonUniStr
+ keyUniStr
);
305 if ( skeletonKeyPairs
.geti(skeletonKeyPair
) == 1 ) {
308 skeletonKeyPairs
.puti(skeletonKeyPair
, 1, status
);
310 UCalendarDateFields calendarField
= UCAL_FIELD_COUNT
;
311 if ( !uprv_strcmp(key
, "y") ) {
312 calendarField
= UCAL_YEAR
;
313 } else if ( !uprv_strcmp(key
, "M") ) {
314 calendarField
= UCAL_MONTH
;
315 } else if ( !uprv_strcmp(key
, "d") ) {
316 calendarField
= UCAL_DATE
;
317 } else if ( !uprv_strcmp(key
, "a") ) {
318 calendarField
= UCAL_AM_PM
;
319 } else if ( !uprv_strcmp(key
, "h") || !uprv_strcmp(key
, "H") ) {
320 calendarField
= UCAL_HOUR
;
321 } else if ( !uprv_strcmp(key
, "m") ) {
322 calendarField
= UCAL_MINUTE
;
324 if ( calendarField
!= UCAL_FIELD_COUNT
) {
325 setIntervalPatternInternally(skeletonUniStr
, calendarField
, pattern
,status
);
331 ures_close(itvDtPtnResource
);
332 ures_close(calTypeBundle
);
333 ures_close(calBundle
);
335 status
= U_ZERO_ERROR
;
336 // Find the name of the appropriate parent locale (from %%Parent if present, else
337 // uloc_getParent on the actual locale name)
338 // (It would be nice to have a ures function that did this...)
340 const UChar
* parentUName
= ures_getStringByKey(rb
, "%%Parent", &locNameLen
, &status
);
341 if (U_SUCCESS(status
) && status
!= U_USING_FALLBACK_WARNING
&& locNameLen
< ULOC_FULLNAME_CAPACITY
) {
342 u_UCharsToChars(parentUName
, parentLocale
, locNameLen
+ 1);
344 status
= U_ZERO_ERROR
;
345 // Get the actual name of the current locale being used
346 const char *curLocaleName
=ures_getLocaleByType(rb
, ULOC_ACTUAL_LOCALE
, &status
);
347 if ( U_FAILURE(status
) ) {
348 curLocaleName
= parentLocale
;
349 status
= U_ZERO_ERROR
;
351 uloc_getParent(curLocaleName
, parentLocale
, ULOC_FULLNAME_CAPACITY
, &status
);
352 if (U_FAILURE(err
) || err
== U_STRING_NOT_TERMINATED_WARNING
) {
353 parentLocale
[0] = 0; // just fallback to root, will cause us to stop
354 status
= U_ZERO_ERROR
;
357 // Now we can close the current locale bundle
359 // If the new current locale is root, then stop
360 // (unlike for DateTimePatternGenerator, DateIntervalFormat does not go all the way up
361 // to root to find additional data for non-root locales)
362 } while ( parentLocale
[0] != 0 && uprv_strcmp(parentLocale
,"root")!=0 );
368 DateIntervalInfo::setIntervalPatternInternally(const UnicodeString
& skeleton
,
369 UCalendarDateFields lrgDiffCalUnit
,
370 const UnicodeString
& intervalPattern
,
371 UErrorCode
& status
) {
372 IntervalPatternIndex index
= calendarFieldToIntervalIndex(lrgDiffCalUnit
,status
);
373 if ( U_FAILURE(status
) ) {
376 UnicodeString
* patternsOfOneSkeleton
= (UnicodeString
*)(fIntervalPatterns
->get(skeleton
));
377 UBool emptyHash
= false;
378 if ( patternsOfOneSkeleton
== NULL
) {
379 patternsOfOneSkeleton
= new UnicodeString
[kIPI_MAX_INDEX
];
383 patternsOfOneSkeleton
[index
] = intervalPattern
;
384 if ( emptyHash
== TRUE
) {
385 fIntervalPatterns
->put(skeleton
, patternsOfOneSkeleton
, status
);
392 DateIntervalInfo::parseSkeleton(const UnicodeString
& skeleton
,
393 int32_t* skeletonFieldWidth
) {
394 const int8_t PATTERN_CHAR_BASE
= 0x41;
396 for ( i
= 0; i
< skeleton
.length(); ++i
) {
397 // it is an ASCII char in skeleton
398 int8_t ch
= (int8_t)skeleton
.charAt(i
);
399 ++skeletonFieldWidth
[ch
- PATTERN_CHAR_BASE
];
406 DateIntervalInfo::stringNumeric(int32_t fieldWidth
, int32_t anotherFieldWidth
,
407 char patternLetter
) {
408 if ( patternLetter
== 'M' ) {
409 if ( (fieldWidth
<= 2 && anotherFieldWidth
> 2) ||
410 (fieldWidth
> 2 && anotherFieldWidth
<= 2 )) {
420 DateIntervalInfo::getBestSkeleton(const UnicodeString
& skeleton
,
421 int8_t& bestMatchDistanceInfo
) const {
422 #ifdef DTITVINF_DEBUG
426 skeleton
.extract(0, skeleton
.length(), result
, "UTF-8");
427 sprintf(mesg
, "in getBestSkeleton: skeleton: %s; \n", result
);
432 int32_t inputSkeletonFieldWidth
[] =
434 // A B C D E F G H I J K L M N O
435 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
436 // P Q R S T U V W X Y Z
437 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
438 // a b c d e f g h i j k l m n o
439 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
440 // p q r s t u v w x y z
441 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
444 int32_t skeletonFieldWidth
[] =
446 // A B C D E F G H I J K L M N O
447 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
448 // P Q R S T U V W X Y Z
449 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
450 // a b c d e f g h i j k l m n o
451 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
452 // p q r s t u v w x y z
453 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
456 const int32_t DIFFERENT_FIELD
= 0x1000;
457 const int32_t STRING_NUMERIC_DIFFERENCE
= 0x100;
458 const int32_t BASE
= 0x41;
459 const UChar CHAR_V
= 0x0076;
460 const UChar CHAR_Z
= 0x007A;
462 // hack for 'v' and 'z'.
463 // resource bundle only have time skeletons ending with 'v',
464 // but not for time skeletons ending with 'z'.
465 UBool replaceZWithV
= false;
466 const UnicodeString
* inputSkeleton
= &skeleton
;
467 UnicodeString copySkeleton
;
468 if ( skeleton
.indexOf(CHAR_Z
) != -1 ) {
469 copySkeleton
= skeleton
;
470 copySkeleton
.findAndReplace(UnicodeString(CHAR_Z
), UnicodeString(CHAR_V
));
471 inputSkeleton
= ©Skeleton
;
472 replaceZWithV
= true;
475 parseSkeleton(*inputSkeleton
, inputSkeletonFieldWidth
);
476 int32_t bestDistance
= MAX_POSITIVE_INT
;
477 const UnicodeString
* bestSkeleton
= NULL
;
479 // 0 means exact the same skeletons;
480 // 1 means having the same field, but with different length,
481 // 2 means only z/v differs
482 // -1 means having different field.
483 bestMatchDistanceInfo
= 0;
484 int8_t fieldLength
= sizeof(skeletonFieldWidth
)/sizeof(skeletonFieldWidth
[0]);
486 int32_t pos
= UHASH_FIRST
;
487 const UHashElement
* elem
= NULL
;
488 while ( (elem
= fIntervalPatterns
->nextElement(pos
)) != NULL
) {
489 const UHashTok keyTok
= elem
->key
;
490 UnicodeString
* skeleton
= (UnicodeString
*)keyTok
.pointer
;
491 #ifdef DTITVINF_DEBUG
492 skeleton
->extract(0, skeleton
->length(), result
, "UTF-8");
493 sprintf(mesg
, "available skeletons: skeleton: %s; \n", result
);
497 // clear skeleton field width
499 for ( i
= 0; i
< fieldLength
; ++i
) {
500 skeletonFieldWidth
[i
] = 0;
502 parseSkeleton(*skeleton
, skeletonFieldWidth
);
503 // calculate distance
504 int32_t distance
= 0;
505 int8_t fieldDifference
= 1;
506 for ( i
= 0; i
< fieldLength
; ++i
) {
507 int32_t inputFieldWidth
= inputSkeletonFieldWidth
[i
];
508 int32_t fieldWidth
= skeletonFieldWidth
[i
];
509 if ( inputFieldWidth
== fieldWidth
) {
512 if ( inputFieldWidth
== 0 ) {
513 fieldDifference
= -1;
514 distance
+= DIFFERENT_FIELD
;
515 } else if ( fieldWidth
== 0 ) {
516 fieldDifference
= -1;
517 distance
+= DIFFERENT_FIELD
;
518 } else if (stringNumeric(inputFieldWidth
, fieldWidth
,
520 distance
+= STRING_NUMERIC_DIFFERENCE
;
522 distance
+= (inputFieldWidth
> fieldWidth
) ?
523 (inputFieldWidth
- fieldWidth
) :
524 (fieldWidth
- inputFieldWidth
);
527 if ( distance
< bestDistance
) {
528 bestSkeleton
= skeleton
;
529 bestDistance
= distance
;
530 bestMatchDistanceInfo
= fieldDifference
;
532 if ( distance
== 0 ) {
533 bestMatchDistanceInfo
= 0;
537 if ( replaceZWithV
&& bestMatchDistanceInfo
!= -1 ) {
538 bestMatchDistanceInfo
= 2;
545 DateIntervalInfo::IntervalPatternIndex
546 DateIntervalInfo::calendarFieldToIntervalIndex(UCalendarDateFields field
,
547 UErrorCode
& status
) {
548 if ( U_FAILURE(status
) ) {
549 return kIPI_MAX_INDEX
;
551 IntervalPatternIndex index
= kIPI_MAX_INDEX
;
563 case UCAL_DAY_OF_WEEK
:
564 //case UCAL_DAY_OF_MONTH:
571 case UCAL_HOUR_OF_DAY
:
581 status
= U_ILLEGAL_ARGUMENT_ERROR
;
589 DateIntervalInfo::deleteHash(Hashtable
* hTable
)
591 if ( hTable
== NULL
) {
594 int32_t pos
= UHASH_FIRST
;
595 const UHashElement
* element
= NULL
;
596 while ( (element
= hTable
->nextElement(pos
)) != NULL
) {
597 const UHashTok valueTok
= element
->value
;
598 const UnicodeString
* value
= (UnicodeString
*)valueTok
.pointer
;
601 delete fIntervalPatterns
;
608 * set hash table value comparator
610 * @param val1 one value in comparison
611 * @param val2 the other value in comparison
612 * @return TRUE if 2 values are the same, FALSE otherwise
614 static UBool U_CALLCONV
dtitvinfHashTableValueComparator(UHashTok val1
, UHashTok val2
);
617 U_CALLCONV
dtitvinfHashTableValueComparator(UHashTok val1
, UHashTok val2
) {
618 const UnicodeString
* pattern1
= (UnicodeString
*)val1
.pointer
;
619 const UnicodeString
* pattern2
= (UnicodeString
*)val2
.pointer
;
622 for ( i
= 0; i
< DateIntervalInfo::kMaxIntervalPatternIndex
&& ret
== TRUE
; ++i
) {
623 ret
= (pattern1
[i
] == pattern2
[i
]);
632 DateIntervalInfo::initHash(UErrorCode
& status
) {
633 if ( U_FAILURE(status
) ) {
637 if ( (hTable
= new Hashtable(FALSE
, status
)) == NULL
) {
638 status
= U_MEMORY_ALLOCATION_ERROR
;
641 if ( U_FAILURE(status
) ) {
645 hTable
->setValueComparator(dtitvinfHashTableValueComparator
);
651 DateIntervalInfo::copyHash(const Hashtable
* source
,
653 UErrorCode
& status
) {
654 if ( U_FAILURE(status
) ) {
657 int32_t pos
= UHASH_FIRST
;
658 const UHashElement
* element
= NULL
;
660 while ( (element
= source
->nextElement(pos
)) != NULL
) {
661 const UHashTok keyTok
= element
->key
;
662 const UnicodeString
* key
= (UnicodeString
*)keyTok
.pointer
;
663 const UHashTok valueTok
= element
->value
;
664 const UnicodeString
* value
= (UnicodeString
*)valueTok
.pointer
;
665 UnicodeString
* copy
= new UnicodeString
[kIPI_MAX_INDEX
];
667 for ( i
= 0; i
< kIPI_MAX_INDEX
; ++i
) {
670 target
->put(UnicodeString(*key
), copy
, status
);
671 if ( U_FAILURE(status
) ) {