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