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