Home | History | Annotate | Download | only in i18n
      1 /*
      2 *******************************************************************************
      3 * Copyright (C) 2007-2012, International Business Machines Corporation and
      4 * others. All Rights Reserved.
      5 *******************************************************************************
      6 */
      7 
      8 #include "unicode/utypes.h"
      9 
     10 #if !UCONFIG_NO_FORMATTING
     11 
     12 #include <stdlib.h>
     13 
     14 #include "reldtfmt.h"
     15 #include "unicode/datefmt.h"
     16 #include "unicode/smpdtfmt.h"
     17 #include "unicode/msgfmt.h"
     18 
     19 #include "gregoimp.h" // for CalendarData
     20 #include "cmemory.h"
     21 
     22 U_NAMESPACE_BEGIN
     23 
     24 
     25 /**
     26  * An array of URelativeString structs is used to store the resource data loaded out of the bundle.
     27  */
     28 struct URelativeString {
     29     int32_t offset;         /** offset of this item, such as, the relative date **/
     30     int32_t len;            /** length of the string **/
     31     const UChar* string;    /** string, or NULL if not set **/
     32 };
     33 
     34 static const char DT_DateTimePatternsTag[]="DateTimePatterns";
     35 
     36 
     37 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat)
     38 
     39 RelativeDateFormat::RelativeDateFormat(const RelativeDateFormat& other) :
     40  DateFormat(other), fDateTimeFormatter(NULL), fDatePattern(other.fDatePattern),
     41  fTimePattern(other.fTimePattern), fCombinedFormat(NULL),
     42  fDateStyle(other.fDateStyle), fLocale(other.fLocale),
     43  fDayMin(other.fDayMin), fDayMax(other.fDayMax),
     44  fDatesLen(other.fDatesLen), fDates(NULL)
     45 {
     46     if(other.fDateTimeFormatter != NULL) {
     47         fDateTimeFormatter = (SimpleDateFormat*)other.fDateTimeFormatter->clone();
     48     }
     49     if(other.fCombinedFormat != NULL) {
     50         fCombinedFormat = (MessageFormat*)other.fCombinedFormat->clone();
     51     }
     52     if (fDatesLen > 0) {
     53         fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen);
     54         uprv_memcpy(fDates, other.fDates, sizeof(fDates[0])*fDatesLen);
     55     }
     56 }
     57 
     58 RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle, UDateFormatStyle dateStyle,
     59                                         const Locale& locale, UErrorCode& status) :
     60  DateFormat(), fDateTimeFormatter(NULL), fDatePattern(), fTimePattern(), fCombinedFormat(NULL),
     61  fDateStyle(dateStyle), fLocale(locale), fDatesLen(0), fDates(NULL)
     62 {
     63     if(U_FAILURE(status) ) {
     64         return;
     65     }
     66 
     67     if (timeStyle < UDAT_NONE || timeStyle > UDAT_SHORT) {
     68         // don't support other time styles (e.g. relative styles), for now
     69         status = U_ILLEGAL_ARGUMENT_ERROR;
     70         return;
     71     }
     72     UDateFormatStyle baseDateStyle = (dateStyle > UDAT_SHORT)? (UDateFormatStyle)(dateStyle & ~UDAT_RELATIVE): dateStyle;
     73     DateFormat * df;
     74     // Get fDateTimeFormatter from either date or time style (does not matter, we will override the pattern).
     75     // We do need to get separate patterns for the date & time styles.
     76     if (baseDateStyle != UDAT_NONE) {
     77         df = createDateInstance((EStyle)baseDateStyle, locale);
     78         fDateTimeFormatter=dynamic_cast<SimpleDateFormat *>(df);
     79         if (fDateTimeFormatter == NULL) {
     80             status = U_UNSUPPORTED_ERROR;
     81              return;
     82         }
     83         fDateTimeFormatter->toPattern(fDatePattern);
     84         if (timeStyle != UDAT_NONE) {
     85             df = createTimeInstance((EStyle)timeStyle, locale);
     86             SimpleDateFormat *sdf = dynamic_cast<SimpleDateFormat *>(df);
     87             if (sdf != NULL) {
     88                 sdf->toPattern(fTimePattern);
     89                 delete sdf;
     90             }
     91         }
     92     } else {
     93         // does not matter whether timeStyle is UDAT_NONE, we need something for fDateTimeFormatter
     94         df = createTimeInstance((EStyle)timeStyle, locale);
     95         fDateTimeFormatter=dynamic_cast<SimpleDateFormat *>(df);
     96         if (fDateTimeFormatter == NULL) {
     97             status = U_UNSUPPORTED_ERROR;
     98             return;
     99         }
    100         fDateTimeFormatter->toPattern(fTimePattern);
    101     }
    102 
    103     // Initialize the parent fCalendar, so that parse() works correctly.
    104     initializeCalendar(NULL, locale, status);
    105     loadDates(status);
    106 }
    107 
    108 RelativeDateFormat::~RelativeDateFormat() {
    109     delete fDateTimeFormatter;
    110     delete fCombinedFormat;
    111     uprv_free(fDates);
    112 }
    113 
    114 
    115 Format* RelativeDateFormat::clone(void) const {
    116     return new RelativeDateFormat(*this);
    117 }
    118 
    119 UBool RelativeDateFormat::operator==(const Format& other) const {
    120     if(DateFormat::operator==(other)) {
    121         // DateFormat::operator== guarantees following cast is safe
    122         RelativeDateFormat* that = (RelativeDateFormat*)&other;
    123         return (fDateStyle==that->fDateStyle   &&
    124                 fDatePattern==that->fDatePattern   &&
    125                 fTimePattern==that->fTimePattern   &&
    126                 fLocale==that->fLocale);
    127     }
    128     return FALSE;
    129 }
    130 
    131 static const UChar APOSTROPHE = (UChar)0x0027;
    132 
    133 UnicodeString& RelativeDateFormat::format(  Calendar& cal,
    134                                 UnicodeString& appendTo,
    135                                 FieldPosition& pos) const {
    136 
    137     UErrorCode status = U_ZERO_ERROR;
    138     UnicodeString relativeDayString;
    139 
    140     // calculate the difference, in days, between 'cal' and now.
    141     int dayDiff = dayDifference(cal, status);
    142 
    143     // look up string
    144     int32_t len = 0;
    145     const UChar *theString = getStringForDay(dayDiff, len, status);
    146     if(U_SUCCESS(status) && (theString!=NULL)) {
    147         // found a relative string
    148         relativeDayString.setTo(theString, len);
    149     }
    150 
    151     if (fDatePattern.isEmpty()) {
    152         fDateTimeFormatter->applyPattern(fTimePattern);
    153         fDateTimeFormatter->format(cal,appendTo,pos);
    154     } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) {
    155         if (relativeDayString.length() > 0) {
    156             appendTo.append(relativeDayString);
    157         } else {
    158             fDateTimeFormatter->applyPattern(fDatePattern);
    159             fDateTimeFormatter->format(cal,appendTo,pos);
    160         }
    161     } else {
    162         UnicodeString datePattern;
    163         if (relativeDayString.length() > 0) {
    164             // Need to quote the relativeDayString to make it a legal date pattern
    165             relativeDayString.findAndReplace(UNICODE_STRING("'", 1), UNICODE_STRING("''", 2) ); // double any existing APOSTROPHE
    166             relativeDayString.insert(0, APOSTROPHE); // add APOSTROPHE at beginning...
    167             relativeDayString.append(APOSTROPHE); // and at end
    168             datePattern.setTo(relativeDayString);
    169         } else {
    170             datePattern.setTo(fDatePattern);
    171         }
    172         UnicodeString combinedPattern;
    173         Formattable timeDatePatterns[] = { fTimePattern, datePattern };
    174         fCombinedFormat->format(timeDatePatterns, 2, combinedPattern, pos, status); // pos is ignored by this
    175         fDateTimeFormatter->applyPattern(combinedPattern);
    176         fDateTimeFormatter->format(cal,appendTo,pos);
    177     }
    178 
    179     return appendTo;
    180 }
    181 
    182 
    183 
    184 UnicodeString&
    185 RelativeDateFormat::format(const Formattable& obj,
    186                          UnicodeString& appendTo,
    187                          FieldPosition& pos,
    188                          UErrorCode& status) const
    189 {
    190     // this is just here to get around the hiding problem
    191     // (the previous format() override would hide the version of
    192     // format() on DateFormat that this function correspond to, so we
    193     // have to redefine it here)
    194     return DateFormat::format(obj, appendTo, pos, status);
    195 }
    196 
    197 
    198 void RelativeDateFormat::parse( const UnicodeString& text,
    199                     Calendar& cal,
    200                     ParsePosition& pos) const {
    201 
    202     int32_t startIndex = pos.getIndex();
    203     if (fDatePattern.isEmpty()) {
    204         // no date pattern, try parsing as time
    205         fDateTimeFormatter->applyPattern(fTimePattern);
    206         fDateTimeFormatter->parse(text,cal,pos);
    207     } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) {
    208         // no time pattern or way to combine, try parsing as date
    209         // first check whether text matches a relativeDayString
    210         UBool matchedRelative = FALSE;
    211         for (int n=0; n < fDatesLen && !matchedRelative; n++) {
    212             if (fDates[n].string != NULL &&
    213                     text.compare(startIndex, fDates[n].len, fDates[n].string) == 0) {
    214                 // it matched, handle the relative day string
    215                 UErrorCode status = U_ZERO_ERROR;
    216                 matchedRelative = TRUE;
    217 
    218                 // Set the calendar to now+offset
    219                 cal.setTime(Calendar::getNow(),status);
    220                 cal.add(UCAL_DATE,fDates[n].offset, status);
    221 
    222                 if(U_FAILURE(status)) {
    223                     // failure in setting calendar field, set offset to beginning of rel day string
    224                     pos.setErrorIndex(startIndex);
    225                 } else {
    226                     pos.setIndex(startIndex + fDates[n].len);
    227                 }
    228             }
    229         }
    230         if (!matchedRelative) {
    231             // just parse as normal date
    232             fDateTimeFormatter->applyPattern(fDatePattern);
    233             fDateTimeFormatter->parse(text,cal,pos);
    234         }
    235     } else {
    236         // Here we replace any relativeDayString in text with the equivalent date
    237         // formatted per fDatePattern, then parse text normally using the combined pattern.
    238         UnicodeString modifiedText(text);
    239         FieldPosition fPos;
    240         int32_t dateStart = 0, origDateLen = 0, modDateLen = 0;
    241         UErrorCode status = U_ZERO_ERROR;
    242         for (int n=0; n < fDatesLen; n++) {
    243             int32_t relativeStringOffset;
    244             if (fDates[n].string != NULL &&
    245                     (relativeStringOffset = modifiedText.indexOf(fDates[n].string, fDates[n].len, startIndex)) >= startIndex) {
    246                 // it matched, replace the relative date with a real one for parsing
    247                 UnicodeString dateString;
    248                 Calendar * tempCal = cal.clone();
    249 
    250                 // Set the calendar to now+offset
    251                 tempCal->setTime(Calendar::getNow(),status);
    252                 tempCal->add(UCAL_DATE,fDates[n].offset, status);
    253                 if(U_FAILURE(status)) {
    254                     pos.setErrorIndex(startIndex);
    255                     delete tempCal;
    256                     return;
    257                 }
    258 
    259                 fDateTimeFormatter->applyPattern(fDatePattern);
    260                 fDateTimeFormatter->format(*tempCal, dateString, fPos);
    261                 dateStart = relativeStringOffset;
    262                 origDateLen = fDates[n].len;
    263                 modDateLen = dateString.length();
    264                 modifiedText.replace(dateStart, origDateLen, dateString);
    265                 delete tempCal;
    266                 break;
    267             }
    268         }
    269         UnicodeString combinedPattern;
    270         Formattable timeDatePatterns[] = { fTimePattern, fDatePattern };
    271         fCombinedFormat->format(timeDatePatterns, 2, combinedPattern, fPos, status); // pos is ignored by this
    272         fDateTimeFormatter->applyPattern(combinedPattern);
    273         fDateTimeFormatter->parse(modifiedText,cal,pos);
    274 
    275         // Adjust offsets
    276         UBool noError = (pos.getErrorIndex() < 0);
    277         int32_t offset = (noError)? pos.getIndex(): pos.getErrorIndex();
    278         if (offset >= dateStart + modDateLen) {
    279             // offset at or after the end of the replaced text,
    280             // correct by the difference between original and replacement
    281             offset -= (modDateLen - origDateLen);
    282         } else if (offset >= dateStart) {
    283             // offset in the replaced text, set it to the beginning of that text
    284             // (i.e. the beginning of the relative day string)
    285             offset = dateStart;
    286         }
    287         if (noError) {
    288             pos.setIndex(offset);
    289         } else {
    290             pos.setErrorIndex(offset);
    291         }
    292     }
    293 }
    294 
    295 UDate
    296 RelativeDateFormat::parse( const UnicodeString& text,
    297                          ParsePosition& pos) const {
    298     // redefined here because the other parse() function hides this function's
    299     // cunterpart on DateFormat
    300     return DateFormat::parse(text, pos);
    301 }
    302 
    303 UDate
    304 RelativeDateFormat::parse(const UnicodeString& text, UErrorCode& status) const
    305 {
    306     // redefined here because the other parse() function hides this function's
    307     // counterpart on DateFormat
    308     return DateFormat::parse(text, status);
    309 }
    310 
    311 
    312 const UChar *RelativeDateFormat::getStringForDay(int32_t day, int32_t &len, UErrorCode &status) const {
    313     if(U_FAILURE(status)) {
    314         return NULL;
    315     }
    316 
    317     // Is it outside the resource bundle's range?
    318     if(day < fDayMin || day > fDayMax) {
    319         return NULL; // don't have it.
    320     }
    321 
    322     // Linear search the held strings
    323     for(int n=0;n<fDatesLen;n++) {
    324         if(fDates[n].offset == day) {
    325             len = fDates[n].len;
    326             return fDates[n].string;
    327         }
    328     }
    329 
    330     return NULL;  // not found.
    331 }
    332 
    333 UnicodeString&
    334 RelativeDateFormat::toPattern(UnicodeString& result, UErrorCode& status) const
    335 {
    336     if (!U_FAILURE(status)) {
    337         result.remove();
    338         if (fDatePattern.isEmpty()) {
    339             result.setTo(fTimePattern);
    340         } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) {
    341             result.setTo(fDatePattern);
    342         } else {
    343             Formattable timeDatePatterns[] = { fTimePattern, fDatePattern };
    344             FieldPosition pos;
    345             fCombinedFormat->format(timeDatePatterns, 2, result, pos, status);
    346         }
    347     }
    348     return result;
    349 }
    350 
    351 UnicodeString&
    352 RelativeDateFormat::toPatternDate(UnicodeString& result, UErrorCode& status) const
    353 {
    354     if (!U_FAILURE(status)) {
    355         result.remove();
    356         result.setTo(fDatePattern);
    357     }
    358     return result;
    359 }
    360 
    361 UnicodeString&
    362 RelativeDateFormat::toPatternTime(UnicodeString& result, UErrorCode& status) const
    363 {
    364     if (!U_FAILURE(status)) {
    365         result.remove();
    366         result.setTo(fTimePattern);
    367     }
    368     return result;
    369 }
    370 
    371 void
    372 RelativeDateFormat::applyPatterns(const UnicodeString& datePattern, const UnicodeString& timePattern, UErrorCode &status)
    373 {
    374     if (!U_FAILURE(status)) {
    375         fDatePattern.setTo(datePattern);
    376         fTimePattern.setTo(timePattern);
    377     }
    378 }
    379 
    380 const DateFormatSymbols*
    381 RelativeDateFormat::getDateFormatSymbols() const
    382 {
    383     return fDateTimeFormatter->getDateFormatSymbols();
    384 }
    385 
    386 void RelativeDateFormat::loadDates(UErrorCode &status) {
    387     CalendarData calData(fLocale, "gregorian", status);
    388 
    389     UErrorCode tempStatus = status;
    390     UResourceBundle *dateTimePatterns = calData.getByKey(DT_DateTimePatternsTag, tempStatus);
    391     if(U_SUCCESS(tempStatus)) {
    392         int32_t patternsSize = ures_getSize(dateTimePatterns);
    393         if (patternsSize > kDateTime) {
    394             int32_t resStrLen = 0;
    395 
    396             int32_t glueIndex = kDateTime;
    397             if (patternsSize >= (DateFormat::kDateTimeOffset + DateFormat::kShort + 1)) {
    398                 // Get proper date time format
    399                 switch (fDateStyle) {
    400                 case kFullRelative:
    401                 case kFull:
    402                     glueIndex = kDateTimeOffset + kFull;
    403                     break;
    404                 case kLongRelative:
    405                 case kLong:
    406                     glueIndex = kDateTimeOffset + kLong;
    407                     break;
    408                 case kMediumRelative:
    409                 case kMedium:
    410                     glueIndex = kDateTimeOffset + kMedium;
    411                     break;
    412                 case kShortRelative:
    413                 case kShort:
    414                     glueIndex = kDateTimeOffset + kShort;
    415                     break;
    416                 default:
    417                     break;
    418                 }
    419             }
    420 
    421             const UChar *resStr = ures_getStringByIndex(dateTimePatterns, glueIndex, &resStrLen, &tempStatus);
    422             fCombinedFormat = new MessageFormat(UnicodeString(TRUE, resStr, resStrLen), fLocale, tempStatus);
    423         }
    424     }
    425 
    426     UResourceBundle *strings = calData.getByKey3("fields", "day", "relative", status);
    427     // set up min/max
    428     fDayMin=-1;
    429     fDayMax=1;
    430 
    431     if(U_FAILURE(status)) {
    432         fDatesLen=0;
    433         return;
    434     }
    435 
    436     fDatesLen = ures_getSize(strings);
    437     fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen);
    438 
    439     // Load in each item into the array...
    440     int n = 0;
    441 
    442     UResourceBundle *subString = NULL;
    443 
    444     while(ures_hasNext(strings) && U_SUCCESS(status)) {  // iterate over items
    445         subString = ures_getNextResource(strings, subString, &status);
    446 
    447         if(U_FAILURE(status) || (subString==NULL)) break;
    448 
    449         // key = offset #
    450         const char *key = ures_getKey(subString);
    451 
    452         // load the string and length
    453         int32_t aLen;
    454         const UChar* aString = ures_getString(subString, &aLen, &status);
    455 
    456         if(U_FAILURE(status) || aString == NULL) break;
    457 
    458         // calculate the offset
    459         int32_t offset = atoi(key);
    460 
    461         // set min/max
    462         if(offset < fDayMin) {
    463             fDayMin = offset;
    464         }
    465         if(offset > fDayMax) {
    466             fDayMax = offset;
    467         }
    468 
    469         // copy the string pointer
    470         fDates[n].offset = offset;
    471         fDates[n].string = aString;
    472         fDates[n].len = aLen;
    473 
    474         n++;
    475     }
    476     ures_close(subString);
    477 
    478     // the fDates[] array could be sorted here, for direct access.
    479 }
    480 
    481 
    482 // this should to be in DateFormat, instead it was copied from SimpleDateFormat.
    483 
    484 Calendar*
    485 RelativeDateFormat::initializeCalendar(TimeZone* adoptZone, const Locale& locale, UErrorCode& status)
    486 {
    487     if(!U_FAILURE(status)) {
    488         fCalendar = Calendar::createInstance(adoptZone?adoptZone:TimeZone::createDefault(), locale, status);
    489     }
    490     if (U_SUCCESS(status) && fCalendar == NULL) {
    491         status = U_MEMORY_ALLOCATION_ERROR;
    492     }
    493     return fCalendar;
    494 }
    495 
    496 int32_t RelativeDateFormat::dayDifference(Calendar &cal, UErrorCode &status) {
    497     if(U_FAILURE(status)) {
    498         return 0;
    499     }
    500     // TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type
    501     Calendar *nowCal = cal.clone();
    502     nowCal->setTime(Calendar::getNow(), status);
    503 
    504     // For the day difference, we are interested in the difference in the (modified) julian day number
    505     // which is midnight to midnight.  Using fieldDifference() is NOT correct here, because
    506     // 6pm Jan 4th  to 10am Jan 5th should be considered "tomorrow".
    507     int32_t dayDiff = cal.get(UCAL_JULIAN_DAY, status) - nowCal->get(UCAL_JULIAN_DAY, status);
    508 
    509     delete nowCal;
    510     return dayDiff;
    511 }
    512 
    513 U_NAMESPACE_END
    514 
    515 #endif
    516 
    517