2 *******************************************************************************
3 * Copyright (C) 2007-2014, 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"
18 #include "unicode/udisplaycontext.h"
19 #include "unicode/uchar.h"
20 #include "unicode/brkiter.h"
22 #include "gregoimp.h" // for CalendarData
30 * An array of URelativeString structs is used to store the resource data loaded out of the bundle.
32 struct URelativeString
{
33 int32_t offset
; /** offset of this item, such as, the relative date **/
34 int32_t len
; /** length of the string **/
35 const UChar
* string
; /** string, or NULL if not set **/
38 static const char DT_DateTimePatternsTag
[]="DateTimePatterns";
41 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat
)
43 RelativeDateFormat::RelativeDateFormat(const RelativeDateFormat
& other
) :
44 DateFormat(other
), fDateTimeFormatter(NULL
), fDatePattern(other
.fDatePattern
),
45 fTimePattern(other
.fTimePattern
), fCombinedFormat(NULL
),
46 fDateStyle(other
.fDateStyle
), fLocale(other
.fLocale
),
47 fDayMin(other
.fDayMin
), fDayMax(other
.fDayMax
),
48 fDatesLen(other
.fDatesLen
), fDates(NULL
),
49 fCombinedHasDateAtStart(other
.fCombinedHasDateAtStart
),
50 fCapitalizationInfoSet(other
.fCapitalizationInfoSet
),
51 fCapitalizationOfRelativeUnitsForUIListMenu(other
.fCapitalizationOfRelativeUnitsForUIListMenu
),
52 fCapitalizationOfRelativeUnitsForStandAlone(other
.fCapitalizationOfRelativeUnitsForStandAlone
),
53 fCapitalizationBrkIter(NULL
)
55 if(other
.fDateTimeFormatter
!= NULL
) {
56 fDateTimeFormatter
= (SimpleDateFormat
*)other
.fDateTimeFormatter
->clone();
58 if(other
.fCombinedFormat
!= NULL
) {
59 fCombinedFormat
= (MessageFormat
*)other
.fCombinedFormat
->clone();
62 fDates
= (URelativeString
*) uprv_malloc(sizeof(fDates
[0])*fDatesLen
);
63 uprv_memcpy(fDates
, other
.fDates
, sizeof(fDates
[0])*fDatesLen
);
65 #if !UCONFIG_NO_BREAK_ITERATION
66 if (other
.fCapitalizationBrkIter
!= NULL
) {
67 fCapitalizationBrkIter
= (other
.fCapitalizationBrkIter
)->clone();
72 RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle
, UDateFormatStyle dateStyle
,
73 const Locale
& locale
, UErrorCode
& status
) :
74 DateFormat(), fDateTimeFormatter(NULL
), fDatePattern(), fTimePattern(), fCombinedFormat(NULL
),
75 fDateStyle(dateStyle
), fLocale(locale
), fDayMin(0), fDayMax(0), fDatesLen(0), fDates(NULL
),
76 fCombinedHasDateAtStart(FALSE
), fCapitalizationInfoSet(FALSE
),
77 fCapitalizationOfRelativeUnitsForUIListMenu(FALSE
), fCapitalizationOfRelativeUnitsForStandAlone(FALSE
),
78 fCapitalizationBrkIter(NULL
)
80 if(U_FAILURE(status
) ) {
84 if (timeStyle
< UDAT_NONE
|| timeStyle
> UDAT_SHORT
) {
85 // don't support other time styles (e.g. relative styles), for now
86 status
= U_ILLEGAL_ARGUMENT_ERROR
;
89 UDateFormatStyle baseDateStyle
= (dateStyle
> UDAT_SHORT
)? (UDateFormatStyle
)(dateStyle
& ~UDAT_RELATIVE
): dateStyle
;
91 // Get fDateTimeFormatter from either date or time style (does not matter, we will override the pattern).
92 // We do need to get separate patterns for the date & time styles.
93 if (baseDateStyle
!= UDAT_NONE
) {
94 df
= createDateInstance((EStyle
)baseDateStyle
, locale
);
95 fDateTimeFormatter
=dynamic_cast<SimpleDateFormat
*>(df
);
96 if (fDateTimeFormatter
== NULL
) {
97 status
= U_UNSUPPORTED_ERROR
;
100 fDateTimeFormatter
->toPattern(fDatePattern
);
101 if (timeStyle
!= UDAT_NONE
) {
102 df
= createTimeInstance((EStyle
)timeStyle
, locale
);
103 SimpleDateFormat
*sdf
= dynamic_cast<SimpleDateFormat
*>(df
);
105 sdf
->toPattern(fTimePattern
);
110 // does not matter whether timeStyle is UDAT_NONE, we need something for fDateTimeFormatter
111 df
= createTimeInstance((EStyle
)timeStyle
, locale
);
112 fDateTimeFormatter
=dynamic_cast<SimpleDateFormat
*>(df
);
113 if (fDateTimeFormatter
== NULL
) {
114 status
= U_UNSUPPORTED_ERROR
;
117 fDateTimeFormatter
->toPattern(fTimePattern
);
120 // Initialize the parent fCalendar, so that parse() works correctly.
121 initializeCalendar(NULL
, locale
, status
);
125 RelativeDateFormat::~RelativeDateFormat() {
126 delete fDateTimeFormatter
;
127 delete fCombinedFormat
;
129 #if !UCONFIG_NO_BREAK_ITERATION
130 delete fCapitalizationBrkIter
;
135 Format
* RelativeDateFormat::clone(void) const {
136 return new RelativeDateFormat(*this);
139 UBool
RelativeDateFormat::operator==(const Format
& other
) const {
140 if(DateFormat::operator==(other
)) {
141 // The DateFormat::operator== check for fCapitalizationContext equality above
142 // is sufficient to check equality of all derived context-related data.
143 // DateFormat::operator== guarantees following cast is safe
144 RelativeDateFormat
* that
= (RelativeDateFormat
*)&other
;
145 return (fDateStyle
==that
->fDateStyle
&&
146 fDatePattern
==that
->fDatePattern
&&
147 fTimePattern
==that
->fTimePattern
&&
148 fLocale
==that
->fLocale
);
153 static const UChar APOSTROPHE
= (UChar
)0x0027;
155 UnicodeString
& RelativeDateFormat::format( Calendar
& cal
,
156 UnicodeString
& appendTo
,
157 FieldPosition
& pos
) const {
159 UErrorCode status
= U_ZERO_ERROR
;
160 UnicodeString relativeDayString
;
161 UDisplayContext capitalizationContext
= getContext(UDISPCTX_TYPE_CAPITALIZATION
, status
);
163 // calculate the difference, in days, between 'cal' and now.
164 int dayDiff
= dayDifference(cal
, status
);
168 const UChar
*theString
= getStringForDay(dayDiff
, len
, status
);
169 if(U_SUCCESS(status
) && (theString
!=NULL
)) {
170 // found a relative string
171 relativeDayString
.setTo(theString
, len
);
174 if ( relativeDayString
.length() > 0 && !fDatePattern
.isEmpty() &&
175 (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
|| fCombinedHasDateAtStart
)) {
176 #if !UCONFIG_NO_BREAK_ITERATION
177 // capitalize relativeDayString according to context for relative, set formatter no context
178 if ( u_islower(relativeDayString
.char32At(0)) && fCapitalizationBrkIter
!= NULL
&&
179 ( capitalizationContext
==UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE
||
180 (capitalizationContext
==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU
&& fCapitalizationOfRelativeUnitsForUIListMenu
) ||
181 (capitalizationContext
==UDISPCTX_CAPITALIZATION_FOR_STANDALONE
&& fCapitalizationOfRelativeUnitsForStandAlone
) ) ) {
182 // titlecase first word of relativeDayString
183 relativeDayString
.toTitle(fCapitalizationBrkIter
, fLocale
, U_TITLECASE_NO_LOWERCASE
| U_TITLECASE_NO_BREAK_ADJUSTMENT
);
186 fDateTimeFormatter
->setContext(UDISPCTX_CAPITALIZATION_NONE
, status
);
188 // set our context for the formatter
189 fDateTimeFormatter
->setContext(capitalizationContext
, status
);
192 if (fDatePattern
.isEmpty()) {
193 fDateTimeFormatter
->applyPattern(fTimePattern
);
194 fDateTimeFormatter
->format(cal
,appendTo
,pos
);
195 } else if (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
) {
196 if (relativeDayString
.length() > 0) {
197 appendTo
.append(relativeDayString
);
199 fDateTimeFormatter
->applyPattern(fDatePattern
);
200 fDateTimeFormatter
->format(cal
,appendTo
,pos
);
203 UnicodeString datePattern
;
204 if (relativeDayString
.length() > 0) {
205 // Need to quote the relativeDayString to make it a legal date pattern
206 relativeDayString
.findAndReplace(UNICODE_STRING("'", 1), UNICODE_STRING("''", 2)); // double any existing APOSTROPHE
207 relativeDayString
.insert(0, APOSTROPHE
); // add APOSTROPHE at beginning...
208 relativeDayString
.append(APOSTROPHE
); // and at end
209 datePattern
.setTo(relativeDayString
);
211 datePattern
.setTo(fDatePattern
);
213 UnicodeString combinedPattern
;
214 Formattable timeDatePatterns
[] = { fTimePattern
, datePattern
};
215 fCombinedFormat
->format(timeDatePatterns
, 2, combinedPattern
, pos
, status
); // pos is ignored by this
216 fDateTimeFormatter
->applyPattern(combinedPattern
);
217 fDateTimeFormatter
->format(cal
,appendTo
,pos
);
226 RelativeDateFormat::format(const Formattable
& obj
,
227 UnicodeString
& appendTo
,
229 UErrorCode
& status
) const
231 // this is just here to get around the hiding problem
232 // (the previous format() override would hide the version of
233 // format() on DateFormat that this function correspond to, so we
234 // have to redefine it here)
235 return DateFormat::format(obj
, appendTo
, pos
, status
);
239 void RelativeDateFormat::parse( const UnicodeString
& text
,
241 ParsePosition
& pos
) const {
243 int32_t startIndex
= pos
.getIndex();
244 if (fDatePattern
.isEmpty()) {
245 // no date pattern, try parsing as time
246 fDateTimeFormatter
->applyPattern(fTimePattern
);
247 fDateTimeFormatter
->parse(text
,cal
,pos
);
248 } else if (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
) {
249 // no time pattern or way to combine, try parsing as date
250 // first check whether text matches a relativeDayString
251 UBool matchedRelative
= FALSE
;
252 for (int n
=0; n
< fDatesLen
&& !matchedRelative
; n
++) {
253 if (fDates
[n
].string
!= NULL
&&
254 text
.compare(startIndex
, fDates
[n
].len
, fDates
[n
].string
) == 0) {
255 // it matched, handle the relative day string
256 UErrorCode status
= U_ZERO_ERROR
;
257 matchedRelative
= TRUE
;
259 // Set the calendar to now+offset
260 cal
.setTime(Calendar::getNow(),status
);
261 cal
.add(UCAL_DATE
,fDates
[n
].offset
, status
);
263 if(U_FAILURE(status
)) {
264 // failure in setting calendar field, set offset to beginning of rel day string
265 pos
.setErrorIndex(startIndex
);
267 pos
.setIndex(startIndex
+ fDates
[n
].len
);
271 if (!matchedRelative
) {
272 // just parse as normal date
273 fDateTimeFormatter
->applyPattern(fDatePattern
);
274 fDateTimeFormatter
->parse(text
,cal
,pos
);
277 // Here we replace any relativeDayString in text with the equivalent date
278 // formatted per fDatePattern, then parse text normally using the combined pattern.
279 UnicodeString
modifiedText(text
);
281 int32_t dateStart
= 0, origDateLen
= 0, modDateLen
= 0;
282 UErrorCode status
= U_ZERO_ERROR
;
283 for (int n
=0; n
< fDatesLen
; n
++) {
284 int32_t relativeStringOffset
;
285 if (fDates
[n
].string
!= NULL
&&
286 (relativeStringOffset
= modifiedText
.indexOf(fDates
[n
].string
, fDates
[n
].len
, startIndex
)) >= startIndex
) {
287 // it matched, replace the relative date with a real one for parsing
288 UnicodeString dateString
;
289 Calendar
* tempCal
= cal
.clone();
291 // Set the calendar to now+offset
292 tempCal
->setTime(Calendar::getNow(),status
);
293 tempCal
->add(UCAL_DATE
,fDates
[n
].offset
, status
);
294 if(U_FAILURE(status
)) {
295 pos
.setErrorIndex(startIndex
);
300 fDateTimeFormatter
->applyPattern(fDatePattern
);
301 fDateTimeFormatter
->format(*tempCal
, dateString
, fPos
);
302 dateStart
= relativeStringOffset
;
303 origDateLen
= fDates
[n
].len
;
304 modDateLen
= dateString
.length();
305 modifiedText
.replace(dateStart
, origDateLen
, dateString
);
310 UnicodeString combinedPattern
;
311 Formattable timeDatePatterns
[] = { fTimePattern
, fDatePattern
};
312 fCombinedFormat
->format(timeDatePatterns
, 2, combinedPattern
, fPos
, status
); // pos is ignored by this
313 fDateTimeFormatter
->applyPattern(combinedPattern
);
314 fDateTimeFormatter
->parse(modifiedText
,cal
,pos
);
317 UBool noError
= (pos
.getErrorIndex() < 0);
318 int32_t offset
= (noError
)? pos
.getIndex(): pos
.getErrorIndex();
319 if (offset
>= dateStart
+ modDateLen
) {
320 // offset at or after the end of the replaced text,
321 // correct by the difference between original and replacement
322 offset
-= (modDateLen
- origDateLen
);
323 } else if (offset
>= dateStart
) {
324 // offset in the replaced text, set it to the beginning of that text
325 // (i.e. the beginning of the relative day string)
329 pos
.setIndex(offset
);
331 pos
.setErrorIndex(offset
);
337 RelativeDateFormat::parse( const UnicodeString
& text
,
338 ParsePosition
& pos
) const {
339 // redefined here because the other parse() function hides this function's
340 // cunterpart on DateFormat
341 return DateFormat::parse(text
, pos
);
345 RelativeDateFormat::parse(const UnicodeString
& text
, UErrorCode
& status
) const
347 // redefined here because the other parse() function hides this function's
348 // counterpart on DateFormat
349 return DateFormat::parse(text
, status
);
353 const UChar
*RelativeDateFormat::getStringForDay(int32_t day
, int32_t &len
, UErrorCode
&status
) const {
354 if(U_FAILURE(status
)) {
358 // Is it outside the resource bundle's range?
359 if(day
< fDayMin
|| day
> fDayMax
) {
360 return NULL
; // don't have it.
363 // Linear search the held strings
364 for(int n
=0;n
<fDatesLen
;n
++) {
365 if(fDates
[n
].offset
== day
) {
367 return fDates
[n
].string
;
371 return NULL
; // not found.
375 RelativeDateFormat::toPattern(UnicodeString
& result
, UErrorCode
& status
) const
377 if (!U_FAILURE(status
)) {
379 if (fDatePattern
.isEmpty()) {
380 result
.setTo(fTimePattern
);
381 } else if (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
) {
382 result
.setTo(fDatePattern
);
384 Formattable timeDatePatterns
[] = { fTimePattern
, fDatePattern
};
386 fCombinedFormat
->format(timeDatePatterns
, 2, result
, pos
, status
);
393 RelativeDateFormat::toPatternDate(UnicodeString
& result
, UErrorCode
& status
) const
395 if (!U_FAILURE(status
)) {
397 result
.setTo(fDatePattern
);
403 RelativeDateFormat::toPatternTime(UnicodeString
& result
, UErrorCode
& status
) const
405 if (!U_FAILURE(status
)) {
407 result
.setTo(fTimePattern
);
413 RelativeDateFormat::applyPatterns(const UnicodeString
& datePattern
, const UnicodeString
& timePattern
, UErrorCode
&status
)
415 if (!U_FAILURE(status
)) {
416 fDatePattern
.setTo(datePattern
);
417 fTimePattern
.setTo(timePattern
);
421 const DateFormatSymbols
*
422 RelativeDateFormat::getDateFormatSymbols() const
424 return fDateTimeFormatter
->getDateFormatSymbols();
427 // override the DateFormat implementation in order to
428 // lazily initialize relevant items
430 RelativeDateFormat::setContext(UDisplayContext value
, UErrorCode
& status
)
432 DateFormat::setContext(value
, status
);
433 if (U_SUCCESS(status
)) {
434 if (!fCapitalizationInfoSet
&&
435 (value
==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU
|| value
==UDISPCTX_CAPITALIZATION_FOR_STANDALONE
)) {
436 initCapitalizationContextInfo(fLocale
);
437 fCapitalizationInfoSet
= TRUE
;
439 #if !UCONFIG_NO_BREAK_ITERATION
440 if ( fCapitalizationBrkIter
== NULL
&& (value
==UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE
||
441 (value
==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU
&& fCapitalizationOfRelativeUnitsForUIListMenu
) ||
442 (value
==UDISPCTX_CAPITALIZATION_FOR_STANDALONE
&& fCapitalizationOfRelativeUnitsForStandAlone
)) ) {
443 UErrorCode status
= U_ZERO_ERROR
;
444 fCapitalizationBrkIter
= BreakIterator::createSentenceInstance(fLocale
, status
);
445 if (U_FAILURE(status
)) {
446 delete fCapitalizationBrkIter
;
447 fCapitalizationBrkIter
= NULL
;
455 RelativeDateFormat::initCapitalizationContextInfo(const Locale
& thelocale
)
457 #if !UCONFIG_NO_BREAK_ITERATION
458 const char * localeID
= (thelocale
!= NULL
)? thelocale
.getBaseName(): NULL
;
459 UErrorCode status
= U_ZERO_ERROR
;
460 UResourceBundle
*rb
= ures_open(NULL
, localeID
, &status
);
461 rb
= ures_getByKeyWithFallback(rb
, "contextTransforms", rb
, &status
);
462 rb
= ures_getByKeyWithFallback(rb
, "relative", rb
, &status
);
463 if (U_SUCCESS(status
) && rb
!= NULL
) {
465 const int32_t * intVector
= ures_getIntVector(rb
, &len
, &status
);
466 if (U_SUCCESS(status
) && intVector
!= NULL
&& len
>= 2) {
467 fCapitalizationOfRelativeUnitsForUIListMenu
= intVector
[0];
468 fCapitalizationOfRelativeUnitsForStandAlone
= intVector
[1];
475 static const UChar patItem1
[] = {0x7B,0x31,0x7D}; // "{1}"
476 static const int32_t patItem1Len
= 3;
478 void RelativeDateFormat::loadDates(UErrorCode
&status
) {
479 CalendarData
calData(fLocale
, "gregorian", status
);
481 UErrorCode tempStatus
= status
;
482 UResourceBundle
*dateTimePatterns
= calData
.getByKey(DT_DateTimePatternsTag
, tempStatus
);
483 if(U_SUCCESS(tempStatus
)) {
484 int32_t patternsSize
= ures_getSize(dateTimePatterns
);
485 if (patternsSize
> kDateTime
) {
486 int32_t resStrLen
= 0;
488 int32_t glueIndex
= kDateTime
;
489 if (patternsSize
>= (DateFormat::kDateTimeOffset
+ DateFormat::kShort
+ 1)) {
490 // Get proper date time format
491 switch (fDateStyle
) {
494 glueIndex
= kDateTimeOffset
+ kFull
;
498 glueIndex
= kDateTimeOffset
+ kLong
;
500 case kMediumRelative
:
502 glueIndex
= kDateTimeOffset
+ kMedium
;
506 glueIndex
= kDateTimeOffset
+ kShort
;
513 const UChar
*resStr
= ures_getStringByIndex(dateTimePatterns
, glueIndex
, &resStrLen
, &tempStatus
);
514 if (U_SUCCESS(tempStatus
) && resStrLen
>= patItem1Len
&& u_strncmp(resStr
,patItem1
,patItem1Len
)==0) {
515 fCombinedHasDateAtStart
= TRUE
;
517 fCombinedFormat
= new MessageFormat(UnicodeString(TRUE
, resStr
, resStrLen
), fLocale
, tempStatus
);
521 UResourceBundle
*rb
= ures_open(NULL
, fLocale
.getBaseName(), &status
);
522 rb
= ures_getByKeyWithFallback(rb
, "fields", rb
, &status
);
523 rb
= ures_getByKeyWithFallback(rb
, "day", rb
, &status
);
524 rb
= ures_getByKeyWithFallback(rb
, "relative", rb
, &status
);
529 if(U_FAILURE(status
)) {
535 fDatesLen
= ures_getSize(rb
);
536 fDates
= (URelativeString
*) uprv_malloc(sizeof(fDates
[0])*fDatesLen
);
538 // Load in each item into the array...
541 UResourceBundle
*subString
= NULL
;
543 while(ures_hasNext(rb
) && U_SUCCESS(status
)) { // iterate over items
544 subString
= ures_getNextResource(rb
, subString
, &status
);
546 if(U_FAILURE(status
) || (subString
==NULL
)) break;
549 const char *key
= ures_getKey(subString
);
551 // load the string and length
553 const UChar
* aString
= ures_getString(subString
, &aLen
, &status
);
555 if(U_FAILURE(status
) || aString
== NULL
) break;
557 // calculate the offset
558 int32_t offset
= atoi(key
);
561 if(offset
< fDayMin
) {
564 if(offset
> fDayMax
) {
568 // copy the string pointer
569 fDates
[n
].offset
= offset
;
570 fDates
[n
].string
= aString
;
571 fDates
[n
].len
= aLen
;
575 ures_close(subString
);
578 // the fDates[] array could be sorted here, for direct access.
581 //----------------------------------------------------------------------
583 // this should to be in DateFormat, instead it was copied from SimpleDateFormat.
586 RelativeDateFormat::initializeCalendar(TimeZone
* adoptZone
, const Locale
& locale
, UErrorCode
& status
)
588 if(!U_FAILURE(status
)) {
589 fCalendar
= Calendar::createInstance(adoptZone
?adoptZone
:TimeZone::createDefault(), locale
, status
);
591 if (U_SUCCESS(status
) && fCalendar
== NULL
) {
592 status
= U_MEMORY_ALLOCATION_ERROR
;
597 int32_t RelativeDateFormat::dayDifference(Calendar
&cal
, UErrorCode
&status
) {
598 if(U_FAILURE(status
)) {
601 // TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type
602 Calendar
*nowCal
= cal
.clone();
603 nowCal
->setTime(Calendar::getNow(), status
);
605 // For the day difference, we are interested in the difference in the (modified) julian day number
606 // which is midnight to midnight. Using fieldDifference() is NOT correct here, because
607 // 6pm Jan 4th to 10am Jan 5th should be considered "tomorrow".
608 int32_t dayDiff
= cal
.get(UCAL_JULIAN_DAY
, status
) - nowCal
->get(UCAL_JULIAN_DAY
, status
);