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