• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
23 U_NAMESPACE_BEGIN
24 
25 
26 /**
27  * An array of URelativeString structs is used to store the resource data loaded out of the bundle.
28  */
29 struct 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 
35 static const char DT_DateTimePatternsTag[]="DateTimePatterns";
36 
37 
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat)38 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat)
39 
40 RelativeDateFormat::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 
RelativeDateFormat(UDateFormatStyle timeStyle,UDateFormatStyle dateStyle,const Locale & locale,UErrorCode & status)59 RelativeDateFormat::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 
~RelativeDateFormat()109 RelativeDateFormat::~RelativeDateFormat() {
110     delete fDateTimeFormatter;
111     delete fCombinedFormat;
112     uprv_free(fDates);
113 }
114 
115 
clone(void) const116 Format* RelativeDateFormat::clone(void) const {
117     return new RelativeDateFormat(*this);
118 }
119 
operator ==(const Format & other) const120 UBool 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 
132 static const UChar APOSTROPHE = (UChar)0x0027;
133 
format(Calendar & cal,UnicodeString & appendTo,FieldPosition & pos) const134 UnicodeString& 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 
185 UnicodeString&
format(const Formattable & obj,UnicodeString & appendTo,FieldPosition & pos,UErrorCode & status) const186 RelativeDateFormat::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 
parse(const UnicodeString & text,Calendar & cal,ParsePosition & pos) const199 void 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 
296 UDate
parse(const UnicodeString & text,ParsePosition & pos) const297 RelativeDateFormat::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 
304 UDate
parse(const UnicodeString & text,UErrorCode & status) const305 RelativeDateFormat::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 
getStringForDay(int32_t day,int32_t & len,UErrorCode & status) const313 const 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 
334 UnicodeString&
toPattern(UnicodeString & result,UErrorCode & status) const335 RelativeDateFormat::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 
352 UnicodeString&
toPatternDate(UnicodeString & result,UErrorCode & status) const353 RelativeDateFormat::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 
362 UnicodeString&
toPatternTime(UnicodeString & result,UErrorCode & status) const363 RelativeDateFormat::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 
372 void
applyPatterns(const UnicodeString & datePattern,const UnicodeString & timePattern,UErrorCode & status)373 RelativeDateFormat::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 
381 const DateFormatSymbols*
getDateFormatSymbols() const382 RelativeDateFormat::getDateFormatSymbols() const
383 {
384     return fDateTimeFormatter->getDateFormatSymbols();
385 }
386 
loadDates(UErrorCode & status)387 void 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 
491 Calendar*
initializeCalendar(TimeZone * adoptZone,const Locale & locale,UErrorCode & status)492 RelativeDateFormat::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 
dayDifference(Calendar & cal,UErrorCode & status)503 int32_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 
520 U_NAMESPACE_END
521 
522 #endif
523 
524