2 * Copyright (C) 2011,2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
32 #include "platform/text/PlatformLocale.h"
34 #include "platform/text/DateTimeFormat.h"
35 #include "public/platform/Platform.h"
36 #include "wtf/MainThread.h"
37 #include "wtf/text/StringBuilder.h"
41 using blink::Platform;
42 using blink::WebLocalizedString;
44 class DateTimeStringBuilder : private DateTimeFormat::TokenHandler {
45 WTF_MAKE_NONCOPYABLE(DateTimeStringBuilder);
47 // The argument objects must be alive until this object dies.
48 DateTimeStringBuilder(Locale&, const DateComponents&);
50 bool build(const String&);
54 // DateTimeFormat::TokenHandler functions.
55 virtual void visitField(DateTimeFormat::FieldType, int) OVERRIDE FINAL;
56 virtual void visitLiteral(const String&) OVERRIDE FINAL;
58 String zeroPadString(const String&, size_t width);
59 void appendNumber(int number, size_t width);
61 StringBuilder m_builder;
63 const DateComponents& m_date;
66 DateTimeStringBuilder::DateTimeStringBuilder(Locale& localizer, const DateComponents& date)
67 : m_localizer(localizer)
72 bool DateTimeStringBuilder::build(const String& formatString)
74 m_builder.reserveCapacity(formatString.length());
75 return DateTimeFormat::parse(formatString, *this);
78 String DateTimeStringBuilder::zeroPadString(const String& string, size_t width)
80 if (string.length() >= width)
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();
90 void DateTimeStringBuilder::appendNumber(int number, size_t width)
92 String zeroPaddedNumberString = zeroPadString(String::number(number), width);
93 m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedNumberString));
96 void DateTimeStringBuilder::visitField(DateTimeFormat::FieldType fieldType, int numberOfPatternCharacters)
99 case DateTimeFormat::FieldTypeYear:
100 // Always use padding width of 4 so it matches DateTimeEditElement.
101 appendNumber(m_date.fullYear(), 4);
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()]);
109 // Always use padding width of 2 so it matches DateTimeEditElement.
110 appendNumber(m_date.month() + 1, 2);
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()]);
119 // Always use padding width of 2 so it matches DateTimeEditElement.
120 appendNumber(m_date.month() + 1, 2);
123 case DateTimeFormat::FieldTypeDayOfMonth:
124 // Always use padding width of 2 so it matches DateTimeEditElement.
125 appendNumber(m_date.monthDay(), 2);
127 case DateTimeFormat::FieldTypeWeekOfYear:
128 // Always use padding width of 2 so it matches DateTimeEditElement.
129 appendNumber(m_date.week(), 2);
131 case DateTimeFormat::FieldTypePeriod:
132 m_builder.append(m_localizer.timeAMPMLabels()[(m_date.hour() >= 12 ? 1 : 0)]);
134 case DateTimeFormat::FieldTypeHour12: {
135 int hour12 = m_date.hour() % 12;
138 appendNumber(hour12, numberOfPatternCharacters);
141 case DateTimeFormat::FieldTypeHour23:
142 appendNumber(m_date.hour(), numberOfPatternCharacters);
144 case DateTimeFormat::FieldTypeHour11:
145 appendNumber(m_date.hour() % 12, numberOfPatternCharacters);
147 case DateTimeFormat::FieldTypeHour24: {
148 int hour24 = m_date.hour();
151 appendNumber(hour24, numberOfPatternCharacters);
154 case DateTimeFormat::FieldTypeMinute:
155 appendNumber(m_date.minute(), numberOfPatternCharacters);
157 case DateTimeFormat::FieldTypeSecond:
158 if (!m_date.millisecond()) {
159 appendNumber(m_date.second(), numberOfPatternCharacters);
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));
171 void DateTimeStringBuilder::visitLiteral(const String& text)
173 ASSERT(text.length());
174 m_builder.append(text);
177 String DateTimeStringBuilder::toString()
179 return m_builder.toString();
182 Locale& Locale::defaultLocale()
184 static Locale* locale = Locale::create(defaultLanguage()).leakPtr();
185 ASSERT(isMainThread());
193 String Locale::queryString(WebLocalizedString::Name name)
195 // FIXME: Returns a string locazlied for this locale.
196 return Platform::current()->queryLocalizedString(name);
199 String Locale::queryString(WebLocalizedString::Name name, const String& parameter)
201 // FIXME: Returns a string locazlied for this locale.
202 return Platform::current()->queryLocalizedString(name, parameter);
205 String Locale::queryString(WebLocalizedString::Name name, const String& parameter1, const String& parameter2)
207 // FIXME: Returns a string locazlied for this locale.
208 return Platform::current()->queryLocalizedString(name, parameter1, parameter2);
211 String Locale::validationMessageTooLongText(unsigned valueLength, int maxLength)
213 return queryString(WebLocalizedString::ValidationTooLong, convertToLocalizedNumber(String::number(valueLength)), convertToLocalizedNumber(String::number(maxLength)));
216 String Locale::weekFormatInLDML()
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;
232 if (literalStart < length)
233 DateTimeFormat::quoteAndAppendLiteral(templ.substring(literalStart, length - literalStart), builder);
234 return builder.toString();
237 void Locale::setLocaleData(const Vector<String, DecimalSymbolsSize>& symbols, const String& positivePrefix, const String& positiveSuffix, const String& negativePrefix, const String& negativeSuffix)
239 for (size_t i = 0; i < symbols.size(); ++i) {
240 ASSERT(!symbols[i].isEmpty());
241 m_decimalSymbols[i] = symbols[i];
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;
251 String Locale::convertToLocalizedNumber(const String& input)
253 initializeLocaleData();
254 if (!m_hasLocaleData || input.isEmpty())
258 bool isNegative = false;
259 StringBuilder builder;
260 builder.reserveCapacity(input.length());
262 if (input[0] == '-') {
265 builder.append(m_negativePrefix);
267 builder.append(m_positivePrefix);
270 for (; i < input.length(); ++i) {
282 builder.append(m_decimalSymbols[input[i] - '0']);
285 builder.append(m_decimalSymbols[DecimalSeparatorIndex]);
288 ASSERT_NOT_REACHED();
292 builder.append(isNegative ? m_negativeSuffix : m_positiveSuffix);
294 return builder.toString();
297 static bool matches(const String& text, unsigned position, const String& part)
301 if (position + part.length() > text.length())
303 for (unsigned i = 0; i < part.length(); ++i) {
304 if (text[position + i] != part[i])
310 bool Locale::detectSignAndGetDigitRange(const String& input, bool& isNegative, unsigned& startIndex, unsigned& endIndex)
313 endIndex = input.length();
314 if (m_negativePrefix.isEmpty() && m_negativeSuffix.isEmpty()) {
315 if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
317 startIndex = m_positivePrefix.length();
318 endIndex -= m_positiveSuffix.length();
323 if (input.startsWith(m_negativePrefix) && input.endsWith(m_negativeSuffix)) {
325 startIndex = m_negativePrefix.length();
326 endIndex -= m_negativeSuffix.length();
329 if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
330 startIndex = m_positivePrefix.length();
331 endIndex -= m_positiveSuffix.length();
340 unsigned Locale::matchedDecimalSymbolIndex(const String& input, unsigned& position)
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();
348 return DecimalSymbolsSize;
351 String Locale::convertFromLocalizedNumber(const String& localized)
353 initializeLocaleData();
354 String input = localized.removeCharacters(isASCIISpace);
355 if (!m_hasLocaleData || input.isEmpty())
361 if (!detectSignAndGetDigitRange(input, isNegative, startIndex, endIndex))
364 StringBuilder builder;
365 builder.reserveCapacity(input.length());
368 for (unsigned i = startIndex; i < endIndex;) {
369 unsigned symbolIndex = matchedDecimalSymbolIndex(input, i);
370 if (symbolIndex >= DecimalSymbolsSize)
372 if (symbolIndex == DecimalSeparatorIndex)
374 else if (symbolIndex == GroupSeparatorIndex)
377 builder.append(static_cast<UChar>('0' + symbolIndex));
379 return builder.toString();
382 #if ENABLE(INPUT_MULTIPLE_FIELDS_UI)
383 String Locale::localizedDecimalSeparator()
385 initializeLocaleData();
386 return m_decimalSymbols[DecimalSeparatorIndex];
390 String Locale::formatDateTime(const DateComponents& date, FormatType formatType)
392 if (date.type() == DateComponents::Invalid)
395 DateTimeStringBuilder builder(*this, date);
396 switch (date.type()) {
397 case DateComponents::Time:
398 builder.build(formatType == FormatTypeShort ? shortTimeFormat() : timeFormat());
400 case DateComponents::Date:
401 builder.build(dateFormat());
403 case DateComponents::Month:
404 builder.build(formatType == FormatTypeShort ? shortMonthFormat() : monthFormat());
406 case DateComponents::Week:
407 builder.build(weekFormatInLDML());
409 case DateComponents::DateTime:
410 case DateComponents::DateTimeLocal:
411 builder.build(formatType == FormatTypeShort ? dateTimeFormatWithoutSeconds() : dateTimeFormatWithSeconds());
413 case DateComponents::Invalid:
414 ASSERT_NOT_REACHED();
417 return builder.toString();