blob: 4d75ea0be29af4ed00f7bf618c5a2a97b7a0aa6e [file] [log] [blame]
/*
* Copyright (C) 2011,2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "PlatformLocale.h"
#include "DateTimeFormat.h"
#include "LocalizedStrings.h"
#include <wtf/text/StringBuilder.h>
namespace WebCore {
#if ENABLE(DATE_AND_TIME_INPUT_TYPES)
class DateTimeStringBuilder : private DateTimeFormat::TokenHandler {
WTF_MAKE_NONCOPYABLE(DateTimeStringBuilder);
public:
// The argument objects must be alive until this object dies.
DateTimeStringBuilder(Locale&, const DateComponents&);
bool build(const String&);
String toString();
private:
// DateTimeFormat::TokenHandler functions.
void visitField(DateTimeFormat::FieldType, int) final;
void visitLiteral(const String&) final;
String zeroPadString(const String&, size_t width);
void appendNumber(int number, size_t width);
StringBuilder m_builder;
Locale& m_localizer;
const DateComponents& m_date;
};
DateTimeStringBuilder::DateTimeStringBuilder(Locale& localizer, const DateComponents& date)
: m_localizer(localizer)
, m_date(date)
{
}
bool DateTimeStringBuilder::build(const String& formatString)
{
m_builder.reserveCapacity(formatString.length());
return DateTimeFormat::parse(formatString, *this);
}
String DateTimeStringBuilder::zeroPadString(const String& string, size_t width)
{
if (string.length() >= width)
return string;
StringBuilder zeroPaddedStringBuilder;
zeroPaddedStringBuilder.reserveCapacity(width);
for (size_t i = string.length(); i < width; ++i)
zeroPaddedStringBuilder.append('0');
zeroPaddedStringBuilder.append(string);
return zeroPaddedStringBuilder.toString();
}
void DateTimeStringBuilder::appendNumber(int number, size_t width)
{
String zeroPaddedNumberString = zeroPadString(String::number(number), width);
m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedNumberString));
}
void DateTimeStringBuilder::visitField(DateTimeFormat::FieldType fieldType, int numberOfPatternCharacters)
{
switch (fieldType) {
case DateTimeFormat::FieldTypeYear:
// Always use padding width of 4 so it matches DateTimeEditElement.
appendNumber(m_date.fullYear(), 4);
return;
case DateTimeFormat::FieldTypeMonth:
if (numberOfPatternCharacters == 3)
m_builder.append(m_localizer.shortMonthLabels()[m_date.month()]);
else if (numberOfPatternCharacters == 4)
m_builder.append(m_localizer.monthLabels()[m_date.month()]);
else {
// Always use padding width of 2 so it matches DateTimeEditElement.
appendNumber(m_date.month() + 1, 2);
}
return;
case DateTimeFormat::FieldTypeMonthStandAlone:
if (numberOfPatternCharacters == 3)
m_builder.append(m_localizer.shortStandAloneMonthLabels()[m_date.month()]);
else if (numberOfPatternCharacters == 4)
m_builder.append(m_localizer.standAloneMonthLabels()[m_date.month()]);
else {
// Always use padding width of 2 so it matches DateTimeEditElement.
appendNumber(m_date.month() + 1, 2);
}
return;
case DateTimeFormat::FieldTypeDayOfMonth:
// Always use padding width of 2 so it matches DateTimeEditElement.
appendNumber(m_date.monthDay(), 2);
return;
case DateTimeFormat::FieldTypeWeekOfYear:
// Always use padding width of 2 so it matches DateTimeEditElement.
appendNumber(m_date.week(), 2);
return;
case DateTimeFormat::FieldTypePeriod:
m_builder.append(m_localizer.timeAMPMLabels()[(m_date.hour() >= 12 ? 1 : 0)]);
return;
case DateTimeFormat::FieldTypeHour12: {
int hour12 = m_date.hour() % 12;
if (!hour12)
hour12 = 12;
appendNumber(hour12, numberOfPatternCharacters);
return;
}
case DateTimeFormat::FieldTypeHour23:
appendNumber(m_date.hour(), numberOfPatternCharacters);
return;
case DateTimeFormat::FieldTypeHour11:
appendNumber(m_date.hour() % 12, numberOfPatternCharacters);
return;
case DateTimeFormat::FieldTypeHour24: {
int hour24 = m_date.hour();
if (!hour24)
hour24 = 24;
appendNumber(hour24, numberOfPatternCharacters);
return;
}
case DateTimeFormat::FieldTypeMinute:
appendNumber(m_date.minute(), numberOfPatternCharacters);
return;
case DateTimeFormat::FieldTypeSecond:
if (!m_date.millisecond())
appendNumber(m_date.second(), numberOfPatternCharacters);
else {
double second = m_date.second() + m_date.millisecond() / 1000.0;
String zeroPaddedSecondString = zeroPadString(String::numberToStringFixedWidth(second, 3), numberOfPatternCharacters + 4);
m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedSecondString));
}
return;
default:
return;
}
}
void DateTimeStringBuilder::visitLiteral(const String& text)
{
ASSERT(text.length());
m_builder.append(text);
}
String DateTimeStringBuilder::toString()
{
return m_builder.toString();
}
#endif
Locale::~Locale() = default;
void Locale::setLocaleData(const Vector<String, DecimalSymbolsSize>& symbols, const String& positivePrefix, const String& positiveSuffix, const String& negativePrefix, const String& negativeSuffix)
{
for (size_t i = 0; i < symbols.size(); ++i) {
ASSERT(!symbols[i].isEmpty());
m_decimalSymbols[i] = symbols[i];
}
m_positivePrefix = positivePrefix;
m_positiveSuffix = positiveSuffix;
m_negativePrefix = negativePrefix;
m_negativeSuffix = negativeSuffix;
ASSERT(!m_positivePrefix.isEmpty() || !m_positiveSuffix.isEmpty() || !m_negativePrefix.isEmpty() || !m_negativeSuffix.isEmpty());
m_hasLocaleData = true;
}
String Locale::convertToLocalizedNumber(const String& input)
{
initializeLocaleData();
if (!m_hasLocaleData || input.isEmpty())
return input;
unsigned i = 0;
bool isNegative = false;
StringBuilder builder;
builder.reserveCapacity(input.length());
if (input[0] == '-') {
++i;
isNegative = true;
builder.append(m_negativePrefix);
} else
builder.append(m_positivePrefix);
for (; i < input.length(); ++i) {
switch (input[i]) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
builder.append(m_decimalSymbols[input[i] - '0']);
break;
case '.':
builder.append(m_decimalSymbols[DecimalSeparatorIndex]);
break;
default:
ASSERT_NOT_REACHED();
}
}
builder.append(isNegative ? m_negativeSuffix : m_positiveSuffix);
return builder.toString();
}
static bool matches(const String& text, unsigned position, const String& part)
{
if (part.isEmpty())
return true;
if (position + part.length() > text.length())
return false;
for (unsigned i = 0; i < part.length(); ++i) {
if (text[position + i] != part[i])
return false;
}
return true;
}
bool Locale::detectSignAndGetDigitRange(const String& input, bool& isNegative, unsigned& startIndex, unsigned& endIndex)
{
startIndex = 0;
endIndex = input.length();
if (m_negativePrefix.isEmpty() && m_negativeSuffix.isEmpty()) {
if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
isNegative = false;
startIndex = m_positivePrefix.length();
endIndex -= m_positiveSuffix.length();
} else
isNegative = true;
} else {
if (input.startsWith(m_negativePrefix) && input.endsWith(m_negativeSuffix)) {
isNegative = true;
startIndex = m_negativePrefix.length();
endIndex -= m_negativeSuffix.length();
} else {
isNegative = false;
if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
startIndex = m_positivePrefix.length();
endIndex -= m_positiveSuffix.length();
} else
return false;
}
}
return true;
}
unsigned Locale::matchedDecimalSymbolIndex(const String& input, unsigned& position)
{
for (unsigned symbolIndex = 0; symbolIndex < DecimalSymbolsSize; ++symbolIndex) {
if (m_decimalSymbols[symbolIndex].length() && matches(input, position, m_decimalSymbols[symbolIndex])) {
position += m_decimalSymbols[symbolIndex].length();
return symbolIndex;
}
}
return DecimalSymbolsSize;
}
String Locale::convertFromLocalizedNumber(const String& localized)
{
initializeLocaleData();
String input = localized.stripWhiteSpace();
if (!m_hasLocaleData || input.isEmpty())
return input;
bool isNegative;
unsigned startIndex;
unsigned endIndex;
if (!detectSignAndGetDigitRange(input, isNegative, startIndex, endIndex))
return input;
StringBuilder builder;
builder.reserveCapacity(input.length());
if (isNegative)
builder.append('-');
for (unsigned i = startIndex; i < endIndex;) {
unsigned symbolIndex = matchedDecimalSymbolIndex(input, i);
if (symbolIndex >= DecimalSymbolsSize)
return input;
if (symbolIndex == DecimalSeparatorIndex)
builder.append('.');
else if (symbolIndex == GroupSeparatorIndex)
return input;
else
builder.append(static_cast<UChar>('0' + symbolIndex));
}
return builder.toString();
}
#if ENABLE(DATE_AND_TIME_INPUT_TYPES)
#if !PLATFORM(IOS_FAMILY)
String Locale::formatDateTime(const DateComponents& date, FormatType formatType)
{
if (date.type() == DateComponents::Invalid)
return String();
#if !ENABLE(INPUT_TYPE_WEEK)
if (date.type() == DateComponents::Week)
return String();
#endif
DateTimeStringBuilder builder(*this, date);
switch (date.type()) {
case DateComponents::Time:
builder.build(formatType == FormatTypeShort ? shortTimeFormat() : timeFormat());
break;
case DateComponents::Date:
builder.build(dateFormat());
break;
case DateComponents::Month:
builder.build(formatType == FormatTypeShort ? shortMonthFormat() : monthFormat());
break;
case DateComponents::Week:
#if ENABLE(INPUT_TYPE_WEEK)
builder.build(weekFormatInLDML());
break;
#endif
case DateComponents::DateTime:
case DateComponents::DateTimeLocal:
builder.build(formatType == FormatTypeShort ? dateTimeFormatWithoutSeconds() : dateTimeFormatWithSeconds());
break;
case DateComponents::Invalid:
ASSERT_NOT_REACHED();
break;
}
return builder.toString();
}
#endif // !PLATFORM(IOS_FAMILY)
#endif
}