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
24U_NAMESPACE_BEGIN
25
26
27/**
28 * An array of URelativeString structs is used to store the resource data loaded out of the bundle.
29 */
30struct 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
36static const char DT_DateTimePatternsTag[]="DateTimePatterns";
37
38
39UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat)
40
41RelativeDateFormat::RelativeDateFormat(const RelativeDateFormat& other) :
42DateFormat(other), fDateFormat(NULL), fTimeFormat(NULL), fCombinedFormat(NULL),
43fDateStyle(other.fDateStyle), fTimeStyle(other.fTimeStyle), fLocale(other.fLocale),
44fDayMin(other.fDayMin), fDayMax(other.fDayMax),
45fDatesLen(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
66RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle, UDateFormatStyle dateStyle, const Locale& locale, UErrorCode& status)
67 : DateFormat(), fDateFormat(NULL), fTimeFormat(NULL), fCombinedFormat(NULL),
68fDateStyle(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
92RelativeDateFormat::~RelativeDateFormat() {
93    delete fDateFormat;
94    delete fTimeFormat;
95    delete fCombinedFormat;
96    uprv_free(fDates);
97}
98
99
100Format* RelativeDateFormat::clone(void) const {
101    return new RelativeDateFormat(*this);
102}
103
104UBool 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
115UnicodeString& 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
166UnicodeString&
167RelativeDateFormat::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
180void 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
220UDate
221RelativeDateFormat::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
228UDate
229RelativeDateFormat::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
237const 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
258UnicodeString&
259RelativeDateFormat::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
285UnicodeString&
286RelativeDateFormat::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
302UnicodeString&
303RelativeDateFormat::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
319void
320RelativeDateFormat::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
342void 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
440Calendar*
441RelativeDateFormat::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
452int32_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
469U_NAMESPACE_END
470
471#endif
472
473