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
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 == kNotFound)
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 == kNotFound)
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    DEFINE_STATIC_LOCAL(NameToLCIDMap, map, ());
111    ensureNameToLCIDMap(map);
112    String localeName = String(name).replace('_', '-');
113    localeName = localeName.lower();
114    do {
115        NameToLCIDMap::const_iterator iterator = map.find(localeName);
116        if (iterator != map.end())
117            return iterator->value;
118        localeName = removeLastComponent(localeName);
119    } while (!localeName.isEmpty());
120    return LOCALE_USER_DEFAULT;
121}
122
123static LCID LCIDFromLocaleInternal(LCID userDefaultLCID, const String& userDefaultLanguageCode, LocaleNameToLCIDPtr localeNameToLCID, const String& locale)
124{
125    String localeLanguageCode = extractLanguageCode(locale);
126    if (equalIgnoringCase(localeLanguageCode, userDefaultLanguageCode))
127        return userDefaultLCID;
128    return localeNameToLCID(locale.charactersWithNullTermination().data(), 0);
129}
130
131static LCID LCIDFromLocale(const String& locale, bool defaultsForLocale)
132{
133    // LocaleNameToLCID() is available since Windows Vista.
134    LocaleNameToLCIDPtr localeNameToLCID = reinterpret_cast<LocaleNameToLCIDPtr>(::GetProcAddress(::GetModuleHandle(L"kernel32"), "LocaleNameToLCID"));
135    if (!localeNameToLCID)
136        localeNameToLCID = convertLocaleNameToLCID;
137
138    // According to MSDN, 9 is enough for LOCALE_SISO639LANGNAME.
139    const size_t languageCodeBufferSize = 9;
140    WCHAR lowercaseLanguageCode[languageCodeBufferSize];
141    ::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME | (defaultsForLocale ? LOCALE_NOUSEROVERRIDE : 0), lowercaseLanguageCode, languageCodeBufferSize);
142    String userDefaultLanguageCode = String(lowercaseLanguageCode);
143
144    LCID lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, userDefaultLanguageCode, localeNameToLCID, locale);
145    if (!lcid)
146        lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, userDefaultLanguageCode, localeNameToLCID, defaultLanguage());
147    return lcid;
148}
149
150PassOwnPtr<Locale> Locale::create(const String& locale)
151{
152    // Whether the default settings for the locale should be used, ignoring user overrides.
153    bool defaultsForLocale = isRunningLayoutTest();
154    return LocaleWin::create(LCIDFromLocale(locale, defaultsForLocale), defaultsForLocale);
155}
156
157inline LocaleWin::LocaleWin(LCID lcid, bool defaultsForLocale)
158    : m_lcid(lcid)
159    , m_didInitializeNumberData(false)
160    , m_defaultsForLocale(defaultsForLocale)
161{
162    DWORD value = 0;
163    getLocaleInfo(LOCALE_IFIRSTDAYOFWEEK | (defaultsForLocale ? LOCALE_NOUSEROVERRIDE : 0), value);
164    // 0:Monday, ..., 6:Sunday.
165    // We need 1 for Monday, 0 for Sunday.
166    m_firstDayOfWeek = (value + 1) % 7;
167}
168
169PassOwnPtr<LocaleWin> LocaleWin::create(LCID lcid, bool defaultsForLocale)
170{
171    return adoptPtr(new LocaleWin(lcid, defaultsForLocale));
172}
173
174LocaleWin::~LocaleWin()
175{
176}
177
178String LocaleWin::getLocaleInfoString(LCTYPE type)
179{
180    int bufferSizeWithNUL = ::GetLocaleInfo(m_lcid, type | (m_defaultsForLocale ? LOCALE_NOUSEROVERRIDE : 0), 0, 0);
181    if (bufferSizeWithNUL <= 0)
182        return String();
183    StringBuffer<UChar> buffer(bufferSizeWithNUL);
184    ::GetLocaleInfo(m_lcid, type | (m_defaultsForLocale ? LOCALE_NOUSEROVERRIDE : 0), buffer.characters(), bufferSizeWithNUL);
185    buffer.shrink(bufferSizeWithNUL - 1);
186    return String::adopt(buffer);
187}
188
189void 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
194void 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
227static 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
239static 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
264static 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                }
282            } else {
283                literalBuffer.append(ch);
284            }
285            continue;
286        }
287
288        if (ch == '\'') {
289            inQuote = true;
290            if (lastQuoteCanBeLiteral && i > 0 && format[i - 1] == '\'') {
291                literalBuffer.append(ch);
292                lastQuoteCanBeLiteral = false;
293            } else {
294                lastQuoteCanBeLiteral = true;
295            }
296        } else if (isASCIIAlpha(ch)) {
297            commitLiteralToken(literalBuffer, converted);
298            unsigned symbolStart = i;
299            unsigned count = countContinuousLetters(format, i);
300            i += count - 1;
301            if (ch == 'h' || ch == 'H' || ch == 'm' || ch == 's' || ch == 'M' || ch == 'y') {
302                converted.append(format, symbolStart, count);
303            } else if (ch == 'd') {
304                if (count <= 2)
305                    converted.append(format, symbolStart, count);
306                else if (count == 3)
307                    converted.append("EEE");
308                else
309                    converted.append("EEEE");
310            } else if (ch == 'g') {
311                if (count == 1) {
312                    converted.append('G');
313                } else {
314                    // gg means imperial era in Windows.
315                    // Just ignore it.
316                }
317            } else if (ch == 't') {
318                converted.append('a');
319            } else {
320                literalBuffer.append(format, symbolStart, count);
321            }
322        } else {
323            literalBuffer.append(ch);
324        }
325    }
326    commitLiteralToken(literalBuffer, converted);
327    return converted.toString();
328}
329
330void LocaleWin::ensureMonthLabels()
331{
332    if (!m_monthLabels.isEmpty())
333        return;
334    const LCTYPE types[12] = {
335        LOCALE_SMONTHNAME1,
336        LOCALE_SMONTHNAME2,
337        LOCALE_SMONTHNAME3,
338        LOCALE_SMONTHNAME4,
339        LOCALE_SMONTHNAME5,
340        LOCALE_SMONTHNAME6,
341        LOCALE_SMONTHNAME7,
342        LOCALE_SMONTHNAME8,
343        LOCALE_SMONTHNAME9,
344        LOCALE_SMONTHNAME10,
345        LOCALE_SMONTHNAME11,
346        LOCALE_SMONTHNAME12,
347    };
348    m_monthLabels.reserveCapacity(WTF_ARRAY_LENGTH(types));
349    for (unsigned i = 0; i < WTF_ARRAY_LENGTH(types); ++i) {
350        m_monthLabels.append(getLocaleInfoString(types[i]));
351        if (m_monthLabels.last().isEmpty()) {
352            m_monthLabels.shrink(0);
353            m_monthLabels.reserveCapacity(WTF_ARRAY_LENGTH(WTF::monthFullName));
354            for (unsigned m = 0; m < WTF_ARRAY_LENGTH(WTF::monthFullName); ++m)
355                m_monthLabels.append(WTF::monthFullName[m]);
356            return;
357        }
358    }
359}
360
361void LocaleWin::ensureWeekDayShortLabels()
362{
363    if (!m_weekDayShortLabels.isEmpty())
364        return;
365    const LCTYPE types[7] = {
366        LOCALE_SABBREVDAYNAME7, // Sunday
367        LOCALE_SABBREVDAYNAME1, // Monday
368        LOCALE_SABBREVDAYNAME2,
369        LOCALE_SABBREVDAYNAME3,
370        LOCALE_SABBREVDAYNAME4,
371        LOCALE_SABBREVDAYNAME5,
372        LOCALE_SABBREVDAYNAME6
373    };
374    m_weekDayShortLabels.reserveCapacity(WTF_ARRAY_LENGTH(types));
375    for (unsigned i = 0; i < WTF_ARRAY_LENGTH(types); ++i) {
376        m_weekDayShortLabels.append(getLocaleInfoString(types[i]));
377        if (m_weekDayShortLabels.last().isEmpty()) {
378            m_weekDayShortLabels.shrink(0);
379            m_weekDayShortLabels.reserveCapacity(WTF_ARRAY_LENGTH(WTF::weekdayName));
380            for (unsigned w = 0; w < WTF_ARRAY_LENGTH(WTF::weekdayName); ++w) {
381                // weekdayName starts with Monday.
382                m_weekDayShortLabels.append(WTF::weekdayName[(w + 6) % 7]);
383            }
384            return;
385        }
386    }
387}
388
389const Vector<String>& LocaleWin::monthLabels()
390{
391    ensureMonthLabels();
392    return m_monthLabels;
393}
394
395const Vector<String>& LocaleWin::weekDayShortLabels()
396{
397    ensureWeekDayShortLabels();
398    return m_weekDayShortLabels;
399}
400
401unsigned LocaleWin::firstDayOfWeek()
402{
403    return m_firstDayOfWeek;
404}
405
406bool LocaleWin::isRTL()
407{
408    WTF::Unicode::Direction dir = WTF::Unicode::direction(monthLabels()[0][0]);
409    return dir == WTF::Unicode::RightToLeft || dir == WTF::Unicode::RightToLeftArabic;
410}
411
412String LocaleWin::dateFormat()
413{
414    if (m_dateFormat.isNull())
415        m_dateFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SSHORTDATE));
416    return m_dateFormat;
417}
418
419String LocaleWin::dateFormat(const String& windowsFormat)
420{
421    return convertWindowsDateTimeFormat(windowsFormat);
422}
423
424String LocaleWin::monthFormat()
425{
426    if (m_monthFormat.isNull())
427        m_monthFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH));
428    return m_monthFormat;
429}
430
431String LocaleWin::shortMonthFormat()
432{
433    if (m_shortMonthFormat.isNull())
434        m_shortMonthFormat = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_SYEARMONTH)).replace("MMMM", "MMM");
435    return m_shortMonthFormat;
436}
437
438String LocaleWin::timeFormat()
439{
440    if (m_timeFormatWithSeconds.isNull())
441        m_timeFormatWithSeconds = convertWindowsDateTimeFormat(getLocaleInfoString(LOCALE_STIMEFORMAT));
442    return m_timeFormatWithSeconds;
443}
444
445String LocaleWin::shortTimeFormat()
446{
447    if (!m_timeFormatWithoutSeconds.isNull())
448        return m_timeFormatWithoutSeconds;
449    String format = getLocaleInfoString(LOCALE_SSHORTTIME);
450    // Vista or older Windows doesn't support LOCALE_SSHORTTIME.
451    if (format.isEmpty()) {
452        format = getLocaleInfoString(LOCALE_STIMEFORMAT);
453        StringBuilder builder;
454        builder.append(getLocaleInfoString(LOCALE_STIME));
455        builder.append("ss");
456        size_t pos = format.reverseFind(builder.toString());
457        if (pos != kNotFound)
458            format.remove(pos, builder.length());
459    }
460    m_timeFormatWithoutSeconds = convertWindowsDateTimeFormat(format);
461    return m_timeFormatWithoutSeconds;
462}
463
464String LocaleWin::dateTimeFormatWithSeconds()
465{
466    if (!m_dateTimeFormatWithSeconds.isNull())
467        return m_dateTimeFormatWithSeconds;
468    StringBuilder builder;
469    builder.append(dateFormat());
470    builder.append(' ');
471    builder.append(timeFormat());
472    m_dateTimeFormatWithSeconds = builder.toString();
473    return m_dateTimeFormatWithSeconds;
474}
475
476String LocaleWin::dateTimeFormatWithoutSeconds()
477{
478    if (!m_dateTimeFormatWithoutSeconds.isNull())
479        return m_dateTimeFormatWithoutSeconds;
480    StringBuilder builder;
481    builder.append(dateFormat());
482    builder.append(' ');
483    builder.append(shortTimeFormat());
484    m_dateTimeFormatWithoutSeconds = builder.toString();
485    return m_dateTimeFormatWithoutSeconds;
486}
487
488const Vector<String>& LocaleWin::shortMonthLabels()
489{
490    ensureShortMonthLabels();
491    return m_shortMonthLabels;
492}
493
494const Vector<String>& LocaleWin::standAloneMonthLabels()
495{
496    // Windows doesn't provide a way to get stand-alone month labels.
497    return monthLabels();
498}
499
500const Vector<String>& LocaleWin::shortStandAloneMonthLabels()
501{
502    // Windows doesn't provide a way to get stand-alone month labels.
503    return shortMonthLabels();
504}
505
506const Vector<String>& LocaleWin::timeAMPMLabels()
507{
508    if (m_timeAMPMLabels.isEmpty()) {
509        m_timeAMPMLabels.append(getLocaleInfoString(LOCALE_S1159));
510        m_timeAMPMLabels.append(getLocaleInfoString(LOCALE_S2359));
511    }
512    return m_timeAMPMLabels;
513}
514
515void LocaleWin::initializeLocaleData()
516{
517    if (m_didInitializeNumberData)
518        return;
519
520    Vector<String, DecimalSymbolsSize> symbols;
521    enum DigitSubstitution {
522        DigitSubstitutionContext = 0,
523        DigitSubstitution0to9 = 1,
524        DigitSubstitutionNative = 2,
525    };
526    DWORD digitSubstitution = DigitSubstitution0to9;
527    getLocaleInfo(LOCALE_IDIGITSUBSTITUTION, digitSubstitution);
528    if (digitSubstitution == DigitSubstitution0to9) {
529        symbols.append("0");
530        symbols.append("1");
531        symbols.append("2");
532        symbols.append("3");
533        symbols.append("4");
534        symbols.append("5");
535        symbols.append("6");
536        symbols.append("7");
537        symbols.append("8");
538        symbols.append("9");
539    } else {
540        String digits = getLocaleInfoString(LOCALE_SNATIVEDIGITS);
541        ASSERT(digits.length() >= 10);
542        for (unsigned i = 0; i < 10; ++i)
543            symbols.append(digits.substring(i, 1));
544    }
545    ASSERT(symbols.size() == DecimalSeparatorIndex);
546    symbols.append(getLocaleInfoString(LOCALE_SDECIMAL));
547    ASSERT(symbols.size() == GroupSeparatorIndex);
548    symbols.append(getLocaleInfoString(LOCALE_STHOUSAND));
549    ASSERT(symbols.size() == DecimalSymbolsSize);
550
551    String negativeSign = getLocaleInfoString(LOCALE_SNEGATIVESIGN);
552    enum NegativeFormat {
553        NegativeFormatParenthesis = 0,
554        NegativeFormatSignPrefix = 1,
555        NegativeFormatSignSpacePrefix = 2,
556        NegativeFormatSignSuffix = 3,
557        NegativeFormatSpaceSignSuffix = 4,
558    };
559    DWORD negativeFormat = NegativeFormatSignPrefix;
560    getLocaleInfo(LOCALE_INEGNUMBER, negativeFormat);
561    String negativePrefix = emptyString();
562    String negativeSuffix = emptyString();
563    switch (negativeFormat) {
564    case NegativeFormatParenthesis:
565        negativePrefix = "(";
566        negativeSuffix = ")";
567        break;
568    case NegativeFormatSignSpacePrefix:
569        negativePrefix = negativeSign + " ";
570        break;
571    case NegativeFormatSignSuffix:
572        negativeSuffix = negativeSign;
573        break;
574    case NegativeFormatSpaceSignSuffix:
575        negativeSuffix = " " + negativeSign;
576        break;
577    case NegativeFormatSignPrefix: // Fall through.
578    default:
579        negativePrefix = negativeSign;
580        break;
581    }
582    m_didInitializeNumberData = true;
583    setLocaleData(symbols, emptyString(), emptyString(), negativePrefix, negativeSuffix);
584}
585
586}
587