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