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