1 /* 2 * Copyright 2003-2009, Axel Dörfler, axeld@pinc-software.de. 3 * Copyright 2009-2010, Adrien Destugues, pulkomandy@gmail.com. 4 * Copyright 2010-2011, Oliver Tappe <zooey@hirschkaefer.de>. 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 #include <unicode/uversion.h> 10 #include <FormattingConventions.h> 11 12 #include <AutoDeleter.h> 13 #include <IconUtils.h> 14 #include <List.h> 15 #include <Language.h> 16 #include <Locale.h> 17 #include <LocaleRoster.h> 18 #include <Resources.h> 19 #include <String.h> 20 #include <UnicodeChar.h> 21 22 #include <unicode/datefmt.h> 23 #include <unicode/locid.h> 24 #include <unicode/smpdtfmt.h> 25 #include <unicode/ulocdata.h> 26 #include <ICUWrapper.h> 27 28 #include <iostream> 29 #include <map> 30 #include <monetary.h> 31 #include <new> 32 #include <stdarg.h> 33 #include <stdlib.h> 34 35 36 U_NAMESPACE_USE 37 38 39 // #pragma mark - helpers 40 41 42 static bool 43 FormatUsesAmPm(const BString& format) 44 { 45 if (format.Length() == 0) 46 return false; 47 48 bool inQuote = false; 49 for (const char* s = format.String(); *s != '\0'; ++s) { 50 switch (*s) { 51 case '\'': 52 inQuote = !inQuote; 53 break; 54 case 'a': 55 if (!inQuote) 56 return true; 57 break; 58 } 59 } 60 61 return false; 62 } 63 64 65 static void 66 CoerceFormatTo12HourClock(BString& format) 67 { 68 char* s = format.LockBuffer(format.Length()); 69 if (s == NULL) 70 return; 71 72 // change format to use h instead of H, k instead of K, and append an 73 // am/pm marker 74 bool inQuote = false; 75 for (; *s != '\0'; ++s) { 76 switch (*s) { 77 case '\'': 78 inQuote = !inQuote; 79 break; 80 case 'H': 81 if (!inQuote) 82 *s = 'h'; 83 break; 84 case 'K': 85 if (!inQuote) 86 *s = 'k'; 87 break; 88 } 89 } 90 format.UnlockBuffer(format.Length()); 91 92 format.Append(" a"); 93 } 94 95 96 static void 97 CoerceFormatTo24HourClock(BString& format) 98 { 99 char* buffer = format.LockBuffer(format.Length()); 100 char* currentPos = buffer; 101 if (currentPos == NULL) 102 return; 103 104 // change the format to use H instead of h, K instead of k, and determine 105 // and remove the am/pm marker (including leading whitespace) 106 bool inQuote = false; 107 bool lastWasWhitespace = false; 108 uint32 ch; 109 const char* amPmStartPos = NULL; 110 const char* amPmEndPos = NULL; 111 const char* lastWhitespaceStart = NULL; 112 for (char* previousPos = currentPos; (ch = BUnicodeChar::FromUTF8( 113 (const char**)¤tPos)) != 0; previousPos = currentPos) { 114 switch (ch) { 115 case '\'': 116 inQuote = !inQuote; 117 break; 118 case 'h': 119 if (!inQuote) 120 *previousPos = 'H'; 121 break; 122 case 'k': 123 if (!inQuote) 124 *previousPos = 'K'; 125 break; 126 case 'a': 127 if (!inQuote) { 128 if (lastWasWhitespace) 129 amPmStartPos = lastWhitespaceStart; 130 else 131 amPmStartPos = previousPos; 132 amPmEndPos = currentPos; 133 } 134 break; 135 default: 136 if (!inQuote && BUnicodeChar::IsWhitespace(ch)) { 137 if (!lastWasWhitespace) { 138 lastWhitespaceStart = previousPos; 139 lastWasWhitespace = true; 140 } 141 continue; 142 } 143 } 144 lastWasWhitespace = false; 145 } 146 147 format.UnlockBuffer(format.Length()); 148 if (amPmStartPos != NULL && amPmEndPos > amPmStartPos) 149 format.Remove(amPmStartPos - buffer, amPmEndPos - amPmStartPos); 150 } 151 152 153 static void 154 CoerceFormatToAbbreviatedTimezone(BString& format) 155 { 156 char* s = format.LockBuffer(format.Length()); 157 if (s == NULL) 158 return; 159 160 // replace a single 'z' with 'V' 161 bool inQuote = false; 162 bool lastWasZ = false; 163 for (; *s != '\0'; ++s) { 164 switch (*s) { 165 case '\'': 166 inQuote = !inQuote; 167 break; 168 case 'z': 169 if (!inQuote && !lastWasZ && *(s+1) != 'z') 170 *s = 'V'; 171 lastWasZ = true; 172 continue; 173 } 174 lastWasZ = false; 175 } 176 format.UnlockBuffer(format.Length()); 177 } 178 179 180 // #pragma mark - BFormattingConventions 181 182 183 enum ClockHoursState { 184 CLOCK_HOURS_UNSET = 0, 185 CLOCK_HOURS_24, 186 CLOCK_HOURS_12 187 }; 188 189 190 BFormattingConventions::BFormattingConventions(const char* id) 191 : 192 fCachedUse24HourClock(CLOCK_HOURS_UNSET), 193 fExplicitUse24HourClock(CLOCK_HOURS_UNSET), 194 fUseStringsFromPreferredLanguage(false), 195 fICULocale(new icu::Locale(id)) 196 { 197 } 198 199 200 BFormattingConventions::BFormattingConventions( 201 const BFormattingConventions& other) 202 : 203 fCachedNumericFormat(other.fCachedNumericFormat), 204 fCachedMonetaryFormat(other.fCachedMonetaryFormat), 205 fCachedUse24HourClock(other.fCachedUse24HourClock), 206 fExplicitNumericFormat(other.fExplicitNumericFormat), 207 fExplicitMonetaryFormat(other.fExplicitMonetaryFormat), 208 fExplicitUse24HourClock(other.fExplicitUse24HourClock), 209 fUseStringsFromPreferredLanguage(other.fUseStringsFromPreferredLanguage), 210 fICULocale(new icu::Locale(*other.fICULocale)) 211 { 212 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) { 213 fCachedDateFormats[s] = other.fCachedDateFormats[s]; 214 fExplicitDateFormats[s] = other.fExplicitDateFormats[s]; 215 216 for (int t = 0; t < B_TIME_FORMAT_STYLE_COUNT; ++t) { 217 fCachedDateTimeFormats[s][t] = other.fCachedDateFormats[s][t]; 218 fExplicitDateTimeFormats[s][t] = other.fExplicitDateFormats[s][t]; 219 } 220 } 221 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) { 222 fCachedTimeFormats[s] = other.fCachedTimeFormats[s]; 223 fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s]; 224 } 225 } 226 227 228 BFormattingConventions::BFormattingConventions(const BMessage* archive) 229 : 230 fCachedUse24HourClock(CLOCK_HOURS_UNSET), 231 fExplicitUse24HourClock(CLOCK_HOURS_UNSET), 232 fUseStringsFromPreferredLanguage(false) 233 { 234 BString conventionsID; 235 status_t status = archive->FindString("conventions", &conventionsID); 236 fICULocale = new icu::Locale(conventionsID); 237 238 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT && status == B_OK; ++s) { 239 BString format; 240 status = archive->FindString("dateFormat", s, &format); 241 if (status == B_OK) 242 fExplicitDateFormats[s] = format; 243 244 status = archive->FindString("timeFormat", s, &format); 245 if (status == B_OK) 246 fExplicitTimeFormats[s] = format; 247 } 248 249 if (status == B_OK) { 250 int8 use24HourClock; 251 status = archive->FindInt8("use24HourClock", &use24HourClock); 252 if (status == B_OK) 253 fExplicitUse24HourClock = use24HourClock; 254 } 255 if (status == B_OK) { 256 bool useStringsFromPreferredLanguage; 257 status = archive->FindBool("useStringsFromPreferredLanguage", 258 &useStringsFromPreferredLanguage); 259 if (status == B_OK) 260 fUseStringsFromPreferredLanguage = useStringsFromPreferredLanguage; 261 } 262 } 263 264 265 BFormattingConventions& 266 BFormattingConventions::operator=(const BFormattingConventions& other) 267 { 268 if (this == &other) 269 return *this; 270 271 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) { 272 fCachedDateFormats[s] = other.fCachedDateFormats[s]; 273 fExplicitDateFormats[s] = other.fExplicitDateFormats[s]; 274 for (int t = 0; t < B_TIME_FORMAT_STYLE_COUNT; ++t) { 275 fCachedDateTimeFormats[s][t] = other.fCachedDateTimeFormats[s][t]; 276 fExplicitDateTimeFormats[s][t] 277 = other.fExplicitDateTimeFormats[s][t]; 278 } 279 } 280 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) { 281 fCachedTimeFormats[s] = other.fCachedTimeFormats[s]; 282 fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s]; 283 } 284 fCachedNumericFormat = other.fCachedNumericFormat; 285 fCachedMonetaryFormat = other.fCachedMonetaryFormat; 286 fCachedUse24HourClock = other.fCachedUse24HourClock; 287 288 fExplicitNumericFormat = other.fExplicitNumericFormat; 289 fExplicitMonetaryFormat = other.fExplicitMonetaryFormat; 290 fExplicitUse24HourClock = other.fExplicitUse24HourClock; 291 292 fUseStringsFromPreferredLanguage = other.fUseStringsFromPreferredLanguage; 293 294 *fICULocale = *other.fICULocale; 295 296 return *this; 297 } 298 299 300 BFormattingConventions::~BFormattingConventions() 301 { 302 delete fICULocale; 303 } 304 305 306 bool 307 BFormattingConventions::operator==(const BFormattingConventions& other) const 308 { 309 if (this == &other) 310 return true; 311 312 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) { 313 if (fExplicitDateFormats[s] != other.fExplicitDateFormats[s]) 314 return false; 315 } 316 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) { 317 if (fExplicitTimeFormats[s] != other.fExplicitTimeFormats[s]) 318 return false; 319 } 320 321 return fExplicitNumericFormat == other.fExplicitNumericFormat 322 && fExplicitMonetaryFormat == other.fExplicitMonetaryFormat 323 && fExplicitUse24HourClock == other.fExplicitUse24HourClock 324 && fUseStringsFromPreferredLanguage 325 == other.fUseStringsFromPreferredLanguage 326 && *fICULocale == *other.fICULocale; 327 } 328 329 330 bool 331 BFormattingConventions::operator!=(const BFormattingConventions& other) const 332 { 333 return !(*this == other); 334 } 335 336 337 const char* 338 BFormattingConventions::ID() const 339 { 340 return fICULocale->getName(); 341 } 342 343 344 const char* 345 BFormattingConventions::LanguageCode() const 346 { 347 return fICULocale->getLanguage(); 348 } 349 350 351 const char* 352 BFormattingConventions::CountryCode() const 353 { 354 const char* country = fICULocale->getCountry(); 355 if (country == NULL || country[0] == '\0') 356 return NULL; 357 358 return country; 359 } 360 361 362 bool 363 BFormattingConventions::AreCountrySpecific() const 364 { 365 return CountryCode() != NULL; 366 } 367 368 369 status_t 370 BFormattingConventions::GetNativeName(BString& name) const 371 { 372 UnicodeString string; 373 fICULocale->getDisplayName(*fICULocale, string); 374 string.toTitle(NULL, *fICULocale); 375 376 name.Truncate(0); 377 BStringByteSink converter(&name); 378 string.toUTF8(converter); 379 380 return B_OK; 381 } 382 383 384 status_t 385 BFormattingConventions::GetName(BString& name, 386 const BLanguage* displayLanguage) const 387 { 388 BString displayLanguageID; 389 if (displayLanguage == NULL) { 390 BLanguage defaultLanguage; 391 BLocale::Default()->GetLanguage(&defaultLanguage); 392 displayLanguageID = defaultLanguage.Code(); 393 } else { 394 displayLanguageID = displayLanguage->Code(); 395 } 396 397 UnicodeString uString; 398 fICULocale->getDisplayName(Locale(displayLanguageID.String()), uString); 399 name.Truncate(0); 400 BStringByteSink stringConverter(&name); 401 uString.toUTF8(stringConverter); 402 403 return B_OK; 404 } 405 406 407 BMeasurementKind 408 BFormattingConventions::MeasurementKind() const 409 { 410 UErrorCode error = U_ZERO_ERROR; 411 switch (ulocdata_getMeasurementSystem(ID(), &error)) { 412 case UMS_US: 413 return B_US; 414 case UMS_SI: 415 default: 416 return B_METRIC; 417 } 418 } 419 420 421 status_t 422 BFormattingConventions::GetDateFormat(BDateFormatStyle style, 423 BString& outFormat) const 424 { 425 if (style < 0 || style >= B_DATE_FORMAT_STYLE_COUNT) 426 return B_BAD_VALUE; 427 428 outFormat = fExplicitDateFormats[style].Length() 429 ? fExplicitDateFormats[style] 430 : fCachedDateFormats[style]; 431 432 if (outFormat.Length() > 0) 433 return B_OK; 434 435 ObjectDeleter<DateFormat> dateFormatter( 436 DateFormat::createDateInstance((DateFormat::EStyle)style, *fICULocale)); 437 if (!dateFormatter.IsSet()) 438 return B_NO_MEMORY; 439 440 SimpleDateFormat* dateFormatterImpl 441 = static_cast<SimpleDateFormat*>(dateFormatter.Get()); 442 443 UnicodeString icuString; 444 dateFormatterImpl->toPattern(icuString); 445 BStringByteSink stringConverter(&outFormat); 446 icuString.toUTF8(stringConverter); 447 448 fCachedDateFormats[style] = outFormat; 449 450 return B_OK; 451 } 452 453 454 status_t 455 BFormattingConventions::GetTimeFormat(BTimeFormatStyle style, 456 BString& outFormat) const 457 { 458 if (style < 0 || style >= B_TIME_FORMAT_STYLE_COUNT) 459 return B_BAD_VALUE; 460 461 outFormat = fExplicitTimeFormats[style].Length() 462 ? fExplicitTimeFormats[style] 463 : fCachedTimeFormats[style]; 464 465 if (outFormat.Length() > 0) 466 return B_OK; 467 468 ObjectDeleter<DateFormat> timeFormatter( 469 DateFormat::createTimeInstance((DateFormat::EStyle)style, *fICULocale)); 470 if (!timeFormatter.IsSet()) 471 return B_NO_MEMORY; 472 473 SimpleDateFormat* timeFormatterImpl 474 = static_cast<SimpleDateFormat*>(timeFormatter.Get()); 475 476 UnicodeString icuString; 477 timeFormatterImpl->toPattern(icuString); 478 BStringByteSink stringConverter(&outFormat); 479 icuString.toUTF8(stringConverter); 480 481 CoerceFormatForClock(outFormat); 482 483 if (style != B_FULL_TIME_FORMAT) { 484 // use abbreviated timezone in short timezone format 485 CoerceFormatToAbbreviatedTimezone(outFormat); 486 } 487 488 fCachedTimeFormats[style] = outFormat; 489 490 return B_OK; 491 } 492 493 494 status_t 495 BFormattingConventions::GetDateTimeFormat(BDateFormatStyle dateStyle, 496 BTimeFormatStyle timeStyle, BString& outFormat) const 497 { 498 if (dateStyle < 0 || dateStyle >= B_DATE_FORMAT_STYLE_COUNT) 499 return B_BAD_VALUE; 500 501 if (timeStyle < 0 || timeStyle >= B_TIME_FORMAT_STYLE_COUNT) 502 return B_BAD_VALUE; 503 504 outFormat = fExplicitDateTimeFormats[dateStyle][timeStyle].Length() 505 ? fExplicitDateTimeFormats[dateStyle][timeStyle] 506 : fCachedDateTimeFormats[dateStyle][timeStyle]; 507 508 if (outFormat.Length() > 0) 509 return B_OK; 510 511 ObjectDeleter<DateFormat> dateFormatter( 512 DateFormat::createDateTimeInstance((DateFormat::EStyle)dateStyle, 513 (DateFormat::EStyle)timeStyle, *fICULocale)); 514 if (!dateFormatter.IsSet()) 515 return B_NO_MEMORY; 516 517 SimpleDateFormat* dateFormatterImpl 518 = static_cast<SimpleDateFormat*>(dateFormatter.Get()); 519 520 UnicodeString icuString; 521 dateFormatterImpl->toPattern(icuString); 522 BStringByteSink stringConverter(&outFormat); 523 icuString.toUTF8(stringConverter); 524 525 CoerceFormatForClock(outFormat); 526 527 if (dateStyle != B_FULL_DATE_FORMAT) { 528 // use abbreviated timezone in short timezone format 529 CoerceFormatToAbbreviatedTimezone(outFormat); 530 } 531 532 fCachedDateTimeFormats[dateStyle][timeStyle] = outFormat; 533 534 return B_OK; 535 } 536 537 538 status_t 539 BFormattingConventions::GetNumericFormat(BString& outFormat) const 540 { 541 // TODO! 542 return B_UNSUPPORTED; 543 } 544 545 546 status_t 547 BFormattingConventions::GetMonetaryFormat(BString& outFormat) const 548 { 549 // TODO! 550 return B_UNSUPPORTED; 551 } 552 553 554 void 555 BFormattingConventions::SetExplicitDateFormat(BDateFormatStyle style, 556 const BString& format) 557 { 558 fExplicitDateFormats[style] = format; 559 } 560 561 562 void 563 BFormattingConventions::SetExplicitTimeFormat(BTimeFormatStyle style, 564 const BString& format) 565 { 566 fExplicitTimeFormats[style] = format; 567 } 568 569 570 void 571 BFormattingConventions::SetExplicitDateTimeFormat(BDateFormatStyle dateStyle, 572 BTimeFormatStyle timeStyle, const BString& format) 573 { 574 fExplicitDateTimeFormats[dateStyle][timeStyle] = format; 575 } 576 577 578 void 579 BFormattingConventions::SetExplicitNumericFormat(const BString& format) 580 { 581 fExplicitNumericFormat = format; 582 } 583 584 585 void 586 BFormattingConventions::SetExplicitMonetaryFormat(const BString& format) 587 { 588 fExplicitMonetaryFormat = format; 589 } 590 591 592 bool 593 BFormattingConventions::UseStringsFromPreferredLanguage() const 594 { 595 return fUseStringsFromPreferredLanguage; 596 } 597 598 599 void 600 BFormattingConventions::SetUseStringsFromPreferredLanguage(bool value) 601 { 602 fUseStringsFromPreferredLanguage = value; 603 } 604 605 606 bool 607 BFormattingConventions::Use24HourClock() const 608 { 609 int8 use24HourClock = fExplicitUse24HourClock != CLOCK_HOURS_UNSET 610 ? fExplicitUse24HourClock : fCachedUse24HourClock; 611 612 if (use24HourClock == CLOCK_HOURS_UNSET) { 613 BString format; 614 GetTimeFormat(B_MEDIUM_TIME_FORMAT, format); 615 fCachedUse24HourClock 616 = FormatUsesAmPm(format) ? CLOCK_HOURS_12 : CLOCK_HOURS_24; 617 return fCachedUse24HourClock == CLOCK_HOURS_24; 618 } 619 620 return fExplicitUse24HourClock == CLOCK_HOURS_24; 621 } 622 623 624 void 625 BFormattingConventions::SetExplicitUse24HourClock(bool value) 626 { 627 int8 newUse24HourClock = value ? CLOCK_HOURS_24 : CLOCK_HOURS_12; 628 if (fExplicitUse24HourClock == newUse24HourClock) 629 return; 630 631 fExplicitUse24HourClock = newUse24HourClock; 632 633 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) 634 fCachedTimeFormats[s].Truncate(0); 635 } 636 637 638 void 639 BFormattingConventions::UnsetExplicitUse24HourClock() 640 { 641 fExplicitUse24HourClock = CLOCK_HOURS_UNSET; 642 643 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) 644 fCachedTimeFormats[s].Truncate(0); 645 } 646 647 648 status_t 649 BFormattingConventions::Archive(BMessage* archive, bool deep) const 650 { 651 status_t status = archive->AddString("conventions", fICULocale->getName()); 652 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT && status == B_OK; ++s) { 653 status = archive->AddString("dateFormat", fExplicitDateFormats[s]); 654 if (status == B_OK) 655 status = archive->AddString("timeFormat", fExplicitTimeFormats[s]); 656 } 657 if (status == B_OK) 658 status = archive->AddInt8("use24HourClock", fExplicitUse24HourClock); 659 if (status == B_OK) { 660 status = archive->AddBool("useStringsFromPreferredLanguage", 661 fUseStringsFromPreferredLanguage); 662 } 663 664 return status; 665 } 666 667 668 void 669 BFormattingConventions::CoerceFormatForClock(BString& outFormat) const 670 { 671 int8 use24HourClock = fExplicitUse24HourClock != CLOCK_HOURS_UNSET 672 ? fExplicitUse24HourClock : fCachedUse24HourClock; 673 if (use24HourClock != CLOCK_HOURS_UNSET) { 674 // adjust to 12/24-hour clock as requested 675 bool localeUses24HourClock = !FormatUsesAmPm(outFormat); 676 if (localeUses24HourClock) { 677 if (use24HourClock == CLOCK_HOURS_12) 678 CoerceFormatTo12HourClock(outFormat); 679 } else { 680 if (use24HourClock == CLOCK_HOURS_24) 681 CoerceFormatTo24HourClock(outFormat); 682 } 683 } 684 } 685 686