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 WebCore { 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 66 DateTimeStringBuilder::DateTimeStringBuilder(Locale& localizer, const DateComponents& date) 67 : m_localizer(localizer) 68 , m_date(date) 69 { 70 } 71 72 bool DateTimeStringBuilder::build(const String& formatString) 73 { 74 m_builder.reserveCapacity(formatString.length()); 75 return DateTimeFormat::parse(formatString, *this); 76 } 77 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 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 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 171 void DateTimeStringBuilder::visitLiteral(const String& text) 172 { 173 ASSERT(text.length()); 174 m_builder.append(text); 175 } 176 177 String DateTimeStringBuilder::toString() 178 { 179 return m_builder.toString(); 180 } 181 182 Locale& Locale::defaultLocale() 183 { 184 static Locale* locale = Locale::create(defaultLanguage()).leakPtr(); 185 ASSERT(isMainThread()); 186 return *locale; 187 } 188 189 Locale::~Locale() 190 { 191 } 192 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 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 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 211 String Locale::validationMessageTooLongText(unsigned valueLength, int maxLength) 212 { 213 return queryString(WebLocalizedString::ValidationTooLong, convertToLocalizedNumber(String::number(valueLength)), convertToLocalizedNumber(String::number(maxLength))); 214 } 215 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 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 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 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 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 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 351 String Locale::convertFromLocalizedNumber(const String& localized) 352 { 353 initializeLocaleData(); 354 String input = localized.stripWhiteSpace(); 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) 383 String Locale::localizedDecimalSeparator() 384 { 385 initializeLocaleData(); 386 return m_decimalSymbols[DecimalSeparatorIndex]; 387 } 388 #endif 389 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