blob: 490dbff01e43b39d11d82f0d15b3a0944846a93b [file] [log] [blame]
/*
* 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_mac.h"
#import <Foundation/NSDateFormatter.h>
#import <Foundation/NSLocale.h>
#include <memory>
#include "base/memory/ptr_util.h"
#include "third_party/blink/renderer/platform/language.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/retain_ptr.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
static inline String LanguageFromLocale(const String& locale) {
String normalized_locale = locale;
normalized_locale.Replace('-', '_');
size_t separator_position = normalized_locale.find('_');
if (separator_position == kNotFound)
return normalized_locale;
return normalized_locale.Left(separator_position);
}
static RetainPtr<NSLocale> DetermineLocale(const String& locale) {
if (!WebTestSupport::IsRunningWebTest()) {
RetainPtr<NSLocale> current_locale = [NSLocale currentLocale];
String current_locale_language =
LanguageFromLocale(String([current_locale.Get() localeIdentifier]));
String locale_language = LanguageFromLocale(locale);
if (DeprecatedEqualIgnoringCase(current_locale_language, locale_language))
return current_locale;
}
// It seems initWithLocaleIdentifier accepts dash-separated locale identifier.
return RetainPtr<NSLocale>(
kAdoptNS, [[NSLocale alloc] initWithLocaleIdentifier:locale]);
}
std::unique_ptr<Locale> Locale::Create(const String& locale) {
return LocaleMac::Create(DetermineLocale(locale).Get());
}
static RetainPtr<NSDateFormatter> CreateDateTimeFormatter(
NSLocale* locale,
NSCalendar* calendar,
NSDateFormatterStyle date_style,
NSDateFormatterStyle time_style) {
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
[formatter setLocale:locale];
[formatter setDateStyle:date_style];
[formatter setTimeStyle:time_style];
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[formatter setCalendar:calendar];
return AdoptNS(formatter);
}
LocaleMac::LocaleMac(NSLocale* locale)
: locale_(locale),
gregorian_calendar_(
kAdoptNS,
[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]),
did_initialize_number_data_(false) {
NSArray* available_languages = [NSLocale ISOLanguageCodes];
// NSLocale returns a lower case NSLocaleLanguageCode so we don't have care
// about case.
NSString* language = [locale_.Get() objectForKey:NSLocaleLanguageCode];
if ([available_languages indexOfObject:language] == NSNotFound)
locale_.AdoptNS(
[[NSLocale alloc] initWithLocaleIdentifier:DefaultLanguage()]);
[gregorian_calendar_.Get() setLocale:locale_.Get()];
}
LocaleMac::~LocaleMac() {}
std::unique_ptr<LocaleMac> LocaleMac::Create(const String& locale_identifier) {
RetainPtr<NSLocale> locale =
[[NSLocale alloc] initWithLocaleIdentifier:locale_identifier];
return base::WrapUnique(new LocaleMac(locale.Get()));
}
std::unique_ptr<LocaleMac> LocaleMac::Create(NSLocale* locale) {
return base::WrapUnique(new LocaleMac(locale));
}
RetainPtr<NSDateFormatter> LocaleMac::ShortDateFormatter() {
return CreateDateTimeFormatter(locale_.Get(), gregorian_calendar_.Get(),
NSDateFormatterShortStyle,
NSDateFormatterNoStyle);
}
const Vector<String>& LocaleMac::MonthLabels() {
if (!month_labels_.IsEmpty())
return month_labels_;
month_labels_.ReserveCapacity(12);
NSArray* array = [ShortDateFormatter().Get() monthSymbols];
if ([array count] == 12) {
for (unsigned i = 0; i < 12; ++i)
month_labels_.push_back(String([array objectAtIndex:i]));
return month_labels_;
}
for (unsigned i = 0; i < arraysize(WTF::kMonthFullName); ++i)
month_labels_.push_back(WTF::kMonthFullName[i]);
return month_labels_;
}
const Vector<String>& LocaleMac::WeekDayShortLabels() {
if (!week_day_short_labels_.IsEmpty())
return week_day_short_labels_;
week_day_short_labels_.ReserveCapacity(7);
NSArray* array = [ShortDateFormatter().Get() shortWeekdaySymbols];
if ([array count] == 7) {
for (unsigned i = 0; i < 7; ++i)
week_day_short_labels_.push_back(String([array objectAtIndex:i]));
return week_day_short_labels_;
}
for (unsigned i = 0; i < arraysize(WTF::kWeekdayName); ++i) {
// weekdayName starts with Monday.
week_day_short_labels_.push_back(WTF::kWeekdayName[(i + 6) % 7]);
}
return week_day_short_labels_;
}
unsigned LocaleMac::FirstDayOfWeek() {
// The document for NSCalendar - firstWeekday doesn't have an explanation of
// firstWeekday value. We can guess it by the document of NSDateComponents -
// weekDay, so it can be 1 through 7 and 1 is Sunday.
return [gregorian_calendar_.Get() firstWeekday] - 1;
}
bool LocaleMac::IsRTL() {
return NSLocaleLanguageDirectionRightToLeft ==
[NSLocale characterDirectionForLanguage:
[NSLocale canonicalLanguageIdentifierFromString:
[locale_.Get() localeIdentifier]]];
}
RetainPtr<NSDateFormatter> LocaleMac::TimeFormatter() {
return CreateDateTimeFormatter(locale_.Get(), gregorian_calendar_.Get(),
NSDateFormatterNoStyle,
NSDateFormatterMediumStyle);
}
RetainPtr<NSDateFormatter> LocaleMac::ShortTimeFormatter() {
return CreateDateTimeFormatter(locale_.Get(), gregorian_calendar_.Get(),
NSDateFormatterNoStyle,
NSDateFormatterShortStyle);
}
RetainPtr<NSDateFormatter> LocaleMac::DateTimeFormatterWithSeconds() {
return CreateDateTimeFormatter(locale_.Get(), gregorian_calendar_.Get(),
NSDateFormatterShortStyle,
NSDateFormatterMediumStyle);
}
RetainPtr<NSDateFormatter> LocaleMac::DateTimeFormatterWithoutSeconds() {
return CreateDateTimeFormatter(locale_.Get(), gregorian_calendar_.Get(),
NSDateFormatterShortStyle,
NSDateFormatterShortStyle);
}
String LocaleMac::DateFormat() {
if (!date_format_.IsNull())
return date_format_;
date_format_ = [ShortDateFormatter().Get() dateFormat];
return date_format_;
}
String LocaleMac::MonthFormat() {
if (!month_format_.IsNull())
return month_format_;
// Gets a format for "MMMM" because Windows API always provides formats for
// "MMMM" in some locales.
month_format_ = [NSDateFormatter dateFormatFromTemplate:@"yyyyMMMM"
options:0
locale:locale_.Get()];
return month_format_;
}
String LocaleMac::ShortMonthFormat() {
if (!short_month_format_.IsNull())
return short_month_format_;
short_month_format_ = [NSDateFormatter dateFormatFromTemplate:@"yyyyMMM"
options:0
locale:locale_.Get()];
return short_month_format_;
}
String LocaleMac::TimeFormat() {
if (!time_format_with_seconds_.IsNull())
return time_format_with_seconds_;
time_format_with_seconds_ = [TimeFormatter().Get() dateFormat];
return time_format_with_seconds_;
}
String LocaleMac::ShortTimeFormat() {
if (!time_format_without_seconds_.IsNull())
return time_format_without_seconds_;
time_format_without_seconds_ = [ShortTimeFormatter().Get() dateFormat];
return time_format_without_seconds_;
}
String LocaleMac::DateTimeFormatWithSeconds() {
if (!date_time_format_with_seconds_.IsNull())
return date_time_format_with_seconds_;
date_time_format_with_seconds_ =
[DateTimeFormatterWithSeconds().Get() dateFormat];
return date_time_format_with_seconds_;
}
String LocaleMac::DateTimeFormatWithoutSeconds() {
if (!date_time_format_without_seconds_.IsNull())
return date_time_format_without_seconds_;
date_time_format_without_seconds_ =
[DateTimeFormatterWithoutSeconds().Get() dateFormat];
return date_time_format_without_seconds_;
}
const Vector<String>& LocaleMac::ShortMonthLabels() {
if (!short_month_labels_.IsEmpty())
return short_month_labels_;
short_month_labels_.ReserveCapacity(12);
NSArray* array = [ShortDateFormatter().Get() shortMonthSymbols];
if ([array count] == 12) {
for (unsigned i = 0; i < 12; ++i)
short_month_labels_.push_back([array objectAtIndex:i]);
return short_month_labels_;
}
for (unsigned i = 0; i < arraysize(WTF::kMonthName); ++i)
short_month_labels_.push_back(WTF::kMonthName[i]);
return short_month_labels_;
}
const Vector<String>& LocaleMac::StandAloneMonthLabels() {
if (!stand_alone_month_labels_.IsEmpty())
return stand_alone_month_labels_;
NSArray* array = [ShortDateFormatter().Get() standaloneMonthSymbols];
if ([array count] == 12) {
stand_alone_month_labels_.ReserveCapacity(12);
for (unsigned i = 0; i < 12; ++i)
stand_alone_month_labels_.push_back([array objectAtIndex:i]);
return stand_alone_month_labels_;
}
stand_alone_month_labels_ = ShortMonthLabels();
return stand_alone_month_labels_;
}
const Vector<String>& LocaleMac::ShortStandAloneMonthLabels() {
if (!short_stand_alone_month_labels_.IsEmpty())
return short_stand_alone_month_labels_;
NSArray* array = [ShortDateFormatter().Get() shortStandaloneMonthSymbols];
if ([array count] == 12) {
short_stand_alone_month_labels_.ReserveCapacity(12);
for (unsigned i = 0; i < 12; ++i)
short_stand_alone_month_labels_.push_back([array objectAtIndex:i]);
return short_stand_alone_month_labels_;
}
short_stand_alone_month_labels_ = ShortMonthLabels();
return short_stand_alone_month_labels_;
}
const Vector<String>& LocaleMac::TimeAMPMLabels() {
if (!time_ampm_labels_.IsEmpty())
return time_ampm_labels_;
time_ampm_labels_.ReserveCapacity(2);
RetainPtr<NSDateFormatter> formatter = ShortTimeFormatter();
time_ampm_labels_.push_back([formatter.Get() AMSymbol]);
time_ampm_labels_.push_back([formatter.Get() PMSymbol]);
return time_ampm_labels_;
}
void LocaleMac::InitializeLocaleData() {
if (did_initialize_number_data_)
return;
did_initialize_number_data_ = true;
RetainPtr<NSNumberFormatter> formatter(kAdoptNS,
[[NSNumberFormatter alloc] init]);
[formatter.Get() setLocale:locale_.Get()];
[formatter.Get() setNumberStyle:NSNumberFormatterDecimalStyle];
[formatter.Get() setUsesGroupingSeparator:NO];
RetainPtr<NSNumber> sample_number(
kAdoptNS, [[NSNumber alloc] initWithDouble:9876543210]);
String nine_to_zero([formatter.Get() stringFromNumber:sample_number.Get()]);
if (nine_to_zero.length() != 10)
return;
Vector<String, kDecimalSymbolsSize> symbols;
for (unsigned i = 0; i < 10; ++i)
symbols.push_back(nine_to_zero.Substring(9 - i, 1));
DCHECK(symbols.size() == kDecimalSeparatorIndex);
symbols.push_back([formatter.Get() decimalSeparator]);
DCHECK(symbols.size() == kGroupSeparatorIndex);
symbols.push_back([formatter.Get() groupingSeparator]);
DCHECK(symbols.size() == kDecimalSymbolsSize);
String positive_prefix([formatter.Get() positivePrefix]);
String positive_suffix([formatter.Get() positiveSuffix]);
String negative_prefix([formatter.Get() negativePrefix]);
String negative_suffix([formatter.Get() negativeSuffix]);
SetLocaleData(symbols, positive_prefix, positive_suffix, negative_prefix,
negative_suffix);
}
}