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_mac.h" 32 33#import <Foundation/Foundation.h> 34 35#include <memory> 36 37#include "base/memory/ptr_util.h" 38#include "base/stl_util.h" 39#include "third_party/blink/renderer/platform/language.h" 40#include "third_party/blink/renderer/platform/runtime_enabled_features.h" 41#include "third_party/blink/renderer/platform/web_test_support.h" 42#include "third_party/blink/renderer/platform/wtf/date_math.h" 43#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" 44#include "ui/base/ui_base_features.h" 45 46namespace blink { 47 48static inline String LanguageFromLocale(const String& locale) { 49 String normalized_locale = locale; 50 normalized_locale.Replace('-', '_'); 51 size_t separator_position = normalized_locale.find('_'); 52 if (separator_position == kNotFound) 53 return normalized_locale; 54 return normalized_locale.Left(separator_position); 55} 56 57static NSLocale* DetermineLocale(const String& locale) { 58 if (!WebTestSupport::IsRunningWebTest()) { 59 NSLocale* current_locale = [NSLocale currentLocale]; 60 String current_locale_language = 61 LanguageFromLocale(String([current_locale localeIdentifier])); 62 String locale_language = LanguageFromLocale(locale); 63 if (DeprecatedEqualIgnoringCase(current_locale_language, locale_language)) 64 return current_locale; 65 } 66 // It seems localeWithLocaleIdentifier accepts dash-separated locale 67 // identifier. 68 return [NSLocale localeWithLocaleIdentifier:locale]; 69} 70 71std::unique_ptr<Locale> Locale::Create(const String& locale) { 72 return LocaleMac::Create(DetermineLocale(locale)); 73} 74 75static base::scoped_nsobject<NSDateFormatter> CreateDateTimeFormatter( 76 NSLocale* locale, 77 NSCalendar* calendar, 78 NSDateFormatterStyle date_style, 79 NSDateFormatterStyle time_style) { 80 NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; 81 [formatter setLocale:locale]; 82 [formatter setDateStyle:date_style]; 83 [formatter setTimeStyle:time_style]; 84 [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; 85 [formatter setCalendar:calendar]; 86 return base::scoped_nsobject<NSDateFormatter>(formatter); 87} 88 89LocaleMac::LocaleMac(NSLocale* locale) 90 : locale_([locale retain]), 91 gregorian_calendar_([[NSCalendar alloc] 92 initWithCalendarIdentifier:NSCalendarIdentifierGregorian]), 93 did_initialize_number_data_(false) { 94 NSArray* available_languages = [NSLocale ISOLanguageCodes]; 95 // NSLocale returns a lower case NSLocaleLanguageCode so we don't have care 96 // about case. 97 NSString* language = [locale_ objectForKey:NSLocaleLanguageCode]; 98 if ([available_languages indexOfObject:language] == NSNotFound) 99 locale_.reset( 100 [[NSLocale alloc] initWithLocaleIdentifier:DefaultLanguage()]); 101 [gregorian_calendar_ setLocale:locale_]; 102} 103 104LocaleMac::~LocaleMac() {} 105 106std::unique_ptr<LocaleMac> LocaleMac::Create(const String& locale_identifier) { 107 NSLocale* locale = [NSLocale localeWithLocaleIdentifier:locale_identifier]; 108 return LocaleMac::Create(locale); 109} 110 111std::unique_ptr<LocaleMac> LocaleMac::Create(NSLocale* locale) { 112 return base::WrapUnique(new LocaleMac(locale)); 113} 114 115base::scoped_nsobject<NSDateFormatter> LocaleMac::ShortDateFormatter() { 116 return CreateDateTimeFormatter(locale_, gregorian_calendar_, 117 NSDateFormatterShortStyle, 118 NSDateFormatterNoStyle); 119} 120 121const Vector<String>& LocaleMac::MonthLabels() { 122 if (!month_labels_.IsEmpty()) 123 return month_labels_; 124 month_labels_.ReserveCapacity(12); 125 NSArray* array = [ShortDateFormatter() monthSymbols]; 126 if ([array count] == 12) { 127 for (unsigned i = 0; i < 12; ++i) 128 month_labels_.push_back(String([array objectAtIndex:i])); 129 return month_labels_; 130 } 131 for (unsigned i = 0; i < base::size(WTF::kMonthFullName); ++i) 132 month_labels_.push_back(WTF::kMonthFullName[i]); 133 return month_labels_; 134} 135 136const Vector<String>& LocaleMac::WeekDayShortLabels() { 137 if (!week_day_short_labels_.IsEmpty()) 138 return week_day_short_labels_; 139 week_day_short_labels_.ReserveCapacity(7); 140 NSArray* array = features::IsFormControlsRefreshEnabled() 141 ? [ShortDateFormatter() veryShortWeekdaySymbols] 142 : [ShortDateFormatter() shortWeekdaySymbols]; 143 if ([array count] == 7) { 144 for (unsigned i = 0; i < 7; ++i) 145 week_day_short_labels_.push_back(String([array objectAtIndex:i])); 146 return week_day_short_labels_; 147 } 148 for (unsigned i = 0; i < base::size(WTF::kWeekdayName); ++i) { 149 // weekdayName starts with Monday. 150 week_day_short_labels_.push_back(WTF::kWeekdayName[(i + 6) % 7]); 151 } 152 return week_day_short_labels_; 153} 154 155unsigned LocaleMac::FirstDayOfWeek() { 156 // The document for NSCalendar - firstWeekday doesn't have an explanation of 157 // firstWeekday value. We can guess it by the document of NSDateComponents - 158 // weekDay, so it can be 1 through 7 and 1 is Sunday. 159 return [gregorian_calendar_ firstWeekday] - 1; 160} 161 162bool LocaleMac::IsRTL() { 163 return NSLocaleLanguageDirectionRightToLeft == 164 [NSLocale characterDirectionForLanguage: 165 [NSLocale canonicalLanguageIdentifierFromString: 166 [locale_ localeIdentifier]]]; 167} 168 169base::scoped_nsobject<NSDateFormatter> LocaleMac::TimeFormatter() { 170 return CreateDateTimeFormatter(locale_, gregorian_calendar_, 171 NSDateFormatterNoStyle, 172 NSDateFormatterMediumStyle); 173} 174 175base::scoped_nsobject<NSDateFormatter> LocaleMac::ShortTimeFormatter() { 176 return CreateDateTimeFormatter(locale_, gregorian_calendar_, 177 NSDateFormatterNoStyle, 178 NSDateFormatterShortStyle); 179} 180 181base::scoped_nsobject<NSDateFormatter> 182LocaleMac::DateTimeFormatterWithSeconds() { 183 return CreateDateTimeFormatter(locale_, gregorian_calendar_, 184 NSDateFormatterShortStyle, 185 NSDateFormatterMediumStyle); 186} 187 188base::scoped_nsobject<NSDateFormatter> 189LocaleMac::DateTimeFormatterWithoutSeconds() { 190 return CreateDateTimeFormatter(locale_, gregorian_calendar_, 191 NSDateFormatterShortStyle, 192 NSDateFormatterShortStyle); 193} 194 195String LocaleMac::DateFormat() { 196 if (!date_format_.IsNull()) 197 return date_format_; 198 date_format_ = [ShortDateFormatter() dateFormat]; 199 return date_format_; 200} 201 202String LocaleMac::MonthFormat() { 203 if (!month_format_.IsNull()) 204 return month_format_; 205 // Gets a format for "MMMM" because Windows API always provides formats for 206 // "MMMM" in some locales. 207 month_format_ = [NSDateFormatter dateFormatFromTemplate:@"yyyyMMMM" 208 options:0 209 locale:locale_]; 210 return month_format_; 211} 212 213String LocaleMac::ShortMonthFormat() { 214 if (!short_month_format_.IsNull()) 215 return short_month_format_; 216 short_month_format_ = [NSDateFormatter dateFormatFromTemplate:@"yyyyMMM" 217 options:0 218 locale:locale_]; 219 return short_month_format_; 220} 221 222String LocaleMac::TimeFormat() { 223 if (!time_format_with_seconds_.IsNull()) 224 return time_format_with_seconds_; 225 time_format_with_seconds_ = [TimeFormatter() dateFormat]; 226 return time_format_with_seconds_; 227} 228 229String LocaleMac::ShortTimeFormat() { 230 if (!time_format_without_seconds_.IsNull()) 231 return time_format_without_seconds_; 232 time_format_without_seconds_ = [ShortTimeFormatter() dateFormat]; 233 return time_format_without_seconds_; 234} 235 236String LocaleMac::DateTimeFormatWithSeconds() { 237 if (!date_time_format_with_seconds_.IsNull()) 238 return date_time_format_with_seconds_; 239 date_time_format_with_seconds_ = [DateTimeFormatterWithSeconds() dateFormat]; 240 return date_time_format_with_seconds_; 241} 242 243String LocaleMac::DateTimeFormatWithoutSeconds() { 244 if (!date_time_format_without_seconds_.IsNull()) 245 return date_time_format_without_seconds_; 246 date_time_format_without_seconds_ = 247 [DateTimeFormatterWithoutSeconds() dateFormat]; 248 return date_time_format_without_seconds_; 249} 250 251const Vector<String>& LocaleMac::ShortMonthLabels() { 252 if (!short_month_labels_.IsEmpty()) 253 return short_month_labels_; 254 short_month_labels_.ReserveCapacity(12); 255 NSArray* array = [ShortDateFormatter() shortMonthSymbols]; 256 if ([array count] == 12) { 257 for (unsigned i = 0; i < 12; ++i) 258 short_month_labels_.push_back([array objectAtIndex:i]); 259 return short_month_labels_; 260 } 261 for (unsigned i = 0; i < base::size(WTF::kMonthName); ++i) 262 short_month_labels_.push_back(WTF::kMonthName[i]); 263 return short_month_labels_; 264} 265 266const Vector<String>& LocaleMac::StandAloneMonthLabels() { 267 if (!stand_alone_month_labels_.IsEmpty()) 268 return stand_alone_month_labels_; 269 NSArray* array = [ShortDateFormatter() standaloneMonthSymbols]; 270 if ([array count] == 12) { 271 stand_alone_month_labels_.ReserveCapacity(12); 272 for (unsigned i = 0; i < 12; ++i) 273 stand_alone_month_labels_.push_back([array objectAtIndex:i]); 274 return stand_alone_month_labels_; 275 } 276 stand_alone_month_labels_ = ShortMonthLabels(); 277 return stand_alone_month_labels_; 278} 279 280const Vector<String>& LocaleMac::ShortStandAloneMonthLabels() { 281 if (!short_stand_alone_month_labels_.IsEmpty()) 282 return short_stand_alone_month_labels_; 283 NSArray* array = [ShortDateFormatter() shortStandaloneMonthSymbols]; 284 if ([array count] == 12) { 285 short_stand_alone_month_labels_.ReserveCapacity(12); 286 for (unsigned i = 0; i < 12; ++i) 287 short_stand_alone_month_labels_.push_back([array objectAtIndex:i]); 288 return short_stand_alone_month_labels_; 289 } 290 short_stand_alone_month_labels_ = ShortMonthLabels(); 291 return short_stand_alone_month_labels_; 292} 293 294const Vector<String>& LocaleMac::TimeAMPMLabels() { 295 if (!time_ampm_labels_.IsEmpty()) 296 return time_ampm_labels_; 297 time_ampm_labels_.ReserveCapacity(2); 298 base::scoped_nsobject<NSDateFormatter> formatter(ShortTimeFormatter()); 299 time_ampm_labels_.push_back([formatter AMSymbol]); 300 time_ampm_labels_.push_back([formatter PMSymbol]); 301 return time_ampm_labels_; 302} 303 304void LocaleMac::InitializeLocaleData() { 305 if (did_initialize_number_data_) 306 return; 307 did_initialize_number_data_ = true; 308 309 base::scoped_nsobject<NSNumberFormatter> formatter( 310 [[NSNumberFormatter alloc] init]); 311 [formatter setLocale:locale_]; 312 [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; 313 [formatter setUsesGroupingSeparator:NO]; 314 315 base::scoped_nsobject<NSNumber> sample_number( 316 [[NSNumber alloc] initWithDouble:9876543210]); 317 String nine_to_zero([formatter stringFromNumber:sample_number]); 318 if (nine_to_zero.length() != 10) 319 return; 320 Vector<String, kDecimalSymbolsSize> symbols; 321 for (unsigned i = 0; i < 10; ++i) 322 symbols.push_back(nine_to_zero.Substring(9 - i, 1)); 323 DCHECK(symbols.size() == kDecimalSeparatorIndex); 324 symbols.push_back([formatter decimalSeparator]); 325 DCHECK(symbols.size() == kGroupSeparatorIndex); 326 symbols.push_back([formatter groupingSeparator]); 327 DCHECK(symbols.size() == kDecimalSymbolsSize); 328 329 String positive_prefix([formatter positivePrefix]); 330 String positive_suffix([formatter positiveSuffix]); 331 String negative_prefix([formatter negativePrefix]); 332 String negative_suffix([formatter negativeSuffix]); 333 SetLocaleData(symbols, positive_prefix, positive_suffix, negative_prefix, 334 negative_suffix); 335} 336} 337