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