/******************************************************************************* * Copyright (C) 2008-2012, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* * * File DTITVINF.CPP * ******************************************************************************* */ #include "unicode/dtitvinf.h" #if !UCONFIG_NO_FORMATTING //TODO: define it in compiler time //#define DTITVINF_DEBUG 1 #ifdef DTITVINF_DEBUG #include <iostream> #endif #include "cstring.h" #include "unicode/msgfmt.h" #include "unicode/uloc.h" #include "unicode/ures.h" #include "dtitv_impl.h" #include "hash.h" #include "gregoimp.h" #include "uresimp.h" #include "hash.h" #include "gregoimp.h" #include "uresimp.h" U_NAMESPACE_BEGIN #ifdef DTITVINF_DEBUG #define PRINTMESG(msg) { std::cout << "(" << __FILE__ << ":" << __LINE__ << ") " << msg << "\n"; } #endif UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalInfo) static const char gCalendarTag[]="calendar"; static const char gGregorianTag[]="gregorian"; static const char gIntervalDateTimePatternTag[]="intervalFormats"; static const char gFallbackPatternTag[]="fallback"; // {0} static const UChar gFirstPattern[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET}; // {1} static const UChar gSecondPattern[] = {LEFT_CURLY_BRACKET, DIGIT_ONE, RIGHT_CURLY_BRACKET}; // default fall-back 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}; DateIntervalInfo::DateIntervalInfo(UErrorCode& status) : fFallbackIntervalPattern(gDefaultFallbackPattern), fFirstDateInPtnIsLaterDate(false), fIntervalPatterns(NULL) { fIntervalPatterns = initHash(status); } DateIntervalInfo::DateIntervalInfo(const Locale& locale, UErrorCode& status) : fFallbackIntervalPattern(gDefaultFallbackPattern), fFirstDateInPtnIsLaterDate(false), fIntervalPatterns(NULL) { initializeData(locale, status); } void DateIntervalInfo::setIntervalPattern(const UnicodeString& skeleton, UCalendarDateFields lrgDiffCalUnit, const UnicodeString& intervalPattern, UErrorCode& status) { if ( lrgDiffCalUnit == UCAL_HOUR_OF_DAY ) { setIntervalPatternInternally(skeleton, UCAL_AM_PM, intervalPattern, status); setIntervalPatternInternally(skeleton, UCAL_HOUR, intervalPattern, status); } else if ( lrgDiffCalUnit == UCAL_DAY_OF_MONTH || lrgDiffCalUnit == UCAL_DAY_OF_WEEK ) { setIntervalPatternInternally(skeleton, UCAL_DATE, intervalPattern, status); } else { setIntervalPatternInternally(skeleton, lrgDiffCalUnit, intervalPattern, status); } } void DateIntervalInfo::setFallbackIntervalPattern( const UnicodeString& fallbackPattern, UErrorCode& status) { if ( U_FAILURE(status) ) { return; } int32_t firstPatternIndex = fallbackPattern.indexOf(gFirstPattern, sizeof(gFirstPattern)/sizeof(gFirstPattern[0]), 0); int32_t secondPatternIndex = fallbackPattern.indexOf(gSecondPattern, sizeof(gSecondPattern)/sizeof(gSecondPattern[0]), 0); if ( firstPatternIndex == -1 || secondPatternIndex == -1 ) { status = U_ILLEGAL_ARGUMENT_ERROR; return; } if ( firstPatternIndex > secondPatternIndex ) { fFirstDateInPtnIsLaterDate = true; } fFallbackIntervalPattern = fallbackPattern; } DateIntervalInfo::DateIntervalInfo(const DateIntervalInfo& dtitvinf) : UObject(dtitvinf), fIntervalPatterns(NULL) { *this = dtitvinf; } DateIntervalInfo& DateIntervalInfo::operator=(const DateIntervalInfo& dtitvinf) { if ( this == &dtitvinf ) { return *this; } UErrorCode status = U_ZERO_ERROR; deleteHash(fIntervalPatterns); fIntervalPatterns = initHash(status); copyHash(dtitvinf.fIntervalPatterns, fIntervalPatterns, status); if ( U_FAILURE(status) ) { return *this; } fFallbackIntervalPattern = dtitvinf.fFallbackIntervalPattern; fFirstDateInPtnIsLaterDate = dtitvinf.fFirstDateInPtnIsLaterDate; return *this; } DateIntervalInfo* DateIntervalInfo::clone() const { return new DateIntervalInfo(*this); } DateIntervalInfo::~DateIntervalInfo() { deleteHash(fIntervalPatterns); fIntervalPatterns = NULL; } UBool DateIntervalInfo::operator==(const DateIntervalInfo& other) const { UBool equal = ( fFallbackIntervalPattern == other.fFallbackIntervalPattern && fFirstDateInPtnIsLaterDate == other.fFirstDateInPtnIsLaterDate ); if ( equal == TRUE ) { equal = fIntervalPatterns->equals(*(other.fIntervalPatterns)); } return equal; } UnicodeString& DateIntervalInfo::getIntervalPattern(const UnicodeString& skeleton, UCalendarDateFields field, UnicodeString& result, UErrorCode& status) const { if ( U_FAILURE(status) ) { return result; } const UnicodeString* patternsOfOneSkeleton = (UnicodeString*) fIntervalPatterns->get(skeleton); if ( patternsOfOneSkeleton != NULL ) { IntervalPatternIndex index = calendarFieldToIntervalIndex(field, status); if ( U_FAILURE(status) ) { return result; } const UnicodeString& intervalPattern = patternsOfOneSkeleton[index]; if ( !intervalPattern.isEmpty() ) { result = intervalPattern; } } return result; } UBool DateIntervalInfo::getDefaultOrder() const { return fFirstDateInPtnIsLaterDate; } UnicodeString& DateIntervalInfo::getFallbackIntervalPattern(UnicodeString& result) const { result = fFallbackIntervalPattern; return result; } #define ULOC_LOCALE_IDENTIFIER_CAPACITY (ULOC_FULLNAME_CAPACITY + 1 + ULOC_KEYWORD_AND_VALUES_CAPACITY) void DateIntervalInfo::initializeData(const Locale& locale, UErrorCode& err) { fIntervalPatterns = initHash(err); if ( U_FAILURE(err) ) { return; } const char *locName = locale.getName(); char parentLocale[ULOC_FULLNAME_CAPACITY]; uprv_strcpy(parentLocale, locName); UErrorCode status = U_ZERO_ERROR; Hashtable skeletonSet(FALSE, status); if ( U_FAILURE(status) ) { return; } // determine calendar type const char * calendarTypeToUse = gGregorianTag; // initial default char calendarType[ULOC_KEYWORDS_CAPACITY]; // to be filled in with the type to use, if all goes well char localeWithCalendarKey[ULOC_LOCALE_IDENTIFIER_CAPACITY]; // obtain a locale that always has the calendar key value that should be used (void)ures_getFunctionalEquivalent(localeWithCalendarKey, ULOC_LOCALE_IDENTIFIER_CAPACITY, NULL, "calendar", "calendar", locName, NULL, FALSE, &status); localeWithCalendarKey[ULOC_LOCALE_IDENTIFIER_CAPACITY-1] = 0; // ensure null termination // now get the calendar key value from that locale int32_t calendarTypeLen = uloc_getKeywordValue(localeWithCalendarKey, "calendar", calendarType, ULOC_KEYWORDS_CAPACITY, &status); if (U_SUCCESS(status) && calendarTypeLen < ULOC_KEYWORDS_CAPACITY) { calendarTypeToUse = calendarType; } status = U_ZERO_ERROR; do { UResourceBundle *rb, *calBundle, *calTypeBundle, *itvDtPtnResource; rb = ures_open(NULL, parentLocale, &status); if ( U_FAILURE(status) ) { break; } calBundle = ures_getByKey(rb, gCalendarTag, NULL, &status); calTypeBundle = ures_getByKey(calBundle, calendarTypeToUse, NULL, &status); itvDtPtnResource = ures_getByKeyWithFallback(calTypeBundle, gIntervalDateTimePatternTag, NULL, &status); if ( U_SUCCESS(status) ) { // look for fallback first, since it establishes the default order const UChar* resStr; int32_t resStrLen = 0; resStr = ures_getStringByKeyWithFallback(itvDtPtnResource, gFallbackPatternTag, &resStrLen, &status); if ( U_SUCCESS(status) ) { UnicodeString pattern = UnicodeString(TRUE, resStr, resStrLen); setFallbackIntervalPattern(pattern, status); } int32_t size = ures_getSize(itvDtPtnResource); int32_t index; for ( index = 0; index < size; ++index ) { LocalUResourceBundlePointer oneRes(ures_getByIndex(itvDtPtnResource, index, NULL, &status)); if ( U_SUCCESS(status) ) { const char* skeleton = ures_getKey(oneRes.getAlias()); if (skeleton == NULL) { continue; } UnicodeString skeletonUniStr(skeleton, -1, US_INV); if ( skeletonSet.geti(skeletonUniStr) == 1 ) { continue; } skeletonSet.puti(skeletonUniStr, 1, status); if ( uprv_strcmp(skeleton, gFallbackPatternTag) == 0 ) { continue; // fallback } LocalUResourceBundlePointer intervalPatterns(ures_getByKey( itvDtPtnResource, skeleton, NULL, &status)); if ( U_FAILURE(status) ) { break; } if ( intervalPatterns == NULL ) { continue; } const char* key; int32_t ptnNum = ures_getSize(intervalPatterns.getAlias()); int32_t ptnIndex; for ( ptnIndex = 0; ptnIndex < ptnNum; ++ptnIndex ) { UnicodeString pattern = ures_getNextUnicodeString(intervalPatterns.getAlias(), &key, &status); if ( U_FAILURE(status) ) { break; } UCalendarDateFields calendarField = UCAL_FIELD_COUNT; if ( !uprv_strcmp(key, "y") ) { calendarField = UCAL_YEAR; } else if ( !uprv_strcmp(key, "M") ) { calendarField = UCAL_MONTH; } else if ( !uprv_strcmp(key, "d") ) { calendarField = UCAL_DATE; } else if ( !uprv_strcmp(key, "a") ) { calendarField = UCAL_AM_PM; } else if ( !uprv_strcmp(key, "h") || !uprv_strcmp(key, "H") ) { calendarField = UCAL_HOUR; } else if ( !uprv_strcmp(key, "m") ) { calendarField = UCAL_MINUTE; } if ( calendarField != UCAL_FIELD_COUNT ) { setIntervalPatternInternally(skeletonUniStr, calendarField, pattern,status); } } } } } ures_close(itvDtPtnResource); ures_close(calTypeBundle); ures_close(calBundle); status = U_ZERO_ERROR; // Find the name of the appropriate parent locale (from %%Parent if present, else // uloc_getParent on the actual locale name) // (It would be nice to have a ures function that did this...) int32_t locNameLen; const UChar * parentUName = ures_getStringByKey(rb, "%%Parent", &locNameLen, &status); if (U_SUCCESS(status) && status != U_USING_FALLBACK_WARNING && locNameLen < ULOC_FULLNAME_CAPACITY) { u_UCharsToChars(parentUName, parentLocale, locNameLen + 1); } else { status = U_ZERO_ERROR; // Get the actual name of the current locale being used const char *curLocaleName=ures_getLocaleByType(rb, ULOC_ACTUAL_LOCALE, &status); if ( U_FAILURE(status) ) { curLocaleName = parentLocale; status = U_ZERO_ERROR; } uloc_getParent(curLocaleName, parentLocale, ULOC_FULLNAME_CAPACITY, &status); if (U_FAILURE(err) || err == U_STRING_NOT_TERMINATED_WARNING) { parentLocale[0] = 0; // just fallback to root, will cause us to stop status = U_ZERO_ERROR; } } // Now we can close the current locale bundle ures_close(rb); // If the new current locale is root, then stop // (unlike for DateTimePatternGenerator, DateIntervalFormat does not go all the way up // to root to find additional data for non-root locales) } while ( parentLocale[0] != 0 && uprv_strcmp(parentLocale,"root")!=0 ); } void DateIntervalInfo::setIntervalPatternInternally(const UnicodeString& skeleton, UCalendarDateFields lrgDiffCalUnit, const UnicodeString& intervalPattern, UErrorCode& status) { IntervalPatternIndex index = calendarFieldToIntervalIndex(lrgDiffCalUnit,status); if ( U_FAILURE(status) ) { return; } UnicodeString* patternsOfOneSkeleton = (UnicodeString*)(fIntervalPatterns->get(skeleton)); UBool emptyHash = false; if ( patternsOfOneSkeleton == NULL ) { patternsOfOneSkeleton = new UnicodeString[kIPI_MAX_INDEX]; emptyHash = true; } patternsOfOneSkeleton[index] = intervalPattern; if ( emptyHash == TRUE ) { fIntervalPatterns->put(skeleton, patternsOfOneSkeleton, status); } } void DateIntervalInfo::parseSkeleton(const UnicodeString& skeleton, int32_t* skeletonFieldWidth) { const int8_t PATTERN_CHAR_BASE = 0x41; int32_t i; for ( i = 0; i < skeleton.length(); ++i ) { // it is an ASCII char in skeleton int8_t ch = (int8_t)skeleton.charAt(i); ++skeletonFieldWidth[ch - PATTERN_CHAR_BASE]; } } UBool DateIntervalInfo::stringNumeric(int32_t fieldWidth, int32_t anotherFieldWidth, char patternLetter) { if ( patternLetter == 'M' ) { if ( (fieldWidth <= 2 && anotherFieldWidth > 2) || (fieldWidth > 2 && anotherFieldWidth <= 2 )) { return true; } } return false; } const UnicodeString* DateIntervalInfo::getBestSkeleton(const UnicodeString& skeleton, int8_t& bestMatchDistanceInfo) const { #ifdef DTITVINF_DEBUG char result[1000]; char result_1[1000]; char mesg[2000]; skeleton.extract(0, skeleton.length(), result, "UTF-8"); sprintf(mesg, "in getBestSkeleton: skeleton: %s; \n", result); PRINTMESG(mesg) #endif int32_t inputSkeletonFieldWidth[] = { // A B C D E F G H I J K L M N O 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // P Q R S T U V W X Y Z 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // a b c d e f g h i j k l m n o 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // p q r s t u v w x y z 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int32_t skeletonFieldWidth[] = { // A B C D E F G H I J K L M N O 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // P Q R S T U V W X Y Z 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // a b c d e f g h i j k l m n o 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // p q r s t u v w x y z 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const int32_t DIFFERENT_FIELD = 0x1000; const int32_t STRING_NUMERIC_DIFFERENCE = 0x100; const int32_t BASE = 0x41; const UChar CHAR_V = 0x0076; const UChar CHAR_Z = 0x007A; // hack for 'v' and 'z'. // resource bundle only have time skeletons ending with 'v', // but not for time skeletons ending with 'z'. UBool replaceZWithV = false; const UnicodeString* inputSkeleton = &skeleton; UnicodeString copySkeleton; if ( skeleton.indexOf(CHAR_Z) != -1 ) { copySkeleton = skeleton; copySkeleton.findAndReplace(UnicodeString(CHAR_Z), UnicodeString(CHAR_V)); inputSkeleton = ©Skeleton; replaceZWithV = true; } parseSkeleton(*inputSkeleton, inputSkeletonFieldWidth); int32_t bestDistance = MAX_POSITIVE_INT; const UnicodeString* bestSkeleton = NULL; // 0 means exact the same skeletons; // 1 means having the same field, but with different length, // 2 means only z/v differs // -1 means having different field. bestMatchDistanceInfo = 0; int8_t fieldLength = sizeof(skeletonFieldWidth)/sizeof(skeletonFieldWidth[0]); int32_t pos = -1; const UHashElement* elem = NULL; while ( (elem = fIntervalPatterns->nextElement(pos)) != NULL ) { const UHashTok keyTok = elem->key; UnicodeString* skeleton = (UnicodeString*)keyTok.pointer; #ifdef DTITVINF_DEBUG skeleton->extract(0, skeleton->length(), result, "UTF-8"); sprintf(mesg, "available skeletons: skeleton: %s; \n", result); PRINTMESG(mesg) #endif // clear skeleton field width int8_t i; for ( i = 0; i < fieldLength; ++i ) { skeletonFieldWidth[i] = 0; } parseSkeleton(*skeleton, skeletonFieldWidth); // calculate distance int32_t distance = 0; int8_t fieldDifference = 1; for ( i = 0; i < fieldLength; ++i ) { int32_t inputFieldWidth = inputSkeletonFieldWidth[i]; int32_t fieldWidth = skeletonFieldWidth[i]; if ( inputFieldWidth == fieldWidth ) { continue; } if ( inputFieldWidth == 0 ) { fieldDifference = -1; distance += DIFFERENT_FIELD; } else if ( fieldWidth == 0 ) { fieldDifference = -1; distance += DIFFERENT_FIELD; } else if (stringNumeric(inputFieldWidth, fieldWidth, (char)(i+BASE) ) ) { distance += STRING_NUMERIC_DIFFERENCE; } else { distance += (inputFieldWidth > fieldWidth) ? (inputFieldWidth - fieldWidth) : (fieldWidth - inputFieldWidth); } } if ( distance < bestDistance ) { bestSkeleton = skeleton; bestDistance = distance; bestMatchDistanceInfo = fieldDifference; } if ( distance == 0 ) { bestMatchDistanceInfo = 0; break; } } if ( replaceZWithV && bestMatchDistanceInfo != -1 ) { bestMatchDistanceInfo = 2; } return bestSkeleton; } DateIntervalInfo::IntervalPatternIndex DateIntervalInfo::calendarFieldToIntervalIndex(UCalendarDateFields field, UErrorCode& status) { if ( U_FAILURE(status) ) { return kIPI_MAX_INDEX; } IntervalPatternIndex index = kIPI_MAX_INDEX; switch ( field ) { case UCAL_ERA: index = kIPI_ERA; break; case UCAL_YEAR: index = kIPI_YEAR; break; case UCAL_MONTH: index = kIPI_MONTH; break; case UCAL_DATE: case UCAL_DAY_OF_WEEK: //case UCAL_DAY_OF_MONTH: index = kIPI_DATE; break; case UCAL_AM_PM: index = kIPI_AM_PM; break; case UCAL_HOUR: case UCAL_HOUR_OF_DAY: index = kIPI_HOUR; break; case UCAL_MINUTE: index = kIPI_MINUTE; break; default: status = U_ILLEGAL_ARGUMENT_ERROR; } return index; } void DateIntervalInfo::deleteHash(Hashtable* hTable) { if ( hTable == NULL ) { return; } int32_t pos = -1; const UHashElement* element = NULL; while ( (element = hTable->nextElement(pos)) != NULL ) { const UHashTok valueTok = element->value; const UnicodeString* value = (UnicodeString*)valueTok.pointer; delete[] value; } delete fIntervalPatterns; } U_CDECL_BEGIN /** * set hash table value comparator * * @param val1 one value in comparison * @param val2 the other value in comparison * @return TRUE if 2 values are the same, FALSE otherwise */ static UBool U_CALLCONV dtitvinfHashTableValueComparator(UHashTok val1, UHashTok val2); static UBool U_CALLCONV dtitvinfHashTableValueComparator(UHashTok val1, UHashTok val2) { const UnicodeString* pattern1 = (UnicodeString*)val1.pointer; const UnicodeString* pattern2 = (UnicodeString*)val2.pointer; UBool ret = TRUE; int8_t i; for ( i = 0; i < DateIntervalInfo::kMaxIntervalPatternIndex && ret == TRUE; ++i ) { ret = (pattern1[i] == pattern2[i]); } return ret; } U_CDECL_END Hashtable* DateIntervalInfo::initHash(UErrorCode& status) { if ( U_FAILURE(status) ) { return NULL; } Hashtable* hTable; if ( (hTable = new Hashtable(FALSE, status)) == NULL ) { status = U_MEMORY_ALLOCATION_ERROR; return NULL; } if ( U_FAILURE(status) ) { delete hTable; return NULL; } hTable->setValueComparator(dtitvinfHashTableValueComparator); return hTable; } void DateIntervalInfo::copyHash(const Hashtable* source, Hashtable* target, UErrorCode& status) { if ( U_FAILURE(status) ) { return; } int32_t pos = -1; const UHashElement* element = NULL; if ( source ) { while ( (element = source->nextElement(pos)) != NULL ) { const UHashTok keyTok = element->key; const UnicodeString* key = (UnicodeString*)keyTok.pointer; const UHashTok valueTok = element->value; const UnicodeString* value = (UnicodeString*)valueTok.pointer; UnicodeString* copy = new UnicodeString[kIPI_MAX_INDEX]; int8_t i; for ( i = 0; i < kIPI_MAX_INDEX; ++i ) { copy[i] = value[i]; } target->put(UnicodeString(*key), copy, status); if ( U_FAILURE(status) ) { return; } } } } U_NAMESPACE_END #endif