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 24U_NAMESPACE_BEGIN 25 26 27/** 28 * An array of URelativeString structs is used to store the resource data loaded out of the bundle. 29 */ 30struct 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 36static const char DT_DateTimePatternsTag[]="DateTimePatterns"; 37 38 39UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat) 40 41RelativeDateFormat::RelativeDateFormat(const RelativeDateFormat& other) : 42DateFormat(other), fDateFormat(NULL), fTimeFormat(NULL), fCombinedFormat(NULL), 43fDateStyle(other.fDateStyle), fTimeStyle(other.fTimeStyle), fLocale(other.fLocale), 44fDayMin(other.fDayMin), fDayMax(other.fDayMax), 45fDatesLen(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 66RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const Locale& locale, UErrorCode& status) 67 : DateFormat(), fDateFormat(NULL), fTimeFormat(NULL), fCombinedFormat(NULL), 68fDateStyle(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 92RelativeDateFormat::~RelativeDateFormat() { 93 delete fDateFormat; 94 delete fTimeFormat; 95 delete fCombinedFormat; 96 uprv_free(fDates); 97} 98 99 100Format* RelativeDateFormat::clone(void) const { 101 return new RelativeDateFormat(*this); 102} 103 104UBool 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 115UnicodeString& 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 166UnicodeString& 167RelativeDateFormat::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 180void 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 220UDate 221RelativeDateFormat::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 228UDate 229RelativeDateFormat::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 237const 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 258UnicodeString& 259RelativeDateFormat::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 285UnicodeString& 286RelativeDateFormat::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 302UnicodeString& 303RelativeDateFormat::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 319void 320RelativeDateFormat::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 342void 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 440Calendar* 441RelativeDateFormat::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 452int32_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 469U_NAMESPACE_END 470 471#endif 472 473