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