diff options
Diffstat (limited to 'WebCore/html/DateComponents.cpp')
-rw-r--r-- | WebCore/html/DateComponents.cpp | 681 |
1 files changed, 681 insertions, 0 deletions
diff --git a/WebCore/html/DateComponents.cpp b/WebCore/html/DateComponents.cpp new file mode 100644 index 0000000..9c62d30 --- /dev/null +++ b/WebCore/html/DateComponents.cpp @@ -0,0 +1,681 @@ +/* + * Copyright (C) 2009 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 "DateComponents.h" + +#include "PlatformString.h" +#include <limits.h> +#include <wtf/ASCIICType.h> +#include <wtf/DateMath.h> +#include <wtf/MathExtras.h> + +using namespace std; + +namespace WebCore { + +// The oldest day of Gregorian Calendar is 1582-10-15. We don't support dates older than it. +static const int gregorianStartYear = 1582; +static const int gregorianStartMonth = 9; // This is October, since months are 0 based. +static const int gregorianStartDay = 15; + +static const int daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +static bool isLeapYear(int year) +{ + if (year % 4) + return false; + if (!(year % 400)) + return true; + if (!(year % 100)) + return false; + return true; +} + +// 'month' is 0-based. +static int maxDayOfMonth(int year, int month) +{ + if (month != 1) // February? + return daysInMonth[month]; + return isLeapYear(year) ? 29 : 28; +} + +// 'month' is 0-based. +static int dayOfWeek(int year, int month, int day) +{ + int shiftedMonth = month + 2; + // 2:January, 3:Feburuary, 4:March, ... + + // Zeller's congruence + if (shiftedMonth <= 3) { + shiftedMonth += 12; + year--; + } + // 4:March, ..., 14:January, 15:February + + int highYear = year / 100; + int lowYear = year % 100; + // We add 6 to make the result Sunday-origin. + int result = (day + 13 * shiftedMonth / 5 + lowYear + lowYear / 4 + highYear / 4 + 5 * highYear + 6) % 7; + return result; +} + +int DateComponents::maxWeekNumberInYear() const +{ + int day = dayOfWeek(m_year, 0, 1); // January 1. + return day == Thursday || (day == Wednesday && isLeapYear(m_year)) ? 53 : 52; +} + +static unsigned countDigits(const UChar* src, unsigned length, unsigned start) +{ + unsigned index = start; + for (; index < length; ++index) { + if (!isASCIIDigit(src[index])) + break; + } + return index - start; +} + +// Very strict integer parser. Do not allow leading or trailing whitespace unlike charactersToIntStrict(). +static bool toInt(const UChar* src, unsigned length, unsigned parseStart, unsigned parseLength, int& out) +{ + if (parseStart + parseLength > length || parseLength <= 0) + return false; + int value = 0; + const UChar* current = src + parseStart; + const UChar* end = current + parseLength; + + // We don't need to handle negative numbers for ISO 8601. + for (; current < end; ++current) { + if (!isASCIIDigit(*current)) + return false; + int digit = *current - '0'; + if (value > (INT_MAX - digit) / 10) // Check for overflow. + return false; + value = value * 10 + digit; + } + out = value; + return true; +} + +bool DateComponents::parseYear(const UChar* src, unsigned length, unsigned start, unsigned& end) +{ + unsigned digitsLength = countDigits(src, length, start); + // Needs at least 4 digits according to the standard. + if (digitsLength < 4) + return false; + int year; + if (!toInt(src, length, start, digitsLength, year)) + return false; + // No support for years before Gregorian calendar. + if (year < gregorianStartYear) + return false; + m_year = year; + end = start + digitsLength; + return true; +} + +static bool beforeGregorianStartDate(int year, int month, int monthDay) +{ + return year < gregorianStartYear + || year == gregorianStartYear && month < gregorianStartMonth + || year == gregorianStartYear && month == gregorianStartMonth && monthDay < gregorianStartDay; +} + +bool DateComponents::addDay(int dayDiff) +{ + ASSERT(m_monthDay); + + int day = m_monthDay + dayDiff; + if (day > maxDayOfMonth(m_year, m_month)) { + day = m_monthDay; + int year = m_year; + int month = m_month; + int maxDay = maxDayOfMonth(year, month); + for (; dayDiff > 0; --dayDiff) { + ++day; + if (day > maxDay) { + day = 1; + ++month; + if (month >= 12) { // month is 0-origin. + month = 0; + ++year; + if (year < 0) // Check for overflow. + return false; + } + maxDay = maxDayOfMonth(year, month); + } + } + m_year = year; + m_month = month; + } else if (day < 1) { + int month = m_month; + int year = m_year; + day = m_monthDay; + for (; dayDiff < 0; ++dayDiff) { + --day; + if (day < 1) { + --month; + if (month < 0) { + month = 11; + --year; + } + day = maxDayOfMonth(year, month); + } + if (beforeGregorianStartDate(year, month, day)) + return false; + } + m_year = year; + m_month = month; + } + m_monthDay = day; + return true; +} + +bool DateComponents::addMinute(int minute) +{ + int carry; + // min can be negative or greater than 59. + minute += m_minute; + if (minute > 59) { + carry = minute / 60; + minute = minute % 60; + } else if (m_minute < 0) { + carry = (59 - m_minute) / 60; + minute += carry * 60; + carry = -carry; + ASSERT(minute >= 0 && minute <= 59); + } else { + m_minute = minute; + return true; + } + + int hour = m_hour + carry; + if (hour > 23) { + carry = hour / 24; + hour = hour % 24; + } else if (hour < 0) { + carry = (23 - hour) / 24; + hour += carry * 24; + carry = -carry; + ASSERT(hour >= 0 && hour <= 23); + } else { + m_minute = minute; + m_hour = hour; + return true; + } + if (!addDay(carry)) + return false; + m_minute = minute; + m_hour = hour; + return true; +} + +// Parses a timezone part, and adjust year, month, monthDay, hour, minute, second, millisecond. +bool DateComponents::parseTimeZone(const UChar* src, unsigned length, unsigned start, unsigned& end) +{ + if (start >= length) + return false; + unsigned index = start; + if (src[index] == 'Z') { + end = index + 1; + return true; + } + + bool minus; + if (src[index] == '+') + minus = false; + else if (src[index] == '-') + minus = true; + else + return false; + ++index; + + int hour; + int minute; + if (!toInt(src, length, index, 2, hour) || hour < 0 || hour > 23) + return false; + index += 2; + + if (index >= length || src[index] != ':') + return false; + ++index; + + if (!toInt(src, length, index, 2, minute) || minute < 0 || minute > 59) + return false; + index += 2; + + if (minus) { + hour = -hour; + minute = -minute; + } + + // Subtract the timezone offset. + if (!addMinute(-(hour * 60 + minute))) + return false; + end = index; + return true; +} + +bool DateComponents::parseMonth(const UChar* src, unsigned length, unsigned start, unsigned& end) +{ + ASSERT(src); + unsigned index; + if (!parseYear(src, length, start, index)) + return false; + if (index >= length || src[index] != '-') + return false; + ++index; + + int month; + if (!toInt(src, length, index, 2, month) || month < 1 || month > 12) + return false; + --month; + // No support for months before Gregorian calendar. + if (beforeGregorianStartDate(m_year, month, gregorianStartDay)) + return false; + m_month = month; + end = index + 2; + m_type = Month; + return true; +} + +bool DateComponents::parseDate(const UChar* src, unsigned length, unsigned start, unsigned& end) +{ + ASSERT(src); + unsigned index; + if (!parseMonth(src, length, start, index)) + return false; + // '-' and 2-digits are needed. + if (index + 2 >= length) + return false; + if (src[index] != '-') + return false; + ++index; + + int day; + if (!toInt(src, length, index, 2, day) || day < 1 || day > maxDayOfMonth(m_year, m_month)) + return false; + // No support for dates before Gregorian calendar. + if (m_year == gregorianStartYear && m_month == gregorianStartMonth && day < gregorianStartDay) + return false; + m_monthDay = day; + end = index + 2; + m_type = Date; + return true; +} + +bool DateComponents::parseWeek(const UChar* src, unsigned length, unsigned start, unsigned& end) +{ + ASSERT(src); + unsigned index; + if (!parseYear(src, length, start, index)) + return false; + + // 4 characters ('-' 'W' digit digit) are needed. + if (index + 3 >= length) + return false; + if (src[index] != '-') + return false; + ++index; + if (src[index] != 'W') + return false; + ++index; + + int week; + if (!toInt(src, length, index, 2, week) || week < 1 || week > maxWeekNumberInYear()) + return false; + // No support for years older than or equals to Gregorian calendar start year. + if (m_year <= gregorianStartYear) + return false; + m_week = week; + end = index + 2; + m_type = Week; + return true; +} + +bool DateComponents::parseTime(const UChar* src, unsigned length, unsigned start, unsigned& end) +{ + ASSERT(src); + int hour; + if (!toInt(src, length, start, 2, hour) || hour < 0 || hour > 23) + return false; + unsigned index = start + 2; + if (index >= length) + return false; + if (src[index] != ':') + return false; + ++index; + + int minute; + if (!toInt(src, length, index, 2, minute) || minute < 0 || minute > 59) + return false; + index += 2; + + int second = 0; + int millisecond = 0; + // Optional second part. + // Do not return with false because the part is optional. + if (index + 2 < length && src[index] == ':') { + if (toInt(src, length, index + 1, 2, second) && second >= 0 && second <= 59) { + index += 3; + + // Optional fractional second part. + if (index < length && src[index] == '.') { + unsigned digitsLength = countDigits(src, length, index + 1); + if (digitsLength > 0) { + ++index; + bool ok; + if (digitsLength == 1) { + ok = toInt(src, length, index, 1, millisecond); + millisecond *= 100; + } else if (digitsLength == 2) { + ok = toInt(src, length, index, 2, millisecond); + millisecond *= 10; + } else // digitsLength >= 3 + ok = toInt(src, length, index, 3, millisecond); + ASSERT(ok); + index += digitsLength; + } + } + } + } + m_hour = hour; + m_minute = minute; + m_second = second; + m_millisecond = millisecond; + end = index; + m_type = Time; + return true; +} + +bool DateComponents::parseDateTimeLocal(const UChar* src, unsigned length, unsigned start, unsigned& end) +{ + ASSERT(src); + unsigned index; + if (!parseDate(src, length, start, index)) + return false; + if (index >= length) + return false; + if (src[index] != 'T') + return false; + ++index; + if (!parseTime(src, length, index, end)) + return false; + m_type = DateTimeLocal; + return true; +} + +bool DateComponents::parseDateTime(const UChar* src, unsigned length, unsigned start, unsigned& end) +{ + ASSERT(src); + unsigned index; + if (!parseDate(src, length, start, index)) + return false; + if (index >= length) + return false; + if (src[index] != 'T') + return false; + ++index; + if (!parseTime(src, length, index, index)) + return false; + if (!parseTimeZone(src, length, index, end)) + return false; + m_type = DateTime; + return true; +} + +static inline double positiveFmod(double value, double divider) +{ + double remainder = fmod(value, divider); + return remainder < 0 ? remainder + divider : remainder; +} + +void DateComponents::setMillisecondsSinceMidnightInternal(double msInDay) +{ + ASSERT(msInDay >= 0 && msInDay < msPerDay); + m_millisecond = static_cast<int>(fmod(msInDay, msPerSecond)); + double value = floor(msInDay / msPerSecond); + m_second = static_cast<int>(fmod(value, secondsPerMinute)); + value = floor(value / secondsPerMinute); + m_minute = static_cast<int>(fmod(value, minutesPerHour)); + m_hour = static_cast<int>(value / minutesPerHour); +} + +bool DateComponents::setMillisecondsSinceEpochForDateInternal(double ms) +{ + m_year = msToYear(ms); + int yearDay = dayInYear(ms, m_year); + m_month = monthFromDayInYear(yearDay, isLeapYear(m_year)); + m_monthDay = dayInMonthFromDayInYear(yearDay, isLeapYear(m_year)); + return true; +} + +bool DateComponents::setMillisecondsSinceEpochForDate(double ms) +{ + m_type = Invalid; + if (!isfinite(ms)) + return false; + if (!setMillisecondsSinceEpochForDateInternal(round(ms))) + return false; + if (beforeGregorianStartDate(m_year, m_month, m_monthDay)) + return false; + m_type = Date; + return true; +} + +bool DateComponents::setMillisecondsSinceEpochForDateTime(double ms) +{ + m_type = Invalid; + if (!isfinite(ms)) + return false; + ms = round(ms); + setMillisecondsSinceMidnightInternal(positiveFmod(ms, msPerDay)); + if (!setMillisecondsSinceEpochForDateInternal(ms)) + return false; + if (beforeGregorianStartDate(m_year, m_month, m_monthDay)) + return false; + m_type = DateTime; + return true; +} + +bool DateComponents::setMillisecondsSinceEpochForDateTimeLocal(double ms) +{ + // Internal representation of DateTimeLocal is the same as DateTime except m_type. + if (!setMillisecondsSinceEpochForDateTime(ms)) + return false; + m_type = DateTimeLocal; + return true; +} + +bool DateComponents::setMillisecondsSinceEpochForMonth(double ms) +{ + m_type = Invalid; + if (!isfinite(ms)) + return false; + if (!setMillisecondsSinceEpochForDateInternal(round(ms))) + return false; + // Ignore m_monthDay updated by setMillisecondsSinceEpochForDateInternal(). + if (beforeGregorianStartDate(m_year, m_month, gregorianStartDay)) + return false; + m_type = Month; + return true; +} + +bool DateComponents::setMillisecondsSinceMidnight(double ms) +{ + m_type = Invalid; + if (!isfinite(ms)) + return false; + setMillisecondsSinceMidnightInternal(positiveFmod(round(ms), msPerDay)); + m_type = Time; + return true; +} + +bool DateComponents::setMonthsSinceEpoch(double months) +{ + if (!isfinite(months)) + return false; + months = round(months); + double doubleMonth = positiveFmod(months, 12); + double doubleYear = 1970 + (months - doubleMonth) / 12; + if (doubleYear < gregorianStartYear || numeric_limits<int>::max() < doubleYear) + return false; + int year = static_cast<int>(doubleYear); + int month = static_cast<int>(doubleMonth); + if (beforeGregorianStartDate(year, month, gregorianStartDay)) + return false; + m_year = year; + m_month = month; + m_type = Month; + return true; +} + +// Offset from January 1st to Monday of the ISO 8601's first week. +// ex. If January 1st is Friday, such Monday is 3 days later. Returns 3. +static int offsetTo1stWeekStart(int year) +{ + int offsetTo1stWeekStart = 1 - dayOfWeek(year, 0, 1); + if (offsetTo1stWeekStart <= -4) + offsetTo1stWeekStart += 7; + return offsetTo1stWeekStart; +} + +bool DateComponents::setMillisecondsSinceEpochForWeek(double ms) +{ + m_type = Invalid; + if (!isfinite(ms)) + return false; + ms = round(ms); + + m_year = msToYear(ms); + // We don't support gregorianStartYear. Week numbers are undefined in that year. + if (m_year <= gregorianStartYear) + return false; + + int yearDay = dayInYear(ms, m_year); + int offset = offsetTo1stWeekStart(m_year); + if (yearDay < offset) { + // The day belongs to the last week of the previous year. + m_year--; + if (m_year <= gregorianStartYear) + return false; + m_week = maxWeekNumberInYear(); + } else { + m_week = ((yearDay - offset) / 7) + 1; + if (m_week > maxWeekNumberInYear()) { + m_year++; + m_week = 1; + } + } + m_type = Week; + return true; +} + +double DateComponents::millisecondsSinceEpochForTime() const +{ + ASSERT(m_type == Time || m_type == DateTime || m_type == DateTimeLocal); + return ((m_hour * minutesPerHour + m_minute) * secondsPerMinute + m_second) * msPerSecond + m_millisecond; +} + +double DateComponents::millisecondsSinceEpoch() const +{ + switch (m_type) { + case Date: + return dateToDaysFrom1970(m_year, m_month, m_monthDay) * msPerDay; + case DateTime: + case DateTimeLocal: + return dateToDaysFrom1970(m_year, m_month, m_monthDay) * msPerDay + millisecondsSinceEpochForTime(); + case Month: + return dateToDaysFrom1970(m_year, m_month, 1) * msPerDay; + case Time: + return millisecondsSinceEpochForTime(); + case Week: + return (dateToDaysFrom1970(m_year, 0, 1) + offsetTo1stWeekStart(m_year) + (m_week - 1) * 7) * msPerDay; + case Invalid: + break; + } + ASSERT_NOT_REACHED(); + return invalidMilliseconds(); +} + +double DateComponents::monthsSinceEpoch() const +{ + ASSERT(m_type == Month); + return (m_year - 1970) * 12 + m_month; +} + +String DateComponents::toStringForTime(SecondFormat format) const +{ + ASSERT(m_type == DateTime || m_type == DateTimeLocal || m_type == Time); + SecondFormat effectiveFormat = format; + if (m_millisecond) + effectiveFormat = Millisecond; + else if (format == None && m_second) + effectiveFormat = Second; + + switch (effectiveFormat) { + default: + ASSERT_NOT_REACHED(); + // Fallback to None. + case None: + return String::format("%02d:%02d", m_hour, m_minute); + case Second: + return String::format("%02d:%02d:%02d", m_hour, m_minute, m_second); + case Millisecond: + return String::format("%02d:%02d:%02d.%03d", m_hour, m_minute, m_second, m_millisecond); + } +} + +String DateComponents::toString(SecondFormat format) const +{ + switch (m_type) { + case Date: + return String::format("%04d-%02d-%02d", m_year, m_month + 1, m_monthDay); + case DateTime: + return String::format("%04d-%02d-%02dT", m_year, m_month + 1, m_monthDay) + + toStringForTime(format) + String("Z"); + case DateTimeLocal: + return String::format("%04d-%02d-%02dT", m_year, m_month + 1, m_monthDay) + + toStringForTime(format); + case Month: + return String::format("%04d-%02d", m_year, m_month + 1); + case Time: + return toStringForTime(format); + case Week: + return String::format("%04d-W%02d", m_year, m_week); + case Invalid: + break; + } + ASSERT_NOT_REACHED(); + return String("(Invalid DateComponents)"); +} + +} // namespace WebCore |