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 42 namespace blink { 43 44 static 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 54 static 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 65 PassOwnPtr<Locale> Locale::create(const String& locale) 66 { 67 return LocaleMac::create(determineLocale(locale).get()); 68 } 69 70 static 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 81 LocaleMac::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 94 LocaleMac::~LocaleMac() 95 { 96 } 97 98 PassOwnPtr<LocaleMac> LocaleMac::create(const String& localeIdentifier) 99 { 100 RetainPtr<NSLocale> locale = [[NSLocale alloc] initWithLocaleIdentifier:localeIdentifier]; 101 return adoptPtr(new LocaleMac(locale.get())); 102 } 103 104 PassOwnPtr<LocaleMac> LocaleMac::create(NSLocale* locale) 105 { 106 return adoptPtr(new LocaleMac(locale)); 107 } 108 109 RetainPtr<NSDateFormatter> LocaleMac::shortDateFormatter() 110 { 111 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterShortStyle, NSDateFormatterNoStyle); 112 } 113 114 const 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 130 const 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 148 unsigned 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 156 bool LocaleMac::isRTL() 157 { 158 return NSLocaleLanguageDirectionRightToLeft == [NSLocale characterDirectionForLanguage:[NSLocale canonicalLanguageIdentifierFromString:[m_locale.get() localeIdentifier]]]; 159 } 160 161 RetainPtr<NSDateFormatter> LocaleMac::timeFormatter() 162 { 163 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterNoStyle, NSDateFormatterMediumStyle); 164 } 165 166 RetainPtr<NSDateFormatter> LocaleMac::shortTimeFormatter() 167 { 168 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterNoStyle, NSDateFormatterShortStyle); 169 } 170 171 RetainPtr<NSDateFormatter> LocaleMac::dateTimeFormatterWithSeconds() 172 { 173 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterShortStyle, NSDateFormatterMediumStyle); 174 } 175 176 RetainPtr<NSDateFormatter> LocaleMac::dateTimeFormatterWithoutSeconds() 177 { 178 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterShortStyle, NSDateFormatterShortStyle); 179 } 180 181 String LocaleMac::dateFormat() 182 { 183 if (!m_dateFormat.isNull()) 184 return m_dateFormat; 185 m_dateFormat = [shortDateFormatter().get() dateFormat]; 186 return m_dateFormat; 187 } 188 189 String 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 199 String 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 207 String LocaleMac::timeFormat() 208 { 209 if (!m_timeFormatWithSeconds.isNull()) 210 return m_timeFormatWithSeconds; 211 m_timeFormatWithSeconds = [timeFormatter().get() dateFormat]; 212 return m_timeFormatWithSeconds; 213 } 214 215 String LocaleMac::shortTimeFormat() 216 { 217 if (!m_timeFormatWithoutSeconds.isNull()) 218 return m_timeFormatWithoutSeconds; 219 m_timeFormatWithoutSeconds = [shortTimeFormatter().get() dateFormat]; 220 return m_timeFormatWithoutSeconds; 221 } 222 223 String LocaleMac::dateTimeFormatWithSeconds() 224 { 225 if (!m_dateTimeFormatWithSeconds.isNull()) 226 return m_dateTimeFormatWithSeconds; 227 m_dateTimeFormatWithSeconds = [dateTimeFormatterWithSeconds().get() dateFormat]; 228 return m_dateTimeFormatWithSeconds; 229 } 230 231 String LocaleMac::dateTimeFormatWithoutSeconds() 232 { 233 if (!m_dateTimeFormatWithoutSeconds.isNull()) 234 return m_dateTimeFormatWithoutSeconds; 235 m_dateTimeFormatWithoutSeconds = [dateTimeFormatterWithoutSeconds().get() dateFormat]; 236 return m_dateTimeFormatWithoutSeconds; 237 } 238 239 const 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 255 const 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 270 const 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 285 const 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 296 void 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