1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*******************************************************************************
4 * Copyright (C) 2008-2016, International Business Machines Corporation and
5 * others. All Rights Reserved.
6 *******************************************************************************
10 *******************************************************************************
13 #include "utypeinfo.h" // for 'typeid' to work
15 #include "unicode/dtitvfmt.h"
17 #if !UCONFIG_NO_FORMATTING
19 //TODO: put in compilation
20 //#define DTITVFMT_DEBUG 1
22 #include "unicode/calendar.h"
23 #include "unicode/dtptngen.h"
24 #include "unicode/dtitvinf.h"
25 #include "unicode/simpleformatter.h"
28 #include "dtitv_impl.h"
31 #include "formattedval_impl.h"
33 #include "unicode/udateintervalformat.h"
44 #define PRINTMESG(msg) { std::cout << "(" << __FILE__ << ":" << __LINE__ << ") " << msg << "\n"; }
48 static const UChar gDateFormatSkeleton
[][11] = {
50 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, CAP_M
, CAP_E
, CAP_E
, CAP_E
, CAP_E
, LOW_D
, 0},
52 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, CAP_M
, LOW_D
, 0},
54 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, LOW_D
, 0},
56 {LOW_Y
, CAP_M
, LOW_D
, 0} };
59 static const char gCalendarTag
[] = "calendar";
60 static const char gGregorianTag
[] = "gregorian";
61 static const char gDateTimePatternsTag
[] = "DateTimePatterns";
65 static const UChar gLaterFirstPrefix
[] = {LOW_L
, LOW_A
, LOW_T
, LOW_E
, LOW_S
,LOW_T
, CAP_F
, LOW_I
, LOW_R
, LOW_S
, LOW_T
, COLON
};
68 static const UChar gEarlierFirstPrefix
[] = {LOW_E
, LOW_A
, LOW_R
, LOW_L
, LOW_I
, LOW_E
, LOW_S
, LOW_T
, CAP_F
, LOW_I
, LOW_R
, LOW_S
, LOW_T
, COLON
};
71 class FormattedDateIntervalData
: public FormattedValueFieldPositionIteratorImpl
{
73 FormattedDateIntervalData(UErrorCode
& status
) : FormattedValueFieldPositionIteratorImpl(5, status
) {}
74 virtual ~FormattedDateIntervalData();
77 FormattedDateIntervalData::~FormattedDateIntervalData() = default;
79 UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedDateInterval
)
82 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalFormat
)
84 // Mutex, protects access to fDateFormat, fFromCalendar and fToCalendar.
85 // Needed because these data members are modified by const methods of DateIntervalFormat.
87 static UMutex
*gFormatterMutex() {
88 static UMutex
*m
= STATIC_NEW(UMutex
);
92 DateIntervalFormat
* U_EXPORT2
93 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
95 return createInstance(skeleton
, Locale::getDefault(), status
);
99 DateIntervalFormat
* U_EXPORT2
100 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
101 const Locale
& locale
,
102 UErrorCode
& status
) {
103 #ifdef DTITVFMT_DEBUG
107 skeleton
.extract(0, skeleton
.length(), result
, "UTF-8");
109 ((SimpleDateFormat
*)dtfmt
)->toPattern(pat
);
110 pat
.extract(0, pat
.length(), result_1
, "UTF-8");
111 sprintf(mesg
, "skeleton: %s; pattern: %s\n", result
, result_1
);
115 DateIntervalInfo
* dtitvinf
= new DateIntervalInfo(locale
, status
);
116 return create(locale
, dtitvinf
, &skeleton
, status
);
121 DateIntervalFormat
* U_EXPORT2
122 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
123 const DateIntervalInfo
& dtitvinf
,
124 UErrorCode
& status
) {
125 return createInstance(skeleton
, Locale::getDefault(), dtitvinf
, status
);
129 DateIntervalFormat
* U_EXPORT2
130 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
131 const Locale
& locale
,
132 const DateIntervalInfo
& dtitvinf
,
133 UErrorCode
& status
) {
134 DateIntervalInfo
* ptn
= dtitvinf
.clone();
135 return create(locale
, ptn
, &skeleton
, status
);
139 DateIntervalFormat::DateIntervalFormat()
144 fLocale(Locale::getRoot()),
147 fDateTimeFormat(NULL
),
148 fMinimizeType(UDTITVFMT_MINIMIZE_NONE
),
149 fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE
)
153 DateIntervalFormat::DateIntervalFormat(const DateIntervalFormat
& itvfmt
)
159 fLocale(itvfmt
.fLocale
),
162 fDateTimeFormat(NULL
),
163 fMinimizeType(UDTITVFMT_MINIMIZE_NONE
),
164 fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE
) {
170 DateIntervalFormat::operator=(const DateIntervalFormat
& itvfmt
) {
171 if ( this != &itvfmt
) {
174 delete fFromCalendar
;
178 delete fDateTimeFormat
;
180 Mutex
lock(gFormatterMutex());
181 if ( itvfmt
.fDateFormat
) {
182 fDateFormat
= (SimpleDateFormat
*)itvfmt
.fDateFormat
->clone();
186 if ( itvfmt
.fFromCalendar
) {
187 fFromCalendar
= itvfmt
.fFromCalendar
->clone();
189 fFromCalendar
= NULL
;
191 if ( itvfmt
.fToCalendar
) {
192 fToCalendar
= itvfmt
.fToCalendar
->clone();
197 if ( itvfmt
.fInfo
) {
198 fInfo
= itvfmt
.fInfo
->clone();
202 fSkeleton
= itvfmt
.fSkeleton
;
204 for ( i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
205 fIntervalPatterns
[i
] = itvfmt
.fIntervalPatterns
[i
];
207 fLocale
= itvfmt
.fLocale
;
208 fDatePattern
= (itvfmt
.fDatePattern
)? (UnicodeString
*)itvfmt
.fDatePattern
->clone(): NULL
;
209 fTimePattern
= (itvfmt
.fTimePattern
)? (UnicodeString
*)itvfmt
.fTimePattern
->clone(): NULL
;
210 fDateTimeFormat
= (itvfmt
.fDateTimeFormat
)? (UnicodeString
*)itvfmt
.fDateTimeFormat
->clone(): NULL
;
211 fMinimizeType
= itvfmt
.fMinimizeType
;
212 fCapitalizationContext
= itvfmt
.fCapitalizationContext
;
218 DateIntervalFormat::~DateIntervalFormat() {
221 delete fFromCalendar
;
225 delete fDateTimeFormat
;
230 DateIntervalFormat::clone(void) const {
231 return new DateIntervalFormat(*this);
236 DateIntervalFormat::operator==(const Format
& other
) const {
237 if (typeid(*this) != typeid(other
)) {return FALSE
;}
238 const DateIntervalFormat
* fmt
= (DateIntervalFormat
*)&other
;
239 if (this == fmt
) {return TRUE
;}
240 if (!Format::operator==(other
)) {return FALSE
;}
241 if ((fInfo
!= fmt
->fInfo
) && (fInfo
== NULL
|| fmt
->fInfo
== NULL
)) {return FALSE
;}
242 if (fInfo
&& fmt
->fInfo
&& (*fInfo
!= *fmt
->fInfo
)) {return FALSE
;}
244 Mutex
lock(gFormatterMutex());
245 if (fDateFormat
!= fmt
->fDateFormat
&& (fDateFormat
== NULL
|| fmt
->fDateFormat
== NULL
)) {return FALSE
;}
246 if (fDateFormat
&& fmt
->fDateFormat
&& (*fDateFormat
!= *fmt
->fDateFormat
)) {return FALSE
;}
248 // note: fFromCalendar and fToCalendar hold no persistent state, and therefore do not participate in operator ==.
249 // fDateFormat has the master calendar for the DateIntervalFormat.
250 if (fSkeleton
!= fmt
->fSkeleton
) {return FALSE
;}
251 if (fDatePattern
!= fmt
->fDatePattern
&& (fDatePattern
== NULL
|| fmt
->fDatePattern
== NULL
)) {return FALSE
;}
252 if (fDatePattern
&& fmt
->fDatePattern
&& (*fDatePattern
!= *fmt
->fDatePattern
)) {return FALSE
;}
253 if (fTimePattern
!= fmt
->fTimePattern
&& (fTimePattern
== NULL
|| fmt
->fTimePattern
== NULL
)) {return FALSE
;}
254 if (fTimePattern
&& fmt
->fTimePattern
&& (*fTimePattern
!= *fmt
->fTimePattern
)) {return FALSE
;}
255 if (fDateTimeFormat
!= fmt
->fDateTimeFormat
&& (fDateTimeFormat
== NULL
|| fmt
->fDateTimeFormat
== NULL
)) {return FALSE
;}
256 if (fDateTimeFormat
&& fmt
->fDateTimeFormat
&& (*fDateTimeFormat
!= *fmt
->fDateTimeFormat
)) {return FALSE
;}
257 if (fLocale
!= fmt
->fLocale
) {return FALSE
;}
259 for (int32_t i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
260 if (fIntervalPatterns
[i
].firstPart
!= fmt
->fIntervalPatterns
[i
].firstPart
) {return FALSE
;}
261 if (fIntervalPatterns
[i
].secondPart
!= fmt
->fIntervalPatterns
[i
].secondPart
) {return FALSE
;}
262 if (fIntervalPatterns
[i
].laterDateFirst
!= fmt
->fIntervalPatterns
[i
].laterDateFirst
) {return FALSE
;}
264 if (fMinimizeType
!= fmt
->fMinimizeType
) {return FALSE
;}
265 if (fCapitalizationContext
!= fmt
->fCapitalizationContext
) {return FALSE
;}
271 DateIntervalFormat::format(const Formattable
& obj
,
272 UnicodeString
& appendTo
,
273 FieldPosition
& fieldPosition
,
274 UErrorCode
& status
) const {
275 if ( U_FAILURE(status
) ) {
279 if ( obj
.getType() == Formattable::kObject
) {
280 const UObject
* formatObj
= obj
.getObject();
281 const DateInterval
* interval
= dynamic_cast<const DateInterval
*>(formatObj
);
282 if (interval
!= NULL
) {
283 return format(interval
, appendTo
, fieldPosition
, status
);
286 status
= U_ILLEGAL_ARGUMENT_ERROR
;
292 DateIntervalFormat::format(const DateInterval
* dtInterval
,
293 UnicodeString
& appendTo
,
294 FieldPosition
& fieldPosition
,
295 UErrorCode
& status
) const {
296 if ( U_FAILURE(status
) ) {
299 if (fDateFormat
== NULL
|| fInfo
== NULL
) {
300 status
= U_INVALID_STATE_ERROR
;
304 FieldPositionOnlyHandler
handler(fieldPosition
);
305 handler
.setAcceptFirstOnly(TRUE
);
308 Mutex
lock(gFormatterMutex());
309 return formatIntervalImpl(*dtInterval
, appendTo
, ignore
, handler
, status
);
313 FormattedDateInterval
DateIntervalFormat::formatToValue(
314 const DateInterval
& dtInterval
,
315 UErrorCode
& status
) const {
316 LocalPointer
<FormattedDateIntervalData
> result(new FormattedDateIntervalData(status
), status
);
317 if (U_FAILURE(status
)) {
318 return FormattedDateInterval(status
);
320 UnicodeString string
;
322 auto handler
= result
->getHandler(status
);
323 handler
.setCategory(UFIELD_CATEGORY_DATE
);
325 Mutex
lock(gFormatterMutex());
326 formatIntervalImpl(dtInterval
, string
, firstIndex
, handler
, status
);
328 handler
.getError(status
);
329 result
->appendString(string
, status
);
330 if (U_FAILURE(status
)) {
331 return FormattedDateInterval(status
);
334 // Compute the span fields and sort them into place:
335 if (firstIndex
!= -1) {
336 result
->addOverlapSpans(UFIELD_CATEGORY_DATE_INTERVAL_SPAN
, firstIndex
, status
);
337 if (U_FAILURE(status
)) {
338 return FormattedDateInterval(status
);
343 return FormattedDateInterval(result
.orphan());
348 DateIntervalFormat::format(Calendar
& fromCalendar
,
349 Calendar
& toCalendar
,
350 UnicodeString
& appendTo
,
352 UErrorCode
& status
) const {
353 FieldPositionOnlyHandler
handler(pos
);
354 handler
.setAcceptFirstOnly(TRUE
);
357 Mutex
lock(gFormatterMutex());
358 return formatImpl(fromCalendar
, toCalendar
, appendTo
, ignore
, handler
, status
);
362 FormattedDateInterval
DateIntervalFormat::formatToValue(
363 Calendar
& fromCalendar
,
364 Calendar
& toCalendar
,
365 UErrorCode
& status
) const {
366 LocalPointer
<FormattedDateIntervalData
> result(new FormattedDateIntervalData(status
), status
);
367 if (U_FAILURE(status
)) {
368 return FormattedDateInterval(status
);
370 UnicodeString string
;
372 auto handler
= result
->getHandler(status
);
373 handler
.setCategory(UFIELD_CATEGORY_DATE
);
375 Mutex
lock(gFormatterMutex());
376 formatImpl(fromCalendar
, toCalendar
, string
, firstIndex
, handler
, status
);
378 handler
.getError(status
);
379 result
->appendString(string
, status
);
380 if (U_FAILURE(status
)) {
381 return FormattedDateInterval(status
);
384 // Compute the span fields and sort them into place:
385 if (firstIndex
!= -1) {
386 result
->addOverlapSpans(UFIELD_CATEGORY_DATE_INTERVAL_SPAN
, firstIndex
, status
);
390 return FormattedDateInterval(result
.orphan());
394 UnicodeString
& DateIntervalFormat::formatIntervalImpl(
395 const DateInterval
& dtInterval
,
396 UnicodeString
& appendTo
,
398 FieldPositionHandler
& fphandler
,
399 UErrorCode
& status
) const {
400 if (U_FAILURE(status
)) {
403 if (fFromCalendar
== nullptr || fToCalendar
== nullptr) {
404 status
= U_INVALID_STATE_ERROR
;
407 fFromCalendar
->setTime(dtInterval
.getFromDate(), status
);
408 fToCalendar
->setTime(dtInterval
.getToDate(), status
);
409 return formatImpl(*fFromCalendar
, *fToCalendar
, appendTo
, firstIndex
, fphandler
, status
);
414 DateIntervalFormat::formatImpl(Calendar
& fromCalendar
,
415 Calendar
& toCalendar
,
416 UnicodeString
& appendTo
,
418 FieldPositionHandler
& fphandler
,
419 UErrorCode
& status
) const {
420 if ( U_FAILURE(status
) ) {
424 // Initialize firstIndex to -1 (single date, no range)
427 // not support different calendar types and time zones
428 //if ( fromCalendar.getType() != toCalendar.getType() ) {
429 if ( !fromCalendar
.isEquivalentTo(toCalendar
) ) {
430 status
= U_ILLEGAL_ARGUMENT_ERROR
;
434 // First, find the largest different calendar field.
435 UCalendarDateFields field
= UCAL_FIELD_COUNT
;
436 UChar patternDay
= 0x0064; // d
437 UChar patternYear
= 0x0079; // y
439 if ( fromCalendar
.get(UCAL_ERA
,status
) != toCalendar
.get(UCAL_ERA
,status
)) {
441 } else if ( fromCalendar
.get(UCAL_YEAR
, status
) !=
442 toCalendar
.get(UCAL_YEAR
, status
) ) {
444 if (fMinimizeType
== UDTITVFMT_MINIMIZE_ADJACENT_MONTHS
&& fSkeleton
.indexOf(patternDay
) >= 0 && fSkeleton
.indexOf(patternYear
) < 0) {
445 UDate fromDate
= fromCalendar
.getTime(status
);
446 UDate toDate
= toCalendar
.getTime(status
);
447 int32_t fromDay
= fromCalendar
.get(UCAL_DATE
, status
);
448 int32_t toDay
= toCalendar
.get(UCAL_DATE
, status
);
449 fromCalendar
.add(UCAL_MONTH
, 1, status
);
450 if ( fromDate
< toDate
&& fromCalendar
.getTime(status
) > toDate
&& fromDay
> toDay
) {
453 fromCalendar
.setTime(fromDate
, status
);
455 } else if ( fromCalendar
.get(UCAL_MONTH
, status
) !=
456 toCalendar
.get(UCAL_MONTH
, status
) ) {
458 if (fMinimizeType
== UDTITVFMT_MINIMIZE_ADJACENT_MONTHS
&& fSkeleton
.indexOf(patternDay
) >= 0) {
459 UDate fromDate
= fromCalendar
.getTime(status
);
460 UDate toDate
= toCalendar
.getTime(status
);
461 int32_t fromDay
= fromCalendar
.get(UCAL_DATE
, status
);
462 int32_t toDay
= toCalendar
.get(UCAL_DATE
, status
);
463 fromCalendar
.add(UCAL_MONTH
, 1, status
);
464 if ( fromDate
< toDate
&& fromCalendar
.getTime(status
) > toDate
&& fromDay
> toDay
) {
467 fromCalendar
.setTime(fromDate
, status
);
469 } else if ( fromCalendar
.get(UCAL_DATE
, status
) !=
470 toCalendar
.get(UCAL_DATE
, status
) ) {
472 if (fMinimizeType
== UDTITVFMT_MINIMIZE_ADJACENT_DAYS
&&
473 // check normalized skeleton for 'H', 'h', 'j'
474 (fSkeleton
.indexOf(0x0048) >= 0 || fSkeleton
.indexOf(0x0068) >= 0 || fSkeleton
.indexOf(0x006A) >= 0)) {
475 UDate fromDate
= fromCalendar
.getTime(status
);
476 UDate toDate
= toCalendar
.getTime(status
);
477 int32_t fromHour
= fromCalendar
.get(UCAL_HOUR
, status
);
478 int32_t toHour
= toCalendar
.get(UCAL_HOUR
, status
);
479 fromCalendar
.add(UCAL_HOUR_OF_DAY
, 12, status
);
480 if ( fromDate
< toDate
&& fromCalendar
.getTime(status
) > toDate
&& fromHour
> toHour
) {
483 fromCalendar
.setTime(fromDate
, status
);
485 } else if ( fromCalendar
.get(UCAL_AM_PM
, status
) !=
486 toCalendar
.get(UCAL_AM_PM
, status
) ) {
488 } else if ( fromCalendar
.get(UCAL_HOUR
, status
) !=
489 toCalendar
.get(UCAL_HOUR
, status
) ) {
491 } else if ( fromCalendar
.get(UCAL_MINUTE
, status
) !=
492 toCalendar
.get(UCAL_MINUTE
, status
) ) {
494 } else if ( fromCalendar
.get(UCAL_SECOND
, status
) !=
495 toCalendar
.get(UCAL_SECOND
, status
) ) {
499 if ( U_FAILURE(status
) ) {
502 if ( field
== UCAL_FIELD_COUNT
) {
503 /* ignore the millisecond etc. small fields' difference.
504 * use single date when all the above are the same.
506 fDateFormat
->setContext(fCapitalizationContext
, status
);
507 return fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
509 UBool fromToOnSameDay
= (field
==UCAL_AM_PM
|| field
==UCAL_HOUR
|| field
==UCAL_MINUTE
|| field
==UCAL_SECOND
);
511 // following call should not set wrong status,
512 // all the pass-in fields are valid till here
513 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
515 const PatternInfo
& intervalPattern
= fIntervalPatterns
[itvPtnIndex
];
517 if ( intervalPattern
.firstPart
.isEmpty() &&
518 intervalPattern
.secondPart
.isEmpty() ) {
519 if ( fDateFormat
->isFieldUnitIgnored(field
) ) {
520 /* the largest different calendar field is small than
521 * the smallest calendar field in pattern,
522 * return single date format.
524 fDateFormat
->setContext(fCapitalizationContext
, status
);
525 return fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
527 return fallbackFormat(fromCalendar
, toCalendar
, fromToOnSameDay
, appendTo
, firstIndex
, fphandler
, status
);
529 // If the first part in interval pattern is empty,
530 // the 2nd part of it saves the full-pattern used in fall-back.
531 // For a 'real' interval pattern, the first part will never be empty.
532 if ( intervalPattern
.firstPart
.isEmpty() ) {
534 UnicodeString originalPattern
;
535 fDateFormat
->toPattern(originalPattern
);
536 fDateFormat
->applyPattern(intervalPattern
.secondPart
);
537 appendTo
= fallbackFormat(fromCalendar
, toCalendar
, fromToOnSameDay
, appendTo
, firstIndex
, fphandler
, status
);
538 fDateFormat
->applyPattern(originalPattern
);
543 if ( intervalPattern
.laterDateFirst
) {
544 firstCal
= &toCalendar
;
545 secondCal
= &fromCalendar
;
548 firstCal
= &fromCalendar
;
549 secondCal
= &toCalendar
;
552 // break the interval pattern into 2 parts,
553 // first part should not be empty,
554 UnicodeString originalPattern
;
555 fDateFormat
->toPattern(originalPattern
);
556 fDateFormat
->applyPattern(intervalPattern
.firstPart
);
557 fDateFormat
->setContext(fCapitalizationContext
, status
);
558 fDateFormat
->_format(*firstCal
, appendTo
, fphandler
, status
);
560 if ( !intervalPattern
.secondPart
.isEmpty() ) {
561 fDateFormat
->applyPattern(intervalPattern
.secondPart
);
562 fDateFormat
->setContext(UDISPCTX_CAPITALIZATION_NONE
, status
);
563 fDateFormat
->_format(*secondCal
, appendTo
, fphandler
, status
);
565 fDateFormat
->applyPattern(originalPattern
);
572 DateIntervalFormat::parseObject(const UnicodeString
& /* source */,
573 Formattable
& /* result */,
574 ParsePosition
& /* parse_pos */) const {
575 // parseObject(const UnicodeString&, Formattable&, UErrorCode&) const
576 // will set status as U_INVALID_FORMAT_ERROR if
577 // parse_pos is still 0
583 const DateIntervalInfo
*
584 DateIntervalFormat::getDateIntervalInfo() const {
590 DateIntervalFormat::setDateIntervalInfo(const DateIntervalInfo
& newItvPattern
,
591 UErrorCode
& status
) {
593 fInfo
= new DateIntervalInfo(newItvPattern
);
595 // Delete patterns that get reset by initializePattern
600 delete fDateTimeFormat
;
601 fDateTimeFormat
= NULL
;
604 initializePattern(status
);
611 DateIntervalFormat::getDateFormat() const {
617 DateIntervalFormat::adoptTimeZone(TimeZone
* zone
)
619 if (fDateFormat
!= NULL
) {
620 fDateFormat
->adoptTimeZone(zone
);
622 // The fDateFormat has the master calendar for the DateIntervalFormat and has
623 // ownership of any adopted TimeZone; fFromCalendar and fToCalendar are internal
624 // work clones of that calendar (and should not also be given ownership of the
625 // adopted TimeZone).
627 fFromCalendar
->setTimeZone(*zone
);
630 fToCalendar
->setTimeZone(*zone
);
635 DateIntervalFormat::setTimeZone(const TimeZone
& zone
)
637 if (fDateFormat
!= NULL
) {
638 fDateFormat
->setTimeZone(zone
);
640 // The fDateFormat has the master calendar for the DateIntervalFormat;
641 // fFromCalendar and fToCalendar are internal work clones of that calendar.
643 fFromCalendar
->setTimeZone(zone
);
646 fToCalendar
->setTimeZone(zone
);
651 DateIntervalFormat::getTimeZone() const
653 if (fDateFormat
!= NULL
) {
654 Mutex
lock(gFormatterMutex());
655 return fDateFormat
->getTimeZone();
657 // If fDateFormat is NULL (unexpected), create default timezone.
658 return *(TimeZone::createDefault());
662 DateIntervalFormat::setAttribute(UDateIntervalFormatAttribute attr
,
663 UDateIntervalFormatAttributeValue value
,
666 if ( U_FAILURE(status
) ) {
669 if (attr
== UDTITVFMT_MINIMIZE_TYPE
) {
670 fMinimizeType
= value
;
672 status
= U_ILLEGAL_ARGUMENT_ERROR
;
677 DateIntervalFormat::setContext(UDisplayContext value
, UErrorCode
& status
)
679 if (U_FAILURE(status
))
681 if ( (UDisplayContextType
)((uint32_t)value
>> 8) == UDISPCTX_TYPE_CAPITALIZATION
) {
682 fCapitalizationContext
= value
;
684 status
= U_ILLEGAL_ARGUMENT_ERROR
;
689 DateIntervalFormat::getContext(UDisplayContextType type
, UErrorCode
& status
) const
691 if (U_FAILURE(status
))
692 return (UDisplayContext
)0;
693 if (type
!= UDISPCTX_TYPE_CAPITALIZATION
) {
694 status
= U_ILLEGAL_ARGUMENT_ERROR
;
695 return (UDisplayContext
)0;
697 return fCapitalizationContext
;
700 DateIntervalFormat::DateIntervalFormat(const Locale
& locale
,
701 DateIntervalInfo
* dtItvInfo
,
702 const UnicodeString
* skeleton
,
711 fDateTimeFormat(NULL
),
712 fMinimizeType(UDTITVFMT_MINIMIZE_NONE
),
713 fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE
)
715 LocalPointer
<DateIntervalInfo
> info(dtItvInfo
, status
);
716 LocalPointer
<SimpleDateFormat
> dtfmt(static_cast<SimpleDateFormat
*>(
717 DateFormat::createInstanceForSkeleton(*skeleton
, locale
, status
)), status
);
718 if (U_FAILURE(status
)) {
723 fSkeleton
= *skeleton
;
725 fInfo
= info
.orphan();
726 fDateFormat
= dtfmt
.orphan();
727 if ( fDateFormat
->getCalendar() ) {
728 fFromCalendar
= fDateFormat
->getCalendar()->clone();
729 fToCalendar
= fDateFormat
->getCalendar()->clone();
731 initializePattern(status
);
734 DateIntervalFormat
* U_EXPORT2
735 DateIntervalFormat::create(const Locale
& locale
,
736 DateIntervalInfo
* dtitvinf
,
737 const UnicodeString
* skeleton
,
738 UErrorCode
& status
) {
739 DateIntervalFormat
* f
= new DateIntervalFormat(locale
, dtitvinf
,
742 status
= U_MEMORY_ALLOCATION_ERROR
;
744 } else if ( U_FAILURE(status
) ) {
745 // safe to delete f, although nothing acutally is saved
755 * Initialize interval patterns locale to this formatter
757 * This code is a bit complicated since
758 * 1. the interval patterns saved in resource bundle files are interval
759 * patterns based on date or time only.
760 * It does not have interval patterns based on both date and time.
761 * Interval patterns on both date and time are algorithm generated.
763 * For example, it has interval patterns on skeleton "dMy" and "hm",
764 * but it does not have interval patterns on skeleton "dMyhm".
766 * The rule to genearte interval patterns for both date and time skeleton are
767 * 1) when the year, month, or day differs, concatenate the two original
768 * expressions with a separator between,
769 * For example, interval pattern from "Jan 10, 2007 10:10 am"
770 * to "Jan 11, 2007 10:10am" is
771 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
773 * 2) otherwise, present the date followed by the range expression
775 * For example, interval pattern from "Jan 10, 2007 10:10 am"
776 * to "Jan 10, 2007 11:10am" is
777 * "Jan 10, 2007 10:10 am - 11:10am"
779 * 2. even a pattern does not request a certion calendar field,
780 * the interval pattern needs to include such field if such fields are
781 * different between 2 dates.
782 * For example, a pattern/skeleton is "hm", but the interval pattern
783 * includes year, month, and date when year, month, and date differs.
785 * @param status output param set to success/failure code on exit
789 DateIntervalFormat::initializePattern(UErrorCode
& status
) {
790 if ( U_FAILURE(status
) ) {
793 const Locale
& locale
= fDateFormat
->getSmpFmtLocale();
794 if ( fSkeleton
.isEmpty() ) {
795 UnicodeString fullPattern
;
796 fDateFormat
->toPattern(fullPattern
);
797 #ifdef DTITVFMT_DEBUG
801 fSkeleton
.extract(0, fSkeleton
.length(), result
, "UTF-8");
802 sprintf(mesg
, "in getBestSkeleton: fSkeleton: %s; \n", result
);
805 // fSkeleton is already set by createDateIntervalInstance()
806 // or by createInstance(UnicodeString skeleton, .... )
807 fSkeleton
= DateTimePatternGenerator::staticGetSkeleton(
808 fullPattern
, status
);
809 if ( U_FAILURE(status
) ) {
814 // initialize the fIntervalPattern ordering
816 for ( i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
817 fIntervalPatterns
[i
].laterDateFirst
= fInfo
->getDefaultOrder();
820 /* Check whether the skeleton is a combination of date and time.
821 * For the complication reason 1 explained above.
823 UnicodeString dateSkeleton
;
824 UnicodeString timeSkeleton
;
825 UnicodeString normalizedTimeSkeleton
;
826 UnicodeString normalizedDateSkeleton
;
829 /* the difference between time skeleton and normalizedTimeSkeleton are:
830 * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true)
831 * 2. 'a' is omitted in normalized time skeleton.
832 * 3. there is only one appearance for 'h' or 'H', 'm','v', 'z' in normalized
835 * The difference between date skeleton and normalizedDateSkeleton are:
836 * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
837 * 2. 'E' and 'EE' are normalized into 'EEE'
838 * 3. 'MM' is normalized into 'M'
840 getDateTimeSkeleton(fSkeleton
, dateSkeleton
, normalizedDateSkeleton
,
841 timeSkeleton
, normalizedTimeSkeleton
);
843 #ifdef DTITVFMT_DEBUG
847 fSkeleton
.extract(0, fSkeleton
.length(), result
, "UTF-8");
848 sprintf(mesg
, "in getBestSkeleton: fSkeleton: %s; \n", result
);
852 // move this up here since we need it for fallbacks
853 if ( timeSkeleton
.length() > 0 && dateSkeleton
.length() > 0 ) {
854 // Need the Date/Time pattern for concatenation of the date
855 // with the time interval.
856 // The date/time pattern ( such as {0} {1} ) is saved in
857 // calendar, that is why need to get the CalendarData here.
858 LocalUResourceBundlePointer
dateTimePatternsRes(ures_open(NULL
, locale
.getBaseName(), &status
));
859 ures_getByKey(dateTimePatternsRes
.getAlias(), gCalendarTag
,
860 dateTimePatternsRes
.getAlias(), &status
);
861 ures_getByKeyWithFallback(dateTimePatternsRes
.getAlias(), gGregorianTag
,
862 dateTimePatternsRes
.getAlias(), &status
);
863 ures_getByKeyWithFallback(dateTimePatternsRes
.getAlias(), gDateTimePatternsTag
,
864 dateTimePatternsRes
.getAlias(), &status
);
866 int32_t dateTimeFormatLength
;
867 const UChar
* dateTimeFormat
= ures_getStringByIndex(
868 dateTimePatternsRes
.getAlias(),
869 (int32_t)DateFormat::kDateTime
,
870 &dateTimeFormatLength
, &status
);
871 if ( U_SUCCESS(status
) && dateTimeFormatLength
>= 3 ) {
872 fDateTimeFormat
= new UnicodeString(dateTimeFormat
, dateTimeFormatLength
);
876 UBool found
= setSeparateDateTimePtn(normalizedDateSkeleton
,
877 normalizedTimeSkeleton
);
879 // for skeletons with seconds, found is false and we enter this block
880 if ( found
== false ) {
882 // TODO: if user asks "m"(minute), but "d"(day) differ
883 if ( timeSkeleton
.length() != 0 ) {
884 if ( dateSkeleton
.length() == 0 ) {
886 timeSkeleton
.insert(0, gDateFormatSkeleton
[DateFormat::kShort
], -1);
887 UnicodeString pattern
= DateFormat::getBestPattern(
888 locale
, timeSkeleton
, status
);
889 if ( U_FAILURE(status
) ) {
892 // for fall back interval patterns,
893 // the first part of the pattern is empty,
894 // the second part of the pattern is the full-pattern
895 // should be used in fall-back.
896 setPatternInfo(UCAL_DATE
, NULL
, &pattern
, fInfo
->getDefaultOrder());
897 setPatternInfo(UCAL_MONTH
, NULL
, &pattern
, fInfo
->getDefaultOrder());
898 setPatternInfo(UCAL_YEAR
, NULL
, &pattern
, fInfo
->getDefaultOrder());
906 } // end of skeleton not found
907 // interval patterns for skeleton are found in resource
908 if ( timeSkeleton
.length() == 0 ) {
910 } else if ( dateSkeleton
.length() == 0 ) {
912 timeSkeleton
.insert(0, gDateFormatSkeleton
[DateFormat::kShort
], -1);
913 UnicodeString pattern
= DateFormat::getBestPattern(
914 locale
, timeSkeleton
, status
);
915 if ( U_FAILURE(status
) ) {
918 // for fall back interval patterns,
919 // the first part of the pattern is empty,
920 // the second part of the pattern is the full-pattern
921 // should be used in fall-back.
922 setPatternInfo(UCAL_DATE
, NULL
, &pattern
, fInfo
->getDefaultOrder());
923 setPatternInfo(UCAL_MONTH
, NULL
, &pattern
, fInfo
->getDefaultOrder());
924 setPatternInfo(UCAL_YEAR
, NULL
, &pattern
, fInfo
->getDefaultOrder());
927 * 1) when the year, month, or day differs,
928 * concatenate the two original expressions with a separator between,
929 * 2) otherwise, present the date followed by the
930 * range expression for the time.
933 * 1) when the year, month, or day differs,
934 * concatenate the two original expressions with a separator between,
936 // if field exists, use fall back
937 UnicodeString skeleton
= fSkeleton
;
938 if ( !fieldExistsInSkeleton(UCAL_DATE
, dateSkeleton
) ) {
939 // prefix skeleton with 'd'
940 skeleton
.insert(0, LOW_D
);
941 setFallbackPattern(UCAL_DATE
, skeleton
, status
);
943 if ( !fieldExistsInSkeleton(UCAL_MONTH
, dateSkeleton
) ) {
944 // then prefix skeleton with 'M'
945 skeleton
.insert(0, CAP_M
);
946 setFallbackPattern(UCAL_MONTH
, skeleton
, status
);
948 if ( !fieldExistsInSkeleton(UCAL_YEAR
, dateSkeleton
) ) {
949 // then prefix skeleton with 'y'
950 skeleton
.insert(0, LOW_Y
);
951 setFallbackPattern(UCAL_YEAR
, skeleton
, status
);
955 * 2) otherwise, present the date followed by the
956 * range expression for the time.
959 if ( fDateTimeFormat
== NULL
) {
960 // earlier failure getting dateTimeFormat
964 UnicodeString datePattern
= DateFormat::getBestPattern(
965 locale
, dateSkeleton
, status
);
967 concatSingleDate2TimeInterval(*fDateTimeFormat
, datePattern
, UCAL_AM_PM
, status
);
968 concatSingleDate2TimeInterval(*fDateTimeFormat
, datePattern
, UCAL_HOUR
, status
);
969 concatSingleDate2TimeInterval(*fDateTimeFormat
, datePattern
, UCAL_MINUTE
, status
);
976 DateIntervalFormat::getDateTimeSkeleton(const UnicodeString
& skeleton
,
977 UnicodeString
& dateSkeleton
,
978 UnicodeString
& normalizedDateSkeleton
,
979 UnicodeString
& timeSkeleton
,
980 UnicodeString
& normalizedTimeSkeleton
) {
981 // dateSkeleton follows the sequence of y*M*E*d*
982 // timeSkeleton follows the sequence of hm*[v|z]?
994 for (i
= 0; i
< skeleton
.length(); ++i
) {
995 UChar ch
= skeleton
[i
];
998 dateSkeleton
.append(ch
);
1002 dateSkeleton
.append(ch
);
1006 dateSkeleton
.append(ch
);
1010 dateSkeleton
.append(ch
);
1029 normalizedDateSkeleton
.append(ch
);
1030 dateSkeleton
.append(ch
);
1033 // 'a' is implicitly handled
1034 timeSkeleton
.append(ch
);
1037 timeSkeleton
.append(ch
);
1041 timeSkeleton
.append(ch
);
1045 timeSkeleton
.append(ch
);
1050 timeSkeleton
.append(ch
);
1054 timeSkeleton
.append(ch
);
1064 timeSkeleton
.append(ch
);
1065 normalizedTimeSkeleton
.append(ch
);
1070 /* generate normalized form for date*/
1071 if ( yCount
!= 0 ) {
1072 for (i
= 0; i
< yCount
; ++i
) {
1073 normalizedDateSkeleton
.append(LOW_Y
);
1076 if ( MCount
!= 0 ) {
1078 normalizedDateSkeleton
.append(CAP_M
);
1080 for ( int32_t j
= 0; j
< MCount
&& j
< MAX_M_COUNT
; ++j
) {
1081 normalizedDateSkeleton
.append(CAP_M
);
1085 if ( ECount
!= 0 ) {
1086 if ( ECount
<= 3 ) {
1087 normalizedDateSkeleton
.append(CAP_E
);
1089 for ( int32_t j
= 0; j
< ECount
&& j
< MAX_E_COUNT
; ++j
) {
1090 normalizedDateSkeleton
.append(CAP_E
);
1094 if ( dCount
!= 0 ) {
1095 normalizedDateSkeleton
.append(LOW_D
);
1098 /* generate normalized form for time */
1099 if ( HCount
!= 0 ) {
1100 normalizedTimeSkeleton
.append(CAP_H
);
1102 else if ( hCount
!= 0 ) {
1103 normalizedTimeSkeleton
.append(LOW_H
);
1105 if ( mCount
!= 0 ) {
1106 normalizedTimeSkeleton
.append(LOW_M
);
1108 if ( zCount
!= 0 ) {
1109 normalizedTimeSkeleton
.append(LOW_Z
);
1111 if ( vCount
!= 0 ) {
1112 normalizedTimeSkeleton
.append(LOW_V
);
1118 * Generate date or time interval pattern from resource,
1119 * and set them into the interval pattern locale to this formatter.
1121 * It needs to handle the following:
1122 * 1. need to adjust field width.
1123 * For example, the interval patterns saved in DateIntervalInfo
1124 * includes "dMMMy", but not "dMMMMy".
1125 * Need to get interval patterns for dMMMMy from dMMMy.
1126 * Another example, the interval patterns saved in DateIntervalInfo
1127 * includes "hmv", but not "hmz".
1128 * Need to get interval patterns for "hmz' from 'hmv'
1130 * 2. there might be no pattern for 'y' differ for skeleton "Md",
1131 * in order to get interval patterns for 'y' differ,
1132 * need to look for it from skeleton 'yMd'
1134 * @param dateSkeleton normalized date skeleton
1135 * @param timeSkeleton normalized time skeleton
1136 * @return whether the resource is found for the skeleton.
1137 * TRUE if interval pattern found for the skeleton,
1142 DateIntervalFormat::setSeparateDateTimePtn(
1143 const UnicodeString
& dateSkeleton
,
1144 const UnicodeString
& timeSkeleton
) {
1145 const UnicodeString
* skeleton
;
1146 // if both date and time skeleton present,
1147 // the final interval pattern might include time interval patterns
1148 // ( when, am_pm, hour, minute differ ),
1149 // but not date interval patterns ( when year, month, day differ ).
1150 // For year/month/day differ, it falls back to fall-back pattern.
1151 if ( timeSkeleton
.length() != 0 ) {
1152 skeleton
= &timeSkeleton
;
1154 skeleton
= &dateSkeleton
;
1157 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
1158 * are defined in resource,
1159 * interval patterns for skeleton "dMMMMy" are calculated by
1160 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
1161 * 2. get the interval patterns for "dMMMy",
1162 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
1163 * getBestSkeleton() is step 1.
1165 // best skeleton, and the difference information
1166 int8_t differenceInfo
= 0;
1167 const UnicodeString
* bestSkeleton
= fInfo
->getBestSkeleton(*skeleton
,
1169 /* best skeleton could be NULL.
1170 For example: in "ca" resource file,
1171 interval format is defined as following
1173 fallback{"{0} - {1}"}
1175 there is no skeletons/interval patterns defined,
1176 and the best skeleton match could be NULL
1178 if ( bestSkeleton
== NULL
) {
1182 // Set patterns for fallback use, need to do this
1183 // before returning if differenceInfo == -1
1185 if ( dateSkeleton
.length() != 0) {
1186 status
= U_ZERO_ERROR
;
1187 fDatePattern
= new UnicodeString(DateFormat::getBestPattern(
1188 fLocale
, dateSkeleton
, status
));
1190 if ( timeSkeleton
.length() != 0) {
1191 status
= U_ZERO_ERROR
;
1192 fTimePattern
= new UnicodeString(DateFormat::getBestPattern(
1193 fLocale
, timeSkeleton
, status
));
1197 // 0 means the best matched skeleton is the same as input skeleton
1198 // 1 means the fields are the same, but field width are different
1199 // 2 means the only difference between fields are v/z,
1200 // -1 means there are other fields difference
1201 // (this will happen, for instance, if the supplied skeleton has seconds,
1202 // but no skeletons in the intervalFormats data do)
1203 if ( differenceInfo
== -1 ) {
1204 // skeleton has different fields, not only v/z difference
1208 if ( timeSkeleton
.length() == 0 ) {
1209 UnicodeString extendedSkeleton
;
1210 UnicodeString extendedBestSkeleton
;
1211 // only has date skeleton
1212 setIntervalPattern(UCAL_DATE
, skeleton
, bestSkeleton
, differenceInfo
,
1213 &extendedSkeleton
, &extendedBestSkeleton
);
1215 UBool extended
= setIntervalPattern(UCAL_MONTH
, skeleton
, bestSkeleton
,
1217 &extendedSkeleton
, &extendedBestSkeleton
);
1220 bestSkeleton
= &extendedBestSkeleton
;
1221 skeleton
= &extendedSkeleton
;
1223 setIntervalPattern(UCAL_YEAR
, skeleton
, bestSkeleton
, differenceInfo
,
1224 &extendedSkeleton
, &extendedBestSkeleton
);
1225 setIntervalPattern(UCAL_ERA
, skeleton
, bestSkeleton
, differenceInfo
,
1226 &extendedSkeleton
, &extendedBestSkeleton
);
1228 setIntervalPattern(UCAL_MINUTE
, skeleton
, bestSkeleton
, differenceInfo
);
1229 setIntervalPattern(UCAL_HOUR
, skeleton
, bestSkeleton
, differenceInfo
);
1230 setIntervalPattern(UCAL_AM_PM
, skeleton
, bestSkeleton
, differenceInfo
);
1238 DateIntervalFormat::setFallbackPattern(UCalendarDateFields field
,
1239 const UnicodeString
& skeleton
,
1240 UErrorCode
& status
) {
1241 if ( U_FAILURE(status
) ) {
1244 UnicodeString pattern
= DateFormat::getBestPattern(
1245 fLocale
, skeleton
, status
);
1246 if ( U_FAILURE(status
) ) {
1249 setPatternInfo(field
, NULL
, &pattern
, fInfo
->getDefaultOrder());
1256 DateIntervalFormat::setPatternInfo(UCalendarDateFields field
,
1257 const UnicodeString
* firstPart
,
1258 const UnicodeString
* secondPart
,
1259 UBool laterDateFirst
) {
1260 // for fall back interval patterns,
1261 // the first part of the pattern is empty,
1262 // the second part of the pattern is the full-pattern
1263 // should be used in fall-back.
1264 UErrorCode status
= U_ZERO_ERROR
;
1265 // following should not set any wrong status.
1266 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
1268 if ( U_FAILURE(status
) ) {
1271 PatternInfo
& ptn
= fIntervalPatterns
[itvPtnIndex
];
1273 ptn
.firstPart
= *firstPart
;
1276 ptn
.secondPart
= *secondPart
;
1278 ptn
.laterDateFirst
= laterDateFirst
;
1282 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1283 const UnicodeString
& intervalPattern
) {
1284 UBool order
= fInfo
->getDefaultOrder();
1285 setIntervalPattern(field
, intervalPattern
, order
);
1290 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1291 const UnicodeString
& intervalPattern
,
1292 UBool laterDateFirst
) {
1293 const UnicodeString
* pattern
= &intervalPattern
;
1294 UBool order
= laterDateFirst
;
1295 // check for "latestFirst:" or "earliestFirst:" prefix
1296 int8_t prefixLength
= UPRV_LENGTHOF(gLaterFirstPrefix
);
1297 int8_t earliestFirstLength
= UPRV_LENGTHOF(gEarlierFirstPrefix
);
1298 UnicodeString realPattern
;
1299 if ( intervalPattern
.startsWith(gLaterFirstPrefix
, prefixLength
) ) {
1301 intervalPattern
.extract(prefixLength
,
1302 intervalPattern
.length() - prefixLength
,
1304 pattern
= &realPattern
;
1305 } else if ( intervalPattern
.startsWith(gEarlierFirstPrefix
,
1306 earliestFirstLength
) ) {
1308 intervalPattern
.extract(earliestFirstLength
,
1309 intervalPattern
.length() - earliestFirstLength
,
1311 pattern
= &realPattern
;
1314 int32_t splitPoint
= splitPatternInto2Part(*pattern
);
1316 UnicodeString firstPart
;
1317 UnicodeString secondPart
;
1318 pattern
->extract(0, splitPoint
, firstPart
);
1319 if ( splitPoint
< pattern
->length() ) {
1320 pattern
->extract(splitPoint
, pattern
->length()-splitPoint
, secondPart
);
1322 setPatternInfo(field
, &firstPart
, &secondPart
, order
);
1329 * Generate interval pattern from existing resource
1331 * It not only save the interval patterns,
1332 * but also return the extended skeleton and its best match skeleton.
1334 * @param field largest different calendar field
1335 * @param skeleton skeleton
1336 * @param bestSkeleton the best match skeleton which has interval pattern
1337 * defined in resource
1338 * @param differenceInfo the difference between skeleton and best skeleton
1339 * 0 means the best matched skeleton is the same as input skeleton
1340 * 1 means the fields are the same, but field width are different
1341 * 2 means the only difference between fields are v/z,
1342 * -1 means there are other fields difference
1344 * @param extendedSkeleton extended skeleton
1345 * @param extendedBestSkeleton extended best match skeleton
1346 * @return whether the interval pattern is found
1347 * through extending skeleton or not.
1348 * TRUE if interval pattern is found by
1349 * extending skeleton, FALSE otherwise.
1353 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1354 const UnicodeString
* skeleton
,
1355 const UnicodeString
* bestSkeleton
,
1356 int8_t differenceInfo
,
1357 UnicodeString
* extendedSkeleton
,
1358 UnicodeString
* extendedBestSkeleton
) {
1359 UErrorCode status
= U_ZERO_ERROR
;
1360 // following getIntervalPattern() should not generate error status
1361 UnicodeString pattern
;
1362 fInfo
->getIntervalPattern(*bestSkeleton
, field
, pattern
, status
);
1363 if ( pattern
.isEmpty() ) {
1365 if ( SimpleDateFormat::isFieldUnitIgnored(*bestSkeleton
, field
) ) {
1366 // do nothing, format will handle it
1370 // for 24 hour system, interval patterns in resource file
1371 // might not include pattern when am_pm differ,
1372 // which should be the same as hour differ.
1373 // add it here for simplicity
1374 if ( field
== UCAL_AM_PM
) {
1375 fInfo
->getIntervalPattern(*bestSkeleton
, UCAL_HOUR
, pattern
,status
);
1376 if ( !pattern
.isEmpty() ) {
1377 setIntervalPattern(field
, pattern
);
1381 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
1382 // first, get best match pattern "MMMd",
1383 // since there is no pattern for 'y' differs for skeleton 'MMMd',
1384 // need to look for it from skeleton 'yMMMd',
1385 // if found, adjust field width in interval pattern from
1387 UChar fieldLetter
= fgCalendarFieldToPatternLetter
[field
];
1388 if ( extendedSkeleton
) {
1389 *extendedSkeleton
= *skeleton
;
1390 *extendedBestSkeleton
= *bestSkeleton
;
1391 extendedSkeleton
->insert(0, fieldLetter
);
1392 extendedBestSkeleton
->insert(0, fieldLetter
);
1393 // for example, looking for patterns when 'y' differ for
1395 fInfo
->getIntervalPattern(*extendedBestSkeleton
,field
,pattern
,status
);
1396 if ( pattern
.isEmpty() && differenceInfo
== 0 ) {
1397 // if there is no skeleton "yMMMM" defined,
1398 // look for the best match skeleton, for example: "yMMM"
1399 const UnicodeString
* tmpBest
= fInfo
->getBestSkeleton(
1400 *extendedBestSkeleton
, differenceInfo
);
1401 if ( tmpBest
!= 0 && differenceInfo
!= -1 ) {
1402 fInfo
->getIntervalPattern(*tmpBest
, field
, pattern
, status
);
1403 bestSkeleton
= tmpBest
;
1408 if ( !pattern
.isEmpty() ) {
1409 if ( differenceInfo
!= 0 ) {
1410 UnicodeString adjustIntervalPattern
;
1411 adjustFieldWidth(*skeleton
, *bestSkeleton
, pattern
, differenceInfo
,
1412 adjustIntervalPattern
);
1413 setIntervalPattern(field
, adjustIntervalPattern
);
1415 setIntervalPattern(field
, pattern
);
1417 if ( extendedSkeleton
&& !extendedSkeleton
->isEmpty() ) {
1427 DateIntervalFormat::splitPatternInto2Part(const UnicodeString
& intervalPattern
) {
1428 UBool inQuote
= false;
1432 /* repeatedPattern used to record whether a pattern has already seen.
1433 It is a pattern applies to first calendar if it is first time seen,
1434 otherwise, it is a pattern applies to the second calendar
1436 UBool patternRepeated
[] =
1438 // A B C D E F G H I J K L M N O
1439 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1440 // P Q R S T U V W X Y Z
1441 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1442 // a b c d e f g h i j k l m n o
1443 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1444 // p q r s t u v w x y z
1445 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1448 int8_t PATTERN_CHAR_BASE
= 0x41;
1450 /* loop through the pattern string character by character looking for
1451 * the first repeated pattern letter, which breaks the interval pattern
1455 UBool foundRepetition
= false;
1456 for (i
= 0; i
< intervalPattern
.length(); ++i
) {
1457 UChar ch
= intervalPattern
.charAt(i
);
1459 if (ch
!= prevCh
&& count
> 0) {
1460 // check the repeativeness of pattern letter
1461 UBool repeated
= patternRepeated
[(int)(prevCh
- PATTERN_CHAR_BASE
)];
1462 if ( repeated
== FALSE
) {
1463 patternRepeated
[prevCh
- PATTERN_CHAR_BASE
] = TRUE
;
1465 foundRepetition
= true;
1470 if (ch
== 0x0027 /*'*/) {
1471 // Consecutive single quotes are a single quote literal,
1472 // either outside of quotes or between quotes
1473 if ((i
+1) < intervalPattern
.length() &&
1474 intervalPattern
.charAt(i
+1) == 0x0027 /*'*/) {
1477 inQuote
= ! inQuote
;
1480 else if (!inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
1481 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
1482 // ch is a date-time pattern character
1487 // check last pattern char, distinguish
1488 // "dd MM" ( no repetition ),
1489 // "d-d"(last char repeated ), and
1490 // "d-d MM" ( repetition found )
1491 if ( count
> 0 && foundRepetition
== FALSE
) {
1492 if ( patternRepeated
[(int)(prevCh
- PATTERN_CHAR_BASE
)] == FALSE
) {
1499 void DateIntervalFormat::fallbackFormatRange(
1500 Calendar
& fromCalendar
,
1501 Calendar
& toCalendar
,
1502 UnicodeString
& appendTo
,
1504 FieldPositionHandler
& fphandler
,
1505 UErrorCode
& status
) const {
1506 UnicodeString fallbackPattern
;
1507 fInfo
->getFallbackIntervalPattern(fallbackPattern
);
1508 SimpleFormatter
sf(fallbackPattern
, 2, 2, status
);
1509 if (U_FAILURE(status
)) {
1513 UnicodeString patternBody
= sf
.getTextWithNoArguments(offsets
, 2);
1515 // TODO(ICU-20406): Use SimpleFormatter Iterator interface when available.
1516 // The context for the first of the _format calls in each pair is set by caller.
1517 // This function always leaves _format context as UDISPCTX_CAPITALIZATION_NONE.
1518 if (offsets
[0] < offsets
[1]) {
1520 appendTo
.append(patternBody
.tempSubStringBetween(0, offsets
[0]));
1521 fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
1522 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[0], offsets
[1]));
1523 fDateFormat
->setContext(UDISPCTX_CAPITALIZATION_NONE
, status
);
1524 fDateFormat
->_format(toCalendar
, appendTo
, fphandler
, status
);
1525 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[1]));
1528 appendTo
.append(patternBody
.tempSubStringBetween(0, offsets
[1]));
1529 fDateFormat
->_format(toCalendar
, appendTo
, fphandler
, status
);
1530 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[1], offsets
[0]));
1531 fDateFormat
->setContext(UDISPCTX_CAPITALIZATION_NONE
, status
);
1532 fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
1533 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[0]));
1538 DateIntervalFormat::fallbackFormat(Calendar
& fromCalendar
,
1539 Calendar
& toCalendar
,
1540 UBool fromToOnSameDay
, // new
1541 UnicodeString
& appendTo
,
1543 FieldPositionHandler
& fphandler
,
1544 UErrorCode
& status
) const {
1545 if ( U_FAILURE(status
) ) {
1549 UBool formatDatePlusTimeRange
= (fromToOnSameDay
&& fDatePattern
&& fTimePattern
);
1550 if (formatDatePlusTimeRange
) {
1551 SimpleFormatter
sf(*fDateTimeFormat
, 2, 2, status
);
1552 if (U_FAILURE(status
)) {
1556 UnicodeString patternBody
= sf
.getTextWithNoArguments(offsets
, 2);
1558 UnicodeString fullPattern
; // for saving the pattern in fDateFormat
1559 fDateFormat
->toPattern(fullPattern
); // save current pattern, restore later
1561 // {0} is time range
1562 // {1} is single date portion
1563 // TODO(ICU-20406): Use SimpleFormatter Iterator interface when available.
1564 if (offsets
[0] < offsets
[1]) {
1565 appendTo
.append(patternBody
.tempSubStringBetween(0, offsets
[0]));
1566 fDateFormat
->applyPattern(*fTimePattern
);
1567 fDateFormat
->setContext(fCapitalizationContext
, status
);
1568 fallbackFormatRange(fromCalendar
, toCalendar
, appendTo
, firstIndex
, fphandler
, status
);
1569 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[0], offsets
[1]));
1570 fDateFormat
->applyPattern(*fDatePattern
);
1571 fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
1572 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[1]));
1574 appendTo
.append(patternBody
.tempSubStringBetween(0, offsets
[1]));
1575 fDateFormat
->applyPattern(*fDatePattern
);
1576 fDateFormat
->setContext(fCapitalizationContext
, status
);
1577 fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
1578 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[1], offsets
[0]));
1579 fDateFormat
->applyPattern(*fTimePattern
);
1580 fDateFormat
->setContext(UDISPCTX_CAPITALIZATION_NONE
, status
);
1581 fallbackFormatRange(fromCalendar
, toCalendar
, appendTo
, firstIndex
, fphandler
, status
);
1582 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[0]));
1585 // restore full pattern
1586 fDateFormat
->applyPattern(fullPattern
);
1588 fDateFormat
->setContext(fCapitalizationContext
, status
);
1589 fallbackFormatRange(fromCalendar
, toCalendar
, appendTo
, firstIndex
, fphandler
, status
);
1598 DateIntervalFormat::fieldExistsInSkeleton(UCalendarDateFields field
,
1599 const UnicodeString
& skeleton
)
1601 const UChar fieldChar
= fgCalendarFieldToPatternLetter
[field
];
1602 return ( (skeleton
.indexOf(fieldChar
) == -1)?FALSE
:TRUE
) ;
1608 DateIntervalFormat::adjustFieldWidth(const UnicodeString
& inputSkeleton
,
1609 const UnicodeString
& bestMatchSkeleton
,
1610 const UnicodeString
& bestIntervalPattern
,
1611 int8_t differenceInfo
,
1612 UnicodeString
& adjustedPtn
) {
1613 adjustedPtn
= bestIntervalPattern
;
1614 int32_t inputSkeletonFieldWidth
[] =
1616 // A B C D E F G H I J K L M N O
1617 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1618 // P Q R S T U V W X Y Z
1619 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1620 // a b c d e f g h i j k l m n o
1621 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1622 // p q r s t u v w x y z
1623 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1626 int32_t bestMatchSkeletonFieldWidth
[] =
1628 // A B C D E F G H I J K L M N O
1629 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1630 // P Q R S T U V W X Y Z
1631 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1632 // a b c d e f g h i j k l m n o
1633 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1634 // p q r s t u v w x y z
1635 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1638 DateIntervalInfo::parseSkeleton(inputSkeleton
, inputSkeletonFieldWidth
);
1639 DateIntervalInfo::parseSkeleton(bestMatchSkeleton
, bestMatchSkeletonFieldWidth
);
1640 if ( differenceInfo
== 2 ) {
1641 adjustedPtn
.findAndReplace(UnicodeString((UChar
)0x76 /* v */),
1642 UnicodeString((UChar
)0x7a /* z */));
1645 UBool inQuote
= false;
1649 const int8_t PATTERN_CHAR_BASE
= 0x41;
1651 // loop through the pattern string character by character
1652 int32_t adjustedPtnLength
= adjustedPtn
.length();
1654 for (i
= 0; i
< adjustedPtnLength
; ++i
) {
1655 UChar ch
= adjustedPtn
.charAt(i
);
1656 if (ch
!= prevCh
&& count
> 0) {
1657 // check the repeativeness of pattern letter
1658 UChar skeletonChar
= prevCh
;
1659 if ( skeletonChar
== CAP_L
) {
1660 // there is no "L" (always be "M") in skeleton,
1661 // but there is "L" in pattern.
1662 // for skeleton "M+", the pattern might be "...L..."
1663 skeletonChar
= CAP_M
;
1665 int32_t fieldCount
= bestMatchSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1666 int32_t inputFieldCount
= inputSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1667 if ( fieldCount
== count
&& inputFieldCount
> fieldCount
) {
1668 count
= inputFieldCount
- fieldCount
;
1670 for ( j
= 0; j
< count
; ++j
) {
1671 adjustedPtn
.insert(i
, prevCh
);
1674 adjustedPtnLength
+= count
;
1678 if (ch
== 0x0027 /*'*/) {
1679 // Consecutive single quotes are a single quote literal,
1680 // either outside of quotes or between quotes
1681 if ((i
+1) < adjustedPtn
.length() && adjustedPtn
.charAt(i
+1) == 0x0027 /* ' */) {
1684 inQuote
= ! inQuote
;
1687 else if ( ! inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
1688 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
1689 // ch is a date-time pattern character
1696 // check the repeativeness of pattern letter
1697 UChar skeletonChar
= prevCh
;
1698 if ( skeletonChar
== CAP_L
) {
1699 // there is no "L" (always be "M") in skeleton,
1700 // but there is "L" in pattern.
1701 // for skeleton "M+", the pattern might be "...L..."
1702 skeletonChar
= CAP_M
;
1704 int32_t fieldCount
= bestMatchSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1705 int32_t inputFieldCount
= inputSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1706 if ( fieldCount
== count
&& inputFieldCount
> fieldCount
) {
1707 count
= inputFieldCount
- fieldCount
;
1709 for ( j
= 0; j
< count
; ++j
) {
1710 adjustedPtn
.append(prevCh
);
1719 DateIntervalFormat::concatSingleDate2TimeInterval(UnicodeString
& format
,
1720 const UnicodeString
& datePattern
,
1721 UCalendarDateFields field
,
1722 UErrorCode
& status
) {
1723 // following should not set wrong status
1724 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
1726 if ( U_FAILURE(status
) ) {
1729 PatternInfo
& timeItvPtnInfo
= fIntervalPatterns
[itvPtnIndex
];
1730 if ( !timeItvPtnInfo
.firstPart
.isEmpty() ) {
1731 UnicodeString
timeIntervalPattern(timeItvPtnInfo
.firstPart
);
1732 timeIntervalPattern
.append(timeItvPtnInfo
.secondPart
);
1733 UnicodeString combinedPattern
;
1734 SimpleFormatter(format
, 2, 2, status
).
1735 format(timeIntervalPattern
, datePattern
, combinedPattern
, status
);
1736 if ( U_FAILURE(status
) ) {
1739 setIntervalPattern(field
, combinedPattern
, timeItvPtnInfo
.laterDateFirst
);
1742 // it should not happen if the interval format defined is valid
1748 DateIntervalFormat::fgCalendarFieldToPatternLetter
[] =
1750 /*GyM*/ CAP_G
, LOW_Y
, CAP_M
,
1751 /*wWd*/ LOW_W
, CAP_W
, LOW_D
,
1752 /*DEF*/ CAP_D
, CAP_E
, CAP_F
,
1753 /*ahH*/ LOW_A
, LOW_H
, CAP_H
,
1754 /*msS*/ LOW_M
, LOW_S
, CAP_S
, // MINUTE, SECOND, MILLISECOND
1755 /*z.Y*/ LOW_Z
, SPACE
, CAP_Y
, // ZONE_OFFSET, DST_OFFSET, YEAR_WOY,
1756 /*eug*/ LOW_E
, LOW_U
, LOW_G
, // DOW_LOCAL, EXTENDED_YEAR, JULIAN_DAY,
1757 /*A..*/ CAP_A
, SPACE
, SPACE
, // MILLISECONDS_IN_DAY, IS_LEAP_MONTH, FIELD_COUNT