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