2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
32 #include "platform/text/LocaleWin.h"
35 #include "platform/DateComponents.h"
36 #include "platform/Language.h"
37 #include "platform/LayoutTestSupport.h"
38 #include "platform/text/DateTimeFormat.h"
39 #include "wtf/CurrentTime.h"
40 #include "wtf/DateMath.h"
41 #include "wtf/HashMap.h"
42 #include "wtf/OwnPtr.h"
43 #include "wtf/PassOwnPtr.h"
44 #include "wtf/text/StringBuffer.h"
45 #include "wtf/text/StringBuilder.h"
46 #include "wtf/text/StringHash.h"
50 typedef LCID (WINAPI
* LocaleNameToLCIDPtr
)(LPCWSTR
, DWORD
);
51 typedef HashMap
<String
, LCID
> NameToLCIDMap
;
53 static String
extractLanguageCode(const String
& locale
)
55 size_t dashPosition
= locale
.find('-');
56 if (dashPosition
== kNotFound
)
58 return locale
.left(dashPosition
);
61 static String
removeLastComponent(const String
& name
)
63 size_t lastSeparator
= name
.reverseFind('-');
64 if (lastSeparator
== kNotFound
)
66 return name
.left(lastSeparator
);
69 static void ensureNameToLCIDMap(NameToLCIDMap
& map
)
73 // http://www.microsoft.com/resources/msdn/goglobal/default.mspx
74 // We add only locales used in layout tests for now.
75 map
.add("ar", 0x0001);
76 map
.add("ar-eg", 0x0C01);
77 map
.add("de", 0x0007);
78 map
.add("de-de", 0x0407);
79 map
.add("el", 0x0008);
80 map
.add("el-gr", 0x0408);
81 map
.add("en", 0x0009);
82 map
.add("en-gb", 0x0809);
83 map
.add("en-us", 0x0409);
84 map
.add("fr", 0x000C);
85 map
.add("fr-fr", 0x040C);
86 map
.add("he", 0x000D);
87 map
.add("he-il", 0x040D);
88 map
.add("hi", 0x0039);
89 map
.add("hi-in", 0x0439);
90 map
.add("ja", 0x0011);
91 map
.add("ja-jp", 0x0411);
92 map
.add("ko", 0x0012);
93 map
.add("ko-kr", 0x0412);
94 map
.add("ru", 0x0019);
95 map
.add("ru-ru", 0x0419);
96 map
.add("zh-cn", 0x0804);
97 map
.add("zh-tw", 0x0404);
100 // Fallback implementation of LocaleNameToLCID API. This is used for
101 // testing on Windows XP.
102 // FIXME: Remove this, ensureNameToLCIDMap, and removeLastComponent when we drop
103 // Windows XP support.
104 static LCID WINAPI
convertLocaleNameToLCID(LPCWSTR name
, DWORD
)
106 if (!name
|| !name
[0])
107 return LOCALE_USER_DEFAULT
;
108 DEFINE_STATIC_LOCAL(NameToLCIDMap
, map
, ());
109 ensureNameToLCIDMap(map
);
110 String localeName
= String(name
).replace('_', '-');
111 localeName
= localeName
.lower();
113 NameToLCIDMap::const_iterator iterator
= map
.find(localeName
);
114 if (iterator
!= map
.end())
115 return iterator
->value
;
116 localeName
= removeLastComponent(localeName
);
117 } while (!localeName
.isEmpty());
118 return LOCALE_USER_DEFAULT
;
121 static LCID
LCIDFromLocaleInternal(LCID userDefaultLCID
, const String
& userDefaultLanguageCode
, LocaleNameToLCIDPtr localeNameToLCID
, const String
& locale
)
123 String localeLanguageCode
= extractLanguageCode(locale
);
124 if (equalIgnoringCase(localeLanguageCode
, userDefaultLanguageCode
))
125 return userDefaultLCID
;
126 return localeNameToLCID(locale
.charactersWithNullTermination().data(), 0);
129 static LCID
LCIDFromLocale(const String
& locale
, bool defaultsForLocale
)
131 // LocaleNameToLCID() is available since Windows Vista.
132 LocaleNameToLCIDPtr localeNameToLCID
= reinterpret_cast<LocaleNameToLCIDPtr
>(::GetProcAddress(::GetModuleHandle(L
"kernel32"), "LocaleNameToLCID"));
133 if (!localeNameToLCID
)
134 localeNameToLCID
= convertLocaleNameToLCID
;
136 // According to MSDN, 9 is enough for LOCALE_SISO639LANGNAME.
137 const size_t languageCodeBufferSize
= 9;
138 WCHAR lowercaseLanguageCode
[languageCodeBufferSize
];
139 ::GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_SISO639LANGNAME
| (defaultsForLocale
? LOCALE_NOUSEROVERRIDE
: 0), lowercaseLanguageCode
, languageCodeBufferSize
);
140 String userDefaultLanguageCode
= String(lowercaseLanguageCode
);
142 LCID lcid
= LCIDFromLocaleInternal(LOCALE_USER_DEFAULT
, userDefaultLanguageCode
, localeNameToLCID
, locale
);
144 lcid
= LCIDFromLocaleInternal(LOCALE_USER_DEFAULT
, userDefaultLanguageCode
, localeNameToLCID
, defaultLanguage());
148 PassOwnPtr
<Locale
> Locale::create(const String
& locale
)
150 // Whether the default settings for the locale should be used, ignoring user overrides.
151 bool defaultsForLocale
= LayoutTestSupport::isRunningLayoutTest();
152 return LocaleWin::create(LCIDFromLocale(locale
, defaultsForLocale
), defaultsForLocale
);
155 inline LocaleWin::LocaleWin(LCID lcid
, bool defaultsForLocale
)
157 , m_didInitializeNumberData(false)
158 , m_defaultsForLocale(defaultsForLocale
)
161 getLocaleInfo(LOCALE_IFIRSTDAYOFWEEK
| (defaultsForLocale
? LOCALE_NOUSEROVERRIDE
: 0), value
);
162 // 0:Monday, ..., 6:Sunday.
163 // We need 1 for Monday, 0 for Sunday.
164 m_firstDayOfWeek
= (value
+ 1) % 7;
167 PassOwnPtr
<LocaleWin
> LocaleWin::create(LCID lcid
, bool defaultsForLocale
)
169 return adoptPtr(new LocaleWin(lcid
, defaultsForLocale
));
172 LocaleWin::~LocaleWin()
176 String
LocaleWin::getLocaleInfoString(LCTYPE type
)
178 int bufferSizeWithNUL
= ::GetLocaleInfo(m_lcid
, type
| (m_defaultsForLocale
? LOCALE_NOUSEROVERRIDE
: 0), 0, 0);
179 if (bufferSizeWithNUL
<= 0)
181 StringBuffer
<UChar
> buffer(bufferSizeWithNUL
);
182 ::GetLocaleInfo(m_lcid
, type
| (m_defaultsForLocale
? LOCALE_NOUSEROVERRIDE
: 0), buffer
.characters(), bufferSizeWithNUL
);
183 buffer
.shrink(bufferSizeWithNUL
- 1);
184 return String::adopt(buffer
);
187 void LocaleWin::getLocaleInfo(LCTYPE type
, DWORD
& result
)
189 ::GetLocaleInfo(m_lcid
, type
| LOCALE_RETURN_NUMBER
, reinterpret_cast<LPWSTR
>(&result
), sizeof(DWORD
) / sizeof(TCHAR
));
192 void LocaleWin::ensureShortMonthLabels()
194 if (!m_shortMonthLabels
.isEmpty())
196 const LCTYPE types
[12] = {
197 LOCALE_SABBREVMONTHNAME1
,
198 LOCALE_SABBREVMONTHNAME2
,
199 LOCALE_SABBREVMONTHNAME3
,
200 LOCALE_SABBREVMONTHNAME4
,
201 LOCALE_SABBREVMONTHNAME5
,
202 LOCALE_SABBREVMONTHNAME6
,
203 LOCALE_SABBREVMONTHNAME7
,
204 LOCALE_SABBREVMONTHNAME8
,
205 LOCALE_SABBREVMONTHNAME9
,
206 LOCALE_SABBREVMONTHNAME10
,
207 LOCALE_SABBREVMONTHNAME11
,
208 LOCALE_SABBREVMONTHNAME12
,
210 m_shortMonthLabels
.reserveCapacity(WTF_ARRAY_LENGTH(types
));
211 for (unsigned i
= 0; i
< WTF_ARRAY_LENGTH(types
); ++i
) {
212 m_shortMonthLabels
.append(getLocaleInfoString(types
[i
]));
213 if (m_shortMonthLabels
.last().isEmpty()) {
214 m_shortMonthLabels
.shrink(0);
215 m_shortMonthLabels
.reserveCapacity(WTF_ARRAY_LENGTH(WTF::monthName
));
216 for (unsigned m
= 0; m
< WTF_ARRAY_LENGTH(WTF::monthName
); ++m
)
217 m_shortMonthLabels
.append(WTF::monthName
[m
]);
223 // -------------------------------- Tokenized date format
225 static unsigned countContinuousLetters(const String
& format
, unsigned index
)
228 UChar reference
= format
[index
];
229 while (index
+ 1 < format
.length()) {
230 if (format
[++index
] != reference
)
237 static void commitLiteralToken(StringBuilder
& literalBuffer
, StringBuilder
& converted
)
239 if (literalBuffer
.length() <= 0)
241 DateTimeFormat::quoteAndAppendLiteral(literalBuffer
.toString(), converted
);
242 literalBuffer
.clear();
245 // This function converts Windows date/time pattern format [1][2] into LDML date
246 // format pattern [3].
249 // We set h, H, m, s, d, dd, M, or y as is. They have same meaning in both of
251 // We need to convert the following patterns:
259 // [1] http://msdn.microsoft.com/en-us/library/dd317787(v=vs.85).aspx
260 // [2] http://msdn.microsoft.com/en-us/library/dd318148(v=vs.85).aspx
261 // [3] LDML http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns
262 static String
convertWindowsDateTimeFormat(const String
& format
)
264 StringBuilder converted
;
265 StringBuilder literalBuffer
;
266 bool inQuote
= false;
267 bool lastQuoteCanBeLiteral
= false;
268 for (unsigned i
= 0; i
< format
.length(); ++i
) {
269 UChar ch
= format
[i
];
274 if (lastQuoteCanBeLiteral
&& format
[i
- 1] == '\'') {
275 literalBuffer
.append('\'');
276 lastQuoteCanBeLiteral
= false;
278 lastQuoteCanBeLiteral
= true;
281 literalBuffer
.append(ch
);
288 if (lastQuoteCanBeLiteral
&& i
> 0 && format
[i
- 1] == '\'') {
289 literalBuffer
.append(ch
);
290 lastQuoteCanBeLiteral
= false;
292 lastQuoteCanBeLiteral
= true;
294 } else if (isASCIIAlpha(ch
)) {
295 commitLiteralToken(literalBuffer
, converted
);
296 unsigned symbolStart
= i
;
297 unsigned count
= countContinuousLetters(format
, i
);
299 if (ch
== 'h' || ch
== 'H' || ch
== 'm' || ch
== 's' || ch
== 'M' || ch
== 'y') {
300 converted
.append(format
, symbolStart
, count
);
301 } else if (ch
== 'd') {
303 converted
.append(format
, symbolStart
, count
);
305 converted
.appendLiteral("EEE");
307 converted
.appendLiteral("EEEE");
308 } else if (ch
== 'g') {
310 converted
.append('G');
312 // gg means imperial era in Windows.
315 } else if (ch
== 't') {
316 converted
.append('a');
318 literalBuffer
.append(format
, symbolStart
, count
);
321 literalBuffer
.append(ch
);
324 commitLiteralToken(literalBuffer
, converted
);
325 return converted
.toString();
328 void LocaleWin::ensureMonthLabels()
330 if (!m_monthLabels
.isEmpty())
332 const LCTYPE types
[12] = {
346 m_monthLabels
.reserveCapacity(WTF_ARRAY_LENGTH(types
));
347 for (unsigned i
= 0; i
< WTF_ARRAY_LENGTH(types
); ++i
) {
348 m_monthLabels
.append(getLocaleInfoString(types
[i
]));
349 if (m_monthLabels
.last().isEmpty()) {
350 m_monthLabels
.shrink(0);
351 m_monthLabels
.reserveCapacity(WTF_ARRAY_LENGTH(WTF::monthFullName
));
352 for (unsigned m
= 0; m
< WTF_ARRAY_LENGTH(WTF::monthFullName
); ++m
)
353 m_monthLabels
.append(WTF::monthFullName
[m
]);
359 void LocaleWin::ensureWeekDayShortLabels()
361 if (!m_weekDayShortLabels
.isEmpty())
363 const LCTYPE types
[7] = {
364 LOCALE_SABBREVDAYNAME7
, // Sunday
365 LOCALE_SABBREVDAYNAME1
, // Monday
366 LOCALE_SABBREVDAYNAME2
,
367 LOCALE_SABBREVDAYNAME3
,
368 LOCALE_SABBREVDAYNAME4
,
369 LOCALE_SABBREVDAYNAME5
,
370 LOCALE_SABBREVDAYNAME6
372 m_weekDayShortLabels
.reserveCapacity(WTF_ARRAY_LENGTH(types
));
373 for (unsigned i
= 0; i
< WTF_ARRAY_LENGTH(types
); ++i
) {
374 m_weekDayShortLabels
.append(getLocaleInfoString(types
[i
]));
375 if (m_weekDayShortLabels
.last().isEmpty()) {
376 m_weekDayShortLabels
.shrink(0);
377 m_weekDayShortLabels
.reserveCapacity(WTF_ARRAY_LENGTH(WTF::weekdayName
));
378 for (unsigned w
= 0; w
< WTF_ARRAY_LENGTH(WTF::weekdayName
); ++w
) {
379 // weekdayName starts with Monday.
380 m_weekDayShortLabels
.append(WTF::weekdayName
[(w
+ 6) % 7]);
387 const Vector
<String
>& LocaleWin::monthLabels()
390 return m_monthLabels
;
393 const Vector
<String
>& LocaleWin::weekDayShortLabels()
395 ensureWeekDayShortLabels();
396 return m_weekDayShortLabels
;
399 unsigned LocaleWin::firstDayOfWeek()
401 return m_firstDayOfWeek
;
404 bool LocaleWin::isRTL()
406 WTF::Unicode::Direction dir
= WTF::Unicode::direction(monthLabels()[0][0]);
407 return dir
== WTF::Unicode::RightToLeft
|| dir
== WTF::Unicode::RightToLeftArabic
;
410 String
LocaleWin::dateFormat()
412 if (m_dateFormat
.isNull())
413 m_dateFormat
= convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SSHORTDATE
));
417 String
LocaleWin::dateFormat(const String
& windowsFormat
)
419 return convertWindowsDateTimeFormat(windowsFormat
);
422 String
LocaleWin::monthFormat()
424 if (m_monthFormat
.isNull())
425 m_monthFormat
= convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH
));
426 return m_monthFormat
;
429 String
LocaleWin::shortMonthFormat()
431 if (m_shortMonthFormat
.isNull())
432 m_shortMonthFormat
= convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH
)).replace("MMMM", "MMM");
433 return m_shortMonthFormat
;
436 String
LocaleWin::timeFormat()
438 if (m_timeFormatWithSeconds
.isNull())
439 m_timeFormatWithSeconds
= convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_STIMEFORMAT
));
440 return m_timeFormatWithSeconds
;
443 String
LocaleWin::shortTimeFormat()
445 if (!m_timeFormatWithoutSeconds
.isNull())
446 return m_timeFormatWithoutSeconds
;
447 String format
= getLocaleInfoString(LOCALE_SSHORTTIME
);
448 // Vista or older Windows doesn't support LOCALE_SSHORTTIME.
449 if (format
.isEmpty()) {
450 format
= getLocaleInfoString(LOCALE_STIMEFORMAT
);
451 StringBuilder builder
;
452 builder
.append(getLocaleInfoString(LOCALE_STIME
));
453 builder
.appendLiteral("ss");
454 size_t pos
= format
.reverseFind(builder
.toString());
455 if (pos
!= kNotFound
)
456 format
.remove(pos
, builder
.length());
458 m_timeFormatWithoutSeconds
= convertWindowsDateTimeFormat(format
);
459 return m_timeFormatWithoutSeconds
;
462 String
LocaleWin::dateTimeFormatWithSeconds()
464 if (!m_dateTimeFormatWithSeconds
.isNull())
465 return m_dateTimeFormatWithSeconds
;
466 StringBuilder builder
;
467 builder
.append(dateFormat());
469 builder
.append(timeFormat());
470 m_dateTimeFormatWithSeconds
= builder
.toString();
471 return m_dateTimeFormatWithSeconds
;
474 String
LocaleWin::dateTimeFormatWithoutSeconds()
476 if (!m_dateTimeFormatWithoutSeconds
.isNull())
477 return m_dateTimeFormatWithoutSeconds
;
478 StringBuilder builder
;
479 builder
.append(dateFormat());
481 builder
.append(shortTimeFormat());
482 m_dateTimeFormatWithoutSeconds
= builder
.toString();
483 return m_dateTimeFormatWithoutSeconds
;
486 const Vector
<String
>& LocaleWin::shortMonthLabels()
488 ensureShortMonthLabels();
489 return m_shortMonthLabels
;
492 const Vector
<String
>& LocaleWin::standAloneMonthLabels()
494 // Windows doesn't provide a way to get stand-alone month labels.
495 return monthLabels();
498 const Vector
<String
>& LocaleWin::shortStandAloneMonthLabels()
500 // Windows doesn't provide a way to get stand-alone month labels.
501 return shortMonthLabels();
504 const Vector
<String
>& LocaleWin::timeAMPMLabels()
506 if (m_timeAMPMLabels
.isEmpty()) {
507 m_timeAMPMLabels
.append(getLocaleInfoString(LOCALE_S1159
));
508 m_timeAMPMLabels
.append(getLocaleInfoString(LOCALE_S2359
));
510 return m_timeAMPMLabels
;
513 void LocaleWin::initializeLocaleData()
515 if (m_didInitializeNumberData
)
518 Vector
<String
, DecimalSymbolsSize
> symbols
;
519 enum DigitSubstitution
{
520 DigitSubstitutionContext
= 0,
521 DigitSubstitution0to9
= 1,
522 DigitSubstitutionNative
= 2,
524 DWORD digitSubstitution
= DigitSubstitution0to9
;
525 getLocaleInfo(LOCALE_IDIGITSUBSTITUTION
, digitSubstitution
);
526 if (digitSubstitution
== DigitSubstitution0to9
) {
538 String digits
= getLocaleInfoString(LOCALE_SNATIVEDIGITS
);
539 ASSERT(digits
.length() >= 10);
540 for (unsigned i
= 0; i
< 10; ++i
)
541 symbols
.append(digits
.substring(i
, 1));
543 ASSERT(symbols
.size() == DecimalSeparatorIndex
);
544 symbols
.append(getLocaleInfoString(LOCALE_SDECIMAL
));
545 ASSERT(symbols
.size() == GroupSeparatorIndex
);
546 symbols
.append(getLocaleInfoString(LOCALE_STHOUSAND
));
547 ASSERT(symbols
.size() == DecimalSymbolsSize
);
549 String negativeSign
= getLocaleInfoString(LOCALE_SNEGATIVESIGN
);
550 enum NegativeFormat
{
551 NegativeFormatParenthesis
= 0,
552 NegativeFormatSignPrefix
= 1,
553 NegativeFormatSignSpacePrefix
= 2,
554 NegativeFormatSignSuffix
= 3,
555 NegativeFormatSpaceSignSuffix
= 4,
557 DWORD negativeFormat
= NegativeFormatSignPrefix
;
558 getLocaleInfo(LOCALE_INEGNUMBER
, negativeFormat
);
559 String negativePrefix
= emptyString();
560 String negativeSuffix
= emptyString();
561 switch (negativeFormat
) {
562 case NegativeFormatParenthesis
:
563 negativePrefix
= "(";
564 negativeSuffix
= ")";
566 case NegativeFormatSignSpacePrefix
:
567 negativePrefix
= negativeSign
+ " ";
569 case NegativeFormatSignSuffix
:
570 negativeSuffix
= negativeSign
;
572 case NegativeFormatSpaceSignSuffix
:
573 negativeSuffix
= " " + negativeSign
;
575 case NegativeFormatSignPrefix
: // Fall through.
577 negativePrefix
= negativeSign
;
580 m_didInitializeNumberData
= true;
581 setLocaleData(symbols
, emptyString(), emptyString(), negativePrefix
, negativeSuffix
);