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
39namespace blink {
40
41using blink::Platform;
42using blink::WebLocalizedString;
43
44class DateTimeStringBuilder : private DateTimeFormat::TokenHandler {
45    WTF_MAKE_NONCOPYABLE(DateTimeStringBuilder);
46public:
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
53private:
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
66DateTimeStringBuilder::DateTimeStringBuilder(Locale& localizer, const DateComponents& date)
67    : m_localizer(localizer)
68    , m_date(date)
69{
70}
71
72bool DateTimeStringBuilder::build(const String& formatString)
73{
74    m_builder.reserveCapacity(formatString.length());
75    return DateTimeFormat::parse(formatString, *this);
76}
77
78String 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
90void 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
96void 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
171void DateTimeStringBuilder::visitLiteral(const String& text)
172{
173    ASSERT(text.length());
174    m_builder.append(text);
175}
176
177String DateTimeStringBuilder::toString()
178{
179    return m_builder.toString();
180}
181
182Locale& Locale::defaultLocale()
183{
184    static Locale* locale = Locale::create(defaultLanguage()).leakPtr();
185    ASSERT(isMainThread());
186    return *locale;
187}
188
189Locale::~Locale()
190{
191}
192
193String Locale::queryString(WebLocalizedString::Name name)
194{
195    // FIXME: Returns a string locazlied for this locale.
196    return Platform::current()->queryLocalizedString(name);
197}
198
199String 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
205String 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
211String Locale::validationMessageTooLongText(unsigned valueLength, int maxLength)
212{
213    return queryString(WebLocalizedString::ValidationTooLong, convertToLocalizedNumber(String::number(valueLength)), convertToLocalizedNumber(String::number(maxLength)));
214}
215
216String 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
237void 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
251String 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
297static 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
310bool 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
340unsigned 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
351String 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)
383String Locale::localizedDecimalSeparator()
384{
385    initializeLocaleData();
386    return m_decimalSymbols[DecimalSeparatorIndex];
387}
388#endif
389
390String 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