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
;
89 DateIntervalFormat
* U_EXPORT2
90 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
92 return createInstance(skeleton
, Locale::getDefault(), status
);
96 DateIntervalFormat
* U_EXPORT2
97 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
100 #ifdef DTITVFMT_DEBUG
104 skeleton
.extract(0, skeleton
.length(), result
, "UTF-8");
106 ((SimpleDateFormat
*)dtfmt
)->toPattern(pat
);
107 pat
.extract(0, pat
.length(), result_1
, "UTF-8");
108 sprintf(mesg
, "skeleton: %s; pattern: %s\n", result
, result_1
);
112 DateIntervalInfo
* dtitvinf
= new DateIntervalInfo(locale
, status
);
113 return create(locale
, dtitvinf
, &skeleton
, status
);
118 DateIntervalFormat
* U_EXPORT2
119 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
120 const DateIntervalInfo
& dtitvinf
,
121 UErrorCode
& status
) {
122 return createInstance(skeleton
, Locale::getDefault(), dtitvinf
, status
);
126 DateIntervalFormat
* U_EXPORT2
127 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
128 const Locale
& locale
,
129 const DateIntervalInfo
& dtitvinf
,
130 UErrorCode
& status
) {
131 DateIntervalInfo
* ptn
= dtitvinf
.clone();
132 return create(locale
, ptn
, &skeleton
, status
);
136 DateIntervalFormat::DateIntervalFormat()
141 fLocale(Locale::getRoot()),
144 fDateTimeFormat(NULL
),
145 fMinimizeType(UDTITVFMT_MINIMIZE_NONE
),
146 fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE
)
150 DateIntervalFormat::DateIntervalFormat(const DateIntervalFormat
& itvfmt
)
156 fLocale(itvfmt
.fLocale
),
159 fDateTimeFormat(NULL
),
160 fMinimizeType(UDTITVFMT_MINIMIZE_NONE
),
161 fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE
) {
167 DateIntervalFormat::operator=(const DateIntervalFormat
& itvfmt
) {
168 if ( this != &itvfmt
) {
171 delete fFromCalendar
;
175 delete fDateTimeFormat
;
177 Mutex
lock(&gFormatterMutex
);
178 if ( itvfmt
.fDateFormat
) {
179 fDateFormat
= itvfmt
.fDateFormat
->clone();
183 if ( itvfmt
.fFromCalendar
) {
184 fFromCalendar
= itvfmt
.fFromCalendar
->clone();
186 fFromCalendar
= NULL
;
188 if ( itvfmt
.fToCalendar
) {
189 fToCalendar
= itvfmt
.fToCalendar
->clone();
194 if ( itvfmt
.fInfo
) {
195 fInfo
= itvfmt
.fInfo
->clone();
199 fSkeleton
= itvfmt
.fSkeleton
;
201 for ( i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
202 fIntervalPatterns
[i
] = itvfmt
.fIntervalPatterns
[i
];
204 fLocale
= itvfmt
.fLocale
;
205 fDatePattern
= (itvfmt
.fDatePattern
)? (UnicodeString
*)itvfmt
.fDatePattern
->clone(): NULL
;
206 fTimePattern
= (itvfmt
.fTimePattern
)? (UnicodeString
*)itvfmt
.fTimePattern
->clone(): NULL
;
207 fDateTimeFormat
= (itvfmt
.fDateTimeFormat
)? (UnicodeString
*)itvfmt
.fDateTimeFormat
->clone(): NULL
;
208 fMinimizeType
= itvfmt
.fMinimizeType
;
209 fCapitalizationContext
= itvfmt
.fCapitalizationContext
;
215 DateIntervalFormat::~DateIntervalFormat() {
218 delete fFromCalendar
;
222 delete fDateTimeFormat
;
227 DateIntervalFormat::clone() const {
228 return new DateIntervalFormat(*this);
233 DateIntervalFormat::operator==(const Format
& other
) const {
234 if (typeid(*this) != typeid(other
)) {return FALSE
;}
235 const DateIntervalFormat
* fmt
= (DateIntervalFormat
*)&other
;
236 if (this == fmt
) {return TRUE
;}
237 if (!Format::operator==(other
)) {return FALSE
;}
238 if ((fInfo
!= fmt
->fInfo
) && (fInfo
== NULL
|| fmt
->fInfo
== NULL
)) {return FALSE
;}
239 if (fInfo
&& fmt
->fInfo
&& (*fInfo
!= *fmt
->fInfo
)) {return FALSE
;}
241 Mutex
lock(&gFormatterMutex
);
242 if (fDateFormat
!= fmt
->fDateFormat
&& (fDateFormat
== NULL
|| fmt
->fDateFormat
== NULL
)) {return FALSE
;}
243 if (fDateFormat
&& fmt
->fDateFormat
&& (*fDateFormat
!= *fmt
->fDateFormat
)) {return FALSE
;}
245 // note: fFromCalendar and fToCalendar hold no persistent state, and therefore do not participate in operator ==.
246 // fDateFormat has the master calendar for the DateIntervalFormat.
247 if (fSkeleton
!= fmt
->fSkeleton
) {return FALSE
;}
248 if (fDatePattern
!= fmt
->fDatePattern
&& (fDatePattern
== NULL
|| fmt
->fDatePattern
== NULL
)) {return FALSE
;}
249 if (fDatePattern
&& fmt
->fDatePattern
&& (*fDatePattern
!= *fmt
->fDatePattern
)) {return FALSE
;}
250 if (fTimePattern
!= fmt
->fTimePattern
&& (fTimePattern
== NULL
|| fmt
->fTimePattern
== NULL
)) {return FALSE
;}
251 if (fTimePattern
&& fmt
->fTimePattern
&& (*fTimePattern
!= *fmt
->fTimePattern
)) {return FALSE
;}
252 if (fDateTimeFormat
!= fmt
->fDateTimeFormat
&& (fDateTimeFormat
== NULL
|| fmt
->fDateTimeFormat
== NULL
)) {return FALSE
;}
253 if (fDateTimeFormat
&& fmt
->fDateTimeFormat
&& (*fDateTimeFormat
!= *fmt
->fDateTimeFormat
)) {return FALSE
;}
254 if (fLocale
!= fmt
->fLocale
) {return FALSE
;}
256 for (int32_t i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
257 if (fIntervalPatterns
[i
].firstPart
!= fmt
->fIntervalPatterns
[i
].firstPart
) {return FALSE
;}
258 if (fIntervalPatterns
[i
].secondPart
!= fmt
->fIntervalPatterns
[i
].secondPart
) {return FALSE
;}
259 if (fIntervalPatterns
[i
].laterDateFirst
!= fmt
->fIntervalPatterns
[i
].laterDateFirst
) {return FALSE
;}
261 if (fMinimizeType
!= fmt
->fMinimizeType
) {return FALSE
;}
262 if (fCapitalizationContext
!= fmt
->fCapitalizationContext
) {return FALSE
;}
268 DateIntervalFormat::format(const Formattable
& obj
,
269 UnicodeString
& appendTo
,
270 FieldPosition
& fieldPosition
,
271 UErrorCode
& status
) const {
272 if ( U_FAILURE(status
) ) {
276 if ( obj
.getType() == Formattable::kObject
) {
277 const UObject
* formatObj
= obj
.getObject();
278 const DateInterval
* interval
= dynamic_cast<const DateInterval
*>(formatObj
);
279 if (interval
!= NULL
) {
280 return format(interval
, appendTo
, fieldPosition
, status
);
283 status
= U_ILLEGAL_ARGUMENT_ERROR
;
289 DateIntervalFormat::format(const DateInterval
* dtInterval
,
290 UnicodeString
& appendTo
,
291 FieldPosition
& fieldPosition
,
292 UErrorCode
& status
) const {
293 if ( U_FAILURE(status
) ) {
296 if (fDateFormat
== NULL
|| fInfo
== NULL
) {
297 status
= U_INVALID_STATE_ERROR
;
301 FieldPositionOnlyHandler
handler(fieldPosition
);
302 handler
.setAcceptFirstOnly(TRUE
);
305 Mutex
lock(&gFormatterMutex
);
306 return formatIntervalImpl(*dtInterval
, appendTo
, ignore
, handler
, status
);
310 FormattedDateInterval
DateIntervalFormat::formatToValue(
311 const DateInterval
& dtInterval
,
312 UErrorCode
& status
) const {
313 LocalPointer
<FormattedDateIntervalData
> result(new FormattedDateIntervalData(status
), status
);
314 if (U_FAILURE(status
)) {
315 return FormattedDateInterval(status
);
317 UnicodeString string
;
319 auto handler
= result
->getHandler(status
);
320 handler
.setCategory(UFIELD_CATEGORY_DATE
);
322 Mutex
lock(&gFormatterMutex
);
323 formatIntervalImpl(dtInterval
, string
, firstIndex
, handler
, status
);
325 handler
.getError(status
);
326 result
->appendString(string
, status
);
327 if (U_FAILURE(status
)) {
328 return FormattedDateInterval(status
);
331 // Compute the span fields and sort them into place:
332 if (firstIndex
!= -1) {
333 result
->addOverlapSpans(UFIELD_CATEGORY_DATE_INTERVAL_SPAN
, firstIndex
, status
);
334 if (U_FAILURE(status
)) {
335 return FormattedDateInterval(status
);
340 return FormattedDateInterval(result
.orphan());
345 DateIntervalFormat::format(Calendar
& fromCalendar
,
346 Calendar
& toCalendar
,
347 UnicodeString
& appendTo
,
349 UErrorCode
& status
) const {
350 FieldPositionOnlyHandler
handler(pos
);
351 handler
.setAcceptFirstOnly(TRUE
);
354 Mutex
lock(&gFormatterMutex
);
355 return formatImpl(fromCalendar
, toCalendar
, appendTo
, ignore
, handler
, status
);
359 FormattedDateInterval
DateIntervalFormat::formatToValue(
360 Calendar
& fromCalendar
,
361 Calendar
& toCalendar
,
362 UErrorCode
& status
) const {
363 LocalPointer
<FormattedDateIntervalData
> result(new FormattedDateIntervalData(status
), status
);
364 if (U_FAILURE(status
)) {
365 return FormattedDateInterval(status
);
367 UnicodeString string
;
369 auto handler
= result
->getHandler(status
);
370 handler
.setCategory(UFIELD_CATEGORY_DATE
);
372 Mutex
lock(&gFormatterMutex
);
373 formatImpl(fromCalendar
, toCalendar
, string
, firstIndex
, handler
, status
);
375 handler
.getError(status
);
376 result
->appendString(string
, status
);
377 if (U_FAILURE(status
)) {
378 return FormattedDateInterval(status
);
381 // Compute the span fields and sort them into place:
382 if (firstIndex
!= -1) {
383 result
->addOverlapSpans(UFIELD_CATEGORY_DATE_INTERVAL_SPAN
, firstIndex
, status
);
387 return FormattedDateInterval(result
.orphan());
391 UnicodeString
& DateIntervalFormat::formatIntervalImpl(
392 const DateInterval
& dtInterval
,
393 UnicodeString
& appendTo
,
395 FieldPositionHandler
& fphandler
,
396 UErrorCode
& status
) const {
397 if (U_FAILURE(status
)) {
400 if (fFromCalendar
== nullptr || fToCalendar
== nullptr) {
401 status
= U_INVALID_STATE_ERROR
;
404 fFromCalendar
->setTime(dtInterval
.getFromDate(), status
);
405 fToCalendar
->setTime(dtInterval
.getToDate(), status
);
406 return formatImpl(*fFromCalendar
, *fToCalendar
, appendTo
, firstIndex
, fphandler
, status
);
411 DateIntervalFormat::formatImpl(Calendar
& fromCalendar
,
412 Calendar
& toCalendar
,
413 UnicodeString
& appendTo
,
415 FieldPositionHandler
& fphandler
,
416 UErrorCode
& status
) const {
417 if ( U_FAILURE(status
) ) {
421 // Initialize firstIndex to -1 (single date, no range)
424 // not support different calendar types and time zones
425 //if ( fromCalendar.getType() != toCalendar.getType() ) {
426 if ( !fromCalendar
.isEquivalentTo(toCalendar
) ) {
427 status
= U_ILLEGAL_ARGUMENT_ERROR
;
431 // First, find the largest different calendar field.
432 UCalendarDateFields field
= UCAL_FIELD_COUNT
;
433 UChar patternDay
= 0x0064; // d
434 UChar patternYear
= 0x0079; // y
436 if ( fromCalendar
.get(UCAL_ERA
,status
) != toCalendar
.get(UCAL_ERA
,status
)) {
438 } else if ( fromCalendar
.get(UCAL_YEAR
, status
) !=
439 toCalendar
.get(UCAL_YEAR
, status
) ) {
441 if (fMinimizeType
== UDTITVFMT_MINIMIZE_ADJACENT_MONTHS
&& fSkeleton
.indexOf(patternDay
) >= 0 && fSkeleton
.indexOf(patternYear
) < 0) {
442 UDate fromDate
= fromCalendar
.getTime(status
);
443 UDate toDate
= toCalendar
.getTime(status
);
444 int32_t fromDay
= fromCalendar
.get(UCAL_DATE
, status
);
445 int32_t toDay
= toCalendar
.get(UCAL_DATE
, status
);
446 fromCalendar
.add(UCAL_MONTH
, 1, status
);
447 if ( fromDate
< toDate
&& fromCalendar
.getTime(status
) > toDate
&& fromDay
> toDay
) {
450 fromCalendar
.setTime(fromDate
, status
);
452 } else if ( fromCalendar
.get(UCAL_MONTH
, status
) !=
453 toCalendar
.get(UCAL_MONTH
, status
) ) {
455 if (fMinimizeType
== UDTITVFMT_MINIMIZE_ADJACENT_MONTHS
&& fSkeleton
.indexOf(patternDay
) >= 0) {
456 UDate fromDate
= fromCalendar
.getTime(status
);
457 UDate toDate
= toCalendar
.getTime(status
);
458 int32_t fromDay
= fromCalendar
.get(UCAL_DATE
, status
);
459 int32_t toDay
= toCalendar
.get(UCAL_DATE
, status
);
460 fromCalendar
.add(UCAL_MONTH
, 1, status
);
461 if ( fromDate
< toDate
&& fromCalendar
.getTime(status
) > toDate
&& fromDay
> toDay
) {
464 fromCalendar
.setTime(fromDate
, status
);
466 } else if ( fromCalendar
.get(UCAL_DATE
, status
) !=
467 toCalendar
.get(UCAL_DATE
, status
) ) {
469 if (fMinimizeType
== UDTITVFMT_MINIMIZE_ADJACENT_DAYS
&&
470 // check normalized skeleton for 'H', 'h', 'j'
471 (fSkeleton
.indexOf(0x0048) >= 0 || fSkeleton
.indexOf(0x0068) >= 0 || fSkeleton
.indexOf(0x006A) >= 0)) {
472 UDate fromDate
= fromCalendar
.getTime(status
);
473 UDate toDate
= toCalendar
.getTime(status
);
474 int32_t fromHour
= fromCalendar
.get(UCAL_HOUR
, status
);
475 int32_t toHour
= toCalendar
.get(UCAL_HOUR
, status
);
476 fromCalendar
.add(UCAL_HOUR_OF_DAY
, 12, status
);
477 if ( fromDate
< toDate
&& fromCalendar
.getTime(status
) > toDate
&& fromHour
> toHour
) {
480 fromCalendar
.setTime(fromDate
, status
);
482 } else if ( fromCalendar
.get(UCAL_AM_PM
, status
) !=
483 toCalendar
.get(UCAL_AM_PM
, status
) ) {
485 } else if ( fromCalendar
.get(UCAL_HOUR
, status
) !=
486 toCalendar
.get(UCAL_HOUR
, status
) ) {
488 } else if ( fromCalendar
.get(UCAL_MINUTE
, status
) !=
489 toCalendar
.get(UCAL_MINUTE
, status
) ) {
491 } else if ( fromCalendar
.get(UCAL_SECOND
, status
) !=
492 toCalendar
.get(UCAL_SECOND
, status
) ) {
496 if ( U_FAILURE(status
) ) {
499 if ( field
== UCAL_FIELD_COUNT
) {
500 /* ignore the millisecond etc. small fields' difference.
501 * use single date when all the above are the same.
503 fDateFormat
->setContext(fCapitalizationContext
, status
);
504 return fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
506 UBool fromToOnSameDay
= (field
==UCAL_AM_PM
|| field
==UCAL_HOUR
|| field
==UCAL_MINUTE
|| field
==UCAL_SECOND
);
508 // following call should not set wrong status,
509 // all the pass-in fields are valid till here
510 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
512 const PatternInfo
& intervalPattern
= fIntervalPatterns
[itvPtnIndex
];
514 if ( intervalPattern
.firstPart
.isEmpty() &&
515 intervalPattern
.secondPart
.isEmpty() ) {
516 if ( fDateFormat
->isFieldUnitIgnored(field
) ) {
517 /* the largest different calendar field is small than
518 * the smallest calendar field in pattern,
519 * return single date format.
521 fDateFormat
->setContext(fCapitalizationContext
, status
);
522 return fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
524 return fallbackFormat(fromCalendar
, toCalendar
, fromToOnSameDay
, appendTo
, firstIndex
, fphandler
, status
);
526 // If the first part in interval pattern is empty,
527 // the 2nd part of it saves the full-pattern used in fall-back.
528 // For a 'real' interval pattern, the first part will never be empty.
529 if ( intervalPattern
.firstPart
.isEmpty() ) {
531 UnicodeString originalPattern
;
532 fDateFormat
->toPattern(originalPattern
);
533 fDateFormat
->applyPattern(intervalPattern
.secondPart
);
534 appendTo
= fallbackFormat(fromCalendar
, toCalendar
, fromToOnSameDay
, appendTo
, firstIndex
, fphandler
, status
);
535 fDateFormat
->applyPattern(originalPattern
);
540 if ( intervalPattern
.laterDateFirst
) {
541 firstCal
= &toCalendar
;
542 secondCal
= &fromCalendar
;
545 firstCal
= &fromCalendar
;
546 secondCal
= &toCalendar
;
549 // break the interval pattern into 2 parts,
550 // first part should not be empty,
551 UnicodeString originalPattern
;
552 fDateFormat
->toPattern(originalPattern
);
553 fDateFormat
->applyPattern(intervalPattern
.firstPart
);
554 fDateFormat
->setContext(fCapitalizationContext
, status
);
555 fDateFormat
->_format(*firstCal
, appendTo
, fphandler
, status
);
557 if ( !intervalPattern
.secondPart
.isEmpty() ) {
558 fDateFormat
->applyPattern(intervalPattern
.secondPart
);
559 fDateFormat
->setContext(UDISPCTX_CAPITALIZATION_NONE
, status
);
560 fDateFormat
->_format(*secondCal
, appendTo
, fphandler
, status
);
562 fDateFormat
->applyPattern(originalPattern
);
569 DateIntervalFormat::parseObject(const UnicodeString
& /* source */,
570 Formattable
& /* result */,
571 ParsePosition
& /* parse_pos */) const {
572 // parseObject(const UnicodeString&, Formattable&, UErrorCode&) const
573 // will set status as U_INVALID_FORMAT_ERROR if
574 // parse_pos is still 0
580 const DateIntervalInfo
*
581 DateIntervalFormat::getDateIntervalInfo() const {
587 DateIntervalFormat::setDateIntervalInfo(const DateIntervalInfo
& newItvPattern
,
588 UErrorCode
& status
) {
590 fInfo
= new DateIntervalInfo(newItvPattern
);
592 // Delete patterns that get reset by initializePattern
597 delete fDateTimeFormat
;
598 fDateTimeFormat
= NULL
;
601 initializePattern(status
);
608 DateIntervalFormat::getDateFormat() const {
614 DateIntervalFormat::adoptTimeZone(TimeZone
* zone
)
616 if (fDateFormat
!= NULL
) {
617 fDateFormat
->adoptTimeZone(zone
);
619 // The fDateFormat has the master calendar for the DateIntervalFormat and has
620 // ownership of any adopted TimeZone; fFromCalendar and fToCalendar are internal
621 // work clones of that calendar (and should not also be given ownership of the
622 // adopted TimeZone).
624 fFromCalendar
->setTimeZone(*zone
);
627 fToCalendar
->setTimeZone(*zone
);
632 DateIntervalFormat::setTimeZone(const TimeZone
& zone
)
634 if (fDateFormat
!= NULL
) {
635 fDateFormat
->setTimeZone(zone
);
637 // The fDateFormat has the master calendar for the DateIntervalFormat;
638 // fFromCalendar and fToCalendar are internal work clones of that calendar.
640 fFromCalendar
->setTimeZone(zone
);
643 fToCalendar
->setTimeZone(zone
);
648 DateIntervalFormat::getTimeZone() const
650 if (fDateFormat
!= NULL
) {
651 Mutex
lock(&gFormatterMutex
);
652 return fDateFormat
->getTimeZone();
654 // If fDateFormat is NULL (unexpected), create default timezone.
655 return *(TimeZone::createDefault());
659 DateIntervalFormat::setAttribute(UDateIntervalFormatAttribute attr
,
660 UDateIntervalFormatAttributeValue value
,
663 if ( U_FAILURE(status
) ) {
666 if (attr
== UDTITVFMT_MINIMIZE_TYPE
) {
667 fMinimizeType
= value
;
669 status
= U_ILLEGAL_ARGUMENT_ERROR
;
674 DateIntervalFormat::setContext(UDisplayContext value
, UErrorCode
& status
)
676 if (U_FAILURE(status
))
678 if ( (UDisplayContextType
)((uint32_t)value
>> 8) == UDISPCTX_TYPE_CAPITALIZATION
) {
679 fCapitalizationContext
= value
;
681 status
= U_ILLEGAL_ARGUMENT_ERROR
;
686 DateIntervalFormat::getContext(UDisplayContextType type
, UErrorCode
& status
) const
688 if (U_FAILURE(status
))
689 return (UDisplayContext
)0;
690 if (type
!= UDISPCTX_TYPE_CAPITALIZATION
) {
691 status
= U_ILLEGAL_ARGUMENT_ERROR
;
692 return (UDisplayContext
)0;
694 return fCapitalizationContext
;
697 DateIntervalFormat::DateIntervalFormat(const Locale
& locale
,
698 DateIntervalInfo
* dtItvInfo
,
699 const UnicodeString
* skeleton
,
708 fDateTimeFormat(NULL
),
709 fMinimizeType(UDTITVFMT_MINIMIZE_NONE
),
710 fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE
)
712 LocalPointer
<DateIntervalInfo
> info(dtItvInfo
, status
);
713 LocalPointer
<SimpleDateFormat
> dtfmt(static_cast<SimpleDateFormat
*>(
714 DateFormat::createInstanceForSkeleton(*skeleton
, locale
, status
)), status
);
715 if (U_FAILURE(status
)) {
720 fSkeleton
= *skeleton
;
722 fInfo
= info
.orphan();
723 fDateFormat
= dtfmt
.orphan();
724 if ( fDateFormat
->getCalendar() ) {
725 fFromCalendar
= fDateFormat
->getCalendar()->clone();
726 fToCalendar
= fDateFormat
->getCalendar()->clone();
728 initializePattern(status
);
731 DateIntervalFormat
* U_EXPORT2
732 DateIntervalFormat::create(const Locale
& locale
,
733 DateIntervalInfo
* dtitvinf
,
734 const UnicodeString
* skeleton
,
735 UErrorCode
& status
) {
736 DateIntervalFormat
* f
= new DateIntervalFormat(locale
, dtitvinf
,
739 status
= U_MEMORY_ALLOCATION_ERROR
;
741 } else if ( U_FAILURE(status
) ) {
742 // safe to delete f, although nothing acutally is saved
752 * Initialize interval patterns locale to this formatter
754 * This code is a bit complicated since
755 * 1. the interval patterns saved in resource bundle files are interval
756 * patterns based on date or time only.
757 * It does not have interval patterns based on both date and time.
758 * Interval patterns on both date and time are algorithm generated.
760 * For example, it has interval patterns on skeleton "dMy" and "hm",
761 * but it does not have interval patterns on skeleton "dMyhm".
763 * The rule to genearte interval patterns for both date and time skeleton are
764 * 1) when the year, month, or day differs, concatenate the two original
765 * expressions with a separator between,
766 * For example, interval pattern from "Jan 10, 2007 10:10 am"
767 * to "Jan 11, 2007 10:10am" is
768 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
770 * 2) otherwise, present the date followed by the range expression
772 * For example, interval pattern from "Jan 10, 2007 10:10 am"
773 * to "Jan 10, 2007 11:10am" is
774 * "Jan 10, 2007 10:10 am - 11:10am"
776 * 2. even a pattern does not request a certion calendar field,
777 * the interval pattern needs to include such field if such fields are
778 * different between 2 dates.
779 * For example, a pattern/skeleton is "hm", but the interval pattern
780 * includes year, month, and date when year, month, and date differs.
782 * @param status output param set to success/failure code on exit
786 DateIntervalFormat::initializePattern(UErrorCode
& status
) {
787 if ( U_FAILURE(status
) ) {
790 const Locale
& locale
= fDateFormat
->getSmpFmtLocale();
791 if ( fSkeleton
.isEmpty() ) {
792 UnicodeString fullPattern
;
793 fDateFormat
->toPattern(fullPattern
);
794 #ifdef DTITVFMT_DEBUG
798 fSkeleton
.extract(0, fSkeleton
.length(), result
, "UTF-8");
799 sprintf(mesg
, "in getBestSkeleton: fSkeleton: %s; \n", result
);
802 // fSkeleton is already set by createDateIntervalInstance()
803 // or by createInstance(UnicodeString skeleton, .... )
804 fSkeleton
= DateTimePatternGenerator::staticGetSkeleton(
805 fullPattern
, status
);
806 if ( U_FAILURE(status
) ) {
811 // initialize the fIntervalPattern ordering
813 for ( i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
814 fIntervalPatterns
[i
].laterDateFirst
= fInfo
->getDefaultOrder();
817 /* Check whether the skeleton is a combination of date and time.
818 * For the complication reason 1 explained above.
820 UnicodeString dateSkeleton
;
821 UnicodeString timeSkeleton
;
822 UnicodeString normalizedTimeSkeleton
;
823 UnicodeString normalizedDateSkeleton
;
826 /* the difference between time skeleton and normalizedTimeSkeleton are:
827 * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true)
828 * 2. 'a' is omitted in normalized time skeleton.
829 * 3. there is only one appearance for 'h' or 'H', 'm','v', 'z' in normalized
832 * The difference between date skeleton and normalizedDateSkeleton are:
833 * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
834 * 2. 'E' and 'EE' are normalized into 'EEE'
835 * 3. 'MM' is normalized into 'M'
837 getDateTimeSkeleton(fSkeleton
, dateSkeleton
, normalizedDateSkeleton
,
838 timeSkeleton
, normalizedTimeSkeleton
);
840 #ifdef DTITVFMT_DEBUG
844 fSkeleton
.extract(0, fSkeleton
.length(), result
, "UTF-8");
845 sprintf(mesg
, "in getBestSkeleton: fSkeleton: %s; \n", result
);
849 // move this up here since we need it for fallbacks
850 if ( timeSkeleton
.length() > 0 && dateSkeleton
.length() > 0 ) {
851 // Need the Date/Time pattern for concatenation of the date
852 // with the time interval.
853 // The date/time pattern ( such as {0} {1} ) is saved in
854 // calendar, that is why need to get the CalendarData here.
855 LocalUResourceBundlePointer
dateTimePatternsRes(ures_open(NULL
, locale
.getBaseName(), &status
));
856 ures_getByKey(dateTimePatternsRes
.getAlias(), gCalendarTag
,
857 dateTimePatternsRes
.getAlias(), &status
);
858 ures_getByKeyWithFallback(dateTimePatternsRes
.getAlias(), gGregorianTag
,
859 dateTimePatternsRes
.getAlias(), &status
);
860 ures_getByKeyWithFallback(dateTimePatternsRes
.getAlias(), gDateTimePatternsTag
,
861 dateTimePatternsRes
.getAlias(), &status
);
863 int32_t dateTimeFormatLength
;
864 const UChar
* dateTimeFormat
= ures_getStringByIndex(
865 dateTimePatternsRes
.getAlias(),
866 (int32_t)DateFormat::kDateTime
,
867 &dateTimeFormatLength
, &status
);
868 if ( U_SUCCESS(status
) && dateTimeFormatLength
>= 3 ) {
869 fDateTimeFormat
= new UnicodeString(dateTimeFormat
, dateTimeFormatLength
);
873 UBool found
= setSeparateDateTimePtn(normalizedDateSkeleton
,
874 normalizedTimeSkeleton
);
876 // for skeletons with seconds, found is false and we enter this block
877 if ( found
== false ) {
879 // TODO: if user asks "m"(minute), but "d"(day) differ
880 if ( timeSkeleton
.length() != 0 ) {
881 if ( dateSkeleton
.length() == 0 ) {
883 timeSkeleton
.insert(0, gDateFormatSkeleton
[DateFormat::kShort
], -1);
884 UnicodeString pattern
= DateFormat::getBestPattern(
885 locale
, timeSkeleton
, status
);
886 if ( U_FAILURE(status
) ) {
889 // for fall back interval patterns,
890 // the first part of the pattern is empty,
891 // the second part of the pattern is the full-pattern
892 // should be used in fall-back.
893 setPatternInfo(UCAL_DATE
, NULL
, &pattern
, fInfo
->getDefaultOrder());
894 setPatternInfo(UCAL_MONTH
, NULL
, &pattern
, fInfo
->getDefaultOrder());
895 setPatternInfo(UCAL_YEAR
, NULL
, &pattern
, fInfo
->getDefaultOrder());
903 } // end of skeleton not found
904 // interval patterns for skeleton are found in resource
905 if ( timeSkeleton
.length() == 0 ) {
907 } else if ( dateSkeleton
.length() == 0 ) {
909 timeSkeleton
.insert(0, gDateFormatSkeleton
[DateFormat::kShort
], -1);
910 UnicodeString pattern
= DateFormat::getBestPattern(
911 locale
, timeSkeleton
, status
);
912 if ( U_FAILURE(status
) ) {
915 // for fall back interval patterns,
916 // the first part of the pattern is empty,
917 // the second part of the pattern is the full-pattern
918 // should be used in fall-back.
919 setPatternInfo(UCAL_DATE
, NULL
, &pattern
, fInfo
->getDefaultOrder());
920 setPatternInfo(UCAL_MONTH
, NULL
, &pattern
, fInfo
->getDefaultOrder());
921 setPatternInfo(UCAL_YEAR
, NULL
, &pattern
, fInfo
->getDefaultOrder());
924 * 1) when the year, month, or day differs,
925 * concatenate the two original expressions with a separator between,
926 * 2) otherwise, present the date followed by the
927 * range expression for the time.
930 * 1) when the year, month, or day differs,
931 * concatenate the two original expressions with a separator between,
933 // if field exists, use fall back
934 UnicodeString skeleton
= fSkeleton
;
935 if ( !fieldExistsInSkeleton(UCAL_DATE
, dateSkeleton
) ) {
936 // prefix skeleton with 'd'
937 skeleton
.insert(0, LOW_D
);
938 setFallbackPattern(UCAL_DATE
, skeleton
, status
);
940 if ( !fieldExistsInSkeleton(UCAL_MONTH
, dateSkeleton
) ) {
941 // then prefix skeleton with 'M'
942 skeleton
.insert(0, CAP_M
);
943 setFallbackPattern(UCAL_MONTH
, skeleton
, status
);
945 if ( !fieldExistsInSkeleton(UCAL_YEAR
, dateSkeleton
) ) {
946 // then prefix skeleton with 'y'
947 skeleton
.insert(0, LOW_Y
);
948 setFallbackPattern(UCAL_YEAR
, skeleton
, status
);
952 * 2) otherwise, present the date followed by the
953 * range expression for the time.
956 if ( fDateTimeFormat
== NULL
) {
957 // earlier failure getting dateTimeFormat
961 UnicodeString datePattern
= DateFormat::getBestPattern(
962 locale
, dateSkeleton
, status
);
964 concatSingleDate2TimeInterval(*fDateTimeFormat
, datePattern
, UCAL_AM_PM
, status
);
965 concatSingleDate2TimeInterval(*fDateTimeFormat
, datePattern
, UCAL_HOUR
, status
);
966 concatSingleDate2TimeInterval(*fDateTimeFormat
, datePattern
, UCAL_MINUTE
, status
);
973 DateIntervalFormat::getDateTimeSkeleton(const UnicodeString
& skeleton
,
974 UnicodeString
& dateSkeleton
,
975 UnicodeString
& normalizedDateSkeleton
,
976 UnicodeString
& timeSkeleton
,
977 UnicodeString
& normalizedTimeSkeleton
) {
978 // dateSkeleton follows the sequence of y*M*E*d*
979 // timeSkeleton follows the sequence of hm*[v|z]?
991 for (i
= 0; i
< skeleton
.length(); ++i
) {
992 UChar ch
= skeleton
[i
];
995 dateSkeleton
.append(ch
);
999 dateSkeleton
.append(ch
);
1003 dateSkeleton
.append(ch
);
1007 dateSkeleton
.append(ch
);
1026 normalizedDateSkeleton
.append(ch
);
1027 dateSkeleton
.append(ch
);
1030 // 'a' is implicitly handled
1031 timeSkeleton
.append(ch
);
1034 timeSkeleton
.append(ch
);
1038 timeSkeleton
.append(ch
);
1042 timeSkeleton
.append(ch
);
1047 timeSkeleton
.append(ch
);
1051 timeSkeleton
.append(ch
);
1061 timeSkeleton
.append(ch
);
1062 normalizedTimeSkeleton
.append(ch
);
1067 /* generate normalized form for date*/
1068 if ( yCount
!= 0 ) {
1069 for (i
= 0; i
< yCount
; ++i
) {
1070 normalizedDateSkeleton
.append(LOW_Y
);
1073 if ( MCount
!= 0 ) {
1075 normalizedDateSkeleton
.append(CAP_M
);
1077 for ( int32_t j
= 0; j
< MCount
&& j
< MAX_M_COUNT
; ++j
) {
1078 normalizedDateSkeleton
.append(CAP_M
);
1082 if ( ECount
!= 0 ) {
1083 if ( ECount
<= 3 ) {
1084 normalizedDateSkeleton
.append(CAP_E
);
1086 for ( int32_t j
= 0; j
< ECount
&& j
< MAX_E_COUNT
; ++j
) {
1087 normalizedDateSkeleton
.append(CAP_E
);
1091 if ( dCount
!= 0 ) {
1092 normalizedDateSkeleton
.append(LOW_D
);
1095 /* generate normalized form for time */
1096 if ( HCount
!= 0 ) {
1097 normalizedTimeSkeleton
.append(CAP_H
);
1099 else if ( hCount
!= 0 ) {
1100 normalizedTimeSkeleton
.append(LOW_H
);
1102 if ( mCount
!= 0 ) {
1103 normalizedTimeSkeleton
.append(LOW_M
);
1105 if ( zCount
!= 0 ) {
1106 normalizedTimeSkeleton
.append(LOW_Z
);
1108 if ( vCount
!= 0 ) {
1109 normalizedTimeSkeleton
.append(LOW_V
);
1115 * Generate date or time interval pattern from resource,
1116 * and set them into the interval pattern locale to this formatter.
1118 * It needs to handle the following:
1119 * 1. need to adjust field width.
1120 * For example, the interval patterns saved in DateIntervalInfo
1121 * includes "dMMMy", but not "dMMMMy".
1122 * Need to get interval patterns for dMMMMy from dMMMy.
1123 * Another example, the interval patterns saved in DateIntervalInfo
1124 * includes "hmv", but not "hmz".
1125 * Need to get interval patterns for "hmz' from 'hmv'
1127 * 2. there might be no pattern for 'y' differ for skeleton "Md",
1128 * in order to get interval patterns for 'y' differ,
1129 * need to look for it from skeleton 'yMd'
1131 * @param dateSkeleton normalized date skeleton
1132 * @param timeSkeleton normalized time skeleton
1133 * @return whether the resource is found for the skeleton.
1134 * TRUE if interval pattern found for the skeleton,
1139 DateIntervalFormat::setSeparateDateTimePtn(
1140 const UnicodeString
& dateSkeleton
,
1141 const UnicodeString
& timeSkeleton
) {
1142 const UnicodeString
* skeleton
;
1143 // if both date and time skeleton present,
1144 // the final interval pattern might include time interval patterns
1145 // ( when, am_pm, hour, minute differ ),
1146 // but not date interval patterns ( when year, month, day differ ).
1147 // For year/month/day differ, it falls back to fall-back pattern.
1148 if ( timeSkeleton
.length() != 0 ) {
1149 skeleton
= &timeSkeleton
;
1151 skeleton
= &dateSkeleton
;
1154 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
1155 * are defined in resource,
1156 * interval patterns for skeleton "dMMMMy" are calculated by
1157 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
1158 * 2. get the interval patterns for "dMMMy",
1159 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
1160 * getBestSkeleton() is step 1.
1162 // best skeleton, and the difference information
1163 int8_t differenceInfo
= 0;
1164 const UnicodeString
* bestSkeleton
= fInfo
->getBestSkeleton(*skeleton
,
1166 /* best skeleton could be NULL.
1167 For example: in "ca" resource file,
1168 interval format is defined as following
1170 fallback{"{0} - {1}"}
1172 there is no skeletons/interval patterns defined,
1173 and the best skeleton match could be NULL
1175 if ( bestSkeleton
== NULL
) {
1179 // Set patterns for fallback use, need to do this
1180 // before returning if differenceInfo == -1
1182 if ( dateSkeleton
.length() != 0) {
1183 status
= U_ZERO_ERROR
;
1184 fDatePattern
= new UnicodeString(DateFormat::getBestPattern(
1185 fLocale
, dateSkeleton
, status
));
1187 if ( timeSkeleton
.length() != 0) {
1188 status
= U_ZERO_ERROR
;
1189 fTimePattern
= new UnicodeString(DateFormat::getBestPattern(
1190 fLocale
, timeSkeleton
, status
));
1194 // 0 means the best matched skeleton is the same as input skeleton
1195 // 1 means the fields are the same, but field width are different
1196 // 2 means the only difference between fields are v/z,
1197 // -1 means there are other fields difference
1198 // (this will happen, for instance, if the supplied skeleton has seconds,
1199 // but no skeletons in the intervalFormats data do)
1200 if ( differenceInfo
== -1 ) {
1201 // skeleton has different fields, not only v/z difference
1205 if ( timeSkeleton
.length() == 0 ) {
1206 UnicodeString extendedSkeleton
;
1207 UnicodeString extendedBestSkeleton
;
1208 // only has date skeleton
1209 setIntervalPattern(UCAL_DATE
, skeleton
, bestSkeleton
, differenceInfo
,
1210 &extendedSkeleton
, &extendedBestSkeleton
);
1212 UBool extended
= setIntervalPattern(UCAL_MONTH
, skeleton
, bestSkeleton
,
1214 &extendedSkeleton
, &extendedBestSkeleton
);
1217 bestSkeleton
= &extendedBestSkeleton
;
1218 skeleton
= &extendedSkeleton
;
1220 setIntervalPattern(UCAL_YEAR
, skeleton
, bestSkeleton
, differenceInfo
,
1221 &extendedSkeleton
, &extendedBestSkeleton
);
1222 setIntervalPattern(UCAL_ERA
, skeleton
, bestSkeleton
, differenceInfo
,
1223 &extendedSkeleton
, &extendedBestSkeleton
);
1225 setIntervalPattern(UCAL_MINUTE
, skeleton
, bestSkeleton
, differenceInfo
);
1226 setIntervalPattern(UCAL_HOUR
, skeleton
, bestSkeleton
, differenceInfo
);
1227 setIntervalPattern(UCAL_AM_PM
, skeleton
, bestSkeleton
, differenceInfo
);
1235 DateIntervalFormat::setFallbackPattern(UCalendarDateFields field
,
1236 const UnicodeString
& skeleton
,
1237 UErrorCode
& status
) {
1238 if ( U_FAILURE(status
) ) {
1241 UnicodeString pattern
= DateFormat::getBestPattern(
1242 fLocale
, skeleton
, status
);
1243 if ( U_FAILURE(status
) ) {
1246 setPatternInfo(field
, NULL
, &pattern
, fInfo
->getDefaultOrder());
1253 DateIntervalFormat::setPatternInfo(UCalendarDateFields field
,
1254 const UnicodeString
* firstPart
,
1255 const UnicodeString
* secondPart
,
1256 UBool laterDateFirst
) {
1257 // for fall back interval patterns,
1258 // the first part of the pattern is empty,
1259 // the second part of the pattern is the full-pattern
1260 // should be used in fall-back.
1261 UErrorCode status
= U_ZERO_ERROR
;
1262 // following should not set any wrong status.
1263 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
1265 if ( U_FAILURE(status
) ) {
1268 PatternInfo
& ptn
= fIntervalPatterns
[itvPtnIndex
];
1270 ptn
.firstPart
= *firstPart
;
1273 ptn
.secondPart
= *secondPart
;
1275 ptn
.laterDateFirst
= laterDateFirst
;
1279 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1280 const UnicodeString
& intervalPattern
) {
1281 UBool order
= fInfo
->getDefaultOrder();
1282 setIntervalPattern(field
, intervalPattern
, order
);
1287 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1288 const UnicodeString
& intervalPattern
,
1289 UBool laterDateFirst
) {
1290 const UnicodeString
* pattern
= &intervalPattern
;
1291 UBool order
= laterDateFirst
;
1292 // check for "latestFirst:" or "earliestFirst:" prefix
1293 int8_t prefixLength
= UPRV_LENGTHOF(gLaterFirstPrefix
);
1294 int8_t earliestFirstLength
= UPRV_LENGTHOF(gEarlierFirstPrefix
);
1295 UnicodeString realPattern
;
1296 if ( intervalPattern
.startsWith(gLaterFirstPrefix
, prefixLength
) ) {
1298 intervalPattern
.extract(prefixLength
,
1299 intervalPattern
.length() - prefixLength
,
1301 pattern
= &realPattern
;
1302 } else if ( intervalPattern
.startsWith(gEarlierFirstPrefix
,
1303 earliestFirstLength
) ) {
1305 intervalPattern
.extract(earliestFirstLength
,
1306 intervalPattern
.length() - earliestFirstLength
,
1308 pattern
= &realPattern
;
1311 int32_t splitPoint
= splitPatternInto2Part(*pattern
);
1313 UnicodeString firstPart
;
1314 UnicodeString secondPart
;
1315 pattern
->extract(0, splitPoint
, firstPart
);
1316 if ( splitPoint
< pattern
->length() ) {
1317 pattern
->extract(splitPoint
, pattern
->length()-splitPoint
, secondPart
);
1319 setPatternInfo(field
, &firstPart
, &secondPart
, order
);
1326 * Generate interval pattern from existing resource
1328 * It not only save the interval patterns,
1329 * but also return the extended skeleton and its best match skeleton.
1331 * @param field largest different calendar field
1332 * @param skeleton skeleton
1333 * @param bestSkeleton the best match skeleton which has interval pattern
1334 * defined in resource
1335 * @param differenceInfo the difference between skeleton and best skeleton
1336 * 0 means the best matched skeleton is the same as input skeleton
1337 * 1 means the fields are the same, but field width are different
1338 * 2 means the only difference between fields are v/z,
1339 * -1 means there are other fields difference
1341 * @param extendedSkeleton extended skeleton
1342 * @param extendedBestSkeleton extended best match skeleton
1343 * @return whether the interval pattern is found
1344 * through extending skeleton or not.
1345 * TRUE if interval pattern is found by
1346 * extending skeleton, FALSE otherwise.
1350 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1351 const UnicodeString
* skeleton
,
1352 const UnicodeString
* bestSkeleton
,
1353 int8_t differenceInfo
,
1354 UnicodeString
* extendedSkeleton
,
1355 UnicodeString
* extendedBestSkeleton
) {
1356 UErrorCode status
= U_ZERO_ERROR
;
1357 // following getIntervalPattern() should not generate error status
1358 UnicodeString pattern
;
1359 fInfo
->getIntervalPattern(*bestSkeleton
, field
, pattern
, status
);
1360 if ( pattern
.isEmpty() ) {
1362 if ( SimpleDateFormat::isFieldUnitIgnored(*bestSkeleton
, field
) ) {
1363 // do nothing, format will handle it
1367 // for 24 hour system, interval patterns in resource file
1368 // might not include pattern when am_pm differ,
1369 // which should be the same as hour differ.
1370 // add it here for simplicity
1371 if ( field
== UCAL_AM_PM
) {
1372 fInfo
->getIntervalPattern(*bestSkeleton
, UCAL_HOUR
, pattern
,status
);
1373 if ( !pattern
.isEmpty() ) {
1374 setIntervalPattern(field
, pattern
);
1378 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
1379 // first, get best match pattern "MMMd",
1380 // since there is no pattern for 'y' differs for skeleton 'MMMd',
1381 // need to look for it from skeleton 'yMMMd',
1382 // if found, adjust field width in interval pattern from
1384 UChar fieldLetter
= fgCalendarFieldToPatternLetter
[field
];
1385 if ( extendedSkeleton
) {
1386 *extendedSkeleton
= *skeleton
;
1387 *extendedBestSkeleton
= *bestSkeleton
;
1388 extendedSkeleton
->insert(0, fieldLetter
);
1389 extendedBestSkeleton
->insert(0, fieldLetter
);
1390 // for example, looking for patterns when 'y' differ for
1392 fInfo
->getIntervalPattern(*extendedBestSkeleton
,field
,pattern
,status
);
1393 if ( pattern
.isEmpty() && differenceInfo
== 0 ) {
1394 // if there is no skeleton "yMMMM" defined,
1395 // look for the best match skeleton, for example: "yMMM"
1396 const UnicodeString
* tmpBest
= fInfo
->getBestSkeleton(
1397 *extendedBestSkeleton
, differenceInfo
);
1398 if ( tmpBest
!= 0 && differenceInfo
!= -1 ) {
1399 fInfo
->getIntervalPattern(*tmpBest
, field
, pattern
, status
);
1400 bestSkeleton
= tmpBest
;
1405 if ( !pattern
.isEmpty() ) {
1406 if ( differenceInfo
!= 0 ) {
1407 UnicodeString adjustIntervalPattern
;
1408 adjustFieldWidth(*skeleton
, *bestSkeleton
, pattern
, differenceInfo
,
1409 adjustIntervalPattern
);
1410 setIntervalPattern(field
, adjustIntervalPattern
);
1412 setIntervalPattern(field
, pattern
);
1414 if ( extendedSkeleton
&& !extendedSkeleton
->isEmpty() ) {
1424 DateIntervalFormat::splitPatternInto2Part(const UnicodeString
& intervalPattern
) {
1425 UBool inQuote
= false;
1429 /* repeatedPattern used to record whether a pattern has already seen.
1430 It is a pattern applies to first calendar if it is first time seen,
1431 otherwise, it is a pattern applies to the second calendar
1433 UBool patternRepeated
[] =
1435 // A B C D E F G H I J K L M N O
1436 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1437 // P Q R S T U V W X Y Z
1438 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1439 // a b c d e f g h i j k l m n o
1440 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1441 // p q r s t u v w x y z
1442 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1445 int8_t PATTERN_CHAR_BASE
= 0x41;
1447 /* loop through the pattern string character by character looking for
1448 * the first repeated pattern letter, which breaks the interval pattern
1452 UBool foundRepetition
= false;
1453 for (i
= 0; i
< intervalPattern
.length(); ++i
) {
1454 UChar ch
= intervalPattern
.charAt(i
);
1456 if (ch
!= prevCh
&& count
> 0) {
1457 // check the repeativeness of pattern letter
1458 UBool repeated
= patternRepeated
[(int)(prevCh
- PATTERN_CHAR_BASE
)];
1459 if ( repeated
== FALSE
) {
1460 patternRepeated
[prevCh
- PATTERN_CHAR_BASE
] = TRUE
;
1462 foundRepetition
= true;
1467 if (ch
== 0x0027 /*'*/) {
1468 // Consecutive single quotes are a single quote literal,
1469 // either outside of quotes or between quotes
1470 if ((i
+1) < intervalPattern
.length() &&
1471 intervalPattern
.charAt(i
+1) == 0x0027 /*'*/) {
1474 inQuote
= ! inQuote
;
1477 else if (!inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
1478 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
1479 // ch is a date-time pattern character
1484 // check last pattern char, distinguish
1485 // "dd MM" ( no repetition ),
1486 // "d-d"(last char repeated ), and
1487 // "d-d MM" ( repetition found )
1488 if ( count
> 0 && foundRepetition
== FALSE
) {
1489 if ( patternRepeated
[(int)(prevCh
- PATTERN_CHAR_BASE
)] == FALSE
) {
1496 void DateIntervalFormat::fallbackFormatRange(
1497 Calendar
& fromCalendar
,
1498 Calendar
& toCalendar
,
1499 UnicodeString
& appendTo
,
1501 FieldPositionHandler
& fphandler
,
1502 UErrorCode
& status
) const {
1503 UnicodeString fallbackPattern
;
1504 fInfo
->getFallbackIntervalPattern(fallbackPattern
);
1505 SimpleFormatter
sf(fallbackPattern
, 2, 2, status
);
1506 if (U_FAILURE(status
)) {
1510 UnicodeString patternBody
= sf
.getTextWithNoArguments(offsets
, 2);
1512 // TODO(ICU-20406): Use SimpleFormatter Iterator interface when available.
1513 // The context for the first of the _format calls in each pair is set by caller.
1514 // This function always leaves _format context as UDISPCTX_CAPITALIZATION_NONE.
1515 if (offsets
[0] < offsets
[1]) {
1517 appendTo
.append(patternBody
.tempSubStringBetween(0, offsets
[0]));
1518 fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
1519 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[0], offsets
[1]));
1520 fDateFormat
->setContext(UDISPCTX_CAPITALIZATION_NONE
, status
);
1521 fDateFormat
->_format(toCalendar
, appendTo
, fphandler
, status
);
1522 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[1]));
1525 appendTo
.append(patternBody
.tempSubStringBetween(0, offsets
[1]));
1526 fDateFormat
->_format(toCalendar
, appendTo
, fphandler
, status
);
1527 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[1], offsets
[0]));
1528 fDateFormat
->setContext(UDISPCTX_CAPITALIZATION_NONE
, status
);
1529 fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
1530 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[0]));
1535 DateIntervalFormat::fallbackFormat(Calendar
& fromCalendar
,
1536 Calendar
& toCalendar
,
1537 UBool fromToOnSameDay
, // new
1538 UnicodeString
& appendTo
,
1540 FieldPositionHandler
& fphandler
,
1541 UErrorCode
& status
) const {
1542 if ( U_FAILURE(status
) ) {
1546 UBool formatDatePlusTimeRange
= (fromToOnSameDay
&& fDatePattern
&& fTimePattern
);
1547 if (formatDatePlusTimeRange
) {
1548 SimpleFormatter
sf(*fDateTimeFormat
, 2, 2, TRUE
, status
);
1549 if (U_FAILURE(status
)) {
1553 UnicodeString patternBody
= sf
.getTextWithNoArguments(offsets
, 2);
1555 UnicodeString fullPattern
; // for saving the pattern in fDateFormat
1556 fDateFormat
->toPattern(fullPattern
); // save current pattern, restore later
1558 // {0} is time range
1559 // {1} is single date portion
1560 // TODO(ICU-20406): Use SimpleFormatter Iterator interface when available.
1561 if (offsets
[0] < offsets
[1]) {
1562 appendTo
.append(patternBody
.tempSubStringBetween(0, offsets
[0]));
1563 fDateFormat
->applyPattern(*fTimePattern
);
1564 fDateFormat
->setContext(fCapitalizationContext
, status
);
1565 fallbackFormatRange(fromCalendar
, toCalendar
, appendTo
, firstIndex
, fphandler
, status
);
1566 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[0], offsets
[1]));
1567 fDateFormat
->applyPattern(*fDatePattern
);
1568 fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
1569 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[1]));
1571 appendTo
.append(patternBody
.tempSubStringBetween(0, offsets
[1]));
1572 fDateFormat
->applyPattern(*fDatePattern
);
1573 fDateFormat
->setContext(fCapitalizationContext
, status
);
1574 fDateFormat
->_format(fromCalendar
, appendTo
, fphandler
, status
);
1575 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[1], offsets
[0]));
1576 fDateFormat
->applyPattern(*fTimePattern
);
1577 fDateFormat
->setContext(UDISPCTX_CAPITALIZATION_NONE
, status
);
1578 fallbackFormatRange(fromCalendar
, toCalendar
, appendTo
, firstIndex
, fphandler
, status
);
1579 appendTo
.append(patternBody
.tempSubStringBetween(offsets
[0]));
1582 // restore full pattern
1583 fDateFormat
->applyPattern(fullPattern
);
1585 fDateFormat
->setContext(fCapitalizationContext
, status
);
1586 fallbackFormatRange(fromCalendar
, toCalendar
, appendTo
, firstIndex
, fphandler
, status
);
1595 DateIntervalFormat::fieldExistsInSkeleton(UCalendarDateFields field
,
1596 const UnicodeString
& skeleton
)
1598 const UChar fieldChar
= fgCalendarFieldToPatternLetter
[field
];
1599 return ( (skeleton
.indexOf(fieldChar
) == -1)?FALSE
:TRUE
) ;
1605 DateIntervalFormat::adjustFieldWidth(const UnicodeString
& inputSkeleton
,
1606 const UnicodeString
& bestMatchSkeleton
,
1607 const UnicodeString
& bestIntervalPattern
,
1608 int8_t differenceInfo
,
1609 UnicodeString
& adjustedPtn
) {
1610 adjustedPtn
= bestIntervalPattern
;
1611 int32_t inputSkeletonFieldWidth
[] =
1613 // A B C D E F G H I J K L M N O
1614 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1615 // P Q R S T U V W X Y Z
1616 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1617 // a b c d e f g h i j k l m n o
1618 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1619 // p q r s t u v w x y z
1620 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1623 int32_t bestMatchSkeletonFieldWidth
[] =
1625 // A B C D E F G H I J K L M N O
1626 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1627 // P Q R S T U V W X Y Z
1628 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1629 // a b c d e f g h i j k l m n o
1630 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1631 // p q r s t u v w x y z
1632 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1635 DateIntervalInfo::parseSkeleton(inputSkeleton
, inputSkeletonFieldWidth
);
1636 DateIntervalInfo::parseSkeleton(bestMatchSkeleton
, bestMatchSkeletonFieldWidth
);
1637 if ( differenceInfo
== 2 ) {
1638 adjustedPtn
.findAndReplace(UnicodeString((UChar
)0x76 /* v */),
1639 UnicodeString((UChar
)0x7a /* z */));
1642 UBool inQuote
= false;
1646 const int8_t PATTERN_CHAR_BASE
= 0x41;
1648 // loop through the pattern string character by character
1649 int32_t adjustedPtnLength
= adjustedPtn
.length();
1651 for (i
= 0; i
< adjustedPtnLength
; ++i
) {
1652 UChar ch
= adjustedPtn
.charAt(i
);
1653 if (ch
!= prevCh
&& count
> 0) {
1654 // check the repeativeness of pattern letter
1655 UChar skeletonChar
= prevCh
;
1656 if ( skeletonChar
== CAP_L
) {
1657 // there is no "L" (always be "M") in skeleton,
1658 // but there is "L" in pattern.
1659 // for skeleton "M+", the pattern might be "...L..."
1660 skeletonChar
= CAP_M
;
1662 int32_t fieldCount
= bestMatchSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1663 int32_t inputFieldCount
= inputSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1664 if ( fieldCount
== count
&& inputFieldCount
> fieldCount
) {
1665 count
= inputFieldCount
- fieldCount
;
1667 for ( j
= 0; j
< count
; ++j
) {
1668 adjustedPtn
.insert(i
, prevCh
);
1671 adjustedPtnLength
+= count
;
1675 if (ch
== 0x0027 /*'*/) {
1676 // Consecutive single quotes are a single quote literal,
1677 // either outside of quotes or between quotes
1678 if ((i
+1) < adjustedPtn
.length() && adjustedPtn
.charAt(i
+1) == 0x0027 /* ' */) {
1681 inQuote
= ! inQuote
;
1684 else if ( ! inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
1685 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
1686 // ch is a date-time pattern character
1693 // check the repeativeness of pattern letter
1694 UChar skeletonChar
= prevCh
;
1695 if ( skeletonChar
== CAP_L
) {
1696 // there is no "L" (always be "M") in skeleton,
1697 // but there is "L" in pattern.
1698 // for skeleton "M+", the pattern might be "...L..."
1699 skeletonChar
= CAP_M
;
1701 int32_t fieldCount
= bestMatchSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1702 int32_t inputFieldCount
= inputSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1703 if ( fieldCount
== count
&& inputFieldCount
> fieldCount
) {
1704 count
= inputFieldCount
- fieldCount
;
1706 for ( j
= 0; j
< count
; ++j
) {
1707 adjustedPtn
.append(prevCh
);
1716 DateIntervalFormat::concatSingleDate2TimeInterval(UnicodeString
& format
,
1717 const UnicodeString
& datePattern
,
1718 UCalendarDateFields field
,
1719 UErrorCode
& status
) {
1720 // following should not set wrong status
1721 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
1723 if ( U_FAILURE(status
) ) {
1726 PatternInfo
& timeItvPtnInfo
= fIntervalPatterns
[itvPtnIndex
];
1727 if ( !timeItvPtnInfo
.firstPart
.isEmpty() ) {
1728 UnicodeString
timeIntervalPattern(timeItvPtnInfo
.firstPart
);
1729 timeIntervalPattern
.append(timeItvPtnInfo
.secondPart
);
1730 UnicodeString combinedPattern
;
1731 SimpleFormatter(format
, 2, 2, status
).
1732 format(timeIntervalPattern
, datePattern
, combinedPattern
, status
);
1733 if ( U_FAILURE(status
) ) {
1736 setIntervalPattern(field
, combinedPattern
, timeItvPtnInfo
.laterDateFirst
);
1739 // it should not happen if the interval format defined is valid
1745 DateIntervalFormat::fgCalendarFieldToPatternLetter
[] =
1747 /*GyM*/ CAP_G
, LOW_Y
, CAP_M
,
1748 /*wWd*/ LOW_W
, CAP_W
, LOW_D
,
1749 /*DEF*/ CAP_D
, CAP_E
, CAP_F
,
1750 /*ahH*/ LOW_A
, LOW_H
, CAP_H
,
1751 /*msS*/ LOW_M
, LOW_S
, CAP_S
, // MINUTE, SECOND, MILLISECOND
1752 /*z.Y*/ LOW_Z
, SPACE
, CAP_Y
, // ZONE_OFFSET, DST_OFFSET, YEAR_WOY,
1753 /*eug*/ LOW_E
, LOW_U
, LOW_G
, // DOW_LOCAL, EXTENDED_YEAR, JULIAN_DAY,
1754 /*A..*/ CAP_A
, SPACE
, SPACE
, // MILLISECONDS_IN_DAY, IS_LEAP_MONTH, FIELD_COUNT