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 43 using namespace std; 44 45 namespace WebCore { 46 47 static 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 57 static 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 68 PassOwnPtr<Locale> Locale::create(const AtomicString& locale) 69 { 70 return LocaleMac::create(determineLocale(locale.string()).get()); 71 } 72 73 static 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 84 LocaleMac::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 97 LocaleMac::~LocaleMac() 98 { 99 } 100 101 PassOwnPtr<LocaleMac> LocaleMac::create(const String& localeIdentifier) 102 { 103 RetainPtr<NSLocale> locale = [[NSLocale alloc] initWithLocaleIdentifier:localeIdentifier]; 104 return adoptPtr(new LocaleMac(locale.get())); 105 } 106 107 PassOwnPtr<LocaleMac> LocaleMac::create(NSLocale* locale) 108 { 109 return adoptPtr(new LocaleMac(locale)); 110 } 111 112 RetainPtr<NSDateFormatter> LocaleMac::shortDateFormatter() 113 { 114 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterShortStyle, NSDateFormatterNoStyle); 115 } 116 117 const 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) 134 const 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 152 unsigned 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 160 bool LocaleMac::isRTL() 161 { 162 return NSLocaleLanguageDirectionRightToLeft == [NSLocale characterDirectionForLanguage:[NSLocale canonicalLanguageIdentifierFromString:[m_locale.get() localeIdentifier]]]; 163 } 164 #endif 165 166 RetainPtr<NSDateFormatter> LocaleMac::timeFormatter() 167 { 168 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterNoStyle, NSDateFormatterMediumStyle); 169 } 170 171 RetainPtr<NSDateFormatter> LocaleMac::shortTimeFormatter() 172 { 173 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterNoStyle, NSDateFormatterShortStyle); 174 } 175 176 RetainPtr<NSDateFormatter> LocaleMac::dateTimeFormatterWithSeconds() 177 { 178 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterShortStyle, NSDateFormatterMediumStyle); 179 } 180 181 RetainPtr<NSDateFormatter> LocaleMac::dateTimeFormatterWithoutSeconds() 182 { 183 return createDateTimeFormatter(m_locale.get(), m_gregorianCalendar.get(), NSDateFormatterShortStyle, NSDateFormatterShortStyle); 184 } 185 186 String LocaleMac::dateFormat() 187 { 188 if (!m_dateFormat.isNull()) 189 return m_dateFormat; 190 m_dateFormat = [shortDateFormatter().get() dateFormat]; 191 return m_dateFormat; 192 } 193 194 String 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 204 String 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 212 String LocaleMac::timeFormat() 213 { 214 if (!m_timeFormatWithSeconds.isNull()) 215 return m_timeFormatWithSeconds; 216 m_timeFormatWithSeconds = [timeFormatter().get() dateFormat]; 217 return m_timeFormatWithSeconds; 218 } 219 220 String LocaleMac::shortTimeFormat() 221 { 222 if (!m_timeFormatWithoutSeconds.isNull()) 223 return m_timeFormatWithoutSeconds; 224 m_timeFormatWithoutSeconds = [shortTimeFormatter().get() dateFormat]; 225 return m_timeFormatWithoutSeconds; 226 } 227 228 String LocaleMac::dateTimeFormatWithSeconds() 229 { 230 if (!m_dateTimeFormatWithSeconds.isNull()) 231 return m_dateTimeFormatWithSeconds; 232 m_dateTimeFormatWithSeconds = [dateTimeFormatterWithSeconds().get() dateFormat]; 233 return m_dateTimeFormatWithSeconds; 234 } 235 236 String LocaleMac::dateTimeFormatWithoutSeconds() 237 { 238 if (!m_dateTimeFormatWithoutSeconds.isNull()) 239 return m_dateTimeFormatWithoutSeconds; 240 m_dateTimeFormatWithoutSeconds = [dateTimeFormatterWithoutSeconds().get() dateFormat]; 241 return m_dateTimeFormatWithoutSeconds; 242 } 243 244 const 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 260 const 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 275 const 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 290 const 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 301 void 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