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