2 *******************************************************************************
3 * Copyright (C) 2007-2016, 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/simpleformatter.h"
17 #include "unicode/smpdtfmt.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
= new SimpleFormatter(*other
.fCombinedFormat
);
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 fCombinedFormat
->format(fTimePattern
, datePattern
, combinedPattern
, status
);
215 fDateTimeFormatter
->applyPattern(combinedPattern
);
216 fDateTimeFormatter
->format(cal
,appendTo
,pos
);
225 RelativeDateFormat::format(const Formattable
& obj
,
226 UnicodeString
& appendTo
,
228 UErrorCode
& status
) const
230 // this is just here to get around the hiding problem
231 // (the previous format() override would hide the version of
232 // format() on DateFormat that this function correspond to, so we
233 // have to redefine it here)
234 return DateFormat::format(obj
, appendTo
, pos
, status
);
238 void RelativeDateFormat::parse( const UnicodeString
& text
,
240 ParsePosition
& pos
) const {
242 int32_t startIndex
= pos
.getIndex();
243 if (fDatePattern
.isEmpty()) {
244 // no date pattern, try parsing as time
245 fDateTimeFormatter
->applyPattern(fTimePattern
);
246 fDateTimeFormatter
->parse(text
,cal
,pos
);
247 } else if (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
) {
248 // no time pattern or way to combine, try parsing as date
249 // first check whether text matches a relativeDayString
250 UBool matchedRelative
= FALSE
;
251 for (int n
=0; n
< fDatesLen
&& !matchedRelative
; n
++) {
252 if (fDates
[n
].string
!= NULL
&&
253 text
.compare(startIndex
, fDates
[n
].len
, fDates
[n
].string
) == 0) {
254 // it matched, handle the relative day string
255 UErrorCode status
= U_ZERO_ERROR
;
256 matchedRelative
= TRUE
;
258 // Set the calendar to now+offset
259 cal
.setTime(Calendar::getNow(),status
);
260 cal
.add(UCAL_DATE
,fDates
[n
].offset
, status
);
262 if(U_FAILURE(status
)) {
263 // failure in setting calendar field, set offset to beginning of rel day string
264 pos
.setErrorIndex(startIndex
);
266 pos
.setIndex(startIndex
+ fDates
[n
].len
);
270 if (!matchedRelative
) {
271 // just parse as normal date
272 fDateTimeFormatter
->applyPattern(fDatePattern
);
273 fDateTimeFormatter
->parse(text
,cal
,pos
);
276 // Here we replace any relativeDayString in text with the equivalent date
277 // formatted per fDatePattern, then parse text normally using the combined pattern.
278 UnicodeString
modifiedText(text
);
280 int32_t dateStart
= 0, origDateLen
= 0, modDateLen
= 0;
281 UErrorCode status
= U_ZERO_ERROR
;
282 for (int n
=0; n
< fDatesLen
; n
++) {
283 int32_t relativeStringOffset
;
284 if (fDates
[n
].string
!= NULL
&&
285 (relativeStringOffset
= modifiedText
.indexOf(fDates
[n
].string
, fDates
[n
].len
, startIndex
)) >= startIndex
) {
286 // it matched, replace the relative date with a real one for parsing
287 UnicodeString dateString
;
288 Calendar
* tempCal
= cal
.clone();
290 // Set the calendar to now+offset
291 tempCal
->setTime(Calendar::getNow(),status
);
292 tempCal
->add(UCAL_DATE
,fDates
[n
].offset
, status
);
293 if(U_FAILURE(status
)) {
294 pos
.setErrorIndex(startIndex
);
299 fDateTimeFormatter
->applyPattern(fDatePattern
);
300 fDateTimeFormatter
->format(*tempCal
, dateString
, fPos
);
301 dateStart
= relativeStringOffset
;
302 origDateLen
= fDates
[n
].len
;
303 modDateLen
= dateString
.length();
304 modifiedText
.replace(dateStart
, origDateLen
, dateString
);
309 UnicodeString combinedPattern
;
310 fCombinedFormat
->format(fTimePattern
, fDatePattern
, combinedPattern
, status
);
311 fDateTimeFormatter
->applyPattern(combinedPattern
);
312 fDateTimeFormatter
->parse(modifiedText
,cal
,pos
);
315 UBool noError
= (pos
.getErrorIndex() < 0);
316 int32_t offset
= (noError
)? pos
.getIndex(): pos
.getErrorIndex();
317 if (offset
>= dateStart
+ modDateLen
) {
318 // offset at or after the end of the replaced text,
319 // correct by the difference between original and replacement
320 offset
-= (modDateLen
- origDateLen
);
321 } else if (offset
>= dateStart
) {
322 // offset in the replaced text, set it to the beginning of that text
323 // (i.e. the beginning of the relative day string)
327 pos
.setIndex(offset
);
329 pos
.setErrorIndex(offset
);
335 RelativeDateFormat::parse( const UnicodeString
& text
,
336 ParsePosition
& pos
) const {
337 // redefined here because the other parse() function hides this function's
338 // cunterpart on DateFormat
339 return DateFormat::parse(text
, pos
);
343 RelativeDateFormat::parse(const UnicodeString
& text
, UErrorCode
& status
) const
345 // redefined here because the other parse() function hides this function's
346 // counterpart on DateFormat
347 return DateFormat::parse(text
, status
);
351 const UChar
*RelativeDateFormat::getStringForDay(int32_t day
, int32_t &len
, UErrorCode
&status
) const {
352 if(U_FAILURE(status
)) {
356 // Is it outside the resource bundle's range?
357 if(day
< fDayMin
|| day
> fDayMax
) {
358 return NULL
; // don't have it.
361 // Linear search the held strings
362 for(int n
=0;n
<fDatesLen
;n
++) {
363 if(fDates
[n
].offset
== day
) {
365 return fDates
[n
].string
;
369 return NULL
; // not found.
373 RelativeDateFormat::toPattern(UnicodeString
& result
, UErrorCode
& status
) const
375 if (!U_FAILURE(status
)) {
377 if (fDatePattern
.isEmpty()) {
378 result
.setTo(fTimePattern
);
379 } else if (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
) {
380 result
.setTo(fDatePattern
);
382 fCombinedFormat
->format(fTimePattern
, fDatePattern
, result
, status
);
389 RelativeDateFormat::toPatternDate(UnicodeString
& result
, UErrorCode
& status
) const
391 if (!U_FAILURE(status
)) {
393 result
.setTo(fDatePattern
);
399 RelativeDateFormat::toPatternTime(UnicodeString
& result
, UErrorCode
& status
) const
401 if (!U_FAILURE(status
)) {
403 result
.setTo(fTimePattern
);
409 RelativeDateFormat::applyPatterns(const UnicodeString
& datePattern
, const UnicodeString
& timePattern
, UErrorCode
&status
)
411 if (!U_FAILURE(status
)) {
412 fDatePattern
.setTo(datePattern
);
413 fTimePattern
.setTo(timePattern
);
417 const DateFormatSymbols
*
418 RelativeDateFormat::getDateFormatSymbols() const
420 return fDateTimeFormatter
->getDateFormatSymbols();
423 // override the DateFormat implementation in order to
424 // lazily initialize relevant items
426 RelativeDateFormat::setContext(UDisplayContext value
, UErrorCode
& status
)
428 DateFormat::setContext(value
, status
);
429 if (U_SUCCESS(status
)) {
430 if (!fCapitalizationInfoSet
&&
431 (value
==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU
|| value
==UDISPCTX_CAPITALIZATION_FOR_STANDALONE
)) {
432 initCapitalizationContextInfo(fLocale
);
433 fCapitalizationInfoSet
= TRUE
;
435 #if !UCONFIG_NO_BREAK_ITERATION
436 if ( fCapitalizationBrkIter
== NULL
&& (value
==UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE
||
437 (value
==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU
&& fCapitalizationOfRelativeUnitsForUIListMenu
) ||
438 (value
==UDISPCTX_CAPITALIZATION_FOR_STANDALONE
&& fCapitalizationOfRelativeUnitsForStandAlone
)) ) {
439 UErrorCode status
= U_ZERO_ERROR
;
440 fCapitalizationBrkIter
= BreakIterator::createSentenceInstance(fLocale
, status
);
441 if (U_FAILURE(status
)) {
442 delete fCapitalizationBrkIter
;
443 fCapitalizationBrkIter
= NULL
;
451 RelativeDateFormat::initCapitalizationContextInfo(const Locale
& thelocale
)
453 #if !UCONFIG_NO_BREAK_ITERATION
454 const char * localeID
= (thelocale
!= NULL
)? thelocale
.getBaseName(): NULL
;
455 UErrorCode status
= U_ZERO_ERROR
;
456 UResourceBundle
*rb
= ures_open(NULL
, localeID
, &status
);
457 rb
= ures_getByKeyWithFallback(rb
, "contextTransforms", rb
, &status
);
458 rb
= ures_getByKeyWithFallback(rb
, "relative", rb
, &status
);
459 if (U_SUCCESS(status
) && rb
!= NULL
) {
461 const int32_t * intVector
= ures_getIntVector(rb
, &len
, &status
);
462 if (U_SUCCESS(status
) && intVector
!= NULL
&& len
>= 2) {
463 fCapitalizationOfRelativeUnitsForUIListMenu
= intVector
[0];
464 fCapitalizationOfRelativeUnitsForStandAlone
= intVector
[1];
471 static const UChar patItem1
[] = {0x7B,0x31,0x7D}; // "{1}"
472 static const int32_t patItem1Len
= 3;
474 void RelativeDateFormat::loadDates(UErrorCode
&status
) {
475 CalendarData
calData(fLocale
, "gregorian", status
);
477 UErrorCode tempStatus
= status
;
478 UResourceBundle
*dateTimePatterns
= calData
.getByKey(DT_DateTimePatternsTag
, tempStatus
);
479 if(U_SUCCESS(tempStatus
)) {
480 int32_t patternsSize
= ures_getSize(dateTimePatterns
);
481 if (patternsSize
> kDateTime
) {
482 int32_t resStrLen
= 0;
484 int32_t glueIndex
= kDateTime
;
485 if (patternsSize
>= (DateFormat::kDateTimeOffset
+ DateFormat::kShort
+ 1)) {
486 // Get proper date time format
487 switch (fDateStyle
) {
490 glueIndex
= kDateTimeOffset
+ kFull
;
494 glueIndex
= kDateTimeOffset
+ kLong
;
496 case kMediumRelative
:
498 glueIndex
= kDateTimeOffset
+ kMedium
;
502 glueIndex
= kDateTimeOffset
+ kShort
;
509 const UChar
*resStr
= ures_getStringByIndex(dateTimePatterns
, glueIndex
, &resStrLen
, &tempStatus
);
510 if (U_SUCCESS(tempStatus
) && resStrLen
>= patItem1Len
&& u_strncmp(resStr
,patItem1
,patItem1Len
)==0) {
511 fCombinedHasDateAtStart
= TRUE
;
513 fCombinedFormat
= new SimpleFormatter(UnicodeString(TRUE
, resStr
, resStrLen
), 2, 2, tempStatus
);
517 UResourceBundle
*rb
= ures_open(NULL
, fLocale
.getBaseName(), &status
);
518 rb
= ures_getByKeyWithFallback(rb
, "fields", rb
, &status
);
519 rb
= ures_getByKeyWithFallback(rb
, "day", rb
, &status
);
520 rb
= ures_getByKeyWithFallback(rb
, "relative", rb
, &status
);
525 if(U_FAILURE(status
)) {
531 fDatesLen
= ures_getSize(rb
);
532 fDates
= (URelativeString
*) uprv_malloc(sizeof(fDates
[0])*fDatesLen
);
534 // Load in each item into the array...
537 UResourceBundle
*subString
= NULL
;
539 while(ures_hasNext(rb
) && U_SUCCESS(status
)) { // iterate over items
540 subString
= ures_getNextResource(rb
, subString
, &status
);
542 if(U_FAILURE(status
) || (subString
==NULL
)) break;
545 const char *key
= ures_getKey(subString
);
547 // load the string and length
549 const UChar
* aString
= ures_getString(subString
, &aLen
, &status
);
551 if(U_FAILURE(status
) || aString
== NULL
) break;
553 // calculate the offset
554 int32_t offset
= atoi(key
);
557 if(offset
< fDayMin
) {
560 if(offset
> fDayMax
) {
564 // copy the string pointer
565 fDates
[n
].offset
= offset
;
566 fDates
[n
].string
= aString
;
567 fDates
[n
].len
= aLen
;
571 ures_close(subString
);
574 // the fDates[] array could be sorted here, for direct access.
577 //----------------------------------------------------------------------
579 // this should to be in DateFormat, instead it was copied from SimpleDateFormat.
582 RelativeDateFormat::initializeCalendar(TimeZone
* adoptZone
, const Locale
& locale
, UErrorCode
& status
)
584 if(!U_FAILURE(status
)) {
585 fCalendar
= Calendar::createInstance(adoptZone
?adoptZone
:TimeZone::createDefault(), locale
, status
);
587 if (U_SUCCESS(status
) && fCalendar
== NULL
) {
588 status
= U_MEMORY_ALLOCATION_ERROR
;
593 int32_t RelativeDateFormat::dayDifference(Calendar
&cal
, UErrorCode
&status
) {
594 if(U_FAILURE(status
)) {
597 // TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type
598 Calendar
*nowCal
= cal
.clone();
599 nowCal
->setTime(Calendar::getNow(), status
);
601 // For the day difference, we are interested in the difference in the (modified) julian day number
602 // which is midnight to midnight. Using fieldDifference() is NOT correct here, because
603 // 6pm Jan 4th to 10am Jan 5th should be considered "tomorrow".
604 int32_t dayDiff
= cal
.get(UCAL_JULIAN_DAY
, status
) - nowCal
->get(UCAL_JULIAN_DAY
, status
);