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