1 /*
2 * Copyright (C) 2011,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 "platform/text/PlatformLocale.h"
33
34 #include "platform/text/DateTimeFormat.h"
35 #include "public/platform/Platform.h"
36 #include "wtf/MainThread.h"
37 #include "wtf/text/StringBuilder.h"
38
39 namespace blink {
40
41 using blink::Platform;
42 using blink::WebLocalizedString;
43
44 class DateTimeStringBuilder : private DateTimeFormat::TokenHandler {
45 WTF_MAKE_NONCOPYABLE(DateTimeStringBuilder);
46 public:
47 // The argument objects must be alive until this object dies.
48 DateTimeStringBuilder(Locale&, const DateComponents&);
49
50 bool build(const String&);
51 String toString();
52
53 private:
54 // DateTimeFormat::TokenHandler functions.
55 virtual void visitField(DateTimeFormat::FieldType, int) OVERRIDE FINAL;
56 virtual void visitLiteral(const String&) OVERRIDE FINAL;
57
58 String zeroPadString(const String&, size_t width);
59 void appendNumber(int number, size_t width);
60
61 StringBuilder m_builder;
62 Locale& m_localizer;
63 const DateComponents& m_date;
64 };
65
DateTimeStringBuilder(Locale & localizer,const DateComponents & date)66 DateTimeStringBuilder::DateTimeStringBuilder(Locale& localizer, const DateComponents& date)
67 : m_localizer(localizer)
68 , m_date(date)
69 {
70 }
71
build(const String & formatString)72 bool DateTimeStringBuilder::build(const String& formatString)
73 {
74 m_builder.reserveCapacity(formatString.length());
75 return DateTimeFormat::parse(formatString, *this);
76 }
77
zeroPadString(const String & string,size_t width)78 String DateTimeStringBuilder::zeroPadString(const String& string, size_t width)
79 {
80 if (string.length() >= width)
81 return string;
82 StringBuilder zeroPaddedStringBuilder;
83 zeroPaddedStringBuilder.reserveCapacity(width);
84 for (size_t i = string.length(); i < width; ++i)
85 zeroPaddedStringBuilder.append('0');
86 zeroPaddedStringBuilder.append(string);
87 return zeroPaddedStringBuilder.toString();
88 }
89
appendNumber(int number,size_t width)90 void DateTimeStringBuilder::appendNumber(int number, size_t width)
91 {
92 String zeroPaddedNumberString = zeroPadString(String::number(number), width);
93 m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedNumberString));
94 }
95
visitField(DateTimeFormat::FieldType fieldType,int numberOfPatternCharacters)96 void DateTimeStringBuilder::visitField(DateTimeFormat::FieldType fieldType, int numberOfPatternCharacters)
97 {
98 switch (fieldType) {
99 case DateTimeFormat::FieldTypeYear:
100 // Always use padding width of 4 so it matches DateTimeEditElement.
101 appendNumber(m_date.fullYear(), 4);
102 return;
103 case DateTimeFormat::FieldTypeMonth:
104 if (numberOfPatternCharacters == 3) {
105 m_builder.append(m_localizer.shortMonthLabels()[m_date.month()]);
106 } else if (numberOfPatternCharacters == 4) {
107 m_builder.append(m_localizer.monthLabels()[m_date.month()]);
108 } else {
109 // Always use padding width of 2 so it matches DateTimeEditElement.
110 appendNumber(m_date.month() + 1, 2);
111 }
112 return;
113 case DateTimeFormat::FieldTypeMonthStandAlone:
114 if (numberOfPatternCharacters == 3) {
115 m_builder.append(m_localizer.shortStandAloneMonthLabels()[m_date.month()]);
116 } else if (numberOfPatternCharacters == 4) {
117 m_builder.append(m_localizer.standAloneMonthLabels()[m_date.month()]);
118 } else {
119 // Always use padding width of 2 so it matches DateTimeEditElement.
120 appendNumber(m_date.month() + 1, 2);
121 }
122 return;
123 case DateTimeFormat::FieldTypeDayOfMonth:
124 // Always use padding width of 2 so it matches DateTimeEditElement.
125 appendNumber(m_date.monthDay(), 2);
126 return;
127 case DateTimeFormat::FieldTypeWeekOfYear:
128 // Always use padding width of 2 so it matches DateTimeEditElement.
129 appendNumber(m_date.week(), 2);
130 return;
131 case DateTimeFormat::FieldTypePeriod:
132 m_builder.append(m_localizer.timeAMPMLabels()[(m_date.hour() >= 12 ? 1 : 0)]);
133 return;
134 case DateTimeFormat::FieldTypeHour12: {
135 int hour12 = m_date.hour() % 12;
136 if (!hour12)
137 hour12 = 12;
138 appendNumber(hour12, numberOfPatternCharacters);
139 return;
140 }
141 case DateTimeFormat::FieldTypeHour23:
142 appendNumber(m_date.hour(), numberOfPatternCharacters);
143 return;
144 case DateTimeFormat::FieldTypeHour11:
145 appendNumber(m_date.hour() % 12, numberOfPatternCharacters);
146 return;
147 case DateTimeFormat::FieldTypeHour24: {
148 int hour24 = m_date.hour();
149 if (!hour24)
150 hour24 = 24;
151 appendNumber(hour24, numberOfPatternCharacters);
152 return;
153 }
154 case DateTimeFormat::FieldTypeMinute:
155 appendNumber(m_date.minute(), numberOfPatternCharacters);
156 return;
157 case DateTimeFormat::FieldTypeSecond:
158 if (!m_date.millisecond()) {
159 appendNumber(m_date.second(), numberOfPatternCharacters);
160 } else {
161 double second = m_date.second() + m_date.millisecond() / 1000.0;
162 String zeroPaddedSecondString = zeroPadString(String::format("%.03f", second), numberOfPatternCharacters + 4);
163 m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedSecondString));
164 }
165 return;
166 default:
167 return;
168 }
169 }
170
visitLiteral(const String & text)171 void DateTimeStringBuilder::visitLiteral(const String& text)
172 {
173 ASSERT(text.length());
174 m_builder.append(text);
175 }
176
toString()177 String DateTimeStringBuilder::toString()
178 {
179 return m_builder.toString();
180 }
181
defaultLocale()182 Locale& Locale::defaultLocale()
183 {
184 static Locale* locale = Locale::create(defaultLanguage()).leakPtr();
185 ASSERT(isMainThread());
186 return *locale;
187 }
188
~Locale()189 Locale::~Locale()
190 {
191 }
192
queryString(WebLocalizedString::Name name)193 String Locale::queryString(WebLocalizedString::Name name)
194 {
195 // FIXME: Returns a string locazlied for this locale.
196 return Platform::current()->queryLocalizedString(name);
197 }
198
queryString(WebLocalizedString::Name name,const String & parameter)199 String Locale::queryString(WebLocalizedString::Name name, const String& parameter)
200 {
201 // FIXME: Returns a string locazlied for this locale.
202 return Platform::current()->queryLocalizedString(name, parameter);
203 }
204
queryString(WebLocalizedString::Name name,const String & parameter1,const String & parameter2)205 String Locale::queryString(WebLocalizedString::Name name, const String& parameter1, const String& parameter2)
206 {
207 // FIXME: Returns a string locazlied for this locale.
208 return Platform::current()->queryLocalizedString(name, parameter1, parameter2);
209 }
210
validationMessageTooLongText(unsigned valueLength,int maxLength)211 String Locale::validationMessageTooLongText(unsigned valueLength, int maxLength)
212 {
213 return queryString(WebLocalizedString::ValidationTooLong, convertToLocalizedNumber(String::number(valueLength)), convertToLocalizedNumber(String::number(maxLength)));
214 }
215
weekFormatInLDML()216 String Locale::weekFormatInLDML()
217 {
218 String templ = queryString(WebLocalizedString::WeekFormatTemplate);
219 // Converts a string like "Week $2, $1" to an LDML date format pattern like
220 // "'Week 'ww', 'yyyy".
221 StringBuilder builder;
222 unsigned literalStart = 0;
223 unsigned length = templ.length();
224 for (unsigned i = 0; i + 1 < length; ++i) {
225 if (templ[i] == '$' && (templ[i + 1] == '1' || templ[i + 1] == '2')) {
226 if (literalStart < i)
227 DateTimeFormat::quoteAndAppendLiteral(templ.substring(literalStart, i - literalStart), builder);
228 builder.append(templ[++i] == '1' ? "yyyy" : "ww");
229 literalStart = i + 1;
230 }
231 }
232 if (literalStart < length)
233 DateTimeFormat::quoteAndAppendLiteral(templ.substring(literalStart, length - literalStart), builder);
234 return builder.toString();
235 }
236
setLocaleData(const Vector<String,DecimalSymbolsSize> & symbols,const String & positivePrefix,const String & positiveSuffix,const String & negativePrefix,const String & negativeSuffix)237 void Locale::setLocaleData(const Vector<String, DecimalSymbolsSize>& symbols, const String& positivePrefix, const String& positiveSuffix, const String& negativePrefix, const String& negativeSuffix)
238 {
239 for (size_t i = 0; i < symbols.size(); ++i) {
240 ASSERT(!symbols[i].isEmpty());
241 m_decimalSymbols[i] = symbols[i];
242 }
243 m_positivePrefix = positivePrefix;
244 m_positiveSuffix = positiveSuffix;
245 m_negativePrefix = negativePrefix;
246 m_negativeSuffix = negativeSuffix;
247 ASSERT(!m_positivePrefix.isEmpty() || !m_positiveSuffix.isEmpty() || !m_negativePrefix.isEmpty() || !m_negativeSuffix.isEmpty());
248 m_hasLocaleData = true;
249 }
250
convertToLocalizedNumber(const String & input)251 String Locale::convertToLocalizedNumber(const String& input)
252 {
253 initializeLocaleData();
254 if (!m_hasLocaleData || input.isEmpty())
255 return input;
256
257 unsigned i = 0;
258 bool isNegative = false;
259 StringBuilder builder;
260 builder.reserveCapacity(input.length());
261
262 if (input[0] == '-') {
263 ++i;
264 isNegative = true;
265 builder.append(m_negativePrefix);
266 } else {
267 builder.append(m_positivePrefix);
268 }
269
270 for (; i < input.length(); ++i) {
271 switch (input[i]) {
272 case '0':
273 case '1':
274 case '2':
275 case '3':
276 case '4':
277 case '5':
278 case '6':
279 case '7':
280 case '8':
281 case '9':
282 builder.append(m_decimalSymbols[input[i] - '0']);
283 break;
284 case '.':
285 builder.append(m_decimalSymbols[DecimalSeparatorIndex]);
286 break;
287 default:
288 ASSERT_NOT_REACHED();
289 }
290 }
291
292 builder.append(isNegative ? m_negativeSuffix : m_positiveSuffix);
293
294 return builder.toString();
295 }
296
matches(const String & text,unsigned position,const String & part)297 static bool matches(const String& text, unsigned position, const String& part)
298 {
299 if (part.isEmpty())
300 return true;
301 if (position + part.length() > text.length())
302 return false;
303 for (unsigned i = 0; i < part.length(); ++i) {
304 if (text[position + i] != part[i])
305 return false;
306 }
307 return true;
308 }
309
detectSignAndGetDigitRange(const String & input,bool & isNegative,unsigned & startIndex,unsigned & endIndex)310 bool Locale::detectSignAndGetDigitRange(const String& input, bool& isNegative, unsigned& startIndex, unsigned& endIndex)
311 {
312 startIndex = 0;
313 endIndex = input.length();
314 if (m_negativePrefix.isEmpty() && m_negativeSuffix.isEmpty()) {
315 if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
316 isNegative = false;
317 startIndex = m_positivePrefix.length();
318 endIndex -= m_positiveSuffix.length();
319 } else {
320 isNegative = true;
321 }
322 } else {
323 if (input.startsWith(m_negativePrefix) && input.endsWith(m_negativeSuffix)) {
324 isNegative = true;
325 startIndex = m_negativePrefix.length();
326 endIndex -= m_negativeSuffix.length();
327 } else {
328 isNegative = false;
329 if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
330 startIndex = m_positivePrefix.length();
331 endIndex -= m_positiveSuffix.length();
332 } else {
333 return false;
334 }
335 }
336 }
337 return true;
338 }
339
matchedDecimalSymbolIndex(const String & input,unsigned & position)340 unsigned Locale::matchedDecimalSymbolIndex(const String& input, unsigned& position)
341 {
342 for (unsigned symbolIndex = 0; symbolIndex < DecimalSymbolsSize; ++symbolIndex) {
343 if (m_decimalSymbols[symbolIndex].length() && matches(input, position, m_decimalSymbols[symbolIndex])) {
344 position += m_decimalSymbols[symbolIndex].length();
345 return symbolIndex;
346 }
347 }
348 return DecimalSymbolsSize;
349 }
350
convertFromLocalizedNumber(const String & localized)351 String Locale::convertFromLocalizedNumber(const String& localized)
352 {
353 initializeLocaleData();
354 String input = localized.removeCharacters(isASCIISpace);
355 if (!m_hasLocaleData || input.isEmpty())
356 return input;
357
358 bool isNegative;
359 unsigned startIndex;
360 unsigned endIndex;
361 if (!detectSignAndGetDigitRange(input, isNegative, startIndex, endIndex))
362 return input;
363
364 StringBuilder builder;
365 builder.reserveCapacity(input.length());
366 if (isNegative)
367 builder.append('-');
368 for (unsigned i = startIndex; i < endIndex;) {
369 unsigned symbolIndex = matchedDecimalSymbolIndex(input, i);
370 if (symbolIndex >= DecimalSymbolsSize)
371 return input;
372 if (symbolIndex == DecimalSeparatorIndex)
373 builder.append('.');
374 else if (symbolIndex == GroupSeparatorIndex)
375 return input;
376 else
377 builder.append(static_cast<UChar>('0' + symbolIndex));
378 }
379 return builder.toString();
380 }
381
382 #if ENABLE(INPUT_MULTIPLE_FIELDS_UI)
localizedDecimalSeparator()383 String Locale::localizedDecimalSeparator()
384 {
385 initializeLocaleData();
386 return m_decimalSymbols[DecimalSeparatorIndex];
387 }
388 #endif
389
formatDateTime(const DateComponents & date,FormatType formatType)390 String Locale::formatDateTime(const DateComponents& date, FormatType formatType)
391 {
392 if (date.type() == DateComponents::Invalid)
393 return String();
394
395 DateTimeStringBuilder builder(*this, date);
396 switch (date.type()) {
397 case DateComponents::Time:
398 builder.build(formatType == FormatTypeShort ? shortTimeFormat() : timeFormat());
399 break;
400 case DateComponents::Date:
401 builder.build(dateFormat());
402 break;
403 case DateComponents::Month:
404 builder.build(formatType == FormatTypeShort ? shortMonthFormat() : monthFormat());
405 break;
406 case DateComponents::Week:
407 builder.build(weekFormatInLDML());
408 break;
409 case DateComponents::DateTime:
410 case DateComponents::DateTimeLocal:
411 builder.build(formatType == FormatTypeShort ? dateTimeFormatWithoutSeconds() : dateTimeFormatWithSeconds());
412 break;
413 case DateComponents::Invalid:
414 ASSERT_NOT_REACHED();
415 break;
416 }
417 return builder.toString();
418 }
419
420 }
421