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