• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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