1 /*******************************************************************************
2 * Copyright (C) 2008-2012, 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
skeletonSet(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 ( skeletonSet
.geti(skeletonUniStr
) == 1 ) {
283 skeletonSet
.puti(skeletonUniStr
, 1, status
);
284 if ( uprv_strcmp(skeleton
, gFallbackPatternTag
) == 0 ) {
285 continue; // fallback
288 LocalUResourceBundlePointer
intervalPatterns(ures_getByKey(
289 itvDtPtnResource
, skeleton
, NULL
, &status
));
291 if ( U_FAILURE(status
) ) {
294 if ( intervalPatterns
== NULL
) {
299 int32_t ptnNum
= ures_getSize(intervalPatterns
.getAlias());
301 for ( ptnIndex
= 0; ptnIndex
< ptnNum
; ++ptnIndex
) {
302 UnicodeString pattern
=
303 ures_getNextUnicodeString(intervalPatterns
.getAlias(), &key
, &status
);
304 if ( U_FAILURE(status
) ) {
308 UCalendarDateFields calendarField
= UCAL_FIELD_COUNT
;
309 if ( !uprv_strcmp(key
, "y") ) {
310 calendarField
= UCAL_YEAR
;
311 } else if ( !uprv_strcmp(key
, "M") ) {
312 calendarField
= UCAL_MONTH
;
313 } else if ( !uprv_strcmp(key
, "d") ) {
314 calendarField
= UCAL_DATE
;
315 } else if ( !uprv_strcmp(key
, "a") ) {
316 calendarField
= UCAL_AM_PM
;
317 } else if ( !uprv_strcmp(key
, "h") || !uprv_strcmp(key
, "H") ) {
318 calendarField
= UCAL_HOUR
;
319 } else if ( !uprv_strcmp(key
, "m") ) {
320 calendarField
= UCAL_MINUTE
;
322 if ( calendarField
!= UCAL_FIELD_COUNT
) {
323 setIntervalPatternInternally(skeletonUniStr
, calendarField
, pattern
,status
);
329 ures_close(itvDtPtnResource
);
330 ures_close(calTypeBundle
);
331 ures_close(calBundle
);
333 status
= U_ZERO_ERROR
;
334 // Find the name of the appropriate parent locale (from %%Parent if present, else
335 // uloc_getParent on the actual locale name)
336 // (It would be nice to have a ures function that did this...)
338 const UChar
* parentUName
= ures_getStringByKey(rb
, "%%Parent", &locNameLen
, &status
);
339 if (U_SUCCESS(status
) && status
!= U_USING_FALLBACK_WARNING
&& locNameLen
< ULOC_FULLNAME_CAPACITY
) {
340 u_UCharsToChars(parentUName
, parentLocale
, locNameLen
+ 1);
342 status
= U_ZERO_ERROR
;
343 // Get the actual name of the current locale being used
344 const char *curLocaleName
=ures_getLocaleByType(rb
, ULOC_ACTUAL_LOCALE
, &status
);
345 if ( U_FAILURE(status
) ) {
346 curLocaleName
= parentLocale
;
347 status
= U_ZERO_ERROR
;
349 uloc_getParent(curLocaleName
, parentLocale
, ULOC_FULLNAME_CAPACITY
, &status
);
350 if (U_FAILURE(err
) || err
== U_STRING_NOT_TERMINATED_WARNING
) {
351 parentLocale
[0] = 0; // just fallback to root, will cause us to stop
352 status
= U_ZERO_ERROR
;
355 // Now we can close the current locale bundle
357 // If the new current locale is root, then stop
358 // (unlike for DateTimePatternGenerator, DateIntervalFormat does not go all the way up
359 // to root to find additional data for non-root locales)
360 } while ( parentLocale
[0] != 0 && uprv_strcmp(parentLocale
,"root")!=0 );
366 DateIntervalInfo::setIntervalPatternInternally(const UnicodeString
& skeleton
,
367 UCalendarDateFields lrgDiffCalUnit
,
368 const UnicodeString
& intervalPattern
,
369 UErrorCode
& status
) {
370 IntervalPatternIndex index
= calendarFieldToIntervalIndex(lrgDiffCalUnit
,status
);
371 if ( U_FAILURE(status
) ) {
374 UnicodeString
* patternsOfOneSkeleton
= (UnicodeString
*)(fIntervalPatterns
->get(skeleton
));
375 UBool emptyHash
= false;
376 if ( patternsOfOneSkeleton
== NULL
) {
377 patternsOfOneSkeleton
= new UnicodeString
[kIPI_MAX_INDEX
];
381 patternsOfOneSkeleton
[index
] = intervalPattern
;
382 if ( emptyHash
== TRUE
) {
383 fIntervalPatterns
->put(skeleton
, patternsOfOneSkeleton
, status
);
390 DateIntervalInfo::parseSkeleton(const UnicodeString
& skeleton
,
391 int32_t* skeletonFieldWidth
) {
392 const int8_t PATTERN_CHAR_BASE
= 0x41;
394 for ( i
= 0; i
< skeleton
.length(); ++i
) {
395 // it is an ASCII char in skeleton
396 int8_t ch
= (int8_t)skeleton
.charAt(i
);
397 ++skeletonFieldWidth
[ch
- PATTERN_CHAR_BASE
];
404 DateIntervalInfo::stringNumeric(int32_t fieldWidth
, int32_t anotherFieldWidth
,
405 char patternLetter
) {
406 if ( patternLetter
== 'M' ) {
407 if ( (fieldWidth
<= 2 && anotherFieldWidth
> 2) ||
408 (fieldWidth
> 2 && anotherFieldWidth
<= 2 )) {
418 DateIntervalInfo::getBestSkeleton(const UnicodeString
& skeleton
,
419 int8_t& bestMatchDistanceInfo
) const {
420 #ifdef DTITVINF_DEBUG
424 skeleton
.extract(0, skeleton
.length(), result
, "UTF-8");
425 sprintf(mesg
, "in getBestSkeleton: skeleton: %s; \n", result
);
430 int32_t inputSkeletonFieldWidth
[] =
432 // A B C D E F G H I J K L M N O
433 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
434 // P Q R S T U V W X Y Z
435 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
436 // a b c d e f g h i j k l m n o
437 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
438 // p q r s t u v w x y z
439 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
442 int32_t skeletonFieldWidth
[] =
444 // A B C D E F G H I J K L M N O
445 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
446 // P Q R S T U V W X Y Z
447 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
448 // a b c d e f g h i j k l m n o
449 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
450 // p q r s t u v w x y z
451 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
454 const int32_t DIFFERENT_FIELD
= 0x1000;
455 const int32_t STRING_NUMERIC_DIFFERENCE
= 0x100;
456 const int32_t BASE
= 0x41;
457 const UChar CHAR_V
= 0x0076;
458 const UChar CHAR_Z
= 0x007A;
460 // hack for 'v' and 'z'.
461 // resource bundle only have time skeletons ending with 'v',
462 // but not for time skeletons ending with 'z'.
463 UBool replaceZWithV
= false;
464 const UnicodeString
* inputSkeleton
= &skeleton
;
465 UnicodeString copySkeleton
;
466 if ( skeleton
.indexOf(CHAR_Z
) != -1 ) {
467 copySkeleton
= skeleton
;
468 copySkeleton
.findAndReplace(UnicodeString(CHAR_Z
), UnicodeString(CHAR_V
));
469 inputSkeleton
= ©Skeleton
;
470 replaceZWithV
= true;
473 parseSkeleton(*inputSkeleton
, inputSkeletonFieldWidth
);
474 int32_t bestDistance
= MAX_POSITIVE_INT
;
475 const UnicodeString
* bestSkeleton
= NULL
;
477 // 0 means exact the same skeletons;
478 // 1 means having the same field, but with different length,
479 // 2 means only z/v differs
480 // -1 means having different field.
481 bestMatchDistanceInfo
= 0;
482 int8_t fieldLength
= sizeof(skeletonFieldWidth
)/sizeof(skeletonFieldWidth
[0]);
485 const UHashElement
* elem
= NULL
;
486 while ( (elem
= fIntervalPatterns
->nextElement(pos
)) != NULL
) {
487 const UHashTok keyTok
= elem
->key
;
488 UnicodeString
* skeleton
= (UnicodeString
*)keyTok
.pointer
;
489 #ifdef DTITVINF_DEBUG
490 skeleton
->extract(0, skeleton
->length(), result
, "UTF-8");
491 sprintf(mesg
, "available skeletons: skeleton: %s; \n", result
);
495 // clear skeleton field width
497 for ( i
= 0; i
< fieldLength
; ++i
) {
498 skeletonFieldWidth
[i
] = 0;
500 parseSkeleton(*skeleton
, skeletonFieldWidth
);
501 // calculate distance
502 int32_t distance
= 0;
503 int8_t fieldDifference
= 1;
504 for ( i
= 0; i
< fieldLength
; ++i
) {
505 int32_t inputFieldWidth
= inputSkeletonFieldWidth
[i
];
506 int32_t fieldWidth
= skeletonFieldWidth
[i
];
507 if ( inputFieldWidth
== fieldWidth
) {
510 if ( inputFieldWidth
== 0 ) {
511 fieldDifference
= -1;
512 distance
+= DIFFERENT_FIELD
;
513 } else if ( fieldWidth
== 0 ) {
514 fieldDifference
= -1;
515 distance
+= DIFFERENT_FIELD
;
516 } else if (stringNumeric(inputFieldWidth
, fieldWidth
,
518 distance
+= STRING_NUMERIC_DIFFERENCE
;
520 distance
+= (inputFieldWidth
> fieldWidth
) ?
521 (inputFieldWidth
- fieldWidth
) :
522 (fieldWidth
- inputFieldWidth
);
525 if ( distance
< bestDistance
) {
526 bestSkeleton
= skeleton
;
527 bestDistance
= distance
;
528 bestMatchDistanceInfo
= fieldDifference
;
530 if ( distance
== 0 ) {
531 bestMatchDistanceInfo
= 0;
535 if ( replaceZWithV
&& bestMatchDistanceInfo
!= -1 ) {
536 bestMatchDistanceInfo
= 2;
543 DateIntervalInfo::IntervalPatternIndex
544 DateIntervalInfo::calendarFieldToIntervalIndex(UCalendarDateFields field
,
545 UErrorCode
& status
) {
546 if ( U_FAILURE(status
) ) {
547 return kIPI_MAX_INDEX
;
549 IntervalPatternIndex index
= kIPI_MAX_INDEX
;
561 case UCAL_DAY_OF_WEEK
:
562 //case UCAL_DAY_OF_MONTH:
569 case UCAL_HOUR_OF_DAY
:
576 status
= U_ILLEGAL_ARGUMENT_ERROR
;
584 DateIntervalInfo::deleteHash(Hashtable
* hTable
)
586 if ( hTable
== NULL
) {
590 const UHashElement
* element
= NULL
;
591 while ( (element
= hTable
->nextElement(pos
)) != NULL
) {
592 const UHashTok valueTok
= element
->value
;
593 const UnicodeString
* value
= (UnicodeString
*)valueTok
.pointer
;
596 delete fIntervalPatterns
;
603 * set hash table value comparator
605 * @param val1 one value in comparison
606 * @param val2 the other value in comparison
607 * @return TRUE if 2 values are the same, FALSE otherwise
609 static UBool U_CALLCONV
dtitvinfHashTableValueComparator(UHashTok val1
, UHashTok val2
);
612 U_CALLCONV
dtitvinfHashTableValueComparator(UHashTok val1
, UHashTok val2
) {
613 const UnicodeString
* pattern1
= (UnicodeString
*)val1
.pointer
;
614 const UnicodeString
* pattern2
= (UnicodeString
*)val2
.pointer
;
617 for ( i
= 0; i
< DateIntervalInfo::kMaxIntervalPatternIndex
&& ret
== TRUE
; ++i
) {
618 ret
= (pattern1
[i
] == pattern2
[i
]);
627 DateIntervalInfo::initHash(UErrorCode
& status
) {
628 if ( U_FAILURE(status
) ) {
632 if ( (hTable
= new Hashtable(FALSE
, status
)) == NULL
) {
633 status
= U_MEMORY_ALLOCATION_ERROR
;
636 if ( U_FAILURE(status
) ) {
640 hTable
->setValueComparator(dtitvinfHashTableValueComparator
);
646 DateIntervalInfo::copyHash(const Hashtable
* source
,
648 UErrorCode
& status
) {
649 if ( U_FAILURE(status
) ) {
653 const UHashElement
* element
= NULL
;
655 while ( (element
= source
->nextElement(pos
)) != NULL
) {
656 const UHashTok keyTok
= element
->key
;
657 const UnicodeString
* key
= (UnicodeString
*)keyTok
.pointer
;
658 const UHashTok valueTok
= element
->value
;
659 const UnicodeString
* value
= (UnicodeString
*)valueTok
.pointer
;
660 UnicodeString
* copy
= new UnicodeString
[kIPI_MAX_INDEX
];
662 for ( i
= 0; i
< kIPI_MAX_INDEX
; ++i
) {
665 target
->put(UnicodeString(*key
), copy
, status
);
666 if ( U_FAILURE(status
) ) {