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