1 /*******************************************************************************
2 * Copyright (C) 2008-2015, International Business Machines Corporation and
3 * others. All Rights Reserved.
4 *******************************************************************************
5 *
6 * File DTITVFMT.CPP
7 *
8 *******************************************************************************
9 */
10
11 #include "utypeinfo.h" // for 'typeid' to work
12
13 #include "unicode/dtitvfmt.h"
14
15 #if !UCONFIG_NO_FORMATTING
16
17 //TODO: put in compilation
18 //#define DTITVFMT_DEBUG 1
19
20 #include "cstring.h"
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"
26
27 #ifdef DTITVFMT_DEBUG
28 #include <iostream>
29 #include "cstring.h"
30 #endif
31
32 #include "gregoimp.h"
33
34 U_NAMESPACE_BEGIN
35
36
37
38 #ifdef DTITVFMT_DEBUG
39 #define PRINTMESG(msg) { std::cout << "(" << __FILE__ << ":" << __LINE__ << ") " << msg << "\n"; }
40 #endif
41
42
43 static const UChar gDateFormatSkeleton[][11] = {
44 //yMMMMEEEEd
45 {LOW_Y, CAP_M, CAP_M, CAP_M, CAP_M, CAP_E, CAP_E, CAP_E, CAP_E, LOW_D, 0},
46 //yMMMMd
47 {LOW_Y, CAP_M, CAP_M, CAP_M, CAP_M, LOW_D, 0},
48 //yMMMd
49 {LOW_Y, CAP_M, CAP_M, CAP_M, LOW_D, 0},
50 //yMd
51 {LOW_Y, CAP_M, LOW_D, 0} };
52
53
54 static const char gDateTimePatternsTag[]="DateTimePatterns";
55
56
57 // latestFirst:
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};
59
60 // earliestFirst:
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};
62
63
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalFormat)64 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DateIntervalFormat)
65
66
67
68 DateIntervalFormat* U_EXPORT2
69 DateIntervalFormat::createInstance(const UnicodeString& skeleton,
70 UErrorCode& status) {
71 return createInstance(skeleton, Locale::getDefault(), status);
72 }
73
74
75 DateIntervalFormat* U_EXPORT2
createInstance(const UnicodeString & skeleton,const Locale & locale,UErrorCode & status)76 DateIntervalFormat::createInstance(const UnicodeString& skeleton,
77 const Locale& locale,
78 UErrorCode& status) {
79 #ifdef DTITVFMT_DEBUG
80 char result[1000];
81 char result_1[1000];
82 char mesg[2000];
83 skeleton.extract(0, skeleton.length(), result, "UTF-8");
84 UnicodeString pat;
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);
88 PRINTMESG(mesg)
89 #endif
90
91 DateIntervalInfo* dtitvinf = new DateIntervalInfo(locale, status);
92 return create(locale, dtitvinf, &skeleton, status);
93 }
94
95
96
97 DateIntervalFormat* U_EXPORT2
createInstance(const UnicodeString & skeleton,const DateIntervalInfo & dtitvinf,UErrorCode & status)98 DateIntervalFormat::createInstance(const UnicodeString& skeleton,
99 const DateIntervalInfo& dtitvinf,
100 UErrorCode& status) {
101 return createInstance(skeleton, Locale::getDefault(), dtitvinf, status);
102 }
103
104
105 DateIntervalFormat* U_EXPORT2
createInstance(const UnicodeString & skeleton,const Locale & locale,const DateIntervalInfo & dtitvinf,UErrorCode & status)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);
112 }
113
114
DateIntervalFormat()115 DateIntervalFormat::DateIntervalFormat()
116 : fInfo(NULL),
117 fDateFormat(NULL),
118 fFromCalendar(NULL),
119 fToCalendar(NULL),
120 fLocale(Locale::getRoot()),
121 fDatePattern(NULL),
122 fTimePattern(NULL),
123 fDateTimeFormat(NULL)
124 {}
125
126
DateIntervalFormat(const DateIntervalFormat & itvfmt)127 DateIntervalFormat::DateIntervalFormat(const DateIntervalFormat& itvfmt)
128 : Format(itvfmt),
129 fInfo(NULL),
130 fDateFormat(NULL),
131 fFromCalendar(NULL),
132 fToCalendar(NULL),
133 fLocale(itvfmt.fLocale),
134 fDatePattern(NULL),
135 fTimePattern(NULL),
136 fDateTimeFormat(NULL) {
137 *this = itvfmt;
138 }
139
140
141 DateIntervalFormat&
operator =(const DateIntervalFormat & itvfmt)142 DateIntervalFormat::operator=(const DateIntervalFormat& itvfmt) {
143 if ( this != &itvfmt ) {
144 delete fDateFormat;
145 delete fInfo;
146 delete fFromCalendar;
147 delete fToCalendar;
148 delete fDatePattern;
149 delete fTimePattern;
150 delete fDateTimeFormat;
151 if ( itvfmt.fDateFormat ) {
152 fDateFormat = (SimpleDateFormat*)itvfmt.fDateFormat->clone();
153 } else {
154 fDateFormat = NULL;
155 }
156 if ( itvfmt.fInfo ) {
157 fInfo = itvfmt.fInfo->clone();
158 } else {
159 fInfo = NULL;
160 }
161 if ( itvfmt.fFromCalendar ) {
162 fFromCalendar = itvfmt.fFromCalendar->clone();
163 } else {
164 fFromCalendar = NULL;
165 }
166 if ( itvfmt.fToCalendar ) {
167 fToCalendar = itvfmt.fToCalendar->clone();
168 } else {
169 fToCalendar = NULL;
170 }
171 fSkeleton = itvfmt.fSkeleton;
172 int8_t i;
173 for ( i = 0; i< DateIntervalInfo::kIPI_MAX_INDEX; ++i ) {
174 fIntervalPatterns[i] = itvfmt.fIntervalPatterns[i];
175 }
176 fLocale = itvfmt.fLocale;
177 fDatePattern = (itvfmt.fDatePattern)? (UnicodeString*)itvfmt.fDatePattern->clone(): NULL;
178 fTimePattern = (itvfmt.fTimePattern)? (UnicodeString*)itvfmt.fTimePattern->clone(): NULL;
179 fDateTimeFormat = (itvfmt.fDateTimeFormat)? (UnicodeString*)itvfmt.fDateTimeFormat->clone(): NULL;
180 }
181 return *this;
182 }
183
184
~DateIntervalFormat()185 DateIntervalFormat::~DateIntervalFormat() {
186 delete fInfo;
187 delete fDateFormat;
188 delete fFromCalendar;
189 delete fToCalendar;
190 delete fDatePattern;
191 delete fTimePattern;
192 delete fDateTimeFormat;
193 }
194
195
196 Format*
clone(void) const197 DateIntervalFormat::clone(void) const {
198 return new DateIntervalFormat(*this);
199 }
200
201
202 UBool
operator ==(const Format & other) const203 DateIntervalFormat::operator==(const Format& other) const {
204 if (typeid(*this) == typeid(other)) {
205 const DateIntervalFormat* fmt = (DateIntervalFormat*)&other;
206 #ifdef DTITVFMT_DEBUG
207 UBool equal;
208 equal = (this == fmt);
209
210 equal = (*fInfo == *fmt->fInfo);
211 equal = (*fDateFormat == *fmt->fDateFormat);
212 equal = fFromCalendar->isEquivalentTo(*fmt->fFromCalendar) ;
213 equal = fToCalendar->isEquivalentTo(*fmt->fToCalendar) ;
214 equal = (fSkeleton == fmt->fSkeleton);
215 equal = ((fDatePattern == NULL && fmt->fDatePattern == NULL) || (fDatePattern && fmt->fDatePattern && *fDatePattern == *fmt->fDatePattern));
216 equal = ((fTimePattern == NULL && fmt->fTimePattern == NULL) || (fTimePattern && fmt->fTimePattern && *fTimePattern == *fmt->fTimePattern));
217 equal = ((fDateTimeFormat == NULL && fmt->fDateTimeFormat == NULL) || (fDateTimeFormat && fmt->fDateTimeFormat && *fDateTimeFormat == *fmt->fDateTimeFormat));
218 #endif
219 UBool res;
220 res = ( this == fmt ) ||
221 ( Format::operator==(other) &&
222 fInfo &&
223 ( *fInfo == *fmt->fInfo ) &&
224 fDateFormat &&
225 ( *fDateFormat == *fmt->fDateFormat ) &&
226 fFromCalendar &&
227 fFromCalendar->isEquivalentTo(*fmt->fFromCalendar) &&
228 fToCalendar &&
229 fToCalendar->isEquivalentTo(*fmt->fToCalendar) &&
230 fSkeleton == fmt->fSkeleton &&
231 ((fDatePattern == NULL && fmt->fDatePattern == NULL) || (fDatePattern && fmt->fDatePattern && *fDatePattern == *fmt->fDatePattern)) &&
232 ((fTimePattern == NULL && fmt->fTimePattern == NULL) || (fTimePattern && fmt->fTimePattern && *fTimePattern == *fmt->fTimePattern)) &&
233 ((fDateTimeFormat == NULL && fmt->fDateTimeFormat == NULL) || (fDateTimeFormat && fmt->fDateTimeFormat && *fDateTimeFormat == *fmt->fDateTimeFormat)) && fLocale == fmt->fLocale);
234 int8_t i;
235 for (i = 0; i< DateIntervalInfo::kIPI_MAX_INDEX && res == TRUE; ++i ) {
236 res = ( fIntervalPatterns[i].firstPart ==
237 fmt->fIntervalPatterns[i].firstPart) &&
238 ( fIntervalPatterns[i].secondPart ==
239 fmt->fIntervalPatterns[i].secondPart ) &&
240 ( fIntervalPatterns[i].laterDateFirst ==
241 fmt->fIntervalPatterns[i].laterDateFirst) ;
242 }
243 return res;
244 }
245 return FALSE;
246 }
247
248
249
250 UnicodeString&
format(const Formattable & obj,UnicodeString & appendTo,FieldPosition & fieldPosition,UErrorCode & status) const251 DateIntervalFormat::format(const Formattable& obj,
252 UnicodeString& appendTo,
253 FieldPosition& fieldPosition,
254 UErrorCode& status) const {
255 if ( U_FAILURE(status) ) {
256 return appendTo;
257 }
258
259 if ( obj.getType() == Formattable::kObject ) {
260 const UObject* formatObj = obj.getObject();
261 const DateInterval* interval = dynamic_cast<const DateInterval*>(formatObj);
262 if (interval != NULL){
263 return format(interval, appendTo, fieldPosition, status);
264 }
265 }
266 status = U_ILLEGAL_ARGUMENT_ERROR;
267 return appendTo;
268 }
269
270
271 UnicodeString&
format(const DateInterval * dtInterval,UnicodeString & appendTo,FieldPosition & fieldPosition,UErrorCode & status) const272 DateIntervalFormat::format(const DateInterval* dtInterval,
273 UnicodeString& appendTo,
274 FieldPosition& fieldPosition,
275 UErrorCode& status) const {
276 if ( U_FAILURE(status) ) {
277 return appendTo;
278 }
279
280 if ( fFromCalendar != NULL && fToCalendar != NULL &&
281 fDateFormat != NULL && fInfo != NULL ) {
282 fFromCalendar->setTime(dtInterval->getFromDate(), status);
283 fToCalendar->setTime(dtInterval->getToDate(), status);
284 if ( U_SUCCESS(status) ) {
285 return format(*fFromCalendar, *fToCalendar, appendTo,fieldPosition, status);
286 }
287 }
288 return appendTo;
289 }
290
291
292 UnicodeString&
format(Calendar & fromCalendar,Calendar & toCalendar,UnicodeString & appendTo,FieldPosition & pos,UErrorCode & status) const293 DateIntervalFormat::format(Calendar& fromCalendar,
294 Calendar& toCalendar,
295 UnicodeString& appendTo,
296 FieldPosition& pos,
297 UErrorCode& status) const {
298 if ( U_FAILURE(status) ) {
299 return appendTo;
300 }
301
302 // not support different calendar types and time zones
303 //if ( fromCalendar.getType() != toCalendar.getType() ) {
304 if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
305 status = U_ILLEGAL_ARGUMENT_ERROR;
306 return appendTo;
307 }
308
309 // First, find the largest different calendar field.
310 UCalendarDateFields field = UCAL_FIELD_COUNT;
311
312 if ( fromCalendar.get(UCAL_ERA,status) != toCalendar.get(UCAL_ERA,status)) {
313 field = UCAL_ERA;
314 } else if ( fromCalendar.get(UCAL_YEAR, status) !=
315 toCalendar.get(UCAL_YEAR, status) ) {
316 field = UCAL_YEAR;
317 } else if ( fromCalendar.get(UCAL_MONTH, status) !=
318 toCalendar.get(UCAL_MONTH, status) ) {
319 field = UCAL_MONTH;
320 } else if ( fromCalendar.get(UCAL_DATE, status) !=
321 toCalendar.get(UCAL_DATE, status) ) {
322 field = UCAL_DATE;
323 } else if ( fromCalendar.get(UCAL_AM_PM, status) !=
324 toCalendar.get(UCAL_AM_PM, status) ) {
325 field = UCAL_AM_PM;
326 } else if ( fromCalendar.get(UCAL_HOUR, status) !=
327 toCalendar.get(UCAL_HOUR, status) ) {
328 field = UCAL_HOUR;
329 } else if ( fromCalendar.get(UCAL_MINUTE, status) !=
330 toCalendar.get(UCAL_MINUTE, status) ) {
331 field = UCAL_MINUTE;
332 } else if ( fromCalendar.get(UCAL_SECOND, status) !=
333 toCalendar.get(UCAL_SECOND, status) ) {
334 field = UCAL_SECOND;
335 }
336
337 if ( U_FAILURE(status) ) {
338 return appendTo;
339 }
340 if ( field == UCAL_FIELD_COUNT ) {
341 /* ignore the millisecond etc. small fields' difference.
342 * use single date when all the above are the same.
343 */
344 return fDateFormat->format(fromCalendar, appendTo, pos);
345 }
346 UBool fromToOnSameDay = (field==UCAL_AM_PM || field==UCAL_HOUR || field==UCAL_MINUTE || field==UCAL_SECOND);
347
348 // following call should not set wrong status,
349 // all the pass-in fields are valid till here
350 int32_t itvPtnIndex = DateIntervalInfo::calendarFieldToIntervalIndex(field,
351 status);
352 const PatternInfo& intervalPattern = fIntervalPatterns[itvPtnIndex];
353
354 if ( intervalPattern.firstPart.isEmpty() &&
355 intervalPattern.secondPart.isEmpty() ) {
356 if ( fDateFormat->isFieldUnitIgnored(field) ) {
357 /* the largest different calendar field is small than
358 * the smallest calendar field in pattern,
359 * return single date format.
360 */
361 return fDateFormat->format(fromCalendar, appendTo, pos);
362 }
363 return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, status);
364 }
365 // If the first part in interval pattern is empty,
366 // the 2nd part of it saves the full-pattern used in fall-back.
367 // For a 'real' interval pattern, the first part will never be empty.
368 if ( intervalPattern.firstPart.isEmpty() ) {
369 // fall back
370 UnicodeString originalPattern;
371 fDateFormat->toPattern(originalPattern);
372 fDateFormat->applyPattern(intervalPattern.secondPart);
373 appendTo = fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, status);
374 fDateFormat->applyPattern(originalPattern);
375 return appendTo;
376 }
377 Calendar* firstCal;
378 Calendar* secondCal;
379 if ( intervalPattern.laterDateFirst ) {
380 firstCal = &toCalendar;
381 secondCal = &fromCalendar;
382 } else {
383 firstCal = &fromCalendar;
384 secondCal = &toCalendar;
385 }
386 // break the interval pattern into 2 parts,
387 // first part should not be empty,
388 UnicodeString originalPattern;
389 fDateFormat->toPattern(originalPattern);
390 fDateFormat->applyPattern(intervalPattern.firstPart);
391 fDateFormat->format(*firstCal, appendTo, pos);
392 if ( !intervalPattern.secondPart.isEmpty() ) {
393 fDateFormat->applyPattern(intervalPattern.secondPart);
394 FieldPosition otherPos;
395 otherPos.setField(pos.getField());
396 fDateFormat->format(*secondCal, appendTo, otherPos);
397 if (pos.getEndIndex() == 0 && otherPos.getEndIndex() > 0) {
398 pos = otherPos;
399 }
400 }
401 fDateFormat->applyPattern(originalPattern);
402 return appendTo;
403 }
404
405
406
407 void
parseObject(const UnicodeString &,Formattable &,ParsePosition &) const408 DateIntervalFormat::parseObject(const UnicodeString& /* source */,
409 Formattable& /* result */,
410 ParsePosition& /* parse_pos */) const {
411 // parseObject(const UnicodeString&, Formattable&, UErrorCode&) const
412 // will set status as U_INVALID_FORMAT_ERROR if
413 // parse_pos is still 0
414 }
415
416
417
418
419 const DateIntervalInfo*
getDateIntervalInfo() const420 DateIntervalFormat::getDateIntervalInfo() const {
421 return fInfo;
422 }
423
424
425 void
setDateIntervalInfo(const DateIntervalInfo & newItvPattern,UErrorCode & status)426 DateIntervalFormat::setDateIntervalInfo(const DateIntervalInfo& newItvPattern,
427 UErrorCode& status) {
428 delete fInfo;
429 fInfo = new DateIntervalInfo(newItvPattern);
430
431 // Delete patterns that get reset by initializePattern
432 delete fDatePattern;
433 fDatePattern = NULL;
434 delete fTimePattern;
435 fTimePattern = NULL;
436 delete fDateTimeFormat;
437 fDateTimeFormat = NULL;
438
439 if ( fDateFormat ) {
440 initializePattern(status);
441 }
442 }
443
444
445
446 const DateFormat*
getDateFormat() const447 DateIntervalFormat::getDateFormat() const {
448 return fDateFormat;
449 }
450
451
452 void
adoptTimeZone(TimeZone * zone)453 DateIntervalFormat::adoptTimeZone(TimeZone* zone)
454 {
455 if (fDateFormat != NULL) {
456 fDateFormat->adoptTimeZone(zone);
457 }
458 // The fDateFormat has the master calendar for the DateIntervalFormat and has
459 // ownership of any adopted TimeZone; fFromCalendar and fToCalendar are internal
460 // work clones of that calendar (and should not also be given ownership of the
461 // adopted TimeZone).
462 if (fFromCalendar) {
463 fFromCalendar->setTimeZone(*zone);
464 }
465 if (fToCalendar) {
466 fToCalendar->setTimeZone(*zone);
467 }
468 }
469
470 void
setTimeZone(const TimeZone & zone)471 DateIntervalFormat::setTimeZone(const TimeZone& zone)
472 {
473 if (fDateFormat != NULL) {
474 fDateFormat->setTimeZone(zone);
475 }
476 // The fDateFormat has the master calendar for the DateIntervalFormat;
477 // fFromCalendar and fToCalendar are internal work clones of that calendar.
478 if (fFromCalendar) {
479 fFromCalendar->setTimeZone(zone);
480 }
481 if (fToCalendar) {
482 fToCalendar->setTimeZone(zone);
483 }
484 }
485
486 const TimeZone&
getTimeZone() const487 DateIntervalFormat::getTimeZone() const
488 {
489 if (fDateFormat != NULL) {
490 return fDateFormat->getTimeZone();
491 }
492 // If fDateFormat is NULL (unexpected), create default timezone.
493 return *(TimeZone::createDefault());
494 }
495
DateIntervalFormat(const Locale & locale,DateIntervalInfo * dtItvInfo,const UnicodeString * skeleton,UErrorCode & status)496 DateIntervalFormat::DateIntervalFormat(const Locale& locale,
497 DateIntervalInfo* dtItvInfo,
498 const UnicodeString* skeleton,
499 UErrorCode& status)
500 : fInfo(NULL),
501 fDateFormat(NULL),
502 fFromCalendar(NULL),
503 fToCalendar(NULL),
504 fLocale(locale),
505 fDatePattern(NULL),
506 fTimePattern(NULL),
507 fDateTimeFormat(NULL)
508 {
509 if ( U_FAILURE(status) ) {
510 delete dtItvInfo;
511 return;
512 }
513 SimpleDateFormat* dtfmt =
514 static_cast<SimpleDateFormat *>(
515 DateFormat::createInstanceForSkeleton(
516 *skeleton, locale, status));
517 if ( U_FAILURE(status) ) {
518 delete dtItvInfo;
519 delete dtfmt;
520 return;
521 }
522 if ( dtfmt == NULL || dtItvInfo == NULL) {
523 status = U_MEMORY_ALLOCATION_ERROR;
524 // safe to delete NULL
525 delete dtfmt;
526 delete dtItvInfo;
527 return;
528 }
529 if ( skeleton ) {
530 fSkeleton = *skeleton;
531 }
532 fInfo = dtItvInfo;
533 fDateFormat = dtfmt;
534 if ( dtfmt->getCalendar() ) {
535 fFromCalendar = dtfmt->getCalendar()->clone();
536 fToCalendar = dtfmt->getCalendar()->clone();
537 } else {
538 fFromCalendar = NULL;
539 fToCalendar = NULL;
540 }
541 initializePattern(status);
542 }
543
544 DateIntervalFormat* U_EXPORT2
create(const Locale & locale,DateIntervalInfo * dtitvinf,const UnicodeString * skeleton,UErrorCode & status)545 DateIntervalFormat::create(const Locale& locale,
546 DateIntervalInfo* dtitvinf,
547 const UnicodeString* skeleton,
548 UErrorCode& status) {
549 DateIntervalFormat* f = new DateIntervalFormat(locale, dtitvinf,
550 skeleton, status);
551 if ( f == NULL ) {
552 status = U_MEMORY_ALLOCATION_ERROR;
553 delete dtitvinf;
554 } else if ( U_FAILURE(status) ) {
555 // safe to delete f, although nothing acutally is saved
556 delete f;
557 f = 0;
558 }
559 return f;
560 }
561
562
563
564 /**
565 * Initialize interval patterns locale to this formatter
566 *
567 * This code is a bit complicated since
568 * 1. the interval patterns saved in resource bundle files are interval
569 * patterns based on date or time only.
570 * It does not have interval patterns based on both date and time.
571 * Interval patterns on both date and time are algorithm generated.
572 *
573 * For example, it has interval patterns on skeleton "dMy" and "hm",
574 * but it does not have interval patterns on skeleton "dMyhm".
575 *
576 * The rule to genearte interval patterns for both date and time skeleton are
577 * 1) when the year, month, or day differs, concatenate the two original
578 * expressions with a separator between,
579 * For example, interval pattern from "Jan 10, 2007 10:10 am"
580 * to "Jan 11, 2007 10:10am" is
581 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
582 *
583 * 2) otherwise, present the date followed by the range expression
584 * for the time.
585 * For example, interval pattern from "Jan 10, 2007 10:10 am"
586 * to "Jan 10, 2007 11:10am" is
587 * "Jan 10, 2007 10:10 am - 11:10am"
588 *
589 * 2. even a pattern does not request a certion calendar field,
590 * the interval pattern needs to include such field if such fields are
591 * different between 2 dates.
592 * For example, a pattern/skeleton is "hm", but the interval pattern
593 * includes year, month, and date when year, month, and date differs.
594 *
595 * @param status output param set to success/failure code on exit
596 * @stable ICU 4.0
597 */
598 void
initializePattern(UErrorCode & status)599 DateIntervalFormat::initializePattern(UErrorCode& status) {
600 if ( U_FAILURE(status) ) {
601 return;
602 }
603 const Locale& locale = fDateFormat->getSmpFmtLocale();
604 if ( fSkeleton.isEmpty() ) {
605 UnicodeString fullPattern;
606 fDateFormat->toPattern(fullPattern);
607 #ifdef DTITVFMT_DEBUG
608 char result[1000];
609 char result_1[1000];
610 char mesg[2000];
611 fSkeleton.extract(0, fSkeleton.length(), result, "UTF-8");
612 sprintf(mesg, "in getBestSkeleton: fSkeleton: %s; \n", result);
613 PRINTMESG(mesg)
614 #endif
615 // fSkeleton is already set by createDateIntervalInstance()
616 // or by createInstance(UnicodeString skeleton, .... )
617 fSkeleton = DateTimePatternGenerator::staticGetSkeleton(
618 fullPattern, status);
619 if ( U_FAILURE(status) ) {
620 return;
621 }
622 }
623
624 // initialize the fIntervalPattern ordering
625 int8_t i;
626 for ( i = 0; i < DateIntervalInfo::kIPI_MAX_INDEX; ++i ) {
627 fIntervalPatterns[i].laterDateFirst = fInfo->getDefaultOrder();
628 }
629
630 /* Check whether the skeleton is a combination of date and time.
631 * For the complication reason 1 explained above.
632 */
633 UnicodeString dateSkeleton;
634 UnicodeString timeSkeleton;
635 UnicodeString normalizedTimeSkeleton;
636 UnicodeString normalizedDateSkeleton;
637
638
639 /* the difference between time skeleton and normalizedTimeSkeleton are:
640 * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true)
641 * 2. 'a' is omitted in normalized time skeleton.
642 * 3. there is only one appearance for 'h' or 'H', 'm','v', 'z' in normalized
643 * time skeleton
644 *
645 * The difference between date skeleton and normalizedDateSkeleton are:
646 * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
647 * 2. 'E' and 'EE' are normalized into 'EEE'
648 * 3. 'MM' is normalized into 'M'
649 */
650 getDateTimeSkeleton(fSkeleton, dateSkeleton, normalizedDateSkeleton,
651 timeSkeleton, normalizedTimeSkeleton);
652
653 #ifdef DTITVFMT_DEBUG
654 char result[1000];
655 char result_1[1000];
656 char mesg[2000];
657 fSkeleton.extract(0, fSkeleton.length(), result, "UTF-8");
658 sprintf(mesg, "in getBestSkeleton: fSkeleton: %s; \n", result);
659 PRINTMESG(mesg)
660 #endif
661
662 // move this up here since we need it for fallbacks
663 if ( timeSkeleton.length() > 0 && dateSkeleton.length() > 0 ) {
664 // Need the Date/Time pattern for concatenation of the date
665 // with the time interval.
666 // The date/time pattern ( such as {0} {1} ) is saved in
667 // calendar, that is why need to get the CalendarData here.
668 CalendarData* calData = new CalendarData(locale, NULL, status);
669 if ( U_FAILURE(status) ) {
670 delete calData;
671 return;
672 }
673 if ( calData == NULL ) {
674 status = U_MEMORY_ALLOCATION_ERROR;
675 return;
676 }
677
678 const UResourceBundle* dateTimePatternsRes = calData->getByKey(
679 gDateTimePatternsTag, status);
680 int32_t dateTimeFormatLength;
681 const UChar* dateTimeFormat = ures_getStringByIndex(
682 dateTimePatternsRes,
683 (int32_t)DateFormat::kDateTime,
684 &dateTimeFormatLength, &status);
685 if ( U_SUCCESS(status) && dateTimeFormatLength >= 3 ) {
686 fDateTimeFormat = new UnicodeString(dateTimeFormat, dateTimeFormatLength);
687 }
688 delete calData;
689 }
690
691 UBool found = setSeparateDateTimePtn(normalizedDateSkeleton,
692 normalizedTimeSkeleton);
693
694 // for skeletons with seconds, found is false and we enter this block
695 if ( found == false ) {
696 // use fallback
697 // TODO: if user asks "m"(minute), but "d"(day) differ
698 if ( timeSkeleton.length() != 0 ) {
699 if ( dateSkeleton.length() == 0 ) {
700 // prefix with yMd
701 timeSkeleton.insert(0, gDateFormatSkeleton[DateFormat::kShort], -1);
702 UnicodeString pattern = DateFormat::getBestPattern(
703 locale, timeSkeleton, status);
704 if ( U_FAILURE(status) ) {
705 return;
706 }
707 // for fall back interval patterns,
708 // the first part of the pattern is empty,
709 // the second part of the pattern is the full-pattern
710 // should be used in fall-back.
711 setPatternInfo(UCAL_DATE, NULL, &pattern, fInfo->getDefaultOrder());
712 setPatternInfo(UCAL_MONTH, NULL, &pattern, fInfo->getDefaultOrder());
713 setPatternInfo(UCAL_YEAR, NULL, &pattern, fInfo->getDefaultOrder());
714 } else {
715 // TODO: fall back
716 }
717 } else {
718 // TODO: fall back
719 }
720 return;
721 } // end of skeleton not found
722 // interval patterns for skeleton are found in resource
723 if ( timeSkeleton.length() == 0 ) {
724 // done
725 } else if ( dateSkeleton.length() == 0 ) {
726 // prefix with yMd
727 timeSkeleton.insert(0, gDateFormatSkeleton[DateFormat::kShort], -1);
728 UnicodeString pattern = DateFormat::getBestPattern(
729 locale, timeSkeleton, status);
730 if ( U_FAILURE(status) ) {
731 return;
732 }
733 // for fall back interval patterns,
734 // the first part of the pattern is empty,
735 // the second part of the pattern is the full-pattern
736 // should be used in fall-back.
737 setPatternInfo(UCAL_DATE, NULL, &pattern, fInfo->getDefaultOrder());
738 setPatternInfo(UCAL_MONTH, NULL, &pattern, fInfo->getDefaultOrder());
739 setPatternInfo(UCAL_YEAR, NULL, &pattern, fInfo->getDefaultOrder());
740 } else {
741 /* if both present,
742 * 1) when the year, month, or day differs,
743 * concatenate the two original expressions with a separator between,
744 * 2) otherwise, present the date followed by the
745 * range expression for the time.
746 */
747 /*
748 * 1) when the year, month, or day differs,
749 * concatenate the two original expressions with a separator between,
750 */
751 // if field exists, use fall back
752 UnicodeString skeleton = fSkeleton;
753 if ( !fieldExistsInSkeleton(UCAL_DATE, dateSkeleton) ) {
754 // prefix skeleton with 'd'
755 skeleton.insert(0, LOW_D);
756 setFallbackPattern(UCAL_DATE, skeleton, status);
757 }
758 if ( !fieldExistsInSkeleton(UCAL_MONTH, dateSkeleton) ) {
759 // then prefix skeleton with 'M'
760 skeleton.insert(0, CAP_M);
761 setFallbackPattern(UCAL_MONTH, skeleton, status);
762 }
763 if ( !fieldExistsInSkeleton(UCAL_YEAR, dateSkeleton) ) {
764 // then prefix skeleton with 'y'
765 skeleton.insert(0, LOW_Y);
766 setFallbackPattern(UCAL_YEAR, skeleton, status);
767 }
768
769 /*
770 * 2) otherwise, present the date followed by the
771 * range expression for the time.
772 */
773
774 if ( fDateTimeFormat == 0 ) {
775 // earlier failure getting dateTimeFormat
776 return;
777 }
778
779 UnicodeString datePattern = DateFormat::getBestPattern(
780 locale, dateSkeleton, status);
781
782 concatSingleDate2TimeInterval(*fDateTimeFormat, datePattern, UCAL_AM_PM, status);
783 concatSingleDate2TimeInterval(*fDateTimeFormat, datePattern, UCAL_HOUR, status);
784 concatSingleDate2TimeInterval(*fDateTimeFormat, datePattern, UCAL_MINUTE, status);
785 }
786 }
787
788
789
790 void U_EXPORT2
getDateTimeSkeleton(const UnicodeString & skeleton,UnicodeString & dateSkeleton,UnicodeString & normalizedDateSkeleton,UnicodeString & timeSkeleton,UnicodeString & normalizedTimeSkeleton)791 DateIntervalFormat::getDateTimeSkeleton(const UnicodeString& skeleton,
792 UnicodeString& dateSkeleton,
793 UnicodeString& normalizedDateSkeleton,
794 UnicodeString& timeSkeleton,
795 UnicodeString& normalizedTimeSkeleton) {
796 // dateSkeleton follows the sequence of y*M*E*d*
797 // timeSkeleton follows the sequence of hm*[v|z]?
798 int32_t ECount = 0;
799 int32_t dCount = 0;
800 int32_t MCount = 0;
801 int32_t yCount = 0;
802 int32_t hCount = 0;
803 int32_t HCount = 0;
804 int32_t mCount = 0;
805 int32_t vCount = 0;
806 int32_t zCount = 0;
807 int32_t i;
808
809 for (i = 0; i < skeleton.length(); ++i) {
810 UChar ch = skeleton[i];
811 switch ( ch ) {
812 case CAP_E:
813 dateSkeleton.append(ch);
814 ++ECount;
815 break;
816 case LOW_D:
817 dateSkeleton.append(ch);
818 ++dCount;
819 break;
820 case CAP_M:
821 dateSkeleton.append(ch);
822 ++MCount;
823 break;
824 case LOW_Y:
825 dateSkeleton.append(ch);
826 ++yCount;
827 break;
828 case CAP_G:
829 case CAP_Y:
830 case LOW_U:
831 case CAP_Q:
832 case LOW_Q:
833 case CAP_L:
834 case LOW_L:
835 case CAP_W:
836 case LOW_W:
837 case CAP_D:
838 case CAP_F:
839 case LOW_G:
840 case LOW_E:
841 case LOW_C:
842 case CAP_U:
843 case LOW_R:
844 normalizedDateSkeleton.append(ch);
845 dateSkeleton.append(ch);
846 break;
847 case LOW_A:
848 // 'a' is implicitly handled
849 timeSkeleton.append(ch);
850 break;
851 case LOW_H:
852 timeSkeleton.append(ch);
853 ++hCount;
854 break;
855 case CAP_H:
856 timeSkeleton.append(ch);
857 ++HCount;
858 break;
859 case LOW_M:
860 timeSkeleton.append(ch);
861 ++mCount;
862 break;
863 case LOW_Z:
864 ++zCount;
865 timeSkeleton.append(ch);
866 break;
867 case LOW_V:
868 ++vCount;
869 timeSkeleton.append(ch);
870 break;
871 case CAP_V:
872 case CAP_Z:
873 case LOW_K:
874 case CAP_K:
875 case LOW_J:
876 case LOW_S:
877 case CAP_S:
878 case CAP_A:
879 timeSkeleton.append(ch);
880 normalizedTimeSkeleton.append(ch);
881 break;
882 }
883 }
884
885 /* generate normalized form for date*/
886 if ( yCount != 0 ) {
887 for (i = 0; i < yCount; ++i) {
888 normalizedDateSkeleton.append(LOW_Y);
889 }
890 }
891 if ( MCount != 0 ) {
892 if ( MCount < 3 ) {
893 normalizedDateSkeleton.append(CAP_M);
894 } else {
895 int32_t i;
896 for ( i = 0; i < MCount && i < MAX_M_COUNT; ++i ) {
897 normalizedDateSkeleton.append(CAP_M);
898 }
899 }
900 }
901 if ( ECount != 0 ) {
902 if ( ECount <= 3 ) {
903 normalizedDateSkeleton.append(CAP_E);
904 } else {
905 int32_t i;
906 for ( i = 0; i < ECount && i < MAX_E_COUNT; ++i ) {
907 normalizedDateSkeleton.append(CAP_E);
908 }
909 }
910 }
911 if ( dCount != 0 ) {
912 normalizedDateSkeleton.append(LOW_D);
913 }
914
915 /* generate normalized form for time */
916 if ( HCount != 0 ) {
917 normalizedTimeSkeleton.append(CAP_H);
918 }
919 else if ( hCount != 0 ) {
920 normalizedTimeSkeleton.append(LOW_H);
921 }
922 if ( mCount != 0 ) {
923 normalizedTimeSkeleton.append(LOW_M);
924 }
925 if ( zCount != 0 ) {
926 normalizedTimeSkeleton.append(LOW_Z);
927 }
928 if ( vCount != 0 ) {
929 normalizedTimeSkeleton.append(LOW_V);
930 }
931 }
932
933
934 /**
935 * Generate date or time interval pattern from resource,
936 * and set them into the interval pattern locale to this formatter.
937 *
938 * It needs to handle the following:
939 * 1. need to adjust field width.
940 * For example, the interval patterns saved in DateIntervalInfo
941 * includes "dMMMy", but not "dMMMMy".
942 * Need to get interval patterns for dMMMMy from dMMMy.
943 * Another example, the interval patterns saved in DateIntervalInfo
944 * includes "hmv", but not "hmz".
945 * Need to get interval patterns for "hmz' from 'hmv'
946 *
947 * 2. there might be no pattern for 'y' differ for skeleton "Md",
948 * in order to get interval patterns for 'y' differ,
949 * need to look for it from skeleton 'yMd'
950 *
951 * @param dateSkeleton normalized date skeleton
952 * @param timeSkeleton normalized time skeleton
953 * @return whether the resource is found for the skeleton.
954 * TRUE if interval pattern found for the skeleton,
955 * FALSE otherwise.
956 * @stable ICU 4.0
957 */
958 UBool
setSeparateDateTimePtn(const UnicodeString & dateSkeleton,const UnicodeString & timeSkeleton)959 DateIntervalFormat::setSeparateDateTimePtn(
960 const UnicodeString& dateSkeleton,
961 const UnicodeString& timeSkeleton) {
962 const UnicodeString* skeleton;
963 // if both date and time skeleton present,
964 // the final interval pattern might include time interval patterns
965 // ( when, am_pm, hour, minute differ ),
966 // but not date interval patterns ( when year, month, day differ ).
967 // For year/month/day differ, it falls back to fall-back pattern.
968 if ( timeSkeleton.length() != 0 ) {
969 skeleton = &timeSkeleton;
970 } else {
971 skeleton = &dateSkeleton;
972 }
973
974 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
975 * are defined in resource,
976 * interval patterns for skeleton "dMMMMy" are calculated by
977 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
978 * 2. get the interval patterns for "dMMMy",
979 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
980 * getBestSkeleton() is step 1.
981 */
982 // best skeleton, and the difference information
983 int8_t differenceInfo = 0;
984 const UnicodeString* bestSkeleton = fInfo->getBestSkeleton(*skeleton,
985 differenceInfo);
986 /* best skeleton could be NULL.
987 For example: in "ca" resource file,
988 interval format is defined as following
989 intervalFormats{
990 fallback{"{0} - {1}"}
991 }
992 there is no skeletons/interval patterns defined,
993 and the best skeleton match could be NULL
994 */
995 if ( bestSkeleton == NULL ) {
996 return false;
997 }
998
999 // Set patterns for fallback use, need to do this
1000 // before returning if differenceInfo == -1
1001 UErrorCode status;
1002 if ( dateSkeleton.length() != 0) {
1003 status = U_ZERO_ERROR;
1004 fDatePattern = new UnicodeString(DateFormat::getBestPattern(
1005 fLocale, dateSkeleton, status));
1006 }
1007 if ( timeSkeleton.length() != 0) {
1008 status = U_ZERO_ERROR;
1009 fTimePattern = new UnicodeString(DateFormat::getBestPattern(
1010 fLocale, timeSkeleton, status));
1011 }
1012
1013 // difference:
1014 // 0 means the best matched skeleton is the same as input skeleton
1015 // 1 means the fields are the same, but field width are different
1016 // 2 means the only difference between fields are v/z,
1017 // -1 means there are other fields difference
1018 // (this will happen, for instance, if the supplied skeleton has seconds,
1019 // but no skeletons in the intervalFormats data do)
1020 if ( differenceInfo == -1 ) {
1021 // skeleton has different fields, not only v/z difference
1022 return false;
1023 }
1024
1025 if ( timeSkeleton.length() == 0 ) {
1026 UnicodeString extendedSkeleton;
1027 UnicodeString extendedBestSkeleton;
1028 // only has date skeleton
1029 setIntervalPattern(UCAL_DATE, skeleton, bestSkeleton, differenceInfo,
1030 &extendedSkeleton, &extendedBestSkeleton);
1031
1032 UBool extended = setIntervalPattern(UCAL_MONTH, skeleton, bestSkeleton,
1033 differenceInfo,
1034 &extendedSkeleton, &extendedBestSkeleton);
1035
1036 if ( extended ) {
1037 bestSkeleton = &extendedBestSkeleton;
1038 skeleton = &extendedSkeleton;
1039 }
1040 setIntervalPattern(UCAL_YEAR, skeleton, bestSkeleton, differenceInfo,
1041 &extendedSkeleton, &extendedBestSkeleton);
1042 } else {
1043 setIntervalPattern(UCAL_MINUTE, skeleton, bestSkeleton, differenceInfo);
1044 setIntervalPattern(UCAL_HOUR, skeleton, bestSkeleton, differenceInfo);
1045 setIntervalPattern(UCAL_AM_PM, skeleton, bestSkeleton, differenceInfo);
1046 }
1047 return true;
1048 }
1049
1050
1051
1052 void
setFallbackPattern(UCalendarDateFields field,const UnicodeString & skeleton,UErrorCode & status)1053 DateIntervalFormat::setFallbackPattern(UCalendarDateFields field,
1054 const UnicodeString& skeleton,
1055 UErrorCode& status) {
1056 if ( U_FAILURE(status) ) {
1057 return;
1058 }
1059 UnicodeString pattern = DateFormat::getBestPattern(
1060 fLocale, skeleton, status);
1061 if ( U_FAILURE(status) ) {
1062 return;
1063 }
1064 setPatternInfo(field, NULL, &pattern, fInfo->getDefaultOrder());
1065 }
1066
1067
1068
1069
1070 void
setPatternInfo(UCalendarDateFields field,const UnicodeString * firstPart,const UnicodeString * secondPart,UBool laterDateFirst)1071 DateIntervalFormat::setPatternInfo(UCalendarDateFields field,
1072 const UnicodeString* firstPart,
1073 const UnicodeString* secondPart,
1074 UBool laterDateFirst) {
1075 // for fall back interval patterns,
1076 // the first part of the pattern is empty,
1077 // the second part of the pattern is the full-pattern
1078 // should be used in fall-back.
1079 UErrorCode status = U_ZERO_ERROR;
1080 // following should not set any wrong status.
1081 int32_t itvPtnIndex = DateIntervalInfo::calendarFieldToIntervalIndex(field,
1082 status);
1083 if ( U_FAILURE(status) ) {
1084 return;
1085 }
1086 PatternInfo& ptn = fIntervalPatterns[itvPtnIndex];
1087 if ( firstPart ) {
1088 ptn.firstPart = *firstPart;
1089 }
1090 if ( secondPart ) {
1091 ptn.secondPart = *secondPart;
1092 }
1093 ptn.laterDateFirst = laterDateFirst;
1094 }
1095
1096 void
setIntervalPattern(UCalendarDateFields field,const UnicodeString & intervalPattern)1097 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field,
1098 const UnicodeString& intervalPattern) {
1099 UBool order = fInfo->getDefaultOrder();
1100 setIntervalPattern(field, intervalPattern, order);
1101 }
1102
1103
1104 void
setIntervalPattern(UCalendarDateFields field,const UnicodeString & intervalPattern,UBool laterDateFirst)1105 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field,
1106 const UnicodeString& intervalPattern,
1107 UBool laterDateFirst) {
1108 const UnicodeString* pattern = &intervalPattern;
1109 UBool order = laterDateFirst;
1110 // check for "latestFirst:" or "earliestFirst:" prefix
1111 int8_t prefixLength = sizeof(gLaterFirstPrefix)/sizeof(gLaterFirstPrefix[0]);
1112 int8_t earliestFirstLength = sizeof(gEarlierFirstPrefix)/sizeof(gEarlierFirstPrefix[0]);
1113 UnicodeString realPattern;
1114 if ( intervalPattern.startsWith(gLaterFirstPrefix, prefixLength) ) {
1115 order = true;
1116 intervalPattern.extract(prefixLength,
1117 intervalPattern.length() - prefixLength,
1118 realPattern);
1119 pattern = &realPattern;
1120 } else if ( intervalPattern.startsWith(gEarlierFirstPrefix,
1121 earliestFirstLength) ) {
1122 order = false;
1123 intervalPattern.extract(earliestFirstLength,
1124 intervalPattern.length() - earliestFirstLength,
1125 realPattern);
1126 pattern = &realPattern;
1127 }
1128
1129 int32_t splitPoint = splitPatternInto2Part(*pattern);
1130
1131 UnicodeString firstPart;
1132 UnicodeString secondPart;
1133 pattern->extract(0, splitPoint, firstPart);
1134 if ( splitPoint < pattern->length() ) {
1135 pattern->extract(splitPoint, pattern->length()-splitPoint, secondPart);
1136 }
1137 setPatternInfo(field, &firstPart, &secondPart, order);
1138 }
1139
1140
1141
1142
1143 /**
1144 * Generate interval pattern from existing resource
1145 *
1146 * It not only save the interval patterns,
1147 * but also return the extended skeleton and its best match skeleton.
1148 *
1149 * @param field largest different calendar field
1150 * @param skeleton skeleton
1151 * @param bestSkeleton the best match skeleton which has interval pattern
1152 * defined in resource
1153 * @param differenceInfo the difference between skeleton and best skeleton
1154 * 0 means the best matched skeleton is the same as input skeleton
1155 * 1 means the fields are the same, but field width are different
1156 * 2 means the only difference between fields are v/z,
1157 * -1 means there are other fields difference
1158 *
1159 * @param extendedSkeleton extended skeleton
1160 * @param extendedBestSkeleton extended best match skeleton
1161 * @return whether the interval pattern is found
1162 * through extending skeleton or not.
1163 * TRUE if interval pattern is found by
1164 * extending skeleton, FALSE otherwise.
1165 * @stable ICU 4.0
1166 */
1167 UBool
setIntervalPattern(UCalendarDateFields field,const UnicodeString * skeleton,const UnicodeString * bestSkeleton,int8_t differenceInfo,UnicodeString * extendedSkeleton,UnicodeString * extendedBestSkeleton)1168 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field,
1169 const UnicodeString* skeleton,
1170 const UnicodeString* bestSkeleton,
1171 int8_t differenceInfo,
1172 UnicodeString* extendedSkeleton,
1173 UnicodeString* extendedBestSkeleton) {
1174 UErrorCode status = U_ZERO_ERROR;
1175 // following getIntervalPattern() should not generate error status
1176 UnicodeString pattern;
1177 fInfo->getIntervalPattern(*bestSkeleton, field, pattern, status);
1178 if ( pattern.isEmpty() ) {
1179 // single date
1180 if ( SimpleDateFormat::isFieldUnitIgnored(*bestSkeleton, field) ) {
1181 // do nothing, format will handle it
1182 return false;
1183 }
1184
1185 // for 24 hour system, interval patterns in resource file
1186 // might not include pattern when am_pm differ,
1187 // which should be the same as hour differ.
1188 // add it here for simplicity
1189 if ( field == UCAL_AM_PM ) {
1190 fInfo->getIntervalPattern(*bestSkeleton, UCAL_HOUR, pattern,status);
1191 if ( !pattern.isEmpty() ) {
1192 setIntervalPattern(field, pattern);
1193 }
1194 return false;
1195 }
1196 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
1197 // first, get best match pattern "MMMd",
1198 // since there is no pattern for 'y' differs for skeleton 'MMMd',
1199 // need to look for it from skeleton 'yMMMd',
1200 // if found, adjust field width in interval pattern from
1201 // "MMM" to "MMMM".
1202 UChar fieldLetter = fgCalendarFieldToPatternLetter[field];
1203 if ( extendedSkeleton ) {
1204 *extendedSkeleton = *skeleton;
1205 *extendedBestSkeleton = *bestSkeleton;
1206 extendedSkeleton->insert(0, fieldLetter);
1207 extendedBestSkeleton->insert(0, fieldLetter);
1208 // for example, looking for patterns when 'y' differ for
1209 // skeleton "MMMM".
1210 fInfo->getIntervalPattern(*extendedBestSkeleton,field,pattern,status);
1211 if ( pattern.isEmpty() && differenceInfo == 0 ) {
1212 // if there is no skeleton "yMMMM" defined,
1213 // look for the best match skeleton, for example: "yMMM"
1214 const UnicodeString* tmpBest = fInfo->getBestSkeleton(
1215 *extendedBestSkeleton, differenceInfo);
1216 if ( tmpBest != 0 && differenceInfo != -1 ) {
1217 fInfo->getIntervalPattern(*tmpBest, field, pattern, status);
1218 bestSkeleton = tmpBest;
1219 }
1220 }
1221 }
1222 }
1223 if ( !pattern.isEmpty() ) {
1224 if ( differenceInfo != 0 ) {
1225 UnicodeString adjustIntervalPattern;
1226 adjustFieldWidth(*skeleton, *bestSkeleton, pattern, differenceInfo,
1227 adjustIntervalPattern);
1228 setIntervalPattern(field, adjustIntervalPattern);
1229 } else {
1230 setIntervalPattern(field, pattern);
1231 }
1232 if ( extendedSkeleton && !extendedSkeleton->isEmpty() ) {
1233 return TRUE;
1234 }
1235 }
1236 return FALSE;
1237 }
1238
1239
1240
1241 int32_t U_EXPORT2
splitPatternInto2Part(const UnicodeString & intervalPattern)1242 DateIntervalFormat::splitPatternInto2Part(const UnicodeString& intervalPattern) {
1243 UBool inQuote = false;
1244 UChar prevCh = 0;
1245 int32_t count = 0;
1246
1247 /* repeatedPattern used to record whether a pattern has already seen.
1248 It is a pattern applies to first calendar if it is first time seen,
1249 otherwise, it is a pattern applies to the second calendar
1250 */
1251 UBool patternRepeated[] =
1252 {
1253 // A B C D E F G H I J K L M N O
1254 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1255 // P Q R S T U V W X Y Z
1256 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1257 // a b c d e f g h i j k l m n o
1258 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1259 // p q r s t u v w x y z
1260 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1261 };
1262
1263 int8_t PATTERN_CHAR_BASE = 0x41;
1264
1265 /* loop through the pattern string character by character looking for
1266 * the first repeated pattern letter, which breaks the interval pattern
1267 * into 2 parts.
1268 */
1269 int32_t i;
1270 UBool foundRepetition = false;
1271 for (i = 0; i < intervalPattern.length(); ++i) {
1272 UChar ch = intervalPattern.charAt(i);
1273
1274 if (ch != prevCh && count > 0) {
1275 // check the repeativeness of pattern letter
1276 UBool repeated = patternRepeated[(int)(prevCh - PATTERN_CHAR_BASE)];
1277 if ( repeated == FALSE ) {
1278 patternRepeated[prevCh - PATTERN_CHAR_BASE] = TRUE;
1279 } else {
1280 foundRepetition = true;
1281 break;
1282 }
1283 count = 0;
1284 }
1285 if (ch == '\'') {
1286 // Consecutive single quotes are a single quote literal,
1287 // either outside of quotes or between quotes
1288 if ((i+1) < intervalPattern.length() &&
1289 intervalPattern.charAt(i+1) == '\'') {
1290 ++i;
1291 } else {
1292 inQuote = ! inQuote;
1293 }
1294 }
1295 else if (!inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
1296 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
1297 // ch is a date-time pattern character
1298 prevCh = ch;
1299 ++count;
1300 }
1301 }
1302 // check last pattern char, distinguish
1303 // "dd MM" ( no repetition ),
1304 // "d-d"(last char repeated ), and
1305 // "d-d MM" ( repetition found )
1306 if ( count > 0 && foundRepetition == FALSE ) {
1307 if ( patternRepeated[(int)(prevCh - PATTERN_CHAR_BASE)] == FALSE ) {
1308 count = 0;
1309 }
1310 }
1311 return (i - count);
1312 }
1313
1314 static const UChar bracketedZero[] = {0x7B,0x30,0x7D};
1315 static const UChar bracketedOne[] = {0x7B,0x31,0x7D};
1316
1317 void
adjustPosition(UnicodeString & combiningPattern,UnicodeString & pat0,FieldPosition & pos0,UnicodeString & pat1,FieldPosition & pos1,FieldPosition & posResult)1318 DateIntervalFormat::adjustPosition(UnicodeString& combiningPattern, // has {0} and {1} in it
1319 UnicodeString& pat0, FieldPosition& pos0, // pattern and pos corresponding to {0}
1320 UnicodeString& pat1, FieldPosition& pos1, // pattern and pos corresponding to {1}
1321 FieldPosition& posResult) {
1322 int32_t index0 = combiningPattern.indexOf(bracketedZero, 3, 0);
1323 int32_t index1 = combiningPattern.indexOf(bracketedOne, 3, 0);
1324 if (index0 < 0 || index1 < 0) {
1325 return;
1326 }
1327 int32_t placeholderLen = 3; // length of "{0}" or "{1}"
1328 if (index0 < index1) {
1329 if (pos0.getEndIndex() > 0) {
1330 posResult.setBeginIndex(pos0.getBeginIndex() + index0);
1331 posResult.setEndIndex(pos0.getEndIndex() + index0);
1332 } else if (pos1.getEndIndex() > 0) {
1333 // here index1 >= 3
1334 index1 += pat0.length() - placeholderLen; // adjust for pat0 replacing {0}
1335 posResult.setBeginIndex(pos1.getBeginIndex() + index1);
1336 posResult.setEndIndex(pos1.getEndIndex() + index1);
1337 }
1338 } else {
1339 if (pos1.getEndIndex() > 0) {
1340 posResult.setBeginIndex(pos1.getBeginIndex() + index1);
1341 posResult.setEndIndex(pos1.getEndIndex() + index1);
1342 } else if (pos0.getEndIndex() > 0) {
1343 // here index0 >= 3
1344 index0 += pat1.length() - placeholderLen; // adjust for pat1 replacing {1}
1345 posResult.setBeginIndex(pos0.getBeginIndex() + index0);
1346 posResult.setEndIndex(pos0.getEndIndex() + index0);
1347 }
1348 }
1349 }
1350
1351 UnicodeString&
fallbackFormat(Calendar & fromCalendar,Calendar & toCalendar,UBool fromToOnSameDay,UnicodeString & appendTo,FieldPosition & pos,UErrorCode & status) const1352 DateIntervalFormat::fallbackFormat(Calendar& fromCalendar,
1353 Calendar& toCalendar,
1354 UBool fromToOnSameDay, // new
1355 UnicodeString& appendTo,
1356 FieldPosition& pos,
1357 UErrorCode& status) const {
1358 if ( U_FAILURE(status) ) {
1359 return appendTo;
1360 }
1361 UnicodeString fullPattern; // for saving the pattern in fDateFormat
1362 UBool formatDatePlusTimeRange = (fromToOnSameDay && fDatePattern && fTimePattern);
1363 // the fall back
1364 // no need delete earlierDate and laterDate since they are adopted
1365 if (formatDatePlusTimeRange) {
1366 fDateFormat->toPattern(fullPattern); // save current pattern, restore later
1367 fDateFormat->applyPattern(*fTimePattern);
1368 }
1369 FieldPosition otherPos;
1370 otherPos.setField(pos.getField());
1371 UnicodeString* earlierDate = new UnicodeString();
1372 fDateFormat->format(fromCalendar, *earlierDate, pos);
1373 UnicodeString* laterDate = new UnicodeString();
1374 fDateFormat->format(toCalendar, *laterDate, otherPos);
1375 UnicodeString fallbackPattern;
1376 fInfo->getFallbackIntervalPattern(fallbackPattern);
1377 adjustPosition(fallbackPattern, *earlierDate, pos, *laterDate, otherPos, pos);
1378 Formattable fmtArray[2];
1379 fmtArray[0].adoptString(earlierDate);
1380 fmtArray[1].adoptString(laterDate);
1381
1382 UnicodeString fallbackRange;
1383 MessageFormat::format(fallbackPattern, fmtArray, 2, fallbackRange, status);
1384 if ( U_SUCCESS(status) && formatDatePlusTimeRange ) {
1385 // fallbackRange has just the time range, need to format the date part and combine that
1386 fDateFormat->applyPattern(*fDatePattern);
1387 UnicodeString* datePortion = new UnicodeString();
1388 otherPos.setBeginIndex(0);
1389 otherPos.setEndIndex(0);
1390 fDateFormat->format(fromCalendar, *datePortion, otherPos);
1391 adjustPosition(*fDateTimeFormat, fallbackRange, pos, *datePortion, otherPos, pos);
1392 fmtArray[0].setString(fallbackRange); // {0} is time range
1393 fmtArray[1].adoptString(datePortion); // {1} is single date portion
1394 fallbackRange.remove();
1395 MessageFormat::format(*fDateTimeFormat, fmtArray, 2, fallbackRange, status);
1396 }
1397 if ( U_SUCCESS(status) ) {
1398 appendTo.append(fallbackRange);
1399 }
1400 if (formatDatePlusTimeRange) {
1401 // restore full pattern
1402 fDateFormat->applyPattern(fullPattern);
1403 }
1404 return appendTo;
1405 }
1406
1407
1408
1409
1410 UBool U_EXPORT2
fieldExistsInSkeleton(UCalendarDateFields field,const UnicodeString & skeleton)1411 DateIntervalFormat::fieldExistsInSkeleton(UCalendarDateFields field,
1412 const UnicodeString& skeleton)
1413 {
1414 const UChar fieldChar = fgCalendarFieldToPatternLetter[field];
1415 return ( (skeleton.indexOf(fieldChar) == -1)?FALSE:TRUE ) ;
1416 }
1417
1418
1419
1420 void U_EXPORT2
adjustFieldWidth(const UnicodeString & inputSkeleton,const UnicodeString & bestMatchSkeleton,const UnicodeString & bestIntervalPattern,int8_t differenceInfo,UnicodeString & adjustedPtn)1421 DateIntervalFormat::adjustFieldWidth(const UnicodeString& inputSkeleton,
1422 const UnicodeString& bestMatchSkeleton,
1423 const UnicodeString& bestIntervalPattern,
1424 int8_t differenceInfo,
1425 UnicodeString& adjustedPtn) {
1426 adjustedPtn = bestIntervalPattern;
1427 int32_t inputSkeletonFieldWidth[] =
1428 {
1429 // A B C D E F G H I J K L M N O
1430 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1431 // P Q R S T U V W X Y Z
1432 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1433 // a b c d e f g h i j k l m n o
1434 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1435 // p q r s t u v w x y z
1436 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1437 };
1438
1439 int32_t bestMatchSkeletonFieldWidth[] =
1440 {
1441 // A B C D E F G H I J K L M N O
1442 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1443 // P Q R S T U V W X Y Z
1444 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1445 // a b c d e f g h i j k l m n o
1446 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1447 // p q r s t u v w x y z
1448 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1449 };
1450
1451 DateIntervalInfo::parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
1452 DateIntervalInfo::parseSkeleton(bestMatchSkeleton, bestMatchSkeletonFieldWidth);
1453 if ( differenceInfo == 2 ) {
1454 adjustedPtn.findAndReplace(UnicodeString((UChar)0x76 /* v */),
1455 UnicodeString((UChar)0x7a /* z */));
1456 }
1457
1458 UBool inQuote = false;
1459 UChar prevCh = 0;
1460 int32_t count = 0;
1461
1462 const int8_t PATTERN_CHAR_BASE = 0x41;
1463
1464 // loop through the pattern string character by character
1465 int32_t adjustedPtnLength = adjustedPtn.length();
1466 int32_t i;
1467 for (i = 0; i < adjustedPtnLength; ++i) {
1468 UChar ch = adjustedPtn.charAt(i);
1469 if (ch != prevCh && count > 0) {
1470 // check the repeativeness of pattern letter
1471 UChar skeletonChar = prevCh;
1472 if ( skeletonChar == CAP_L ) {
1473 // there is no "L" (always be "M") in skeleton,
1474 // but there is "L" in pattern.
1475 // for skeleton "M+", the pattern might be "...L..."
1476 skeletonChar = CAP_M;
1477 }
1478 int32_t fieldCount = bestMatchSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
1479 int32_t inputFieldCount = inputSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
1480 if ( fieldCount == count && inputFieldCount > fieldCount ) {
1481 count = inputFieldCount - fieldCount;
1482 int32_t j;
1483 for ( j = 0; j < count; ++j ) {
1484 adjustedPtn.insert(i, prevCh);
1485 }
1486 i += count;
1487 adjustedPtnLength += count;
1488 }
1489 count = 0;
1490 }
1491 if (ch == '\'') {
1492 // Consecutive single quotes are a single quote literal,
1493 // either outside of quotes or between quotes
1494 if ((i+1) < adjustedPtn.length() && adjustedPtn.charAt(i+1) == '\'') {
1495 ++i;
1496 } else {
1497 inQuote = ! inQuote;
1498 }
1499 }
1500 else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
1501 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
1502 // ch is a date-time pattern character
1503 prevCh = ch;
1504 ++count;
1505 }
1506 }
1507 if ( count > 0 ) {
1508 // last item
1509 // check the repeativeness of pattern letter
1510 UChar skeletonChar = prevCh;
1511 if ( skeletonChar == CAP_L ) {
1512 // there is no "L" (always be "M") in skeleton,
1513 // but there is "L" in pattern.
1514 // for skeleton "M+", the pattern might be "...L..."
1515 skeletonChar = CAP_M;
1516 }
1517 int32_t fieldCount = bestMatchSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
1518 int32_t inputFieldCount = inputSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
1519 if ( fieldCount == count && inputFieldCount > fieldCount ) {
1520 count = inputFieldCount - fieldCount;
1521 int32_t j;
1522 for ( j = 0; j < count; ++j ) {
1523 adjustedPtn.append(prevCh);
1524 }
1525 }
1526 }
1527 }
1528
1529
1530
1531 void
concatSingleDate2TimeInterval(UnicodeString & format,const UnicodeString & datePattern,UCalendarDateFields field,UErrorCode & status)1532 DateIntervalFormat::concatSingleDate2TimeInterval(UnicodeString& format,
1533 const UnicodeString& datePattern,
1534 UCalendarDateFields field,
1535 UErrorCode& status) {
1536 // following should not set wrong status
1537 int32_t itvPtnIndex = DateIntervalInfo::calendarFieldToIntervalIndex(field,
1538 status);
1539 if ( U_FAILURE(status) ) {
1540 return;
1541 }
1542 PatternInfo& timeItvPtnInfo = fIntervalPatterns[itvPtnIndex];
1543 if ( !timeItvPtnInfo.firstPart.isEmpty() ) {
1544 // UnicodeString allocated here is adopted, so no need to delete
1545 UnicodeString* timeIntervalPattern = new UnicodeString(timeItvPtnInfo.firstPart);
1546 timeIntervalPattern->append(timeItvPtnInfo.secondPart);
1547 UnicodeString* dateStr = new UnicodeString(datePattern);
1548 Formattable fmtArray[2];
1549 fmtArray[0].adoptString(timeIntervalPattern);
1550 fmtArray[1].adoptString(dateStr);
1551 UnicodeString combinedPattern;
1552 MessageFormat::format(format, fmtArray, 2, combinedPattern, status);
1553 if ( U_FAILURE(status) ) {
1554 return;
1555 }
1556 setIntervalPattern(field, combinedPattern, timeItvPtnInfo.laterDateFirst);
1557 }
1558 // else: fall back
1559 // it should not happen if the interval format defined is valid
1560 }
1561
1562
1563
1564 const UChar
1565 DateIntervalFormat::fgCalendarFieldToPatternLetter[] =
1566 {
1567 /*GyM*/ CAP_G, LOW_Y, CAP_M,
1568 /*wWd*/ LOW_W, CAP_W, LOW_D,
1569 /*DEF*/ CAP_D, CAP_E, CAP_F,
1570 /*ahH*/ LOW_A, LOW_H, CAP_H,
1571 /*msS*/ LOW_M, LOW_S, CAP_S, // MINUTE, SECOND, MILLISECOND
1572 /*z.Y*/ LOW_Z, SPACE, CAP_Y, // ZONE_OFFSET, DST_OFFSET, YEAR_WOY,
1573 /*eug*/ LOW_E, LOW_U, LOW_G, // DOW_LOCAL, EXTENDED_YEAR, JULIAN_DAY,
1574 /*A..*/ CAP_A, SPACE, SPACE, // MILLISECONDS_IN_DAY, IS_LEAP_MONTH, FIELD_COUNT
1575 };
1576
1577
1578 U_NAMESPACE_END
1579
1580 #endif
1581