Home | History | Annotate | Download | only in i18n
      1 /*
      2 *******************************************************************************
      3 * Copyright (C) 2007-2009, 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;
    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             if ( fDateFormat->getDynamicClassID()==SimpleDateFormat::getStaticClassID() ) {
    292                 ((SimpleDateFormat*)fDateFormat)->toPattern(result);
    293             } else {
    294                 status = U_UNSUPPORTED_ERROR;
    295             }
    296         }
    297     }
    298     return result;
    299 }
    300 
    301 UnicodeString&
    302 RelativeDateFormat::toPatternTime(UnicodeString& result, UErrorCode& status) const
    303 {
    304     if (!U_FAILURE(status)) {
    305         result.remove();
    306         if ( fTimeFormat ) {
    307             if ( fTimeFormat->getDynamicClassID()==SimpleDateFormat::getStaticClassID() ) {
    308                 ((SimpleDateFormat*)fTimeFormat)->toPattern(result);
    309             } else {
    310                 status = U_UNSUPPORTED_ERROR;
    311             }
    312         }
    313     }
    314     return result;
    315 }
    316 
    317 void
    318 RelativeDateFormat::applyPatterns(const UnicodeString& datePattern, const UnicodeString& timePattern, UErrorCode &status)
    319 {
    320     if (!U_FAILURE(status)) {
    321         if ( fDateFormat && fDateFormat->getDynamicClassID()!=SimpleDateFormat::getStaticClassID() ) {
    322             status = U_UNSUPPORTED_ERROR;
    323             return;
    324         }
    325         if ( fTimeFormat && fTimeFormat->getDynamicClassID()!=SimpleDateFormat::getStaticClassID() ) {
    326             status = U_UNSUPPORTED_ERROR;
    327             return;
    328         }
    329         if ( fDateFormat ) {
    330             ((SimpleDateFormat*)fDateFormat)->applyPattern(datePattern);
    331         }
    332         if ( fTimeFormat ) {
    333             ((SimpleDateFormat*)fTimeFormat)->applyPattern(timePattern);
    334         }
    335     }
    336 }
    337 
    338 void RelativeDateFormat::loadDates(UErrorCode &status) {
    339     CalendarData calData(fLocale, "gregorian", status);
    340 
    341     UErrorCode tempStatus = status;
    342     UResourceBundle *dateTimePatterns = calData.getByKey(DT_DateTimePatternsTag, tempStatus);
    343     if(U_SUCCESS(tempStatus)) {
    344         int32_t patternsSize = ures_getSize(dateTimePatterns);
    345         if (patternsSize > kDateTime) {
    346             int32_t resStrLen = 0;
    347 
    348             int32_t glueIndex = kDateTime;
    349             if (patternsSize >= (DateFormat::kDateTimeOffset + DateFormat::kShort + 1)) {
    350                 // Get proper date time format
    351                 switch (fDateStyle) {
    352  	            case kFullRelative:
    353  	            case kFull:
    354  	                glueIndex = kDateTimeOffset + kFull;
    355  	                break;
    356  	            case kLongRelative:
    357  	            case kLong:
    358  	                glueIndex = kDateTimeOffset + kLong;
    359  	                break;
    360  	            case kMediumRelative:
    361  	            case kMedium:
    362  	                glueIndex = kDateTimeOffset + kMedium;
    363  	                break;
    364  	            case kShortRelative:
    365  	            case kShort:
    366  	                glueIndex = kDateTimeOffset + kShort;
    367  	                break;
    368  	            default:
    369  	                break;
    370  	            }
    371             }
    372 
    373             const UChar *resStr = ures_getStringByIndex(dateTimePatterns, glueIndex, &resStrLen, &tempStatus);
    374             fCombinedFormat = new MessageFormat(UnicodeString(TRUE, resStr, resStrLen), fLocale, tempStatus);
    375         }
    376     }
    377 
    378     UResourceBundle *strings = calData.getByKey3("fields", "day", "relative", status);
    379     // set up min/max
    380     fDayMin=-1;
    381     fDayMax=1;
    382 
    383     if(U_FAILURE(status)) {
    384         fDatesLen=0;
    385         return;
    386     }
    387 
    388     fDatesLen = ures_getSize(strings);
    389     fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen);
    390 
    391     // Load in each item into the array...
    392     int n = 0;
    393 
    394     UResourceBundle *subString = NULL;
    395 
    396     while(ures_hasNext(strings) && U_SUCCESS(status)) {  // iterate over items
    397         subString = ures_getNextResource(strings, subString, &status);
    398 
    399         if(U_FAILURE(status) || (subString==NULL)) break;
    400 
    401         // key = offset #
    402         const char *key = ures_getKey(subString);
    403 
    404         // load the string and length
    405         int32_t aLen;
    406         const UChar* aString = ures_getString(subString, &aLen, &status);
    407 
    408         if(U_FAILURE(status) || aString == NULL) break;
    409 
    410         // calculate the offset
    411         int32_t offset = atoi(key);
    412 
    413         // set min/max
    414         if(offset < fDayMin) {
    415             fDayMin = offset;
    416         }
    417         if(offset > fDayMax) {
    418             fDayMax = offset;
    419         }
    420 
    421         // copy the string pointer
    422         fDates[n].offset = offset;
    423         fDates[n].string = aString;
    424         fDates[n].len = aLen;
    425 
    426         n++;
    427     }
    428     ures_close(subString);
    429 
    430     // the fDates[] array could be sorted here, for direct access.
    431 }
    432 
    433 
    434 // this should to be in DateFormat, instead it was copied from SimpleDateFormat.
    435 
    436 Calendar*
    437 RelativeDateFormat::initializeCalendar(TimeZone* adoptZone, const Locale& locale, UErrorCode& status)
    438 {
    439     if(!U_FAILURE(status)) {
    440         fCalendar = Calendar::createInstance(adoptZone?adoptZone:TimeZone::createDefault(), locale, status);
    441     }
    442     if (U_SUCCESS(status) && fCalendar == NULL) {
    443         status = U_MEMORY_ALLOCATION_ERROR;
    444     }
    445     return fCalendar;
    446 }
    447 
    448 int32_t RelativeDateFormat::dayDifference(Calendar &cal, UErrorCode &status) {
    449     if(U_FAILURE(status)) {
    450         return 0;
    451     }
    452     // TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type
    453     Calendar *nowCal = cal.clone();
    454     nowCal->setTime(Calendar::getNow(), status);
    455 
    456     // For the day difference, we are interested in the difference in the (modified) julian day number
    457     // which is midnight to midnight.  Using fieldDifference() is NOT correct here, because
    458     // 6pm Jan 4th  to 10am Jan 5th should be considered "tomorrow".
    459     int32_t dayDiff = cal.get(UCAL_JULIAN_DAY, status) - nowCal->get(UCAL_JULIAN_DAY, status);
    460 
    461     delete nowCal;
    462     return dayDiff;
    463 }
    464 
    465 U_NAMESPACE_END
    466 
    467 #endif
    468 
    469