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 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) 211 fCachedTimeFormats[s] = other.fCachedTimeFormats[s]; 212 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) 213 fExplicitDateFormats[s] = other.fExplicitDateFormats[s]; 214 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) 215 fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s]; 216 } 217 218 219 BFormattingConventions::BFormattingConventions(const BMessage* archive) 220 : 221 fCachedUse24HourClock(CLOCK_HOURS_UNSET), 222 fExplicitUse24HourClock(CLOCK_HOURS_UNSET), 223 fUseStringsFromPreferredLanguage(false) 224 { 225 BString conventionsID; 226 status_t status = archive->FindString("conventions", &conventionsID); 227 fICULocale = new icu::Locale(conventionsID); 228 229 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT && status == B_OK; ++s) { 230 BString format; 231 status = archive->FindString("dateFormat", s, &format); 232 if (status == B_OK) 233 fExplicitDateFormats[s] = format; 234 235 status = archive->FindString("timeFormat", s, &format); 236 if (status == B_OK) 237 fExplicitTimeFormats[s] = format; 238 } 239 240 if (status == B_OK) { 241 int8 use24HourClock; 242 status = archive->FindInt8("use24HourClock", &use24HourClock); 243 if (status == B_OK) 244 fExplicitUse24HourClock = use24HourClock; 245 } 246 if (status == B_OK) { 247 bool useStringsFromPreferredLanguage; 248 status = archive->FindBool("useStringsFromPreferredLanguage", 249 &useStringsFromPreferredLanguage); 250 if (status == B_OK) 251 fUseStringsFromPreferredLanguage = useStringsFromPreferredLanguage; 252 } 253 } 254 255 256 BFormattingConventions& 257 BFormattingConventions::operator=(const BFormattingConventions& other) 258 { 259 if (this == &other) 260 return *this; 261 262 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) 263 fCachedDateFormats[s] = other.fCachedDateFormats[s]; 264 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) 265 fCachedTimeFormats[s] = other.fCachedTimeFormats[s]; 266 fCachedNumericFormat = other.fCachedNumericFormat; 267 fCachedMonetaryFormat = other.fCachedMonetaryFormat; 268 fCachedUse24HourClock = other.fCachedUse24HourClock; 269 270 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) 271 fExplicitDateFormats[s] = other.fExplicitDateFormats[s]; 272 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) 273 fExplicitTimeFormats[s] = other.fExplicitTimeFormats[s]; 274 fExplicitNumericFormat = other.fExplicitNumericFormat; 275 fExplicitMonetaryFormat = other.fExplicitMonetaryFormat; 276 fExplicitUse24HourClock = other.fExplicitUse24HourClock; 277 278 fUseStringsFromPreferredLanguage = other.fUseStringsFromPreferredLanguage; 279 280 *fICULocale = *other.fICULocale; 281 282 return *this; 283 } 284 285 286 BFormattingConventions::~BFormattingConventions() 287 { 288 delete fICULocale; 289 } 290 291 292 bool 293 BFormattingConventions::operator==(const BFormattingConventions& other) const 294 { 295 if (this == &other) 296 return true; 297 298 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT; ++s) { 299 if (fExplicitDateFormats[s] != other.fExplicitDateFormats[s]) 300 return false; 301 } 302 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) { 303 if (fExplicitTimeFormats[s] != other.fExplicitTimeFormats[s]) 304 return false; 305 } 306 307 return fExplicitNumericFormat == other.fExplicitNumericFormat 308 && fExplicitMonetaryFormat == other.fExplicitMonetaryFormat 309 && fExplicitUse24HourClock == other.fExplicitUse24HourClock 310 && fUseStringsFromPreferredLanguage 311 == other.fUseStringsFromPreferredLanguage 312 && *fICULocale == *other.fICULocale; 313 } 314 315 316 bool 317 BFormattingConventions::operator!=(const BFormattingConventions& other) const 318 { 319 return !(*this == other); 320 } 321 322 323 const char* 324 BFormattingConventions::ID() const 325 { 326 return fICULocale->getName(); 327 } 328 329 330 const char* 331 BFormattingConventions::LanguageCode() const 332 { 333 return fICULocale->getLanguage(); 334 } 335 336 337 const char* 338 BFormattingConventions::CountryCode() const 339 { 340 const char* country = fICULocale->getCountry(); 341 if (country == NULL || country[0] == '\0') 342 return NULL; 343 344 return country; 345 } 346 347 348 bool 349 BFormattingConventions::AreCountrySpecific() const 350 { 351 return CountryCode() != NULL; 352 } 353 354 355 status_t 356 BFormattingConventions::GetNativeName(BString& name) const 357 { 358 UnicodeString string; 359 fICULocale->getDisplayName(*fICULocale, string); 360 string.toTitle(NULL, *fICULocale); 361 362 name.Truncate(0); 363 BStringByteSink converter(&name); 364 string.toUTF8(converter); 365 366 return B_OK; 367 } 368 369 370 status_t 371 BFormattingConventions::GetName(BString& name, 372 const BLanguage* displayLanguage) const 373 { 374 BString displayLanguageID; 375 if (displayLanguage == NULL) { 376 BLanguage defaultLanguage; 377 BLocale::Default()->GetLanguage(&defaultLanguage); 378 displayLanguageID = defaultLanguage.Code(); 379 } else { 380 displayLanguageID = displayLanguage->Code(); 381 } 382 383 UnicodeString uString; 384 fICULocale->getDisplayName(Locale(displayLanguageID.String()), uString); 385 name.Truncate(0); 386 BStringByteSink stringConverter(&name); 387 uString.toUTF8(stringConverter); 388 389 return B_OK; 390 } 391 392 393 BMeasurementKind 394 BFormattingConventions::MeasurementKind() const 395 { 396 UErrorCode error = U_ZERO_ERROR; 397 switch (ulocdata_getMeasurementSystem(ID(), &error)) { 398 case UMS_US: 399 return B_US; 400 case UMS_SI: 401 default: 402 return B_METRIC; 403 } 404 } 405 406 407 status_t 408 BFormattingConventions::GetDateFormat(BDateFormatStyle style, 409 BString& outFormat) const 410 { 411 if (style < 0 || style >= B_DATE_FORMAT_STYLE_COUNT) 412 return B_BAD_VALUE; 413 414 outFormat = fExplicitDateFormats[style].Length() 415 ? fExplicitDateFormats[style] 416 : fCachedDateFormats[style]; 417 418 if (outFormat.Length() > 0) 419 return B_OK; 420 421 ObjectDeleter<DateFormat> dateFormatter( 422 DateFormat::createDateInstance((DateFormat::EStyle)style, *fICULocale)); 423 if (dateFormatter.Get() == NULL) 424 return B_NO_MEMORY; 425 426 SimpleDateFormat* dateFormatterImpl 427 = static_cast<SimpleDateFormat*>(dateFormatter.Get()); 428 429 UnicodeString icuString; 430 dateFormatterImpl->toPattern(icuString); 431 BStringByteSink stringConverter(&outFormat); 432 icuString.toUTF8(stringConverter); 433 434 fCachedDateFormats[style] = outFormat; 435 436 return B_OK; 437 } 438 439 440 status_t 441 BFormattingConventions::GetTimeFormat(BTimeFormatStyle style, 442 BString& outFormat) const 443 { 444 if (style < 0 || style >= B_TIME_FORMAT_STYLE_COUNT) 445 return B_BAD_VALUE; 446 447 outFormat = fExplicitTimeFormats[style].Length() 448 ? fExplicitTimeFormats[style] 449 : fCachedTimeFormats[style]; 450 451 if (outFormat.Length() > 0) 452 return B_OK; 453 454 ObjectDeleter<DateFormat> timeFormatter( 455 DateFormat::createTimeInstance((DateFormat::EStyle)style, *fICULocale)); 456 if (timeFormatter.Get() == NULL) 457 return B_NO_MEMORY; 458 459 SimpleDateFormat* timeFormatterImpl 460 = static_cast<SimpleDateFormat*>(timeFormatter.Get()); 461 462 UnicodeString icuString; 463 timeFormatterImpl->toPattern(icuString); 464 BStringByteSink stringConverter(&outFormat); 465 icuString.toUTF8(stringConverter); 466 467 int8 use24HourClock = fExplicitUse24HourClock != CLOCK_HOURS_UNSET 468 ? fExplicitUse24HourClock : fCachedUse24HourClock; 469 if (use24HourClock != CLOCK_HOURS_UNSET) { 470 // adjust to 12/24-hour clock as requested 471 bool localeUses24HourClock = !FormatUsesAmPm(outFormat); 472 if (localeUses24HourClock) { 473 if (use24HourClock == CLOCK_HOURS_12) 474 CoerceFormatTo12HourClock(outFormat); 475 } else { 476 if (use24HourClock == CLOCK_HOURS_24) 477 CoerceFormatTo24HourClock(outFormat); 478 } 479 } 480 481 if (style != B_FULL_TIME_FORMAT) { 482 // use abbreviated timezone in short timezone format 483 CoerceFormatToAbbreviatedTimezone(outFormat); 484 } 485 486 fCachedTimeFormats[style] = outFormat; 487 488 return B_OK; 489 } 490 491 492 status_t 493 BFormattingConventions::GetNumericFormat(BString& outFormat) const 494 { 495 // TODO! 496 return B_UNSUPPORTED; 497 } 498 499 500 status_t 501 BFormattingConventions::GetMonetaryFormat(BString& outFormat) const 502 { 503 // TODO! 504 return B_UNSUPPORTED; 505 } 506 507 508 void 509 BFormattingConventions::SetExplicitDateFormat(BDateFormatStyle style, 510 const BString& format) 511 { 512 fExplicitDateFormats[style] = format; 513 } 514 515 516 void 517 BFormattingConventions::SetExplicitTimeFormat(BTimeFormatStyle style, 518 const BString& format) 519 { 520 fExplicitTimeFormats[style] = format; 521 } 522 523 524 void 525 BFormattingConventions::SetExplicitNumericFormat(const BString& format) 526 { 527 fExplicitNumericFormat = format; 528 } 529 530 531 void 532 BFormattingConventions::SetExplicitMonetaryFormat(const BString& format) 533 { 534 fExplicitMonetaryFormat = format; 535 } 536 537 538 bool 539 BFormattingConventions::UseStringsFromPreferredLanguage() const 540 { 541 return fUseStringsFromPreferredLanguage; 542 } 543 544 545 void 546 BFormattingConventions::SetUseStringsFromPreferredLanguage(bool value) 547 { 548 fUseStringsFromPreferredLanguage = value; 549 } 550 551 552 bool 553 BFormattingConventions::Use24HourClock() const 554 { 555 int8 use24HourClock = fExplicitUse24HourClock != CLOCK_HOURS_UNSET 556 ? fExplicitUse24HourClock : fCachedUse24HourClock; 557 558 if (use24HourClock == CLOCK_HOURS_UNSET) { 559 BString format; 560 GetTimeFormat(B_MEDIUM_TIME_FORMAT, format); 561 fCachedUse24HourClock 562 = FormatUsesAmPm(format) ? CLOCK_HOURS_12 : CLOCK_HOURS_24; 563 return fCachedUse24HourClock == CLOCK_HOURS_24; 564 } 565 566 return fExplicitUse24HourClock == CLOCK_HOURS_24; 567 } 568 569 570 void 571 BFormattingConventions::SetExplicitUse24HourClock(bool value) 572 { 573 int8 newUse24HourClock = value ? CLOCK_HOURS_24 : CLOCK_HOURS_12; 574 if (fExplicitUse24HourClock == newUse24HourClock) 575 return; 576 577 fExplicitUse24HourClock = newUse24HourClock; 578 579 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) 580 fCachedTimeFormats[s].Truncate(0); 581 } 582 583 584 void 585 BFormattingConventions::UnsetExplicitUse24HourClock() 586 { 587 fExplicitUse24HourClock = CLOCK_HOURS_UNSET; 588 589 for (int s = 0; s < B_TIME_FORMAT_STYLE_COUNT; ++s) 590 fCachedTimeFormats[s].Truncate(0); 591 } 592 593 594 status_t 595 BFormattingConventions::Archive(BMessage* archive, bool deep) const 596 { 597 status_t status = archive->AddString("conventions", fICULocale->getName()); 598 for (int s = 0; s < B_DATE_FORMAT_STYLE_COUNT && status == B_OK; ++s) { 599 status = archive->AddString("dateFormat", fExplicitDateFormats[s]); 600 if (status == B_OK) 601 status = archive->AddString("timeFormat", fExplicitTimeFormats[s]); 602 } 603 if (status == B_OK) 604 status = archive->AddInt8("use24HourClock", fExplicitUse24HourClock); 605 if (status == B_OK) { 606 status = archive->AddBool("useStringsFromPreferredLanguage", 607 fUseStringsFromPreferredLanguage); 608 } 609 610 return status; 611 } 612