1 /*******************************************************************************
2 * Copyright (C) 2008-2011, 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
];
225 uprv_strcpy(parentLocale
, locName
);
226 UErrorCode status
= U_ZERO_ERROR
;
227 Hashtable
skeletonSet(FALSE
, status
);
228 if ( U_FAILURE(status
) ) {
232 // determine calendar type
233 const char * calendarTypeToUse
= gGregorianTag
; // initial default
234 char calendarType
[ULOC_KEYWORDS_CAPACITY
]; // to be filled in with the type to use, if all goes well
235 char localeWithCalendarKey
[ULOC_LOCALE_IDENTIFIER_CAPACITY
];
236 // obtain a locale that always has the calendar key value that should be used
237 (void)ures_getFunctionalEquivalent(localeWithCalendarKey
, ULOC_LOCALE_IDENTIFIER_CAPACITY
, NULL
,
238 "calendar", "calendar", locName
, NULL
, FALSE
, &status
);
239 localeWithCalendarKey
[ULOC_LOCALE_IDENTIFIER_CAPACITY
-1] = 0; // ensure null termination
240 // now get the calendar key value from that locale
241 int32_t calendarTypeLen
= uloc_getKeywordValue(localeWithCalendarKey
, "calendar", calendarType
, ULOC_KEYWORDS_CAPACITY
, &status
);
242 if (U_SUCCESS(status
) && calendarTypeLen
< ULOC_KEYWORDS_CAPACITY
) {
243 calendarTypeToUse
= calendarType
;
245 status
= U_ZERO_ERROR
;
248 UResourceBundle
*rb
, *calBundle
, *calTypeBundle
, *itvDtPtnResource
;
249 rb
= ures_open(NULL
, parentLocale
, &status
);
250 calBundle
= ures_getByKey(rb
, gCalendarTag
, NULL
, &status
);
251 calTypeBundle
= ures_getByKey(calBundle
, calendarTypeToUse
, NULL
, &status
);
252 itvDtPtnResource
= ures_getByKeyWithFallback(calTypeBundle
,
253 gIntervalDateTimePatternTag
, NULL
, &status
);
255 if ( U_SUCCESS(status
) ) {
256 // look for fallback first, since it establishes the default order
258 int32_t resStrLen
= 0;
259 resStr
= ures_getStringByKeyWithFallback(itvDtPtnResource
,
261 &resStrLen
, &status
);
262 if ( U_SUCCESS(status
) ) {
263 UnicodeString pattern
= UnicodeString(TRUE
, resStr
, resStrLen
);
264 setFallbackIntervalPattern(pattern
, status
);
267 int32_t size
= ures_getSize(itvDtPtnResource
);
269 for ( index
= 0; index
< size
; ++index
) {
270 UResourceBundle
* oneRes
= ures_getByIndex(itvDtPtnResource
, index
,
272 if ( U_SUCCESS(status
) ) {
273 const char* skeleton
= ures_getKey(oneRes
);
274 if ( skeleton
== NULL
||
275 skeletonSet
.geti(UnicodeString(skeleton
)) == 1 ) {
279 skeletonSet
.puti(UnicodeString(skeleton
), 1, status
);
280 if ( uprv_strcmp(skeleton
, gFallbackPatternTag
) == 0 ) {
282 continue; // fallback
285 UResourceBundle
* intervalPatterns
= ures_getByKey(
286 itvDtPtnResource
, skeleton
, NULL
, &status
);
288 if ( U_FAILURE(status
) ) {
289 ures_close(intervalPatterns
);
293 if ( intervalPatterns
== NULL
) {
294 ures_close(intervalPatterns
);
299 const UChar
* pattern
;
302 int32_t ptnNum
= ures_getSize(intervalPatterns
);
304 for ( ptnIndex
= 0; ptnIndex
< ptnNum
; ++ptnIndex
) {
305 pattern
= ures_getNextString(intervalPatterns
, &ptLength
, &key
,
307 if ( U_FAILURE(status
) ) {
311 UCalendarDateFields calendarField
= UCAL_FIELD_COUNT
;
312 if ( !uprv_strcmp(key
, "y") ) {
313 calendarField
= UCAL_YEAR
;
314 } else if ( !uprv_strcmp(key
, "M") ) {
315 calendarField
= UCAL_MONTH
;
316 } else if ( !uprv_strcmp(key
, "d") ) {
317 calendarField
= UCAL_DATE
;
318 } else if ( !uprv_strcmp(key
, "a") ) {
319 calendarField
= UCAL_AM_PM
;
320 } else if ( !uprv_strcmp(key
, "h") || !uprv_strcmp(key
, "H") ) {
321 calendarField
= UCAL_HOUR
;
322 } else if ( !uprv_strcmp(key
, "m") ) {
323 calendarField
= UCAL_MINUTE
;
325 if ( calendarField
!= UCAL_FIELD_COUNT
) {
326 setIntervalPatternInternally(skeleton
, calendarField
, pattern
,status
);
329 ures_close(intervalPatterns
);
334 ures_close(itvDtPtnResource
);
335 ures_close(calTypeBundle
);
336 ures_close(calBundle
);
338 status
= U_ZERO_ERROR
;
339 locNameLen
= uloc_getParent(parentLocale
, parentLocale
,
340 ULOC_FULLNAME_CAPACITY
,&status
);
341 } while ( locNameLen
> 0 );
347 DateIntervalInfo::setIntervalPatternInternally(const UnicodeString
& skeleton
,
348 UCalendarDateFields lrgDiffCalUnit
,
349 const UnicodeString
& intervalPattern
,
350 UErrorCode
& status
) {
351 IntervalPatternIndex index
= calendarFieldToIntervalIndex(lrgDiffCalUnit
,status
);
352 if ( U_FAILURE(status
) ) {
355 UnicodeString
* patternsOfOneSkeleton
= (UnicodeString
*)(fIntervalPatterns
->get(skeleton
));
356 UBool emptyHash
= false;
357 if ( patternsOfOneSkeleton
== NULL
) {
358 patternsOfOneSkeleton
= new UnicodeString
[kIPI_MAX_INDEX
];
362 patternsOfOneSkeleton
[index
] = intervalPattern
;
363 if ( emptyHash
== TRUE
) {
364 fIntervalPatterns
->put(skeleton
, patternsOfOneSkeleton
, status
);
371 DateIntervalInfo::parseSkeleton(const UnicodeString
& skeleton
,
372 int32_t* skeletonFieldWidth
) {
373 const int8_t PATTERN_CHAR_BASE
= 0x41;
375 for ( i
= 0; i
< skeleton
.length(); ++i
) {
376 // it is an ASCII char in skeleton
377 int8_t ch
= (int8_t)skeleton
.charAt(i
);
378 ++skeletonFieldWidth
[ch
- PATTERN_CHAR_BASE
];
385 DateIntervalInfo::stringNumeric(int32_t fieldWidth
, int32_t anotherFieldWidth
,
386 char patternLetter
) {
387 if ( patternLetter
== 'M' ) {
388 if ( (fieldWidth
<= 2 && anotherFieldWidth
> 2) ||
389 (fieldWidth
> 2 && anotherFieldWidth
<= 2 )) {
399 DateIntervalInfo::getBestSkeleton(const UnicodeString
& skeleton
,
400 int8_t& bestMatchDistanceInfo
) const {
401 #ifdef DTITVINF_DEBUG
405 skeleton
.extract(0, skeleton
.length(), result
, "UTF-8");
406 sprintf(mesg
, "in getBestSkeleton: skeleton: %s; \n", result
);
411 int32_t inputSkeletonFieldWidth
[] =
413 // A B C D E F G H I J K L M N O
414 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
415 // P Q R S T U V W X Y Z
416 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
417 // a b c d e f g h i j k l m n o
418 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
419 // p q r s t u v w x y z
420 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
423 int32_t skeletonFieldWidth
[] =
425 // A B C D E F G H I J K L M N O
426 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
427 // P Q R S T U V W X Y Z
428 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
429 // a b c d e f g h i j k l m n o
430 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
431 // p q r s t u v w x y z
432 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
435 const int32_t DIFFERENT_FIELD
= 0x1000;
436 const int32_t STRING_NUMERIC_DIFFERENCE
= 0x100;
437 const int32_t BASE
= 0x41;
438 const UChar CHAR_V
= 0x0076;
439 const UChar CHAR_Z
= 0x007A;
441 // hack for 'v' and 'z'.
442 // resource bundle only have time skeletons ending with 'v',
443 // but not for time skeletons ending with 'z'.
444 UBool replaceZWithV
= false;
445 const UnicodeString
* inputSkeleton
= &skeleton
;
446 UnicodeString copySkeleton
;
447 if ( skeleton
.indexOf(CHAR_Z
) != -1 ) {
454 copySkeleton
= skeleton
;
455 copySkeleton
.findAndReplace(zstr
, vstr
);
456 inputSkeleton
= ©Skeleton
;
457 replaceZWithV
= true;
460 parseSkeleton(*inputSkeleton
, inputSkeletonFieldWidth
);
461 int32_t bestDistance
= MAX_POSITIVE_INT
;
462 const UnicodeString
* bestSkeleton
= NULL
;
464 // 0 means exact the same skeletons;
465 // 1 means having the same field, but with different length,
466 // 2 means only z/v differs
467 // -1 means having different field.
468 bestMatchDistanceInfo
= 0;
469 int8_t fieldLength
= sizeof(skeletonFieldWidth
)/sizeof(skeletonFieldWidth
[0]);
472 const UHashElement
* elem
= NULL
;
473 while ( (elem
= fIntervalPatterns
->nextElement(pos
)) != NULL
) {
474 const UHashTok keyTok
= elem
->key
;
475 UnicodeString
* skeleton
= (UnicodeString
*)keyTok
.pointer
;
476 #ifdef DTITVINF_DEBUG
477 skeleton
->extract(0, skeleton
->length(), result
, "UTF-8");
478 sprintf(mesg
, "available skeletons: skeleton: %s; \n", result
);
482 // clear skeleton field width
484 for ( i
= 0; i
< fieldLength
; ++i
) {
485 skeletonFieldWidth
[i
] = 0;
487 parseSkeleton(*skeleton
, skeletonFieldWidth
);
488 // calculate distance
489 int32_t distance
= 0;
490 int8_t fieldDifference
= 1;
491 for ( i
= 0; i
< fieldLength
; ++i
) {
492 int32_t inputFieldWidth
= inputSkeletonFieldWidth
[i
];
493 int32_t fieldWidth
= skeletonFieldWidth
[i
];
494 if ( inputFieldWidth
== fieldWidth
) {
497 if ( inputFieldWidth
== 0 ) {
498 fieldDifference
= -1;
499 distance
+= DIFFERENT_FIELD
;
500 } else if ( fieldWidth
== 0 ) {
501 fieldDifference
= -1;
502 distance
+= DIFFERENT_FIELD
;
503 } else if (stringNumeric(inputFieldWidth
, fieldWidth
,
505 distance
+= STRING_NUMERIC_DIFFERENCE
;
507 distance
+= (inputFieldWidth
> fieldWidth
) ?
508 (inputFieldWidth
- fieldWidth
) :
509 (fieldWidth
- inputFieldWidth
);
512 if ( distance
< bestDistance
) {
513 bestSkeleton
= skeleton
;
514 bestDistance
= distance
;
515 bestMatchDistanceInfo
= fieldDifference
;
517 if ( distance
== 0 ) {
518 bestMatchDistanceInfo
= 0;
522 if ( replaceZWithV
&& bestMatchDistanceInfo
!= -1 ) {
523 bestMatchDistanceInfo
= 2;
530 DateIntervalInfo::IntervalPatternIndex
531 DateIntervalInfo::calendarFieldToIntervalIndex(UCalendarDateFields field
,
532 UErrorCode
& status
) {
533 if ( U_FAILURE(status
) ) {
534 return kIPI_MAX_INDEX
;
536 IntervalPatternIndex index
= kIPI_MAX_INDEX
;
548 case UCAL_DAY_OF_WEEK
:
549 //case UCAL_DAY_OF_MONTH:
556 case UCAL_HOUR_OF_DAY
:
563 status
= U_ILLEGAL_ARGUMENT_ERROR
;
571 DateIntervalInfo::deleteHash(Hashtable
* hTable
)
573 if ( hTable
== NULL
) {
577 const UHashElement
* element
= NULL
;
578 while ( (element
= hTable
->nextElement(pos
)) != NULL
) {
579 const UHashTok keyTok
= element
->key
;
580 const UHashTok valueTok
= element
->value
;
581 const UnicodeString
* value
= (UnicodeString
*)valueTok
.pointer
;
584 delete fIntervalPatterns
;
591 * set hash table value comparator
593 * @param val1 one value in comparison
594 * @param val2 the other value in comparison
595 * @return TRUE if 2 values are the same, FALSE otherwise
597 static UBool U_CALLCONV
dtitvinfHashTableValueComparator(UHashTok val1
, UHashTok val2
);
600 U_CALLCONV
dtitvinfHashTableValueComparator(UHashTok val1
, UHashTok val2
) {
601 const UnicodeString
* pattern1
= (UnicodeString
*)val1
.pointer
;
602 const UnicodeString
* pattern2
= (UnicodeString
*)val2
.pointer
;
605 for ( i
= 0; i
< DateIntervalInfo::kMaxIntervalPatternIndex
&& ret
== TRUE
; ++i
) {
606 ret
= (pattern1
[i
] == pattern2
[i
]);
615 DateIntervalInfo::initHash(UErrorCode
& status
) {
616 if ( U_FAILURE(status
) ) {
620 if ( (hTable
= new Hashtable(FALSE
, status
)) == NULL
) {
621 status
= U_MEMORY_ALLOCATION_ERROR
;
624 if ( U_FAILURE(status
) ) {
628 hTable
->setValueComparator(dtitvinfHashTableValueComparator
);
634 DateIntervalInfo::copyHash(const Hashtable
* source
,
636 UErrorCode
& status
) {
637 if ( U_FAILURE(status
) ) {
641 const UHashElement
* element
= NULL
;
643 while ( (element
= source
->nextElement(pos
)) != NULL
) {
644 const UHashTok keyTok
= element
->key
;
645 const UnicodeString
* key
= (UnicodeString
*)keyTok
.pointer
;
646 const UHashTok valueTok
= element
->value
;
647 const UnicodeString
* value
= (UnicodeString
*)valueTok
.pointer
;
648 UnicodeString
* copy
= new UnicodeString
[kIPI_MAX_INDEX
];
650 for ( i
= 0; i
< kIPI_MAX_INDEX
; ++i
) {
653 target
->put(UnicodeString(*key
), copy
, status
);
654 if ( U_FAILURE(status
) ) {