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 "third_party/blink/renderer/platform/text/locale_mac.h"
32
33#import <Foundation/Foundation.h>
34
35#include <memory>
36
37#include "base/memory/ptr_util.h"
38#include "base/stl_util.h"
39#include "third_party/blink/renderer/platform/language.h"
40#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
41#include "third_party/blink/renderer/platform/web_test_support.h"
42#include "third_party/blink/renderer/platform/wtf/date_math.h"
43#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
44#include "ui/base/ui_base_features.h"
45
46namespace blink {
47
48static inline String LanguageFromLocale(const String& locale) {
49  String normalized_locale = locale;
50  normalized_locale.Replace('-', '_');
51  size_t separator_position = normalized_locale.find('_');
52  if (separator_position == kNotFound)
53    return normalized_locale;
54  return normalized_locale.Left(separator_position);
55}
56
57static NSLocale* DetermineLocale(const String& locale) {
58  if (!WebTestSupport::IsRunningWebTest()) {
59    NSLocale* current_locale = [NSLocale currentLocale];
60    String current_locale_language =
61        LanguageFromLocale(String([current_locale localeIdentifier]));
62    String locale_language = LanguageFromLocale(locale);
63    if (DeprecatedEqualIgnoringCase(current_locale_language, locale_language))
64      return current_locale;
65  }
66  // It seems localeWithLocaleIdentifier accepts dash-separated locale
67  // identifier.
68  return [NSLocale localeWithLocaleIdentifier:locale];
69}
70
71std::unique_ptr<Locale> Locale::Create(const String& locale) {
72  return LocaleMac::Create(DetermineLocale(locale));
73}
74
75static base::scoped_nsobject<NSDateFormatter> CreateDateTimeFormatter(
76    NSLocale* locale,
77    NSCalendar* calendar,
78    NSDateFormatterStyle date_style,
79    NSDateFormatterStyle time_style) {
80  NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
81  [formatter setLocale:locale];
82  [formatter setDateStyle:date_style];
83  [formatter setTimeStyle:time_style];
84  [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
85  [formatter setCalendar:calendar];
86  return base::scoped_nsobject<NSDateFormatter>(formatter);
87}
88
89LocaleMac::LocaleMac(NSLocale* locale)
90    : locale_([locale retain]),
91      gregorian_calendar_([[NSCalendar alloc]
92          initWithCalendarIdentifier:NSCalendarIdentifierGregorian]),
93      did_initialize_number_data_(false) {
94  NSArray* available_languages = [NSLocale ISOLanguageCodes];
95  // NSLocale returns a lower case NSLocaleLanguageCode so we don't have care
96  // about case.
97  NSString* language = [locale_ objectForKey:NSLocaleLanguageCode];
98  if ([available_languages indexOfObject:language] == NSNotFound)
99    locale_.reset(
100        [[NSLocale alloc] initWithLocaleIdentifier:DefaultLanguage()]);
101  [gregorian_calendar_ setLocale:locale_];
102}
103
104LocaleMac::~LocaleMac() {}
105
106std::unique_ptr<LocaleMac> LocaleMac::Create(const String& locale_identifier) {
107  NSLocale* locale = [NSLocale localeWithLocaleIdentifier:locale_identifier];
108  return LocaleMac::Create(locale);
109}
110
111std::unique_ptr<LocaleMac> LocaleMac::Create(NSLocale* locale) {
112  return base::WrapUnique(new LocaleMac(locale));
113}
114
115base::scoped_nsobject<NSDateFormatter> LocaleMac::ShortDateFormatter() {
116  return CreateDateTimeFormatter(locale_, gregorian_calendar_,
117                                 NSDateFormatterShortStyle,
118                                 NSDateFormatterNoStyle);
119}
120
121const Vector<String>& LocaleMac::MonthLabels() {
122  if (!month_labels_.IsEmpty())
123    return month_labels_;
124  month_labels_.ReserveCapacity(12);
125  NSArray* array = [ShortDateFormatter() monthSymbols];
126  if ([array count] == 12) {
127    for (unsigned i = 0; i < 12; ++i)
128      month_labels_.push_back(String([array objectAtIndex:i]));
129    return month_labels_;
130  }
131  for (unsigned i = 0; i < base::size(WTF::kMonthFullName); ++i)
132    month_labels_.push_back(WTF::kMonthFullName[i]);
133  return month_labels_;
134}
135
136const Vector<String>& LocaleMac::WeekDayShortLabels() {
137  if (!week_day_short_labels_.IsEmpty())
138    return week_day_short_labels_;
139  week_day_short_labels_.ReserveCapacity(7);
140  NSArray* array = features::IsFormControlsRefreshEnabled()
141                       ? [ShortDateFormatter() veryShortWeekdaySymbols]
142                       : [ShortDateFormatter() shortWeekdaySymbols];
143  if ([array count] == 7) {
144    for (unsigned i = 0; i < 7; ++i)
145      week_day_short_labels_.push_back(String([array objectAtIndex:i]));
146    return week_day_short_labels_;
147  }
148  for (unsigned i = 0; i < base::size(WTF::kWeekdayName); ++i) {
149    // weekdayName starts with Monday.
150    week_day_short_labels_.push_back(WTF::kWeekdayName[(i + 6) % 7]);
151  }
152  return week_day_short_labels_;
153}
154
155unsigned LocaleMac::FirstDayOfWeek() {
156  // The document for NSCalendar - firstWeekday doesn't have an explanation of
157  // firstWeekday value. We can guess it by the document of NSDateComponents -
158  // weekDay, so it can be 1 through 7 and 1 is Sunday.
159  return [gregorian_calendar_ firstWeekday] - 1;
160}
161
162bool LocaleMac::IsRTL() {
163  return NSLocaleLanguageDirectionRightToLeft ==
164         [NSLocale characterDirectionForLanguage:
165                       [NSLocale canonicalLanguageIdentifierFromString:
166                                     [locale_ localeIdentifier]]];
167}
168
169base::scoped_nsobject<NSDateFormatter> LocaleMac::TimeFormatter() {
170  return CreateDateTimeFormatter(locale_, gregorian_calendar_,
171                                 NSDateFormatterNoStyle,
172                                 NSDateFormatterMediumStyle);
173}
174
175base::scoped_nsobject<NSDateFormatter> LocaleMac::ShortTimeFormatter() {
176  return CreateDateTimeFormatter(locale_, gregorian_calendar_,
177                                 NSDateFormatterNoStyle,
178                                 NSDateFormatterShortStyle);
179}
180
181base::scoped_nsobject<NSDateFormatter>
182LocaleMac::DateTimeFormatterWithSeconds() {
183  return CreateDateTimeFormatter(locale_, gregorian_calendar_,
184                                 NSDateFormatterShortStyle,
185                                 NSDateFormatterMediumStyle);
186}
187
188base::scoped_nsobject<NSDateFormatter>
189LocaleMac::DateTimeFormatterWithoutSeconds() {
190  return CreateDateTimeFormatter(locale_, gregorian_calendar_,
191                                 NSDateFormatterShortStyle,
192                                 NSDateFormatterShortStyle);
193}
194
195String LocaleMac::DateFormat() {
196  if (!date_format_.IsNull())
197    return date_format_;
198  date_format_ = [ShortDateFormatter() dateFormat];
199  return date_format_;
200}
201
202String LocaleMac::MonthFormat() {
203  if (!month_format_.IsNull())
204    return month_format_;
205  // Gets a format for "MMMM" because Windows API always provides formats for
206  // "MMMM" in some locales.
207  month_format_ = [NSDateFormatter dateFormatFromTemplate:@"yyyyMMMM"
208                                                  options:0
209                                                   locale:locale_];
210  return month_format_;
211}
212
213String LocaleMac::ShortMonthFormat() {
214  if (!short_month_format_.IsNull())
215    return short_month_format_;
216  short_month_format_ = [NSDateFormatter dateFormatFromTemplate:@"yyyyMMM"
217                                                        options:0
218                                                         locale:locale_];
219  return short_month_format_;
220}
221
222String LocaleMac::TimeFormat() {
223  if (!time_format_with_seconds_.IsNull())
224    return time_format_with_seconds_;
225  time_format_with_seconds_ = [TimeFormatter() dateFormat];
226  return time_format_with_seconds_;
227}
228
229String LocaleMac::ShortTimeFormat() {
230  if (!time_format_without_seconds_.IsNull())
231    return time_format_without_seconds_;
232  time_format_without_seconds_ = [ShortTimeFormatter() dateFormat];
233  return time_format_without_seconds_;
234}
235
236String LocaleMac::DateTimeFormatWithSeconds() {
237  if (!date_time_format_with_seconds_.IsNull())
238    return date_time_format_with_seconds_;
239  date_time_format_with_seconds_ = [DateTimeFormatterWithSeconds() dateFormat];
240  return date_time_format_with_seconds_;
241}
242
243String LocaleMac::DateTimeFormatWithoutSeconds() {
244  if (!date_time_format_without_seconds_.IsNull())
245    return date_time_format_without_seconds_;
246  date_time_format_without_seconds_ =
247      [DateTimeFormatterWithoutSeconds() dateFormat];
248  return date_time_format_without_seconds_;
249}
250
251const Vector<String>& LocaleMac::ShortMonthLabels() {
252  if (!short_month_labels_.IsEmpty())
253    return short_month_labels_;
254  short_month_labels_.ReserveCapacity(12);
255  NSArray* array = [ShortDateFormatter() shortMonthSymbols];
256  if ([array count] == 12) {
257    for (unsigned i = 0; i < 12; ++i)
258      short_month_labels_.push_back([array objectAtIndex:i]);
259    return short_month_labels_;
260  }
261  for (unsigned i = 0; i < base::size(WTF::kMonthName); ++i)
262    short_month_labels_.push_back(WTF::kMonthName[i]);
263  return short_month_labels_;
264}
265
266const Vector<String>& LocaleMac::StandAloneMonthLabels() {
267  if (!stand_alone_month_labels_.IsEmpty())
268    return stand_alone_month_labels_;
269  NSArray* array = [ShortDateFormatter() standaloneMonthSymbols];
270  if ([array count] == 12) {
271    stand_alone_month_labels_.ReserveCapacity(12);
272    for (unsigned i = 0; i < 12; ++i)
273      stand_alone_month_labels_.push_back([array objectAtIndex:i]);
274    return stand_alone_month_labels_;
275  }
276  stand_alone_month_labels_ = ShortMonthLabels();
277  return stand_alone_month_labels_;
278}
279
280const Vector<String>& LocaleMac::ShortStandAloneMonthLabels() {
281  if (!short_stand_alone_month_labels_.IsEmpty())
282    return short_stand_alone_month_labels_;
283  NSArray* array = [ShortDateFormatter() shortStandaloneMonthSymbols];
284  if ([array count] == 12) {
285    short_stand_alone_month_labels_.ReserveCapacity(12);
286    for (unsigned i = 0; i < 12; ++i)
287      short_stand_alone_month_labels_.push_back([array objectAtIndex:i]);
288    return short_stand_alone_month_labels_;
289  }
290  short_stand_alone_month_labels_ = ShortMonthLabels();
291  return short_stand_alone_month_labels_;
292}
293
294const Vector<String>& LocaleMac::TimeAMPMLabels() {
295  if (!time_ampm_labels_.IsEmpty())
296    return time_ampm_labels_;
297  time_ampm_labels_.ReserveCapacity(2);
298  base::scoped_nsobject<NSDateFormatter> formatter(ShortTimeFormatter());
299  time_ampm_labels_.push_back([formatter AMSymbol]);
300  time_ampm_labels_.push_back([formatter PMSymbol]);
301  return time_ampm_labels_;
302}
303
304void LocaleMac::InitializeLocaleData() {
305  if (did_initialize_number_data_)
306    return;
307  did_initialize_number_data_ = true;
308
309  base::scoped_nsobject<NSNumberFormatter> formatter(
310      [[NSNumberFormatter alloc] init]);
311  [formatter setLocale:locale_];
312  [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
313  [formatter setUsesGroupingSeparator:NO];
314
315  base::scoped_nsobject<NSNumber> sample_number(
316      [[NSNumber alloc] initWithDouble:9876543210]);
317  String nine_to_zero([formatter stringFromNumber:sample_number]);
318  if (nine_to_zero.length() != 10)
319    return;
320  Vector<String, kDecimalSymbolsSize> symbols;
321  for (unsigned i = 0; i < 10; ++i)
322    symbols.push_back(nine_to_zero.Substring(9 - i, 1));
323  DCHECK(symbols.size() == kDecimalSeparatorIndex);
324  symbols.push_back([formatter decimalSeparator]);
325  DCHECK(symbols.size() == kGroupSeparatorIndex);
326  symbols.push_back([formatter groupingSeparator]);
327  DCHECK(symbols.size() == kDecimalSymbolsSize);
328
329  String positive_prefix([formatter positivePrefix]);
330  String positive_suffix([formatter positiveSuffix]);
331  String negative_prefix([formatter negativePrefix]);
332  String negative_suffix([formatter negativeSuffix]);
333  SetLocaleData(symbols, positive_prefix, positive_suffix, negative_prefix,
334                negative_suffix);
335}
336}
337