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 void visitField(DateTimeFormat::FieldType, int) final;
55 void visitLiteral(const String&) 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::numberToStringFixedWidth(second, 3), 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() = default;
184
185void Locale::setLocaleData(const Vector<String, DecimalSymbolsSize>& symbols, const String& positivePrefix, const String& positiveSuffix, const String& negativePrefix, const String& negativeSuffix)
186{
187 for (size_t i = 0; i < symbols.size(); ++i) {
188 ASSERT(!symbols[i].isEmpty());
189 m_decimalSymbols[i] = symbols[i];
190 }
191 m_positivePrefix = positivePrefix;
192 m_positiveSuffix = positiveSuffix;
193 m_negativePrefix = negativePrefix;
194 m_negativeSuffix = negativeSuffix;
195 ASSERT(!m_positivePrefix.isEmpty() || !m_positiveSuffix.isEmpty() || !m_negativePrefix.isEmpty() || !m_negativeSuffix.isEmpty());
196 m_hasLocaleData = true;
197}
198
199String Locale::convertToLocalizedNumber(const String& input)
200{
201 initializeLocaleData();
202 if (!m_hasLocaleData || input.isEmpty())
203 return input;
204
205 unsigned i = 0;
206 bool isNegative = false;
207 StringBuilder builder;
208 builder.reserveCapacity(input.length());
209
210 if (input[0] == '-') {
211 ++i;
212 isNegative = true;
213 builder.append(m_negativePrefix);
214 } else
215 builder.append(m_positivePrefix);
216
217 for (; i < input.length(); ++i) {
218 switch (input[i]) {
219 case '0':
220 case '1':
221 case '2':
222 case '3':
223 case '4':
224 case '5':
225 case '6':
226 case '7':
227 case '8':
228 case '9':
229 builder.append(m_decimalSymbols[input[i] - '0']);
230 break;
231 case '.':
232 builder.append(m_decimalSymbols[DecimalSeparatorIndex]);
233 break;
234 default:
235 ASSERT_NOT_REACHED();
236 }
237 }
238
239 builder.append(isNegative ? m_negativeSuffix : m_positiveSuffix);
240
241 return builder.toString();
242}
243
244static bool matches(const String& text, unsigned position, const String& part)
245{
246 if (part.isEmpty())
247 return true;
248 if (position + part.length() > text.length())
249 return false;
250 for (unsigned i = 0; i < part.length(); ++i) {
251 if (text[position + i] != part[i])
252 return false;
253 }
254 return true;
255}
256
257bool Locale::detectSignAndGetDigitRange(const String& input, bool& isNegative, unsigned& startIndex, unsigned& endIndex)
258{
259 startIndex = 0;
260 endIndex = input.length();
261 if (m_negativePrefix.isEmpty() && m_negativeSuffix.isEmpty()) {
262 if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
263 isNegative = false;
264 startIndex = m_positivePrefix.length();
265 endIndex -= m_positiveSuffix.length();
266 } else
267 isNegative = true;
268 } else {
269 if (input.startsWith(m_negativePrefix) && input.endsWith(m_negativeSuffix)) {
270 isNegative = true;
271 startIndex = m_negativePrefix.length();
272 endIndex -= m_negativeSuffix.length();
273 } else {
274 isNegative = false;
275 if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
276 startIndex = m_positivePrefix.length();
277 endIndex -= m_positiveSuffix.length();
278 } else
279 return false;
280 }
281 }
282 return true;
283}
284
285unsigned Locale::matchedDecimalSymbolIndex(const String& input, unsigned& position)
286{
287 for (unsigned symbolIndex = 0; symbolIndex < DecimalSymbolsSize; ++symbolIndex) {
288 if (m_decimalSymbols[symbolIndex].length() && matches(input, position, m_decimalSymbols[symbolIndex])) {
289 position += m_decimalSymbols[symbolIndex].length();
290 return symbolIndex;
291 }
292 }
293 return DecimalSymbolsSize;
294}
295
296String Locale::convertFromLocalizedNumber(const String& localized)
297{
298 initializeLocaleData();
299 String input = localized.stripWhiteSpace();
300 if (!m_hasLocaleData || input.isEmpty())
301 return input;
302
303 bool isNegative;
304 unsigned startIndex;
305 unsigned endIndex;
306 if (!detectSignAndGetDigitRange(input, isNegative, startIndex, endIndex))
307 return input;
308
309 StringBuilder builder;
310 builder.reserveCapacity(input.length());
311 if (isNegative)
312 builder.append('-');
313 for (unsigned i = startIndex; i < endIndex;) {
314 unsigned symbolIndex = matchedDecimalSymbolIndex(input, i);
315 if (symbolIndex >= DecimalSymbolsSize)
316 return input;
317 if (symbolIndex == DecimalSeparatorIndex)
318 builder.append('.');
319 else if (symbolIndex == GroupSeparatorIndex)
320 return input;
321 else
322 builder.append(static_cast<UChar>('0' + symbolIndex));
323 }
324 return builder.toString();
325}
326
327#if ENABLE(DATE_AND_TIME_INPUT_TYPES)
328
329#if !PLATFORM(IOS_FAMILY)
330String Locale::formatDateTime(const DateComponents& date, FormatType formatType)
331{
332 if (date.type() == DateComponents::Invalid)
333 return String();
334#if !ENABLE(INPUT_TYPE_WEEK)
335 if (date.type() == DateComponents::Week)
336 return String();
337#endif
338
339 DateTimeStringBuilder builder(*this, date);
340 switch (date.type()) {
341 case DateComponents::Time:
342 builder.build(formatType == FormatTypeShort ? shortTimeFormat() : timeFormat());
343 break;
344 case DateComponents::Date:
345 builder.build(dateFormat());
346 break;
347 case DateComponents::Month:
348 builder.build(formatType == FormatTypeShort ? shortMonthFormat() : monthFormat());
349 break;
350 case DateComponents::Week:
351#if ENABLE(INPUT_TYPE_WEEK)
352 builder.build(weekFormatInLDML());
353 break;
354#endif
355 case DateComponents::DateTime:
356 case DateComponents::DateTimeLocal:
357 builder.build(formatType == FormatTypeShort ? dateTimeFormatWithoutSeconds() : dateTimeFormatWithSeconds());
358 break;
359 case DateComponents::Invalid:
360 ASSERT_NOT_REACHED();
361 break;
362 }
363 return builder.toString();
364}
365#endif // !PLATFORM(IOS_FAMILY)
366
367#endif
368
369}
370