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
23U_NAMESPACE_BEGIN
24
25
26/**
27 * An array of URelativeString structs is used to store the resource data loaded out of the bundle.
28 */
29struct 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
35static const char DT_DateTimePatternsTag[]="DateTimePatterns";
36
37
38UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat)
39
40RelativeDateFormat::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
59RelativeDateFormat::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
109RelativeDateFormat::~RelativeDateFormat() {
110    delete fDateTimeFormatter;
111    delete fCombinedFormat;
112    uprv_free(fDates);
113}
114
115
116Format* RelativeDateFormat::clone(void) const {
117    return new RelativeDateFormat(*this);
118}
119
120UBool 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
132static const UChar APOSTROPHE = (UChar)0x0027;
133
134UnicodeString& 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
185UnicodeString&
186RelativeDateFormat::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
199void 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
296UDate
297RelativeDateFormat::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
304UDate
305RelativeDateFormat::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
313const 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
334UnicodeString&
335RelativeDateFormat::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
352UnicodeString&
353RelativeDateFormat::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
362UnicodeString&
363RelativeDateFormat::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
372void
373RelativeDateFormat::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
381const DateFormatSymbols*
382RelativeDateFormat::getDateFormatSymbols() const
383{
384    return fDateTimeFormatter->getDateFormatSymbols();
385}
386
387void 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
491Calendar*
492RelativeDateFormat::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
503int32_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
520U_NAMESPACE_END
521
522#endif
523
524