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