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