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