| /* |
| * Copyright (C) 2012 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/platform/text/locale_win.h" |
| |
| #include <limits> |
| #include <memory> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/stl_util.h" |
| #include "third_party/blink/renderer/platform/date_components.h" |
| #include "third_party/blink/renderer/platform/language.h" |
| #include "third_party/blink/renderer/platform/text/date_time_format.h" |
| #include "third_party/blink/renderer/platform/web_test_support.h" |
| #include "third_party/blink/renderer/platform/wtf/date_math.h" |
| #include "third_party/blink/renderer/platform/wtf/hash_map.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_buffer.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_hash.h" |
| #include "third_party/blink/renderer/platform/wtf/time.h" |
| |
| namespace blink { |
| |
| static String ExtractLanguageCode(const String& locale) { |
| size_t dash_position = locale.find('-'); |
| if (dash_position == kNotFound) |
| return locale; |
| return locale.Left(dash_position); |
| } |
| |
| static LCID LCIDFromLocaleInternal(LCID user_default_lcid, |
| const String& user_default_language_code, |
| const String& locale) { |
| String locale_language_code = ExtractLanguageCode(locale); |
| if (DeprecatedEqualIgnoringCase(locale_language_code, |
| user_default_language_code)) |
| return user_default_lcid; |
| if (locale.length() >= LOCALE_NAME_MAX_LENGTH) |
| return 0; |
| UChar buffer[LOCALE_NAME_MAX_LENGTH]; |
| if (locale.Is8Bit()) |
| StringImpl::CopyChars(buffer, locale.Characters8(), locale.length()); |
| else |
| StringImpl::CopyChars(buffer, locale.Characters16(), locale.length()); |
| buffer[locale.length()] = '\0'; |
| return ::LocaleNameToLCID(buffer, 0); |
| } |
| |
| static LCID LCIDFromLocale(const String& locale, bool defaults_for_locale) { |
| // According to MSDN, 9 is enough for LOCALE_SISO639LANGNAME. |
| const size_t kLanguageCodeBufferSize = 9; |
| WCHAR lowercase_language_code[kLanguageCodeBufferSize]; |
| ::GetLocaleInfo(LOCALE_USER_DEFAULT, |
| LOCALE_SISO639LANGNAME | |
| (defaults_for_locale ? LOCALE_NOUSEROVERRIDE : 0), |
| lowercase_language_code, kLanguageCodeBufferSize); |
| String user_default_language_code = String(lowercase_language_code); |
| |
| LCID lcid = LCIDFromLocaleInternal(LOCALE_USER_DEFAULT, |
| user_default_language_code, locale); |
| if (!lcid) |
| lcid = LCIDFromLocaleInternal( |
| LOCALE_USER_DEFAULT, user_default_language_code, DefaultLanguage()); |
| return lcid; |
| } |
| |
| std::unique_ptr<Locale> Locale::Create(const String& locale) { |
| // Whether the default settings for the locale should be used, ignoring user |
| // overrides. |
| bool defaults_for_locale = WebTestSupport::IsRunningWebTest(); |
| return LocaleWin::Create(LCIDFromLocale(locale, defaults_for_locale), |
| defaults_for_locale); |
| } |
| |
| inline LocaleWin::LocaleWin(LCID lcid, bool defaults_for_locale) |
| : lcid_(lcid), |
| did_initialize_number_data_(false), |
| defaults_for_locale_(defaults_for_locale) { |
| DWORD value = 0; |
| GetLocaleInfo(LOCALE_IFIRSTDAYOFWEEK | |
| (defaults_for_locale ? LOCALE_NOUSEROVERRIDE : 0), |
| value); |
| // 0:Monday, ..., 6:Sunday. |
| // We need 1 for Monday, 0 for Sunday. |
| first_day_of_week_ = (value + 1) % 7; |
| } |
| |
| std::unique_ptr<LocaleWin> LocaleWin::Create(LCID lcid, |
| bool defaults_for_locale) { |
| return base::WrapUnique(new LocaleWin(lcid, defaults_for_locale)); |
| } |
| |
| LocaleWin::~LocaleWin() {} |
| |
| String LocaleWin::GetLocaleInfoString(LCTYPE type) { |
| int buffer_size_with_nul = ::GetLocaleInfo( |
| lcid_, type | (defaults_for_locale_ ? LOCALE_NOUSEROVERRIDE : 0), 0, 0); |
| if (buffer_size_with_nul <= 0) |
| return String(); |
| StringBuffer<UChar> buffer(buffer_size_with_nul); |
| ::GetLocaleInfo(lcid_, |
| type | (defaults_for_locale_ ? LOCALE_NOUSEROVERRIDE : 0), |
| buffer.Characters(), buffer_size_with_nul); |
| buffer.Shrink(buffer_size_with_nul - 1); |
| return String::Adopt(buffer); |
| } |
| |
| void LocaleWin::GetLocaleInfo(LCTYPE type, DWORD& result) { |
| ::GetLocaleInfo(lcid_, type | LOCALE_RETURN_NUMBER, |
| reinterpret_cast<LPWSTR>(&result), |
| sizeof(DWORD) / sizeof(TCHAR)); |
| } |
| |
| void LocaleWin::EnsureShortMonthLabels() { |
| if (!short_month_labels_.IsEmpty()) |
| return; |
| const LCTYPE kTypes[12] = { |
| LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2, |
| LOCALE_SABBREVMONTHNAME3, LOCALE_SABBREVMONTHNAME4, |
| LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6, |
| LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8, |
| LOCALE_SABBREVMONTHNAME9, LOCALE_SABBREVMONTHNAME10, |
| LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12, |
| }; |
| short_month_labels_.ReserveCapacity(base::size(kTypes)); |
| for (unsigned i = 0; i < base::size(kTypes); ++i) { |
| short_month_labels_.push_back(GetLocaleInfoString(kTypes[i])); |
| if (short_month_labels_.back().IsEmpty()) { |
| short_month_labels_.Shrink(0); |
| short_month_labels_.ReserveCapacity(base::size(WTF::kMonthName)); |
| for (unsigned m = 0; m < base::size(WTF::kMonthName); ++m) |
| short_month_labels_.push_back(WTF::kMonthName[m]); |
| return; |
| } |
| } |
| } |
| |
| // -------------------------------- Tokenized date format |
| |
| static unsigned CountContinuousLetters(const String& format, unsigned index) { |
| unsigned count = 1; |
| UChar reference = format[index]; |
| while (index + 1 < format.length()) { |
| if (format[++index] != reference) |
| break; |
| ++count; |
| } |
| return count; |
| } |
| |
| static void CommitLiteralToken(StringBuilder& literal_buffer, |
| StringBuilder& converted) { |
| if (literal_buffer.length() <= 0) |
| return; |
| DateTimeFormat::QuoteAndappend(literal_buffer.ToString(), converted); |
| literal_buffer.Clear(); |
| } |
| |
| // This function converts Windows date/time pattern format [1][2] into LDML date |
| // format pattern [3]. |
| // |
| // i.e. |
| // We set h, H, m, s, d, dd, M, or y as is. They have same meaning in both of |
| // Windows and LDML. |
| // We need to convert the following patterns: |
| // t -> a |
| // tt -> a |
| // ddd -> EEE |
| // dddd -> EEEE |
| // g -> G |
| // gg -> ignore |
| // |
| // [1] http://msdn.microsoft.com/en-us/library/dd317787(v=vs.85).aspx |
| // [2] http://msdn.microsoft.com/en-us/library/dd318148(v=vs.85).aspx |
| // [3] LDML http://unicode.org/reports/tr35/tr35-6.html#Date_Format_Patterns |
| static String ConvertWindowsDateTimeFormat(const String& format) { |
| StringBuilder converted; |
| StringBuilder literal_buffer; |
| bool in_quote = false; |
| bool last_quote_can_be_literal = false; |
| for (unsigned i = 0; i < format.length(); ++i) { |
| UChar ch = format[i]; |
| if (in_quote) { |
| if (ch == '\'') { |
| in_quote = false; |
| DCHECK(i); |
| if (last_quote_can_be_literal && format[i - 1] == '\'') { |
| literal_buffer.Append('\''); |
| last_quote_can_be_literal = false; |
| } else { |
| last_quote_can_be_literal = true; |
| } |
| } else { |
| literal_buffer.Append(ch); |
| } |
| continue; |
| } |
| |
| if (ch == '\'') { |
| in_quote = true; |
| if (last_quote_can_be_literal && i > 0 && format[i - 1] == '\'') { |
| literal_buffer.Append(ch); |
| last_quote_can_be_literal = false; |
| } else { |
| last_quote_can_be_literal = true; |
| } |
| } else if (IsASCIIAlpha(ch)) { |
| CommitLiteralToken(literal_buffer, converted); |
| unsigned symbol_start = i; |
| unsigned count = CountContinuousLetters(format, i); |
| i += count - 1; |
| if (ch == 'h' || ch == 'H' || ch == 'm' || ch == 's' || ch == 'M' || |
| ch == 'y') { |
| converted.Append(format, symbol_start, count); |
| } else if (ch == 'd') { |
| if (count <= 2) |
| converted.Append(format, symbol_start, count); |
| else if (count == 3) |
| converted.Append("EEE"); |
| else |
| converted.Append("EEEE"); |
| } else if (ch == 'g') { |
| if (count == 1) { |
| converted.Append('G'); |
| } else { |
| // gg means imperial era in Windows. |
| // Just ignore it. |
| } |
| } else if (ch == 't') { |
| converted.Append('a'); |
| } else { |
| literal_buffer.Append(format, symbol_start, count); |
| } |
| } else { |
| literal_buffer.Append(ch); |
| } |
| } |
| CommitLiteralToken(literal_buffer, converted); |
| return converted.ToString(); |
| } |
| |
| void LocaleWin::EnsureMonthLabels() { |
| if (!month_labels_.IsEmpty()) |
| return; |
| const LCTYPE kTypes[12] = { |
| LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3, |
| LOCALE_SMONTHNAME4, LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6, |
| LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8, LOCALE_SMONTHNAME9, |
| LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12, |
| }; |
| month_labels_.ReserveCapacity(base::size(kTypes)); |
| for (unsigned i = 0; i < base::size(kTypes); ++i) { |
| month_labels_.push_back(GetLocaleInfoString(kTypes[i])); |
| if (month_labels_.back().IsEmpty()) { |
| month_labels_.Shrink(0); |
| month_labels_.ReserveCapacity(base::size(WTF::kMonthFullName)); |
| for (unsigned m = 0; m < base::size(WTF::kMonthFullName); ++m) |
| month_labels_.push_back(WTF::kMonthFullName[m]); |
| return; |
| } |
| } |
| } |
| |
| void LocaleWin::EnsureWeekDayShortLabels() { |
| if (!week_day_short_labels_.IsEmpty()) |
| return; |
| const LCTYPE kTypes[7] = {LOCALE_SABBREVDAYNAME7, // Sunday |
| LOCALE_SABBREVDAYNAME1, // Monday |
| LOCALE_SABBREVDAYNAME2, LOCALE_SABBREVDAYNAME3, |
| LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5, |
| LOCALE_SABBREVDAYNAME6}; |
| week_day_short_labels_.ReserveCapacity(base::size(kTypes)); |
| for (unsigned i = 0; i < base::size(kTypes); ++i) { |
| week_day_short_labels_.push_back(GetLocaleInfoString(kTypes[i])); |
| if (week_day_short_labels_.back().IsEmpty()) { |
| week_day_short_labels_.Shrink(0); |
| week_day_short_labels_.ReserveCapacity(base::size(WTF::kWeekdayName)); |
| for (unsigned w = 0; w < base::size(WTF::kWeekdayName); ++w) { |
| // weekdayName starts with Monday. |
| week_day_short_labels_.push_back(WTF::kWeekdayName[(w + 6) % 7]); |
| } |
| return; |
| } |
| } |
| } |
| |
| const Vector<String>& LocaleWin::MonthLabels() { |
| EnsureMonthLabels(); |
| return month_labels_; |
| } |
| |
| const Vector<String>& LocaleWin::WeekDayShortLabels() { |
| EnsureWeekDayShortLabels(); |
| return week_day_short_labels_; |
| } |
| |
| unsigned LocaleWin::FirstDayOfWeek() { |
| return first_day_of_week_; |
| } |
| |
| bool LocaleWin::IsRTL() { |
| WTF::unicode::CharDirection dir = |
| WTF::unicode::Direction(MonthLabels()[0][0]); |
| return dir == WTF::unicode::kRightToLeft || |
| dir == WTF::unicode::kRightToLeftArabic; |
| } |
| |
| String LocaleWin::DateFormat() { |
| if (date_format_.IsNull()) |
| date_format_ = |
| ConvertWindowsDateTimeFormat(GetLocaleInfoString(LOCALE_SSHORTDATE)); |
| return date_format_; |
| } |
| |
| String LocaleWin::DateFormat(const String& windows_format) { |
| return ConvertWindowsDateTimeFormat(windows_format); |
| } |
| |
| String LocaleWin::MonthFormat() { |
| if (month_format_.IsNull()) |
| month_format_ = |
| ConvertWindowsDateTimeFormat(GetLocaleInfoString(LOCALE_SYEARMONTH)); |
| return month_format_; |
| } |
| |
| String LocaleWin::ShortMonthFormat() { |
| if (short_month_format_.IsNull()) |
| short_month_format_ = |
| ConvertWindowsDateTimeFormat(GetLocaleInfoString(LOCALE_SYEARMONTH)) |
| .Replace("MMMM", "MMM"); |
| return short_month_format_; |
| } |
| |
| String LocaleWin::TimeFormat() { |
| if (time_format_with_seconds_.IsNull()) |
| time_format_with_seconds_ = |
| ConvertWindowsDateTimeFormat(GetLocaleInfoString(LOCALE_STIMEFORMAT)); |
| return time_format_with_seconds_; |
| } |
| |
| String LocaleWin::ShortTimeFormat() { |
| if (!time_format_without_seconds_.IsNull()) |
| return time_format_without_seconds_; |
| String format = GetLocaleInfoString(LOCALE_SSHORTTIME); |
| // Vista or older Windows doesn't support LOCALE_SSHORTTIME. |
| if (format.IsEmpty()) { |
| format = GetLocaleInfoString(LOCALE_STIMEFORMAT); |
| StringBuilder builder; |
| builder.Append(GetLocaleInfoString(LOCALE_STIME)); |
| builder.Append("ss"); |
| size_t pos = format.ReverseFind(builder.ToString()); |
| if (pos != kNotFound) |
| format.Remove(pos, builder.length()); |
| } |
| time_format_without_seconds_ = ConvertWindowsDateTimeFormat(format); |
| return time_format_without_seconds_; |
| } |
| |
| String LocaleWin::DateTimeFormatWithSeconds() { |
| if (!date_time_format_with_seconds_.IsNull()) |
| return date_time_format_with_seconds_; |
| StringBuilder builder; |
| builder.Append(DateFormat()); |
| builder.Append(' '); |
| builder.Append(TimeFormat()); |
| date_time_format_with_seconds_ = builder.ToString(); |
| return date_time_format_with_seconds_; |
| } |
| |
| String LocaleWin::DateTimeFormatWithoutSeconds() { |
| if (!date_time_format_without_seconds_.IsNull()) |
| return date_time_format_without_seconds_; |
| StringBuilder builder; |
| builder.Append(DateFormat()); |
| builder.Append(' '); |
| builder.Append(ShortTimeFormat()); |
| date_time_format_without_seconds_ = builder.ToString(); |
| return date_time_format_without_seconds_; |
| } |
| |
| const Vector<String>& LocaleWin::ShortMonthLabels() { |
| EnsureShortMonthLabels(); |
| return short_month_labels_; |
| } |
| |
| const Vector<String>& LocaleWin::StandAloneMonthLabels() { |
| // Windows doesn't provide a way to get stand-alone month labels. |
| return MonthLabels(); |
| } |
| |
| const Vector<String>& LocaleWin::ShortStandAloneMonthLabels() { |
| // Windows doesn't provide a way to get stand-alone month labels. |
| return ShortMonthLabels(); |
| } |
| |
| const Vector<String>& LocaleWin::TimeAMPMLabels() { |
| if (time_ampm_labels_.IsEmpty()) { |
| time_ampm_labels_.push_back(GetLocaleInfoString(LOCALE_S1159)); |
| time_ampm_labels_.push_back(GetLocaleInfoString(LOCALE_S2359)); |
| } |
| return time_ampm_labels_; |
| } |
| |
| void LocaleWin::InitializeLocaleData() { |
| if (did_initialize_number_data_) |
| return; |
| |
| Vector<String, kDecimalSymbolsSize> symbols; |
| enum DigitSubstitution { |
| kDigitSubstitutionContext = 0, |
| kDigitSubstitution0to9 = 1, |
| kDigitSubstitutionNative = 2, |
| }; |
| DWORD digit_substitution = kDigitSubstitution0to9; |
| GetLocaleInfo(LOCALE_IDIGITSUBSTITUTION, digit_substitution); |
| if (digit_substitution == kDigitSubstitution0to9) { |
| symbols.push_back("0"); |
| symbols.push_back("1"); |
| symbols.push_back("2"); |
| symbols.push_back("3"); |
| symbols.push_back("4"); |
| symbols.push_back("5"); |
| symbols.push_back("6"); |
| symbols.push_back("7"); |
| symbols.push_back("8"); |
| symbols.push_back("9"); |
| } else { |
| String digits = GetLocaleInfoString(LOCALE_SNATIVEDIGITS); |
| DCHECK_GE(digits.length(), 10u); |
| for (unsigned i = 0; i < 10; ++i) |
| symbols.push_back(digits.Substring(i, 1)); |
| } |
| DCHECK(symbols.size() == kDecimalSeparatorIndex); |
| symbols.push_back(GetLocaleInfoString(LOCALE_SDECIMAL)); |
| DCHECK(symbols.size() == kGroupSeparatorIndex); |
| symbols.push_back(GetLocaleInfoString(LOCALE_STHOUSAND)); |
| DCHECK(symbols.size() == kDecimalSymbolsSize); |
| |
| String negative_sign = GetLocaleInfoString(LOCALE_SNEGATIVESIGN); |
| enum NegativeFormat { |
| kNegativeFormatParenthesis = 0, |
| kNegativeFormatSignPrefix = 1, |
| kNegativeFormatSignSpacePrefix = 2, |
| kNegativeFormatSignSuffix = 3, |
| kNegativeFormatSpaceSignSuffix = 4, |
| }; |
| DWORD negative_format = kNegativeFormatSignPrefix; |
| GetLocaleInfo(LOCALE_INEGNUMBER, negative_format); |
| String negative_prefix = g_empty_string; |
| String negative_suffix = g_empty_string; |
| switch (negative_format) { |
| case kNegativeFormatParenthesis: |
| negative_prefix = "("; |
| negative_suffix = ")"; |
| break; |
| case kNegativeFormatSignSpacePrefix: |
| negative_prefix = negative_sign + " "; |
| break; |
| case kNegativeFormatSignSuffix: |
| negative_suffix = negative_sign; |
| break; |
| case kNegativeFormatSpaceSignSuffix: |
| negative_suffix = " " + negative_sign; |
| break; |
| case kNegativeFormatSignPrefix: // Fall through. |
| default: |
| negative_prefix = negative_sign; |
| break; |
| } |
| did_initialize_number_data_ = true; |
| SetLocaleData(symbols, g_empty_string, g_empty_string, negative_prefix, |
| negative_suffix); |
| } |
| } |