• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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