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 23U_NAMESPACE_BEGIN 24 25 26/** 27 * An array of URelativeString structs is used to store the resource data loaded out of the bundle. 28 */ 29struct 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 35static const char DT_DateTimePatternsTag[]="DateTimePatterns"; 36 37 38UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat) 39 40RelativeDateFormat::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 59RelativeDateFormat::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 109RelativeDateFormat::~RelativeDateFormat() { 110 delete fDateTimeFormatter; 111 delete fCombinedFormat; 112 uprv_free(fDates); 113} 114 115 116Format* RelativeDateFormat::clone(void) const { 117 return new RelativeDateFormat(*this); 118} 119 120UBool 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 132static const UChar APOSTROPHE = (UChar)0x0027; 133 134UnicodeString& 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 185UnicodeString& 186RelativeDateFormat::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 199void 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 296UDate 297RelativeDateFormat::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 304UDate 305RelativeDateFormat::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 313const 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 334UnicodeString& 335RelativeDateFormat::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 352UnicodeString& 353RelativeDateFormat::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 362UnicodeString& 363RelativeDateFormat::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 372void 373RelativeDateFormat::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 381const DateFormatSymbols* 382RelativeDateFormat::getDateFormatSymbols() const 383{ 384 return fDateTimeFormatter->getDateFormatSymbols(); 385} 386 387void 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 491Calendar* 492RelativeDateFormat::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 503int32_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 520U_NAMESPACE_END 521 522#endif 523 524