WebCore/html/DateComponents.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/html/DateComponents.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -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