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