Home | History | Annotate | Download | only in text
      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