• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 *******************************************************************************
3 * Copyright (C) 2007-2010, 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 //#define DEBUG_RELDTFMT
13 
14 #include <stdio.h>
15 #include <stdlib.h>
16 
17 #include "reldtfmt.h"
18 #include "unicode/msgfmt.h"
19 #include "unicode/smpdtfmt.h"
20 
21 #include "gregoimp.h" // for CalendarData
22 #include "cmemory.h"
23 
24 U_NAMESPACE_BEGIN
25 
26 
27 /**
28  * An array of URelativeString structs is used to store the resource data loaded out of the bundle.
29  */
30 struct URelativeString {
31     int32_t offset;         /** offset of this item, such as, the relative date **/
32     int32_t len;            /** length of the string **/
33     const UChar* string;    /** string, or NULL if not set **/
34 };
35 
36 static const char DT_DateTimePatternsTag[]="DateTimePatterns";
37 
38 
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat)39 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat)
40 
41 RelativeDateFormat::RelativeDateFormat(const RelativeDateFormat& other) :
42 DateFormat(other), fDateFormat(NULL), fTimeFormat(NULL), fCombinedFormat(NULL),
43 fDateStyle(other.fDateStyle), fTimeStyle(other.fTimeStyle), fLocale(other.fLocale),
44 fDayMin(other.fDayMin), fDayMax(other.fDayMax),
45 fDatesLen(other.fDatesLen), fDates(NULL)
46 {
47     if(other.fDateFormat != NULL) {
48         fDateFormat = (DateFormat*)other.fDateFormat->clone();
49     } else {
50         fDateFormat = NULL;
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     //fCalendar = other.fCalendar->clone();
57 /*
58     if(other.fTimeFormat != NULL) {
59         fTimeFormat = (DateFormat*)other.fTimeFormat->clone();
60     } else {
61         fTimeFormat = NULL;
62     }
63 */
64 }
65 
RelativeDateFormat(UDateFormatStyle timeStyle,UDateFormatStyle dateStyle,const Locale & locale,UErrorCode & status)66 RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const Locale& locale, UErrorCode& status)
67  : DateFormat(), fDateFormat(NULL), fTimeFormat(NULL), fCombinedFormat(NULL),
68 fDateStyle(dateStyle), fTimeStyle(timeStyle), fLocale(locale), fDatesLen(0), fDates(NULL)
69  {
70     if(U_FAILURE(status) ) {
71         return;
72     }
73 
74     if(fDateStyle != UDAT_NONE) {
75         EStyle newStyle = (EStyle)(fDateStyle & ~UDAT_RELATIVE);
76         // Create a DateFormat in the non-relative style requested.
77         fDateFormat = createDateInstance(newStyle, locale);
78     }
79     if(fTimeStyle >= UDAT_FULL && fTimeStyle <= UDAT_SHORT) {
80         fTimeFormat = createTimeInstance((EStyle)fTimeStyle, locale);
81     } else if(fTimeStyle != UDAT_NONE) {
82         // don't support other time styles (e.g. relative styles), for now
83         status = U_ILLEGAL_ARGUMENT_ERROR;
84         return;
85     }
86 
87     // Initialize the parent fCalendar, so that parse() works correctly.
88     initializeCalendar(NULL, locale, status);
89     loadDates(status);
90 }
91 
~RelativeDateFormat()92 RelativeDateFormat::~RelativeDateFormat() {
93     delete fDateFormat;
94     delete fTimeFormat;
95     delete fCombinedFormat;
96     uprv_free(fDates);
97 }
98 
99 
clone(void) const100 Format* RelativeDateFormat::clone(void) const {
101     return new RelativeDateFormat(*this);
102 }
103 
operator ==(const Format & other) const104 UBool RelativeDateFormat::operator==(const Format& other) const {
105     if(DateFormat::operator==(other)) {
106         // DateFormat::operator== guarantees following cast is safe
107         RelativeDateFormat* that = (RelativeDateFormat*)&other;
108         return (fDateStyle==that->fDateStyle   &&
109                 fTimeStyle==that->fTimeStyle   &&
110                 fLocale==that->fLocale);
111     }
112     return FALSE;
113 }
114 
format(Calendar & cal,UnicodeString & appendTo,FieldPosition & pos) const115 UnicodeString& RelativeDateFormat::format(  Calendar& cal,
116                                 UnicodeString& appendTo,
117                                 FieldPosition& pos) const {
118 
119     UErrorCode status = U_ZERO_ERROR;
120     UChar emptyStr = 0;
121     UnicodeString dateString(&emptyStr);
122 
123     // calculate the difference, in days, between 'cal' and now.
124     int dayDiff = dayDifference(cal, status);
125 
126     // look up string
127     int32_t len = 0;
128     const UChar *theString = getStringForDay(dayDiff, len, status);
129     if(U_SUCCESS(status) && (theString!=NULL)) {
130         // found a relative string
131         dateString.setTo(theString, len);
132     }
133 
134     if(fTimeFormat == NULL || fCombinedFormat == 0) {
135         if (dateString.length() > 0) {
136             appendTo.append(dateString);
137         } else if(fDateFormat != NULL) {
138             fDateFormat->format(cal,appendTo,pos);
139         }
140     } else {
141         if (dateString.length() == 0 && fDateFormat != NULL) {
142             fDateFormat->format(cal,dateString,pos);
143         }
144         UnicodeString timeString(&emptyStr);
145         FieldPosition timepos = pos;
146         fTimeFormat->format(cal,timeString,timepos);
147         Formattable timeDateStrings[] = { timeString, dateString };
148         fCombinedFormat->format(timeDateStrings, 2, appendTo, pos, status); // pos is ignored by this
149         int32_t offset;
150         if (pos.getEndIndex() > 0 && (offset = appendTo.indexOf(dateString)) >= 0) {
151             // pos.field was found in dateString, offset start & end based on final position of dateString
152             pos.setBeginIndex( pos.getBeginIndex() + offset );
153             pos.setEndIndex( pos.getEndIndex() + offset );
154         } else if (timepos.getEndIndex() > 0 && (offset = appendTo.indexOf(timeString)) >= 0) {
155             // pos.field was found in timeString, offset start & end based on final position of timeString
156             pos.setBeginIndex( timepos.getBeginIndex() + offset );
157             pos.setEndIndex( timepos.getEndIndex() + offset );
158         }
159     }
160 
161     return appendTo;
162 }
163 
164 
165 
166 UnicodeString&
format(const Formattable & obj,UnicodeString & appendTo,FieldPosition & pos,UErrorCode & status) const167 RelativeDateFormat::format(const Formattable& obj,
168                          UnicodeString& appendTo,
169                          FieldPosition& pos,
170                          UErrorCode& status) const
171 {
172     // this is just here to get around the hiding problem
173     // (the previous format() override would hide the version of
174     // format() on DateFormat that this function correspond to, so we
175     // have to redefine it here)
176     return DateFormat::format(obj, appendTo, pos, status);
177 }
178 
179 
parse(const UnicodeString & text,Calendar & cal,ParsePosition & pos) const180 void RelativeDateFormat::parse( const UnicodeString& text,
181                     Calendar& cal,
182                     ParsePosition& pos) const {
183 
184     // Can the fDateFormat parse it?
185     if(fDateFormat != NULL) {
186         ParsePosition aPos(pos);
187         fDateFormat->parse(text,cal,aPos);
188         if((aPos.getIndex() != pos.getIndex()) &&
189             (aPos.getErrorIndex()==-1)) {
190                 pos=aPos; // copy the sub parse
191                 return; // parsed subfmt OK
192         }
193     }
194 
195     // Linear search the relative strings
196     for(int n=0;n<fDatesLen;n++) {
197         if(fDates[n].string != NULL &&
198             (0==text.compare(pos.getIndex(),
199                          fDates[n].len,
200                          fDates[n].string))) {
201             UErrorCode status = U_ZERO_ERROR;
202 
203             // Set the calendar to now+offset
204             cal.setTime(Calendar::getNow(),status);
205             cal.add(UCAL_DATE,fDates[n].offset, status);
206 
207             if(U_FAILURE(status)) {
208                 // failure in setting calendar fields
209                 pos.setErrorIndex(pos.getIndex()+fDates[n].len);
210             } else {
211                 pos.setIndex(pos.getIndex()+fDates[n].len);
212             }
213             return;
214         }
215     }
216 
217     // parse failed
218 }
219 
220 UDate
parse(const UnicodeString & text,ParsePosition & pos) const221 RelativeDateFormat::parse( const UnicodeString& text,
222                          ParsePosition& pos) const {
223     // redefined here because the other parse() function hides this function's
224     // cunterpart on DateFormat
225     return DateFormat::parse(text, pos);
226 }
227 
228 UDate
parse(const UnicodeString & text,UErrorCode & status) const229 RelativeDateFormat::parse(const UnicodeString& text, UErrorCode& status) const
230 {
231     // redefined here because the other parse() function hides this function's
232     // counterpart on DateFormat
233     return DateFormat::parse(text, status);
234 }
235 
236 
getStringForDay(int32_t day,int32_t & len,UErrorCode & status) const237 const UChar *RelativeDateFormat::getStringForDay(int32_t day, int32_t &len, UErrorCode &status) const {
238     if(U_FAILURE(status)) {
239         return NULL;
240     }
241 
242     // Is it outside the resource bundle's range?
243     if(day < fDayMin || day > fDayMax) {
244         return NULL; // don't have it.
245     }
246 
247     // Linear search the held strings
248     for(int n=0;n<fDatesLen;n++) {
249         if(fDates[n].offset == day) {
250             len = fDates[n].len;
251             return fDates[n].string;
252         }
253     }
254 
255     return NULL;  // not found.
256 }
257 
258 UnicodeString&
toPattern(UnicodeString & result,UErrorCode & status) const259 RelativeDateFormat::toPattern(UnicodeString& result, UErrorCode& status) const
260 {
261     if (!U_FAILURE(status)) {
262         result.remove();
263         if (fTimeFormat == NULL || fCombinedFormat == 0) {
264             if (fDateFormat != NULL) {
265                 UnicodeString datePattern;
266                 this->toPatternDate(datePattern, status);
267                 if (!U_FAILURE(status)) {
268                     result.setTo(datePattern);
269                 }
270             }
271         } else {
272             UnicodeString datePattern, timePattern;
273             this->toPatternDate(datePattern, status);
274             this->toPatternTime(timePattern, status);
275             if (!U_FAILURE(status)) {
276                 Formattable timeDatePatterns[] = { timePattern, datePattern };
277                 FieldPosition pos;
278                 fCombinedFormat->format(timeDatePatterns, 2, result, pos, status);
279             }
280         }
281     }
282     return result;
283 }
284 
285 UnicodeString&
toPatternDate(UnicodeString & result,UErrorCode & status) const286 RelativeDateFormat::toPatternDate(UnicodeString& result, UErrorCode& status) const
287 {
288     if (!U_FAILURE(status)) {
289         result.remove();
290         if ( fDateFormat ) {
291             SimpleDateFormat* sdtfmt = dynamic_cast<SimpleDateFormat*>(fDateFormat);
292             if (sdtfmt != NULL) {
293                 sdtfmt->toPattern(result);
294             } else {
295                 status = U_UNSUPPORTED_ERROR;
296             }
297         }
298     }
299     return result;
300 }
301 
302 UnicodeString&
toPatternTime(UnicodeString & result,UErrorCode & status) const303 RelativeDateFormat::toPatternTime(UnicodeString& result, UErrorCode& status) const
304 {
305     if (!U_FAILURE(status)) {
306         result.remove();
307         if ( fTimeFormat ) {
308             SimpleDateFormat* sdtfmt = dynamic_cast<SimpleDateFormat*>(fTimeFormat);
309             if (sdtfmt != NULL) {
310                 sdtfmt->toPattern(result);
311             } else {
312                 status = U_UNSUPPORTED_ERROR;
313             }
314         }
315     }
316     return result;
317 }
318 
319 void
applyPatterns(const UnicodeString & datePattern,const UnicodeString & timePattern,UErrorCode & status)320 RelativeDateFormat::applyPatterns(const UnicodeString& datePattern, const UnicodeString& timePattern, UErrorCode &status)
321 {
322     if (!U_FAILURE(status)) {
323         SimpleDateFormat* sdtfmt = NULL;
324         SimpleDateFormat* stmfmt = NULL;
325         if (fDateFormat && (sdtfmt = dynamic_cast<SimpleDateFormat*>(fDateFormat)) == NULL) {
326             status = U_UNSUPPORTED_ERROR;
327             return;
328         }
329         if (fTimeFormat && (stmfmt = dynamic_cast<SimpleDateFormat*>(fTimeFormat)) == NULL) {
330             status = U_UNSUPPORTED_ERROR;
331             return;
332         }
333         if ( fDateFormat ) {
334             sdtfmt->applyPattern(datePattern);
335         }
336         if ( fTimeFormat ) {
337             stmfmt->applyPattern(timePattern);
338         }
339     }
340 }
341 
loadDates(UErrorCode & status)342 void RelativeDateFormat::loadDates(UErrorCode &status) {
343     CalendarData calData(fLocale, "gregorian", status);
344 
345     UErrorCode tempStatus = status;
346     UResourceBundle *dateTimePatterns = calData.getByKey(DT_DateTimePatternsTag, tempStatus);
347     if(U_SUCCESS(tempStatus)) {
348         int32_t patternsSize = ures_getSize(dateTimePatterns);
349         if (patternsSize > kDateTime) {
350             int32_t resStrLen = 0;
351 
352             int32_t glueIndex = kDateTime;
353             if (patternsSize >= (DateFormat::kDateTimeOffset + DateFormat::kShort + 1)) {
354                 // Get proper date time format
355                 switch (fDateStyle) {
356  	            case kFullRelative:
357  	            case kFull:
358  	                glueIndex = kDateTimeOffset + kFull;
359  	                break;
360  	            case kLongRelative:
361  	            case kLong:
362  	                glueIndex = kDateTimeOffset + kLong;
363  	                break;
364  	            case kMediumRelative:
365  	            case kMedium:
366  	                glueIndex = kDateTimeOffset + kMedium;
367  	                break;
368  	            case kShortRelative:
369  	            case kShort:
370  	                glueIndex = kDateTimeOffset + kShort;
371  	                break;
372  	            default:
373  	                break;
374  	            }
375             }
376 
377             const UChar *resStr = ures_getStringByIndex(dateTimePatterns, glueIndex, &resStrLen, &tempStatus);
378             fCombinedFormat = new MessageFormat(UnicodeString(TRUE, resStr, resStrLen), fLocale, tempStatus);
379         }
380     }
381 
382     UResourceBundle *strings = calData.getByKey3("fields", "day", "relative", status);
383     // set up min/max
384     fDayMin=-1;
385     fDayMax=1;
386 
387     if(U_FAILURE(status)) {
388         fDatesLen=0;
389         return;
390     }
391 
392     fDatesLen = ures_getSize(strings);
393     fDates = (URelativeString*) uprv_malloc(sizeof(fDates[0])*fDatesLen);
394 
395     // Load in each item into the array...
396     int n = 0;
397 
398     UResourceBundle *subString = NULL;
399 
400     while(ures_hasNext(strings) && U_SUCCESS(status)) {  // iterate over items
401         subString = ures_getNextResource(strings, subString, &status);
402 
403         if(U_FAILURE(status) || (subString==NULL)) break;
404 
405         // key = offset #
406         const char *key = ures_getKey(subString);
407 
408         // load the string and length
409         int32_t aLen;
410         const UChar* aString = ures_getString(subString, &aLen, &status);
411 
412         if(U_FAILURE(status) || aString == NULL) break;
413 
414         // calculate the offset
415         int32_t offset = atoi(key);
416 
417         // set min/max
418         if(offset < fDayMin) {
419             fDayMin = offset;
420         }
421         if(offset > fDayMax) {
422             fDayMax = offset;
423         }
424 
425         // copy the string pointer
426         fDates[n].offset = offset;
427         fDates[n].string = aString;
428         fDates[n].len = aLen;
429 
430         n++;
431     }
432     ures_close(subString);
433 
434     // the fDates[] array could be sorted here, for direct access.
435 }
436 
437 
438 // this should to be in DateFormat, instead it was copied from SimpleDateFormat.
439 
440 Calendar*
initializeCalendar(TimeZone * adoptZone,const Locale & locale,UErrorCode & status)441 RelativeDateFormat::initializeCalendar(TimeZone* adoptZone, const Locale& locale, UErrorCode& status)
442 {
443     if(!U_FAILURE(status)) {
444         fCalendar = Calendar::createInstance(adoptZone?adoptZone:TimeZone::createDefault(), locale, status);
445     }
446     if (U_SUCCESS(status) && fCalendar == NULL) {
447         status = U_MEMORY_ALLOCATION_ERROR;
448     }
449     return fCalendar;
450 }
451 
dayDifference(Calendar & cal,UErrorCode & status)452 int32_t RelativeDateFormat::dayDifference(Calendar &cal, UErrorCode &status) {
453     if(U_FAILURE(status)) {
454         return 0;
455     }
456     // TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type
457     Calendar *nowCal = cal.clone();
458     nowCal->setTime(Calendar::getNow(), status);
459 
460     // For the day difference, we are interested in the difference in the (modified) julian day number
461     // which is midnight to midnight.  Using fieldDifference() is NOT correct here, because
462     // 6pm Jan 4th  to 10am Jan 5th should be considered "tomorrow".
463     int32_t dayDiff = cal.get(UCAL_JULIAN_DAY, status) - nowCal->get(UCAL_JULIAN_DAY, status);
464 
465     delete nowCal;
466     return dayDiff;
467 }
468 
469 U_NAMESPACE_END
470 
471 #endif
472 
473