/* * 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 { // HTML5 uses ISO-8601 format with year >= 1. Gregorian calendar started in // 1582. However, we need to support 0001-01-01 in Gregorian calendar rule. static const int minimumYear = 1; // Date in ECMAScript can't represent dates later than 275760-09-13T00:00Z. // So, we have the same upper limit in HTML5 dates. static const int maximumYear = 275760; static const int maximumMonthInMaximumYear = 8; // This is September, since months are 0 based. static const int maximumDayInMaximumMonth = 13; static const int maximumWeekInMaximumYear = 37; // The week of 275760-09-13 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; if (year < minimumYear || year > maximumYear) return false; m_year = year; end = start + digitsLength; return true; } static bool withinHTMLDateLimits(int year, int month) { if (year < minimumYear) return false; if (year < maximumYear) return true; return month <= maximumMonthInMaximumYear; } static bool withinHTMLDateLimits(int year, int month, int monthDay) { if (year < minimumYear) return false; if (year < maximumYear) return true; if (month < maximumMonthInMaximumYear) return true; return monthDay <= maximumDayInMaximumMonth; } static bool withinHTMLDateLimits(int year, int month, int monthDay, int hour, int minute, int second, int millisecond) { if (year < minimumYear) return false; if (year < maximumYear) return true; if (month < maximumMonthInMaximumYear) return true; if (monthDay < maximumDayInMaximumMonth) return true; if (monthDay > maximumDayInMaximumMonth) return false; // (year, month, monthDay) = (maximumYear, maximumMonthInMaximumYear, maximumDayInMaximumMonth) return !hour && !minute && !second && !millisecond; } 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; } maxDay = maxDayOfMonth(year, month); } } if (!withinHTMLDateLimits(year, month, day)) return false; 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 (!withinHTMLDateLimits(year, month, day)) return false; m_year = year; m_month = month; } else { if (!withinHTMLDateLimits(m_year, m_month, day)) return false; } m_monthDay = day; return true; } bool DateComponents::addMinute(int minute) { // This function is used to adjust timezone offset. So m_year, m_month, // m_monthDay have values between the lower and higher limits. ASSERT(withinHTMLDateLimits(m_year, m_month, m_monthDay)); int carry; // minute 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 { if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, minute, m_second, m_millisecond)) return false; 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 { if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, hour, minute, m_second, m_millisecond)) return false; m_minute = minute; m_hour = hour; return true; } if (!addDay(carry)) return false; if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, hour, minute, m_second, m_millisecond)) 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; if (!withinHTMLDateLimits(m_year, month)) 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; if (!withinHTMLDateLimits(m_year, m_month, day)) 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; if (m_year == maximumYear && week > maximumWeekInMaximumYear) 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; if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond)) 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; if (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond)) 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 (!withinHTMLDateLimits(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 (!withinHTMLDateLimits(m_year, m_month, m_monthDay, m_hour, m_minute, m_second, m_millisecond)) 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; if (!withinHTMLDateLimits(m_year, m_month)) 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 < minimumYear || maximumYear < doubleYear) return false; int year = static_cast<int>(doubleYear); int month = static_cast<int>(doubleMonth); if (!withinHTMLDateLimits(year, month)) 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); if (m_year < minimumYear || m_year > maximumYear) 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 <= minimumYear) return false; m_week = maxWeekNumberInYear(); } else { m_week = ((yearDay - offset) / 7) + 1; if (m_week > maxWeekNumberInYear()) { m_year++; m_week = 1; } if (m_year > maximumYear || (m_year == maximumYear && m_week > maximumWeekInMaximumYear)) return false; } 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