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