/* ******************************************************************************* * Copyright (C) 2007-2013, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* */ #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING #include #include "reldtfmt.h" #include "unicode/datefmt.h" #include "unicode/smpdtfmt.h" #include "unicode/msgfmt.h" #include "unicode/udisplaycontext.h" #include "unicode/uchar.h" #include "gregoimp.h" // for CalendarData #include "cmemory.h" #include "uresimp.h" U_NAMESPACE_BEGIN /** * An array of URelativeString structs is used to store the resource data loaded out of the bundle. */ struct URelativeString { int32_t offset; /** offset of this item, such as, the relative date **/ int32_t len; /** length of the string **/ const UChar* string; /** string, or NULL if not set **/ }; static const char DT_DateTimePatternsTag[]="DateTimePatterns"; UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat) RelativeDateFormat::RelativeDateFormat(const RelativeDateFormat& other) : DateFormat(other), fDateTimeFormatter(NULL), fDatePattern(other.fDatePattern), fTimePattern(other.fTimePattern), fCombinedFormat(NULL), fDateStyle(other.fDateStyle), fLocale(other.fLocale), fDayMin(other.fDayMin), fDayMax(other.fDayMax), fDatesLen(other.fDatesLen), fDates(NULL), fCapitalizationContext(other.fCapitalizationContext), fCombinedHasDateAtStart(other.fCombinedHasDateAtStart) { if(other.fDateTimeFormatter != NULL) { fDateTimeFormatter = (SimpleDateFormat*)other.fDateTimeFormatter->clone(); } if(other.fCombinedFormat != NULL) { fCombinedFormat = (MessageFormat*)other.fCombinedFormat->clone(); } if (fDatesLen > 0) { fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen); uprv_memcpy(fDates, other.fDates, sizeof(fDates[0])*fDatesLen); } fCapitalizationForRelativeUnits[0] = other.fCapitalizationForRelativeUnits[0]; fCapitalizationForRelativeUnits[1] = other.fCapitalizationForRelativeUnits[1]; } RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const Locale& locale, UErrorCode& status) : DateFormat(), fDateTimeFormatter(NULL), fDatePattern(), fTimePattern(), fCombinedFormat(NULL), fDateStyle(dateStyle), fLocale(locale), fDayMin(0), fDayMax(0), fDatesLen(0), fDates(NULL), fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE), fCombinedHasDateAtStart(FALSE) { if(U_FAILURE(status) ) { return; } if (timeStyle < UDAT_NONE || timeStyle > UDAT_SHORT) { // don't support other time styles (e.g. relative styles), for now status = U_ILLEGAL_ARGUMENT_ERROR; return; } UDateFormatStyle baseDateStyle = (dateStyle > UDAT_SHORT)? (UDateFormatStyle)(dateStyle & ~UDAT_RELATIVE): dateStyle; DateFormat * df; // Get fDateTimeFormatter from either date or time style (does not matter, we will override the pattern). // We do need to get separate patterns for the date & time styles. if (baseDateStyle != UDAT_NONE) { df = createDateInstance((EStyle)baseDateStyle, locale); fDateTimeFormatter=dynamic_cast(df); if (fDateTimeFormatter == NULL) { status = U_UNSUPPORTED_ERROR; return; } fDateTimeFormatter->toPattern(fDatePattern); if (timeStyle != UDAT_NONE) { df = createTimeInstance((EStyle)timeStyle, locale); SimpleDateFormat *sdf = dynamic_cast(df); if (sdf != NULL) { sdf->toPattern(fTimePattern); delete sdf; } } } else { // does not matter whether timeStyle is UDAT_NONE, we need something for fDateTimeFormatter df = createTimeInstance((EStyle)timeStyle, locale); fDateTimeFormatter=dynamic_cast(df); if (fDateTimeFormatter == NULL) { status = U_UNSUPPORTED_ERROR; return; } fDateTimeFormatter->toPattern(fTimePattern); } // Initialize the parent fCalendar, so that parse() works correctly. initializeCalendar(NULL, locale, status); loadDates(status); } RelativeDateFormat::~RelativeDateFormat() { delete fDateTimeFormatter; delete fCombinedFormat; uprv_free(fDates); } Format* RelativeDateFormat::clone(void) const { return new RelativeDateFormat(*this); } UBool RelativeDateFormat::operator==(const Format& other) const { if(DateFormat::operator==(other)) { // DateFormat::operator== guarantees following cast is safe RelativeDateFormat* that = (RelativeDateFormat*)&other; return (fDateStyle==that->fDateStyle && fDatePattern==that->fDatePattern && fTimePattern==that->fTimePattern && fLocale==that->fLocale && fCapitalizationContext==that->fCapitalizationContext); } return FALSE; } static const UChar APOSTROPHE = (UChar)0x0027; UnicodeString& RelativeDateFormat::format( Calendar& cal, UnicodeString& appendTo, FieldPosition& pos) const { UErrorCode status = U_ZERO_ERROR; UnicodeString relativeDayString; // calculate the difference, in days, between 'cal' and now. int dayDiff = dayDifference(cal, status); // look up string int32_t len = 0; const UChar *theString = getStringForDay(dayDiff, len, status); if(U_SUCCESS(status) && (theString!=NULL)) { // found a relative string relativeDayString.setTo(theString, len); } if ( relativeDayString.length() > 0 && !fDatePattern.isEmpty() && (fTimePattern.isEmpty() || fCombinedFormat == NULL || fCombinedHasDateAtStart)) { // capitalize relativeDayString according to context for tense, set formatter no context if ( fCapitalizationContext==UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE || (fCapitalizationContext==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU && fCapitalizationForRelativeUnits[0]) || (fCapitalizationContext==UDISPCTX_CAPITALIZATION_FOR_STANDALONE && fCapitalizationForRelativeUnits[1]) ) { // titlecase first word of relativeDayString, do like LocaleDisplayNamesImpl::adjustForUsageAndContext int32_t stopPos, stopPosLimit = 8; if ( stopPosLimit > len ) { stopPosLimit = len; } for ( stopPos = 0; stopPos < stopPosLimit; stopPos++ ) { UChar32 ch = relativeDayString.char32At(stopPos); int32_t wb = u_getIntPropertyValue(ch, UCHAR_WORD_BREAK); if (!(u_islower(ch) || wb==U_WB_EXTEND || wb==U_WB_SINGLE_QUOTE || wb==U_WB_MIDNUMLET || wb==U_WB_MIDLETTER)) { break; } if (ch >= 0x10000) { stopPos++; } } if ( stopPos > 0 && stopPos < len ) { UnicodeString firstWord(relativeDayString, 0, stopPos); firstWord.toTitle(NULL, fLocale, U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT); relativeDayString.replaceBetween(0, stopPos, firstWord); } else { // no stopPos, titlecase the whole text relativeDayString.toTitle(NULL, fLocale, U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT); } } fDateTimeFormatter->setContext(UDISPCTX_CAPITALIZATION_NONE, status); } else { // set our context for the formatter fDateTimeFormatter->setContext(fCapitalizationContext, status); } if (fDatePattern.isEmpty()) { fDateTimeFormatter->applyPattern(fTimePattern); fDateTimeFormatter->format(cal,appendTo,pos); } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) { if (relativeDayString.length() > 0) { appendTo.append(relativeDayString); } else { fDateTimeFormatter->applyPattern(fDatePattern); fDateTimeFormatter->format(cal,appendTo,pos); } } else { UnicodeString datePattern; if (relativeDayString.length() > 0) { // Need to quote the relativeDayString to make it a legal date pattern relativeDayString.findAndReplace(UNICODE_STRING("'", 1), UNICODE_STRING("''", 2)); // double any existing APOSTROPHE relativeDayString.insert(0, APOSTROPHE); // add APOSTROPHE at beginning... relativeDayString.append(APOSTROPHE); // and at end datePattern.setTo(relativeDayString); } else { datePattern.setTo(fDatePattern); } UnicodeString combinedPattern; Formattable timeDatePatterns[] = { fTimePattern, datePattern }; fCombinedFormat->format(timeDatePatterns, 2, combinedPattern, pos, status); // pos is ignored by this fDateTimeFormatter->applyPattern(combinedPattern); fDateTimeFormatter->format(cal,appendTo,pos); } return appendTo; } UnicodeString& RelativeDateFormat::format(const Formattable& obj, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const { // this is just here to get around the hiding problem // (the previous format() override would hide the version of // format() on DateFormat that this function correspond to, so we // have to redefine it here) return DateFormat::format(obj, appendTo, pos, status); } void RelativeDateFormat::parse( const UnicodeString& text, Calendar& cal, ParsePosition& pos) const { int32_t startIndex = pos.getIndex(); if (fDatePattern.isEmpty()) { // no date pattern, try parsing as time fDateTimeFormatter->applyPattern(fTimePattern); fDateTimeFormatter->parse(text,cal,pos); } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) { // no time pattern or way to combine, try parsing as date // first check whether text matches a relativeDayString UBool matchedRelative = FALSE; for (int n=0; n < fDatesLen && !matchedRelative; n++) { if (fDates[n].string != NULL && text.compare(startIndex, fDates[n].len, fDates[n].string) == 0) { // it matched, handle the relative day string UErrorCode status = U_ZERO_ERROR; matchedRelative = TRUE; // Set the calendar to now+offset cal.setTime(Calendar::getNow(),status); cal.add(UCAL_DATE,fDates[n].offset, status); if(U_FAILURE(status)) { // failure in setting calendar field, set offset to beginning of rel day string pos.setErrorIndex(startIndex); } else { pos.setIndex(startIndex + fDates[n].len); } } } if (!matchedRelative) { // just parse as normal date fDateTimeFormatter->applyPattern(fDatePattern); fDateTimeFormatter->parse(text,cal,pos); } } else { // Here we replace any relativeDayString in text with the equivalent date // formatted per fDatePattern, then parse text normally using the combined pattern. UnicodeString modifiedText(text); FieldPosition fPos; int32_t dateStart = 0, origDateLen = 0, modDateLen = 0; UErrorCode status = U_ZERO_ERROR; for (int n=0; n < fDatesLen; n++) { int32_t relativeStringOffset; if (fDates[n].string != NULL && (relativeStringOffset = modifiedText.indexOf(fDates[n].string, fDates[n].len, startIndex)) >= startIndex) { // it matched, replace the relative date with a real one for parsing UnicodeString dateString; Calendar * tempCal = cal.clone(); // Set the calendar to now+offset tempCal->setTime(Calendar::getNow(),status); tempCal->add(UCAL_DATE,fDates[n].offset, status); if(U_FAILURE(status)) { pos.setErrorIndex(startIndex); delete tempCal; return; } fDateTimeFormatter->applyPattern(fDatePattern); fDateTimeFormatter->format(*tempCal, dateString, fPos); dateStart = relativeStringOffset; origDateLen = fDates[n].len; modDateLen = dateString.length(); modifiedText.replace(dateStart, origDateLen, dateString); delete tempCal; break; } } UnicodeString combinedPattern; Formattable timeDatePatterns[] = { fTimePattern, fDatePattern }; fCombinedFormat->format(timeDatePatterns, 2, combinedPattern, fPos, status); // pos is ignored by this fDateTimeFormatter->applyPattern(combinedPattern); fDateTimeFormatter->parse(modifiedText,cal,pos); // Adjust offsets UBool noError = (pos.getErrorIndex() < 0); int32_t offset = (noError)? pos.getIndex(): pos.getErrorIndex(); if (offset >= dateStart + modDateLen) { // offset at or after the end of the replaced text, // correct by the difference between original and replacement offset -= (modDateLen - origDateLen); } else if (offset >= dateStart) { // offset in the replaced text, set it to the beginning of that text // (i.e. the beginning of the relative day string) offset = dateStart; } if (noError) { pos.setIndex(offset); } else { pos.setErrorIndex(offset); } } } UDate RelativeDateFormat::parse( const UnicodeString& text, ParsePosition& pos) const { // redefined here because the other parse() function hides this function's // cunterpart on DateFormat return DateFormat::parse(text, pos); } UDate RelativeDateFormat::parse(const UnicodeString& text, UErrorCode& status) const { // redefined here because the other parse() function hides this function's // counterpart on DateFormat return DateFormat::parse(text, status); } const UChar *RelativeDateFormat::getStringForDay(int32_t day, int32_t &len, UErrorCode &status) const { if(U_FAILURE(status)) { return NULL; } // Is it outside the resource bundle's range? if(day < fDayMin || day > fDayMax) { return NULL; // don't have it. } // Linear search the held strings for(int n=0;nformat(timeDatePatterns, 2, result, pos, status); } } return result; } UnicodeString& RelativeDateFormat::toPatternDate(UnicodeString& result, UErrorCode& status) const { if (!U_FAILURE(status)) { result.remove(); result.setTo(fDatePattern); } return result; } UnicodeString& RelativeDateFormat::toPatternTime(UnicodeString& result, UErrorCode& status) const { if (!U_FAILURE(status)) { result.remove(); result.setTo(fTimePattern); } return result; } void RelativeDateFormat::applyPatterns(const UnicodeString& datePattern, const UnicodeString& timePattern, UErrorCode &status) { if (!U_FAILURE(status)) { fDatePattern.setTo(datePattern); fTimePattern.setTo(timePattern); } } const DateFormatSymbols* RelativeDateFormat::getDateFormatSymbols() const { return fDateTimeFormatter->getDateFormatSymbols(); } static const UChar patItem1[] = {0x7B,0x31,0x7D}; // "{1}" static const int32_t patItem1Len = 3; void RelativeDateFormat::loadDates(UErrorCode &status) { CalendarData calData(fLocale, "gregorian", status); UErrorCode tempStatus = status; UResourceBundle *dateTimePatterns = calData.getByKey(DT_DateTimePatternsTag, tempStatus); if(U_SUCCESS(tempStatus)) { int32_t patternsSize = ures_getSize(dateTimePatterns); if (patternsSize > kDateTime) { int32_t resStrLen = 0; int32_t glueIndex = kDateTime; if (patternsSize >= (DateFormat::kDateTimeOffset + DateFormat::kShort + 1)) { // Get proper date time format switch (fDateStyle) { case kFullRelative: case kFull: glueIndex = kDateTimeOffset + kFull; break; case kLongRelative: case kLong: glueIndex = kDateTimeOffset + kLong; break; case kMediumRelative: case kMedium: glueIndex = kDateTimeOffset + kMedium; break; case kShortRelative: case kShort: glueIndex = kDateTimeOffset + kShort; break; default: break; } } const UChar *resStr = ures_getStringByIndex(dateTimePatterns, glueIndex, &resStrLen, &tempStatus); if (U_SUCCESS(tempStatus) && resStrLen >= patItem1Len && u_strncmp(resStr,patItem1,patItem1Len)==0) { fCombinedHasDateAtStart = TRUE; } fCombinedFormat = new MessageFormat(UnicodeString(TRUE, resStr, resStrLen), fLocale, tempStatus); } } fCapitalizationForRelativeUnits[0] = fCapitalizationForRelativeUnits[1] = FALSE; UResourceBundle *lb = ures_open(NULL, fLocale.getBaseName(), &status); tempStatus = status; UResourceBundle *rb = ures_getByKeyWithFallback(lb, "contextTransforms", NULL, &tempStatus); UResourceBundle *sb = ures_getByKeyWithFallback(rb, "tense", NULL, &tempStatus); if (U_SUCCESS(tempStatus) && sb != NULL) { int32_t len = 0; const int32_t * intVector = ures_getIntVector(sb, &len, &tempStatus); if (U_SUCCESS(tempStatus) && intVector != NULL && len >= 2) { fCapitalizationForRelativeUnits[0] = intVector[0]; fCapitalizationForRelativeUnits[1] = intVector[1]; } } sb = ures_getByKeyWithFallback(lb, "fields", sb, &status); rb = ures_getByKeyWithFallback(sb, "day", rb, &status); sb = ures_getByKeyWithFallback(rb, "relative", sb, &status); ures_close(rb); ures_close(lb); // set up min/max fDayMin=-1; fDayMax=1; if(U_FAILURE(status)) { fDatesLen=0; ures_close(sb); return; } fDatesLen = ures_getSize(sb); fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen); // Load in each item into the array... int n = 0; UResourceBundle *subString = NULL; while(ures_hasNext(sb) && U_SUCCESS(status)) { // iterate over items subString = ures_getNextResource(sb, subString, &status); if(U_FAILURE(status) || (subString==NULL)) break; // key = offset # const char *key = ures_getKey(subString); // load the string and length int32_t aLen; const UChar* aString = ures_getString(subString, &aLen, &status); if(U_FAILURE(status) || aString == NULL) break; // calculate the offset int32_t offset = atoi(key); // set min/max if(offset < fDayMin) { fDayMin = offset; } if(offset > fDayMax) { fDayMax = offset; } // copy the string pointer fDates[n].offset = offset; fDates[n].string = aString; fDates[n].len = aLen; n++; } ures_close(subString); ures_close(sb); // the fDates[] array could be sorted here, for direct access. } //---------------------------------------------------------------------- void RelativeDateFormat::setContext(UDisplayContext value, UErrorCode& status) { if (U_FAILURE(status)) return; if ( (UDisplayContextType)((uint32_t)value >> 8) == UDISPCTX_TYPE_CAPITALIZATION ) { fCapitalizationContext = value; } else { status = U_ILLEGAL_ARGUMENT_ERROR; } } //---------------------------------------------------------------------- UDisplayContext RelativeDateFormat::getContext(UDisplayContextType type, UErrorCode& status) const { if (U_FAILURE(status)) return (UDisplayContext)0; if (type != UDISPCTX_TYPE_CAPITALIZATION) { status = U_ILLEGAL_ARGUMENT_ERROR; return (UDisplayContext)0; } return fCapitalizationContext; } //---------------------------------------------------------------------- // this should to be in DateFormat, instead it was copied from SimpleDateFormat. Calendar* RelativeDateFormat::initializeCalendar(TimeZone* adoptZone, const Locale& locale, UErrorCode& status) { if(!U_FAILURE(status)) { fCalendar = Calendar::createInstance(adoptZone?adoptZone:TimeZone::createDefault(), locale, status); } if (U_SUCCESS(status) && fCalendar == NULL) { status = U_MEMORY_ALLOCATION_ERROR; } return fCalendar; } int32_t RelativeDateFormat::dayDifference(Calendar &cal, UErrorCode &status) { if(U_FAILURE(status)) { return 0; } // TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type Calendar *nowCal = cal.clone(); nowCal->setTime(Calendar::getNow(), status); // For the day difference, we are interested in the difference in the (modified) julian day number // which is midnight to midnight. Using fieldDifference() is NOT correct here, because // 6pm Jan 4th to 10am Jan 5th should be considered "tomorrow". int32_t dayDiff = cal.get(UCAL_JULIAN_DAY, status) - nowCal->get(UCAL_JULIAN_DAY, status); delete nowCal; return dayDiff; } U_NAMESPACE_END #endif