2 *******************************************************************************
3 * Copyright (C) 2007-2013, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 *******************************************************************************
8 #include "unicode/utypes.h"
10 #if !UCONFIG_NO_FORMATTING
15 #include "unicode/datefmt.h"
16 #include "unicode/smpdtfmt.h"
17 #include "unicode/msgfmt.h"
19 #include "gregoimp.h" // for CalendarData
27 * An array of URelativeString structs is used to store the resource data loaded out of the bundle.
29 struct 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 **/
35 static const char DT_DateTimePatternsTag
[]="DateTimePatterns";
38 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat
)
40 RelativeDateFormat::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
)
47 if(other
.fDateTimeFormatter
!= NULL
) {
48 fDateTimeFormatter
= (SimpleDateFormat
*)other
.fDateTimeFormatter
->clone();
50 if(other
.fCombinedFormat
!= NULL
) {
51 fCombinedFormat
= (MessageFormat
*)other
.fCombinedFormat
->clone();
54 fDates
= (URelativeString
*) uprv_malloc(sizeof(fDates
[0])*fDatesLen
);
55 uprv_memcpy(fDates
, other
.fDates
, sizeof(fDates
[0])*fDatesLen
);
59 RelativeDateFormat::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
)
64 if(U_FAILURE(status
) ) {
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
;
73 UDateFormatStyle baseDateStyle
= (dateStyle
> UDAT_SHORT
)? (UDateFormatStyle
)(dateStyle
& ~UDAT_RELATIVE
): dateStyle
;
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
;
84 fDateTimeFormatter
->toPattern(fDatePattern
);
85 if (timeStyle
!= UDAT_NONE
) {
86 df
= createTimeInstance((EStyle
)timeStyle
, locale
);
87 SimpleDateFormat
*sdf
= dynamic_cast<SimpleDateFormat
*>(df
);
89 sdf
->toPattern(fTimePattern
);
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
;
101 fDateTimeFormatter
->toPattern(fTimePattern
);
104 // Initialize the parent fCalendar, so that parse() works correctly.
105 initializeCalendar(NULL
, locale
, status
);
109 RelativeDateFormat::~RelativeDateFormat() {
110 delete fDateTimeFormatter
;
111 delete fCombinedFormat
;
116 Format
* RelativeDateFormat::clone(void) const {
117 return new RelativeDateFormat(*this);
120 UBool
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
);
132 static const UChar APOSTROPHE
= (UChar
)0x0027;
134 UnicodeString
& RelativeDateFormat::format( Calendar
& cal
,
135 UnicodeString
& appendTo
,
136 FieldPosition
& pos
) const {
138 UErrorCode status
= U_ZERO_ERROR
;
139 UnicodeString relativeDayString
;
141 // calculate the difference, in days, between 'cal' and now.
142 int dayDiff
= dayDifference(cal
, status
);
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
);
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
);
159 fDateTimeFormatter
->applyPattern(fDatePattern
);
160 fDateTimeFormatter
->format(cal
,appendTo
,pos
);
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
);
171 datePattern
.setTo(fDatePattern
);
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
);
186 RelativeDateFormat::format(const Formattable
& obj
,
187 UnicodeString
& appendTo
,
189 UErrorCode
& status
) const
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
);
199 void RelativeDateFormat::parse( const UnicodeString
& text
,
201 ParsePosition
& pos
) const {
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
;
219 // Set the calendar to now+offset
220 cal
.setTime(Calendar::getNow(),status
);
221 cal
.add(UCAL_DATE
,fDates
[n
].offset
, status
);
223 if(U_FAILURE(status
)) {
224 // failure in setting calendar field, set offset to beginning of rel day string
225 pos
.setErrorIndex(startIndex
);
227 pos
.setIndex(startIndex
+ fDates
[n
].len
);
231 if (!matchedRelative
) {
232 // just parse as normal date
233 fDateTimeFormatter
->applyPattern(fDatePattern
);
234 fDateTimeFormatter
->parse(text
,cal
,pos
);
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
);
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();
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
);
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
);
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
);
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)
289 pos
.setIndex(offset
);
291 pos
.setErrorIndex(offset
);
297 RelativeDateFormat::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
);
305 RelativeDateFormat::parse(const UnicodeString
& text
, UErrorCode
& status
) const
307 // redefined here because the other parse() function hides this function's
308 // counterpart on DateFormat
309 return DateFormat::parse(text
, status
);
313 const UChar
*RelativeDateFormat::getStringForDay(int32_t day
, int32_t &len
, UErrorCode
&status
) const {
314 if(U_FAILURE(status
)) {
318 // Is it outside the resource bundle's range?
319 if(day
< fDayMin
|| day
> fDayMax
) {
320 return NULL
; // don't have it.
323 // Linear search the held strings
324 for(int n
=0;n
<fDatesLen
;n
++) {
325 if(fDates
[n
].offset
== day
) {
327 return fDates
[n
].string
;
331 return NULL
; // not found.
335 RelativeDateFormat::toPattern(UnicodeString
& result
, UErrorCode
& status
) const
337 if (!U_FAILURE(status
)) {
339 if (fDatePattern
.isEmpty()) {
340 result
.setTo(fTimePattern
);
341 } else if (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
) {
342 result
.setTo(fDatePattern
);
344 Formattable timeDatePatterns
[] = { fTimePattern
, fDatePattern
};
346 fCombinedFormat
->format(timeDatePatterns
, 2, result
, pos
, status
);
353 RelativeDateFormat::toPatternDate(UnicodeString
& result
, UErrorCode
& status
) const
355 if (!U_FAILURE(status
)) {
357 result
.setTo(fDatePattern
);
363 RelativeDateFormat::toPatternTime(UnicodeString
& result
, UErrorCode
& status
) const
365 if (!U_FAILURE(status
)) {
367 result
.setTo(fTimePattern
);
373 RelativeDateFormat::applyPatterns(const UnicodeString
& datePattern
, const UnicodeString
& timePattern
, UErrorCode
&status
)
375 if (!U_FAILURE(status
)) {
376 fDatePattern
.setTo(datePattern
);
377 fTimePattern
.setTo(timePattern
);
381 const DateFormatSymbols
*
382 RelativeDateFormat::getDateFormatSymbols() const
384 return fDateTimeFormatter
->getDateFormatSymbols();
387 void RelativeDateFormat::loadDates(UErrorCode
&status
) {
388 CalendarData
calData(fLocale
, "gregorian", status
);
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;
397 int32_t glueIndex
= kDateTime
;
398 if (patternsSize
>= (DateFormat::kDateTimeOffset
+ DateFormat::kShort
+ 1)) {
399 // Get proper date time format
400 switch (fDateStyle
) {
403 glueIndex
= kDateTimeOffset
+ kFull
;
407 glueIndex
= kDateTimeOffset
+ kLong
;
409 case kMediumRelative
:
411 glueIndex
= kDateTimeOffset
+ kMedium
;
415 glueIndex
= kDateTimeOffset
+ kShort
;
422 const UChar
*resStr
= ures_getStringByIndex(dateTimePatterns
, glueIndex
, &resStrLen
, &tempStatus
);
423 fCombinedFormat
= new MessageFormat(UnicodeString(TRUE
, resStr
, resStrLen
), fLocale
, tempStatus
);
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
);
436 if(U_FAILURE(status
)) {
442 fDatesLen
= ures_getSize(sb
);
443 fDates
= (URelativeString
*) uprv_malloc(sizeof(fDates
[0])*fDatesLen
);
445 // Load in each item into the array...
448 UResourceBundle
*subString
= NULL
;
450 while(ures_hasNext(sb
) && U_SUCCESS(status
)) { // iterate over items
451 subString
= ures_getNextResource(sb
, subString
, &status
);
453 if(U_FAILURE(status
) || (subString
==NULL
)) break;
456 const char *key
= ures_getKey(subString
);
458 // load the string and length
460 const UChar
* aString
= ures_getString(subString
, &aLen
, &status
);
462 if(U_FAILURE(status
) || aString
== NULL
) break;
464 // calculate the offset
465 int32_t offset
= atoi(key
);
468 if(offset
< fDayMin
) {
471 if(offset
> fDayMax
) {
475 // copy the string pointer
476 fDates
[n
].offset
= offset
;
477 fDates
[n
].string
= aString
;
478 fDates
[n
].len
= aLen
;
482 ures_close(subString
);
485 // the fDates[] array could be sorted here, for direct access.
489 // this should to be in DateFormat, instead it was copied from SimpleDateFormat.
492 RelativeDateFormat::initializeCalendar(TimeZone
* adoptZone
, const Locale
& locale
, UErrorCode
& status
)
494 if(!U_FAILURE(status
)) {
495 fCalendar
= Calendar::createInstance(adoptZone
?adoptZone
:TimeZone::createDefault(), locale
, status
);
497 if (U_SUCCESS(status
) && fCalendar
== NULL
) {
498 status
= U_MEMORY_ALLOCATION_ERROR
;
503 int32_t RelativeDateFormat::dayDifference(Calendar
&cal
, UErrorCode
&status
) {
504 if(U_FAILURE(status
)) {
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
);
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
);