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 "third_party/blink/renderer/platform/text/locale_win.h"
32
33 #include <limits>
34 #include <memory>
35
36 #include "base/memory/ptr_util.h"
37 #include "base/stl_util.h"
38 #include "third_party/blink/renderer/platform/language.h"
39 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
40 #include "third_party/blink/renderer/platform/text/date_components.h"
41 #include "third_party/blink/renderer/platform/text/date_time_format.h"
42 #include "third_party/blink/renderer/platform/web_test_support.h"
43 #include "third_party/blink/renderer/platform/wtf/date_math.h"
44 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
45 #include "third_party/blink/renderer/platform/wtf/text/string_buffer.h"
46 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
47 #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
48 #include "ui/base/ui_base_features.h"
49
50 namespace blink {
51
ExtractLanguageCode(const String & locale)52 static String ExtractLanguageCode(const String& locale) {
53 size_t dash_position = locale.find('-');
54 if (dash_position == kNotFound)
55 return locale;
56 return locale.Left(dash_position);
57 }
58
LCIDFromLocaleInternal(LCID user_default_lcid,const String & user_default_language_code,const String & locale)59 static LCID LCIDFromLocaleInternal(LCID user_default_lcid,
60 const String& user_default_language_code,
61 const String& locale) {
62 String locale_language_code = ExtractLanguageCode(locale);
63 if (DeprecatedEqualIgnoringCase(locale_language_code,
64 user_default_language_code))
65 return user_default_lcid;
66 if (locale.length() >= LOCALE_NAME_MAX_LENGTH)
67 return 0;
68 UChar buffer[LOCALE_NAME_MAX_LENGTH];
69 if (locale.Is8Bit())
70 StringImpl::CopyChars(buffer, locale.Characters8(), locale.length());
71 else
72 StringImpl::CopyChars(buffer, locale.Characters16(), locale.length());
73 buffer[locale.length()] = '\0';
74 return ::LocaleNameToLCID(buffer, 0);
75 }
76
LCIDFromLocale(const String & locale,bool defaults_for_locale)77 static LCID LCIDFromLocale(const String& locale, bool defaults_for_locale) {
78 // According to MSDN, 9 is enough for LOCALE_SISO639LANGNAME.
79 const size_t kLanguageCodeBufferSize = 9;
80 WCHAR lowercase_language_code[kLanguageCodeBufferSize];
81 ::GetLocaleInfo(LOCALE_USER_DEFAULT,
82 LOCALE_SISO639LANGNAME |
83 (defaults_for_locale ? LOCALE_NOUSEROVERRIDE : 0),
84 lowercase_language_code, kLanguageCodeBufferSize);
85 String user_default_language_code = String(lowercase_language_code);
86
87 LCID lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT,
88 user_default_language_code, locale);
89 if (!lcid)
90 lcid = LCIDFromLocaleInternal(
91 LOCALE_USER_DEFAULT, user_default_language_code, DefaultLanguage());
92 return lcid;
93 }
94
Create(const String & locale)95 std::unique_ptr<Locale> Locale::Create(const String& locale) {
96 // Whether the default settings for the locale should be used, ignoring user
97 // overrides.
98 bool defaults_for_locale = WebTestSupport::IsRunningWebTest();
99 return LocaleWin::Create(LCIDFromLocale(locale, defaults_for_locale),
100 defaults_for_locale);
101 }
102
LocaleWin(LCID lcid,bool defaults_for_locale)103 inline LocaleWin::LocaleWin(LCID lcid, bool defaults_for_locale)
104 : lcid_(lcid),
105 did_initialize_number_data_(false),
106 defaults_for_locale_(defaults_for_locale) {
107 DWORD value = 0;
108 GetLocaleInfo(LOCALE_IFIRSTDAYOFWEEK |
109 (defaults_for_locale ? LOCALE_NOUSEROVERRIDE : 0),
110 value);
111 // 0:Monday, ..., 6:Sunday.
112 // We need 1 for Monday, 0 for Sunday.
113 first_day_of_week_ = (value + 1) % 7;
114 }
115
Create(LCID lcid,bool defaults_for_locale)116 std::unique_ptr<LocaleWin> LocaleWin::Create(LCID lcid,
117 bool defaults_for_locale) {
118 return base::WrapUnique(new LocaleWin(lcid, defaults_for_locale));
119 }
120
~LocaleWin()121 LocaleWin::~LocaleWin() {}
122
GetLocaleInfoString(LCTYPE type)123 String LocaleWin::GetLocaleInfoString(LCTYPE type) {
124 int buffer_size_with_nul = ::GetLocaleInfo(
125 lcid_, type | (defaults_for_locale_ ? LOCALE_NOUSEROVERRIDE : 0), 0, 0);
126 if (buffer_size_with_nul <= 0)
127 return String();
128 StringBuffer<UChar> buffer(buffer_size_with_nul);
129 ::GetLocaleInfo(lcid_,
130 type | (defaults_for_locale_ ? LOCALE_NOUSEROVERRIDE : 0),
131 buffer.Characters(), buffer_size_with_nul);
132 buffer.Shrink(buffer_size_with_nul - 1);
133 return String::Adopt(buffer);
134 }
135
GetLocaleInfo(LCTYPE type,DWORD & result)136 void LocaleWin::GetLocaleInfo(LCTYPE type, DWORD& result) {
137 ::GetLocaleInfo(lcid_, type | LOCALE_RETURN_NUMBER,
138 reinterpret_cast<LPWSTR>(&result),
139 sizeof(DWORD) / sizeof(TCHAR));
140 }
141
EnsureShortMonthLabels()142 void LocaleWin::EnsureShortMonthLabels() {
143 if (!short_month_labels_.IsEmpty())
144 return;
145 const LCTYPE kTypes[12] = {
146 LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2,
147 LOCALE_SABBREVMONTHNAME3, LOCALE_SABBREVMONTHNAME4,
148 LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6,
149 LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8,
150 LOCALE_SABBREVMONTHNAME9, LOCALE_SABBREVMONTHNAME10,
151 LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12,
152 };
153 short_month_labels_.ReserveCapacity(base::size(kTypes));
154 for (unsigned i = 0; i < base::size(kTypes); ++i) {
155 short_month_labels_.push_back(GetLocaleInfoString(kTypes[i]));
156 if (short_month_labels_.back().IsEmpty()) {
157 short_month_labels_.Shrink(0);
158 short_month_labels_.ReserveCapacity(base::size(WTF::kMonthName));
159 for (unsigned m = 0; m < base::size(WTF::kMonthName); ++m)
160 short_month_labels_.push_back(WTF::kMonthName[m]);
161 return;
162 }
163 }
164 }
165
166 // -------------------------------- Tokenized date format
167
CountContinuousLetters(const String & format,unsigned index)168 static unsigned CountContinuousLetters(const String& format, unsigned index) {
169 unsigned count = 1;
170 UChar reference = format[index];
171 while (index + 1 < format.length()) {
172 if (format[++index] != reference)
173 break;
174 ++count;
175 }
176 return count;
177 }
178
CommitLiteralToken(StringBuilder & literal_buffer,StringBuilder & converted)179 static void CommitLiteralToken(StringBuilder& literal_buffer,
180 StringBuilder& converted) {
181 if (literal_buffer.length() <= 0)
182 return;
183 DateTimeFormat::QuoteAndappend(literal_buffer.ToString(), converted);
184 literal_buffer.Clear();
185 }
186
187 // This function converts Windows date/time pattern format [1][2] into LDML date
188 // format pattern [3].
189 //
190 // i.e.
191 // We set h, H, m, s, d, dd, M, or y as is. They have same meaning in both of
192 // Windows and LDML.
193 // We need to convert the following patterns:
194 // t -> a
195 // tt -> a
196 // ddd -> EEE
197 // dddd -> EEEE
198 // g -> G
199 // gg -> ignore
200 //
201 // [1] http://msdn.microsoft.com/en-us/library/dd317787(v=vs.85).aspx
202 // [2] http://msdn.microsoft.com/en-us/library/dd318148(v=vs.85).aspx
203 // [3] LDML http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns
ConvertWindowsDateTimeFormat(const String & format)204 static String ConvertWindowsDateTimeFormat(const String& format) {
205 StringBuilder converted;
206 StringBuilder literal_buffer;
207 bool in_quote = false;
208 bool last_quote_can_be_literal = false;
209 for (unsigned i = 0; i < format.length(); ++i) {
210 UChar ch = format[i];
211 if (in_quote) {
212 if (ch == '\'') {
213 in_quote = false;
214 DCHECK(i);
215 if (last_quote_can_be_literal && format[i - 1] == '\'') {
216 literal_buffer.Append('\'');
217 last_quote_can_be_literal = false;
218 } else {
219 last_quote_can_be_literal = true;
220 }
221 } else {
222 literal_buffer.Append(ch);
223 }
224 continue;
225 }
226
227 if (ch == '\'') {
228 in_quote = true;
229 if (last_quote_can_be_literal && i > 0 && format[i - 1] == '\'') {
230 literal_buffer.Append(ch);
231 last_quote_can_be_literal = false;
232 } else {
233 last_quote_can_be_literal = true;
234 }
235 } else if (IsASCIIAlpha(ch)) {
236 CommitLiteralToken(literal_buffer, converted);
237 unsigned symbol_start = i;
238 unsigned count = CountContinuousLetters(format, i);
239 i += count - 1;
240 if (ch == 'h' || ch == 'H' || ch == 'm' || ch == 's' || ch == 'M' ||
241 ch == 'y') {
242 converted.Append(format, symbol_start, count);
243 } else if (ch == 'd') {
244 if (count <= 2)
245 converted.Append(format, symbol_start, count);
246 else if (count == 3)
247 converted.Append("EEE");
248 else
249 converted.Append("EEEE");
250 } else if (ch == 'g') {
251 if (count == 1) {
252 converted.Append('G');
253 } else {
254 // gg means imperial era in Windows.
255 // Just ignore it.
256 }
257 } else if (ch == 't') {
258 converted.Append('a');
259 } else {
260 literal_buffer.Append(format, symbol_start, count);
261 }
262 } else {
263 literal_buffer.Append(ch);
264 }
265 }
266 CommitLiteralToken(literal_buffer, converted);
267 return converted.ToString();
268 }
269
EnsureMonthLabels()270 void LocaleWin::EnsureMonthLabels() {
271 if (!month_labels_.IsEmpty())
272 return;
273 const LCTYPE kTypes[12] = {
274 LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3,
275 LOCALE_SMONTHNAME4, LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6,
276 LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8, LOCALE_SMONTHNAME9,
277 LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12,
278 };
279 month_labels_.ReserveCapacity(base::size(kTypes));
280 for (unsigned i = 0; i < base::size(kTypes); ++i) {
281 month_labels_.push_back(GetLocaleInfoString(kTypes[i]));
282 if (month_labels_.back().IsEmpty()) {
283 month_labels_.Shrink(0);
284 month_labels_.ReserveCapacity(base::size(WTF::kMonthFullName));
285 for (unsigned m = 0; m < base::size(WTF::kMonthFullName); ++m)
286 month_labels_.push_back(WTF::kMonthFullName[m]);
287 return;
288 }
289 }
290 }
291
EnsureWeekDayShortLabels()292 void LocaleWin::EnsureWeekDayShortLabels() {
293 if (!week_day_short_labels_.IsEmpty())
294 return;
295 const LCTYPE kTypes[7] = {LOCALE_SABBREVDAYNAME7, // Sunday
296 LOCALE_SABBREVDAYNAME1, // Monday
297 LOCALE_SABBREVDAYNAME2, LOCALE_SABBREVDAYNAME3,
298 LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5,
299 LOCALE_SABBREVDAYNAME6};
300 const LCTYPE kTypesRefresh[7] = {
301 LOCALE_SSHORTESTDAYNAME7, // Sunday
302 LOCALE_SSHORTESTDAYNAME1, // Monday
303 LOCALE_SSHORTESTDAYNAME2, LOCALE_SSHORTESTDAYNAME3,
304 LOCALE_SSHORTESTDAYNAME4, LOCALE_SSHORTESTDAYNAME5,
305 LOCALE_SSHORTESTDAYNAME6};
306 week_day_short_labels_.ReserveCapacity(base::size(kTypes));
307 for (unsigned i = 0; i < base::size(kTypes); ++i) {
308 if (features::IsFormControlsRefreshEnabled()) {
309 week_day_short_labels_.push_back(GetLocaleInfoString(kTypesRefresh[i]));
310 } else {
311 week_day_short_labels_.push_back(GetLocaleInfoString(kTypes[i]));
312 }
313 if (week_day_short_labels_.back().IsEmpty()) {
314 week_day_short_labels_.Shrink(0);
315 week_day_short_labels_.ReserveCapacity(base::size(WTF::kWeekdayName));
316 for (unsigned w = 0; w < base::size(WTF::kWeekdayName); ++w) {
317 // weekdayName starts with Monday.
318 week_day_short_labels_.push_back(WTF::kWeekdayName[(w + 6) % 7]);
319 }
320 return;
321 }
322 }
323 }
324
MonthLabels()325 const Vector<String>& LocaleWin::MonthLabels() {
326 EnsureMonthLabels();
327 return month_labels_;
328 }
329
WeekDayShortLabels()330 const Vector<String>& LocaleWin::WeekDayShortLabels() {
331 EnsureWeekDayShortLabels();
332 return week_day_short_labels_;
333 }
334
FirstDayOfWeek()335 unsigned LocaleWin::FirstDayOfWeek() {
336 return first_day_of_week_;
337 }
338
IsRTL()339 bool LocaleWin::IsRTL() {
340 WTF::unicode::CharDirection dir =
341 WTF::unicode::Direction(MonthLabels()[0][0]);
342 return dir == WTF::unicode::kRightToLeft ||
343 dir == WTF::unicode::kRightToLeftArabic;
344 }
345
DateFormat()346 String LocaleWin::DateFormat() {
347 if (date_format_.IsNull())
348 date_format_ =
349 ConvertWindowsDateTimeFormat(GetLocaleInfoString(LOCALE_SSHORTDATE));
350 return date_format_;
351 }
352
DateFormat(const String & windows_format)353 String LocaleWin::DateFormat(const String& windows_format) {
354 return ConvertWindowsDateTimeFormat(windows_format);
355 }
356
MonthFormat()357 String LocaleWin::MonthFormat() {
358 if (month_format_.IsNull())
359 month_format_ =
360 ConvertWindowsDateTimeFormat(GetLocaleInfoString(LOCALE_SYEARMONTH));
361 return month_format_;
362 }
363
ShortMonthFormat()364 String LocaleWin::ShortMonthFormat() {
365 if (short_month_format_.IsNull())
366 short_month_format_ =
367 ConvertWindowsDateTimeFormat(GetLocaleInfoString(LOCALE_SYEARMONTH))
368 .Replace("MMMM", "MMM");
369 return short_month_format_;
370 }
371
TimeFormat()372 String LocaleWin::TimeFormat() {
373 if (time_format_with_seconds_.IsNull())
374 time_format_with_seconds_ =
375 ConvertWindowsDateTimeFormat(GetLocaleInfoString(LOCALE_STIMEFORMAT));
376 return time_format_with_seconds_;
377 }
378
ShortTimeFormat()379 String LocaleWin::ShortTimeFormat() {
380 if (!time_format_without_seconds_.IsNull())
381 return time_format_without_seconds_;
382 String format = GetLocaleInfoString(LOCALE_SSHORTTIME);
383 // Vista or older Windows doesn't support LOCALE_SSHORTTIME.
384 if (format.IsEmpty()) {
385 format = GetLocaleInfoString(LOCALE_STIMEFORMAT);
386 StringBuilder builder;
387 builder.Append(GetLocaleInfoString(LOCALE_STIME));
388 builder.Append("ss");
389 size_t pos = format.ReverseFind(builder.ToString());
390 if (pos != kNotFound)
391 format.Remove(pos, builder.length());
392 }
393 time_format_without_seconds_ = ConvertWindowsDateTimeFormat(format);
394 return time_format_without_seconds_;
395 }
396
DateTimeFormatWithSeconds()397 String LocaleWin::DateTimeFormatWithSeconds() {
398 if (!date_time_format_with_seconds_.IsNull())
399 return date_time_format_with_seconds_;
400 StringBuilder builder;
401 builder.Append(DateFormat());
402 builder.Append(' ');
403 builder.Append(TimeFormat());
404 date_time_format_with_seconds_ = builder.ToString();
405 return date_time_format_with_seconds_;
406 }
407
DateTimeFormatWithoutSeconds()408 String LocaleWin::DateTimeFormatWithoutSeconds() {
409 if (!date_time_format_without_seconds_.IsNull())
410 return date_time_format_without_seconds_;
411 StringBuilder builder;
412 builder.Append(DateFormat());
413 builder.Append(' ');
414 builder.Append(ShortTimeFormat());
415 date_time_format_without_seconds_ = builder.ToString();
416 return date_time_format_without_seconds_;
417 }
418
ShortMonthLabels()419 const Vector<String>& LocaleWin::ShortMonthLabels() {
420 EnsureShortMonthLabels();
421 return short_month_labels_;
422 }
423
StandAloneMonthLabels()424 const Vector<String>& LocaleWin::StandAloneMonthLabels() {
425 // Windows doesn't provide a way to get stand-alone month labels.
426 return MonthLabels();
427 }
428
ShortStandAloneMonthLabels()429 const Vector<String>& LocaleWin::ShortStandAloneMonthLabels() {
430 // Windows doesn't provide a way to get stand-alone month labels.
431 return ShortMonthLabels();
432 }
433
TimeAMPMLabels()434 const Vector<String>& LocaleWin::TimeAMPMLabels() {
435 if (time_ampm_labels_.IsEmpty()) {
436 time_ampm_labels_.push_back(GetLocaleInfoString(LOCALE_S1159));
437 time_ampm_labels_.push_back(GetLocaleInfoString(LOCALE_S2359));
438 }
439 return time_ampm_labels_;
440 }
441
InitializeLocaleData()442 void LocaleWin::InitializeLocaleData() {
443 if (did_initialize_number_data_)
444 return;
445
446 Vector<String, kDecimalSymbolsSize> symbols;
447 enum DigitSubstitution {
448 kDigitSubstitutionContext = 0,
449 kDigitSubstitution0to9 = 1,
450 kDigitSubstitutionNative = 2,
451 };
452 DWORD digit_substitution = kDigitSubstitution0to9;
453 GetLocaleInfo(LOCALE_IDIGITSUBSTITUTION, digit_substitution);
454 if (digit_substitution == kDigitSubstitution0to9) {
455 symbols.push_back("0");
456 symbols.push_back("1");
457 symbols.push_back("2");
458 symbols.push_back("3");
459 symbols.push_back("4");
460 symbols.push_back("5");
461 symbols.push_back("6");
462 symbols.push_back("7");
463 symbols.push_back("8");
464 symbols.push_back("9");
465 } else {
466 String digits = GetLocaleInfoString(LOCALE_SNATIVEDIGITS);
467 DCHECK_GE(digits.length(), 10u);
468 for (unsigned i = 0; i < 10; ++i)
469 symbols.push_back(digits.Substring(i, 1));
470 }
471 DCHECK(symbols.size() == kDecimalSeparatorIndex);
472 symbols.push_back(GetLocaleInfoString(LOCALE_SDECIMAL));
473 DCHECK(symbols.size() == kGroupSeparatorIndex);
474 symbols.push_back(GetLocaleInfoString(LOCALE_STHOUSAND));
475 DCHECK(symbols.size() == kDecimalSymbolsSize);
476
477 String negative_sign = GetLocaleInfoString(LOCALE_SNEGATIVESIGN);
478 enum NegativeFormat {
479 kNegativeFormatParenthesis = 0,
480 kNegativeFormatSignPrefix = 1,
481 kNegativeFormatSignSpacePrefix = 2,
482 kNegativeFormatSignSuffix = 3,
483 kNegativeFormatSpaceSignSuffix = 4,
484 };
485 DWORD negative_format = kNegativeFormatSignPrefix;
486 GetLocaleInfo(LOCALE_INEGNUMBER, negative_format);
487 String negative_prefix = g_empty_string;
488 String negative_suffix = g_empty_string;
489 switch (negative_format) {
490 case kNegativeFormatParenthesis:
491 negative_prefix = "(";
492 negative_suffix = ")";
493 break;
494 case kNegativeFormatSignSpacePrefix:
495 negative_prefix = negative_sign + " ";
496 break;
497 case kNegativeFormatSignSuffix:
498 negative_suffix = negative_sign;
499 break;
500 case kNegativeFormatSpaceSignSuffix:
501 negative_suffix = " " + negative_sign;
502 break;
503 case kNegativeFormatSignPrefix: // Fall through.
504 default:
505 negative_prefix = negative_sign;
506 break;
507 }
508 did_initialize_number_data_ = true;
509 SetLocaleData(symbols, g_empty_string, g_empty_string, negative_prefix,
510 negative_suffix);
511 }
512 }
513