2 *******************************************************************************
3 * Copyright (C) 2007-2010, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 *******************************************************************************
8 #include "unicode/utypes.h"
10 #if !UCONFIG_NO_FORMATTING
12 //#define DEBUG_RELDTFMT
18 #include "unicode/msgfmt.h"
19 #include "unicode/smpdtfmt.h"
21 #include "gregoimp.h" // for CalendarData
28 * An array of URelativeString structs is used to store the resource data loaded out of the bundle.
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 **/
36 static const char DT_DateTimePatternsTag
[]="DateTimePatterns";
39 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat
)
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
)
47 if(other
.fDateFormat
!= NULL
) {
48 fDateFormat
= (DateFormat
*)other
.fDateFormat
->clone();
53 fDates
= (URelativeString
*) uprv_malloc(sizeof(fDates
[0])*fDatesLen
);
54 uprv_memcpy(fDates
, other
.fDates
, sizeof(fDates
[0])*fDatesLen
);
56 //fCalendar = other.fCalendar->clone();
58 if(other.fTimeFormat != NULL) {
59 fTimeFormat = (DateFormat*)other.fTimeFormat->clone();
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
)
70 if(U_FAILURE(status
) ) {
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
);
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
;
87 // Initialize the parent fCalendar, so that parse() works correctly.
88 initializeCalendar(NULL
, locale
, status
);
92 RelativeDateFormat::~RelativeDateFormat() {
95 delete fCombinedFormat
;
100 Format
* RelativeDateFormat::clone(void) const {
101 return new RelativeDateFormat(*this);
104 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
);
115 UnicodeString
& RelativeDateFormat::format( Calendar
& cal
,
116 UnicodeString
& appendTo
,
117 FieldPosition
& pos
) const {
119 UErrorCode status
= U_ZERO_ERROR
;
121 UnicodeString
dateString(&emptyStr
);
123 // calculate the difference, in days, between 'cal' and now.
124 int dayDiff
= dayDifference(cal
, status
);
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
);
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
);
141 if (dateString
.length() == 0 && fDateFormat
!= NULL
) {
142 fDateFormat
->format(cal
,dateString
,pos
);
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
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
);
167 RelativeDateFormat::format(const Formattable
& obj
,
168 UnicodeString
& appendTo
,
170 UErrorCode
& status
) const
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
);
180 void RelativeDateFormat::parse( const UnicodeString
& text
,
182 ParsePosition
& pos
) const {
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
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(),
200 fDates
[n
].string
))) {
201 UErrorCode status
= U_ZERO_ERROR
;
203 // Set the calendar to now+offset
204 cal
.setTime(Calendar::getNow(),status
);
205 cal
.add(UCAL_DATE
,fDates
[n
].offset
, status
);
207 if(U_FAILURE(status
)) {
208 // failure in setting calendar fields
209 pos
.setErrorIndex(pos
.getIndex()+fDates
[n
].len
);
211 pos
.setIndex(pos
.getIndex()+fDates
[n
].len
);
221 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
);
229 RelativeDateFormat::parse(const UnicodeString
& text
, UErrorCode
& status
) const
231 // redefined here because the other parse() function hides this function's
232 // counterpart on DateFormat
233 return DateFormat::parse(text
, status
);
237 const UChar
*RelativeDateFormat::getStringForDay(int32_t day
, int32_t &len
, UErrorCode
&status
) const {
238 if(U_FAILURE(status
)) {
242 // Is it outside the resource bundle's range?
243 if(day
< fDayMin
|| day
> fDayMax
) {
244 return NULL
; // don't have it.
247 // Linear search the held strings
248 for(int n
=0;n
<fDatesLen
;n
++) {
249 if(fDates
[n
].offset
== day
) {
251 return fDates
[n
].string
;
255 return NULL
; // not found.
259 RelativeDateFormat::toPattern(UnicodeString
& result
, UErrorCode
& status
) const
261 if (!U_FAILURE(status
)) {
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
);
272 UnicodeString datePattern
, timePattern
;
273 this->toPatternDate(datePattern
, status
);
274 this->toPatternTime(timePattern
, status
);
275 if (!U_FAILURE(status
)) {
276 Formattable timeDatePatterns
[] = { timePattern
, datePattern
};
278 fCombinedFormat
->format(timeDatePatterns
, 2, result
, pos
, status
);
286 RelativeDateFormat::toPatternDate(UnicodeString
& result
, UErrorCode
& status
) const
288 if (!U_FAILURE(status
)) {
291 SimpleDateFormat
* sdtfmt
= dynamic_cast<SimpleDateFormat
*>(fDateFormat
);
292 if (sdtfmt
!= NULL
) {
293 sdtfmt
->toPattern(result
);
295 status
= U_UNSUPPORTED_ERROR
;
303 RelativeDateFormat::toPatternTime(UnicodeString
& result
, UErrorCode
& status
) const
305 if (!U_FAILURE(status
)) {
308 SimpleDateFormat
* sdtfmt
= dynamic_cast<SimpleDateFormat
*>(fTimeFormat
);
309 if (sdtfmt
!= NULL
) {
310 sdtfmt
->toPattern(result
);
312 status
= U_UNSUPPORTED_ERROR
;
320 RelativeDateFormat::applyPatterns(const UnicodeString
& datePattern
, const UnicodeString
& timePattern
, UErrorCode
&status
)
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
;
329 if (fTimeFormat
&& (stmfmt
= dynamic_cast<SimpleDateFormat
*>(fTimeFormat
)) == NULL
) {
330 status
= U_UNSUPPORTED_ERROR
;
334 sdtfmt
->applyPattern(datePattern
);
337 stmfmt
->applyPattern(timePattern
);
342 const DateFormatSymbols
*
343 RelativeDateFormat::getDateFormatSymbols() const
345 SimpleDateFormat
* sdtfmt
= NULL
;
346 if (fDateFormat
&& (sdtfmt
= dynamic_cast<SimpleDateFormat
*>(fDateFormat
)) != NULL
) {
347 return sdtfmt
->getDateFormatSymbols();
352 void RelativeDateFormat::loadDates(UErrorCode
&status
) {
353 CalendarData
calData(fLocale
, "gregorian", status
);
355 UErrorCode tempStatus
= status
;
356 UResourceBundle
*dateTimePatterns
= calData
.getByKey(DT_DateTimePatternsTag
, tempStatus
);
357 if(U_SUCCESS(tempStatus
)) {
358 int32_t patternsSize
= ures_getSize(dateTimePatterns
);
359 if (patternsSize
> kDateTime
) {
360 int32_t resStrLen
= 0;
362 int32_t glueIndex
= kDateTime
;
363 if (patternsSize
>= (DateFormat::kDateTimeOffset
+ DateFormat::kShort
+ 1)) {
364 // Get proper date time format
365 switch (fDateStyle
) {
368 glueIndex
= kDateTimeOffset
+ kFull
;
372 glueIndex
= kDateTimeOffset
+ kLong
;
374 case kMediumRelative
:
376 glueIndex
= kDateTimeOffset
+ kMedium
;
380 glueIndex
= kDateTimeOffset
+ kShort
;
387 const UChar
*resStr
= ures_getStringByIndex(dateTimePatterns
, glueIndex
, &resStrLen
, &tempStatus
);
388 fCombinedFormat
= new MessageFormat(UnicodeString(TRUE
, resStr
, resStrLen
), fLocale
, tempStatus
);
392 UResourceBundle
*strings
= calData
.getByKey3("fields", "day", "relative", status
);
397 if(U_FAILURE(status
)) {
402 fDatesLen
= ures_getSize(strings
);
403 fDates
= (URelativeString
*) uprv_malloc(sizeof(fDates
[0])*fDatesLen
);
405 // Load in each item into the array...
408 UResourceBundle
*subString
= NULL
;
410 while(ures_hasNext(strings
) && U_SUCCESS(status
)) { // iterate over items
411 subString
= ures_getNextResource(strings
, subString
, &status
);
413 if(U_FAILURE(status
) || (subString
==NULL
)) break;
416 const char *key
= ures_getKey(subString
);
418 // load the string and length
420 const UChar
* aString
= ures_getString(subString
, &aLen
, &status
);
422 if(U_FAILURE(status
) || aString
== NULL
) break;
424 // calculate the offset
425 int32_t offset
= atoi(key
);
428 if(offset
< fDayMin
) {
431 if(offset
> fDayMax
) {
435 // copy the string pointer
436 fDates
[n
].offset
= offset
;
437 fDates
[n
].string
= aString
;
438 fDates
[n
].len
= aLen
;
442 ures_close(subString
);
444 // the fDates[] array could be sorted here, for direct access.
448 // this should to be in DateFormat, instead it was copied from SimpleDateFormat.
451 RelativeDateFormat::initializeCalendar(TimeZone
* adoptZone
, const Locale
& locale
, UErrorCode
& status
)
453 if(!U_FAILURE(status
)) {
454 fCalendar
= Calendar::createInstance(adoptZone
?adoptZone
:TimeZone::createDefault(), locale
, status
);
456 if (U_SUCCESS(status
) && fCalendar
== NULL
) {
457 status
= U_MEMORY_ALLOCATION_ERROR
;
462 int32_t RelativeDateFormat::dayDifference(Calendar
&cal
, UErrorCode
&status
) {
463 if(U_FAILURE(status
)) {
466 // TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type
467 Calendar
*nowCal
= cal
.clone();
468 nowCal
->setTime(Calendar::getNow(), status
);
470 // For the day difference, we are interested in the difference in the (modified) julian day number
471 // which is midnight to midnight. Using fieldDifference() is NOT correct here, because
472 // 6pm Jan 4th to 10am Jan 5th should be considered "tomorrow".
473 int32_t dayDiff
= cal
.get(UCAL_JULIAN_DAY
, status
) - nowCal
->get(UCAL_JULIAN_DAY
, status
);