1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 *******************************************************************************
5 * Copyright (C) 2007-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
10 #include "unicode/utypes.h"
12 #if !UCONFIG_NO_FORMATTING
16 #include "unicode/datefmt.h"
17 #include "unicode/reldatefmt.h"
18 #include "unicode/simpleformatter.h"
19 #include "unicode/smpdtfmt.h"
20 #include "unicode/udisplaycontext.h"
21 #include "unicode/uchar.h"
22 #include "unicode/brkiter.h"
23 #include "unicode/ucasemap.h"
32 * An array of URelativeString structs is used to store the resource data loaded out of the bundle.
34 struct URelativeString
{
35 int32_t offset
; /** offset of this item, such as, the relative date **/
36 int32_t len
; /** length of the string **/
37 const UChar
* string
; /** string, or NULL if not set **/
40 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RelativeDateFormat
)
42 RelativeDateFormat::RelativeDateFormat(const RelativeDateFormat
& other
) :
43 DateFormat(other
), fDateTimeFormatter(NULL
), fDatePattern(other
.fDatePattern
),
44 fTimePattern(other
.fTimePattern
), fCombinedFormat(NULL
),
45 fDateStyle(other
.fDateStyle
), fLocale(other
.fLocale
),
46 fDatesLen(other
.fDatesLen
), fDates(NULL
),
47 fCombinedHasDateAtStart(other
.fCombinedHasDateAtStart
),
48 fCapitalizationInfoSet(other
.fCapitalizationInfoSet
),
49 fCapitalizationOfRelativeUnitsForUIListMenu(other
.fCapitalizationOfRelativeUnitsForUIListMenu
),
50 fCapitalizationOfRelativeUnitsForStandAlone(other
.fCapitalizationOfRelativeUnitsForStandAlone
),
51 fCapitalizationBrkIter(NULL
)
53 if(other
.fDateTimeFormatter
!= NULL
) {
54 fDateTimeFormatter
= (SimpleDateFormat
*)other
.fDateTimeFormatter
->clone();
56 if(other
.fCombinedFormat
!= NULL
) {
57 fCombinedFormat
= new SimpleFormatter(*other
.fCombinedFormat
);
60 fDates
= (URelativeString
*) uprv_malloc(sizeof(fDates
[0])*(size_t)fDatesLen
);
61 uprv_memcpy(fDates
, other
.fDates
, sizeof(fDates
[0])*(size_t)fDatesLen
);
63 #if !UCONFIG_NO_BREAK_ITERATION
64 if (other
.fCapitalizationBrkIter
!= NULL
) {
65 fCapitalizationBrkIter
= (other
.fCapitalizationBrkIter
)->clone();
70 RelativeDateFormat::RelativeDateFormat( UDateFormatStyle timeStyle
, UDateFormatStyle dateStyle
,
71 const Locale
& locale
, UErrorCode
& status
) :
72 DateFormat(), fDateTimeFormatter(NULL
), fDatePattern(), fTimePattern(), fCombinedFormat(NULL
),
73 fDateStyle(dateStyle
), fLocale(locale
), fDatesLen(0), fDates(NULL
),
74 fCombinedHasDateAtStart(FALSE
), fCapitalizationInfoSet(FALSE
),
75 fCapitalizationOfRelativeUnitsForUIListMenu(FALSE
), fCapitalizationOfRelativeUnitsForStandAlone(FALSE
),
76 fCapitalizationBrkIter(NULL
)
78 if(U_FAILURE(status
) ) {
82 if (timeStyle
< UDAT_NONE
|| timeStyle
> UDAT_SHORT
) {
83 // don't support other time styles (e.g. relative styles), for now
84 status
= U_ILLEGAL_ARGUMENT_ERROR
;
87 UDateFormatStyle baseDateStyle
= (dateStyle
> UDAT_SHORT
)? (UDateFormatStyle
)(dateStyle
& ~UDAT_RELATIVE
): dateStyle
;
89 // Get fDateTimeFormatter from either date or time style (does not matter, we will override the pattern).
90 // We do need to get separate patterns for the date & time styles.
91 if (baseDateStyle
!= UDAT_NONE
) {
92 df
= createDateInstance((EStyle
)baseDateStyle
, locale
);
93 fDateTimeFormatter
=dynamic_cast<SimpleDateFormat
*>(df
);
94 if (fDateTimeFormatter
== NULL
) {
95 status
= U_UNSUPPORTED_ERROR
;
98 fDateTimeFormatter
->toPattern(fDatePattern
);
99 if (timeStyle
!= UDAT_NONE
) {
100 df
= createTimeInstance((EStyle
)timeStyle
, locale
);
101 SimpleDateFormat
*sdf
= dynamic_cast<SimpleDateFormat
*>(df
);
103 sdf
->toPattern(fTimePattern
);
108 // does not matter whether timeStyle is UDAT_NONE, we need something for fDateTimeFormatter
109 df
= createTimeInstance((EStyle
)timeStyle
, locale
);
110 fDateTimeFormatter
=dynamic_cast<SimpleDateFormat
*>(df
);
111 if (fDateTimeFormatter
== NULL
) {
112 status
= U_UNSUPPORTED_ERROR
;
116 fDateTimeFormatter
->toPattern(fTimePattern
);
119 // Initialize the parent fCalendar, so that parse() works correctly.
120 initializeCalendar(NULL
, locale
, status
);
124 RelativeDateFormat::~RelativeDateFormat() {
125 delete fDateTimeFormatter
;
126 delete fCombinedFormat
;
128 #if !UCONFIG_NO_BREAK_ITERATION
129 delete fCapitalizationBrkIter
;
134 Format
* RelativeDateFormat::clone(void) const {
135 return new RelativeDateFormat(*this);
138 UBool
RelativeDateFormat::operator==(const Format
& other
) const {
139 if(DateFormat::operator==(other
)) {
140 // The DateFormat::operator== check for fCapitalizationContext equality above
141 // is sufficient to check equality of all derived context-related data.
142 // DateFormat::operator== guarantees following cast is safe
143 RelativeDateFormat
* that
= (RelativeDateFormat
*)&other
;
144 return (fDateStyle
==that
->fDateStyle
&&
145 fDatePattern
==that
->fDatePattern
&&
146 fTimePattern
==that
->fTimePattern
&&
147 fLocale
==that
->fLocale
);
152 static const UChar APOSTROPHE
= (UChar
)0x0027;
154 UnicodeString
& RelativeDateFormat::format( Calendar
& cal
,
155 UnicodeString
& appendTo
,
156 FieldPosition
& pos
) const {
158 UErrorCode status
= U_ZERO_ERROR
;
159 UnicodeString relativeDayString
;
160 UDisplayContext capitalizationContext
= getContext(UDISPCTX_TYPE_CAPITALIZATION
, status
);
162 // calculate the difference, in days, between 'cal' and now.
163 int dayDiff
= dayDifference(cal
, status
);
167 const UChar
*theString
= getStringForDay(dayDiff
, len
, status
);
168 if(U_SUCCESS(status
) && (theString
!=NULL
)) {
169 // found a relative string
170 relativeDayString
.setTo(theString
, len
);
173 if ( relativeDayString
.length() > 0 && !fDatePattern
.isEmpty() &&
174 (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
|| fCombinedHasDateAtStart
)) {
175 #if !UCONFIG_NO_BREAK_ITERATION
176 // capitalize relativeDayString according to context for relative, set formatter no context
177 if ( u_islower(relativeDayString
.char32At(0)) && fCapitalizationBrkIter
!= NULL
&&
178 ( capitalizationContext
==UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE
||
179 (capitalizationContext
==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU
&& fCapitalizationOfRelativeUnitsForUIListMenu
) ||
180 (capitalizationContext
==UDISPCTX_CAPITALIZATION_FOR_STANDALONE
&& fCapitalizationOfRelativeUnitsForStandAlone
) ) ) {
181 // titlecase first word of relativeDayString
182 relativeDayString
.toTitle(fCapitalizationBrkIter
, fLocale
, U_TITLECASE_NO_LOWERCASE
| U_TITLECASE_NO_BREAK_ADJUSTMENT
);
185 fDateTimeFormatter
->setContext(UDISPCTX_CAPITALIZATION_NONE
, status
);
187 // set our context for the formatter
188 fDateTimeFormatter
->setContext(capitalizationContext
, status
);
191 if (fDatePattern
.isEmpty()) {
192 fDateTimeFormatter
->applyPattern(fTimePattern
);
193 fDateTimeFormatter
->format(cal
,appendTo
,pos
);
194 } else if (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
) {
195 if (relativeDayString
.length() > 0) {
196 appendTo
.append(relativeDayString
);
198 fDateTimeFormatter
->applyPattern(fDatePattern
);
199 fDateTimeFormatter
->format(cal
,appendTo
,pos
);
202 UnicodeString datePattern
;
203 if (relativeDayString
.length() > 0) {
204 // Need to quote the relativeDayString to make it a legal date pattern
205 relativeDayString
.findAndReplace(UNICODE_STRING("'", 1), UNICODE_STRING("''", 2)); // double any existing APOSTROPHE
206 relativeDayString
.insert(0, APOSTROPHE
); // add APOSTROPHE at beginning...
207 relativeDayString
.append(APOSTROPHE
); // and at end
208 datePattern
.setTo(relativeDayString
);
210 datePattern
.setTo(fDatePattern
);
212 UnicodeString combinedPattern
;
213 fCombinedFormat
->format(fTimePattern
, datePattern
, combinedPattern
, status
);
214 fDateTimeFormatter
->applyPattern(combinedPattern
);
215 fDateTimeFormatter
->format(cal
,appendTo
,pos
);
224 RelativeDateFormat::format(const Formattable
& obj
,
225 UnicodeString
& appendTo
,
227 UErrorCode
& status
) const
229 // this is just here to get around the hiding problem
230 // (the previous format() override would hide the version of
231 // format() on DateFormat that this function correspond to, so we
232 // have to redefine it here)
233 return DateFormat::format(obj
, appendTo
, pos
, status
);
237 void RelativeDateFormat::parse( const UnicodeString
& text
,
239 ParsePosition
& pos
) const {
241 int32_t startIndex
= pos
.getIndex();
242 if (fDatePattern
.isEmpty()) {
243 // no date pattern, try parsing as time
244 fDateTimeFormatter
->applyPattern(fTimePattern
);
245 fDateTimeFormatter
->parse(text
,cal
,pos
);
246 } else if (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
) {
247 // no time pattern or way to combine, try parsing as date
248 // first check whether text matches a relativeDayString
249 UBool matchedRelative
= FALSE
;
250 for (int n
=0; n
< fDatesLen
&& !matchedRelative
; n
++) {
251 if (fDates
[n
].string
!= NULL
&&
252 text
.compare(startIndex
, fDates
[n
].len
, fDates
[n
].string
) == 0) {
253 // it matched, handle the relative day string
254 UErrorCode status
= U_ZERO_ERROR
;
255 matchedRelative
= TRUE
;
257 // Set the calendar to now+offset
258 cal
.setTime(Calendar::getNow(),status
);
259 cal
.add(UCAL_DATE
,fDates
[n
].offset
, status
);
261 if(U_FAILURE(status
)) {
262 // failure in setting calendar field, set offset to beginning of rel day string
263 pos
.setErrorIndex(startIndex
);
265 pos
.setIndex(startIndex
+ fDates
[n
].len
);
269 if (!matchedRelative
) {
270 // just parse as normal date
271 fDateTimeFormatter
->applyPattern(fDatePattern
);
272 fDateTimeFormatter
->parse(text
,cal
,pos
);
275 // Here we replace any relativeDayString in text with the equivalent date
276 // formatted per fDatePattern, then parse text normally using the combined pattern.
277 UnicodeString
modifiedText(text
);
279 int32_t dateStart
= 0, origDateLen
= 0, modDateLen
= 0;
280 UErrorCode status
= U_ZERO_ERROR
;
281 for (int n
=0; n
< fDatesLen
; n
++) {
282 int32_t relativeStringOffset
;
283 if (fDates
[n
].string
!= NULL
&&
284 (relativeStringOffset
= modifiedText
.indexOf(fDates
[n
].string
, fDates
[n
].len
, startIndex
)) >= startIndex
) {
285 // it matched, replace the relative date with a real one for parsing
286 UnicodeString dateString
;
287 Calendar
* tempCal
= cal
.clone();
289 // Set the calendar to now+offset
290 tempCal
->setTime(Calendar::getNow(),status
);
291 tempCal
->add(UCAL_DATE
,fDates
[n
].offset
, status
);
292 if(U_FAILURE(status
)) {
293 pos
.setErrorIndex(startIndex
);
298 fDateTimeFormatter
->applyPattern(fDatePattern
);
299 fDateTimeFormatter
->format(*tempCal
, dateString
, fPos
);
300 dateStart
= relativeStringOffset
;
301 origDateLen
= fDates
[n
].len
;
302 modDateLen
= dateString
.length();
303 modifiedText
.replace(dateStart
, origDateLen
, dateString
);
308 UnicodeString combinedPattern
;
309 fCombinedFormat
->format(fTimePattern
, fDatePattern
, combinedPattern
, status
);
310 fDateTimeFormatter
->applyPattern(combinedPattern
);
311 fDateTimeFormatter
->parse(modifiedText
,cal
,pos
);
314 UBool noError
= (pos
.getErrorIndex() < 0);
315 int32_t offset
= (noError
)? pos
.getIndex(): pos
.getErrorIndex();
316 if (offset
>= dateStart
+ modDateLen
) {
317 // offset at or after the end of the replaced text,
318 // correct by the difference between original and replacement
319 offset
-= (modDateLen
- origDateLen
);
320 } else if (offset
>= dateStart
) {
321 // offset in the replaced text, set it to the beginning of that text
322 // (i.e. the beginning of the relative day string)
326 pos
.setIndex(offset
);
328 pos
.setErrorIndex(offset
);
334 RelativeDateFormat::parse( const UnicodeString
& text
,
335 ParsePosition
& pos
) const {
336 // redefined here because the other parse() function hides this function's
337 // cunterpart on DateFormat
338 return DateFormat::parse(text
, pos
);
342 RelativeDateFormat::parse(const UnicodeString
& text
, UErrorCode
& status
) const
344 // redefined here because the other parse() function hides this function's
345 // counterpart on DateFormat
346 return DateFormat::parse(text
, status
);
350 const UChar
*RelativeDateFormat::getStringForDay(int32_t day
, int32_t &len
, UErrorCode
&status
) const {
351 if(U_FAILURE(status
)) {
355 // Is it inside the resource bundle's range?
356 int n
= day
+ UDAT_DIRECTION_THIS
;
357 if (n
>= 0 && n
< fDatesLen
) {
358 if (fDates
[n
].offset
== day
&& fDates
[n
].string
!= NULL
) {
360 return fDates
[n
].string
;
363 return NULL
; // not found.
367 RelativeDateFormat::toPattern(UnicodeString
& result
, UErrorCode
& status
) const
369 if (!U_FAILURE(status
)) {
371 if (fDatePattern
.isEmpty()) {
372 result
.setTo(fTimePattern
);
373 } else if (fTimePattern
.isEmpty() || fCombinedFormat
== NULL
) {
374 result
.setTo(fDatePattern
);
376 fCombinedFormat
->format(fTimePattern
, fDatePattern
, result
, status
);
383 RelativeDateFormat::toPatternDate(UnicodeString
& result
, UErrorCode
& status
) const
385 if (!U_FAILURE(status
)) {
387 result
.setTo(fDatePattern
);
393 RelativeDateFormat::toPatternTime(UnicodeString
& result
, UErrorCode
& status
) const
395 if (!U_FAILURE(status
)) {
397 result
.setTo(fTimePattern
);
403 RelativeDateFormat::applyPatterns(const UnicodeString
& datePattern
, const UnicodeString
& timePattern
, UErrorCode
&status
)
405 if (!U_FAILURE(status
)) {
406 fDatePattern
.setTo(datePattern
);
407 fTimePattern
.setTo(timePattern
);
411 const DateFormatSymbols
*
412 RelativeDateFormat::getDateFormatSymbols() const
414 return fDateTimeFormatter
->getDateFormatSymbols();
417 // override the DateFormat implementation in order to
418 // lazily initialize relevant items
420 RelativeDateFormat::setContext(UDisplayContext value
, UErrorCode
& status
)
422 DateFormat::setContext(value
, status
);
423 if (U_SUCCESS(status
)) {
424 if (!fCapitalizationInfoSet
&&
425 (value
==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU
|| value
==UDISPCTX_CAPITALIZATION_FOR_STANDALONE
)) {
426 initCapitalizationContextInfo(fLocale
);
427 fCapitalizationInfoSet
= TRUE
;
429 #if !UCONFIG_NO_BREAK_ITERATION
430 if ( fCapitalizationBrkIter
== NULL
&& (value
==UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE
||
431 (value
==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU
&& fCapitalizationOfRelativeUnitsForUIListMenu
) ||
432 (value
==UDISPCTX_CAPITALIZATION_FOR_STANDALONE
&& fCapitalizationOfRelativeUnitsForStandAlone
)) ) {
433 UErrorCode status
= U_ZERO_ERROR
;
434 fCapitalizationBrkIter
= BreakIterator::createSentenceInstance(fLocale
, status
);
435 if (U_FAILURE(status
)) {
436 delete fCapitalizationBrkIter
;
437 fCapitalizationBrkIter
= NULL
;
445 RelativeDateFormat::initCapitalizationContextInfo(const Locale
& thelocale
)
447 #if !UCONFIG_NO_BREAK_ITERATION
448 const char * localeID
= (thelocale
!= NULL
)? thelocale
.getBaseName(): NULL
;
449 UErrorCode status
= U_ZERO_ERROR
;
450 LocalUResourceBundlePointer
rb(ures_open(NULL
, localeID
, &status
));
451 ures_getByKeyWithFallback(rb
.getAlias(),
452 "contextTransforms/relative",
453 rb
.getAlias(), &status
);
454 if (U_SUCCESS(status
) && rb
!= NULL
) {
456 const int32_t * intVector
= ures_getIntVector(rb
.getAlias(),
458 if (U_SUCCESS(status
) && intVector
!= NULL
&& len
>= 2) {
459 fCapitalizationOfRelativeUnitsForUIListMenu
= intVector
[0];
460 fCapitalizationOfRelativeUnitsForStandAlone
= intVector
[1];
469 * Sink for getting data from fields/day/relative data.
470 * For loading relative day names, e.g., "yesterday", "today".
473 struct RelDateFmtDataSink
: public ResourceSink
{
474 URelativeString
*fDatesPtr
;
477 RelDateFmtDataSink(URelativeString
* fDates
, int32_t len
) : fDatesPtr(fDates
), fDatesLen(len
) {
478 for (int32_t i
= 0; i
< fDatesLen
; ++i
) {
479 fDatesPtr
[i
].offset
= 0;
480 fDatesPtr
[i
].string
= NULL
;
481 fDatesPtr
[i
].len
= -1;
485 virtual ~RelDateFmtDataSink();
487 virtual void put(const char *key
, ResourceValue
&value
,
488 UBool
/*noFallback*/, UErrorCode
&errorCode
) {
489 ResourceTable relDayTable
= value
.getTable(errorCode
);
492 for (int32_t i
= 0; relDayTable
.getKeyAndValue(i
, key
, value
); ++i
) {
493 // Find the relative offset.
494 int32_t offset
= atoi(key
);
496 // Put in the proper spot, but don't override existing data.
497 n
= offset
+ UDAT_DIRECTION_THIS
; // Converts to index in UDAT_R
498 if (n
< fDatesLen
&& fDatesPtr
[n
].string
== NULL
) {
499 // Not found and n is an empty slot.
500 fDatesPtr
[n
].offset
= offset
;
501 fDatesPtr
[n
].string
= value
.getString(len
, errorCode
);
502 fDatesPtr
[n
].len
= len
;
509 // Virtual destructors must be defined out of line.
510 RelDateFmtDataSink::~RelDateFmtDataSink() {}
515 static const UChar patItem1
[] = {0x7B,0x31,0x7D}; // "{1}"
516 static const int32_t patItem1Len
= 3;
518 void RelativeDateFormat::loadDates(UErrorCode
&status
) {
519 UResourceBundle
*rb
= ures_open(NULL
, fLocale
.getBaseName(), &status
);
520 LocalUResourceBundlePointer
dateTimePatterns(
521 ures_getByKeyWithFallback(rb
,
522 "calendar/gregorian/DateTimePatterns",
523 (UResourceBundle
*)NULL
, &status
));
524 if(U_SUCCESS(status
)) {
525 int32_t patternsSize
= ures_getSize(dateTimePatterns
.getAlias());
526 if (patternsSize
> kDateTime
) {
527 int32_t resStrLen
= 0;
528 int32_t glueIndex
= kDateTime
;
529 if (patternsSize
>= (kDateTimeOffset
+ kShort
+ 1)) {
530 int32_t offsetIncrement
= (fDateStyle
& ~kRelative
); // Remove relative bit.
531 if (offsetIncrement
>= (int32_t)kFull
&&
532 offsetIncrement
<= (int32_t)kShortRelative
) {
533 glueIndex
= kDateTimeOffset
+ offsetIncrement
;
537 const UChar
*resStr
= ures_getStringByIndex(dateTimePatterns
.getAlias(), glueIndex
, &resStrLen
, &status
);
538 if (U_SUCCESS(status
) && resStrLen
>= patItem1Len
&& u_strncmp(resStr
,patItem1
,patItem1Len
)==0) {
539 fCombinedHasDateAtStart
= TRUE
;
541 fCombinedFormat
= new SimpleFormatter(UnicodeString(TRUE
, resStr
, resStrLen
), 2, 2, status
);
545 // Data loading for relative names, e.g., "yesterday", "today", "tomorrow".
546 fDatesLen
= UDAT_DIRECTION_COUNT
; // Maximum defined by data.
547 fDates
= (URelativeString
*) uprv_malloc(sizeof(fDates
[0])*fDatesLen
);
549 RelDateFmtDataSink
sink(fDates
, fDatesLen
);
550 ures_getAllItemsWithFallback(rb
, "fields/day/relative", sink
, status
);
554 if(U_FAILURE(status
)) {
560 //----------------------------------------------------------------------
562 // this should to be in DateFormat, instead it was copied from SimpleDateFormat.
565 RelativeDateFormat::initializeCalendar(TimeZone
* adoptZone
, const Locale
& locale
, UErrorCode
& status
)
567 if(!U_FAILURE(status
)) {
568 fCalendar
= Calendar::createInstance(adoptZone
?adoptZone
:TimeZone::createDefault(), locale
, status
);
570 if (U_SUCCESS(status
) && fCalendar
== NULL
) {
571 status
= U_MEMORY_ALLOCATION_ERROR
;
576 int32_t RelativeDateFormat::dayDifference(Calendar
&cal
, UErrorCode
&status
) {
577 if(U_FAILURE(status
)) {
580 // TODO: Cache the nowCal to avoid heap allocs? Would be difficult, don't know the calendar type
581 Calendar
*nowCal
= cal
.clone();
582 nowCal
->setTime(Calendar::getNow(), status
);
584 // For the day difference, we are interested in the difference in the (modified) julian day number
585 // which is midnight to midnight. Using fieldDifference() is NOT correct here, because
586 // 6pm Jan 4th to 10am Jan 5th should be considered "tomorrow".
587 int32_t dayDiff
= cal
.get(UCAL_JULIAN_DAY
, status
) - nowCal
->get(UCAL_JULIAN_DAY
, status
);
595 #endif /* !UCONFIG_NO_FORMATTING */