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