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 "core/platform/text/mac/LocaleMac.h" 33 34#import <Foundation/NSDateFormatter.h> 35#import <Foundation/NSLocale.h> 36#include "core/platform/Language.h" 37#include "core/platform/LocalizedStrings.h" 38#include "wtf/DateMath.h" 39#include "wtf/PassOwnPtr.h" 40#include "wtf/RetainPtr.h" 41#include "wtf/text/StringBuilder.h" 42 43using namespace std; 44 45namespace WebCore { 46 47static inline String languageFromLocale(const String& locale) 48{ 49 String normalizedLocale = locale; 50 normalizedLocale.replace('-', '_'); 51 size_t separatorPosition = normalizedLocale.find('_'); 52 if (separatorPosition == notFound) 53 return normalizedLocale; 54 return normalizedLocale.left(separatorPosition); 55} 56 57static RetainPtr<NSLocale> determineLocale(const String& locale) 58{ 59 RetainPtr<NSLocale> currentLocale = [NSLocale currentLocale]; 60 String currentLocaleLanguage = languageFromLocale(String([currentLocale.get() localeIdentifier])); 61 String localeLanguage = languageFromLocale(locale); 62 if (equalIgnoringCase(currentLocaleLanguage, localeLanguage)) 63 return currentLocale; 64 // It seems initWithLocaleIdentifier accepts dash-separated locale identifier. 65 return RetainPtr<NSLocale>(AdoptNS, [[NSLocale alloc] initWithLocaleIdentifier:locale]); 66} 67 68PassOwnPtr<Locale> Locale::create(const AtomicString& locale) 69{ 70 return LocaleMac::create(determineLocale(locale.string()).get()); 71} 72 73static RetainPtr<NSDateFormatter> createDateTimeFormatter(NSLocale* locale, NSCalendar* calendar, NSDateFormatterStyle dateStyle, NSDateFormatterStyle timeStyle) 74{ 75 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 76 [formatter setLocale:locale]; 77 [formatter setDateStyle:dateStyle]; 78 [formatter setTimeStyle:timeStyle]; 79 [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; 80 [formatter setCalendar:calendar]; 81 return adoptNS(formatter); 82} 83 84LocaleMac::LocaleMac(NSLocale* locale) 85 : m_locale(locale) 86 , m_gregorianCalendar(AdoptNS, [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]) 87 , m_didInitializeNumberData(false) 88{ 89 NSArray* availableLanguages = [NSLocale ISOLanguageCodes]; 90 // NSLocale returns a lower case NSLocaleLanguageCode so we don't have care about case. 91 NSString* language = [m_locale.get() objectForKey:NSLocaleLanguageCode]; 92 if ([availableLanguages indexOfObject:language] == NSNotFound) 93 m_locale.adoptNS([[NSLocale alloc] initWithLocaleIdentifier:defaultLanguage()]); 94 [m_gregorianCalendar.get() setLocale:m_locale.get()]; 95} 96 97LocaleMac::~LocaleMac() 98{ 99} 100 101PassOwnPtr<LocaleMac> LocaleMac::create(const String& localeIdentifier) 102{ 103 RetainPtr<NSLocale> locale = [[NSLocale alloc] initWithLocaleIdentifier:localeIdentifier]; 104 return adoptPtr(new LocaleMac(locale.get())); 105} 106 107PassOwnPtr<LocaleMac> LocaleMac::create(NSLocale* locale) 108{ 109 return adoptPtr(new LocaleMac(locale)); 110} 111 112RetainPtr<NSDateFormatter> LocaleMac::shortDateFormatter() 113{ 114 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterShortStyle, NSDateFormatterNoStyle); 115} 116 117const Vector<String>& LocaleMac::monthLabels() 118{ 119 if (!m_monthLabels.isEmpty()) 120 return m_monthLabels; 121 m_monthLabels.reserveCapacity(12); 122 NSArray *array = [shortDateFormatter().get() monthSymbols]; 123 if ([array count] == 12) { 124 for (unsigned i = 0; i < 12; ++i) 125 m_monthLabels.append(String([array objectAtIndex:i])); 126 return m_monthLabels; 127 } 128 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(WTF::monthFullName); ++i) 129 m_monthLabels.append(WTF::monthFullName[i]); 130 return m_monthLabels; 131} 132 133#if ENABLE(CALENDAR_PICKER) 134const Vector<String>& LocaleMac::weekDayShortLabels() 135{ 136 if (!m_weekDayShortLabels.isEmpty()) 137 return m_weekDayShortLabels; 138 m_weekDayShortLabels.reserveCapacity(7); 139 NSArray *array = [shortDateFormatter().get() shortWeekdaySymbols]; 140 if ([array count] == 7) { 141 for (unsigned i = 0; i < 7; ++i) 142 m_weekDayShortLabels.append(String([array objectAtIndex:i])); 143 return m_weekDayShortLabels; 144 } 145 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(WTF::weekdayName); ++i) { 146 // weekdayName starts with Monday. 147 m_weekDayShortLabels.append(WTF::weekdayName[(i + 6) % 7]); 148 } 149 return m_weekDayShortLabels; 150} 151 152unsigned LocaleMac::firstDayOfWeek() 153{ 154 // The document for NSCalendar - firstWeekday doesn't have an explanation of 155 // firstWeekday value. We can guess it by the document of NSDateComponents - 156 // weekDay, so it can be 1 through 7 and 1 is Sunday. 157 return [m_gregorianCalendar.get() firstWeekday] - 1; 158} 159 160bool LocaleMac::isRTL() 161{ 162 return NSLocaleLanguageDirectionRightToLeft == [NSLocale characterDirectionForLanguage:[NSLocale canonicalLanguageIdentifierFromString:[m_locale.get() localeIdentifier]]]; 163} 164#endif 165 166RetainPtr<NSDateFormatter> LocaleMac::timeFormatter() 167{ 168 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterNoStyle, NSDateFormatterMediumStyle); 169} 170 171RetainPtr<NSDateFormatter> LocaleMac::shortTimeFormatter() 172{ 173 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterNoStyle, NSDateFormatterShortStyle); 174} 175 176RetainPtr<NSDateFormatter> LocaleMac::dateTimeFormatterWithSeconds() 177{ 178 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterShortStyle, NSDateFormatterMediumStyle); 179} 180 181RetainPtr<NSDateFormatter> LocaleMac::dateTimeFormatterWithoutSeconds() 182{ 183 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterShortStyle, NSDateFormatterShortStyle); 184} 185 186String LocaleMac::dateFormat() 187{ 188 if (!m_dateFormat.isNull()) 189 return m_dateFormat; 190 m_dateFormat = [shortDateFormatter().get() dateFormat]; 191 return m_dateFormat; 192} 193 194String LocaleMac::monthFormat() 195{ 196 if (!m_monthFormat.isNull()) 197 return m_monthFormat; 198 // Gets a format for "MMMM" because Windows API always provides formats for 199 // "MMMM" in some locales. 200 m_monthFormat = [NSDateFormatter dateFormatFromTemplate:@"yyyyMMMM" options:0 locale:m_locale.get()]; 201 return m_monthFormat; 202} 203 204String LocaleMac::shortMonthFormat() 205{ 206 if (!m_shortMonthFormat.isNull()) 207 return m_shortMonthFormat; 208 m_shortMonthFormat = [NSDateFormatter dateFormatFromTemplate:@"yyyyMMM" options:0 locale:m_locale.get()]; 209 return m_shortMonthFormat; 210} 211 212String LocaleMac::timeFormat() 213{ 214 if (!m_timeFormatWithSeconds.isNull()) 215 return m_timeFormatWithSeconds; 216 m_timeFormatWithSeconds = [timeFormatter().get() dateFormat]; 217 return m_timeFormatWithSeconds; 218} 219 220String LocaleMac::shortTimeFormat() 221{ 222 if (!m_timeFormatWithoutSeconds.isNull()) 223 return m_timeFormatWithoutSeconds; 224 m_timeFormatWithoutSeconds = [shortTimeFormatter().get() dateFormat]; 225 return m_timeFormatWithoutSeconds; 226} 227 228String LocaleMac::dateTimeFormatWithSeconds() 229{ 230 if (!m_dateTimeFormatWithSeconds.isNull()) 231 return m_dateTimeFormatWithSeconds; 232 m_dateTimeFormatWithSeconds = [dateTimeFormatterWithSeconds().get() dateFormat]; 233 return m_dateTimeFormatWithSeconds; 234} 235 236String LocaleMac::dateTimeFormatWithoutSeconds() 237{ 238 if (!m_dateTimeFormatWithoutSeconds.isNull()) 239 return m_dateTimeFormatWithoutSeconds; 240 m_dateTimeFormatWithoutSeconds = [dateTimeFormatterWithoutSeconds().get() dateFormat]; 241 return m_dateTimeFormatWithoutSeconds; 242} 243 244const Vector<String>& LocaleMac::shortMonthLabels() 245{ 246 if (!m_shortMonthLabels.isEmpty()) 247 return m_shortMonthLabels; 248 m_shortMonthLabels.reserveCapacity(12); 249 NSArray *array = [shortDateFormatter().get() shortMonthSymbols]; 250 if ([array count] == 12) { 251 for (unsigned i = 0; i < 12; ++i) 252 m_shortMonthLabels.append([array objectAtIndex:i]); 253 return m_shortMonthLabels; 254 } 255 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(WTF::monthName); ++i) 256 m_shortMonthLabels.append(WTF::monthName[i]); 257 return m_shortMonthLabels; 258} 259 260const Vector<String>& LocaleMac::standAloneMonthLabels() 261{ 262 if (!m_standAloneMonthLabels.isEmpty()) 263 return m_standAloneMonthLabels; 264 NSArray *array = [shortDateFormatter().get() standaloneMonthSymbols]; 265 if ([array count] == 12) { 266 m_standAloneMonthLabels.reserveCapacity(12); 267 for (unsigned i = 0; i < 12; ++i) 268 m_standAloneMonthLabels.append([array objectAtIndex:i]); 269 return m_standAloneMonthLabels; 270 } 271 m_standAloneMonthLabels = shortMonthLabels(); 272 return m_standAloneMonthLabels; 273} 274 275const Vector<String>& LocaleMac::shortStandAloneMonthLabels() 276{ 277 if (!m_shortStandAloneMonthLabels.isEmpty()) 278 return m_shortStandAloneMonthLabels; 279 NSArray *array = [shortDateFormatter().get() shortStandaloneMonthSymbols]; 280 if ([array count] == 12) { 281 m_shortStandAloneMonthLabels.reserveCapacity(12); 282 for (unsigned i = 0; i < 12; ++i) 283 m_shortStandAloneMonthLabels.append([array objectAtIndex:i]); 284 return m_shortStandAloneMonthLabels; 285 } 286 m_shortStandAloneMonthLabels = shortMonthLabels(); 287 return m_shortStandAloneMonthLabels; 288} 289 290const Vector<String>& LocaleMac::timeAMPMLabels() 291{ 292 if (!m_timeAMPMLabels.isEmpty()) 293 return m_timeAMPMLabels; 294 m_timeAMPMLabels.reserveCapacity(2); 295 RetainPtr<NSDateFormatter> formatter = shortTimeFormatter(); 296 m_timeAMPMLabels.append([formatter.get() AMSymbol]); 297 m_timeAMPMLabels.append([formatter.get() PMSymbol]); 298 return m_timeAMPMLabels; 299} 300 301void LocaleMac::initializeLocaleData() 302{ 303 if (m_didInitializeNumberData) 304 return; 305 m_didInitializeNumberData = true; 306 307 RetainPtr<NSNumberFormatter> formatter(AdoptNS, [[NSNumberFormatter alloc] init]); 308 [formatter.get() setLocale:m_locale.get()]; 309 [formatter.get() setNumberStyle:NSNumberFormatterDecimalStyle]; 310 [formatter.get() setUsesGroupingSeparator:NO]; 311 312 RetainPtr<NSNumber> sampleNumber(AdoptNS, [[NSNumber alloc] initWithDouble:9876543210]); 313 String nineToZero([formatter.get() stringFromNumber:sampleNumber.get()]); 314 if (nineToZero.length() != 10) 315 return; 316 Vector<String, DecimalSymbolsSize> symbols; 317 for (unsigned i = 0; i < 10; ++i) 318 symbols.append(nineToZero.substring(9 - i, 1)); 319 ASSERT(symbols.size() == DecimalSeparatorIndex); 320 symbols.append([formatter.get() decimalSeparator]); 321 ASSERT(symbols.size() == GroupSeparatorIndex); 322 symbols.append([formatter.get() groupingSeparator]); 323 ASSERT(symbols.size() == DecimalSymbolsSize); 324 325 String positivePrefix([formatter.get() positivePrefix]); 326 String positiveSuffix([formatter.get() positiveSuffix]); 327 String negativePrefix([formatter.get() negativePrefix]); 328 String negativeSuffix([formatter.get() negativeSuffix]); 329 setLocaleData(symbols, positivePrefix, positiveSuffix, negativePrefix, negativeSuffix); 330} 331 332} 333