1 /******************************************************************************* 2 * Copyright (C) 2008-2009, International Business Machines Corporation and 3 * others. All Rights Reserved. 4 ******************************************************************************* 5 * 6 * File DTITVINF.CPP 7 * 8 ******************************************************************************* 9 */ 10 11 #include "unicode/dtitvinf.h" 12 13 14 #if !UCONFIG_NO_FORMATTING 15 16 //TODO: define it in compiler time 17 //#define DTITVINF_DEBUG 1 18 19 20 #ifdef DTITVINF_DEBUG 21 #include <iostream> 22 #endif 23 24 #include "cstring.h" 25 #include "unicode/msgfmt.h" 26 #include "dtitv_impl.h" 27 #include "hash.h" 28 #include "gregoimp.h" 29 #include "uresimp.h" 30 #include "hash.h" 31 #include "gregoimp.h" 32 #include "uresimp.h" 33 34 35 U_NAMESPACE_BEGIN 36 37 38 #ifdef DTITVINF_DEBUG 39 #define PRINTMESG(msg) { std::cout << "(" << __FILE__ << ":" << __LINE__ << ") " << msg << "\n"; } 40 #endif 41 42 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalInfo) 43 44 static const char gCalendarTag[]="calendar"; 45 static const char gGregorianTag[]="gregorian"; 46 static const char gIntervalDateTimePatternTag[]="intervalFormats"; 47 static const char gFallbackPatternTag[]="fallback"; 48 49 // {0} 50 static const UChar gFirstPattern[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET}; 51 // {1} 52 static const UChar gSecondPattern[] = {LEFT_CURLY_BRACKET, DIGIT_ONE, RIGHT_CURLY_BRACKET}; 53 54 // default fall-back 55 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}; 56 57 58 59 DateIntervalInfo::DateIntervalInfo(UErrorCode& status) 60 : fFallbackIntervalPattern(gDefaultFallbackPattern), 61 fFirstDateInPtnIsLaterDate(false), 62 fIntervalPatterns(NULL) 63 { 64 fIntervalPatterns = initHash(status); 65 } 66 67 68 69 DateIntervalInfo::DateIntervalInfo(const Locale& locale, UErrorCode& status) 70 : fFallbackIntervalPattern(gDefaultFallbackPattern), 71 fFirstDateInPtnIsLaterDate(false), 72 fIntervalPatterns(NULL) 73 { 74 initializeData(locale, status); 75 } 76 77 78 79 void 80 DateIntervalInfo::setIntervalPattern(const UnicodeString& skeleton, 81 UCalendarDateFields lrgDiffCalUnit, 82 const UnicodeString& intervalPattern, 83 UErrorCode& status) { 84 85 if ( lrgDiffCalUnit == UCAL_HOUR_OF_DAY ) { 86 setIntervalPatternInternally(skeleton, UCAL_AM_PM, intervalPattern, status); 87 setIntervalPatternInternally(skeleton, UCAL_HOUR, intervalPattern, status); 88 } else if ( lrgDiffCalUnit == UCAL_DAY_OF_MONTH || 89 lrgDiffCalUnit == UCAL_DAY_OF_WEEK ) { 90 setIntervalPatternInternally(skeleton, UCAL_DATE, intervalPattern, status); 91 } else { 92 setIntervalPatternInternally(skeleton, lrgDiffCalUnit, intervalPattern, status); 93 } 94 } 95 96 97 void 98 DateIntervalInfo::setFallbackIntervalPattern( 99 const UnicodeString& fallbackPattern, 100 UErrorCode& status) { 101 if ( U_FAILURE(status) ) { 102 return; 103 } 104 int32_t firstPatternIndex = fallbackPattern.indexOf(gFirstPattern, 105 sizeof(gFirstPattern)/sizeof(gFirstPattern[0]), 0); 106 int32_t secondPatternIndex = fallbackPattern.indexOf(gSecondPattern, 107 sizeof(gSecondPattern)/sizeof(gSecondPattern[0]), 0); 108 if ( firstPatternIndex == -1 || secondPatternIndex == -1 ) { 109 status = U_ILLEGAL_ARGUMENT_ERROR; 110 return; 111 } 112 if ( firstPatternIndex > secondPatternIndex ) { 113 fFirstDateInPtnIsLaterDate = true; 114 } 115 fFallbackIntervalPattern = fallbackPattern; 116 } 117 118 119 120 DateIntervalInfo::DateIntervalInfo(const DateIntervalInfo& dtitvinf) 121 : UObject(dtitvinf), 122 fIntervalPatterns(NULL) 123 { 124 *this = dtitvinf; 125 } 126 127 128 129 DateIntervalInfo& 130 DateIntervalInfo::operator=(const DateIntervalInfo& dtitvinf) { 131 if ( this == &dtitvinf ) { 132 return *this; 133 } 134 135 UErrorCode status = U_ZERO_ERROR; 136 deleteHash(fIntervalPatterns); 137 fIntervalPatterns = initHash(status); 138 copyHash(dtitvinf.fIntervalPatterns, fIntervalPatterns, status); 139 if ( U_FAILURE(status) ) { 140 return *this; 141 } 142 143 fFallbackIntervalPattern = dtitvinf.fFallbackIntervalPattern; 144 fFirstDateInPtnIsLaterDate = dtitvinf.fFirstDateInPtnIsLaterDate; 145 return *this; 146 } 147 148 149 DateIntervalInfo* 150 DateIntervalInfo::clone() const { 151 return new DateIntervalInfo(*this); 152 } 153 154 155 DateIntervalInfo::~DateIntervalInfo() { 156 deleteHash(fIntervalPatterns); 157 fIntervalPatterns = NULL; 158 } 159 160 161 UBool 162 DateIntervalInfo::operator==(const DateIntervalInfo& other) const { 163 UBool equal = ( 164 fFallbackIntervalPattern == other.fFallbackIntervalPattern && 165 fFirstDateInPtnIsLaterDate == other.fFirstDateInPtnIsLaterDate ); 166 167 if ( equal == TRUE ) { 168 equal = fIntervalPatterns->equals(*(other.fIntervalPatterns)); 169 } 170 171 return equal; 172 } 173 174 175 UnicodeString& 176 DateIntervalInfo::getIntervalPattern(const UnicodeString& skeleton, 177 UCalendarDateFields field, 178 UnicodeString& result, 179 UErrorCode& status) const { 180 if ( U_FAILURE(status) ) { 181 return result; 182 } 183 184 const UnicodeString* patternsOfOneSkeleton = (UnicodeString*) fIntervalPatterns->get(skeleton); 185 if ( patternsOfOneSkeleton != NULL ) { 186 IntervalPatternIndex index = calendarFieldToIntervalIndex(field, status); 187 if ( U_FAILURE(status) ) { 188 return result; 189 } 190 const UnicodeString& intervalPattern = patternsOfOneSkeleton[index]; 191 if ( !intervalPattern.isEmpty() ) { 192 result = intervalPattern; 193 } 194 } 195 return result; 196 } 197 198 199 UBool 200 DateIntervalInfo::getDefaultOrder() const { 201 return fFirstDateInPtnIsLaterDate; 202 } 203 204 205 UnicodeString& 206 DateIntervalInfo::getFallbackIntervalPattern(UnicodeString& result) const { 207 result = fFallbackIntervalPattern; 208 return result; 209 } 210 211 212 void 213 DateIntervalInfo::initializeData(const Locale& locale, UErrorCode& err) 214 { 215 fIntervalPatterns = initHash(err); 216 if ( U_FAILURE(err) ) { 217 return; 218 } 219 const char *locName = locale.getName(); 220 char parentLocale[ULOC_FULLNAME_CAPACITY]; 221 int32_t locNameLen; 222 uprv_strcpy(parentLocale, locName); 223 UErrorCode status = U_ZERO_ERROR; 224 Hashtable skeletonSet(TRUE, status); 225 if ( U_FAILURE(status) ) { 226 return; 227 } 228 do { 229 UResourceBundle *rb, *calBundle, *gregorianBundle, *itvDtPtnResource; 230 rb = ures_open(NULL, parentLocale, &status); 231 calBundle = ures_getByKey(rb, gCalendarTag, NULL, &status); 232 gregorianBundle = ures_getByKey(calBundle, gGregorianTag, NULL, &status); 233 itvDtPtnResource = ures_getByKeyWithFallback(gregorianBundle, 234 gIntervalDateTimePatternTag, NULL, &status); 235 236 if ( U_SUCCESS(status) ) { 237 // look for fallback first, since it establishes the default order 238 const UChar* resStr; 239 int32_t resStrLen = 0; 240 resStr = ures_getStringByKeyWithFallback(itvDtPtnResource, 241 gFallbackPatternTag, 242 &resStrLen, &status); 243 if ( U_SUCCESS(status) ) { 244 UnicodeString pattern = UnicodeString(TRUE, resStr, resStrLen); 245 setFallbackIntervalPattern(pattern, status); 246 } 247 248 int32_t size = ures_getSize(itvDtPtnResource); 249 int32_t index; 250 for ( index = 0; index < size; ++index ) { 251 UResourceBundle* oneRes = ures_getByIndex(itvDtPtnResource, index, 252 NULL, &status); 253 if ( U_SUCCESS(status) ) { 254 const char* skeleton = ures_getKey(oneRes); 255 if ( skeleton == NULL || 256 skeletonSet.geti(UnicodeString(skeleton)) == 1 ) { 257 ures_close(oneRes); 258 continue; 259 } 260 skeletonSet.puti(UnicodeString(skeleton), 1, status); 261 if ( uprv_strcmp(skeleton, gFallbackPatternTag) == 0 ) { 262 ures_close(oneRes); 263 continue; // fallback 264 } 265 266 UResourceBundle* intervalPatterns = ures_getByKey( 267 itvDtPtnResource, skeleton, NULL, &status); 268 269 if ( U_FAILURE(status) ) { 270 ures_close(intervalPatterns); 271 ures_close(oneRes); 272 break; 273 } 274 if ( intervalPatterns == NULL ) { 275 ures_close(intervalPatterns); 276 ures_close(oneRes); 277 continue; 278 } 279 280 const UChar* pattern; 281 const char* key; 282 int32_t ptLength; 283 int32_t ptnNum = ures_getSize(intervalPatterns); 284 int32_t ptnIndex; 285 for ( ptnIndex = 0; ptnIndex < ptnNum; ++ptnIndex ) { 286 pattern = ures_getNextString(intervalPatterns, &ptLength, &key, 287 &status); 288 if ( U_FAILURE(status) ) { 289 break; 290 } 291 292 UCalendarDateFields calendarField = UCAL_FIELD_COUNT; 293 if ( !uprv_strcmp(key, "y") ) { 294 calendarField = UCAL_YEAR; 295 } else if ( !uprv_strcmp(key, "M") ) { 296 calendarField = UCAL_MONTH; 297 } else if ( !uprv_strcmp(key, "d") ) { 298 calendarField = UCAL_DATE; 299 } else if ( !uprv_strcmp(key, "a") ) { 300 calendarField = UCAL_AM_PM; 301 } else if ( !uprv_strcmp(key, "h") ) { 302 calendarField = UCAL_HOUR; 303 } else if ( !uprv_strcmp(key, "m") ) { 304 calendarField = UCAL_MINUTE; 305 } 306 if ( calendarField != UCAL_FIELD_COUNT ) { 307 setIntervalPatternInternally(skeleton, calendarField, pattern,status); 308 } 309 } 310 ures_close(intervalPatterns); 311 } 312 ures_close(oneRes); 313 } 314 } 315 ures_close(itvDtPtnResource); 316 ures_close(gregorianBundle); 317 ures_close(calBundle); 318 ures_close(rb); 319 status = U_ZERO_ERROR; 320 locNameLen = uloc_getParent(parentLocale, parentLocale, 321 ULOC_FULLNAME_CAPACITY,&status); 322 } while ( locNameLen > 0 ); 323 } 324 325 326 327 void 328 DateIntervalInfo::setIntervalPatternInternally(const UnicodeString& skeleton, 329 UCalendarDateFields lrgDiffCalUnit, 330 const UnicodeString& intervalPattern, 331 UErrorCode& status) { 332 IntervalPatternIndex index = calendarFieldToIntervalIndex(lrgDiffCalUnit,status); 333 if ( U_FAILURE(status) ) { 334 return; 335 } 336 UnicodeString* patternsOfOneSkeleton = (UnicodeString*)(fIntervalPatterns->get(skeleton)); 337 UBool emptyHash = false; 338 if ( patternsOfOneSkeleton == NULL ) { 339 patternsOfOneSkeleton = new UnicodeString[kIPI_MAX_INDEX]; 340 emptyHash = true; 341 } 342 343 patternsOfOneSkeleton[index] = intervalPattern; 344 if ( emptyHash == TRUE ) { 345 fIntervalPatterns->put(skeleton, patternsOfOneSkeleton, status); 346 } 347 } 348 349 350 351 void 352 DateIntervalInfo::parseSkeleton(const UnicodeString& skeleton, 353 int32_t* skeletonFieldWidth) { 354 const int8_t PATTERN_CHAR_BASE = 0x41; 355 int32_t i; 356 for ( i = 0; i < skeleton.length(); ++i ) { 357 // it is an ASCII char in skeleton 358 int8_t ch = (int8_t)skeleton.charAt(i); 359 ++skeletonFieldWidth[ch - PATTERN_CHAR_BASE]; 360 } 361 } 362 363 364 365 UBool 366 DateIntervalInfo::stringNumeric(int32_t fieldWidth, int32_t anotherFieldWidth, 367 char patternLetter) { 368 if ( patternLetter == 'M' ) { 369 if ( fieldWidth <= 2 && anotherFieldWidth > 2 || 370 fieldWidth > 2 && anotherFieldWidth <= 2 ) { 371 return true; 372 } 373 } 374 return false; 375 } 376 377 378 379 const UnicodeString* 380 DateIntervalInfo::getBestSkeleton(const UnicodeString& skeleton, 381 int8_t& bestMatchDistanceInfo) const { 382 #ifdef DTITVINF_DEBUG 383 char result[1000]; 384 char result_1[1000]; 385 char mesg[2000]; 386 skeleton.extract(0, skeleton.length(), result, "UTF-8"); 387 sprintf(mesg, "in getBestSkeleton: skeleton: %s; \n", result); 388 PRINTMESG(mesg) 389 #endif 390 391 392 int32_t inputSkeletonFieldWidth[] = 393 { 394 // A B C D E F G H I J K L M N O 395 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 396 // P Q R S T U V W X Y Z 397 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 398 // a b c d e f g h i j k l m n o 399 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 400 // p q r s t u v w x y z 401 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 402 }; 403 404 int32_t skeletonFieldWidth[] = 405 { 406 // A B C D E F G H I J K L M N O 407 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 408 // P Q R S T U V W X Y Z 409 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 410 // a b c d e f g h i j k l m n o 411 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 412 // p q r s t u v w x y z 413 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 414 }; 415 416 const int32_t DIFFERENT_FIELD = 0x1000; 417 const int32_t STRING_NUMERIC_DIFFERENCE = 0x100; 418 const int32_t BASE = 0x41; 419 const UChar CHAR_V = 0x0076; 420 const UChar CHAR_Z = 0x007A; 421 422 // hack for 'v' and 'z'. 423 // resource bundle only have time skeletons ending with 'v', 424 // but not for time skeletons ending with 'z'. 425 UBool replaceZWithV = false; 426 const UnicodeString* inputSkeleton = &skeleton; 427 UnicodeString copySkeleton; 428 if ( skeleton.indexOf(CHAR_Z) != -1 ) { 429 UChar zstr[2]; 430 UChar vstr[2]; 431 zstr[0]=CHAR_Z; 432 vstr[0]=CHAR_V; 433 zstr[1]=0; 434 vstr[1]=0; 435 copySkeleton = skeleton; 436 copySkeleton.findAndReplace(zstr, vstr); 437 inputSkeleton = ©Skeleton; 438 replaceZWithV = true; 439 } 440 441 parseSkeleton(*inputSkeleton, inputSkeletonFieldWidth); 442 int32_t bestDistance = MAX_POSITIVE_INT; 443 const UnicodeString* bestSkeleton = NULL; 444 445 // 0 means exact the same skeletons; 446 // 1 means having the same field, but with different length, 447 // 2 means only z/v differs 448 // -1 means having different field. 449 bestMatchDistanceInfo = 0; 450 int8_t fieldLength = sizeof(skeletonFieldWidth)/sizeof(skeletonFieldWidth[0]); 451 452 int32_t pos = -1; 453 const UHashElement* elem = NULL; 454 while ( (elem = fIntervalPatterns->nextElement(pos)) != NULL ) { 455 const UHashTok keyTok = elem->key; 456 UnicodeString* skeleton = (UnicodeString*)keyTok.pointer; 457 #ifdef DTITVINF_DEBUG 458 skeleton->extract(0, skeleton->length(), result, "UTF-8"); 459 sprintf(mesg, "available skeletons: skeleton: %s; \n", result); 460 PRINTMESG(mesg) 461 #endif 462 463 // clear skeleton field width 464 int8_t i; 465 for ( i = 0; i < fieldLength; ++i ) { 466 skeletonFieldWidth[i] = 0; 467 } 468 parseSkeleton(*skeleton, skeletonFieldWidth); 469 // calculate distance 470 int32_t distance = 0; 471 int8_t fieldDifference = 1; 472 for ( i = 0; i < fieldLength; ++i ) { 473 int32_t inputFieldWidth = inputSkeletonFieldWidth[i]; 474 int32_t fieldWidth = skeletonFieldWidth[i]; 475 if ( inputFieldWidth == fieldWidth ) { 476 continue; 477 } 478 if ( inputFieldWidth == 0 ) { 479 fieldDifference = -1; 480 distance += DIFFERENT_FIELD; 481 } else if ( fieldWidth == 0 ) { 482 fieldDifference = -1; 483 distance += DIFFERENT_FIELD; 484 } else if (stringNumeric(inputFieldWidth, fieldWidth, 485 (char)(i+BASE) ) ) { 486 distance += STRING_NUMERIC_DIFFERENCE; 487 } else { 488 distance += (inputFieldWidth > fieldWidth) ? 489 (inputFieldWidth - fieldWidth) : 490 (fieldWidth - inputFieldWidth); 491 } 492 } 493 if ( distance < bestDistance ) { 494 bestSkeleton = skeleton; 495 bestDistance = distance; 496 bestMatchDistanceInfo = fieldDifference; 497 } 498 if ( distance == 0 ) { 499 bestMatchDistanceInfo = 0; 500 break; 501 } 502 } 503 if ( replaceZWithV && bestMatchDistanceInfo != -1 ) { 504 bestMatchDistanceInfo = 2; 505 } 506 return bestSkeleton; 507 } 508 509 510 511 DateIntervalInfo::IntervalPatternIndex 512 DateIntervalInfo::calendarFieldToIntervalIndex(UCalendarDateFields field, 513 UErrorCode& status) { 514 if ( U_FAILURE(status) ) { 515 return kIPI_MAX_INDEX; 516 } 517 IntervalPatternIndex index = kIPI_MAX_INDEX; 518 switch ( field ) { 519 case UCAL_ERA: 520 index = kIPI_ERA; 521 break; 522 case UCAL_YEAR: 523 index = kIPI_YEAR; 524 break; 525 case UCAL_MONTH: 526 index = kIPI_MONTH; 527 break; 528 case UCAL_DATE: 529 case UCAL_DAY_OF_WEEK: 530 //case UCAL_DAY_OF_MONTH: 531 index = kIPI_DATE; 532 break; 533 case UCAL_AM_PM: 534 index = kIPI_AM_PM; 535 break; 536 case UCAL_HOUR: 537 case UCAL_HOUR_OF_DAY: 538 index = kIPI_HOUR; 539 break; 540 case UCAL_MINUTE: 541 index = kIPI_MINUTE; 542 break; 543 default: 544 status = U_ILLEGAL_ARGUMENT_ERROR; 545 } 546 return index; 547 } 548 549 550 551 void 552 DateIntervalInfo::deleteHash(Hashtable* hTable) 553 { 554 if ( hTable == NULL ) { 555 return; 556 } 557 int32_t pos = -1; 558 const UHashElement* element = NULL; 559 while ( (element = hTable->nextElement(pos)) != NULL ) { 560 const UHashTok keyTok = element->key; 561 const UHashTok valueTok = element->value; 562 const UnicodeString* value = (UnicodeString*)valueTok.pointer; 563 delete[] value; 564 } 565 delete fIntervalPatterns; 566 } 567 568 569 U_CDECL_BEGIN 570 571 /** 572 * set hash table value comparator 573 * 574 * @param val1 one value in comparison 575 * @param val2 the other value in comparison 576 * @return TRUE if 2 values are the same, FALSE otherwise 577 */ 578 UBool U_CALLCONV dtitvinfHashTableValueComparator(UHashTok val1, UHashTok val2); 579 580 U_CDECL_END 581 582 UBool 583 U_CALLCONV dtitvinfHashTableValueComparator(UHashTok val1, UHashTok val2) { 584 const UnicodeString* pattern1 = (UnicodeString*)val1.pointer; 585 const UnicodeString* pattern2 = (UnicodeString*)val2.pointer; 586 UBool ret = TRUE; 587 int8_t i; 588 for ( i = 0; i < DateIntervalInfo::kIPI_MAX_INDEX && ret == TRUE; ++i ) { 589 ret = (pattern1[i] == pattern2[i]); 590 } 591 return ret; 592 } 593 594 595 596 Hashtable* 597 DateIntervalInfo::initHash(UErrorCode& status) { 598 if ( U_FAILURE(status) ) { 599 return NULL; 600 } 601 Hashtable* hTable; 602 if ( (hTable = new Hashtable(TRUE, status)) == NULL ) { 603 status = U_MEMORY_ALLOCATION_ERROR; 604 return NULL; 605 } 606 hTable->setValueCompartor(dtitvinfHashTableValueComparator); 607 return hTable; 608 } 609 610 611 void 612 DateIntervalInfo::copyHash(const Hashtable* source, 613 Hashtable* target, 614 UErrorCode& status) { 615 if ( U_FAILURE(status) ) { 616 return; 617 } 618 int32_t pos = -1; 619 const UHashElement* element = NULL; 620 if ( source ) { 621 while ( (element = source->nextElement(pos)) != NULL ) { 622 const UHashTok keyTok = element->key; 623 const UnicodeString* key = (UnicodeString*)keyTok.pointer; 624 const UHashTok valueTok = element->value; 625 const UnicodeString* value = (UnicodeString*)valueTok.pointer; 626 UnicodeString* copy = new UnicodeString[kIPI_MAX_INDEX]; 627 int8_t i; 628 for ( i = 0; i < kIPI_MAX_INDEX; ++i ) { 629 copy[i] = value[i]; 630 } 631 target->put(UnicodeString(*key), copy, status); 632 if ( U_FAILURE(status) ) { 633 return; 634 } 635 } 636 } 637 } 638 639 640 U_NAMESPACE_END 641 642 #endif 643