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
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat)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
RelativeDateFormat(UDateFormatStyle timeStyle,UDateFormatStyle dateStyle,const Locale & locale,UErrorCode & status)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
~RelativeDateFormat()108 RelativeDateFormat::~RelativeDateFormat() {
109 delete fDateTimeFormatter;
110 delete fCombinedFormat;
111 uprv_free(fDates);
112 }
113
114
clone(void) const115 Format* RelativeDateFormat::clone(void) const {
116 return new RelativeDateFormat(*this);
117 }
118
operator ==(const Format & other) const119 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
format(Calendar & cal,UnicodeString & appendTo,FieldPosition & pos) const133 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&
format(const Formattable & obj,UnicodeString & appendTo,FieldPosition & pos,UErrorCode & status) const185 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
parse(const UnicodeString & text,Calendar & cal,ParsePosition & pos) const198 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
parse(const UnicodeString & text,ParsePosition & pos) const296 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
parse(const UnicodeString & text,UErrorCode & status) const304 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
getStringForDay(int32_t day,int32_t & len,UErrorCode & status) const312 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&
toPattern(UnicodeString & result,UErrorCode & status) const334 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&
toPatternDate(UnicodeString & result,UErrorCode & status) const352 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&
toPatternTime(UnicodeString & result,UErrorCode & status) const362 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
applyPatterns(const UnicodeString & datePattern,const UnicodeString & timePattern,UErrorCode & status)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*
getDateFormatSymbols() const381 RelativeDateFormat::getDateFormatSymbols() const
382 {
383 return fDateTimeFormatter->getDateFormatSymbols();
384 }
385
loadDates(UErrorCode & status)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*
initializeCalendar(TimeZone * adoptZone,const Locale & locale,UErrorCode & status)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
dayDifference(Calendar & cal,UErrorCode & status)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