1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/intl/icu/source/i18n/reldtfmt.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,523 @@ 1.4 +/* 1.5 +******************************************************************************* 1.6 +* Copyright (C) 2007-2013, International Business Machines Corporation and 1.7 +* others. All Rights Reserved. 1.8 +******************************************************************************* 1.9 +*/ 1.10 + 1.11 +#include "unicode/utypes.h" 1.12 + 1.13 +#if !UCONFIG_NO_FORMATTING 1.14 + 1.15 +#include <stdlib.h> 1.16 + 1.17 +#include "reldtfmt.h" 1.18 +#include "unicode/datefmt.h" 1.19 +#include "unicode/smpdtfmt.h" 1.20 +#include "unicode/msgfmt.h" 1.21 + 1.22 +#include "gregoimp.h" // for CalendarData 1.23 +#include "cmemory.h" 1.24 +#include "uresimp.h" 1.25 + 1.26 +U_NAMESPACE_BEGIN 1.27 + 1.28 + 1.29 +/** 1.30 + * An array of URelativeString structs is used to store the resource data loaded out of the bundle. 1.31 + */ 1.32 +struct URelativeString { 1.33 + int32_t offset; /** offset of this item, such as, the relative date **/ 1.34 + int32_t len; /** length of the string **/ 1.35 + const UChar* string; /** string, or NULL if not set **/ 1.36 +}; 1.37 + 1.38 +static const char DT_DateTimePatternsTag[]="DateTimePatterns"; 1.39 + 1.40 + 1.41 +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat) 1.42 + 1.43 +RelativeDateFormat::RelativeDateFormat(const RelativeDateFormat& other) : 1.44 + DateFormat(other), fDateTimeFormatter(NULL), fDatePattern(other.fDatePattern), 1.45 + fTimePattern(other.fTimePattern), fCombinedFormat(NULL), 1.46 + fDateStyle(other.fDateStyle), fLocale(other.fLocale), 1.47 + fDayMin(other.fDayMin), fDayMax(other.fDayMax), 1.48 + fDatesLen(other.fDatesLen), fDates(NULL) 1.49 +{ 1.50 + if(other.fDateTimeFormatter != NULL) { 1.51 + fDateTimeFormatter = (SimpleDateFormat*)other.fDateTimeFormatter->clone(); 1.52 + } 1.53 + if(other.fCombinedFormat != NULL) { 1.54 + fCombinedFormat = (MessageFormat*)other.fCombinedFormat->clone(); 1.55 + } 1.56 + if (fDatesLen > 0) { 1.57 + fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen); 1.58 + uprv_memcpy(fDates, other.fDates, sizeof(fDates[0])*fDatesLen); 1.59 + } 1.60 +} 1.61 + 1.62 +RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, 1.63 + const Locale& locale, UErrorCode& status) : 1.64 + DateFormat(), fDateTimeFormatter(NULL), fDatePattern(), fTimePattern(), fCombinedFormat(NULL), 1.65 + fDateStyle(dateStyle), fLocale(locale), fDatesLen(0), fDates(NULL) 1.66 +{ 1.67 + if(U_FAILURE(status) ) { 1.68 + return; 1.69 + } 1.70 + 1.71 + if (timeStyle < UDAT_NONE || timeStyle > UDAT_SHORT) { 1.72 + // don't support other time styles (e.g. relative styles), for now 1.73 + status = U_ILLEGAL_ARGUMENT_ERROR; 1.74 + return; 1.75 + } 1.76 + UDateFormatStyle baseDateStyle = (dateStyle > UDAT_SHORT)? (UDateFormatStyle)(dateStyle & ~UDAT_RELATIVE): dateStyle; 1.77 + DateFormat * df; 1.78 + // Get fDateTimeFormatter from either date or time style (does not matter, we will override the pattern). 1.79 + // We do need to get separate patterns for the date & time styles. 1.80 + if (baseDateStyle != UDAT_NONE) { 1.81 + df = createDateInstance((EStyle)baseDateStyle, locale); 1.82 + fDateTimeFormatter=dynamic_cast<SimpleDateFormat *>(df); 1.83 + if (fDateTimeFormatter == NULL) { 1.84 + status = U_UNSUPPORTED_ERROR; 1.85 + return; 1.86 + } 1.87 + fDateTimeFormatter->toPattern(fDatePattern); 1.88 + if (timeStyle != UDAT_NONE) { 1.89 + df = createTimeInstance((EStyle)timeStyle, locale); 1.90 + SimpleDateFormat *sdf = dynamic_cast<SimpleDateFormat *>(df); 1.91 + if (sdf != NULL) { 1.92 + sdf->toPattern(fTimePattern); 1.93 + delete sdf; 1.94 + } 1.95 + } 1.96 + } else { 1.97 + // does not matter whether timeStyle is UDAT_NONE, we need something for fDateTimeFormatter 1.98 + df = createTimeInstance((EStyle)timeStyle, locale); 1.99 + fDateTimeFormatter=dynamic_cast<SimpleDateFormat *>(df); 1.100 + if (fDateTimeFormatter == NULL) { 1.101 + status = U_UNSUPPORTED_ERROR; 1.102 + return; 1.103 + } 1.104 + fDateTimeFormatter->toPattern(fTimePattern); 1.105 + } 1.106 + 1.107 + // Initialize the parent fCalendar, so that parse() works correctly. 1.108 + initializeCalendar(NULL, locale, status); 1.109 + loadDates(status); 1.110 +} 1.111 + 1.112 +RelativeDateFormat::~RelativeDateFormat() { 1.113 + delete fDateTimeFormatter; 1.114 + delete fCombinedFormat; 1.115 + uprv_free(fDates); 1.116 +} 1.117 + 1.118 + 1.119 +Format* RelativeDateFormat::clone(void) const { 1.120 + return new RelativeDateFormat(*this); 1.121 +} 1.122 + 1.123 +UBool RelativeDateFormat::operator==(const Format& other) const { 1.124 + if(DateFormat::operator==(other)) { 1.125 + // DateFormat::operator== guarantees following cast is safe 1.126 + RelativeDateFormat* that = (RelativeDateFormat*)&other; 1.127 + return (fDateStyle==that->fDateStyle && 1.128 + fDatePattern==that->fDatePattern && 1.129 + fTimePattern==that->fTimePattern && 1.130 + fLocale==that->fLocale); 1.131 + } 1.132 + return FALSE; 1.133 +} 1.134 + 1.135 +static const UChar APOSTROPHE = (UChar)0x0027; 1.136 + 1.137 +UnicodeString& RelativeDateFormat::format( Calendar& cal, 1.138 + UnicodeString& appendTo, 1.139 + FieldPosition& pos) const { 1.140 + 1.141 + UErrorCode status = U_ZERO_ERROR; 1.142 + UnicodeString relativeDayString; 1.143 + 1.144 + // calculate the difference, in days, between 'cal' and now. 1.145 + int dayDiff = dayDifference(cal, status); 1.146 + 1.147 + // look up string 1.148 + int32_t len = 0; 1.149 + const UChar *theString = getStringForDay(dayDiff, len, status); 1.150 + if(U_SUCCESS(status) && (theString!=NULL)) { 1.151 + // found a relative string 1.152 + relativeDayString.setTo(theString, len); 1.153 + } 1.154 + 1.155 + if (fDatePattern.isEmpty()) { 1.156 + fDateTimeFormatter->applyPattern(fTimePattern); 1.157 + fDateTimeFormatter->format(cal,appendTo,pos); 1.158 + } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) { 1.159 + if (relativeDayString.length() > 0) { 1.160 + appendTo.append(relativeDayString); 1.161 + } else { 1.162 + fDateTimeFormatter->applyPattern(fDatePattern); 1.163 + fDateTimeFormatter->format(cal,appendTo,pos); 1.164 + } 1.165 + } else { 1.166 + UnicodeString datePattern; 1.167 + if (relativeDayString.length() > 0) { 1.168 + // Need to quote the relativeDayString to make it a legal date pattern 1.169 + relativeDayString.findAndReplace(UNICODE_STRING("'", 1), UNICODE_STRING("''", 2)); // double any existing APOSTROPHE 1.170 + relativeDayString.insert(0, APOSTROPHE); // add APOSTROPHE at beginning... 1.171 + relativeDayString.append(APOSTROPHE); // and at end 1.172 + datePattern.setTo(relativeDayString); 1.173 + } else { 1.174 + datePattern.setTo(fDatePattern); 1.175 + } 1.176 + UnicodeString combinedPattern; 1.177 + Formattable timeDatePatterns[] = { fTimePattern, datePattern }; 1.178 + fCombinedFormat->format(timeDatePatterns, 2, combinedPattern, pos, status); // pos is ignored by this 1.179 + fDateTimeFormatter->applyPattern(combinedPattern); 1.180 + fDateTimeFormatter->format(cal,appendTo,pos); 1.181 + } 1.182 + 1.183 + return appendTo; 1.184 +} 1.185 + 1.186 + 1.187 + 1.188 +UnicodeString& 1.189 +RelativeDateFormat::format(const Formattable& obj, 1.190 + UnicodeString& appendTo, 1.191 + FieldPosition& pos, 1.192 + UErrorCode& status) const 1.193 +{ 1.194 + // this is just here to get around the hiding problem 1.195 + // (the previous format() override would hide the version of 1.196 + // format() on DateFormat that this function correspond to, so we 1.197 + // have to redefine it here) 1.198 + return DateFormat::format(obj, appendTo, pos, status); 1.199 +} 1.200 + 1.201 + 1.202 +void RelativeDateFormat::parse( const UnicodeString& text, 1.203 + Calendar& cal, 1.204 + ParsePosition& pos) const { 1.205 + 1.206 + int32_t startIndex = pos.getIndex(); 1.207 + if (fDatePattern.isEmpty()) { 1.208 + // no date pattern, try parsing as time 1.209 + fDateTimeFormatter->applyPattern(fTimePattern); 1.210 + fDateTimeFormatter->parse(text,cal,pos); 1.211 + } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) { 1.212 + // no time pattern or way to combine, try parsing as date 1.213 + // first check whether text matches a relativeDayString 1.214 + UBool matchedRelative = FALSE; 1.215 + for (int n=0; n < fDatesLen && !matchedRelative; n++) { 1.216 + if (fDates[n].string != NULL && 1.217 + text.compare(startIndex, fDates[n].len, fDates[n].string) == 0) { 1.218 + // it matched, handle the relative day string 1.219 + UErrorCode status = U_ZERO_ERROR; 1.220 + matchedRelative = TRUE; 1.221 + 1.222 + // Set the calendar to now+offset 1.223 + cal.setTime(Calendar::getNow(),status); 1.224 + cal.add(UCAL_DATE,fDates[n].offset, status); 1.225 + 1.226 + if(U_FAILURE(status)) { 1.227 + // failure in setting calendar field, set offset to beginning of rel day string 1.228 + pos.setErrorIndex(startIndex); 1.229 + } else { 1.230 + pos.setIndex(startIndex + fDates[n].len); 1.231 + } 1.232 + } 1.233 + } 1.234 + if (!matchedRelative) { 1.235 + // just parse as normal date 1.236 + fDateTimeFormatter->applyPattern(fDatePattern); 1.237 + fDateTimeFormatter->parse(text,cal,pos); 1.238 + } 1.239 + } else { 1.240 + // Here we replace any relativeDayString in text with the equivalent date 1.241 + // formatted per fDatePattern, then parse text normally using the combined pattern. 1.242 + UnicodeString modifiedText(text); 1.243 + FieldPosition fPos; 1.244 + int32_t dateStart = 0, origDateLen = 0, modDateLen = 0; 1.245 + UErrorCode status = U_ZERO_ERROR; 1.246 + for (int n=0; n < fDatesLen; n++) { 1.247 + int32_t relativeStringOffset; 1.248 + if (fDates[n].string != NULL && 1.249 + (relativeStringOffset = modifiedText.indexOf(fDates[n].string, fDates[n].len, startIndex)) >= startIndex) { 1.250 + // it matched, replace the relative date with a real one for parsing 1.251 + UnicodeString dateString; 1.252 + Calendar * tempCal = cal.clone(); 1.253 + 1.254 + // Set the calendar to now+offset 1.255 + tempCal->setTime(Calendar::getNow(),status); 1.256 + tempCal->add(UCAL_DATE,fDates[n].offset, status); 1.257 + if(U_FAILURE(status)) { 1.258 + pos.setErrorIndex(startIndex); 1.259 + delete tempCal; 1.260 + return; 1.261 + } 1.262 + 1.263 + fDateTimeFormatter->applyPattern(fDatePattern); 1.264 + fDateTimeFormatter->format(*tempCal, dateString, fPos); 1.265 + dateStart = relativeStringOffset; 1.266 + origDateLen = fDates[n].len; 1.267 + modDateLen = dateString.length(); 1.268 + modifiedText.replace(dateStart, origDateLen, dateString); 1.269 + delete tempCal; 1.270 + break; 1.271 + } 1.272 + } 1.273 + UnicodeString combinedPattern; 1.274 + Formattable timeDatePatterns[] = { fTimePattern, fDatePattern }; 1.275 + fCombinedFormat->format(timeDatePatterns, 2, combinedPattern, fPos, status); // pos is ignored by this 1.276 + fDateTimeFormatter->applyPattern(combinedPattern); 1.277 + fDateTimeFormatter->parse(modifiedText,cal,pos); 1.278 + 1.279 + // Adjust offsets 1.280 + UBool noError = (pos.getErrorIndex() < 0); 1.281 + int32_t offset = (noError)? pos.getIndex(): pos.getErrorIndex(); 1.282 + if (offset >= dateStart + modDateLen) { 1.283 + // offset at or after the end of the replaced text, 1.284 + // correct by the difference between original and replacement 1.285 + offset -= (modDateLen - origDateLen); 1.286 + } else if (offset >= dateStart) { 1.287 + // offset in the replaced text, set it to the beginning of that text 1.288 + // (i.e. the beginning of the relative day string) 1.289 + offset = dateStart; 1.290 + } 1.291 + if (noError) { 1.292 + pos.setIndex(offset); 1.293 + } else { 1.294 + pos.setErrorIndex(offset); 1.295 + } 1.296 + } 1.297 +} 1.298 + 1.299 +UDate 1.300 +RelativeDateFormat::parse( const UnicodeString& text, 1.301 + ParsePosition& pos) const { 1.302 + // redefined here because the other parse() function hides this function's 1.303 + // cunterpart on DateFormat 1.304 + return DateFormat::parse(text, pos); 1.305 +} 1.306 + 1.307 +UDate 1.308 +RelativeDateFormat::parse(const UnicodeString& text, UErrorCode& status) const 1.309 +{ 1.310 + // redefined here because the other parse() function hides this function's 1.311 + // counterpart on DateFormat 1.312 + return DateFormat::parse(text, status); 1.313 +} 1.314 + 1.315 + 1.316 +const UChar *RelativeDateFormat::getStringForDay(int32_t day, int32_t &len, UErrorCode &status) const { 1.317 + if(U_FAILURE(status)) { 1.318 + return NULL; 1.319 + } 1.320 + 1.321 + // Is it outside the resource bundle's range? 1.322 + if(day < fDayMin || day > fDayMax) { 1.323 + return NULL; // don't have it. 1.324 + } 1.325 + 1.326 + // Linear search the held strings 1.327 + for(int n=0;n<fDatesLen;n++) { 1.328 + if(fDates[n].offset == day) { 1.329 + len = fDates[n].len; 1.330 + return fDates[n].string; 1.331 + } 1.332 + } 1.333 + 1.334 + return NULL; // not found. 1.335 +} 1.336 + 1.337 +UnicodeString& 1.338 +RelativeDateFormat::toPattern(UnicodeString& result, UErrorCode& status) const 1.339 +{ 1.340 + if (!U_FAILURE(status)) { 1.341 + result.remove(); 1.342 + if (fDatePattern.isEmpty()) { 1.343 + result.setTo(fTimePattern); 1.344 + } else if (fTimePattern.isEmpty() || fCombinedFormat == NULL) { 1.345 + result.setTo(fDatePattern); 1.346 + } else { 1.347 + Formattable timeDatePatterns[] = { fTimePattern, fDatePattern }; 1.348 + FieldPosition pos; 1.349 + fCombinedFormat->format(timeDatePatterns, 2, result, pos, status); 1.350 + } 1.351 + } 1.352 + return result; 1.353 +} 1.354 + 1.355 +UnicodeString& 1.356 +RelativeDateFormat::toPatternDate(UnicodeString& result, UErrorCode& status) const 1.357 +{ 1.358 + if (!U_FAILURE(status)) { 1.359 + result.remove(); 1.360 + result.setTo(fDatePattern); 1.361 + } 1.362 + return result; 1.363 +} 1.364 + 1.365 +UnicodeString& 1.366 +RelativeDateFormat::toPatternTime(UnicodeString& result, UErrorCode& status) const 1.367 +{ 1.368 + if (!U_FAILURE(status)) { 1.369 + result.remove(); 1.370 + result.setTo(fTimePattern); 1.371 + } 1.372 + return result; 1.373 +} 1.374 + 1.375 +void 1.376 +RelativeDateFormat::applyPatterns(const UnicodeString& datePattern, const UnicodeString& timePattern, UErrorCode &status) 1.377 +{ 1.378 + if (!U_FAILURE(status)) { 1.379 + fDatePattern.setTo(datePattern); 1.380 + fTimePattern.setTo(timePattern); 1.381 + } 1.382 +} 1.383 + 1.384 +const DateFormatSymbols* 1.385 +RelativeDateFormat::getDateFormatSymbols() const 1.386 +{ 1.387 + return fDateTimeFormatter->getDateFormatSymbols(); 1.388 +} 1.389 + 1.390 +void RelativeDateFormat::loadDates(UErrorCode &status) { 1.391 + CalendarData calData(fLocale, "gregorian", status); 1.392 + 1.393 + UErrorCode tempStatus = status; 1.394 + UResourceBundle *dateTimePatterns = calData.getByKey(DT_DateTimePatternsTag, tempStatus); 1.395 + if(U_SUCCESS(tempStatus)) { 1.396 + int32_t patternsSize = ures_getSize(dateTimePatterns); 1.397 + if (patternsSize > kDateTime) { 1.398 + int32_t resStrLen = 0; 1.399 + 1.400 + int32_t glueIndex = kDateTime; 1.401 + if (patternsSize >= (DateFormat::kDateTimeOffset + DateFormat::kShort + 1)) { 1.402 + // Get proper date time format 1.403 + switch (fDateStyle) { 1.404 + case kFullRelative: 1.405 + case kFull: 1.406 + glueIndex = kDateTimeOffset + kFull; 1.407 + break; 1.408 + case kLongRelative: 1.409 + case kLong: 1.410 + glueIndex = kDateTimeOffset + kLong; 1.411 + break; 1.412 + case kMediumRelative: 1.413 + case kMedium: 1.414 + glueIndex = kDateTimeOffset + kMedium; 1.415 + break; 1.416 + case kShortRelative: 1.417 + case kShort: 1.418 + glueIndex = kDateTimeOffset + kShort; 1.419 + break; 1.420 + default: 1.421 + break; 1.422 + } 1.423 + } 1.424 + 1.425 + const UChar *resStr = ures_getStringByIndex(dateTimePatterns, glueIndex, &resStrLen, &tempStatus); 1.426 + fCombinedFormat = new MessageFormat(UnicodeString(TRUE, resStr, resStrLen), fLocale, tempStatus); 1.427 + } 1.428 + } 1.429 + 1.430 + UResourceBundle *rb = ures_open(NULL, fLocale.getBaseName(), &status); 1.431 + UResourceBundle *sb = ures_getByKeyWithFallback(rb, "fields", NULL, &status); 1.432 + rb = ures_getByKeyWithFallback(sb, "day", rb, &status); 1.433 + sb = ures_getByKeyWithFallback(rb, "relative", sb, &status); 1.434 + ures_close(rb); 1.435 + // set up min/max 1.436 + fDayMin=-1; 1.437 + fDayMax=1; 1.438 + 1.439 + if(U_FAILURE(status)) { 1.440 + fDatesLen=0; 1.441 + ures_close(sb); 1.442 + return; 1.443 + } 1.444 + 1.445 + fDatesLen = ures_getSize(sb); 1.446 + fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen); 1.447 + 1.448 + // Load in each item into the array... 1.449 + int n = 0; 1.450 + 1.451 + UResourceBundle *subString = NULL; 1.452 + 1.453 + while(ures_hasNext(sb) && U_SUCCESS(status)) { // iterate over items 1.454 + subString = ures_getNextResource(sb, subString, &status); 1.455 + 1.456 + if(U_FAILURE(status) || (subString==NULL)) break; 1.457 + 1.458 + // key = offset # 1.459 + const char *key = ures_getKey(subString); 1.460 + 1.461 + // load the string and length 1.462 + int32_t aLen; 1.463 + const UChar* aString = ures_getString(subString, &aLen, &status); 1.464 + 1.465 + if(U_FAILURE(status) || aString == NULL) break; 1.466 + 1.467 + // calculate the offset 1.468 + int32_t offset = atoi(key); 1.469 + 1.470 + // set min/max 1.471 + if(offset < fDayMin) { 1.472 + fDayMin = offset; 1.473 + } 1.474 + if(offset > fDayMax) { 1.475 + fDayMax = offset; 1.476 + } 1.477 + 1.478 + // copy the string pointer 1.479 + fDates[n].offset = offset; 1.480 + fDates[n].string = aString; 1.481 + fDates[n].len = aLen; 1.482 + 1.483 + n++; 1.484 + } 1.485 + ures_close(subString); 1.486 + ures_close(sb); 1.487 + 1.488 + // the fDates[] array could be sorted here, for direct access. 1.489 +} 1.490 + 1.491 + 1.492 +// this should to be in DateFormat, instead it was copied from SimpleDateFormat. 1.493 + 1.494 +Calendar* 1.495 +RelativeDateFormat::initializeCalendar(TimeZone* adoptZone, const Locale& locale, UErrorCode& status) 1.496 +{ 1.497 + if(!U_FAILURE(status)) { 1.498 + fCalendar = Calendar::createInstance(adoptZone?adoptZone:TimeZone::createDefault(), locale, status); 1.499 + } 1.500 + if (U_SUCCESS(status) && fCalendar == NULL) { 1.501 + status = U_MEMORY_ALLOCATION_ERROR; 1.502 + } 1.503 + return fCalendar; 1.504 +} 1.505 + 1.506 +int32_t RelativeDateFormat::dayDifference(Calendar &cal, UErrorCode &status) { 1.507 + if(U_FAILURE(status)) { 1.508 + return 0; 1.509 + } 1.510 + // TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type 1.511 + Calendar *nowCal = cal.clone(); 1.512 + nowCal->setTime(Calendar::getNow(), status); 1.513 + 1.514 + // For the day difference, we are interested in the difference in the (modified) julian day number 1.515 + // which is midnight to midnight. Using fieldDifference() is NOT correct here, because 1.516 + // 6pm Jan 4th to 10am Jan 5th should be considered "tomorrow". 1.517 + int32_t dayDiff = cal.get(UCAL_JULIAN_DAY, status) - nowCal->get(UCAL_JULIAN_DAY, status); 1.518 + 1.519 + delete nowCal; 1.520 + return dayDiff; 1.521 +} 1.522 + 1.523 +U_NAMESPACE_END 1.524 + 1.525 +#endif 1.526 +