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