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