| OLD | NEW |
| (Empty) |
| 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 blink { | |
| 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 DateCompon
ents& 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.mon
th()]); | |
| 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(zeroPaddedSeco
ndString)); | |
| 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& paramete
r) | |
| 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& paramete
r1, const String& parameter2) | |
| 206 { | |
| 207 // FIXME: Returns a string locazlied for this locale. | |
| 208 return Platform::current()->queryLocalizedString(name, parameter1, parameter
2); | |
| 209 } | |
| 210 | |
| 211 String Locale::validationMessageTooLongText(unsigned valueLength, int maxLength) | |
| 212 { | |
| 213 return queryString(WebLocalizedString::ValidationTooLong, convertToLocalized
Number(String::number(valueLength)), convertToLocalizedNumber(String::number(max
Length))); | |
| 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(literalSta
rt, 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, leng
th - literalStart), builder); | |
| 234 return builder.toString(); | |
| 235 } | |
| 236 | |
| 237 void Locale::setLocaleData(const Vector<String, DecimalSymbolsSize>& symbols, co
nst String& positivePrefix, const String& positiveSuffix, const String& negative
Prefix, 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_nega
tivePrefix.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, u
nsigned& 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_positiveSuffi
x)) { | |
| 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_negativeSuffi
x)) { | |
| 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_positiveS
uffix)) { | |
| 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& positi
on) | |
| 341 { | |
| 342 for (unsigned symbolIndex = 0; symbolIndex < DecimalSymbolsSize; ++symbolInd
ex) { | |
| 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.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 String Locale::formatDateTime(const DateComponents& date, FormatType formatType) | |
| 383 { | |
| 384 if (date.type() == DateComponents::Invalid) | |
| 385 return String(); | |
| 386 | |
| 387 DateTimeStringBuilder builder(*this, date); | |
| 388 switch (date.type()) { | |
| 389 case DateComponents::Time: | |
| 390 builder.build(formatType == FormatTypeShort ? shortTimeFormat() : timeFo
rmat()); | |
| 391 break; | |
| 392 case DateComponents::Date: | |
| 393 builder.build(dateFormat()); | |
| 394 break; | |
| 395 case DateComponents::Month: | |
| 396 builder.build(formatType == FormatTypeShort ? shortMonthFormat() : month
Format()); | |
| 397 break; | |
| 398 case DateComponents::Week: | |
| 399 builder.build(weekFormatInLDML()); | |
| 400 break; | |
| 401 case DateComponents::DateTime: | |
| 402 case DateComponents::DateTimeLocal: | |
| 403 builder.build(formatType == FormatTypeShort ? dateTimeFormatWithoutSecon
ds() : dateTimeFormatWithSeconds()); | |
| 404 break; | |
| 405 case DateComponents::Invalid: | |
| 406 ASSERT_NOT_REACHED(); | |
| 407 break; | |
| 408 } | |
| 409 return builder.toString(); | |
| 410 } | |
| 411 | |
| 412 } | |
| OLD | NEW |