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 "LocaleWin.h" 33 34#include "DateComponents.h" 35#include "DateTimeFormat.h" 36#include "Language.h" 37#include "LocalizedStrings.h" 38#include <limits> 39#include <windows.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/StringBuilder.h> 46#include <wtf/text/StringHash.h> 47 48using namespace std; 49 50namespace WebCore { 51 52typedef LCID (WINAPI* LocaleNameToLCIDPtr)(LPCWSTR, DWORD); 53typedef HashMap<String, LCID> NameToLCIDMap; 54 55static String extractLanguageCode(const String& locale) 56{ 57 size_t dashPosition = locale.find('-'); 58 if (dashPosition == notFound) 59 return locale; 60 return locale.left(dashPosition); 61} 62 63static String removeLastComponent(const String& name) 64{ 65 size_t lastSeparator = name.reverseFind('-'); 66 if (lastSeparator == notFound) 67 return emptyString(); 68 return name.left(lastSeparator); 69} 70 71static void ensureNameToLCIDMap(NameToLCIDMap& map) 72{ 73 if (!map.isEmpty()) 74 return; 75 // http://www.microsoft.com/resources/msdn/goglobal/default.mspx 76 // We add only locales used in layout tests for now. 77 map.add("ar", 0x0001); 78 map.add("ar-eg", 0x0C01); 79 map.add("de", 0x0007); 80 map.add("de-de", 0x0407); 81 map.add("el", 0x0008); 82 map.add("el-gr", 0x0408); 83 map.add("en", 0x0009); 84 map.add("en-gb", 0x0809); 85 map.add("en-us", 0x0409); 86 map.add("fr", 0x000C); 87 map.add("fr-fr", 0x040C); 88 map.add("he", 0x000D); 89 map.add("he-il", 0x040D); 90 map.add("hi", 0x0039); 91 map.add("hi-in", 0x0439); 92 map.add("ja", 0x0011); 93 map.add("ja-jp", 0x0411); 94 map.add("ko", 0x0012); 95 map.add("ko-kr", 0x0412); 96 map.add("ru", 0x0019); 97 map.add("ru-ru", 0x0419); 98 map.add("zh-cn", 0x0804); 99 map.add("zh-tw", 0x0404); 100} 101 102// Fallback implementation of LocaleNameToLCID API. This is used for 103// testing on Windows XP. 104// FIXME: Remove this, ensureNameToLCIDMap, and removeLastComponent when we drop 105// Windows XP support. 106static LCID WINAPI convertLocaleNameToLCID(LPCWSTR name, DWORD) 107{ 108 if (!name || !name[0]) 109 return LOCALE_USER_DEFAULT; 110 DEPRECATED_DEFINE_STATIC_LOCAL(NameToLCIDMap, map, ()); 111 ensureNameToLCIDMap(map); 112 String localeName = String(name).replace('_', '-').lower(); 113 do { 114 NameToLCIDMap::const_iterator iterator = map.find(localeName); 115 if (iterator != map.end()) 116 return iterator->value; 117 localeName = removeLastComponent(localeName); 118 } while (!localeName.isEmpty()); 119 return LOCALE_USER_DEFAULT; 120} 121 122static LCID LCIDFromLocaleInternal(LCID userDefaultLCID, const String& userDefaultLanguageCode, LocaleNameToLCIDPtr localeNameToLCID, String& locale) 123{ 124 String localeLanguageCode = extractLanguageCode(locale); 125 if (equalIgnoringCase(localeLanguageCode, userDefaultLanguageCode)) 126 return userDefaultLCID; 127 return localeNameToLCID(locale.charactersWithNullTermination().data(), 0); 128} 129 130static LCID LCIDFromLocale(const AtomicString& locale) 131{ 132 // LocaleNameToLCID() is available since Windows Vista. 133 LocaleNameToLCIDPtr localeNameToLCID = reinterpret_cast<LocaleNameToLCIDPtr>(::GetProcAddress(::GetModuleHandle(L"kernel32"), "LocaleNameToLCID")); 134 if (!localeNameToLCID) 135 localeNameToLCID = convertLocaleNameToLCID; 136 137 // According to MSDN, 9 is enough for LOCALE_SISO639LANGNAME. 138 const size_t languageCodeBufferSize = 9; 139 WCHAR lowercaseLanguageCode[languageCodeBufferSize]; 140 ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, lowercaseLanguageCode, languageCodeBufferSize); 141 String userDefaultLanguageCode = String(lowercaseLanguageCode); 142 143 LCID lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, userDefaultLanguageCode, localeNameToLCID, String(locale)); 144 if (!lcid) 145 lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, userDefaultLanguageCode, localeNameToLCID, defaultLanguage()); 146 return lcid; 147} 148 149PassOwnPtr<Locale> Locale::create(const AtomicString& locale) 150{ 151 return LocaleWin::create(LCIDFromLocale(locale)); 152} 153 154inline LocaleWin::LocaleWin(LCID lcid) 155 : m_lcid(lcid) 156 , m_didInitializeNumberData(false) 157{ 158} 159 160PassOwnPtr<LocaleWin> LocaleWin::create(LCID lcid) 161{ 162 return adoptPtr(new LocaleWin(lcid)); 163} 164 165LocaleWin::~LocaleWin() 166{ 167} 168 169String LocaleWin::getLocaleInfoString(LCTYPE type) 170{ 171 int bufferSizeWithNUL = ::GetLocaleInfo(m_lcid, type, 0, 0); 172 if (bufferSizeWithNUL <= 0) 173 return String(); 174 Vector<UChar> buffer(bufferSizeWithNUL); 175 ::GetLocaleInfo(m_lcid, type, buffer.data(), bufferSizeWithNUL); 176 buffer.shrink(bufferSizeWithNUL - 1); 177 return String::adopt(buffer); 178} 179 180void LocaleWin::getLocaleInfo(LCTYPE type, DWORD& result) 181{ 182 ::GetLocaleInfo(m_lcid, type | LOCALE_RETURN_NUMBER, reinterpret_cast<LPWSTR>(&result), sizeof(DWORD) / sizeof(TCHAR)); 183} 184 185void LocaleWin::ensureShortMonthLabels() 186{ 187#if ENABLE(DATE_AND_TIME_INPUT_TYPES) 188 if (!m_shortMonthLabels.isEmpty()) 189 return; 190 const LCTYPE types[12] = { 191 LOCALE_SABBREVMONTHNAME1, 192 LOCALE_SABBREVMONTHNAME2, 193 LOCALE_SABBREVMONTHNAME3, 194 LOCALE_SABBREVMONTHNAME4, 195 LOCALE_SABBREVMONTHNAME5, 196 LOCALE_SABBREVMONTHNAME6, 197 LOCALE_SABBREVMONTHNAME7, 198 LOCALE_SABBREVMONTHNAME8, 199 LOCALE_SABBREVMONTHNAME9, 200 LOCALE_SABBREVMONTHNAME10, 201 LOCALE_SABBREVMONTHNAME11, 202 LOCALE_SABBREVMONTHNAME12, 203 }; 204 m_shortMonthLabels.reserveCapacity(WTF_ARRAY_LENGTH(types)); 205 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(types); ++i) { 206 m_shortMonthLabels.append(getLocaleInfoString(types[i])); 207 if (m_shortMonthLabels.last().isEmpty()) { 208 m_shortMonthLabels.shrink(0); 209 m_shortMonthLabels.reserveCapacity(WTF_ARRAY_LENGTH(WTF::monthName)); 210 for (unsigned m = 0; m < WTF_ARRAY_LENGTH(WTF::monthName); ++m) 211 m_shortMonthLabels.append(WTF::monthName[m]); 212 return; 213 } 214 } 215#endif 216} 217 218// -------------------------------- Tokenized date format 219 220#if ENABLE(DATE_AND_TIME_INPUT_TYPES) 221static unsigned countContinuousLetters(const String& format, unsigned index) 222{ 223 unsigned count = 1; 224 UChar reference = format[index]; 225 while (index + 1 < format.length()) { 226 if (format[++index] != reference) 227 break; 228 ++count; 229 } 230 return count; 231} 232 233static void commitLiteralToken(StringBuilder& literalBuffer, StringBuilder& converted) 234{ 235 if (literalBuffer.length() <= 0) 236 return; 237 DateTimeFormat::quoteAndAppendLiteral(literalBuffer.toString(), converted); 238 literalBuffer.clear(); 239} 240 241// This function converts Windows date/time pattern format [1][2] into LDML date 242// format pattern [3]. 243// 244// i.e. 245// We set h, H, m, s, d, dd, M, or y as is. They have same meaning in both of 246// Windows and LDML. 247// We need to convert the following patterns: 248// t -> a 249// tt -> a 250// ddd -> EEE 251// dddd -> EEEE 252// g -> G 253// gg -> ignore 254// 255// [1] http://msdn.microsoft.com/en-us/library/dd317787(v=vs.85).aspx 256// [2] http://msdn.microsoft.com/en-us/library/dd318148(v=vs.85).aspx 257// [3] LDML http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns 258static String convertWindowsDateTimeFormat(const String& format) 259{ 260 StringBuilder converted; 261 StringBuilder literalBuffer; 262 bool inQuote = false; 263 bool lastQuoteCanBeLiteral = false; 264 for (unsigned i = 0; i < format.length(); ++i) { 265 UChar ch = format[i]; 266 if (inQuote) { 267 if (ch == '\'') { 268 inQuote = false; 269 ASSERT(i); 270 if (lastQuoteCanBeLiteral && format[i - 1] == '\'') { 271 literalBuffer.append('\''); 272 lastQuoteCanBeLiteral = false; 273 } else 274 lastQuoteCanBeLiteral = true; 275 } else 276 literalBuffer.append(ch); 277 continue; 278 } 279 280 if (ch == '\'') { 281 inQuote = true; 282 if (lastQuoteCanBeLiteral && i > 0 && format[i - 1] == '\'') { 283 literalBuffer.append(ch); 284 lastQuoteCanBeLiteral = false; 285 } else 286 lastQuoteCanBeLiteral = true; 287 } else if (isASCIIAlpha(ch)) { 288 commitLiteralToken(literalBuffer, converted); 289 unsigned symbolStart = i; 290 unsigned count = countContinuousLetters(format, i); 291 i += count - 1; 292 if (ch == 'h' || ch == 'H' || ch == 'm' || ch == 's' || ch == 'M' || ch == 'y') 293 converted.append(format, symbolStart, count); 294 else if (ch == 'd') { 295 if (count <= 2) 296 converted.append(format, symbolStart, count); 297 else if (count == 3) 298 converted.append("EEE"); 299 else 300 converted.append("EEEE"); 301 } else if (ch == 'g') { 302 if (count == 1) 303 converted.append('G'); 304 else { 305 // gg means imperial era in Windows. 306 // Just ignore it. 307 } 308 } else if (ch == 't') 309 converted.append('a'); 310 else 311 literalBuffer.append(format, symbolStart, count); 312 } else 313 literalBuffer.append(ch); 314 } 315 commitLiteralToken(literalBuffer, converted); 316 return converted.toString(); 317} 318#endif 319 320void LocaleWin::ensureMonthLabels() 321{ 322#if ENABLE(DATE_AND_TIME_INPUT_TYPES) 323 if (!m_monthLabels.isEmpty()) 324 return; 325 const LCTYPE types[12] = { 326 LOCALE_SMONTHNAME1, 327 LOCALE_SMONTHNAME2, 328 LOCALE_SMONTHNAME3, 329 LOCALE_SMONTHNAME4, 330 LOCALE_SMONTHNAME5, 331 LOCALE_SMONTHNAME6, 332 LOCALE_SMONTHNAME7, 333 LOCALE_SMONTHNAME8, 334 LOCALE_SMONTHNAME9, 335 LOCALE_SMONTHNAME10, 336 LOCALE_SMONTHNAME11, 337 LOCALE_SMONTHNAME12, 338 }; 339 m_monthLabels.reserveCapacity(WTF_ARRAY_LENGTH(types)); 340 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(types); ++i) { 341 m_monthLabels.append(getLocaleInfoString(types[i])); 342 if (m_monthLabels.last().isEmpty()) { 343 m_monthLabels.shrink(0); 344 m_monthLabels.reserveCapacity(WTF_ARRAY_LENGTH(WTF::monthFullName)); 345 for (unsigned m = 0; m < WTF_ARRAY_LENGTH(WTF::monthFullName); ++m) 346 m_monthLabels.append(WTF::monthFullName[m]); 347 return; 348 } 349 } 350#endif 351} 352 353#if ENABLE(DATE_AND_TIME_INPUT_TYPES) 354const Vector<String>& LocaleWin::monthLabels() 355{ 356 ensureMonthLabels(); 357 return m_monthLabels; 358} 359 360String LocaleWin::dateFormat() 361{ 362 if (m_dateFormat.isNull()) 363 m_dateFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SSHORTDATE)); 364 return m_dateFormat; 365} 366 367String LocaleWin::dateFormat(const String& windowsFormat) 368{ 369 return convertWindowsDateTimeFormat(windowsFormat); 370} 371 372String LocaleWin::monthFormat() 373{ 374 if (m_monthFormat.isNull()) 375 m_monthFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH)); 376 return m_monthFormat; 377} 378 379String LocaleWin::shortMonthFormat() 380{ 381 if (m_shortMonthFormat.isNull()) 382 m_shortMonthFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH)).replace("MMMM", "MMM"); 383 return m_shortMonthFormat; 384} 385 386String LocaleWin::timeFormat() 387{ 388 if (m_timeFormatWithSeconds.isNull()) 389 m_timeFormatWithSeconds = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_STIMEFORMAT)); 390 return m_timeFormatWithSeconds; 391} 392 393String LocaleWin::shortTimeFormat() 394{ 395 if (!m_timeFormatWithoutSeconds.isNull()) 396 return m_timeFormatWithoutSeconds; 397 String format = getLocaleInfoString(LOCALE_SSHORTTIME); 398 // Vista or older Windows doesn't support LOCALE_SSHORTTIME. 399 if (format.isEmpty()) { 400 format = getLocaleInfoString(LOCALE_STIMEFORMAT); 401 StringBuilder builder; 402 builder.append(getLocaleInfoString(LOCALE_STIME)); 403 builder.append("ss"); 404 size_t pos = format.reverseFind(builder.toString()); 405 if (pos != notFound) 406 format.remove(pos, builder.length()); 407 } 408 m_timeFormatWithoutSeconds = convertWindowsDateTimeFormat(format); 409 return m_timeFormatWithoutSeconds; 410} 411 412String LocaleWin::dateTimeFormatWithSeconds() 413{ 414 if (!m_dateTimeFormatWithSeconds.isNull()) 415 return m_dateTimeFormatWithSeconds; 416 StringBuilder builder; 417 builder.append(dateFormat()); 418 builder.append(' '); 419 builder.append(timeFormat()); 420 m_dateTimeFormatWithSeconds = builder.toString(); 421 return m_dateTimeFormatWithSeconds; 422} 423 424String LocaleWin::dateTimeFormatWithoutSeconds() 425{ 426 if (!m_dateTimeFormatWithoutSeconds.isNull()) 427 return m_dateTimeFormatWithoutSeconds; 428 StringBuilder builder; 429 builder.append(dateFormat()); 430 builder.append(' '); 431 builder.append(shortTimeFormat()); 432 m_dateTimeFormatWithoutSeconds = builder.toString(); 433 return m_dateTimeFormatWithoutSeconds; 434} 435 436const Vector<String>& LocaleWin::shortMonthLabels() 437{ 438 ensureShortMonthLabels(); 439 return m_shortMonthLabels; 440} 441 442const Vector<String>& LocaleWin::standAloneMonthLabels() 443{ 444 // Windows doesn't provide a way to get stand-alone month labels. 445 return monthLabels(); 446} 447 448const Vector<String>& LocaleWin::shortStandAloneMonthLabels() 449{ 450 // Windows doesn't provide a way to get stand-alone month labels. 451 return shortMonthLabels(); 452} 453 454const Vector<String>& LocaleWin::timeAMPMLabels() 455{ 456 if (m_timeAMPMLabels.isEmpty()) { 457 m_timeAMPMLabels.append(getLocaleInfoString(LOCALE_S1159)); 458 m_timeAMPMLabels.append(getLocaleInfoString(LOCALE_S2359)); 459 } 460 return m_timeAMPMLabels; 461} 462#endif 463 464void LocaleWin::initializeLocaleData() 465{ 466 if (m_didInitializeNumberData) 467 return; 468 469 Vector<String, DecimalSymbolsSize> symbols; 470 enum DigitSubstitution { 471 DigitSubstitutionContext = 0, 472 DigitSubstitution0to9 = 1, 473 DigitSubstitutionNative = 2, 474 }; 475 DWORD digitSubstitution = DigitSubstitution0to9; 476 getLocaleInfo(LOCALE_IDIGITSUBSTITUTION, digitSubstitution); 477 if (digitSubstitution == DigitSubstitution0to9) { 478 symbols.append("0"); 479 symbols.append("1"); 480 symbols.append("2"); 481 symbols.append("3"); 482 symbols.append("4"); 483 symbols.append("5"); 484 symbols.append("6"); 485 symbols.append("7"); 486 symbols.append("8"); 487 symbols.append("9"); 488 } else { 489 String digits = getLocaleInfoString(LOCALE_SNATIVEDIGITS); 490 ASSERT(digits.length() >= 10); 491 for (unsigned i = 0; i < 10; ++i) 492 symbols.append(digits.substring(i, 1)); 493 } 494 ASSERT(symbols.size() == DecimalSeparatorIndex); 495 symbols.append(getLocaleInfoString(LOCALE_SDECIMAL)); 496 ASSERT(symbols.size() == GroupSeparatorIndex); 497 symbols.append(getLocaleInfoString(LOCALE_STHOUSAND)); 498 ASSERT(symbols.size() == DecimalSymbolsSize); 499 500 String negativeSign = getLocaleInfoString(LOCALE_SNEGATIVESIGN); 501 enum NegativeFormat { 502 NegativeFormatParenthesis = 0, 503 NegativeFormatSignPrefix = 1, 504 NegativeFormatSignSpacePrefix = 2, 505 NegativeFormatSignSuffix = 3, 506 NegativeFormatSpaceSignSuffix = 4, 507 }; 508 DWORD negativeFormat = NegativeFormatSignPrefix; 509 getLocaleInfo(LOCALE_INEGNUMBER, negativeFormat); 510 String negativePrefix = emptyString(); 511 String negativeSuffix = emptyString(); 512 switch (negativeFormat) { 513 case NegativeFormatParenthesis: 514 negativePrefix = "("; 515 negativeSuffix = ")"; 516 break; 517 case NegativeFormatSignSpacePrefix: 518 negativePrefix = negativeSign + " "; 519 break; 520 case NegativeFormatSignSuffix: 521 negativeSuffix = negativeSign; 522 break; 523 case NegativeFormatSpaceSignSuffix: 524 negativeSuffix = " " + negativeSign; 525 break; 526 case NegativeFormatSignPrefix: 527 FALLTHROUGH; 528 default: 529 negativePrefix = negativeSign; 530 break; 531 } 532 m_didInitializeNumberData = true; 533 setLocaleData(symbols, emptyString(), emptyString(), negativePrefix, negativeSuffix); 534} 535 536} 537