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