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 class DateTimeStringBuilder : private DateTimeFormat::TokenHandler {
42 WTF_MAKE_NONCOPYABLE(DateTimeStringBuilder);
44 // The argument objects must be alive until this object dies.
45 DateTimeStringBuilder(Locale&, const DateComponents&);
47 bool build(const String&);
51 // DateTimeFormat::TokenHandler functions.
52 virtual void visitField(DateTimeFormat::FieldType, int) override final;
53 virtual void visitLiteral(const String&) override final;
55 String zeroPadString(const String&, size_t width);
56 void appendNumber(int number, size_t width);
58 StringBuilder m_builder;
60 const DateComponents& m_date;
63 DateTimeStringBuilder::DateTimeStringBuilder(Locale& localizer, const DateComponents& date)
64 : m_localizer(localizer)
69 bool DateTimeStringBuilder::build(const String& formatString)
71 m_builder.reserveCapacity(formatString.length());
72 return DateTimeFormat::parse(formatString, *this);
75 String DateTimeStringBuilder::zeroPadString(const String& string, size_t width)
77 if (string.length() >= width)
79 StringBuilder zeroPaddedStringBuilder;
80 zeroPaddedStringBuilder.reserveCapacity(width);
81 for (size_t i = string.length(); i < width; ++i)
82 zeroPaddedStringBuilder.append('0');
83 zeroPaddedStringBuilder.append(string);
84 return zeroPaddedStringBuilder.toString();
87 void DateTimeStringBuilder::appendNumber(int number, size_t width)
89 String zeroPaddedNumberString = zeroPadString(String::number(number), width);
90 m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedNumberString));
93 void DateTimeStringBuilder::visitField(DateTimeFormat::FieldType fieldType, int numberOfPatternCharacters)
96 case DateTimeFormat::FieldTypeYear:
97 // Always use padding width of 4 so it matches DateTimeEditElement.
98 appendNumber(m_date.fullYear(), 4);
100 case DateTimeFormat::FieldTypeMonth:
101 if (numberOfPatternCharacters == 3) {
102 m_builder.append(m_localizer.shortMonthLabels()[m_date.month()]);
103 } else if (numberOfPatternCharacters == 4) {
104 m_builder.append(m_localizer.monthLabels()[m_date.month()]);
106 // Always use padding width of 2 so it matches DateTimeEditElement.
107 appendNumber(m_date.month() + 1, 2);
110 case DateTimeFormat::FieldTypeMonthStandAlone:
111 if (numberOfPatternCharacters == 3) {
112 m_builder.append(m_localizer.shortStandAloneMonthLabels()[m_date.month()]);
113 } else if (numberOfPatternCharacters == 4) {
114 m_builder.append(m_localizer.standAloneMonthLabels()[m_date.month()]);
116 // Always use padding width of 2 so it matches DateTimeEditElement.
117 appendNumber(m_date.month() + 1, 2);
120 case DateTimeFormat::FieldTypeDayOfMonth:
121 // Always use padding width of 2 so it matches DateTimeEditElement.
122 appendNumber(m_date.monthDay(), 2);
124 case DateTimeFormat::FieldTypeWeekOfYear:
125 // Always use padding width of 2 so it matches DateTimeEditElement.
126 appendNumber(m_date.week(), 2);
128 case DateTimeFormat::FieldTypePeriod:
129 m_builder.append(m_localizer.timeAMPMLabels()[(m_date.hour() >= 12 ? 1 : 0)]);
131 case DateTimeFormat::FieldTypeHour12: {
132 int hour12 = m_date.hour() % 12;
135 appendNumber(hour12, numberOfPatternCharacters);
138 case DateTimeFormat::FieldTypeHour23:
139 appendNumber(m_date.hour(), numberOfPatternCharacters);
141 case DateTimeFormat::FieldTypeHour11:
142 appendNumber(m_date.hour() % 12, numberOfPatternCharacters);
144 case DateTimeFormat::FieldTypeHour24: {
145 int hour24 = m_date.hour();
148 appendNumber(hour24, numberOfPatternCharacters);
151 case DateTimeFormat::FieldTypeMinute:
152 appendNumber(m_date.minute(), numberOfPatternCharacters);
154 case DateTimeFormat::FieldTypeSecond:
155 if (!m_date.millisecond()) {
156 appendNumber(m_date.second(), numberOfPatternCharacters);
158 double second = m_date.second() + m_date.millisecond() / 1000.0;
159 String zeroPaddedSecondString = zeroPadString(String::format("%.03f", second), numberOfPatternCharacters + 4);
160 m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedSecondString));
168 void DateTimeStringBuilder::visitLiteral(const String& text)
170 ASSERT(text.length());
171 m_builder.append(text);
174 String DateTimeStringBuilder::toString()
176 return m_builder.toString();
179 Locale& Locale::defaultLocale()
181 static Locale* locale = Locale::create(defaultLanguage()).leakPtr();
182 ASSERT(isMainThread());
190 String Locale::queryString(WebLocalizedString::Name name)
192 // FIXME: Returns a string locazlied for this locale.
193 return Platform::current()->queryLocalizedString(name);
196 String Locale::queryString(WebLocalizedString::Name name, const String& parameter)
198 // FIXME: Returns a string locazlied for this locale.
199 return Platform::current()->queryLocalizedString(name, parameter);
202 String Locale::queryString(WebLocalizedString::Name name, const String& parameter1, const String& parameter2)
204 // FIXME: Returns a string locazlied for this locale.
205 return Platform::current()->queryLocalizedString(name, parameter1, parameter2);
208 String Locale::validationMessageTooLongText(unsigned valueLength, int maxLength)
210 return queryString(WebLocalizedString::ValidationTooLong, convertToLocalizedNumber(String::number(valueLength)), convertToLocalizedNumber(String::number(maxLength)));
213 String Locale::validationMessageTooShortText(unsigned valueLength, int minLength)
215 return queryString(WebLocalizedString::ValidationTooShort, convertToLocalizedNumber(String::number(valueLength)), convertToLocalizedNumber(String::number(minLength)));
218 String Locale::weekFormatInLDML()
220 String templ = queryString(WebLocalizedString::WeekFormatTemplate);
221 // Converts a string like "Week $2, $1" to an LDML date format pattern like
222 // "'Week 'ww', 'yyyy".
223 StringBuilder builder;
224 unsigned literalStart = 0;
225 unsigned length = templ.length();
226 for (unsigned i = 0; i + 1 < length; ++i) {
227 if (templ[i] == '$' && (templ[i + 1] == '1' || templ[i + 1] == '2')) {
228 if (literalStart < i)
229 DateTimeFormat::quoteAndAppendLiteral(templ.substring(literalStart, i - literalStart), builder);
230 builder.append(templ[++i] == '1' ? "yyyy" : "ww");
231 literalStart = i + 1;
234 if (literalStart < length)
235 DateTimeFormat::quoteAndAppendLiteral(templ.substring(literalStart, length - literalStart), builder);
236 return builder.toString();
239 void Locale::setLocaleData(const Vector<String, DecimalSymbolsSize>& symbols, const String& positivePrefix, const String& positiveSuffix, const String& negativePrefix, const String& negativeSuffix)
241 for (size_t i = 0; i < symbols.size(); ++i) {
242 ASSERT(!symbols[i].isEmpty());
243 m_decimalSymbols[i] = symbols[i];
245 m_positivePrefix = positivePrefix;
246 m_positiveSuffix = positiveSuffix;
247 m_negativePrefix = negativePrefix;
248 m_negativeSuffix = negativeSuffix;
249 ASSERT(!m_positivePrefix.isEmpty() || !m_positiveSuffix.isEmpty() || !m_negativePrefix.isEmpty() || !m_negativeSuffix.isEmpty());
250 m_hasLocaleData = true;
253 String Locale::convertToLocalizedNumber(const String& input)
255 initializeLocaleData();
256 if (!m_hasLocaleData || input.isEmpty())
260 bool isNegative = false;
261 StringBuilder builder;
262 builder.reserveCapacity(input.length());
264 if (input[0] == '-') {
267 builder.append(m_negativePrefix);
269 builder.append(m_positivePrefix);
272 for (; i < input.length(); ++i) {
284 builder.append(m_decimalSymbols[input[i] - '0']);
287 builder.append(m_decimalSymbols[DecimalSeparatorIndex]);
290 ASSERT_NOT_REACHED();
294 builder.append(isNegative ? m_negativeSuffix : m_positiveSuffix);
296 return builder.toString();
299 static bool matches(const String& text, unsigned position, const String& part)
303 if (position + part.length() > text.length())
305 for (unsigned i = 0; i < part.length(); ++i) {
306 if (text[position + i] != part[i])
312 bool Locale::detectSignAndGetDigitRange(const String& input, bool& isNegative, unsigned& startIndex, unsigned& endIndex)
315 endIndex = input.length();
316 if (m_negativePrefix.isEmpty() && m_negativeSuffix.isEmpty()) {
317 if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
319 startIndex = m_positivePrefix.length();
320 endIndex -= m_positiveSuffix.length();
325 if (input.startsWith(m_negativePrefix) && input.endsWith(m_negativeSuffix)) {
327 startIndex = m_negativePrefix.length();
328 endIndex -= m_negativeSuffix.length();
331 if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
332 startIndex = m_positivePrefix.length();
333 endIndex -= m_positiveSuffix.length();
342 unsigned Locale::matchedDecimalSymbolIndex(const String& input, unsigned& position)
344 for (unsigned symbolIndex = 0; symbolIndex < DecimalSymbolsSize; ++symbolIndex) {
345 if (m_decimalSymbols[symbolIndex].length() && matches(input, position, m_decimalSymbols[symbolIndex])) {
346 position += m_decimalSymbols[symbolIndex].length();
350 return DecimalSymbolsSize;
353 String Locale::convertFromLocalizedNumber(const String& localized)
355 initializeLocaleData();
356 String input = localized.removeCharacters(isASCIISpace);
357 if (!m_hasLocaleData || input.isEmpty())
363 if (!detectSignAndGetDigitRange(input, isNegative, startIndex, endIndex))
366 StringBuilder builder;
367 builder.reserveCapacity(input.length());
370 for (unsigned i = startIndex; i < endIndex;) {
371 unsigned symbolIndex = matchedDecimalSymbolIndex(input, i);
372 if (symbolIndex >= DecimalSymbolsSize)
374 if (symbolIndex == DecimalSeparatorIndex)
376 else if (symbolIndex == GroupSeparatorIndex)
379 builder.append(static_cast<UChar>('0' + symbolIndex));
381 return builder.toString();
384 #if ENABLE(INPUT_MULTIPLE_FIELDS_UI)
385 String Locale::localizedDecimalSeparator()
387 initializeLocaleData();
388 return m_decimalSymbols[DecimalSeparatorIndex];
392 String Locale::formatDateTime(const DateComponents& date, FormatType formatType)
394 if (date.type() == DateComponents::Invalid)
397 DateTimeStringBuilder builder(*this, date);
398 switch (date.type()) {
399 case DateComponents::Time:
400 builder.build(formatType == FormatTypeShort ? shortTimeFormat() : timeFormat());
402 case DateComponents::Date:
403 builder.build(dateFormat());
405 case DateComponents::Month:
406 builder.build(formatType == FormatTypeShort ? shortMonthFormat() : monthFormat());
408 case DateComponents::Week:
409 builder.build(weekFormatInLDML());
411 case DateComponents::DateTime:
412 case DateComponents::DateTimeLocal:
413 builder.build(formatType == FormatTypeShort ? dateTimeFormatWithoutSeconds() : dateTimeFormatWithSeconds());
415 case DateComponents::Invalid:
416 ASSERT_NOT_REACHED();
419 return builder.toString();