1 /*******************************************************************************
2 * Copyright (C) 2008-2010, International Business Machines Corporation and
3 * others. All Rights Reserved.
4 *******************************************************************************
8 *******************************************************************************
11 #include <typeinfo> // for 'typeid' to work
13 #include "unicode/dtitvfmt.h"
15 #if !UCONFIG_NO_FORMATTING
17 //TODO: put in compilation
18 //#define DTITVFMT_DEBUG 1
21 #include "unicode/msgfmt.h"
22 #include "unicode/dtptngen.h"
23 #include "unicode/dtitvinf.h"
24 #include "unicode/calendar.h"
25 #include "dtitv_impl.h"
39 #define PRINTMESG(msg) { std::cout << "(" << __FILE__ << ":" << __LINE__ << ") " << msg << "\n"; }
43 static const UChar gDateFormatSkeleton
[][11] = {
45 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, CAP_M
, CAP_E
, CAP_E
, CAP_E
, CAP_E
, LOW_D
, 0},
47 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, CAP_M
, LOW_D
, 0},
49 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, LOW_D
, 0},
51 {LOW_Y
, CAP_M
, LOW_D
, 0} };
54 static const char gDateTimePatternsTag
[]="DateTimePatterns";
58 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
};
61 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
};
64 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalFormat
)
68 DateIntervalFormat
* U_EXPORT2
69 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
71 return createInstance(skeleton
, Locale::getDefault(), status
);
75 DateIntervalFormat
* U_EXPORT2
76 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
83 skeleton
.extract(0, skeleton
.length(), result
, "UTF-8");
85 ((SimpleDateFormat
*)dtfmt
)->toPattern(pat
);
86 pat
.extract(0, pat
.length(), result_1
, "UTF-8");
87 sprintf(mesg
, "skeleton: %s; pattern: %s\n", result
, result_1
);
91 DateIntervalInfo
* dtitvinf
= new DateIntervalInfo(locale
, status
);
92 return create(locale
, dtitvinf
, &skeleton
, status
);
97 DateIntervalFormat
* U_EXPORT2
98 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
99 const DateIntervalInfo
& dtitvinf
,
100 UErrorCode
& status
) {
101 return createInstance(skeleton
, Locale::getDefault(), dtitvinf
, status
);
105 DateIntervalFormat
* U_EXPORT2
106 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
107 const Locale
& locale
,
108 const DateIntervalInfo
& dtitvinf
,
109 UErrorCode
& status
) {
110 DateIntervalInfo
* ptn
= dtitvinf
.clone();
111 return create(locale
, ptn
, &skeleton
, status
);
115 DateIntervalFormat::DateIntervalFormat()
124 DateIntervalFormat::DateIntervalFormat(const DateIntervalFormat
& itvfmt
)
136 DateIntervalFormat::operator=(const DateIntervalFormat
& itvfmt
) {
137 if ( this != &itvfmt
) {
140 delete fFromCalendar
;
143 if ( itvfmt
.fDateFormat
) {
144 fDateFormat
= (SimpleDateFormat
*)itvfmt
.fDateFormat
->clone();
148 if ( itvfmt
.fInfo
) {
149 fInfo
= itvfmt
.fInfo
->clone();
153 if ( itvfmt
.fFromCalendar
) {
154 fFromCalendar
= itvfmt
.fFromCalendar
->clone();
156 fFromCalendar
= NULL
;
158 if ( itvfmt
.fToCalendar
) {
159 fToCalendar
= itvfmt
.fToCalendar
->clone();
163 fSkeleton
= itvfmt
.fSkeleton
;
165 for ( i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
166 fIntervalPatterns
[i
] = itvfmt
.fIntervalPatterns
[i
];
169 fDtpng
= itvfmt
.fDtpng
->clone();
176 DateIntervalFormat::~DateIntervalFormat() {
179 delete fFromCalendar
;
186 DateIntervalFormat::clone(void) const {
187 return new DateIntervalFormat(*this);
192 DateIntervalFormat::operator==(const Format
& other
) const {
193 if (typeid(*this) == typeid(other
)) {
194 const DateIntervalFormat
* fmt
= (DateIntervalFormat
*)&other
;
195 #ifdef DTITVFMT_DEBUG
197 equal
= (this == fmt
);
199 equal
= (*fInfo
== *fmt
->fInfo
);
200 equal
= (*fDateFormat
== *fmt
->fDateFormat
);
201 equal
= fFromCalendar
->isEquivalentTo(*fmt
->fFromCalendar
) ;
202 equal
= fToCalendar
->isEquivalentTo(*fmt
->fToCalendar
) ;
203 equal
= (fSkeleton
== fmt
->fSkeleton
);
206 res
= ( this == fmt
) ||
207 ( Format::operator==(other
) &&
209 ( *fInfo
== *fmt
->fInfo
) &&
211 ( *fDateFormat
== *fmt
->fDateFormat
) &&
213 fFromCalendar
->isEquivalentTo(*fmt
->fFromCalendar
) &&
215 fToCalendar
->isEquivalentTo(*fmt
->fToCalendar
) &&
216 fSkeleton
== fmt
->fSkeleton
&&
218 (*fDtpng
== *fmt
->fDtpng
) );
220 for (i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
&& res
== TRUE
; ++i
) {
221 res
= ( fIntervalPatterns
[i
].firstPart
==
222 fmt
->fIntervalPatterns
[i
].firstPart
) &&
223 ( fIntervalPatterns
[i
].secondPart
==
224 fmt
->fIntervalPatterns
[i
].secondPart
) &&
225 ( fIntervalPatterns
[i
].laterDateFirst
==
226 fmt
->fIntervalPatterns
[i
].laterDateFirst
) ;
236 DateIntervalFormat::format(const Formattable
& obj
,
237 UnicodeString
& appendTo
,
238 FieldPosition
& fieldPosition
,
239 UErrorCode
& status
) const {
240 if ( U_FAILURE(status
) ) {
244 if ( obj
.getType() == Formattable::kObject
) {
245 const UObject
* formatObj
= obj
.getObject();
246 const DateInterval
* interval
= dynamic_cast<const DateInterval
*>(formatObj
);
247 if (interval
!= NULL
){
248 return format(interval
, appendTo
, fieldPosition
, status
);
251 status
= U_ILLEGAL_ARGUMENT_ERROR
;
257 DateIntervalFormat::format(const DateInterval
* dtInterval
,
258 UnicodeString
& appendTo
,
259 FieldPosition
& fieldPosition
,
260 UErrorCode
& status
) const {
261 if ( U_FAILURE(status
) ) {
265 if ( fFromCalendar
!= NULL
&& fToCalendar
!= NULL
&&
266 fDateFormat
!= NULL
&& fInfo
!= NULL
) {
267 fFromCalendar
->setTime(dtInterval
->getFromDate(), status
);
268 fToCalendar
->setTime(dtInterval
->getToDate(), status
);
269 if ( U_SUCCESS(status
) ) {
270 return format(*fFromCalendar
, *fToCalendar
, appendTo
,fieldPosition
, status
);
278 DateIntervalFormat::format(Calendar
& fromCalendar
,
279 Calendar
& toCalendar
,
280 UnicodeString
& appendTo
,
282 UErrorCode
& status
) const {
283 if ( U_FAILURE(status
) ) {
287 // not support different calendar types and time zones
288 //if ( fromCalendar.getType() != toCalendar.getType() ) {
289 if ( !fromCalendar
.isEquivalentTo(toCalendar
) ) {
290 status
= U_ILLEGAL_ARGUMENT_ERROR
;
294 // First, find the largest different calendar field.
295 UCalendarDateFields field
= UCAL_FIELD_COUNT
;
297 if ( fromCalendar
.get(UCAL_ERA
,status
) != toCalendar
.get(UCAL_ERA
,status
)) {
299 } else if ( fromCalendar
.get(UCAL_YEAR
, status
) !=
300 toCalendar
.get(UCAL_YEAR
, status
) ) {
302 } else if ( fromCalendar
.get(UCAL_MONTH
, status
) !=
303 toCalendar
.get(UCAL_MONTH
, status
) ) {
305 } else if ( fromCalendar
.get(UCAL_DATE
, status
) !=
306 toCalendar
.get(UCAL_DATE
, status
) ) {
308 } else if ( fromCalendar
.get(UCAL_AM_PM
, status
) !=
309 toCalendar
.get(UCAL_AM_PM
, status
) ) {
311 } else if ( fromCalendar
.get(UCAL_HOUR
, status
) !=
312 toCalendar
.get(UCAL_HOUR
, status
) ) {
314 } else if ( fromCalendar
.get(UCAL_MINUTE
, status
) !=
315 toCalendar
.get(UCAL_MINUTE
, status
) ) {
319 if ( U_FAILURE(status
) ) {
322 if ( field
== UCAL_FIELD_COUNT
) {
323 /* ignore the second/millisecond etc. small fields' difference.
324 * use single date when all the above are the same.
326 return fDateFormat
->format(fromCalendar
, appendTo
, pos
);
329 // following call should not set wrong status,
330 // all the pass-in fields are valid till here
331 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
333 const PatternInfo
& intervalPattern
= fIntervalPatterns
[itvPtnIndex
];
335 if ( intervalPattern
.firstPart
.isEmpty() &&
336 intervalPattern
.secondPart
.isEmpty() ) {
337 if ( fDateFormat
->isFieldUnitIgnored(field
) ) {
338 /* the largest different calendar field is small than
339 * the smallest calendar field in pattern,
340 * return single date format.
342 return fDateFormat
->format(fromCalendar
, appendTo
, pos
);
344 return fallbackFormat(fromCalendar
, toCalendar
, appendTo
, pos
, status
);
346 // If the first part in interval pattern is empty,
347 // the 2nd part of it saves the full-pattern used in fall-back.
348 // For a 'real' interval pattern, the first part will never be empty.
349 if ( intervalPattern
.firstPart
.isEmpty() ) {
351 UnicodeString originalPattern
;
352 fDateFormat
->toPattern(originalPattern
);
353 fDateFormat
->applyPattern(intervalPattern
.secondPart
);
354 appendTo
= fallbackFormat(fromCalendar
, toCalendar
, appendTo
, pos
, status
);
355 fDateFormat
->applyPattern(originalPattern
);
360 if ( intervalPattern
.laterDateFirst
) {
361 firstCal
= &toCalendar
;
362 secondCal
= &fromCalendar
;
364 firstCal
= &fromCalendar
;
365 secondCal
= &toCalendar
;
367 // break the interval pattern into 2 parts,
368 // first part should not be empty,
369 UnicodeString originalPattern
;
370 fDateFormat
->toPattern(originalPattern
);
371 fDateFormat
->applyPattern(intervalPattern
.firstPart
);
372 fDateFormat
->format(*firstCal
, appendTo
, pos
);
373 if ( !intervalPattern
.secondPart
.isEmpty() ) {
374 fDateFormat
->applyPattern(intervalPattern
.secondPart
);
375 fDateFormat
->format(*secondCal
, appendTo
, pos
);
377 fDateFormat
->applyPattern(originalPattern
);
384 DateIntervalFormat::parseObject(const UnicodeString
& /* source */,
385 Formattable
& /* result */,
386 ParsePosition
& /* parse_pos */) const {
387 // parseObject(const UnicodeString&, Formattable&, UErrorCode&) const
388 // will set status as U_INVALID_FORMAT_ERROR if
389 // parse_pos is still 0
395 const DateIntervalInfo
*
396 DateIntervalFormat::getDateIntervalInfo() const {
402 DateIntervalFormat::setDateIntervalInfo(const DateIntervalInfo
& newItvPattern
,
403 UErrorCode
& status
) {
405 fInfo
= new DateIntervalInfo(newItvPattern
);
407 initializePattern(status
);
414 DateIntervalFormat::getDateFormat() const {
419 DateIntervalFormat::DateIntervalFormat(const Locale
& locale
,
420 DateIntervalInfo
* dtItvInfo
,
421 const UnicodeString
* skeleton
,
429 if ( U_FAILURE(status
) ) {
433 fDtpng
= DateTimePatternGenerator::createInstance(locale
, status
);
434 SimpleDateFormat
* dtfmt
= createSDFPatternInstance(*skeleton
, locale
,
436 if ( U_FAILURE(status
) ) {
442 if ( dtfmt
== NULL
|| dtItvInfo
== NULL
|| fDtpng
== NULL
) {
443 status
= U_MEMORY_ALLOCATION_ERROR
;
444 // safe to delete NULL
451 fSkeleton
= *skeleton
;
455 if ( dtfmt
->getCalendar() ) {
456 fFromCalendar
= dtfmt
->getCalendar()->clone();
457 fToCalendar
= dtfmt
->getCalendar()->clone();
459 fFromCalendar
= NULL
;
462 initializePattern(status
);
466 SimpleDateFormat
* U_EXPORT2
467 DateIntervalFormat::createSDFPatternInstance(const UnicodeString
& skeleton
,
468 const Locale
& locale
,
469 DateTimePatternGenerator
* dtpng
,
472 if ( U_FAILURE(status
) ) {
476 const UnicodeString pattern
= dtpng
->getBestPattern(skeleton
, status
);
477 if ( U_FAILURE(status
) ) {
480 SimpleDateFormat
* dtfmt
= new SimpleDateFormat(pattern
, locale
, status
);
481 if ( U_FAILURE(status
) ) {
489 DateIntervalFormat
* U_EXPORT2
490 DateIntervalFormat::create(const Locale
& locale
,
491 DateIntervalInfo
* dtitvinf
,
492 const UnicodeString
* skeleton
,
493 UErrorCode
& status
) {
494 DateIntervalFormat
* f
= new DateIntervalFormat(locale
, dtitvinf
,
497 status
= U_MEMORY_ALLOCATION_ERROR
;
499 } else if ( U_FAILURE(status
) ) {
500 // safe to delete f, although nothing acutally is saved
510 * Initialize interval patterns locale to this formatter
512 * This code is a bit complicated since
513 * 1. the interval patterns saved in resource bundle files are interval
514 * patterns based on date or time only.
515 * It does not have interval patterns based on both date and time.
516 * Interval patterns on both date and time are algorithm generated.
518 * For example, it has interval patterns on skeleton "dMy" and "hm",
519 * but it does not have interval patterns on skeleton "dMyhm".
521 * The rule to genearte interval patterns for both date and time skeleton are
522 * 1) when the year, month, or day differs, concatenate the two original
523 * expressions with a separator between,
524 * For example, interval pattern from "Jan 10, 2007 10:10 am"
525 * to "Jan 11, 2007 10:10am" is
526 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
528 * 2) otherwise, present the date followed by the range expression
530 * For example, interval pattern from "Jan 10, 2007 10:10 am"
531 * to "Jan 10, 2007 11:10am" is
532 * "Jan 10, 2007 10:10 am - 11:10am"
534 * 2. even a pattern does not request a certion calendar field,
535 * the interval pattern needs to include such field if such fields are
536 * different between 2 dates.
537 * For example, a pattern/skeleton is "hm", but the interval pattern
538 * includes year, month, and date when year, month, and date differs.
540 * @param status output param set to success/failure code on exit
544 DateIntervalFormat::initializePattern(UErrorCode
& status
) {
545 if ( U_FAILURE(status
) ) {
548 const Locale
& locale
= fDateFormat
->getSmpFmtLocale();
549 if ( fSkeleton
.isEmpty() ) {
550 UnicodeString fullPattern
;
551 fDateFormat
->toPattern(fullPattern
);
552 #ifdef DTITVFMT_DEBUG
556 fSkeleton
.extract(0, fSkeleton
.length(), result
, "UTF-8");
557 sprintf(mesg
, "in getBestSkeleton: fSkeleton: %s; \n", result
);
560 // fSkeleton is already set by createDateIntervalInstance()
561 // or by createInstance(UnicodeString skeleton, .... )
562 fSkeleton
= fDtpng
->getSkeleton(fullPattern
, status
);
563 if ( U_FAILURE(status
) ) {
568 // initialize the fIntervalPattern ordering
570 for ( i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
571 fIntervalPatterns
[i
].laterDateFirst
= fInfo
->getDefaultOrder();
574 /* Check whether the skeleton is a combination of date and time.
575 * For the complication reason 1 explained above.
577 UnicodeString dateSkeleton
;
578 UnicodeString timeSkeleton
;
579 UnicodeString normalizedTimeSkeleton
;
580 UnicodeString normalizedDateSkeleton
;
583 /* the difference between time skeleton and normalizedTimeSkeleton are:
584 * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true)
585 * 2. 'a' is omitted in normalized time skeleton.
586 * 3. there is only one appearance for 'h' or 'H', 'm','v', 'z' in normalized
589 * The difference between date skeleton and normalizedDateSkeleton are:
590 * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
591 * 2. 'E' and 'EE' are normalized into 'EEE'
592 * 3. 'MM' is normalized into 'M'
594 getDateTimeSkeleton(fSkeleton
, dateSkeleton
, normalizedDateSkeleton
,
595 timeSkeleton
, normalizedTimeSkeleton
);
597 #ifdef DTITVFMT_DEBUG
601 fSkeleton
.extract(0, fSkeleton
.length(), result
, "UTF-8");
602 sprintf(mesg
, "in getBestSkeleton: fSkeleton: %s; \n", result
);
607 UBool found
= setSeparateDateTimePtn(normalizedDateSkeleton
,
608 normalizedTimeSkeleton
);
610 if ( found
== false ) {
612 // TODO: if user asks "m"(minute), but "d"(day) differ
613 if ( timeSkeleton
.length() != 0 ) {
614 if ( dateSkeleton
.length() == 0 ) {
616 timeSkeleton
.insert(0, gDateFormatSkeleton
[DateFormat::kShort
]);
617 UnicodeString pattern
= fDtpng
->getBestPattern(timeSkeleton
, status
);
618 if ( U_FAILURE(status
) ) {
621 // for fall back interval patterns,
622 // the first part of the pattern is empty,
623 // the second part of the pattern is the full-pattern
624 // should be used in fall-back.
625 setPatternInfo(UCAL_DATE
, NULL
, &pattern
, fInfo
->getDefaultOrder());
626 setPatternInfo(UCAL_MONTH
, NULL
, &pattern
, fInfo
->getDefaultOrder());
627 setPatternInfo(UCAL_YEAR
, NULL
, &pattern
, fInfo
->getDefaultOrder());
635 } // end of skeleton not found
636 // interval patterns for skeleton are found in resource
637 if ( timeSkeleton
.length() == 0 ) {
639 } else if ( dateSkeleton
.length() == 0 ) {
641 timeSkeleton
.insert(0, gDateFormatSkeleton
[DateFormat::kShort
]);
642 UnicodeString pattern
= fDtpng
->getBestPattern(timeSkeleton
, status
);
643 if ( U_FAILURE(status
) ) {
646 // for fall back interval patterns,
647 // the first part of the pattern is empty,
648 // the second part of the pattern is the full-pattern
649 // should be used in fall-back.
650 setPatternInfo(UCAL_DATE
, NULL
, &pattern
, fInfo
->getDefaultOrder());
651 setPatternInfo(UCAL_MONTH
, NULL
, &pattern
, fInfo
->getDefaultOrder());
652 setPatternInfo(UCAL_YEAR
, NULL
, &pattern
, fInfo
->getDefaultOrder());
655 * 1) when the year, month, or day differs,
656 * concatenate the two original expressions with a separator between,
657 * 2) otherwise, present the date followed by the
658 * range expression for the time.
661 * 1) when the year, month, or day differs,
662 * concatenate the two original expressions with a separator between,
664 // if field exists, use fall back
665 UnicodeString skeleton
= fSkeleton
;
666 if ( !fieldExistsInSkeleton(UCAL_DATE
, dateSkeleton
) ) {
667 // prefix skeleton with 'd'
668 skeleton
.insert(0, LOW_D
);
669 setFallbackPattern(UCAL_DATE
, skeleton
, status
);
671 if ( !fieldExistsInSkeleton(UCAL_MONTH
, dateSkeleton
) ) {
672 // then prefix skeleton with 'M'
673 skeleton
.insert(0, CAP_M
);
674 setFallbackPattern(UCAL_MONTH
, skeleton
, status
);
676 if ( !fieldExistsInSkeleton(UCAL_YEAR
, dateSkeleton
) ) {
677 // then prefix skeleton with 'y'
678 skeleton
.insert(0, LOW_Y
);
679 setFallbackPattern(UCAL_YEAR
, skeleton
, status
);
683 * 2) otherwise, present the date followed by the
684 * range expression for the time.
686 // Need the Date/Time pattern for concatnation the date with
687 // the time interval.
688 // The date/time pattern ( such as {0} {1} ) is saved in
689 // calendar, that is why need to get the CalendarData here.
690 CalendarData
* calData
= new CalendarData(locale
, NULL
, status
);
692 if ( U_FAILURE(status
) ) {
697 if ( calData
== NULL
) {
698 status
= U_MEMORY_ALLOCATION_ERROR
;
702 const UResourceBundle
* dateTimePatternsRes
= calData
->getByKey(
703 gDateTimePatternsTag
, status
);
704 int32_t dateTimeFormatLength
;
705 const UChar
* dateTimeFormat
= ures_getStringByIndex(
707 (int32_t)DateFormat::kDateTime
,
708 &dateTimeFormatLength
, &status
);
709 if ( U_FAILURE(status
) ) {
713 UnicodeString datePattern
= fDtpng
->getBestPattern(dateSkeleton
, status
);
715 concatSingleDate2TimeInterval(dateTimeFormat
, dateTimeFormatLength
,
716 datePattern
, UCAL_AM_PM
, status
);
717 concatSingleDate2TimeInterval(dateTimeFormat
, dateTimeFormatLength
,
718 datePattern
, UCAL_HOUR
, status
);
719 concatSingleDate2TimeInterval(dateTimeFormat
, dateTimeFormatLength
,
720 datePattern
, UCAL_MINUTE
, status
);
728 DateIntervalFormat::getDateTimeSkeleton(const UnicodeString
& skeleton
,
729 UnicodeString
& dateSkeleton
,
730 UnicodeString
& normalizedDateSkeleton
,
731 UnicodeString
& timeSkeleton
,
732 UnicodeString
& normalizedTimeSkeleton
) {
733 // dateSkeleton follows the sequence of y*M*E*d*
734 // timeSkeleton follows the sequence of hm*[v|z]?
746 for (i
= 0; i
< skeleton
.length(); ++i
) {
747 UChar ch
= skeleton
[i
];
750 dateSkeleton
.append(ch
);
754 dateSkeleton
.append(ch
);
758 dateSkeleton
.append(ch
);
762 dateSkeleton
.append(ch
);
779 normalizedDateSkeleton
.append(ch
);
780 dateSkeleton
.append(ch
);
783 // 'a' is implicitly handled
784 timeSkeleton
.append(ch
);
787 timeSkeleton
.append(ch
);
791 timeSkeleton
.append(ch
);
795 timeSkeleton
.append(ch
);
800 timeSkeleton
.append(ch
);
804 timeSkeleton
.append(ch
);
814 timeSkeleton
.append(ch
);
815 normalizedTimeSkeleton
.append(ch
);
820 /* generate normalized form for date*/
822 normalizedDateSkeleton
.append(LOW_Y
);
826 normalizedDateSkeleton
.append(CAP_M
);
829 for ( i
= 0; i
< MCount
&& i
< MAX_M_COUNT
; ++i
) {
830 normalizedDateSkeleton
.append(CAP_M
);
836 normalizedDateSkeleton
.append(CAP_E
);
839 for ( i
= 0; i
< ECount
&& i
< MAX_E_COUNT
; ++i
) {
840 normalizedDateSkeleton
.append(CAP_E
);
845 normalizedDateSkeleton
.append(LOW_D
);
848 /* generate normalized form for time */
850 normalizedTimeSkeleton
.append(CAP_H
);
852 else if ( hCount
!= 0 ) {
853 normalizedTimeSkeleton
.append(LOW_H
);
856 normalizedTimeSkeleton
.append(LOW_M
);
859 normalizedTimeSkeleton
.append(LOW_Z
);
862 normalizedTimeSkeleton
.append(LOW_V
);
868 * Generate date or time interval pattern from resource,
869 * and set them into the interval pattern locale to this formatter.
871 * It needs to handle the following:
872 * 1. need to adjust field width.
873 * For example, the interval patterns saved in DateIntervalInfo
874 * includes "dMMMy", but not "dMMMMy".
875 * Need to get interval patterns for dMMMMy from dMMMy.
876 * Another example, the interval patterns saved in DateIntervalInfo
877 * includes "hmv", but not "hmz".
878 * Need to get interval patterns for "hmz' from 'hmv'
880 * 2. there might be no pattern for 'y' differ for skeleton "Md",
881 * in order to get interval patterns for 'y' differ,
882 * need to look for it from skeleton 'yMd'
884 * @param dateSkeleton normalized date skeleton
885 * @param timeSkeleton normalized time skeleton
886 * @return whether the resource is found for the skeleton.
887 * TRUE if interval pattern found for the skeleton,
892 DateIntervalFormat::setSeparateDateTimePtn(
893 const UnicodeString
& dateSkeleton
,
894 const UnicodeString
& timeSkeleton
) {
895 const UnicodeString
* skeleton
;
896 // if both date and time skeleton present,
897 // the final interval pattern might include time interval patterns
898 // ( when, am_pm, hour, minute differ ),
899 // but not date interval patterns ( when year, month, day differ ).
900 // For year/month/day differ, it falls back to fall-back pattern.
901 if ( timeSkeleton
.length() != 0 ) {
902 skeleton
= &timeSkeleton
;
904 skeleton
= &dateSkeleton
;
907 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
908 * are defined in resource,
909 * interval patterns for skeleton "dMMMMy" are calculated by
910 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
911 * 2. get the interval patterns for "dMMMy",
912 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
913 * getBestSkeleton() is step 1.
915 // best skeleton, and the difference information
916 int8_t differenceInfo
= 0;
917 const UnicodeString
* bestSkeleton
= fInfo
->getBestSkeleton(*skeleton
,
919 /* best skeleton could be NULL.
920 For example: in "ca" resource file,
921 interval format is defined as following
923 fallback{"{0} - {1}"}
925 there is no skeletons/interval patterns defined,
926 and the best skeleton match could be NULL
928 if ( bestSkeleton
== NULL
) {
933 // 0 means the best matched skeleton is the same as input skeleton
934 // 1 means the fields are the same, but field width are different
935 // 2 means the only difference between fields are v/z,
936 // -1 means there are other fields difference
937 if ( differenceInfo
== -1 ) {
938 // skeleton has different fields, not only v/z difference
942 if ( timeSkeleton
.length() == 0 ) {
943 UnicodeString extendedSkeleton
;
944 UnicodeString extendedBestSkeleton
;
945 // only has date skeleton
946 setIntervalPattern(UCAL_DATE
, skeleton
, bestSkeleton
, differenceInfo
,
947 &extendedSkeleton
, &extendedBestSkeleton
);
949 UBool extended
= setIntervalPattern(UCAL_MONTH
, skeleton
, bestSkeleton
,
951 &extendedSkeleton
, &extendedBestSkeleton
);
954 bestSkeleton
= &extendedBestSkeleton
;
955 skeleton
= &extendedSkeleton
;
957 setIntervalPattern(UCAL_YEAR
, skeleton
, bestSkeleton
, differenceInfo
,
958 &extendedSkeleton
, &extendedBestSkeleton
);
960 setIntervalPattern(UCAL_MINUTE
, skeleton
, bestSkeleton
, differenceInfo
);
961 setIntervalPattern(UCAL_HOUR
, skeleton
, bestSkeleton
, differenceInfo
);
962 setIntervalPattern(UCAL_AM_PM
, skeleton
, bestSkeleton
, differenceInfo
);
970 DateIntervalFormat::setFallbackPattern(UCalendarDateFields field
,
971 const UnicodeString
& skeleton
,
972 UErrorCode
& status
) {
973 if ( U_FAILURE(status
) ) {
976 UnicodeString pattern
= fDtpng
->getBestPattern(skeleton
, status
);
977 if ( U_FAILURE(status
) ) {
980 setPatternInfo(field
, NULL
, &pattern
, fInfo
->getDefaultOrder());
987 DateIntervalFormat::setPatternInfo(UCalendarDateFields field
,
988 const UnicodeString
* firstPart
,
989 const UnicodeString
* secondPart
,
990 UBool laterDateFirst
) {
991 // for fall back interval patterns,
992 // the first part of the pattern is empty,
993 // the second part of the pattern is the full-pattern
994 // should be used in fall-back.
995 UErrorCode status
= U_ZERO_ERROR
;
996 // following should not set any wrong status.
997 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
999 if ( U_FAILURE(status
) ) {
1002 PatternInfo
& ptn
= fIntervalPatterns
[itvPtnIndex
];
1004 ptn
.firstPart
= *firstPart
;
1007 ptn
.secondPart
= *secondPart
;
1009 ptn
.laterDateFirst
= laterDateFirst
;
1013 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1014 const UnicodeString
& intervalPattern
) {
1015 UBool order
= fInfo
->getDefaultOrder();
1016 setIntervalPattern(field
, intervalPattern
, order
);
1021 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1022 const UnicodeString
& intervalPattern
,
1023 UBool laterDateFirst
) {
1024 const UnicodeString
* pattern
= &intervalPattern
;
1025 UBool order
= laterDateFirst
;
1026 // check for "latestFirst:" or "earliestFirst:" prefix
1027 int8_t prefixLength
= sizeof(gLaterFirstPrefix
)/sizeof(gLaterFirstPrefix
[0]);
1028 int8_t earliestFirstLength
= sizeof(gEarlierFirstPrefix
)/sizeof(gEarlierFirstPrefix
[0]);
1029 UnicodeString realPattern
;
1030 if ( intervalPattern
.startsWith(gLaterFirstPrefix
, prefixLength
) ) {
1032 intervalPattern
.extract(prefixLength
,
1033 intervalPattern
.length() - prefixLength
,
1035 pattern
= &realPattern
;
1036 } else if ( intervalPattern
.startsWith(gEarlierFirstPrefix
,
1037 earliestFirstLength
) ) {
1039 intervalPattern
.extract(earliestFirstLength
,
1040 intervalPattern
.length() - earliestFirstLength
,
1042 pattern
= &realPattern
;
1045 int32_t splitPoint
= splitPatternInto2Part(*pattern
);
1047 UnicodeString firstPart
;
1048 UnicodeString secondPart
;
1049 pattern
->extract(0, splitPoint
, firstPart
);
1050 if ( splitPoint
< pattern
->length() ) {
1051 pattern
->extract(splitPoint
, pattern
->length()-splitPoint
, secondPart
);
1053 setPatternInfo(field
, &firstPart
, &secondPart
, order
);
1060 * Generate interval pattern from existing resource
1062 * It not only save the interval patterns,
1063 * but also return the extended skeleton and its best match skeleton.
1065 * @param field largest different calendar field
1066 * @param skeleton skeleton
1067 * @param bestSkeleton the best match skeleton which has interval pattern
1068 * defined in resource
1069 * @param differenceInfo the difference between skeleton and best skeleton
1070 * 0 means the best matched skeleton is the same as input skeleton
1071 * 1 means the fields are the same, but field width are different
1072 * 2 means the only difference between fields are v/z,
1073 * -1 means there are other fields difference
1075 * @param extendedSkeleton extended skeleton
1076 * @param extendedBestSkeleton extended best match skeleton
1077 * @return whether the interval pattern is found
1078 * through extending skeleton or not.
1079 * TRUE if interval pattern is found by
1080 * extending skeleton, FALSE otherwise.
1084 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1085 const UnicodeString
* skeleton
,
1086 const UnicodeString
* bestSkeleton
,
1087 int8_t differenceInfo
,
1088 UnicodeString
* extendedSkeleton
,
1089 UnicodeString
* extendedBestSkeleton
) {
1090 UErrorCode status
= U_ZERO_ERROR
;
1091 // following getIntervalPattern() should not generate error status
1092 UnicodeString pattern
;
1093 fInfo
->getIntervalPattern(*bestSkeleton
, field
, pattern
, status
);
1094 if ( pattern
.isEmpty() ) {
1096 if ( SimpleDateFormat::isFieldUnitIgnored(*bestSkeleton
, field
) ) {
1097 // do nothing, format will handle it
1101 // for 24 hour system, interval patterns in resource file
1102 // might not include pattern when am_pm differ,
1103 // which should be the same as hour differ.
1104 // add it here for simplicity
1105 if ( field
== UCAL_AM_PM
) {
1106 fInfo
->getIntervalPattern(*bestSkeleton
, UCAL_HOUR
, pattern
,status
);
1107 if ( !pattern
.isEmpty() ) {
1108 setIntervalPattern(field
, pattern
);
1112 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
1113 // first, get best match pattern "MMMd",
1114 // since there is no pattern for 'y' differs for skeleton 'MMMd',
1115 // need to look for it from skeleton 'yMMMd',
1116 // if found, adjust field width in interval pattern from
1118 UChar fieldLetter
= fgCalendarFieldToPatternLetter
[field
];
1119 if ( extendedSkeleton
) {
1120 *extendedSkeleton
= *skeleton
;
1121 *extendedBestSkeleton
= *bestSkeleton
;
1122 extendedSkeleton
->insert(0, fieldLetter
);
1123 extendedBestSkeleton
->insert(0, fieldLetter
);
1124 // for example, looking for patterns when 'y' differ for
1126 fInfo
->getIntervalPattern(*extendedBestSkeleton
,field
,pattern
,status
);
1127 if ( pattern
.isEmpty() && differenceInfo
== 0 ) {
1128 // if there is no skeleton "yMMMM" defined,
1129 // look for the best match skeleton, for example: "yMMM"
1130 const UnicodeString
* tmpBest
= fInfo
->getBestSkeleton(
1131 *extendedBestSkeleton
, differenceInfo
);
1132 if ( tmpBest
!= 0 && differenceInfo
!= -1 ) {
1133 fInfo
->getIntervalPattern(*tmpBest
, field
, pattern
, status
);
1134 bestSkeleton
= tmpBest
;
1139 if ( !pattern
.isEmpty() ) {
1140 if ( differenceInfo
!= 0 ) {
1141 UnicodeString adjustIntervalPattern
;
1142 adjustFieldWidth(*skeleton
, *bestSkeleton
, pattern
, differenceInfo
,
1143 adjustIntervalPattern
);
1144 setIntervalPattern(field
, adjustIntervalPattern
);
1146 setIntervalPattern(field
, pattern
);
1148 if ( extendedSkeleton
&& !extendedSkeleton
->isEmpty() ) {
1158 DateIntervalFormat::splitPatternInto2Part(const UnicodeString
& intervalPattern
) {
1159 UBool inQuote
= false;
1163 /* repeatedPattern used to record whether a pattern has already seen.
1164 It is a pattern applies to first calendar if it is first time seen,
1165 otherwise, it is a pattern applies to the second calendar
1167 UBool patternRepeated
[] =
1169 // A B C D E F G H I J K L M N O
1170 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1171 // P Q R S T U V W X Y Z
1172 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1173 // a b c d e f g h i j k l m n o
1174 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1175 // p q r s t u v w x y z
1176 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1179 int8_t PATTERN_CHAR_BASE
= 0x41;
1181 /* loop through the pattern string character by character looking for
1182 * the first repeated pattern letter, which breaks the interval pattern
1186 UBool foundRepetition
= false;
1187 for (i
= 0; i
< intervalPattern
.length(); ++i
) {
1188 UChar ch
= intervalPattern
.charAt(i
);
1190 if (ch
!= prevCh
&& count
> 0) {
1191 // check the repeativeness of pattern letter
1192 UBool repeated
= patternRepeated
[(int)(prevCh
- PATTERN_CHAR_BASE
)];
1193 if ( repeated
== FALSE
) {
1194 patternRepeated
[prevCh
- PATTERN_CHAR_BASE
] = TRUE
;
1196 foundRepetition
= true;
1202 // Consecutive single quotes are a single quote literal,
1203 // either outside of quotes or between quotes
1204 if ((i
+1) < intervalPattern
.length() &&
1205 intervalPattern
.charAt(i
+1) == '\'') {
1208 inQuote
= ! inQuote
;
1211 else if (!inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
1212 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
1213 // ch is a date-time pattern character
1218 // check last pattern char, distinguish
1219 // "dd MM" ( no repetition ),
1220 // "d-d"(last char repeated ), and
1221 // "d-d MM" ( repetition found )
1222 if ( count
> 0 && foundRepetition
== FALSE
) {
1223 if ( patternRepeated
[(int)(prevCh
- PATTERN_CHAR_BASE
)] == FALSE
) {
1233 DateIntervalFormat::fallbackFormat(Calendar
& fromCalendar
,
1234 Calendar
& toCalendar
,
1235 UnicodeString
& appendTo
,
1237 UErrorCode
& status
) const {
1238 if ( U_FAILURE(status
) ) {
1242 // no need delete earlierDate and laterDate since they are adopted
1243 UnicodeString
* earlierDate
= new UnicodeString();
1244 *earlierDate
= fDateFormat
->format(fromCalendar
, *earlierDate
, pos
);
1245 UnicodeString
* laterDate
= new UnicodeString();
1246 *laterDate
= fDateFormat
->format(toCalendar
, *laterDate
, pos
);
1247 UnicodeString fallbackPattern
;
1248 fInfo
->getFallbackIntervalPattern(fallbackPattern
);
1249 Formattable fmtArray
[2];
1250 fmtArray
[0].adoptString(earlierDate
);
1251 fmtArray
[1].adoptString(laterDate
);
1253 UnicodeString fallback
;
1254 MessageFormat::format(fallbackPattern
, fmtArray
, 2, fallback
, status
);
1255 if ( U_SUCCESS(status
) ) {
1256 appendTo
.append(fallback
);
1265 DateIntervalFormat::fieldExistsInSkeleton(UCalendarDateFields field
,
1266 const UnicodeString
& skeleton
)
1268 const UChar fieldChar
= fgCalendarFieldToPatternLetter
[field
];
1269 return ( (skeleton
.indexOf(fieldChar
) == -1)?FALSE
:TRUE
) ;
1275 DateIntervalFormat::adjustFieldWidth(const UnicodeString
& inputSkeleton
,
1276 const UnicodeString
& bestMatchSkeleton
,
1277 const UnicodeString
& bestIntervalPattern
,
1278 int8_t differenceInfo
,
1279 UnicodeString
& adjustedPtn
) {
1280 adjustedPtn
= bestIntervalPattern
;
1281 int32_t inputSkeletonFieldWidth
[] =
1283 // A B C D E F G H I J K L M N O
1284 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1285 // P Q R S T U V W X Y Z
1286 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1287 // a b c d e f g h i j k l m n o
1288 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1289 // p q r s t u v w x y z
1290 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1293 int32_t bestMatchSkeletonFieldWidth
[] =
1295 // A B C D E F G H I J K L M N O
1296 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1297 // P Q R S T U V W X Y Z
1298 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1299 // a b c d e f g h i j k l m n o
1300 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1301 // p q r s t u v w x y z
1302 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1305 DateIntervalInfo::parseSkeleton(inputSkeleton
, inputSkeletonFieldWidth
);
1306 DateIntervalInfo::parseSkeleton(bestMatchSkeleton
, bestMatchSkeletonFieldWidth
);
1307 if ( differenceInfo
== 2 ) {
1308 adjustedPtn
.findAndReplace("v", "z");
1311 UBool inQuote
= false;
1315 const int8_t PATTERN_CHAR_BASE
= 0x41;
1317 // loop through the pattern string character by character
1318 int32_t adjustedPtnLength
= adjustedPtn
.length();
1320 for (i
= 0; i
< adjustedPtnLength
; ++i
) {
1321 UChar ch
= adjustedPtn
.charAt(i
);
1322 if (ch
!= prevCh
&& count
> 0) {
1323 // check the repeativeness of pattern letter
1324 UChar skeletonChar
= prevCh
;
1325 if ( skeletonChar
== CAP_L
) {
1326 // there is no "L" (always be "M") in skeleton,
1327 // but there is "L" in pattern.
1328 // for skeleton "M+", the pattern might be "...L..."
1329 skeletonChar
= CAP_M
;
1331 int32_t fieldCount
= bestMatchSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1332 int32_t inputFieldCount
= inputSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1333 if ( fieldCount
== count
&& inputFieldCount
> fieldCount
) {
1334 count
= inputFieldCount
- fieldCount
;
1336 for ( j
= 0; j
< count
; ++j
) {
1337 adjustedPtn
.insert(i
, prevCh
);
1340 adjustedPtnLength
+= count
;
1345 // Consecutive single quotes are a single quote literal,
1346 // either outside of quotes or between quotes
1347 if ((i
+1) < adjustedPtn
.length() && adjustedPtn
.charAt(i
+1) == '\'') {
1350 inQuote
= ! inQuote
;
1353 else if ( ! inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
1354 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
1355 // ch is a date-time pattern character
1362 // check the repeativeness of pattern letter
1363 UChar skeletonChar
= prevCh
;
1364 if ( skeletonChar
== CAP_L
) {
1365 // there is no "L" (always be "M") in skeleton,
1366 // but there is "L" in pattern.
1367 // for skeleton "M+", the pattern might be "...L..."
1368 skeletonChar
= CAP_M
;
1370 int32_t fieldCount
= bestMatchSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1371 int32_t inputFieldCount
= inputSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1372 if ( fieldCount
== count
&& inputFieldCount
> fieldCount
) {
1373 count
= inputFieldCount
- fieldCount
;
1375 for ( j
= 0; j
< count
; ++j
) {
1376 adjustedPtn
.append(prevCh
);
1385 DateIntervalFormat::concatSingleDate2TimeInterval(const UChar
* format
,
1387 const UnicodeString
& datePattern
,
1388 UCalendarDateFields field
,
1389 UErrorCode
& status
) {
1390 // following should not set wrong status
1391 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
1393 if ( U_FAILURE(status
) ) {
1396 PatternInfo
& timeItvPtnInfo
= fIntervalPatterns
[itvPtnIndex
];
1397 if ( !timeItvPtnInfo
.firstPart
.isEmpty() ) {
1398 // UnicodeString allocated here is adopted, so no need to delete
1399 UnicodeString
* timeIntervalPattern
= new UnicodeString(timeItvPtnInfo
.firstPart
);
1400 timeIntervalPattern
->append(timeItvPtnInfo
.secondPart
);
1401 UnicodeString
* dateStr
= new UnicodeString(datePattern
);
1402 Formattable fmtArray
[2];
1403 fmtArray
[0].adoptString(timeIntervalPattern
);
1404 fmtArray
[1].adoptString(dateStr
);
1405 UnicodeString combinedPattern
;
1406 MessageFormat::format(UnicodeString(TRUE
, format
, formatLen
),
1407 fmtArray
, 2, combinedPattern
, status
);
1408 if ( U_FAILURE(status
) ) {
1411 setIntervalPattern(field
, combinedPattern
, timeItvPtnInfo
.laterDateFirst
);
1414 // it should not happen if the interval format defined is valid
1420 DateIntervalFormat::fgCalendarFieldToPatternLetter
[] =
1422 /*GyM*/ CAP_G
, LOW_Y
, CAP_M
,
1423 /*wWd*/ LOW_W
, CAP_W
, LOW_D
,
1424 /*DEF*/ CAP_D
, CAP_E
, CAP_F
,
1425 /*ahH*/ LOW_A
, LOW_H
, CAP_H
,