1 /*******************************************************************************
2 * Copyright (C) 2008, International Business Machines Corporation and
3 * others. All Rights Reserved.
4 *******************************************************************************
8 *******************************************************************************
11 #include "unicode/dtitvfmt.h"
13 #if !UCONFIG_NO_FORMATTING
15 //FIXME: put in compilation
16 //#define DTITVFMT_DEBUG 1
19 #include "unicode/msgfmt.h"
20 #include "unicode/dtptngen.h"
21 #include "unicode/dtitvinf.h"
22 #include "unicode/calendar.h"
23 #include "dtitv_impl.h"
37 #define PRINTMESG(msg) { std::cout << "(" << __FILE__ << ":" << __LINE__ << ") " << msg << "\n"; }
41 static const UChar gDateFormatSkeleton
[][11] = {
43 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, CAP_M
, CAP_E
, CAP_E
, CAP_E
, CAP_E
, LOW_D
, 0},
45 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, CAP_M
, LOW_D
, 0},
47 {LOW_Y
, CAP_M
, CAP_M
, CAP_M
, LOW_D
, 0},
49 {LOW_Y
, CAP_M
, LOW_D
, 0} };
52 static const char gDateTimePatternsTag
[]="DateTimePatterns";
56 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
, 0};
59 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
, 0};
62 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalFormat
)
66 DateIntervalFormat
* U_EXPORT2
67 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
69 return createInstance(skeleton
, Locale::getDefault(), status
);
73 DateIntervalFormat
* U_EXPORT2
74 DateIntervalFormat::createInstance(const UnicodeString
& skeleton
,
77 DateFormat
* dtfmt
= DateFormat::createPatternInstance(skeleton
, locale
, status
);
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(dtfmt
, 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 DateFormat
* dtfmt
= DateFormat::createPatternInstance(skeleton
, locale
, status
);
111 DateIntervalInfo
* ptn
= dtitvinf
.clone();
112 return create(dtfmt
, ptn
, &skeleton
, status
);
116 DateIntervalFormat::DateIntervalFormat()
124 DateIntervalFormat::DateIntervalFormat(const DateIntervalFormat
& itvfmt
)
135 DateIntervalFormat::operator=(const DateIntervalFormat
& itvfmt
) {
136 if ( this != &itvfmt
) {
139 delete fFromCalendar
;
141 if ( itvfmt
.fDateFormat
) {
142 fDateFormat
= (SimpleDateFormat
*)itvfmt
.fDateFormat
->clone();
146 if ( itvfmt
.fInfo
) {
147 fInfo
= itvfmt
.fInfo
->clone();
151 if ( itvfmt
.fFromCalendar
) {
152 fFromCalendar
= itvfmt
.fFromCalendar
->clone();
154 fFromCalendar
= NULL
;
156 if ( itvfmt
.fToCalendar
) {
157 fToCalendar
= itvfmt
.fToCalendar
->clone();
161 fSkeleton
= itvfmt
.fSkeleton
;
163 for ( i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
164 fIntervalPatterns
[i
] = itvfmt
.fIntervalPatterns
[i
];
171 DateIntervalFormat::~DateIntervalFormat() {
174 delete fFromCalendar
;
180 DateIntervalFormat::clone(void) const {
181 return new DateIntervalFormat(*this);
186 DateIntervalFormat::operator==(const Format
& other
) const {
187 if ( other
.getDynamicClassID() == DateIntervalFormat::getStaticClassID() ) {
188 DateIntervalFormat
* fmt
= (DateIntervalFormat
*)&other
;
189 #ifdef DTITVFMT_DEBUG
191 equal
= (this == fmt
);
193 equal
= (*fInfo
== *fmt
->fInfo
);
194 equal
= (*fDateFormat
== *fmt
->fDateFormat
);
195 equal
= fFromCalendar
->isEquivalentTo(*fmt
->fFromCalendar
) ;
196 equal
= fToCalendar
->isEquivalentTo(*fmt
->fToCalendar
) ;
197 equal
= (fSkeleton
== fmt
->fSkeleton
);
200 res
= ( this == fmt
) ||
201 ( Format::operator==(other
) &&
203 ( *fInfo
== *fmt
->fInfo
) &&
205 ( *fDateFormat
== *fmt
->fDateFormat
) &&
207 fFromCalendar
->isEquivalentTo(*fmt
->fFromCalendar
) &&
209 fToCalendar
->isEquivalentTo(*fmt
->fToCalendar
) &&
210 fSkeleton
== fmt
->fSkeleton
);
212 for (i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
&& res
== TRUE
; ++i
) {
213 res
= ( fIntervalPatterns
[i
].firstPart
==
214 fmt
->fIntervalPatterns
[i
].firstPart
) &&
215 ( fIntervalPatterns
[i
].secondPart
==
216 fmt
->fIntervalPatterns
[i
].secondPart
) &&
217 ( fIntervalPatterns
[i
].laterDateFirst
==
218 fmt
->fIntervalPatterns
[i
].laterDateFirst
) ;
228 DateIntervalFormat::format(const Formattable
& obj
,
229 UnicodeString
& appendTo
,
230 FieldPosition
& fieldPosition
,
231 UErrorCode
& status
) const {
232 if ( U_FAILURE(status
) ) {
236 if ( obj
.getType() == Formattable::kObject
) {
237 const UObject
* formatObj
= obj
.getObject();
238 if (formatObj
->getDynamicClassID() == DateInterval::getStaticClassID()){
239 return format((DateInterval
*)formatObj
, appendTo
, fieldPosition
, status
);
242 status
= U_ILLEGAL_ARGUMENT_ERROR
;
248 DateIntervalFormat::format(const DateInterval
* dtInterval
,
249 UnicodeString
& appendTo
,
250 FieldPosition
& fieldPosition
,
251 UErrorCode
& status
) const {
252 if ( U_FAILURE(status
) ) {
256 if ( fFromCalendar
!= NULL
&& fToCalendar
!= NULL
&&
257 fDateFormat
!= NULL
&& fInfo
!= NULL
) {
258 fFromCalendar
->setTime(dtInterval
->getFromDate(), status
);
259 fToCalendar
->setTime(dtInterval
->getToDate(), status
);
260 if ( U_SUCCESS(status
) ) {
261 return format(*fFromCalendar
, *fToCalendar
, appendTo
,fieldPosition
, status
);
269 DateIntervalFormat::format(Calendar
& fromCalendar
,
270 Calendar
& toCalendar
,
271 UnicodeString
& appendTo
,
273 UErrorCode
& status
) const {
274 if ( U_FAILURE(status
) ) {
278 // not support different calendar types and time zones
279 //if ( fromCalendar.getType() != toCalendar.getType() ) {
280 if ( !fromCalendar
.isEquivalentTo(toCalendar
) ||
281 uprv_strcmp(fromCalendar
.getType(), "gregorian") ) {
282 status
= U_ILLEGAL_ARGUMENT_ERROR
;
286 // First, find the largest different calendar field.
287 UCalendarDateFields field
= UCAL_FIELD_COUNT
;
289 if ( fromCalendar
.get(UCAL_ERA
,status
) != toCalendar
.get(UCAL_ERA
,status
)) {
291 } else if ( fromCalendar
.get(UCAL_YEAR
, status
) !=
292 toCalendar
.get(UCAL_YEAR
, status
) ) {
294 } else if ( fromCalendar
.get(UCAL_MONTH
, status
) !=
295 toCalendar
.get(UCAL_MONTH
, status
) ) {
297 } else if ( fromCalendar
.get(UCAL_DATE
, status
) !=
298 toCalendar
.get(UCAL_DATE
, status
) ) {
300 } else if ( fromCalendar
.get(UCAL_AM_PM
, status
) !=
301 toCalendar
.get(UCAL_AM_PM
, status
) ) {
303 } else if ( fromCalendar
.get(UCAL_HOUR
, status
) !=
304 toCalendar
.get(UCAL_HOUR
, status
) ) {
306 } else if ( fromCalendar
.get(UCAL_MINUTE
, status
) !=
307 toCalendar
.get(UCAL_MINUTE
, status
) ) {
311 if ( U_FAILURE(status
) ) {
314 if ( field
== UCAL_FIELD_COUNT
) {
315 /* ignore the second/millisecond etc. small fields' difference.
316 * use single date when all the above are the same.
318 return fDateFormat
->format(fromCalendar
, appendTo
, pos
);
321 // following call should not set wrong status,
322 // all the pass-in fields are valid till here
323 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
325 const PatternInfo
& intervalPattern
= fIntervalPatterns
[itvPtnIndex
];
327 if ( intervalPattern
.firstPart
.isEmpty() &&
328 intervalPattern
.secondPart
.isEmpty() ) {
329 if ( fDateFormat
->isFieldUnitIgnored(field
) ) {
330 /* the largest different calendar field is small than
331 * the smallest calendar field in pattern,
332 * return single date format.
334 return fDateFormat
->format(fromCalendar
, appendTo
, pos
);
336 return fallbackFormat(fromCalendar
, toCalendar
, appendTo
, pos
, status
);
338 // If the first part in interval pattern is empty,
339 // the 2nd part of it saves the full-pattern used in fall-back.
340 // For a 'real' interval pattern, the first part will never be empty.
341 if ( intervalPattern
.firstPart
.isEmpty() ) {
343 UnicodeString originalPattern
;
344 fDateFormat
->toPattern(originalPattern
);
345 fDateFormat
->applyPattern(intervalPattern
.secondPart
);
346 appendTo
= fallbackFormat(fromCalendar
, toCalendar
, appendTo
, pos
, status
);
347 fDateFormat
->applyPattern(originalPattern
);
352 if ( intervalPattern
.laterDateFirst
) {
353 firstCal
= &toCalendar
;
354 secondCal
= &fromCalendar
;
356 firstCal
= &fromCalendar
;
357 secondCal
= &toCalendar
;
359 // break the interval pattern into 2 parts,
360 // first part should not be empty,
361 UnicodeString originalPattern
;
362 fDateFormat
->toPattern(originalPattern
);
363 fDateFormat
->applyPattern(intervalPattern
.firstPart
);
364 fDateFormat
->format(*firstCal
, appendTo
, pos
);
365 if ( !intervalPattern
.secondPart
.isEmpty() ) {
366 fDateFormat
->applyPattern(intervalPattern
.secondPart
);
367 fDateFormat
->format(*secondCal
, appendTo
, pos
);
369 fDateFormat
->applyPattern(originalPattern
);
376 DateIntervalFormat::parseObject(const UnicodeString
& /* source */,
377 Formattable
& /* result */,
378 ParsePosition
& /* parse_pos */) const {
379 // parseObject(const UnicodeString&, Formattable&, UErrorCode&) const
380 // will set status as U_INVALID_FORMAT_ERROR if
381 // parse_pos is still 0
387 const DateIntervalInfo
*
388 DateIntervalFormat::getDateIntervalInfo() const {
394 DateIntervalFormat::setDateIntervalInfo(const DateIntervalInfo
& newItvPattern
,
395 UErrorCode
& status
) {
397 fInfo
= new DateIntervalInfo(newItvPattern
);
399 initializePattern(status
);
406 DateIntervalFormat::getDateFormat() const {
411 DateIntervalFormat::DateIntervalFormat(DateFormat
* dtfmt
,
412 DateIntervalInfo
* dtItvInfo
,
413 const UnicodeString
* skeleton
,
420 if ( U_FAILURE(status
) ) {
425 if ( dtfmt
== NULL
|| dtItvInfo
== NULL
) {
426 status
= U_MEMORY_ALLOCATION_ERROR
;
427 // safe to delete NULL
433 fSkeleton
= *skeleton
;
436 fDateFormat
= (SimpleDateFormat
*)dtfmt
;
437 if ( dtfmt
->getCalendar() ) {
438 fFromCalendar
= dtfmt
->getCalendar()->clone();
439 fToCalendar
= dtfmt
->getCalendar()->clone();
441 fFromCalendar
= NULL
;
444 initializePattern(status
);
449 DateIntervalFormat
* U_EXPORT2
450 DateIntervalFormat::create(DateFormat
* dtfmt
,
451 DateIntervalInfo
* dtitvinf
,
452 const UnicodeString
* skeleton
,
453 UErrorCode
& status
) {
454 DateIntervalFormat
* f
= new DateIntervalFormat(dtfmt
, dtitvinf
,
457 status
= U_MEMORY_ALLOCATION_ERROR
;
460 } else if ( U_FAILURE(status
) ) {
461 // safe to delete f, although nothing acutally is saved
471 * Initialize interval patterns locale to this formatter
473 * This code is a bit complicated since
474 * 1. the interval patterns saved in resource bundle files are interval
475 * patterns based on date or time only.
476 * It does not have interval patterns based on both date and time.
477 * Interval patterns on both date and time are algorithm generated.
479 * For example, it has interval patterns on skeleton "dMy" and "hm",
480 * but it does not have interval patterns on skeleton "dMyhm".
482 * The rule to genearte interval patterns for both date and time skeleton are
483 * 1) when the year, month, or day differs, concatenate the two original
484 * expressions with a separator between,
485 * For example, interval pattern from "Jan 10, 2007 10:10 am"
486 * to "Jan 11, 2007 10:10am" is
487 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
489 * 2) otherwise, present the date followed by the range expression
491 * For example, interval pattern from "Jan 10, 2007 10:10 am"
492 * to "Jan 10, 2007 11:10am" is
493 * "Jan 10, 2007 10:10 am - 11:10am"
495 * 2. even a pattern does not request a certion calendar field,
496 * the interval pattern needs to include such field if such fields are
497 * different between 2 dates.
498 * For example, a pattern/skeleton is "hm", but the interval pattern
499 * includes year, month, and date when year, month, and date differs.
501 * @param status output param set to success/failure code on exit
505 DateIntervalFormat::initializePattern(UErrorCode
& status
) {
506 if ( U_FAILURE(status
) ) {
509 const Locale
& locale
= fDateFormat
->getSmpFmtLocale();
510 DateTimePatternGenerator
* dtpng
= DateTimePatternGenerator::createInstance(locale
, status
);
511 if ( U_FAILURE(status
) ) {
515 if ( fSkeleton
.isEmpty() ) {
516 UnicodeString fullPattern
;
517 fDateFormat
->toPattern(fullPattern
);
518 #ifdef DTITVFMT_DEBUG
522 fSkeleton
.extract(0, fSkeleton
.length(), result
, "UTF-8");
523 sprintf(mesg
, "in getBestSkeleton: fSkeleton: %s; \n", result
);
526 // fSkeleton is already set by createDateIntervalInstance()
527 // or by createInstance(UnicodeString skeleton, .... )
528 fSkeleton
= dtpng
->getSkeleton(fullPattern
, status
);
529 if ( U_FAILURE(status
) ) {
535 // initialize the fIntervalPattern ordering
537 for ( i
= 0; i
< DateIntervalInfo::kIPI_MAX_INDEX
; ++i
) {
538 fIntervalPatterns
[i
].laterDateFirst
= fInfo
->getDefaultOrder();
541 /* Check whether the skeleton is a combination of date and time.
542 * For the complication reason 1 explained above.
544 UnicodeString dateSkeleton
;
545 UnicodeString timeSkeleton
;
546 UnicodeString normalizedTimeSkeleton
;
547 UnicodeString normalizedDateSkeleton
;
550 /* the difference between time skeleton and normalizedTimeSkeleton are:
551 * 1. both 'H' and 'h' are normalized as 'h' in normalized time skeleton,
552 * 2. 'a' is omitted in normalized time skeleton.
553 * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized
556 * The difference between date skeleton and normalizedDateSkeleton are:
557 * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
558 * 2. 'E' and 'EE' are normalized into 'EEE'
559 * 3. 'MM' is normalized into 'M'
561 getDateTimeSkeleton(fSkeleton
, dateSkeleton
, normalizedDateSkeleton
,
562 timeSkeleton
, normalizedTimeSkeleton
);
564 #ifdef DTITVFMT_DEBUG
568 fSkeleton
.extract(0, fSkeleton
.length(), result
, "UTF-8");
569 sprintf(mesg
, "in getBestSkeleton: fSkeleton: %s; \n", result
);
574 UBool found
= setSeparateDateTimePtn(normalizedDateSkeleton
,
575 normalizedTimeSkeleton
);
577 if ( found
== false ) {
579 // TODO: if user asks "m"(minute), but "d"(day) differ
580 if ( timeSkeleton
.length() != 0 ) {
581 if ( dateSkeleton
.length() == 0 ) {
583 timeSkeleton
.insert(0, gDateFormatSkeleton
[DateFormat::kShort
]);
584 UnicodeString pattern
=dtpng
->getBestPattern(timeSkeleton
, status
);
585 if ( U_FAILURE(status
) ) {
589 // for fall back interval patterns,
590 // the first part of the pattern is empty,
591 // the second part of the pattern is the full-pattern
592 // should be used in fall-back.
593 setPatternInfo(UCAL_DATE
, NULL
, &pattern
, fInfo
->getDefaultOrder());
594 setPatternInfo(UCAL_MONTH
, NULL
, &pattern
, fInfo
->getDefaultOrder());
595 setPatternInfo(UCAL_YEAR
, NULL
, &pattern
, fInfo
->getDefaultOrder());
604 } // end of skeleton not found
605 // interval patterns for skeleton are found in resource
606 if ( timeSkeleton
.length() == 0 ) {
608 } else if ( dateSkeleton
.length() == 0 ) {
610 timeSkeleton
.insert(0, gDateFormatSkeleton
[DateFormat::kShort
]);
611 UnicodeString pattern
=dtpng
->getBestPattern(timeSkeleton
, status
);
612 if ( U_FAILURE(status
) ) {
616 // for fall back interval patterns,
617 // the first part of the pattern is empty,
618 // the second part of the pattern is the full-pattern
619 // should be used in fall-back.
620 setPatternInfo(UCAL_DATE
, NULL
, &pattern
, fInfo
->getDefaultOrder());
621 setPatternInfo(UCAL_MONTH
, NULL
, &pattern
, fInfo
->getDefaultOrder());
622 setPatternInfo(UCAL_YEAR
, NULL
, &pattern
, fInfo
->getDefaultOrder());
625 * 1) when the year, month, or day differs,
626 * concatenate the two original expressions with a separator between,
627 * 2) otherwise, present the date followed by the
628 * range expression for the time.
631 * 1) when the year, month, or day differs,
632 * concatenate the two original expressions with a separator between,
634 // if field exists, use fall back
635 UnicodeString skeleton
= fSkeleton
;
636 if ( !fieldExistsInSkeleton(UCAL_DATE
, dateSkeleton
) ) {
637 // prefix skeleton with 'd'
638 skeleton
.insert(0, LOW_D
);
639 setFallbackPattern(UCAL_DATE
, skeleton
, dtpng
, status
);
641 if ( !fieldExistsInSkeleton(UCAL_MONTH
, dateSkeleton
) ) {
642 // then prefix skeleton with 'M'
643 skeleton
.insert(0, CAP_M
);
644 setFallbackPattern(UCAL_MONTH
, skeleton
, dtpng
, status
);
646 if ( !fieldExistsInSkeleton(UCAL_YEAR
, dateSkeleton
) ) {
647 // then prefix skeleton with 'y'
648 skeleton
.insert(0, LOW_Y
);
649 setFallbackPattern(UCAL_YEAR
, skeleton
, dtpng
, status
);
653 * 2) otherwise, present the date followed by the
654 * range expression for the time.
656 // Need the Date/Time pattern for concatnation the date with
657 // the time interval.
658 // The date/time pattern ( such as {0} {1} ) is saved in
659 // calendar, that is why need to get the CalendarData here.
660 CalendarData
* calData
= new CalendarData(locale
, NULL
, status
);
662 if ( U_FAILURE(status
) ) {
668 if ( calData
== NULL
) {
669 status
= U_MEMORY_ALLOCATION_ERROR
;
674 const UResourceBundle
* dateTimePatternsRes
= calData
->getByKey(
675 gDateTimePatternsTag
, status
);
676 int32_t dateTimeFormatLength
;
677 const UChar
* dateTimeFormat
= ures_getStringByIndex(
679 (int32_t)DateFormat::kDateTime
,
680 &dateTimeFormatLength
, &status
);
681 if ( U_FAILURE(status
) ) {
686 UnicodeString datePattern
= dtpng
->getBestPattern(dateSkeleton
, status
);
688 concatSingleDate2TimeInterval(dateTimeFormat
, dateTimeFormatLength
,
689 datePattern
, UCAL_AM_PM
, status
);
690 concatSingleDate2TimeInterval(dateTimeFormat
, dateTimeFormatLength
,
691 datePattern
, UCAL_HOUR
, status
);
692 concatSingleDate2TimeInterval(dateTimeFormat
, dateTimeFormatLength
,
693 datePattern
, UCAL_MINUTE
, status
);
702 DateIntervalFormat::getDateTimeSkeleton(const UnicodeString
& skeleton
,
703 UnicodeString
& dateSkeleton
,
704 UnicodeString
& normalizedDateSkeleton
,
705 UnicodeString
& timeSkeleton
,
706 UnicodeString
& normalizedTimeSkeleton
) {
707 // dateSkeleton follows the sequence of y*M*E*d*
708 // timeSkeleton follows the sequence of hm*[v|z]?
719 for (i
= 0; i
< skeleton
.length(); ++i
) {
720 UChar ch
= skeleton
[i
];
723 dateSkeleton
.append(ch
);
727 dateSkeleton
.append(ch
);
731 dateSkeleton
.append(ch
);
735 dateSkeleton
.append(ch
);
752 normalizedDateSkeleton
.append(ch
);
753 dateSkeleton
.append(ch
);
756 // 'a' is implicitly handled
757 timeSkeleton
.append(ch
);
761 timeSkeleton
.append(ch
);
765 timeSkeleton
.append(ch
);
770 timeSkeleton
.append(ch
);
774 timeSkeleton
.append(ch
);
776 // FIXME: what is the difference between CAP_V/Z and LOW_V/Z
785 timeSkeleton
.append(ch
);
786 normalizedTimeSkeleton
.append(ch
);
791 /* generate normalized form for date*/
793 normalizedDateSkeleton
.append(LOW_Y
);
797 normalizedDateSkeleton
.append(CAP_M
);
800 for ( i
= 0; i
< MCount
&& i
< MAX_M_COUNT
; ++i
) {
801 normalizedDateSkeleton
.append(CAP_M
);
807 normalizedDateSkeleton
.append(CAP_E
);
810 for ( i
= 0; i
< ECount
&& i
< MAX_E_COUNT
; ++i
) {
811 normalizedDateSkeleton
.append(CAP_E
);
816 normalizedDateSkeleton
.append(LOW_D
);
819 /* generate normalized form for time */
821 normalizedTimeSkeleton
.append(LOW_H
);
824 normalizedTimeSkeleton
.append(LOW_M
);
827 normalizedTimeSkeleton
.append(LOW_Z
);
830 normalizedTimeSkeleton
.append(LOW_V
);
836 * Generate date or time interval pattern from resource,
837 * and set them into the interval pattern locale to this formatter.
839 * It needs to handle the following:
840 * 1. need to adjust field width.
841 * For example, the interval patterns saved in DateIntervalInfo
842 * includes "dMMMy", but not "dMMMMy".
843 * Need to get interval patterns for dMMMMy from dMMMy.
844 * Another example, the interval patterns saved in DateIntervalInfo
845 * includes "hmv", but not "hmz".
846 * Need to get interval patterns for "hmz' from 'hmv'
848 * 2. there might be no pattern for 'y' differ for skeleton "Md",
849 * in order to get interval patterns for 'y' differ,
850 * need to look for it from skeleton 'yMd'
852 * @param dateSkeleton normalized date skeleton
853 * @param timeSkeleton normalized time skeleton
854 * @return whether the resource is found for the skeleton.
855 * TRUE if interval pattern found for the skeleton,
860 DateIntervalFormat::setSeparateDateTimePtn(
861 const UnicodeString
& dateSkeleton
,
862 const UnicodeString
& timeSkeleton
) {
863 const UnicodeString
* skeleton
;
864 // if both date and time skeleton present,
865 // the final interval pattern might include time interval patterns
866 // ( when, am_pm, hour, minute differ ),
867 // but not date interval patterns ( when year, month, day differ ).
868 // For year/month/day differ, it falls back to fall-back pattern.
869 if ( timeSkeleton
.length() != 0 ) {
870 skeleton
= &timeSkeleton
;
872 skeleton
= &dateSkeleton
;
875 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
876 * are defined in resource,
877 * interval patterns for skeleton "dMMMMy" are calculated by
878 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
879 * 2. get the interval patterns for "dMMMy",
880 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
881 * getBestSkeleton() is step 1.
883 // best skeleton, and the difference information
884 int8_t differenceInfo
= 0;
885 const UnicodeString
* bestSkeleton
= fInfo
->getBestSkeleton(*skeleton
,
887 /* best skeleton could be NULL.
888 For example: in "ca" resource file,
889 interval format is defined as following
891 fallback{"{0} - {1}"}
893 there is no skeletons/interval patterns defined,
894 and the best skeleton match could be NULL
896 if ( bestSkeleton
== NULL
) {
901 // 0 means the best matched skeleton is the same as input skeleton
902 // 1 means the fields are the same, but field width are different
903 // 2 means the only difference between fields are v/z,
904 // -1 means there are other fields difference
905 if ( differenceInfo
== -1 ) {
906 // skeleton has different fields, not only v/z difference
910 if ( timeSkeleton
.length() == 0 ) {
911 UnicodeString extendedSkeleton
;
912 UnicodeString extendedBestSkeleton
;
913 // only has date skeleton
914 setIntervalPattern(UCAL_DATE
, skeleton
, bestSkeleton
, differenceInfo
,
915 &extendedSkeleton
, &extendedBestSkeleton
);
917 UBool extended
= setIntervalPattern(UCAL_MONTH
, skeleton
, bestSkeleton
,
919 &extendedSkeleton
, &extendedBestSkeleton
);
922 bestSkeleton
= &extendedBestSkeleton
;
923 skeleton
= &extendedSkeleton
;
925 setIntervalPattern(UCAL_YEAR
, skeleton
, bestSkeleton
, differenceInfo
,
926 &extendedSkeleton
, &extendedBestSkeleton
);
928 setIntervalPattern(UCAL_MINUTE
, skeleton
, bestSkeleton
, differenceInfo
);
929 setIntervalPattern(UCAL_HOUR
, skeleton
, bestSkeleton
, differenceInfo
);
930 setIntervalPattern(UCAL_AM_PM
, skeleton
, bestSkeleton
, differenceInfo
);
938 DateIntervalFormat::setFallbackPattern(UCalendarDateFields field
,
939 const UnicodeString
& skeleton
,
940 DateTimePatternGenerator
* dtpng
,
941 UErrorCode
& status
) {
942 if ( U_FAILURE(status
) ) {
945 UnicodeString pattern
=dtpng
->getBestPattern(skeleton
, status
);
946 if ( U_FAILURE(status
) ) {
949 setPatternInfo(field
, NULL
, &pattern
, fInfo
->getDefaultOrder());
956 DateIntervalFormat::setPatternInfo(UCalendarDateFields field
,
957 const UnicodeString
* firstPart
,
958 const UnicodeString
* secondPart
,
959 UBool laterDateFirst
) {
960 // for fall back interval patterns,
961 // the first part of the pattern is empty,
962 // the second part of the pattern is the full-pattern
963 // should be used in fall-back.
964 UErrorCode status
= U_ZERO_ERROR
;
965 // following should not set any wrong status.
966 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
968 if ( U_FAILURE(status
) ) {
971 PatternInfo
& ptn
= fIntervalPatterns
[itvPtnIndex
];
973 ptn
.firstPart
= *firstPart
;
976 ptn
.secondPart
= *secondPart
;
978 ptn
.laterDateFirst
= laterDateFirst
;
982 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
983 const UnicodeString
& intervalPattern
) {
984 UBool order
= fInfo
->getDefaultOrder();
985 setIntervalPattern(field
, intervalPattern
, order
);
990 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
991 const UnicodeString
& intervalPattern
,
992 UBool laterDateFirst
) {
993 const UnicodeString
* pattern
= &intervalPattern
;
994 UBool order
= laterDateFirst
;
995 // check for "latestFirst:" or "earliestFirst:" prefix
996 int8_t prefixLength
= sizeof(gLaterFirstPrefix
)/sizeof(gLaterFirstPrefix
[0]);
997 int8_t earliestFirstLength
= sizeof(gEarlierFirstPrefix
)/sizeof(gEarlierFirstPrefix
[0]);
998 UnicodeString realPattern
;
999 if ( intervalPattern
.startsWith(gLaterFirstPrefix
, prefixLength
) ) {
1001 intervalPattern
.extract(prefixLength
,
1002 intervalPattern
.length() - prefixLength
,
1004 pattern
= &realPattern
;
1005 } else if ( intervalPattern
.startsWith(gEarlierFirstPrefix
,
1006 earliestFirstLength
) ) {
1008 intervalPattern
.extract(earliestFirstLength
,
1009 intervalPattern
.length() - earliestFirstLength
,
1011 pattern
= &realPattern
;
1014 int32_t splitPoint
= splitPatternInto2Part(*pattern
);
1016 UnicodeString firstPart
;
1017 UnicodeString secondPart
;
1018 pattern
->extract(0, splitPoint
, firstPart
);
1019 if ( splitPoint
< pattern
->length() ) {
1020 pattern
->extract(splitPoint
, pattern
->length()-splitPoint
, secondPart
);
1022 setPatternInfo(field
, &firstPart
, &secondPart
, order
);
1029 * Generate interval pattern from existing resource
1031 * It not only save the interval patterns,
1032 * but also return the extended skeleton and its best match skeleton.
1034 * @param field largest different calendar field
1035 * @param skeleton skeleton
1036 * @param bestSkeleton the best match skeleton which has interval pattern
1037 * defined in resource
1038 * @param differenceInfo the difference between skeleton and best skeleton
1039 * 0 means the best matched skeleton is the same as input skeleton
1040 * 1 means the fields are the same, but field width are different
1041 * 2 means the only difference between fields are v/z,
1042 * -1 means there are other fields difference
1044 * @param extendedSkeleton extended skeleton
1045 * @param extendedBestSkeleton extended best match skeleton
1046 * @return whether the interval pattern is found
1047 * through extending skeleton or not.
1048 * TRUE if interval pattern is found by
1049 * extending skeleton, FALSE otherwise.
1053 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field
,
1054 const UnicodeString
* skeleton
,
1055 const UnicodeString
* bestSkeleton
,
1056 int8_t differenceInfo
,
1057 UnicodeString
* extendedSkeleton
,
1058 UnicodeString
* extendedBestSkeleton
) {
1059 UErrorCode status
= U_ZERO_ERROR
;
1060 // following getIntervalPattern() should not generate error status
1061 UnicodeString pattern
;
1062 fInfo
->getIntervalPattern(*bestSkeleton
, field
, pattern
, status
);
1063 if ( pattern
.isEmpty() ) {
1065 if ( SimpleDateFormat::isFieldUnitIgnored(*bestSkeleton
, field
) ) {
1066 // do nothing, format will handle it
1070 // for 24 hour system, interval patterns in resource file
1071 // might not include pattern when am_pm differ,
1072 // which should be the same as hour differ.
1073 // add it here for simplicity
1074 if ( field
== UCAL_AM_PM
) {
1075 fInfo
->getIntervalPattern(*bestSkeleton
, UCAL_HOUR
, pattern
,status
);
1076 if ( !pattern
.isEmpty() ) {
1077 setIntervalPattern(field
, pattern
);
1081 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
1082 // first, get best match pattern "MMMd",
1083 // since there is no pattern for 'y' differs for skeleton 'MMMd',
1084 // need to look for it from skeleton 'yMMMd',
1085 // if found, adjust field width in interval pattern from
1087 UChar fieldLetter
= fgCalendarFieldToPatternLetter
[field
];
1088 if ( extendedSkeleton
) {
1089 *extendedSkeleton
= *skeleton
;
1090 *extendedBestSkeleton
= *bestSkeleton
;
1091 extendedSkeleton
->insert(0, fieldLetter
);
1092 extendedBestSkeleton
->insert(0, fieldLetter
);
1093 // for example, looking for patterns when 'y' differ for
1095 fInfo
->getIntervalPattern(*extendedBestSkeleton
,field
,pattern
,status
);
1096 if ( pattern
.isEmpty() && differenceInfo
== 0 ) {
1097 // if there is no skeleton "yMMMM" defined,
1098 // look for the best match skeleton, for example: "yMMM"
1099 const UnicodeString
* tmpBest
= fInfo
->getBestSkeleton(
1100 *extendedBestSkeleton
, differenceInfo
);
1101 if ( tmpBest
!= 0 && differenceInfo
!= -1 ) {
1102 fInfo
->getIntervalPattern(*tmpBest
, field
, pattern
, status
);
1103 bestSkeleton
= tmpBest
;
1108 if ( !pattern
.isEmpty() ) {
1109 if ( differenceInfo
!= 0 ) {
1110 UnicodeString adjustIntervalPattern
;
1111 adjustFieldWidth(*skeleton
, *bestSkeleton
, pattern
, differenceInfo
,
1112 adjustIntervalPattern
);
1113 setIntervalPattern(field
, adjustIntervalPattern
);
1115 setIntervalPattern(field
, pattern
);
1117 if ( extendedSkeleton
&& !extendedSkeleton
->isEmpty() ) {
1127 DateIntervalFormat::splitPatternInto2Part(const UnicodeString
& intervalPattern
) {
1128 UBool inQuote
= false;
1132 /* repeatedPattern used to record whether a pattern has already seen.
1133 It is a pattern applies to first calendar if it is first time seen,
1134 otherwise, it is a pattern applies to the second calendar
1136 UBool patternRepeated
[] =
1138 // A B C D E F G H I J K L M N O
1139 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1140 // P Q R S T U V W X Y Z
1141 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1142 // a b c d e f g h i j k l m n o
1143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1144 // p q r s t u v w x y z
1145 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1148 int8_t PATTERN_CHAR_BASE
= 0x41;
1150 /* loop through the pattern string character by character looking for
1151 * the first repeated pattern letter, which breaks the interval pattern
1155 UBool foundRepetition
= false;
1156 for (i
= 0; i
< intervalPattern
.length(); ++i
) {
1157 UChar ch
= intervalPattern
.charAt(i
);
1159 if (ch
!= prevCh
&& count
> 0) {
1160 // check the repeativeness of pattern letter
1161 UBool repeated
= patternRepeated
[(int)(prevCh
- PATTERN_CHAR_BASE
)];
1162 if ( repeated
== FALSE
) {
1163 patternRepeated
[prevCh
- PATTERN_CHAR_BASE
] = TRUE
;
1165 foundRepetition
= true;
1171 // Consecutive single quotes are a single quote literal,
1172 // either outside of quotes or between quotes
1173 if ((i
+1) < intervalPattern
.length() &&
1174 intervalPattern
.charAt(i
+1) == '\'') {
1177 inQuote
= ! inQuote
;
1180 else if (!inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
1181 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
1182 // ch is a date-time pattern character
1187 // check last pattern char, distinguish
1188 // "dd MM" ( no repetition ),
1189 // "d-d"(last char repeated ), and
1190 // "d-d MM" ( repetition found )
1191 if ( count
> 0 && foundRepetition
== FALSE
) {
1192 if ( patternRepeated
[(int)(prevCh
- PATTERN_CHAR_BASE
)] == FALSE
) {
1202 DateIntervalFormat::fallbackFormat(Calendar
& fromCalendar
,
1203 Calendar
& toCalendar
,
1204 UnicodeString
& appendTo
,
1206 UErrorCode
& status
) const {
1207 if ( U_FAILURE(status
) ) {
1211 // no need delete earlierDate and laterDate since they are adopted
1212 UnicodeString
* earlierDate
= new UnicodeString();
1213 *earlierDate
= fDateFormat
->format(fromCalendar
, *earlierDate
, pos
);
1214 UnicodeString
* laterDate
= new UnicodeString();
1215 *laterDate
= fDateFormat
->format(toCalendar
, *laterDate
, pos
);
1216 UnicodeString fallbackPattern
;
1217 fInfo
->getFallbackIntervalPattern(fallbackPattern
);
1218 Formattable fmtArray
[2];
1219 fmtArray
[0].adoptString(earlierDate
);
1220 fmtArray
[1].adoptString(laterDate
);
1222 UnicodeString fallback
;
1223 MessageFormat::format(fallbackPattern
, fmtArray
, 2, fallback
, status
);
1224 if ( U_SUCCESS(status
) ) {
1225 appendTo
.append(fallback
);
1234 DateIntervalFormat::fieldExistsInSkeleton(UCalendarDateFields field
,
1235 const UnicodeString
& skeleton
)
1237 const UChar fieldChar
= fgCalendarFieldToPatternLetter
[field
];
1238 return ( (skeleton
.indexOf(fieldChar
) == -1)?FALSE
:TRUE
) ;
1244 DateIntervalFormat::adjustFieldWidth(const UnicodeString
& inputSkeleton
,
1245 const UnicodeString
& bestMatchSkeleton
,
1246 const UnicodeString
& bestIntervalPattern
,
1247 int8_t differenceInfo
,
1248 UnicodeString
& adjustedPtn
) {
1249 adjustedPtn
= bestIntervalPattern
;
1250 int32_t inputSkeletonFieldWidth
[] =
1252 // A B C D E F G H I J K L M N O
1253 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1254 // P Q R S T U V W X Y Z
1255 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1256 // a b c d e f g h i j k l m n o
1257 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1258 // p q r s t u v w x y z
1259 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1262 int32_t bestMatchSkeletonFieldWidth
[] =
1264 // A B C D E F G H I J K L M N O
1265 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1266 // P Q R S T U V W X Y Z
1267 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1268 // a b c d e f g h i j k l m n o
1269 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1270 // p q r s t u v w x y z
1271 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1274 DateIntervalInfo::parseSkeleton(inputSkeleton
, inputSkeletonFieldWidth
);
1275 DateIntervalInfo::parseSkeleton(bestMatchSkeleton
, bestMatchSkeletonFieldWidth
);
1276 if ( differenceInfo
== 2 ) {
1277 adjustedPtn
.findAndReplace("v", "z");
1280 UBool inQuote
= false;
1284 const int8_t PATTERN_CHAR_BASE
= 0x41;
1286 // loop through the pattern string character by character
1287 int32_t adjustedPtnLength
= adjustedPtn
.length();
1289 for (i
= 0; i
< adjustedPtnLength
; ++i
) {
1290 UChar ch
= adjustedPtn
.charAt(i
);
1291 if (ch
!= prevCh
&& count
> 0) {
1292 // check the repeativeness of pattern letter
1293 UChar skeletonChar
= prevCh
;
1294 if ( skeletonChar
== CAP_L
) {
1295 // there is no "L" (always be "M") in skeleton,
1296 // but there is "L" in pattern.
1297 // for skeleton "M+", the pattern might be "...L..."
1298 skeletonChar
= CAP_M
;
1300 int32_t fieldCount
= bestMatchSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1301 int32_t inputFieldCount
= inputSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1302 if ( fieldCount
== count
&& inputFieldCount
> fieldCount
) {
1303 count
= inputFieldCount
- fieldCount
;
1305 for ( j
= 0; j
< count
; ++j
) {
1306 adjustedPtn
.insert(i
, prevCh
);
1309 adjustedPtnLength
+= count
;
1314 // Consecutive single quotes are a single quote literal,
1315 // either outside of quotes or between quotes
1316 if ((i
+1) < adjustedPtn
.length() && adjustedPtn
.charAt(i
+1) == '\'') {
1319 inQuote
= ! inQuote
;
1322 else if ( ! inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
1323 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
1324 // ch is a date-time pattern character
1331 // check the repeativeness of pattern letter
1332 UChar skeletonChar
= prevCh
;
1333 if ( skeletonChar
== CAP_L
) {
1334 // there is no "L" (always be "M") in skeleton,
1335 // but there is "L" in pattern.
1336 // for skeleton "M+", the pattern might be "...L..."
1337 skeletonChar
= CAP_M
;
1339 int32_t fieldCount
= bestMatchSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1340 int32_t inputFieldCount
= inputSkeletonFieldWidth
[(int)(skeletonChar
- PATTERN_CHAR_BASE
)];
1341 if ( fieldCount
== count
&& inputFieldCount
> fieldCount
) {
1342 count
= inputFieldCount
- fieldCount
;
1344 for ( j
= 0; j
< count
; ++j
) {
1345 adjustedPtn
.append(prevCh
);
1354 DateIntervalFormat::concatSingleDate2TimeInterval(const UChar
* format
,
1356 const UnicodeString
& datePattern
,
1357 UCalendarDateFields field
,
1358 UErrorCode
& status
) {
1359 // following should not set wrong status
1360 int32_t itvPtnIndex
= DateIntervalInfo::calendarFieldToIntervalIndex(field
,
1362 if ( U_FAILURE(status
) ) {
1365 PatternInfo
& timeItvPtnInfo
= fIntervalPatterns
[itvPtnIndex
];
1366 if ( !timeItvPtnInfo
.firstPart
.isEmpty() ) {
1367 // UnicodeString allocated here is adopted, so no need to delete
1368 UnicodeString
* timeIntervalPattern
= new UnicodeString(timeItvPtnInfo
.firstPart
);
1369 timeIntervalPattern
->append(timeItvPtnInfo
.secondPart
);
1370 UnicodeString
* dateStr
= new UnicodeString(datePattern
);
1371 Formattable fmtArray
[2];
1372 fmtArray
[0].adoptString(timeIntervalPattern
);
1373 fmtArray
[1].adoptString(dateStr
);
1374 UnicodeString combinedPattern
;
1375 MessageFormat::format(UnicodeString(TRUE
, format
, formatLen
),
1376 fmtArray
, 2, combinedPattern
, status
);
1377 if ( U_FAILURE(status
) ) {
1380 setIntervalPattern(field
, combinedPattern
, timeItvPtnInfo
.laterDateFirst
);
1383 // it should not happen if the interval format defined is valid
1389 DateIntervalFormat::fgCalendarFieldToPatternLetter
[] =
1391 /*GyM*/ CAP_G
, LOW_Y
, CAP_M
,
1392 /*wWd*/ LOW_W
, CAP_W
, LOW_D
,
1393 /*DEF*/ CAP_D
, CAP_E
, CAP_F
,
1394 /*ahH*/ LOW_A
, LOW_H
, CAP_H
,