1 /* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "core/platform/text/win/LocaleWin.h" 33 34 #include <windows.h> 35 #include <limits> 36 #include "core/platform/DateComponents.h" 37 #include "core/platform/Language.h" 38 #include "core/platform/LocalizedStrings.h" 39 #include "core/platform/text/DateTimeFormat.h" 40 #include "wtf/CurrentTime.h" 41 #include "wtf/DateMath.h" 42 #include "wtf/HashMap.h" 43 #include "wtf/OwnPtr.h" 44 #include "wtf/PassOwnPtr.h" 45 #include "wtf/text/StringBuffer.h" 46 #include "wtf/text/StringBuilder.h" 47 #include "wtf/text/StringHash.h" 48 49 using namespace std; 50 51 namespace WebCore { 52 53 typedef LCID (WINAPI* LocaleNameToLCIDPtr)(LPCWSTR, DWORD); 54 typedef HashMap<String, LCID> NameToLCIDMap; 55 56 static String extractLanguageCode(const String& locale) 57 { 58 size_t dashPosition = locale.find('-'); 59 if (dashPosition == notFound) 60 return locale; 61 return locale.left(dashPosition); 62 } 63 64 static String removeLastComponent(const String& name) 65 { 66 size_t lastSeparator = name.reverseFind('-'); 67 if (lastSeparator == notFound) 68 return emptyString(); 69 return name.left(lastSeparator); 70 } 71 72 static void ensureNameToLCIDMap(NameToLCIDMap& map) 73 { 74 if (!map.isEmpty()) 75 return; 76 // http://www.microsoft.com/resources/msdn/goglobal/default.mspx 77 // We add only locales used in layout tests for now. 78 map.add("ar", 0x0001); 79 map.add("ar-eg", 0x0C01); 80 map.add("de", 0x0007); 81 map.add("de-de", 0x0407); 82 map.add("el", 0x0008); 83 map.add("el-gr", 0x0408); 84 map.add("en", 0x0009); 85 map.add("en-gb", 0x0809); 86 map.add("en-us", 0x0409); 87 map.add("fr", 0x000C); 88 map.add("fr-fr", 0x040C); 89 map.add("he", 0x000D); 90 map.add("he-il", 0x040D); 91 map.add("hi", 0x0039); 92 map.add("hi-in", 0x0439); 93 map.add("ja", 0x0011); 94 map.add("ja-jp", 0x0411); 95 map.add("ko", 0x0012); 96 map.add("ko-kr", 0x0412); 97 map.add("ru", 0x0019); 98 map.add("ru-ru", 0x0419); 99 map.add("zh-cn", 0x0804); 100 map.add("zh-tw", 0x0404); 101 } 102 103 // Fallback implementation of LocaleNameToLCID API. This is used for 104 // testing on Windows XP. 105 // FIXME: Remove this, ensureNameToLCIDMap, and removeLastComponent when we drop 106 // Windows XP support. 107 static LCID WINAPI convertLocaleNameToLCID(LPCWSTR name, DWORD) 108 { 109 if (!name || !name[0]) 110 return LOCALE_USER_DEFAULT; 111 DEFINE_STATIC_LOCAL(NameToLCIDMap, map, ()); 112 ensureNameToLCIDMap(map); 113 String localeName = String(name).replace('_', '-'); 114 localeName.makeLower(); 115 do { 116 NameToLCIDMap::const_iterator iterator = map.find(localeName); 117 if (iterator != map.end()) 118 return iterator->value; 119 localeName = removeLastComponent(localeName); 120 } while (!localeName.isEmpty()); 121 return LOCALE_USER_DEFAULT; 122 } 123 124 static LCID LCIDFromLocaleInternal(LCID userDefaultLCID, const String& userDefaultLanguageCode, LocaleNameToLCIDPtr localeNameToLCID, String& locale) 125 { 126 String localeLanguageCode = extractLanguageCode(locale); 127 if (equalIgnoringCase(localeLanguageCode, userDefaultLanguageCode)) 128 return userDefaultLCID; 129 return localeNameToLCID(locale.charactersWithNullTermination().data(), 0); 130 } 131 132 static LCID LCIDFromLocale(const AtomicString& locale) 133 { 134 // LocaleNameToLCID() is available since Windows Vista. 135 LocaleNameToLCIDPtr localeNameToLCID = reinterpret_cast<LocaleNameToLCIDPtr>(::GetProcAddress(::GetModuleHandle(L"kernel32"), "LocaleNameToLCID")); 136 if (!localeNameToLCID) 137 localeNameToLCID = convertLocaleNameToLCID; 138 139 // According to MSDN, 9 is enough for LOCALE_SISO639LANGNAME. 140 const size_t languageCodeBufferSize = 9; 141 WCHAR lowercaseLanguageCode[languageCodeBufferSize]; 142 ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, lowercaseLanguageCode, languageCodeBufferSize); 143 String userDefaultLanguageCode = String(lowercaseLanguageCode); 144 145 LCID lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, userDefaultLanguageCode, localeNameToLCID, String(locale)); 146 if (!lcid) 147 lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, userDefaultLanguageCode, localeNameToLCID, defaultLanguage()); 148 return lcid; 149 } 150 151 PassOwnPtr<Locale> Locale::create(const AtomicString& locale) 152 { 153 return LocaleWin::create(LCIDFromLocale(locale)); 154 } 155 156 inline LocaleWin::LocaleWin(LCID lcid) 157 : m_lcid(lcid) 158 , m_didInitializeNumberData(false) 159 { 160 #if ENABLE(CALENDAR_PICKER) 161 DWORD value = 0; 162 getLocaleInfo(LOCALE_IFIRSTDAYOFWEEK, value); 163 // 0:Monday, ..., 6:Sunday. 164 // We need 1 for Monday, 0 for Sunday. 165 m_firstDayOfWeek = (value + 1) % 7; 166 #endif 167 } 168 169 PassOwnPtr<LocaleWin> LocaleWin::create(LCID lcid) 170 { 171 return adoptPtr(new LocaleWin(lcid)); 172 } 173 174 LocaleWin::~LocaleWin() 175 { 176 } 177 178 String LocaleWin::getLocaleInfoString(LCTYPE type) 179 { 180 int bufferSizeWithNUL = ::GetLocaleInfo(m_lcid, type, 0, 0); 181 if (bufferSizeWithNUL <= 0) 182 return String(); 183 StringBuffer<UChar> buffer(bufferSizeWithNUL); 184 ::GetLocaleInfo(m_lcid, type, buffer.characters(), bufferSizeWithNUL); 185 buffer.shrink(bufferSizeWithNUL - 1); 186 return String::adopt(buffer); 187 } 188 189 void LocaleWin::getLocaleInfo(LCTYPE type, DWORD& result) 190 { 191 ::GetLocaleInfo(m_lcid, type | LOCALE_RETURN_NUMBER, reinterpret_cast<LPWSTR>(&result), sizeof(DWORD) / sizeof(TCHAR)); 192 } 193 194 void LocaleWin::ensureShortMonthLabels() 195 { 196 if (!m_shortMonthLabels.isEmpty()) 197 return; 198 const LCTYPE types[12] = { 199 LOCALE_SABBREVMONTHNAME1, 200 LOCALE_SABBREVMONTHNAME2, 201 LOCALE_SABBREVMONTHNAME3, 202 LOCALE_SABBREVMONTHNAME4, 203 LOCALE_SABBREVMONTHNAME5, 204 LOCALE_SABBREVMONTHNAME6, 205 LOCALE_SABBREVMONTHNAME7, 206 LOCALE_SABBREVMONTHNAME8, 207 LOCALE_SABBREVMONTHNAME9, 208 LOCALE_SABBREVMONTHNAME10, 209 LOCALE_SABBREVMONTHNAME11, 210 LOCALE_SABBREVMONTHNAME12, 211 }; 212 m_shortMonthLabels.reserveCapacity(WTF_ARRAY_LENGTH(types)); 213 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(types); ++i) { 214 m_shortMonthLabels.append(getLocaleInfoString(types[i])); 215 if (m_shortMonthLabels.last().isEmpty()) { 216 m_shortMonthLabels.shrink(0); 217 m_shortMonthLabels.reserveCapacity(WTF_ARRAY_LENGTH(WTF::monthName)); 218 for (unsigned m = 0; m < WTF_ARRAY_LENGTH(WTF::monthName); ++m) 219 m_shortMonthLabels.append(WTF::monthName[m]); 220 return; 221 } 222 } 223 } 224 225 // -------------------------------- Tokenized date format 226 227 static unsigned countContinuousLetters(const String& format, unsigned index) 228 { 229 unsigned count = 1; 230 UChar reference = format[index]; 231 while (index + 1 < format.length()) { 232 if (format[++index] != reference) 233 break; 234 ++count; 235 } 236 return count; 237 } 238 239 static void commitLiteralToken(StringBuilder& literalBuffer, StringBuilder& converted) 240 { 241 if (literalBuffer.length() <= 0) 242 return; 243 DateTimeFormat::quoteAndAppendLiteral(literalBuffer.toString(), converted); 244 literalBuffer.clear(); 245 } 246 247 // This function converts Windows date/time pattern format [1][2] into LDML date 248 // format pattern [3]. 249 // 250 // i.e. 251 // We set h, H, m, s, d, dd, M, or y as is. They have same meaning in both of 252 // Windows and LDML. 253 // We need to convert the following patterns: 254 // t -> a 255 // tt -> a 256 // ddd -> EEE 257 // dddd -> EEEE 258 // g -> G 259 // gg -> ignore 260 // 261 // [1] http://msdn.microsoft.com/en-us/library/dd317787(v=vs.85).aspx 262 // [2] http://msdn.microsoft.com/en-us/library/dd318148(v=vs.85).aspx 263 // [3] LDML http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns 264 static String convertWindowsDateTimeFormat(const String& format) 265 { 266 StringBuilder converted; 267 StringBuilder literalBuffer; 268 bool inQuote = false; 269 bool lastQuoteCanBeLiteral = false; 270 for (unsigned i = 0; i < format.length(); ++i) { 271 UChar ch = format[i]; 272 if (inQuote) { 273 if (ch == '\'') { 274 inQuote = false; 275 ASSERT(i); 276 if (lastQuoteCanBeLiteral && format[i - 1] == '\'') { 277 literalBuffer.append('\''); 278 lastQuoteCanBeLiteral = false; 279 } else 280 lastQuoteCanBeLiteral = true; 281 } else 282 literalBuffer.append(ch); 283 continue; 284 } 285 286 if (ch == '\'') { 287 inQuote = true; 288 if (lastQuoteCanBeLiteral && i > 0 && format[i - 1] == '\'') { 289 literalBuffer.append(ch); 290 lastQuoteCanBeLiteral = false; 291 } else 292 lastQuoteCanBeLiteral = true; 293 } else if (isASCIIAlpha(ch)) { 294 commitLiteralToken(literalBuffer, converted); 295 unsigned symbolStart = i; 296 unsigned count = countContinuousLetters(format, i); 297 i += count - 1; 298 if (ch == 'h' || ch == 'H' || ch == 'm' || ch == 's' || ch == 'M' || ch == 'y') 299 converted.append(format, symbolStart, count); 300 else if (ch == 'd') { 301 if (count <= 2) 302 converted.append(format, symbolStart, count); 303 else if (count == 3) 304 converted.append("EEE"); 305 else 306 converted.append("EEEE"); 307 } else if (ch == 'g') { 308 if (count == 1) 309 converted.append('G'); 310 else { 311 // gg means imperial era in Windows. 312 // Just ignore it. 313 } 314 } else if (ch == 't') 315 converted.append('a'); 316 else 317 literalBuffer.append(format, symbolStart, count); 318 } else 319 literalBuffer.append(ch); 320 } 321 commitLiteralToken(literalBuffer, converted); 322 return converted.toString(); 323 } 324 325 void LocaleWin::ensureMonthLabels() 326 { 327 if (!m_monthLabels.isEmpty()) 328 return; 329 const LCTYPE types[12] = { 330 LOCALE_SMONTHNAME1, 331 LOCALE_SMONTHNAME2, 332 LOCALE_SMONTHNAME3, 333 LOCALE_SMONTHNAME4, 334 LOCALE_SMONTHNAME5, 335 LOCALE_SMONTHNAME6, 336 LOCALE_SMONTHNAME7, 337 LOCALE_SMONTHNAME8, 338 LOCALE_SMONTHNAME9, 339 LOCALE_SMONTHNAME10, 340 LOCALE_SMONTHNAME11, 341 LOCALE_SMONTHNAME12, 342 }; 343 m_monthLabels.reserveCapacity(WTF_ARRAY_LENGTH(types)); 344 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(types); ++i) { 345 m_monthLabels.append(getLocaleInfoString(types[i])); 346 if (m_monthLabels.last().isEmpty()) { 347 m_monthLabels.shrink(0); 348 m_monthLabels.reserveCapacity(WTF_ARRAY_LENGTH(WTF::monthFullName)); 349 for (unsigned m = 0; m < WTF_ARRAY_LENGTH(WTF::monthFullName); ++m) 350 m_monthLabels.append(WTF::monthFullName[m]); 351 return; 352 } 353 } 354 } 355 356 void LocaleWin::ensureWeekDayShortLabels() 357 { 358 if (!m_weekDayShortLabels.isEmpty()) 359 return; 360 const LCTYPE types[7] = { 361 LOCALE_SABBREVDAYNAME7, // Sunday 362 LOCALE_SABBREVDAYNAME1, // Monday 363 LOCALE_SABBREVDAYNAME2, 364 LOCALE_SABBREVDAYNAME3, 365 LOCALE_SABBREVDAYNAME4, 366 LOCALE_SABBREVDAYNAME5, 367 LOCALE_SABBREVDAYNAME6 368 }; 369 m_weekDayShortLabels.reserveCapacity(WTF_ARRAY_LENGTH(types)); 370 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(types); ++i) { 371 m_weekDayShortLabels.append(getLocaleInfoString(types[i])); 372 if (m_weekDayShortLabels.last().isEmpty()) { 373 m_weekDayShortLabels.shrink(0); 374 m_weekDayShortLabels.reserveCapacity(WTF_ARRAY_LENGTH(WTF::weekdayName)); 375 for (unsigned w = 0; w < WTF_ARRAY_LENGTH(WTF::weekdayName); ++w) { 376 // weekdayName starts with Monday. 377 m_weekDayShortLabels.append(WTF::weekdayName[(w + 6) % 7]); 378 } 379 return; 380 } 381 } 382 } 383 384 const Vector<String>& LocaleWin::monthLabels() 385 { 386 ensureMonthLabels(); 387 return m_monthLabels; 388 } 389 390 #if ENABLE(CALENDAR_PICKER) 391 const Vector<String>& LocaleWin::weekDayShortLabels() 392 { 393 ensureWeekDayShortLabels(); 394 return m_weekDayShortLabels; 395 } 396 397 unsigned LocaleWin::firstDayOfWeek() 398 { 399 return m_firstDayOfWeek; 400 } 401 402 bool LocaleWin::isRTL() 403 { 404 WTF::Unicode::Direction dir = WTF::Unicode::direction(monthLabels()[0][0]); 405 return dir == WTF::Unicode::RightToLeft || dir == WTF::Unicode::RightToLeftArabic; 406 } 407 #endif 408 409 String LocaleWin::dateFormat() 410 { 411 if (m_dateFormat.isNull()) 412 m_dateFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SSHORTDATE)); 413 return m_dateFormat; 414 } 415 416 String LocaleWin::dateFormat(const String& windowsFormat) 417 { 418 return convertWindowsDateTimeFormat(windowsFormat); 419 } 420 421 String LocaleWin::monthFormat() 422 { 423 if (m_monthFormat.isNull()) 424 m_monthFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH)); 425 return m_monthFormat; 426 } 427 428 String LocaleWin::shortMonthFormat() 429 { 430 if (m_shortMonthFormat.isNull()) 431 m_shortMonthFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH)).replace("MMMM", "MMM"); 432 return m_shortMonthFormat; 433 } 434 435 String LocaleWin::timeFormat() 436 { 437 if (m_timeFormatWithSeconds.isNull()) 438 m_timeFormatWithSeconds = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_STIMEFORMAT)); 439 return m_timeFormatWithSeconds; 440 } 441 442 String LocaleWin::shortTimeFormat() 443 { 444 if (!m_timeFormatWithoutSeconds.isNull()) 445 return m_timeFormatWithoutSeconds; 446 String format = getLocaleInfoString(LOCALE_SSHORTTIME); 447 // Vista or older Windows doesn't support LOCALE_SSHORTTIME. 448 if (format.isEmpty()) { 449 format = getLocaleInfoString(LOCALE_STIMEFORMAT); 450 StringBuilder builder; 451 builder.append(getLocaleInfoString(LOCALE_STIME)); 452 builder.append("ss"); 453 size_t pos = format.reverseFind(builder.toString()); 454 if (pos != notFound) 455 format.remove(pos, builder.length()); 456 } 457 m_timeFormatWithoutSeconds = convertWindowsDateTimeFormat(format); 458 return m_timeFormatWithoutSeconds; 459 } 460 461 String LocaleWin::dateTimeFormatWithSeconds() 462 { 463 if (!m_dateTimeFormatWithSeconds.isNull()) 464 return m_dateTimeFormatWithSeconds; 465 StringBuilder builder; 466 builder.append(dateFormat()); 467 builder.append(' '); 468 builder.append(timeFormat()); 469 m_dateTimeFormatWithSeconds = builder.toString(); 470 return m_dateTimeFormatWithSeconds; 471 } 472 473 String LocaleWin::dateTimeFormatWithoutSeconds() 474 { 475 if (!m_dateTimeFormatWithoutSeconds.isNull()) 476 return m_dateTimeFormatWithoutSeconds; 477 StringBuilder builder; 478 builder.append(dateFormat()); 479 builder.append(' '); 480 builder.append(shortTimeFormat()); 481 m_dateTimeFormatWithoutSeconds = builder.toString(); 482 return m_dateTimeFormatWithoutSeconds; 483 } 484 485 const Vector<String>& LocaleWin::shortMonthLabels() 486 { 487 ensureShortMonthLabels(); 488 return m_shortMonthLabels; 489 } 490 491 const Vector<String>& LocaleWin::standAloneMonthLabels() 492 { 493 // Windows doesn't provide a way to get stand-alone month labels. 494 return monthLabels(); 495 } 496 497 const Vector<String>& LocaleWin::shortStandAloneMonthLabels() 498 { 499 // Windows doesn't provide a way to get stand-alone month labels. 500 return shortMonthLabels(); 501 } 502 503 const Vector<String>& LocaleWin::timeAMPMLabels() 504 { 505 if (m_timeAMPMLabels.isEmpty()) { 506 m_timeAMPMLabels.append(getLocaleInfoString(LOCALE_S1159)); 507 m_timeAMPMLabels.append(getLocaleInfoString(LOCALE_S2359)); 508 } 509 return m_timeAMPMLabels; 510 } 511 512 void LocaleWin::initializeLocaleData() 513 { 514 if (m_didInitializeNumberData) 515 return; 516 517 Vector<String, DecimalSymbolsSize> symbols; 518 enum DigitSubstitution { 519 DigitSubstitutionContext = 0, 520 DigitSubstitution0to9 = 1, 521 DigitSubstitutionNative = 2, 522 }; 523 DWORD digitSubstitution = DigitSubstitution0to9; 524 getLocaleInfo(LOCALE_IDIGITSUBSTITUTION, digitSubstitution); 525 if (digitSubstitution == DigitSubstitution0to9) { 526 symbols.append("0"); 527 symbols.append("1"); 528 symbols.append("2"); 529 symbols.append("3"); 530 symbols.append("4"); 531 symbols.append("5"); 532 symbols.append("6"); 533 symbols.append("7"); 534 symbols.append("8"); 535 symbols.append("9"); 536 } else { 537 String digits = getLocaleInfoString(LOCALE_SNATIVEDIGITS); 538 ASSERT(digits.length() >= 10); 539 for (unsigned i = 0; i < 10; ++i) 540 symbols.append(digits.substring(i, 1)); 541 } 542 ASSERT(symbols.size() == DecimalSeparatorIndex); 543 symbols.append(getLocaleInfoString(LOCALE_SDECIMAL)); 544 ASSERT(symbols.size() == GroupSeparatorIndex); 545 symbols.append(getLocaleInfoString(LOCALE_STHOUSAND)); 546 ASSERT(symbols.size() == DecimalSymbolsSize); 547 548 String negativeSign = getLocaleInfoString(LOCALE_SNEGATIVESIGN); 549 enum NegativeFormat { 550 NegativeFormatParenthesis = 0, 551 NegativeFormatSignPrefix = 1, 552 NegativeFormatSignSpacePrefix = 2, 553 NegativeFormatSignSuffix = 3, 554 NegativeFormatSpaceSignSuffix = 4, 555 }; 556 DWORD negativeFormat = NegativeFormatSignPrefix; 557 getLocaleInfo(LOCALE_INEGNUMBER, negativeFormat); 558 String negativePrefix = emptyString(); 559 String negativeSuffix = emptyString(); 560 switch (negativeFormat) { 561 case NegativeFormatParenthesis: 562 negativePrefix = "("; 563 negativeSuffix = ")"; 564 break; 565 case NegativeFormatSignSpacePrefix: 566 negativePrefix = negativeSign + " "; 567 break; 568 case NegativeFormatSignSuffix: 569 negativeSuffix = negativeSign; 570 break; 571 case NegativeFormatSpaceSignSuffix: 572 negativeSuffix = " " + negativeSign; 573 break; 574 case NegativeFormatSignPrefix: // Fall through. 575 default: 576 negativePrefix = negativeSign; 577 break; 578 } 579 m_didInitializeNumberData = true; 580 setLocaleData(symbols, emptyString(), emptyString(), negativePrefix, negativeSuffix); 581 } 582 583 } 584