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