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 "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 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.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 #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