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/udateintervalformat.h"
26 #include "unicode/simpleformatter.h"
29 #include "dtitv_impl.h"
42 #define PRINTMESG(msg) { std::cout << "(" << __FILE__ << ":" << __LINE__ << ") " << msg << "\n"; }
46 static const UChar gDateFormatSkeleton
[][11] = {
48 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, CAP_M
, CAP_E
, CAP_E
, CAP_E
, CAP_E
, LOW_D
, 0},
50 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, CAP_M
, LOW_D
, 0},
52 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, LOW_D
, 0},
54 {LOW_Y
, CAP_M
, LOW_D
, 0} };
57 static const char gCalendarTag
[] = "calendar";
58 static const char gGregorianTag
[] = "gregorian";
59 static const char gDateTimePatternsTag
[] = "DateTimePatterns";
63 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
};
66 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
};
69 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalFormat
)
71 // Mutex, protects access to fDateFormat, fFromCalendar and fToCalendar.
72 // Needed because these data members are modified by const methods of DateIntervalFormat.
74 static UMutex gFormatterMutex
= U_MUTEX_INITIALIZER
;
76 DateIntervalFormat
* U_EXPORT2
77 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
79 return createInstance(skeleton
, Locale::getDefault(), status
);
83 DateIntervalFormat
* U_EXPORT2
84 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
91 skeleton
.extract(0, skeleton
.length(), result
, "UTF-8");
93 ((SimpleDateFormat
*)dtfmt
)->toPattern(pat
);
94 pat
.extract(0, pat
.length(), result_1
, "UTF-8");
95 sprintf(mesg
, "skeleton: %s; pattern: %s\n", result
, result_1
);
99 DateIntervalInfo
* dtitvinf
= new DateIntervalInfo(locale
, status
);
100 return create(locale
, dtitvinf
, &skeleton
, status
);
105 DateIntervalFormat
* U_EXPORT2
106 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
107 const DateIntervalInfo
& dtitvinf
,
108 UErrorCode
& status
) {
109 return createInstance(skeleton
, Locale::getDefault(), dtitvinf
, status
);
113 DateIntervalFormat
* U_EXPORT2
114 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
115 const Locale
& locale
,
116 const DateIntervalInfo
& dtitvinf
,
117 UErrorCode
& status
) {
118 DateIntervalInfo
* ptn
= dtitvinf
.clone();
119 return create(locale
, ptn
, &skeleton
, status
);
123 DateIntervalFormat::DateIntervalFormat()
128 fLocale(Locale::getRoot()),
131 fDateTimeFormat(NULL
),
132 fMinimizeType(UDTITVFMT_MINIMIZE_NONE
)
136 DateIntervalFormat::DateIntervalFormat(const DateIntervalFormat
& itvfmt
)
142 fLocale(itvfmt
.fLocale
),
145 fDateTimeFormat(NULL
),
146 fMinimizeType(UDTITVFMT_MINIMIZE_NONE
) {
152 DateIntervalFormat::operator=(const DateIntervalFormat
& itvfmt
) {
153 if ( this != &itvfmt
) {
156 delete fFromCalendar
;
160 delete fDateTimeFormat
;
162 Mutex
lock(&gFormatterMutex
);
163 if ( itvfmt
.fDateFormat
) {
164 fDateFormat
= (SimpleDateFormat
*)itvfmt
.fDateFormat
->clone();
168 if ( itvfmt
.fFromCalendar
) {
169 fFromCalendar
= itvfmt
.fFromCalendar
->clone();
171 fFromCalendar
= NULL
;
173 if ( itvfmt
.fToCalendar
) {
174 fToCalendar
= itvfmt
.fToCalendar
->clone();
179 if ( itvfmt
.fInfo
) {
180 fInfo
= itvfmt
.fInfo
->clone();
184 fSkeleton
= itvfmt
.fSkeleton
;
186 for ( i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
187 fIntervalPatterns
[i
] = itvfmt
.fIntervalPatterns
[i
];
189 fLocale
= itvfmt
.fLocale
;
190 fDatePattern
= (itvfmt
.fDatePattern
)? (UnicodeString
*)itvfmt
.fDatePattern
->clone(): NULL
;
191 fTimePattern
= (itvfmt
.fTimePattern
)? (UnicodeString
*)itvfmt
.fTimePattern
->clone(): NULL
;
192 fDateTimeFormat
= (itvfmt
.fDateTimeFormat
)? (UnicodeString
*)itvfmt
.fDateTimeFormat
->clone(): NULL
;
198 DateIntervalFormat::~DateIntervalFormat() {
201 delete fFromCalendar
;
205 delete fDateTimeFormat
;
210 DateIntervalFormat::clone(void) const {
211 return new DateIntervalFormat(*this);
216 DateIntervalFormat::operator==(const Format
& other
) const {
217 if (typeid(*this) != typeid(other
)) {return FALSE
;}
218 const DateIntervalFormat
* fmt
= (DateIntervalFormat
*)&other
;
219 if (this == fmt
) {return TRUE
;}
220 if (!Format::operator==(other
)) {return FALSE
;}
221 if ((fInfo
!= fmt
->fInfo
) && (fInfo
== NULL
|| fmt
->fInfo
== NULL
)) {return FALSE
;}
222 if (fInfo
&& fmt
->fInfo
&& (*fInfo
!= *fmt
->fInfo
)) {return FALSE
;}
224 Mutex
lock(&gFormatterMutex
);
225 if (fDateFormat
!= fmt
->fDateFormat
&& (fDateFormat
== NULL
|| fmt
->fDateFormat
== NULL
)) {return FALSE
;}
226 if (fDateFormat
&& fmt
->fDateFormat
&& (*fDateFormat
!= *fmt
->fDateFormat
)) {return FALSE
;}
228 // note: fFromCalendar and fToCalendar hold no persistent state, and therefore do not participate in operator ==.
229 // fDateFormat has the master calendar for the DateIntervalFormat.
230 if (fSkeleton
!= fmt
->fSkeleton
) {return FALSE
;}
231 if (fDatePattern
!= fmt
->fDatePattern
&& (fDatePattern
== NULL
|| fmt
->fDatePattern
== NULL
)) {return FALSE
;}
232 if (fDatePattern
&& fmt
->fDatePattern
&& (*fDatePattern
!= *fmt
->fDatePattern
)) {return FALSE
;}
233 if (fTimePattern
!= fmt
->fTimePattern
&& (fTimePattern
== NULL
|| fmt
->fTimePattern
== NULL
)) {return FALSE
;}
234 if (fTimePattern
&& fmt
->fTimePattern
&& (*fTimePattern
!= *fmt
->fTimePattern
)) {return FALSE
;}
235 if (fDateTimeFormat
!= fmt
->fDateTimeFormat
&& (fDateTimeFormat
== NULL
|| fmt
->fDateTimeFormat
== NULL
)) {return FALSE
;}
236 if (fDateTimeFormat
&& fmt
->fDateTimeFormat
&& (*fDateTimeFormat
!= *fmt
->fDateTimeFormat
)) {return FALSE
;}
237 if (fLocale
!= fmt
->fLocale
) {return FALSE
;}
239 for (int32_t i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
240 if (fIntervalPatterns
[i
].firstPart
!= fmt
->fIntervalPatterns
[i
].firstPart
) {return FALSE
;}
241 if (fIntervalPatterns
[i
].secondPart
!= fmt
->fIntervalPatterns
[i
].secondPart
) {return FALSE
;}
242 if (fIntervalPatterns
[i
].laterDateFirst
!= fmt
->fIntervalPatterns
[i
].laterDateFirst
) {return FALSE
;}
249 DateIntervalFormat::format(const Formattable
& obj
,
250 UnicodeString
& appendTo
,
251 FieldPosition
& fieldPosition
,
252 UErrorCode
& status
) const {
253 if ( U_FAILURE(status
) ) {
257 if ( obj
.getType() == Formattable::kObject
) {
258 const UObject
* formatObj
= obj
.getObject();
259 const DateInterval
* interval
= dynamic_cast<const DateInterval
*>(formatObj
);
260 if (interval
!= NULL
) {
261 return format(interval
, appendTo
, fieldPosition
, status
);
264 status
= U_ILLEGAL_ARGUMENT_ERROR
;
270 DateIntervalFormat::format(const DateInterval
* dtInterval
,
271 UnicodeString
& appendTo
,
272 FieldPosition
& fieldPosition
,
273 UErrorCode
& status
) const {
274 if ( U_FAILURE(status
) ) {
277 if (fFromCalendar
== NULL
|| fToCalendar
== NULL
|| fDateFormat
== NULL
|| fInfo
== NULL
) {
278 status
= U_INVALID_STATE_ERROR
;
282 Mutex
lock(&gFormatterMutex
);
283 fFromCalendar
->setTime(dtInterval
->getFromDate(), status
);
284 fToCalendar
->setTime(dtInterval
->getToDate(), status
);
285 return formatImpl(*fFromCalendar
, *fToCalendar
, appendTo
,fieldPosition
, status
);
290 DateIntervalFormat::format(Calendar
& fromCalendar
,
291 Calendar
& toCalendar
,
292 UnicodeString
& appendTo
,
294 UErrorCode
& status
) const {
295 Mutex
lock(&gFormatterMutex
);
296 return formatImpl(fromCalendar
, toCalendar
, appendTo
, pos
, status
);
301 DateIntervalFormat::formatImpl(Calendar
& fromCalendar
,
302 Calendar
& toCalendar
,
303 UnicodeString
& appendTo
,
305 UErrorCode
& status
) const {
306 if ( U_FAILURE(status
) ) {
310 // not support different calendar types and time zones
311 //if ( fromCalendar.getType() != toCalendar.getType() ) {
312 if ( !fromCalendar
.isEquivalentTo(toCalendar
) ) {
313 status
= U_ILLEGAL_ARGUMENT_ERROR
;
317 // First, find the largest different calendar field.
318 UCalendarDateFields field
= UCAL_FIELD_COUNT
;
319 UChar patternDay
= 0x0064; // d
320 UChar patternYear
= 0x0079; // y
322 if ( fromCalendar
.get(UCAL_ERA
,status
) != toCalendar
.get(UCAL_ERA
,status
)) {
324 } else if ( fromCalendar
.get(UCAL_YEAR
, status
) !=
325 toCalendar
.get(UCAL_YEAR
, status
) ) {
327 if (fMinimizeType
== UDTITVFMT_MINIMIZE_ADJACENT_MONTHS
&& fSkeleton
.indexOf(patternDay
) >= 0 && fSkeleton
.indexOf(patternYear
) < 0) {
328 UDate fromDate
= fromCalendar
.getTime(status
);
329 UDate toDate
= toCalendar
.getTime(status
);
330 int32_t fromDay
= fromCalendar
.get(UCAL_DATE
, status
);
331 int32_t toDay
= toCalendar
.get(UCAL_DATE
, status
);
332 fromCalendar
.add(UCAL_MONTH
, 1, status
);
333 if ( fromDate
< toDate
&& fromCalendar
.getTime(status
) > toDate
&& fromDay
> toDay
) {
336 fromCalendar
.setTime(fromDate
, status
);
338 } else if ( fromCalendar
.get(UCAL_MONTH
, status
) !=
339 toCalendar
.get(UCAL_MONTH
, status
) ) {
341 if (fMinimizeType
== UDTITVFMT_MINIMIZE_ADJACENT_MONTHS
&& fSkeleton
.indexOf(patternDay
) >= 0) {
342 UDate fromDate
= fromCalendar
.getTime(status
);
343 UDate toDate
= toCalendar
.getTime(status
);
344 int32_t fromDay
= fromCalendar
.get(UCAL_DATE
, status
);
345 int32_t toDay
= toCalendar
.get(UCAL_DATE
, status
);
346 fromCalendar
.add(UCAL_MONTH
, 1, status
);
347 if ( fromDate
< toDate
&& fromCalendar
.getTime(status
) > toDate
&& fromDay
> toDay
) {
350 fromCalendar
.setTime(fromDate
, status
);
352 } else if ( fromCalendar
.get(UCAL_DATE
, status
) !=
353 toCalendar
.get(UCAL_DATE
, status
) ) {
355 if (fMinimizeType
== UDTITVFMT_MINIMIZE_ADJACENT_DAYS
&&
356 // check normalized skeleton for 'H', 'h', 'j'
357 (fSkeleton
.indexOf(0x0048) >= 0 || fSkeleton
.indexOf(0x0068) >= 0 || fSkeleton
.indexOf(0x006A) >= 0)) {
358 UDate fromDate
= fromCalendar
.getTime(status
);
359 UDate toDate
= toCalendar
.getTime(status
);
360 int32_t fromHour
= fromCalendar
.get(UCAL_HOUR
, status
);
361 int32_t toHour
= toCalendar
.get(UCAL_HOUR
, status
);
362 fromCalendar
.add(UCAL_HOUR_OF_DAY
, 12, status
);
363 if ( fromDate
< toDate
&& fromCalendar
.getTime(status
) > toDate
&& fromHour
> toHour
) {
366 fromCalendar
.setTime(fromDate
, status
);
368 } else if ( fromCalendar
.get(UCAL_AM_PM
, status
) !=
369 toCalendar
.get(UCAL_AM_PM
, status
) ) {
371 } else if ( fromCalendar
.get(UCAL_HOUR
, status
) !=
372 toCalendar
.get(UCAL_HOUR
, status
) ) {
374 } else if ( fromCalendar
.get(UCAL_MINUTE
, status
) !=
375 toCalendar
.get(UCAL_MINUTE
, status
) ) {
377 } else if ( fromCalendar
.get(UCAL_SECOND
, status
) !=
378 toCalendar
.get(UCAL_SECOND
, status
) ) {
382 if ( U_FAILURE(status
) ) {
385 if ( field
== UCAL_FIELD_COUNT
) {
386 /* ignore the millisecond etc. small fields' difference.
387 * use single date when all the above are the same.
389 return fDateFormat
->format(fromCalendar
, appendTo
, pos
);
391 UBool fromToOnSameDay
= (field
==UCAL_AM_PM
|| field
==UCAL_HOUR
|| field
==UCAL_MINUTE
|| field
==UCAL_SECOND
);
393 // following call should not set wrong status,
394 // all the pass-in fields are valid till here
395 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
397 const PatternInfo
& intervalPattern
= fIntervalPatterns
[itvPtnIndex
];
399 if ( intervalPattern
.firstPart
.isEmpty() &&
400 intervalPattern
.secondPart
.isEmpty() ) {
401 if ( fDateFormat
->isFieldUnitIgnored(field
) ) {
402 /* the largest different calendar field is small than
403 * the smallest calendar field in pattern,
404 * return single date format.
406 return fDateFormat
->format(fromCalendar
, appendTo
, pos
);
408 return fallbackFormat(fromCalendar
, toCalendar
, fromToOnSameDay
, appendTo
, pos
, status
);
410 // If the first part in interval pattern is empty,
411 // the 2nd part of it saves the full-pattern used in fall-back.
412 // For a 'real' interval pattern, the first part will never be empty.
413 if ( intervalPattern
.firstPart
.isEmpty() ) {
415 UnicodeString originalPattern
;
416 fDateFormat
->toPattern(originalPattern
);
417 fDateFormat
->applyPattern(intervalPattern
.secondPart
);
418 appendTo
= fallbackFormat(fromCalendar
, toCalendar
, fromToOnSameDay
, appendTo
, pos
, status
);
419 fDateFormat
->applyPattern(originalPattern
);
424 if ( intervalPattern
.laterDateFirst
) {
425 firstCal
= &toCalendar
;
426 secondCal
= &fromCalendar
;
428 firstCal
= &fromCalendar
;
429 secondCal
= &toCalendar
;
431 // break the interval pattern into 2 parts,
432 // first part should not be empty,
433 UnicodeString originalPattern
;
434 fDateFormat
->toPattern(originalPattern
);
435 fDateFormat
->applyPattern(intervalPattern
.firstPart
);
436 fDateFormat
->format(*firstCal
, appendTo
, pos
);
437 if ( !intervalPattern
.secondPart
.isEmpty() ) {
438 fDateFormat
->applyPattern(intervalPattern
.secondPart
);
439 FieldPosition otherPos
;
440 otherPos
.setField(pos
.getField());
441 fDateFormat
->format(*secondCal
, appendTo
, otherPos
);
442 if (pos
.getEndIndex() == 0 && otherPos
.getEndIndex() > 0) {
446 fDateFormat
->applyPattern(originalPattern
);
453 DateIntervalFormat::parseObject(const UnicodeString
& /* source */,
454 Formattable
& /* result */,
455 ParsePosition
& /* parse_pos */) const {
456 // parseObject(const UnicodeString&, Formattable&, UErrorCode&) const
457 // will set status as U_INVALID_FORMAT_ERROR if
458 // parse_pos is still 0
464 const DateIntervalInfo
*
465 DateIntervalFormat::getDateIntervalInfo() const {
471 DateIntervalFormat::setDateIntervalInfo(const DateIntervalInfo
& newItvPattern
,
472 UErrorCode
& status
) {
474 fInfo
= new DateIntervalInfo(newItvPattern
);
476 // Delete patterns that get reset by initializePattern
481 delete fDateTimeFormat
;
482 fDateTimeFormat
= NULL
;
485 initializePattern(status
);
492 DateIntervalFormat::getDateFormat() const {
498 DateIntervalFormat::adoptTimeZone(TimeZone
* zone
)
500 if (fDateFormat
!= NULL
) {
501 fDateFormat
->adoptTimeZone(zone
);
503 // The fDateFormat has the master calendar for the DateIntervalFormat and has
504 // ownership of any adopted TimeZone; fFromCalendar and fToCalendar are internal
505 // work clones of that calendar (and should not also be given ownership of the
506 // adopted TimeZone).
508 fFromCalendar
->setTimeZone(*zone
);
511 fToCalendar
->setTimeZone(*zone
);
516 DateIntervalFormat::setTimeZone(const TimeZone
& zone
)
518 if (fDateFormat
!= NULL
) {
519 fDateFormat
->setTimeZone(zone
);
521 // The fDateFormat has the master calendar for the DateIntervalFormat;
522 // fFromCalendar and fToCalendar are internal work clones of that calendar.
524 fFromCalendar
->setTimeZone(zone
);
527 fToCalendar
->setTimeZone(zone
);
532 DateIntervalFormat::getTimeZone() const
534 if (fDateFormat
!= NULL
) {
535 Mutex
lock(&gFormatterMutex
);
536 return fDateFormat
->getTimeZone();
538 // If fDateFormat is NULL (unexpected), create default timezone.
539 return *(TimeZone::createDefault());
543 DateIntervalFormat::setAttribute(UDateIntervalFormatAttribute attr
,
544 UDateIntervalFormatAttributeValue value
,
547 if ( U_FAILURE(status
) ) {
550 if (attr
== UDTITVFMT_MINIMIZE_TYPE
) {
551 fMinimizeType
= value
;
553 status
= U_ILLEGAL_ARGUMENT_ERROR
;
557 DateIntervalFormat::DateIntervalFormat(const Locale
& locale
,
558 DateIntervalInfo
* dtItvInfo
,
559 const UnicodeString
* skeleton
,
568 fDateTimeFormat(NULL
),
569 fMinimizeType(UDTITVFMT_MINIMIZE_NONE
)
571 LocalPointer
<DateIntervalInfo
> info(dtItvInfo
, status
);
572 LocalPointer
<SimpleDateFormat
> dtfmt(static_cast<SimpleDateFormat
*>(
573 DateFormat::createInstanceForSkeleton(*skeleton
, locale
, status
)), status
);
574 if (U_FAILURE(status
)) {
579 fSkeleton
= *skeleton
;
581 fInfo
= info
.orphan();
582 fDateFormat
= dtfmt
.orphan();
583 if ( fDateFormat
->getCalendar() ) {
584 fFromCalendar
= fDateFormat
->getCalendar()->clone();
585 fToCalendar
= fDateFormat
->getCalendar()->clone();
587 initializePattern(status
);
590 DateIntervalFormat
* U_EXPORT2
591 DateIntervalFormat::create(const Locale
& locale
,
592 DateIntervalInfo
* dtitvinf
,
593 const UnicodeString
* skeleton
,
594 UErrorCode
& status
) {
595 DateIntervalFormat
* f
= new DateIntervalFormat(locale
, dtitvinf
,
598 status
= U_MEMORY_ALLOCATION_ERROR
;
600 } else if ( U_FAILURE(status
) ) {
601 // safe to delete f, although nothing acutally is saved
611 * Initialize interval patterns locale to this formatter
613 * This code is a bit complicated since
614 * 1. the interval patterns saved in resource bundle files are interval
615 * patterns based on date or time only.
616 * It does not have interval patterns based on both date and time.
617 * Interval patterns on both date and time are algorithm generated.
619 * For example, it has interval patterns on skeleton "dMy" and "hm",
620 * but it does not have interval patterns on skeleton "dMyhm".
622 * The rule to genearte interval patterns for both date and time skeleton are
623 * 1) when the year, month, or day differs, concatenate the two original
624 * expressions with a separator between,
625 * For example, interval pattern from "Jan 10, 2007 10:10 am"
626 * to "Jan 11, 2007 10:10am" is
627 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
629 * 2) otherwise, present the date followed by the range expression
631 * For example, interval pattern from "Jan 10, 2007 10:10 am"
632 * to "Jan 10, 2007 11:10am" is
633 * "Jan 10, 2007 10:10 am - 11:10am"
635 * 2. even a pattern does not request a certion calendar field,
636 * the interval pattern needs to include such field if such fields are
637 * different between 2 dates.
638 * For example, a pattern/skeleton is "hm", but the interval pattern
639 * includes year, month, and date when year, month, and date differs.
641 * @param status output param set to success/failure code on exit
645 DateIntervalFormat::initializePattern(UErrorCode
& status
) {
646 if ( U_FAILURE(status
) ) {
649 const Locale
& locale
= fDateFormat
->getSmpFmtLocale();
650 if ( fSkeleton
.isEmpty() ) {
651 UnicodeString fullPattern
;
652 fDateFormat
->toPattern(fullPattern
);
653 #ifdef DTITVFMT_DEBUG
657 fSkeleton
.extract(0, fSkeleton
.length(), result
, "UTF-8");
658 sprintf(mesg
, "in getBestSkeleton: fSkeleton: %s; \n", result
);
661 // fSkeleton is already set by createDateIntervalInstance()
662 // or by createInstance(UnicodeString skeleton, .... )
663 fSkeleton
= DateTimePatternGenerator::staticGetSkeleton(
664 fullPattern
, status
);
665 if ( U_FAILURE(status
) ) {
670 // initialize the fIntervalPattern ordering
672 for ( i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
673 fIntervalPatterns
[i
].laterDateFirst
= fInfo
->getDefaultOrder();
676 /* Check whether the skeleton is a combination of date and time.
677 * For the complication reason 1 explained above.
679 UnicodeString dateSkeleton
;
680 UnicodeString timeSkeleton
;
681 UnicodeString normalizedTimeSkeleton
;
682 UnicodeString normalizedDateSkeleton
;
685 /* the difference between time skeleton and normalizedTimeSkeleton are:
686 * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true)
687 * 2. 'a' is omitted in normalized time skeleton.
688 * 3. there is only one appearance for 'h' or 'H', 'm','v', 'z' in normalized
691 * The difference between date skeleton and normalizedDateSkeleton are:
692 * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
693 * 2. 'E' and 'EE' are normalized into 'EEE'
694 * 3. 'MM' is normalized into 'M'
696 getDateTimeSkeleton(fSkeleton
, dateSkeleton
, normalizedDateSkeleton
,
697 timeSkeleton
, normalizedTimeSkeleton
);
699 #ifdef DTITVFMT_DEBUG
703 fSkeleton
.extract(0, fSkeleton
.length(), result
, "UTF-8");
704 sprintf(mesg
, "in getBestSkeleton: fSkeleton: %s; \n", result
);
708 // move this up here since we need it for fallbacks
709 if ( timeSkeleton
.length() > 0 && dateSkeleton
.length() > 0 ) {
710 // Need the Date/Time pattern for concatenation of the date
711 // with the time interval.
712 // The date/time pattern ( such as {0} {1} ) is saved in
713 // calendar, that is why need to get the CalendarData here.
714 LocalUResourceBundlePointer
dateTimePatternsRes(ures_open(NULL
, locale
.getBaseName(), &status
));
715 ures_getByKey(dateTimePatternsRes
.getAlias(), gCalendarTag
,
716 dateTimePatternsRes
.getAlias(), &status
);
717 ures_getByKeyWithFallback(dateTimePatternsRes
.getAlias(), gGregorianTag
,
718 dateTimePatternsRes
.getAlias(), &status
);
719 ures_getByKeyWithFallback(dateTimePatternsRes
.getAlias(), gDateTimePatternsTag
,
720 dateTimePatternsRes
.getAlias(), &status
);
722 int32_t dateTimeFormatLength
;
723 const UChar
* dateTimeFormat
= ures_getStringByIndex(
724 dateTimePatternsRes
.getAlias(),
725 (int32_t)DateFormat::kDateTime
,
726 &dateTimeFormatLength
, &status
);
727 if ( U_SUCCESS(status
) && dateTimeFormatLength
>= 3 ) {
728 fDateTimeFormat
= new UnicodeString(dateTimeFormat
, dateTimeFormatLength
);
732 UBool found
= setSeparateDateTimePtn(normalizedDateSkeleton
,
733 normalizedTimeSkeleton
);
735 // for skeletons with seconds, found is false and we enter this block
736 if ( found
== false ) {
738 // TODO: if user asks "m"(minute), but "d"(day) differ
739 if ( timeSkeleton
.length() != 0 ) {
740 if ( dateSkeleton
.length() == 0 ) {
742 timeSkeleton
.insert(0, gDateFormatSkeleton
[DateFormat::kShort
], -1);
743 UnicodeString pattern
= DateFormat::getBestPattern(
744 locale
, timeSkeleton
, status
);
745 if ( U_FAILURE(status
) ) {
748 // for fall back interval patterns,
749 // the first part of the pattern is empty,
750 // the second part of the pattern is the full-pattern
751 // should be used in fall-back.
752 setPatternInfo(UCAL_DATE
, NULL
, &pattern
, fInfo
->getDefaultOrder());
753 setPatternInfo(UCAL_MONTH
, NULL
, &pattern
, fInfo
->getDefaultOrder());
754 setPatternInfo(UCAL_YEAR
, NULL
, &pattern
, fInfo
->getDefaultOrder());
762 } // end of skeleton not found
763 // interval patterns for skeleton are found in resource
764 if ( timeSkeleton
.length() == 0 ) {
766 } else if ( dateSkeleton
.length() == 0 ) {
768 timeSkeleton
.insert(0, gDateFormatSkeleton
[DateFormat::kShort
], -1);
769 UnicodeString pattern
= DateFormat::getBestPattern(
770 locale
, timeSkeleton
, status
);
771 if ( U_FAILURE(status
) ) {
774 // for fall back interval patterns,
775 // the first part of the pattern is empty,
776 // the second part of the pattern is the full-pattern
777 // should be used in fall-back.
778 setPatternInfo(UCAL_DATE
, NULL
, &pattern
, fInfo
->getDefaultOrder());
779 setPatternInfo(UCAL_MONTH
, NULL
, &pattern
, fInfo
->getDefaultOrder());
780 setPatternInfo(UCAL_YEAR
, NULL
, &pattern
, fInfo
->getDefaultOrder());
783 * 1) when the year, month, or day differs,
784 * concatenate the two original expressions with a separator between,
785 * 2) otherwise, present the date followed by the
786 * range expression for the time.
789 * 1) when the year, month, or day differs,
790 * concatenate the two original expressions with a separator between,
792 // if field exists, use fall back
793 UnicodeString skeleton
= fSkeleton
;
794 if ( !fieldExistsInSkeleton(UCAL_DATE
, dateSkeleton
) ) {
795 // prefix skeleton with 'd'
796 skeleton
.insert(0, LOW_D
);
797 setFallbackPattern(UCAL_DATE
, skeleton
, status
);
799 if ( !fieldExistsInSkeleton(UCAL_MONTH
, dateSkeleton
) ) {
800 // then prefix skeleton with 'M'
801 skeleton
.insert(0, CAP_M
);
802 setFallbackPattern(UCAL_MONTH
, skeleton
, status
);
804 if ( !fieldExistsInSkeleton(UCAL_YEAR
, dateSkeleton
) ) {
805 // then prefix skeleton with 'y'
806 skeleton
.insert(0, LOW_Y
);
807 setFallbackPattern(UCAL_YEAR
, skeleton
, status
);
811 * 2) otherwise, present the date followed by the
812 * range expression for the time.
815 if ( fDateTimeFormat
== NULL
) {
816 // earlier failure getting dateTimeFormat
820 UnicodeString datePattern
= DateFormat::getBestPattern(
821 locale
, dateSkeleton
, status
);
823 concatSingleDate2TimeInterval(*fDateTimeFormat
, datePattern
, UCAL_AM_PM
, status
);
824 concatSingleDate2TimeInterval(*fDateTimeFormat
, datePattern
, UCAL_HOUR
, status
);
825 concatSingleDate2TimeInterval(*fDateTimeFormat
, datePattern
, UCAL_MINUTE
, status
);
832 DateIntervalFormat::getDateTimeSkeleton(const UnicodeString
& skeleton
,
833 UnicodeString
& dateSkeleton
,
834 UnicodeString
& normalizedDateSkeleton
,
835 UnicodeString
& timeSkeleton
,
836 UnicodeString
& normalizedTimeSkeleton
) {
837 // dateSkeleton follows the sequence of y*M*E*d*
838 // timeSkeleton follows the sequence of hm*[v|z]?
850 for (i
= 0; i
< skeleton
.length(); ++i
) {
851 UChar ch
= skeleton
[i
];
854 dateSkeleton
.append(ch
);
858 dateSkeleton
.append(ch
);
862 dateSkeleton
.append(ch
);
866 dateSkeleton
.append(ch
);
885 normalizedDateSkeleton
.append(ch
);
886 dateSkeleton
.append(ch
);
889 // 'a' is implicitly handled
890 timeSkeleton
.append(ch
);
893 timeSkeleton
.append(ch
);
897 timeSkeleton
.append(ch
);
901 timeSkeleton
.append(ch
);
906 timeSkeleton
.append(ch
);
910 timeSkeleton
.append(ch
);
920 timeSkeleton
.append(ch
);
921 normalizedTimeSkeleton
.append(ch
);
926 /* generate normalized form for date*/
928 for (i
= 0; i
< yCount
; ++i
) {
929 normalizedDateSkeleton
.append(LOW_Y
);
934 normalizedDateSkeleton
.append(CAP_M
);
937 for ( i
= 0; i
< MCount
&& i
< MAX_M_COUNT
; ++i
) {
938 normalizedDateSkeleton
.append(CAP_M
);
944 normalizedDateSkeleton
.append(CAP_E
);
947 for ( i
= 0; i
< ECount
&& i
< MAX_E_COUNT
; ++i
) {
948 normalizedDateSkeleton
.append(CAP_E
);
953 normalizedDateSkeleton
.append(LOW_D
);
956 /* generate normalized form for time */
958 normalizedTimeSkeleton
.append(CAP_H
);
960 else if ( hCount
!= 0 ) {
961 normalizedTimeSkeleton
.append(LOW_H
);
964 normalizedTimeSkeleton
.append(LOW_M
);
967 normalizedTimeSkeleton
.append(LOW_Z
);
970 normalizedTimeSkeleton
.append(LOW_V
);
976 * Generate date or time interval pattern from resource,
977 * and set them into the interval pattern locale to this formatter.
979 * It needs to handle the following:
980 * 1. need to adjust field width.
981 * For example, the interval patterns saved in DateIntervalInfo
982 * includes "dMMMy", but not "dMMMMy".
983 * Need to get interval patterns for dMMMMy from dMMMy.
984 * Another example, the interval patterns saved in DateIntervalInfo
985 * includes "hmv", but not "hmz".
986 * Need to get interval patterns for "hmz' from 'hmv'
988 * 2. there might be no pattern for 'y' differ for skeleton "Md",
989 * in order to get interval patterns for 'y' differ,
990 * need to look for it from skeleton 'yMd'
992 * @param dateSkeleton normalized date skeleton
993 * @param timeSkeleton normalized time skeleton
994 * @return whether the resource is found for the skeleton.
995 * TRUE if interval pattern found for the skeleton,
1000 DateIntervalFormat::setSeparateDateTimePtn(
1001 const UnicodeString
& dateSkeleton
,
1002 const UnicodeString
& timeSkeleton
) {
1003 const UnicodeString
* skeleton
;
1004 // if both date and time skeleton present,
1005 // the final interval pattern might include time interval patterns
1006 // ( when, am_pm, hour, minute differ ),
1007 // but not date interval patterns ( when year, month, day differ ).
1008 // For year/month/day differ, it falls back to fall-back pattern.
1009 if ( timeSkeleton
.length() != 0 ) {
1010 skeleton
= &timeSkeleton
;
1012 skeleton
= &dateSkeleton
;
1015 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
1016 * are defined in resource,
1017 * interval patterns for skeleton "dMMMMy" are calculated by
1018 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
1019 * 2. get the interval patterns for "dMMMy",
1020 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
1021 * getBestSkeleton() is step 1.
1023 // best skeleton, and the difference information
1024 int8_t differenceInfo
= 0;
1025 const UnicodeString
* bestSkeleton
= fInfo
->getBestSkeleton(*skeleton
,
1027 /* best skeleton could be NULL.
1028 For example: in "ca" resource file,
1029 interval format is defined as following
1031 fallback{"{0} - {1}"}
1033 there is no skeletons/interval patterns defined,
1034 and the best skeleton match could be NULL
1036 if ( bestSkeleton
== NULL
) {
1040 // Set patterns for fallback use, need to do this
1041 // before returning if differenceInfo == -1
1043 if ( dateSkeleton
.length() != 0) {
1044 status
= U_ZERO_ERROR
;
1045 fDatePattern
= new UnicodeString(DateFormat::getBestPattern(
1046 fLocale
, dateSkeleton
, status
));
1048 if ( timeSkeleton
.length() != 0) {
1049 status
= U_ZERO_ERROR
;
1050 fTimePattern
= new UnicodeString(DateFormat::getBestPattern(
1051 fLocale
, timeSkeleton
, status
));
1055 // 0 means the best matched skeleton is the same as input skeleton
1056 // 1 means the fields are the same, but field width are different
1057 // 2 means the only difference between fields are v/z,
1058 // -1 means there are other fields difference
1059 // (this will happen, for instance, if the supplied skeleton has seconds,
1060 // but no skeletons in the intervalFormats data do)
1061 if ( differenceInfo
== -1 ) {
1062 // skeleton has different fields, not only v/z difference
1066 if ( timeSkeleton
.length() == 0 ) {
1067 UnicodeString extendedSkeleton
;
1068 UnicodeString extendedBestSkeleton
;
1069 // only has date skeleton
1070 setIntervalPattern(UCAL_DATE
, skeleton
, bestSkeleton
, differenceInfo
,
1071 &extendedSkeleton
, &extendedBestSkeleton
);
1073 UBool extended
= setIntervalPattern(UCAL_MONTH
, skeleton
, bestSkeleton
,
1075 &extendedSkeleton
, &extendedBestSkeleton
);
1078 bestSkeleton
= &extendedBestSkeleton
;
1079 skeleton
= &extendedSkeleton
;
1081 setIntervalPattern(UCAL_YEAR
, skeleton
, bestSkeleton
, differenceInfo
,
1082 &extendedSkeleton
, &extendedBestSkeleton
);
1084 setIntervalPattern(UCAL_MINUTE
, skeleton
, bestSkeleton
, differenceInfo
);
1085 setIntervalPattern(UCAL_HOUR
, skeleton
, bestSkeleton
, differenceInfo
);
1086 setIntervalPattern(UCAL_AM_PM
, skeleton
, bestSkeleton
, differenceInfo
);
1094 DateIntervalFormat::setFallbackPattern(UCalendarDateFields field
,
1095 const UnicodeString
& skeleton
,
1096 UErrorCode
& status
) {
1097 if ( U_FAILURE(status
) ) {
1100 UnicodeString pattern
= DateFormat::getBestPattern(
1101 fLocale
, skeleton
, status
);
1102 if ( U_FAILURE(status
) ) {
1105 setPatternInfo(field
, NULL
, &pattern
, fInfo
->getDefaultOrder());
1112 DateIntervalFormat::setPatternInfo(UCalendarDateFields field
,
1113 const UnicodeString
* firstPart
,
1114 const UnicodeString
* secondPart
,
1115 UBool laterDateFirst
) {
1116 // for fall back interval patterns,
1117 // the first part of the pattern is empty,
1118 // the second part of the pattern is the full-pattern
1119 // should be used in fall-back.
1120 UErrorCode status
= U_ZERO_ERROR
;
1121 // following should not set any wrong status.
1122 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
1124 if ( U_FAILURE(status
) ) {
1127 PatternInfo
& ptn
= fIntervalPatterns
[itvPtnIndex
];
1129 ptn
.firstPart
= *firstPart
;
1132 ptn
.secondPart
= *secondPart
;
1134 ptn
.laterDateFirst
= laterDateFirst
;
1138 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1139 const UnicodeString
& intervalPattern
) {
1140 UBool order
= fInfo
->getDefaultOrder();
1141 setIntervalPattern(field
, intervalPattern
, order
);
1146 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1147 const UnicodeString
& intervalPattern
,
1148 UBool laterDateFirst
) {
1149 const UnicodeString
* pattern
= &intervalPattern
;
1150 UBool order
= laterDateFirst
;
1151 // check for "latestFirst:" or "earliestFirst:" prefix
1152 int8_t prefixLength
= UPRV_LENGTHOF(gLaterFirstPrefix
);
1153 int8_t earliestFirstLength
= UPRV_LENGTHOF(gEarlierFirstPrefix
);
1154 UnicodeString realPattern
;
1155 if ( intervalPattern
.startsWith(gLaterFirstPrefix
, prefixLength
) ) {
1157 intervalPattern
.extract(prefixLength
,
1158 intervalPattern
.length() - prefixLength
,
1160 pattern
= &realPattern
;
1161 } else if ( intervalPattern
.startsWith(gEarlierFirstPrefix
,
1162 earliestFirstLength
) ) {
1164 intervalPattern
.extract(earliestFirstLength
,
1165 intervalPattern
.length() - earliestFirstLength
,
1167 pattern
= &realPattern
;
1170 int32_t splitPoint
= splitPatternInto2Part(*pattern
);
1172 UnicodeString firstPart
;
1173 UnicodeString secondPart
;
1174 pattern
->extract(0, splitPoint
, firstPart
);
1175 if ( splitPoint
< pattern
->length() ) {
1176 pattern
->extract(splitPoint
, pattern
->length()-splitPoint
, secondPart
);
1178 setPatternInfo(field
, &firstPart
, &secondPart
, order
);
1185 * Generate interval pattern from existing resource
1187 * It not only save the interval patterns,
1188 * but also return the extended skeleton and its best match skeleton.
1190 * @param field largest different calendar field
1191 * @param skeleton skeleton
1192 * @param bestSkeleton the best match skeleton which has interval pattern
1193 * defined in resource
1194 * @param differenceInfo the difference between skeleton and best skeleton
1195 * 0 means the best matched skeleton is the same as input skeleton
1196 * 1 means the fields are the same, but field width are different
1197 * 2 means the only difference between fields are v/z,
1198 * -1 means there are other fields difference
1200 * @param extendedSkeleton extended skeleton
1201 * @param extendedBestSkeleton extended best match skeleton
1202 * @return whether the interval pattern is found
1203 * through extending skeleton or not.
1204 * TRUE if interval pattern is found by
1205 * extending skeleton, FALSE otherwise.
1209 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1210 const UnicodeString
* skeleton
,
1211 const UnicodeString
* bestSkeleton
,
1212 int8_t differenceInfo
,
1213 UnicodeString
* extendedSkeleton
,
1214 UnicodeString
* extendedBestSkeleton
) {
1215 UErrorCode status
= U_ZERO_ERROR
;
1216 // following getIntervalPattern() should not generate error status
1217 UnicodeString pattern
;
1218 fInfo
->getIntervalPattern(*bestSkeleton
, field
, pattern
, status
);
1219 if ( pattern
.isEmpty() ) {
1221 if ( SimpleDateFormat::isFieldUnitIgnored(*bestSkeleton
, field
) ) {
1222 // do nothing, format will handle it
1226 // for 24 hour system, interval patterns in resource file
1227 // might not include pattern when am_pm differ,
1228 // which should be the same as hour differ.
1229 // add it here for simplicity
1230 if ( field
== UCAL_AM_PM
) {
1231 fInfo
->getIntervalPattern(*bestSkeleton
, UCAL_HOUR
, pattern
,status
);
1232 if ( !pattern
.isEmpty() ) {
1233 setIntervalPattern(field
, pattern
);
1237 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
1238 // first, get best match pattern "MMMd",
1239 // since there is no pattern for 'y' differs for skeleton 'MMMd',
1240 // need to look for it from skeleton 'yMMMd',
1241 // if found, adjust field width in interval pattern from
1243 UChar fieldLetter
= fgCalendarFieldToPatternLetter
[field
];
1244 if ( extendedSkeleton
) {
1245 *extendedSkeleton
= *skeleton
;
1246 *extendedBestSkeleton
= *bestSkeleton
;
1247 extendedSkeleton
->insert(0, fieldLetter
);
1248 extendedBestSkeleton
->insert(0, fieldLetter
);
1249 // for example, looking for patterns when 'y' differ for
1251 fInfo
->getIntervalPattern(*extendedBestSkeleton
,field
,pattern
,status
);
1252 if ( pattern
.isEmpty() && differenceInfo
== 0 ) {
1253 // if there is no skeleton "yMMMM" defined,
1254 // look for the best match skeleton, for example: "yMMM"
1255 const UnicodeString
* tmpBest
= fInfo
->getBestSkeleton(
1256 *extendedBestSkeleton
, differenceInfo
);
1257 if ( tmpBest
!= 0 && differenceInfo
!= -1 ) {
1258 fInfo
->getIntervalPattern(*tmpBest
, field
, pattern
, status
);
1259 bestSkeleton
= tmpBest
;
1264 if ( !pattern
.isEmpty() ) {
1265 if ( differenceInfo
!= 0 ) {
1266 UnicodeString adjustIntervalPattern
;
1267 adjustFieldWidth(*skeleton
, *bestSkeleton
, pattern
, differenceInfo
,
1268 adjustIntervalPattern
);
1269 setIntervalPattern(field
, adjustIntervalPattern
);
1271 setIntervalPattern(field
, pattern
);
1273 if ( extendedSkeleton
&& !extendedSkeleton
->isEmpty() ) {
1283 DateIntervalFormat::splitPatternInto2Part(const UnicodeString
& intervalPattern
) {
1284 UBool inQuote
= false;
1288 /* repeatedPattern used to record whether a pattern has already seen.
1289 It is a pattern applies to first calendar if it is first time seen,
1290 otherwise, it is a pattern applies to the second calendar
1292 UBool patternRepeated
[] =
1294 // A B C D E F G H I J K L M N O
1295 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1296 // P Q R S T U V W X Y Z
1297 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1298 // a b c d e f g h i j k l m n o
1299 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1300 // p q r s t u v w x y z
1301 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1304 int8_t PATTERN_CHAR_BASE
= 0x41;
1306 /* loop through the pattern string character by character looking for
1307 * the first repeated pattern letter, which breaks the interval pattern
1311 UBool foundRepetition
= false;
1312 for (i
= 0; i
< intervalPattern
.length(); ++i
) {
1313 UChar ch
= intervalPattern
.charAt(i
);
1315 if (ch
!= prevCh
&& count
> 0) {
1316 // check the repeativeness of pattern letter
1317 UBool repeated
= patternRepeated
[(int)(prevCh
- PATTERN_CHAR_BASE
)];
1318 if ( repeated
== FALSE
) {
1319 patternRepeated
[prevCh
- PATTERN_CHAR_BASE
] = TRUE
;
1321 foundRepetition
= true;
1326 if (ch
== 0x0027 /*'*/) {
1327 // Consecutive single quotes are a single quote literal,
1328 // either outside of quotes or between quotes
1329 if ((i
+1) < intervalPattern
.length() &&
1330 intervalPattern
.charAt(i
+1) == 0x0027 /*'*/) {
1333 inQuote
= ! inQuote
;
1336 else if (!inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
1337 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
1338 // ch is a date-time pattern character
1343 // check last pattern char, distinguish
1344 // "dd MM" ( no repetition ),
1345 // "d-d"(last char repeated ), and
1346 // "d-d MM" ( repetition found )
1347 if ( count
> 0 && foundRepetition
== FALSE
) {
1348 if ( patternRepeated
[(int)(prevCh
- PATTERN_CHAR_BASE
)] == FALSE
) {
1355 static const UChar bracketedZero
[] = {0x7B,0x30,0x7D};
1356 static const UChar bracketedOne
[] = {0x7B,0x31,0x7D};
1359 DateIntervalFormat::adjustPosition(UnicodeString
& combiningPattern
, // has {0} and {1} in it
1360 UnicodeString
& pat0
, FieldPosition
& pos0
, // pattern and pos corresponding to {0}
1361 UnicodeString
& pat1
, FieldPosition
& pos1
, // pattern and pos corresponding to {1}
1362 FieldPosition
& posResult
) {
1363 int32_t index0
= combiningPattern
.indexOf(bracketedZero
, 3, 0);
1364 int32_t index1
= combiningPattern
.indexOf(bracketedOne
, 3, 0);
1365 if (index0
< 0 || index1
< 0) {
1368 int32_t placeholderLen
= 3; // length of "{0}" or "{1}"
1369 if (index0
< index1
) {
1370 if (pos0
.getEndIndex() > 0) {
1371 posResult
.setBeginIndex(pos0
.getBeginIndex() + index0
);
1372 posResult
.setEndIndex(pos0
.getEndIndex() + index0
);
1373 } else if (pos1
.getEndIndex() > 0) {
1375 index1
+= pat0
.length() - placeholderLen
; // adjust for pat0 replacing {0}
1376 posResult
.setBeginIndex(pos1
.getBeginIndex() + index1
);
1377 posResult
.setEndIndex(pos1
.getEndIndex() + index1
);
1380 if (pos1
.getEndIndex() > 0) {
1381 posResult
.setBeginIndex(pos1
.getBeginIndex() + index1
);
1382 posResult
.setEndIndex(pos1
.getEndIndex() + index1
);
1383 } else if (pos0
.getEndIndex() > 0) {
1385 index0
+= pat1
.length() - placeholderLen
; // adjust for pat1 replacing {1}
1386 posResult
.setBeginIndex(pos0
.getBeginIndex() + index0
);
1387 posResult
.setEndIndex(pos0
.getEndIndex() + index0
);
1393 DateIntervalFormat::fallbackFormat(Calendar
& fromCalendar
,
1394 Calendar
& toCalendar
,
1395 UBool fromToOnSameDay
, // new
1396 UnicodeString
& appendTo
,
1398 UErrorCode
& status
) const {
1399 if ( U_FAILURE(status
) ) {
1402 UnicodeString fullPattern
; // for saving the pattern in fDateFormat
1403 UBool formatDatePlusTimeRange
= (fromToOnSameDay
&& fDatePattern
&& fTimePattern
);
1405 if (formatDatePlusTimeRange
) {
1406 fDateFormat
->toPattern(fullPattern
); // save current pattern, restore later
1407 fDateFormat
->applyPattern(*fTimePattern
);
1409 FieldPosition otherPos
;
1410 otherPos
.setField(pos
.getField());
1411 UnicodeString earlierDate
;
1412 fDateFormat
->format(fromCalendar
, earlierDate
, pos
);
1413 UnicodeString laterDate
;
1414 fDateFormat
->format(toCalendar
, laterDate
, otherPos
);
1415 UnicodeString fallbackPattern
;
1416 fInfo
->getFallbackIntervalPattern(fallbackPattern
);
1417 adjustPosition(fallbackPattern
, earlierDate
, pos
, laterDate
, otherPos
, pos
);
1418 UnicodeString fallbackRange
;
1419 SimpleFormatter(fallbackPattern
, 2, 2, status
).
1420 format(earlierDate
, laterDate
, fallbackRange
, status
);
1421 if ( U_SUCCESS(status
) && formatDatePlusTimeRange
) {
1422 // fallbackRange has just the time range, need to format the date part and combine that
1423 UnicodeString
dateTimeFormatNoQuote(*fDateTimeFormat
);
1424 dateTimeFormatNoQuote
.findAndReplace(UnicodeString(0x0027), UnicodeString());
1425 fDateFormat
->applyPattern(*fDatePattern
);
1426 UnicodeString datePortion
;
1427 otherPos
.setBeginIndex(0);
1428 otherPos
.setEndIndex(0);
1429 fDateFormat
->format(fromCalendar
, datePortion
, otherPos
);
1430 adjustPosition(dateTimeFormatNoQuote
, fallbackRange
, pos
, datePortion
, otherPos
, pos
);
1431 const UnicodeString
*values
[2] = {
1432 &fallbackRange
, // {0} is time range
1433 &datePortion
, // {1} is single date portion
1435 SimpleFormatter(dateTimeFormatNoQuote
, 2, 2, status
).
1436 formatAndReplace(values
, 2, fallbackRange
, NULL
, 0, status
);
1438 if ( U_SUCCESS(status
) ) {
1439 appendTo
.append(fallbackRange
);
1441 if (formatDatePlusTimeRange
) {
1442 // restore full pattern
1443 fDateFormat
->applyPattern(fullPattern
);
1452 DateIntervalFormat::fieldExistsInSkeleton(UCalendarDateFields field
,
1453 const UnicodeString
& skeleton
)
1455 const UChar fieldChar
= fgCalendarFieldToPatternLetter
[field
];
1456 return ( (skeleton
.indexOf(fieldChar
) == -1)?FALSE
:TRUE
) ;
1462 DateIntervalFormat::adjustFieldWidth(const UnicodeString
& inputSkeleton
,
1463 const UnicodeString
& bestMatchSkeleton
,
1464 const UnicodeString
& bestIntervalPattern
,
1465 int8_t differenceInfo
,
1466 UnicodeString
& adjustedPtn
) {
1467 adjustedPtn
= bestIntervalPattern
;
1468 int32_t inputSkeletonFieldWidth
[] =
1470 // A B C D E F G H I J K L M N O
1471 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1472 // P Q R S T U V W X Y Z
1473 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1474 // a b c d e f g h i j k l m n o
1475 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1476 // p q r s t u v w x y z
1477 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1480 int32_t bestMatchSkeletonFieldWidth
[] =
1482 // A B C D E F G H I J K L M N O
1483 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1484 // P Q R S T U V W X Y Z
1485 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1486 // a b c d e f g h i j k l m n o
1487 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1488 // p q r s t u v w x y z
1489 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1492 DateIntervalInfo::parseSkeleton(inputSkeleton
, inputSkeletonFieldWidth
);
1493 DateIntervalInfo::parseSkeleton(bestMatchSkeleton
, bestMatchSkeletonFieldWidth
);
1494 if ( differenceInfo
== 2 ) {
1495 adjustedPtn
.findAndReplace(UnicodeString((UChar
)0x76 /* v */),
1496 UnicodeString((UChar
)0x7a /* z */));
1499 UBool inQuote
= false;
1503 const int8_t PATTERN_CHAR_BASE
= 0x41;
1505 // loop through the pattern string character by character
1506 int32_t adjustedPtnLength
= adjustedPtn
.length();
1508 for (i
= 0; i
< adjustedPtnLength
; ++i
) {
1509 UChar ch
= adjustedPtn
.charAt(i
);
1510 if (ch
!= prevCh
&& count
> 0) {
1511 // check the repeativeness of pattern letter
1512 UChar skeletonChar
= prevCh
;
1513 if ( skeletonChar
== CAP_L
) {
1514 // there is no "L" (always be "M") in skeleton,
1515 // but there is "L" in pattern.
1516 // for skeleton "M+", the pattern might be "...L..."
1517 skeletonChar
= CAP_M
;
1519 int32_t fieldCount
= bestMatchSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1520 int32_t inputFieldCount
= inputSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1521 if ( fieldCount
== count
&& inputFieldCount
> fieldCount
) {
1522 count
= inputFieldCount
- fieldCount
;
1524 for ( j
= 0; j
< count
; ++j
) {
1525 adjustedPtn
.insert(i
, prevCh
);
1528 adjustedPtnLength
+= count
;
1532 if (ch
== 0x0027 /*'*/) {
1533 // Consecutive single quotes are a single quote literal,
1534 // either outside of quotes or between quotes
1535 if ((i
+1) < adjustedPtn
.length() && adjustedPtn
.charAt(i
+1) == 0x0027 /* ' */) {
1538 inQuote
= ! inQuote
;
1541 else if ( ! inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
1542 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
1543 // ch is a date-time pattern character
1550 // check the repeativeness of pattern letter
1551 UChar skeletonChar
= prevCh
;
1552 if ( skeletonChar
== CAP_L
) {
1553 // there is no "L" (always be "M") in skeleton,
1554 // but there is "L" in pattern.
1555 // for skeleton "M+", the pattern might be "...L..."
1556 skeletonChar
= CAP_M
;
1558 int32_t fieldCount
= bestMatchSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1559 int32_t inputFieldCount
= inputSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1560 if ( fieldCount
== count
&& inputFieldCount
> fieldCount
) {
1561 count
= inputFieldCount
- fieldCount
;
1563 for ( j
= 0; j
< count
; ++j
) {
1564 adjustedPtn
.append(prevCh
);
1573 DateIntervalFormat::concatSingleDate2TimeInterval(UnicodeString
& format
,
1574 const UnicodeString
& datePattern
,
1575 UCalendarDateFields field
,
1576 UErrorCode
& status
) {
1577 // following should not set wrong status
1578 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
1580 if ( U_FAILURE(status
) ) {
1583 PatternInfo
& timeItvPtnInfo
= fIntervalPatterns
[itvPtnIndex
];
1584 if ( !timeItvPtnInfo
.firstPart
.isEmpty() ) {
1585 UnicodeString
timeIntervalPattern(timeItvPtnInfo
.firstPart
);
1586 timeIntervalPattern
.append(timeItvPtnInfo
.secondPart
);
1587 UnicodeString combinedPattern
;
1588 SimpleFormatter(format
, 2, 2, status
).
1589 format(timeIntervalPattern
, datePattern
, combinedPattern
, status
);
1590 if ( U_FAILURE(status
) ) {
1593 setIntervalPattern(field
, combinedPattern
, timeItvPtnInfo
.laterDateFirst
);
1596 // it should not happen if the interval format defined is valid
1602 DateIntervalFormat::fgCalendarFieldToPatternLetter
[] =
1604 /*GyM*/ CAP_G
, LOW_Y
, CAP_M
,
1605 /*wWd*/ LOW_W
, CAP_W
, LOW_D
,
1606 /*DEF*/ CAP_D
, CAP_E
, CAP_F
,
1607 /*ahH*/ LOW_A
, LOW_H
, CAP_H
,
1608 /*msS*/ LOW_M
, LOW_S
, CAP_S
, // MINUTE, SECOND, MILLISECOND
1609 /*z.Y*/ LOW_Z
, SPACE
, CAP_Y
, // ZONE_OFFSET, DST_OFFSET, YEAR_WOY,
1610 /*eug*/ LOW_E
, LOW_U
, LOW_G
, // DOW_LOCAL, EXTENDED_YEAR, JULIAN_DAY,
1611 /*A..*/ CAP_A
, SPACE
, SPACE
, // MILLISECONDS_IN_DAY, IS_LEAP_MONTH, FIELD_COUNT