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