1 // Copyright (C) 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 int32_t i;
881 for ( i = 0; i < MCount && i < MAX_M_COUNT; ++i ) {
882 normalizedDateSkeleton.append(CAP_M);
883 }
884 }
885 }
886 if ( ECount != 0 ) {
887 if ( ECount <= 3 ) {
888 normalizedDateSkeleton.append(CAP_E);
889 } else {
890 int32_t i;
891 for ( i = 0; i < ECount && i < MAX_E_COUNT; ++i ) {
892 normalizedDateSkeleton.append(CAP_E);
893 }
894 }
895 }
896 if ( dCount != 0 ) {
897 normalizedDateSkeleton.append(LOW_D);
898 }
899
900 /* generate normalized form for time */
901 if ( HCount != 0 ) {
902 normalizedTimeSkeleton.append(CAP_H);
903 }
904 else if ( hCount != 0 ) {
905 normalizedTimeSkeleton.append(LOW_H);
906 }
907 if ( mCount != 0 ) {
908 normalizedTimeSkeleton.append(LOW_M);
909 }
910 if ( zCount != 0 ) {
911 normalizedTimeSkeleton.append(LOW_Z);
912 }
913 if ( vCount != 0 ) {
914 normalizedTimeSkeleton.append(LOW_V);
915 }
916 }
917
918
919 /**
920 * Generate date or time interval pattern from resource,
921 * and set them into the interval pattern locale to this formatter.
922 *
923 * It needs to handle the following:
924 * 1. need to adjust field width.
925 * For example, the interval patterns saved in DateIntervalInfo
926 * includes "dMMMy", but not "dMMMMy".
927 * Need to get interval patterns for dMMMMy from dMMMy.
928 * Another example, the interval patterns saved in DateIntervalInfo
929 * includes "hmv", but not "hmz".
930 * Need to get interval patterns for "hmz' from 'hmv'
931 *
932 * 2. there might be no pattern for 'y' differ for skeleton "Md",
933 * in order to get interval patterns for 'y' differ,
934 * need to look for it from skeleton 'yMd'
935 *
936 * @param dateSkeleton normalized date skeleton
937 * @param timeSkeleton normalized time skeleton
938 * @return whether the resource is found for the skeleton.
939 * TRUE if interval pattern found for the skeleton,
940 * FALSE otherwise.
941 * @stable ICU 4.0
942 */
943 UBool
setSeparateDateTimePtn(const UnicodeString & dateSkeleton,const UnicodeString & timeSkeleton)944 DateIntervalFormat::setSeparateDateTimePtn(
945 const UnicodeString& dateSkeleton,
946 const UnicodeString& timeSkeleton) {
947 const UnicodeString* skeleton;
948 // if both date and time skeleton present,
949 // the final interval pattern might include time interval patterns
950 // ( when, am_pm, hour, minute differ ),
951 // but not date interval patterns ( when year, month, day differ ).
952 // For year/month/day differ, it falls back to fall-back pattern.
953 if ( timeSkeleton.length() != 0 ) {
954 skeleton = &timeSkeleton;
955 } else {
956 skeleton = &dateSkeleton;
957 }
958
959 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
960 * are defined in resource,
961 * interval patterns for skeleton "dMMMMy" are calculated by
962 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
963 * 2. get the interval patterns for "dMMMy",
964 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
965 * getBestSkeleton() is step 1.
966 */
967 // best skeleton, and the difference information
968 int8_t differenceInfo = 0;
969 const UnicodeString* bestSkeleton = fInfo->getBestSkeleton(*skeleton,
970 differenceInfo);
971 /* best skeleton could be NULL.
972 For example: in "ca" resource file,
973 interval format is defined as following
974 intervalFormats{
975 fallback{"{0} - {1}"}
976 }
977 there is no skeletons/interval patterns defined,
978 and the best skeleton match could be NULL
979 */
980 if ( bestSkeleton == NULL ) {
981 return false;
982 }
983
984 // Set patterns for fallback use, need to do this
985 // before returning if differenceInfo == -1
986 UErrorCode status;
987 if ( dateSkeleton.length() != 0) {
988 status = U_ZERO_ERROR;
989 fDatePattern = new UnicodeString(DateFormat::getBestPattern(
990 fLocale, dateSkeleton, status));
991 }
992 if ( timeSkeleton.length() != 0) {
993 status = U_ZERO_ERROR;
994 fTimePattern = new UnicodeString(DateFormat::getBestPattern(
995 fLocale, timeSkeleton, status));
996 }
997
998 // difference:
999 // 0 means the best matched skeleton is the same as input skeleton
1000 // 1 means the fields are the same, but field width are different
1001 // 2 means the only difference between fields are v/z,
1002 // -1 means there are other fields difference
1003 // (this will happen, for instance, if the supplied skeleton has seconds,
1004 // but no skeletons in the intervalFormats data do)
1005 if ( differenceInfo == -1 ) {
1006 // skeleton has different fields, not only v/z difference
1007 return false;
1008 }
1009
1010 if ( timeSkeleton.length() == 0 ) {
1011 UnicodeString extendedSkeleton;
1012 UnicodeString extendedBestSkeleton;
1013 // only has date skeleton
1014 setIntervalPattern(UCAL_DATE, skeleton, bestSkeleton, differenceInfo,
1015 &extendedSkeleton, &extendedBestSkeleton);
1016
1017 UBool extended = setIntervalPattern(UCAL_MONTH, skeleton, bestSkeleton,
1018 differenceInfo,
1019 &extendedSkeleton, &extendedBestSkeleton);
1020
1021 if ( extended ) {
1022 bestSkeleton = &extendedBestSkeleton;
1023 skeleton = &extendedSkeleton;
1024 }
1025 setIntervalPattern(UCAL_YEAR, skeleton, bestSkeleton, differenceInfo,
1026 &extendedSkeleton, &extendedBestSkeleton);
1027 } else {
1028 setIntervalPattern(UCAL_MINUTE, skeleton, bestSkeleton, differenceInfo);
1029 setIntervalPattern(UCAL_HOUR, skeleton, bestSkeleton, differenceInfo);
1030 setIntervalPattern(UCAL_AM_PM, skeleton, bestSkeleton, differenceInfo);
1031 }
1032 return true;
1033 }
1034
1035
1036
1037 void
setFallbackPattern(UCalendarDateFields field,const UnicodeString & skeleton,UErrorCode & status)1038 DateIntervalFormat::setFallbackPattern(UCalendarDateFields field,
1039 const UnicodeString& skeleton,
1040 UErrorCode& status) {
1041 if ( U_FAILURE(status) ) {
1042 return;
1043 }
1044 UnicodeString pattern = DateFormat::getBestPattern(
1045 fLocale, skeleton, status);
1046 if ( U_FAILURE(status) ) {
1047 return;
1048 }
1049 setPatternInfo(field, NULL, &pattern, fInfo->getDefaultOrder());
1050 }
1051
1052
1053
1054
1055 void
setPatternInfo(UCalendarDateFields field,const UnicodeString * firstPart,const UnicodeString * secondPart,UBool laterDateFirst)1056 DateIntervalFormat::setPatternInfo(UCalendarDateFields field,
1057 const UnicodeString* firstPart,
1058 const UnicodeString* secondPart,
1059 UBool laterDateFirst) {
1060 // for fall back interval patterns,
1061 // the first part of the pattern is empty,
1062 // the second part of the pattern is the full-pattern
1063 // should be used in fall-back.
1064 UErrorCode status = U_ZERO_ERROR;
1065 // following should not set any wrong status.
1066 int32_t itvPtnIndex = DateIntervalInfo::calendarFieldToIntervalIndex(field,
1067 status);
1068 if ( U_FAILURE(status) ) {
1069 return;
1070 }
1071 PatternInfo& ptn = fIntervalPatterns[itvPtnIndex];
1072 if ( firstPart ) {
1073 ptn.firstPart = *firstPart;
1074 }
1075 if ( secondPart ) {
1076 ptn.secondPart = *secondPart;
1077 }
1078 ptn.laterDateFirst = laterDateFirst;
1079 }
1080
1081 void
setIntervalPattern(UCalendarDateFields field,const UnicodeString & intervalPattern)1082 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field,
1083 const UnicodeString& intervalPattern) {
1084 UBool order = fInfo->getDefaultOrder();
1085 setIntervalPattern(field, intervalPattern, order);
1086 }
1087
1088
1089 void
setIntervalPattern(UCalendarDateFields field,const UnicodeString & intervalPattern,UBool laterDateFirst)1090 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field,
1091 const UnicodeString& intervalPattern,
1092 UBool laterDateFirst) {
1093 const UnicodeString* pattern = &intervalPattern;
1094 UBool order = laterDateFirst;
1095 // check for "latestFirst:" or "earliestFirst:" prefix
1096 int8_t prefixLength = UPRV_LENGTHOF(gLaterFirstPrefix);
1097 int8_t earliestFirstLength = UPRV_LENGTHOF(gEarlierFirstPrefix);
1098 UnicodeString realPattern;
1099 if ( intervalPattern.startsWith(gLaterFirstPrefix, prefixLength) ) {
1100 order = true;
1101 intervalPattern.extract(prefixLength,
1102 intervalPattern.length() - prefixLength,
1103 realPattern);
1104 pattern = &realPattern;
1105 } else if ( intervalPattern.startsWith(gEarlierFirstPrefix,
1106 earliestFirstLength) ) {
1107 order = false;
1108 intervalPattern.extract(earliestFirstLength,
1109 intervalPattern.length() - earliestFirstLength,
1110 realPattern);
1111 pattern = &realPattern;
1112 }
1113
1114 int32_t splitPoint = splitPatternInto2Part(*pattern);
1115
1116 UnicodeString firstPart;
1117 UnicodeString secondPart;
1118 pattern->extract(0, splitPoint, firstPart);
1119 if ( splitPoint < pattern->length() ) {
1120 pattern->extract(splitPoint, pattern->length()-splitPoint, secondPart);
1121 }
1122 setPatternInfo(field, &firstPart, &secondPart, order);
1123 }
1124
1125
1126
1127
1128 /**
1129 * Generate interval pattern from existing resource
1130 *
1131 * It not only save the interval patterns,
1132 * but also return the extended skeleton and its best match skeleton.
1133 *
1134 * @param field largest different calendar field
1135 * @param skeleton skeleton
1136 * @param bestSkeleton the best match skeleton which has interval pattern
1137 * defined in resource
1138 * @param differenceInfo the difference between skeleton and best skeleton
1139 * 0 means the best matched skeleton is the same as input skeleton
1140 * 1 means the fields are the same, but field width are different
1141 * 2 means the only difference between fields are v/z,
1142 * -1 means there are other fields difference
1143 *
1144 * @param extendedSkeleton extended skeleton
1145 * @param extendedBestSkeleton extended best match skeleton
1146 * @return whether the interval pattern is found
1147 * through extending skeleton or not.
1148 * TRUE if interval pattern is found by
1149 * extending skeleton, FALSE otherwise.
1150 * @stable ICU 4.0
1151 */
1152 UBool
setIntervalPattern(UCalendarDateFields field,const UnicodeString * skeleton,const UnicodeString * bestSkeleton,int8_t differenceInfo,UnicodeString * extendedSkeleton,UnicodeString * extendedBestSkeleton)1153 DateIntervalFormat::setIntervalPattern(UCalendarDateFields field,
1154 const UnicodeString* skeleton,
1155 const UnicodeString* bestSkeleton,
1156 int8_t differenceInfo,
1157 UnicodeString* extendedSkeleton,
1158 UnicodeString* extendedBestSkeleton) {
1159 UErrorCode status = U_ZERO_ERROR;
1160 // following getIntervalPattern() should not generate error status
1161 UnicodeString pattern;
1162 fInfo->getIntervalPattern(*bestSkeleton, field, pattern, status);
1163 if ( pattern.isEmpty() ) {
1164 // single date
1165 if ( SimpleDateFormat::isFieldUnitIgnored(*bestSkeleton, field) ) {
1166 // do nothing, format will handle it
1167 return false;
1168 }
1169
1170 // for 24 hour system, interval patterns in resource file
1171 // might not include pattern when am_pm differ,
1172 // which should be the same as hour differ.
1173 // add it here for simplicity
1174 if ( field == UCAL_AM_PM ) {
1175 fInfo->getIntervalPattern(*bestSkeleton, UCAL_HOUR, pattern,status);
1176 if ( !pattern.isEmpty() ) {
1177 setIntervalPattern(field, pattern);
1178 }
1179 return false;
1180 }
1181 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
1182 // first, get best match pattern "MMMd",
1183 // since there is no pattern for 'y' differs for skeleton 'MMMd',
1184 // need to look for it from skeleton 'yMMMd',
1185 // if found, adjust field width in interval pattern from
1186 // "MMM" to "MMMM".
1187 UChar fieldLetter = fgCalendarFieldToPatternLetter[field];
1188 if ( extendedSkeleton ) {
1189 *extendedSkeleton = *skeleton;
1190 *extendedBestSkeleton = *bestSkeleton;
1191 extendedSkeleton->insert(0, fieldLetter);
1192 extendedBestSkeleton->insert(0, fieldLetter);
1193 // for example, looking for patterns when 'y' differ for
1194 // skeleton "MMMM".
1195 fInfo->getIntervalPattern(*extendedBestSkeleton,field,pattern,status);
1196 if ( pattern.isEmpty() && differenceInfo == 0 ) {
1197 // if there is no skeleton "yMMMM" defined,
1198 // look for the best match skeleton, for example: "yMMM"
1199 const UnicodeString* tmpBest = fInfo->getBestSkeleton(
1200 *extendedBestSkeleton, differenceInfo);
1201 if ( tmpBest != 0 && differenceInfo != -1 ) {
1202 fInfo->getIntervalPattern(*tmpBest, field, pattern, status);
1203 bestSkeleton = tmpBest;
1204 }
1205 }
1206 }
1207 }
1208 if ( !pattern.isEmpty() ) {
1209 if ( differenceInfo != 0 ) {
1210 UnicodeString adjustIntervalPattern;
1211 adjustFieldWidth(*skeleton, *bestSkeleton, pattern, differenceInfo,
1212 adjustIntervalPattern);
1213 setIntervalPattern(field, adjustIntervalPattern);
1214 } else {
1215 setIntervalPattern(field, pattern);
1216 }
1217 if ( extendedSkeleton && !extendedSkeleton->isEmpty() ) {
1218 return TRUE;
1219 }
1220 }
1221 return FALSE;
1222 }
1223
1224
1225
1226 int32_t U_EXPORT2
splitPatternInto2Part(const UnicodeString & intervalPattern)1227 DateIntervalFormat::splitPatternInto2Part(const UnicodeString& intervalPattern) {
1228 UBool inQuote = false;
1229 UChar prevCh = 0;
1230 int32_t count = 0;
1231
1232 /* repeatedPattern used to record whether a pattern has already seen.
1233 It is a pattern applies to first calendar if it is first time seen,
1234 otherwise, it is a pattern applies to the second calendar
1235 */
1236 UBool patternRepeated[] =
1237 {
1238 // A B C D E F G H I J K L M N O
1239 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1240 // P Q R S T U V W X Y Z
1241 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1242 // a b c d e f g h i j k l m n o
1243 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1244 // p q r s t u v w x y z
1245 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1246 };
1247
1248 int8_t PATTERN_CHAR_BASE = 0x41;
1249
1250 /* loop through the pattern string character by character looking for
1251 * the first repeated pattern letter, which breaks the interval pattern
1252 * into 2 parts.
1253 */
1254 int32_t i;
1255 UBool foundRepetition = false;
1256 for (i = 0; i < intervalPattern.length(); ++i) {
1257 UChar ch = intervalPattern.charAt(i);
1258
1259 if (ch != prevCh && count > 0) {
1260 // check the repeativeness of pattern letter
1261 UBool repeated = patternRepeated[(int)(prevCh - PATTERN_CHAR_BASE)];
1262 if ( repeated == FALSE ) {
1263 patternRepeated[prevCh - PATTERN_CHAR_BASE] = TRUE;
1264 } else {
1265 foundRepetition = true;
1266 break;
1267 }
1268 count = 0;
1269 }
1270 if (ch == 0x0027 /*'*/) {
1271 // Consecutive single quotes are a single quote literal,
1272 // either outside of quotes or between quotes
1273 if ((i+1) < intervalPattern.length() &&
1274 intervalPattern.charAt(i+1) == 0x0027 /*'*/) {
1275 ++i;
1276 } else {
1277 inQuote = ! inQuote;
1278 }
1279 }
1280 else if (!inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
1281 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
1282 // ch is a date-time pattern character
1283 prevCh = ch;
1284 ++count;
1285 }
1286 }
1287 // check last pattern char, distinguish
1288 // "dd MM" ( no repetition ),
1289 // "d-d"(last char repeated ), and
1290 // "d-d MM" ( repetition found )
1291 if ( count > 0 && foundRepetition == FALSE ) {
1292 if ( patternRepeated[(int)(prevCh - PATTERN_CHAR_BASE)] == FALSE ) {
1293 count = 0;
1294 }
1295 }
1296 return (i - count);
1297 }
1298
1299 static const UChar bracketedZero[] = {0x7B,0x30,0x7D};
1300 static const UChar bracketedOne[] = {0x7B,0x31,0x7D};
1301
1302 void
adjustPosition(UnicodeString & combiningPattern,UnicodeString & pat0,FieldPosition & pos0,UnicodeString & pat1,FieldPosition & pos1,FieldPosition & posResult)1303 DateIntervalFormat::adjustPosition(UnicodeString& combiningPattern, // has {0} and {1} in it
1304 UnicodeString& pat0, FieldPosition& pos0, // pattern and pos corresponding to {0}
1305 UnicodeString& pat1, FieldPosition& pos1, // pattern and pos corresponding to {1}
1306 FieldPosition& posResult) {
1307 int32_t index0 = combiningPattern.indexOf(bracketedZero, 3, 0);
1308 int32_t index1 = combiningPattern.indexOf(bracketedOne, 3, 0);
1309 if (index0 < 0 || index1 < 0) {
1310 return;
1311 }
1312 int32_t placeholderLen = 3; // length of "{0}" or "{1}"
1313 if (index0 < index1) {
1314 if (pos0.getEndIndex() > 0) {
1315 posResult.setBeginIndex(pos0.getBeginIndex() + index0);
1316 posResult.setEndIndex(pos0.getEndIndex() + index0);
1317 } else if (pos1.getEndIndex() > 0) {
1318 // here index1 >= 3
1319 index1 += pat0.length() - placeholderLen; // adjust for pat0 replacing {0}
1320 posResult.setBeginIndex(pos1.getBeginIndex() + index1);
1321 posResult.setEndIndex(pos1.getEndIndex() + index1);
1322 }
1323 } else {
1324 if (pos1.getEndIndex() > 0) {
1325 posResult.setBeginIndex(pos1.getBeginIndex() + index1);
1326 posResult.setEndIndex(pos1.getEndIndex() + index1);
1327 } else if (pos0.getEndIndex() > 0) {
1328 // here index0 >= 3
1329 index0 += pat1.length() - placeholderLen; // adjust for pat1 replacing {1}
1330 posResult.setBeginIndex(pos0.getBeginIndex() + index0);
1331 posResult.setEndIndex(pos0.getEndIndex() + index0);
1332 }
1333 }
1334 }
1335
1336 UnicodeString&
fallbackFormat(Calendar & fromCalendar,Calendar & toCalendar,UBool fromToOnSameDay,UnicodeString & appendTo,FieldPosition & pos,UErrorCode & status) const1337 DateIntervalFormat::fallbackFormat(Calendar& fromCalendar,
1338 Calendar& toCalendar,
1339 UBool fromToOnSameDay, // new
1340 UnicodeString& appendTo,
1341 FieldPosition& pos,
1342 UErrorCode& status) const {
1343 if ( U_FAILURE(status) ) {
1344 return appendTo;
1345 }
1346 UnicodeString fullPattern; // for saving the pattern in fDateFormat
1347 UBool formatDatePlusTimeRange = (fromToOnSameDay && fDatePattern && fTimePattern);
1348 // the fall back
1349 if (formatDatePlusTimeRange) {
1350 fDateFormat->toPattern(fullPattern); // save current pattern, restore later
1351 fDateFormat->applyPattern(*fTimePattern);
1352 }
1353 FieldPosition otherPos;
1354 otherPos.setField(pos.getField());
1355 UnicodeString earlierDate;
1356 fDateFormat->format(fromCalendar, earlierDate, pos);
1357 UnicodeString laterDate;
1358 fDateFormat->format(toCalendar, laterDate, otherPos);
1359 UnicodeString fallbackPattern;
1360 fInfo->getFallbackIntervalPattern(fallbackPattern);
1361 adjustPosition(fallbackPattern, earlierDate, pos, laterDate, otherPos, pos);
1362 UnicodeString fallbackRange;
1363 SimpleFormatter(fallbackPattern, 2, 2, status).
1364 format(earlierDate, laterDate, fallbackRange, status);
1365 if ( U_SUCCESS(status) && formatDatePlusTimeRange ) {
1366 // fallbackRange has just the time range, need to format the date part and combine that
1367 fDateFormat->applyPattern(*fDatePattern);
1368 UnicodeString datePortion;
1369 otherPos.setBeginIndex(0);
1370 otherPos.setEndIndex(0);
1371 fDateFormat->format(fromCalendar, datePortion, otherPos);
1372 adjustPosition(*fDateTimeFormat, fallbackRange, pos, datePortion, otherPos, pos);
1373 const UnicodeString *values[2] = {
1374 &fallbackRange, // {0} is time range
1375 &datePortion, // {1} is single date portion
1376 };
1377 SimpleFormatter(*fDateTimeFormat, 2, 2, status).
1378 formatAndReplace(values, 2, fallbackRange, NULL, 0, status);
1379 }
1380 if ( U_SUCCESS(status) ) {
1381 appendTo.append(fallbackRange);
1382 }
1383 if (formatDatePlusTimeRange) {
1384 // restore full pattern
1385 fDateFormat->applyPattern(fullPattern);
1386 }
1387 return appendTo;
1388 }
1389
1390
1391
1392
1393 UBool U_EXPORT2
fieldExistsInSkeleton(UCalendarDateFields field,const UnicodeString & skeleton)1394 DateIntervalFormat::fieldExistsInSkeleton(UCalendarDateFields field,
1395 const UnicodeString& skeleton)
1396 {
1397 const UChar fieldChar = fgCalendarFieldToPatternLetter[field];
1398 return ( (skeleton.indexOf(fieldChar) == -1)?FALSE:TRUE ) ;
1399 }
1400
1401
1402
1403 void U_EXPORT2
adjustFieldWidth(const UnicodeString & inputSkeleton,const UnicodeString & bestMatchSkeleton,const UnicodeString & bestIntervalPattern,int8_t differenceInfo,UnicodeString & adjustedPtn)1404 DateIntervalFormat::adjustFieldWidth(const UnicodeString& inputSkeleton,
1405 const UnicodeString& bestMatchSkeleton,
1406 const UnicodeString& bestIntervalPattern,
1407 int8_t differenceInfo,
1408 UnicodeString& adjustedPtn) {
1409 adjustedPtn = bestIntervalPattern;
1410 int32_t inputSkeletonFieldWidth[] =
1411 {
1412 // A B C D E F G H I J K L M N O
1413 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1414 // P Q R S T U V W X Y Z
1415 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1416 // a b c d e f g h i j k l m n o
1417 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1418 // p q r s t u v w x y z
1419 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1420 };
1421
1422 int32_t bestMatchSkeletonFieldWidth[] =
1423 {
1424 // A B C D E F G H I J K L M N O
1425 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1426 // P Q R S T U V W X Y Z
1427 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1428 // a b c d e f g h i j k l m n o
1429 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1430 // p q r s t u v w x y z
1431 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
1432 };
1433
1434 DateIntervalInfo::parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
1435 DateIntervalInfo::parseSkeleton(bestMatchSkeleton, bestMatchSkeletonFieldWidth);
1436 if ( differenceInfo == 2 ) {
1437 adjustedPtn.findAndReplace(UnicodeString((UChar)0x76 /* v */),
1438 UnicodeString((UChar)0x7a /* z */));
1439 }
1440
1441 UBool inQuote = false;
1442 UChar prevCh = 0;
1443 int32_t count = 0;
1444
1445 const int8_t PATTERN_CHAR_BASE = 0x41;
1446
1447 // loop through the pattern string character by character
1448 int32_t adjustedPtnLength = adjustedPtn.length();
1449 int32_t i;
1450 for (i = 0; i < adjustedPtnLength; ++i) {
1451 UChar ch = adjustedPtn.charAt(i);
1452 if (ch != prevCh && count > 0) {
1453 // check the repeativeness of pattern letter
1454 UChar skeletonChar = prevCh;
1455 if ( skeletonChar == CAP_L ) {
1456 // there is no "L" (always be "M") in skeleton,
1457 // but there is "L" in pattern.
1458 // for skeleton "M+", the pattern might be "...L..."
1459 skeletonChar = CAP_M;
1460 }
1461 int32_t fieldCount = bestMatchSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
1462 int32_t inputFieldCount = inputSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
1463 if ( fieldCount == count && inputFieldCount > fieldCount ) {
1464 count = inputFieldCount - fieldCount;
1465 int32_t j;
1466 for ( j = 0; j < count; ++j ) {
1467 adjustedPtn.insert(i, prevCh);
1468 }
1469 i += count;
1470 adjustedPtnLength += count;
1471 }
1472 count = 0;
1473 }
1474 if (ch == 0x0027 /*'*/) {
1475 // Consecutive single quotes are a single quote literal,
1476 // either outside of quotes or between quotes
1477 if ((i+1) < adjustedPtn.length() && adjustedPtn.charAt(i+1) == 0x0027 /* ' */) {
1478 ++i;
1479 } else {
1480 inQuote = ! inQuote;
1481 }
1482 }
1483 else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
1484 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
1485 // ch is a date-time pattern character
1486 prevCh = ch;
1487 ++count;
1488 }
1489 }
1490 if ( count > 0 ) {
1491 // last item
1492 // check the repeativeness of pattern letter
1493 UChar skeletonChar = prevCh;
1494 if ( skeletonChar == CAP_L ) {
1495 // there is no "L" (always be "M") in skeleton,
1496 // but there is "L" in pattern.
1497 // for skeleton "M+", the pattern might be "...L..."
1498 skeletonChar = CAP_M;
1499 }
1500 int32_t fieldCount = bestMatchSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
1501 int32_t inputFieldCount = inputSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
1502 if ( fieldCount == count && inputFieldCount > fieldCount ) {
1503 count = inputFieldCount - fieldCount;
1504 int32_t j;
1505 for ( j = 0; j < count; ++j ) {
1506 adjustedPtn.append(prevCh);
1507 }
1508 }
1509 }
1510 }
1511
1512
1513
1514 void
concatSingleDate2TimeInterval(UnicodeString & format,const UnicodeString & datePattern,UCalendarDateFields field,UErrorCode & status)1515 DateIntervalFormat::concatSingleDate2TimeInterval(UnicodeString& format,
1516 const UnicodeString& datePattern,
1517 UCalendarDateFields field,
1518 UErrorCode& status) {
1519 // following should not set wrong status
1520 int32_t itvPtnIndex = DateIntervalInfo::calendarFieldToIntervalIndex(field,
1521 status);
1522 if ( U_FAILURE(status) ) {
1523 return;
1524 }
1525 PatternInfo& timeItvPtnInfo = fIntervalPatterns[itvPtnIndex];
1526 if ( !timeItvPtnInfo.firstPart.isEmpty() ) {
1527 UnicodeString timeIntervalPattern(timeItvPtnInfo.firstPart);
1528 timeIntervalPattern.append(timeItvPtnInfo.secondPart);
1529 UnicodeString combinedPattern;
1530 SimpleFormatter(format, 2, 2, status).
1531 format(timeIntervalPattern, datePattern, combinedPattern, status);
1532 if ( U_FAILURE(status) ) {
1533 return;
1534 }
1535 setIntervalPattern(field, combinedPattern, timeItvPtnInfo.laterDateFirst);
1536 }
1537 // else: fall back
1538 // it should not happen if the interval format defined is valid
1539 }
1540
1541
1542
1543 const UChar
1544 DateIntervalFormat::fgCalendarFieldToPatternLetter[] =
1545 {
1546 /*GyM*/ CAP_G, LOW_Y, CAP_M,
1547 /*wWd*/ LOW_W, CAP_W, LOW_D,
1548 /*DEF*/ CAP_D, CAP_E, CAP_F,
1549 /*ahH*/ LOW_A, LOW_H, CAP_H,
1550 /*msS*/ LOW_M, LOW_S, CAP_S, // MINUTE, SECOND, MILLISECOND
1551 /*z.Y*/ LOW_Z, SPACE, CAP_Y, // ZONE_OFFSET, DST_OFFSET, YEAR_WOY,
1552 /*eug*/ LOW_E, LOW_U, LOW_G, // DOW_LOCAL, EXTENDED_YEAR, JULIAN_DAY,
1553 /*A..*/ CAP_A, SPACE, SPACE, // MILLISECONDS_IN_DAY, IS_LEAP_MONTH, FIELD_COUNT
1554 };
1555
1556
1557 U_NAMESPACE_END
1558
1559 #endif
1560