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 "platform/text/LocaleWin.h"
33
34#include <limits>
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"
47
48namespace blink {
49
50typedef LCID (WINAPI* LocaleNameToLCIDPtr)(LPCWSTR, DWORD);
51typedef HashMap<String, LCID> NameToLCIDMap;
52
53static String extractLanguageCode(const String& locale)
54{
55    size_t dashPosition = locale.find('-');
56    if (dashPosition == kNotFound)
57        return locale;
58    return locale.left(dashPosition);
59}
60
61static String removeLastComponent(const String& name)
62{
63    size_t lastSeparator = name.reverseFind('-');
64    if (lastSeparator == kNotFound)
65        return emptyString();
66    return name.left(lastSeparator);
67}
68
69static void ensureNameToLCIDMap(NameToLCIDMap& map)
70{
71    if (!map.isEmpty())
72        return;
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);
98}
99
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.
104static LCID WINAPI convertLocaleNameToLCID(LPCWSTR name, DWORD)
105{
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();
112    do {
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;
119}
120
121static LCID LCIDFromLocaleInternal(LCID userDefaultLCID, const String& userDefaultLanguageCode, LocaleNameToLCIDPtr localeNameToLCID, const String& locale)
122{
123    String localeLanguageCode = extractLanguageCode(locale);
124    if (equalIgnoringCase(localeLanguageCode, userDefaultLanguageCode))
125        return userDefaultLCID;
126    return localeNameToLCID(locale.charactersWithNullTermination().data(), 0);
127}
128
129static LCID LCIDFromLocale(const String& locale, bool defaultsForLocale)
130{
131    // LocaleNameToLCID() is available since Windows Vista.
132    LocaleNameToLCIDPtr localeNameToLCID = reinterpret_cast<LocaleNameToLCIDPtr>(::GetProcAddress(::GetModuleHandle(L"kernel32"), "LocaleNameToLCID"));
133    if (!localeNameToLCID)
134        localeNameToLCID = convertLocaleNameToLCID;
135
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);
141
142    LCID lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, userDefaultLanguageCode, localeNameToLCID, locale);
143    if (!lcid)
144        lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, userDefaultLanguageCode, localeNameToLCID, defaultLanguage());
145    return lcid;
146}
147
148PassOwnPtr<Locale> Locale::create(const String& locale)
149{
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);
153}
154
155inline LocaleWin::LocaleWin(LCID lcid, bool defaultsForLocale)
156    : m_lcid(lcid)
157    , m_didInitializeNumberData(false)
158    , m_defaultsForLocale(defaultsForLocale)
159{
160    DWORD value = 0;
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;
165}
166
167PassOwnPtr<LocaleWin> LocaleWin::create(LCID lcid, bool defaultsForLocale)
168{
169    return adoptPtr(new LocaleWin(lcid, defaultsForLocale));
170}
171
172LocaleWin::~LocaleWin()
173{
174}
175
176String LocaleWin::getLocaleInfoString(LCTYPE type)
177{
178    int bufferSizeWithNUL = ::GetLocaleInfo(m_lcid, type | (m_defaultsForLocale ? LOCALE_NOUSEROVERRIDE : 0), 0, 0);
179    if (bufferSizeWithNUL <= 0)
180        return String();
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);
185}
186
187void LocaleWin::getLocaleInfo(LCTYPE type, DWORD& result)
188{
189    ::GetLocaleInfo(m_lcid, type | LOCALE_RETURN_NUMBER, reinterpret_cast<LPWSTR>(&result), sizeof(DWORD) / sizeof(TCHAR));
190}
191
192void LocaleWin::ensureShortMonthLabels()
193{
194    if (!m_shortMonthLabels.isEmpty())
195        return;
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,
209    };
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]);
218            return;
219        }
220    }
221}
222
223// -------------------------------- Tokenized date format
224
225static unsigned countContinuousLetters(const String& format, unsigned index)
226{
227    unsigned count = 1;
228    UChar reference = format[index];
229    while (index + 1 < format.length()) {
230        if (format[++index] != reference)
231            break;
232        ++count;
233    }
234    return count;
235}
236
237static void commitLiteralToken(StringBuilder& literalBuffer, StringBuilder& converted)
238{
239    if (literalBuffer.length() <= 0)
240        return;
241    DateTimeFormat::quoteAndAppendLiteral(literalBuffer.toString(), converted);
242    literalBuffer.clear();
243}
244
245// This function converts Windows date/time pattern format [1][2] into LDML date
246// format pattern [3].
247//
248// i.e.
249//   We set h, H, m, s, d, dd, M, or y as is. They have same meaning in both of
250//   Windows and LDML.
251//   We need to convert the following patterns:
252//     t -> a
253//     tt -> a
254//     ddd -> EEE
255//     dddd -> EEEE
256//     g -> G
257//     gg -> ignore
258//
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
262static String convertWindowsDateTimeFormat(const String& format)
263{
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];
270        if (inQuote) {
271            if (ch == '\'') {
272                inQuote = false;
273                ASSERT(i);
274                if (lastQuoteCanBeLiteral && format[i - 1] == '\'') {
275                    literalBuffer.append('\'');
276                    lastQuoteCanBeLiteral = false;
277                } else {
278                    lastQuoteCanBeLiteral = true;
279                }
280            } else {
281                literalBuffer.append(ch);
282            }
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            }
294        } else if (isASCIIAlpha(ch)) {
295            commitLiteralToken(literalBuffer, converted);
296            unsigned symbolStart = i;
297            unsigned count = countContinuousLetters(format, i);
298            i += count - 1;
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') {
302                if (count <= 2)
303                    converted.append(format, symbolStart, count);
304                else if (count == 3)
305                    converted.appendLiteral("EEE");
306                else
307                    converted.appendLiteral("EEEE");
308            } else if (ch == 'g') {
309                if (count == 1) {
310                    converted.append('G');
311                } else {
312                    // gg means imperial era in Windows.
313                    // Just ignore it.
314                }
315            } else if (ch == 't') {
316                converted.append('a');
317            } else {
318                literalBuffer.append(format, symbolStart, count);
319            }
320        } else {
321            literalBuffer.append(ch);
322        }
323    }
324    commitLiteralToken(literalBuffer, converted);
325    return converted.toString();
326}
327
328void LocaleWin::ensureMonthLabels()
329{
330    if (!m_monthLabels.isEmpty())
331        return;
332    const LCTYPE types[12] = {
333        LOCALE_SMONTHNAME1,
334        LOCALE_SMONTHNAME2,
335        LOCALE_SMONTHNAME3,
336        LOCALE_SMONTHNAME4,
337        LOCALE_SMONTHNAME5,
338        LOCALE_SMONTHNAME6,
339        LOCALE_SMONTHNAME7,
340        LOCALE_SMONTHNAME8,
341        LOCALE_SMONTHNAME9,
342        LOCALE_SMONTHNAME10,
343        LOCALE_SMONTHNAME11,
344        LOCALE_SMONTHNAME12,
345    };
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]);
354            return;
355        }
356    }
357}
358
359void LocaleWin::ensureWeekDayShortLabels()
360{
361    if (!m_weekDayShortLabels.isEmpty())
362        return;
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
371    };
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]);
381            }
382            return;
383        }
384    }
385}
386
387const Vector<String>& LocaleWin::monthLabels()
388{
389    ensureMonthLabels();
390    return m_monthLabels;
391}
392
393const Vector<String>& LocaleWin::weekDayShortLabels()
394{
395    ensureWeekDayShortLabels();
396    return m_weekDayShortLabels;
397}
398
399unsigned LocaleWin::firstDayOfWeek()
400{
401    return m_firstDayOfWeek;
402}
403
404bool LocaleWin::isRTL()
405{
406    WTF::Unicode::Direction dir = WTF::Unicode::direction(monthLabels()[0][0]);
407    return dir == WTF::Unicode::RightToLeft || dir == WTF::Unicode::RightToLeftArabic;
408}
409
410String LocaleWin::dateFormat()
411{
412    if (m_dateFormat.isNull())
413        m_dateFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SSHORTDATE));
414    return m_dateFormat;
415}
416
417String LocaleWin::dateFormat(const String& windowsFormat)
418{
419    return convertWindowsDateTimeFormat(windowsFormat);
420}
421
422String LocaleWin::monthFormat()
423{
424    if (m_monthFormat.isNull())
425        m_monthFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH));
426    return m_monthFormat;
427}
428
429String LocaleWin::shortMonthFormat()
430{
431    if (m_shortMonthFormat.isNull())
432        m_shortMonthFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH)).replace("MMMM", "MMM");
433    return m_shortMonthFormat;
434}
435
436String LocaleWin::timeFormat()
437{
438    if (m_timeFormatWithSeconds.isNull())
439        m_timeFormatWithSeconds = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_STIMEFORMAT));
440    return m_timeFormatWithSeconds;
441}
442
443String LocaleWin::shortTimeFormat()
444{
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());
457    }
458    m_timeFormatWithoutSeconds = convertWindowsDateTimeFormat(format);
459    return m_timeFormatWithoutSeconds;
460}
461
462String LocaleWin::dateTimeFormatWithSeconds()
463{
464    if (!m_dateTimeFormatWithSeconds.isNull())
465        return m_dateTimeFormatWithSeconds;
466    StringBuilder builder;
467    builder.append(dateFormat());
468    builder.append(' ');
469    builder.append(timeFormat());
470    m_dateTimeFormatWithSeconds = builder.toString();
471    return m_dateTimeFormatWithSeconds;
472}
473
474String LocaleWin::dateTimeFormatWithoutSeconds()
475{
476    if (!m_dateTimeFormatWithoutSeconds.isNull())
477        return m_dateTimeFormatWithoutSeconds;
478    StringBuilder builder;
479    builder.append(dateFormat());
480    builder.append(' ');
481    builder.append(shortTimeFormat());
482    m_dateTimeFormatWithoutSeconds = builder.toString();
483    return m_dateTimeFormatWithoutSeconds;
484}
485
486const Vector<String>& LocaleWin::shortMonthLabels()
487{
488    ensureShortMonthLabels();
489    return m_shortMonthLabels;
490}
491
492const Vector<String>& LocaleWin::standAloneMonthLabels()
493{
494    // Windows doesn't provide a way to get stand-alone month labels.
495    return monthLabels();
496}
497
498const Vector<String>& LocaleWin::shortStandAloneMonthLabels()
499{
500    // Windows doesn't provide a way to get stand-alone month labels.
501    return shortMonthLabels();
502}
503
504const Vector<String>& LocaleWin::timeAMPMLabels()
505{
506    if (m_timeAMPMLabels.isEmpty()) {
507        m_timeAMPMLabels.append(getLocaleInfoString(LOCALE_S1159));
508        m_timeAMPMLabels.append(getLocaleInfoString(LOCALE_S2359));
509    }
510    return m_timeAMPMLabels;
511}
512
513void LocaleWin::initializeLocaleData()
514{
515    if (m_didInitializeNumberData)
516        return;
517
518    Vector<String, DecimalSymbolsSize> symbols;
519    enum DigitSubstitution {
520        DigitSubstitutionContext = 0,
521        DigitSubstitution0to9 = 1,
522        DigitSubstitutionNative = 2,
523    };
524    DWORD digitSubstitution = DigitSubstitution0to9;
525    getLocaleInfo(LOCALE_IDIGITSUBSTITUTION, digitSubstitution);
526    if (digitSubstitution == DigitSubstitution0to9) {
527        symbols.append("0");
528        symbols.append("1");
529        symbols.append("2");
530        symbols.append("3");
531        symbols.append("4");
532        symbols.append("5");
533        symbols.append("6");
534        symbols.append("7");
535        symbols.append("8");
536        symbols.append("9");
537    } else {
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));
542    }
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);
548
549    String negativeSign = getLocaleInfoString(LOCALE_SNEGATIVESIGN);
550    enum NegativeFormat {
551        NegativeFormatParenthesis = 0,
552        NegativeFormatSignPrefix = 1,
553        NegativeFormatSignSpacePrefix = 2,
554        NegativeFormatSignSuffix = 3,
555        NegativeFormatSpaceSignSuffix = 4,
556    };
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 = ")";
565        break;
566    case NegativeFormatSignSpacePrefix:
567        negativePrefix = negativeSign + " ";
568        break;
569    case NegativeFormatSignSuffix:
570        negativeSuffix = negativeSign;
571        break;
572    case NegativeFormatSpaceSignSuffix:
573        negativeSuffix = " " + negativeSign;
574        break;
575    case NegativeFormatSignPrefix: // Fall through.
576    default:
577        negativePrefix = negativeSign;
578        break;
579    }
580    m_didInitializeNumberData = true;
581    setLocaleData(symbols, emptyString(), emptyString(), negativePrefix, negativeSuffix);
582}
583
584}
585